Compare commits

...

363 Commits

Author SHA1 Message Date
sfeakes f8672ca354 Update aqmanager.html 2025-09-14 12:38:08 -05:00
sfeakes e818723a4c Version 2.6.11 2025-09-14 11:46:08 -05:00
sfeakes 6811ae5018
Merge pull request #456 from csharp36/fix/typos-startup-logs
Fix typos in startup logs
2025-09-07 11:22:20 -05:00
Chris Sharp 6b6c2a97bb Fix typos in startup logs 2025-09-07 11:50:40 -04:00
sfeakes fcfa203df3 Version 2.6.10 2025-08-31 17:10:09 -05:00
sfeakes be9a518ffa Version 2.6.9 2025-07-26 20:41:14 -05:00
sfeakes 71bc573f61 Release 2.6.8 2025-06-29 18:28:52 -05:00
sfeakes f95310d170 Update for 2.6.8 (Dev) 2025-06-14 15:56:27 -05:00
sfeakes 0518337472
Merge pull request #433 from locnho/pda-ps6-swg-prog-fix
Fix failure setting SWG percent
2025-06-14 15:49:40 -05:00
sfeakes de5697bb25
Merge pull request #430 from locnho/psa-ps6-freeze-prot-fix
Fix #79: Freeze protect falsely reported as off from ballle98
2025-06-14 15:34:53 -05:00
sfeakes 9d5d90937b 2.6.8 (Dev) 2025-06-14 15:32:35 -05:00
sfeakes e513f14dd6
Merge pull request #428 from locnho/pda-boost-pool-menu-fix
Fix PDA menu with 'BOOST POOL' instead 'BOOST
2025-06-14 15:30:05 -05:00
sfeakes 62943ab133 2.6.8 (Dev) 2025-06-14 13:14:11 -05:00
locnho c4cc4504d9 Fix failure setting SWG percent
At time, setting the SWG percent doesn't work. Instead, it turns off the
filter pump. Require to bring back to the HOME menu with the
first item highlighted.
Add an option to got back to PDM Home menu with goto_pda_menu.

Signed-off-by: locnho <locnhinho@gmail.com>
2025-06-08 16:18:32 -07:00
Loc Ho 9a357841aa Fix #79: Freeze protect falsely reported as off from ballle98
Signed-off-by: Loc Ho <locnhinho@gmail.com>
2025-06-08 15:48:46 -07:00
sfeakes 5b704cf3fb Version 2.6.7 2025-05-31 14:44:10 -05:00
Loc Ho f7846a8f91 Fix PDA menu with 'BOOST POOL' instead 'BOOST
Signed-off-by: Loc Ho <locnhinho@gmail.com>
2025-05-31 09:45:04 -07:00
sfeakes d7e45594b3 Version 2.6.6 2025-05-23 18:11:24 -05:00
sfeakes 94fc20191b Update install.sh 2025-05-17 16:37:53 -05:00
sfeakes c06a0037b6 Update install.sh 2025-05-17 14:55:15 -05:00
sfeakes dd7161ef08 Update install.sh 2025-05-17 14:48:10 -05:00
sfeakes d182784ae2 Update 2025-05-17 14:15:16 -05:00
sfeakes fa5ea8e85c 2.6.5 update 2025-05-10 11:02:24 -05:00
sfeakes 95f9d7c20c Update 2025-05-04 16:29:35 -05:00
sfeakes 32c292dd77 Update remote_install.sh 2025-05-04 15:32:09 -05:00
sfeakes 48574ff73e Version 2.6.4 2025-05-04 14:24:41 -05:00
sfeakes 6472e3ccd8 Version 2.6.3 2025-04-13 14:19:18 -05:00
sfeakes d789fc2cfb Update aqmanager.html 2025-04-13 14:04:56 -05:00
sfeakes 76f3adfcb9 Update aqmanager.html 2025-04-13 13:52:57 -05:00
sfeakes d23db569d0 Update 2.6.2 2025-04-13 13:42:13 -05:00
sfeakes c04ec5ce01 Update 2.6.2 2025-04-13 13:15:49 -05:00
sfeakes 47c56ee5d4 Update aqmanager.html 2025-04-13 12:17:05 -05:00
sfeakes 5fcbc2a2e9 Update README.md 2025-04-13 12:04:33 -05:00
sfeakes a410d31ad6 Updates 2.6.2 2025-04-13 12:03:42 -05:00
sfeakes 4faa2e0db0 Updates for 2.6.2 2025-04-13 12:03:02 -05:00
sfeakes a38a1c6e00 self update changes 2025-04-13 11:38:23 -05:00
sfeakes 1965a7460c Update upgrade.sh 2025-04-05 16:00:09 -05:00
sfeakes 39a8ca5684 Update upgrade.sh 2025-04-05 13:34:31 -05:00
sfeakes 411891fd88 Create upgrade.sh 2025-04-05 13:16:01 -05:00
sfeakes 766ddbc502 v2.6.1 2025-03-27 11:26:28 -05:00
sfeakes 0b3e98559e Update README.md 2025-03-23 11:58:17 -05:00
sfeakes b0ead8dfe9 Release 2.6.0 2025-03-22 16:14:14 -05:00
sfeakes 53c88deda2 2.6.0 (dev 0.3) 2025-03-15 12:46:43 -05:00
sfeakes c3a15de5ab Update for 2.6.0 dev 2025-03-14 17:34:06 -05:00
sfeakes d203927d12 Update for 2.6.0 (dev 0.2) 2025-03-14 17:26:00 -05:00
sfeakes 2e6140aa67 2.6.0 (dev) 2025-03-09 17:39:41 -05:00
sfeakes 25bcdce8ff Merge branch 'master' of github.com:sfeakes/AqualinkD 2025-01-28 17:58:14 -06:00
sfeakes b7a41968d6 Update HASSIO.Implementation.txt 2025-01-28 17:56:59 -06:00
sfeakes 7973213889 Dev update 2025-01-28 17:45:53 -06:00
sfeakes 65b845743d
Merge pull request #373 from shbatm/patch-1
Update HASSIO.Implementation.txt for Spa Mode
2024-12-23 12:36:51 -06:00
shbatm d6e55357d5
Update HASSIO.Implementation.txt for Spa Mode
Change Spa_Mode to Spa for state topic
2024-12-23 12:31:04 -06:00
sfeakes 3b79c83d02 Release v2.5.1 2024-12-07 13:15:11 -06:00
sfeakes 32f1aac02e update 2024-12-07 12:50:00 -06:00
sfeakes fd3c64b9c2 update 2024-12-07 11:52:18 -06:00
sfeakes 6ef38924ed update 2024-12-07 09:51:08 -06:00
sfeakes 765339a51f Update 2.5.1 dev 2024-12-06 09:53:44 -06:00
sfeakes 3608f9f4ae 3.5.1 Dev update 2024-12-03 17:53:50 -06:00
sfeakes 0b1b7f055f
Merge pull request #362 from shbatm/mac_fix
Use MAC address of first connected interface
2024-11-17 13:53:02 -06:00
shbatm 465534c9af Use MAC address of first connected interface 2024-11-17 13:46:35 -06:00
sfeakes 3399664ee5 2.5.1 (dev) 2024-11-17 11:35:58 -06:00
sfeakes 3f2544b1a4 Release 2.5.0 2024-11-16 09:35:39 -06:00
sfeakes 0491f2ab01
Merge pull request #360 from shbatm/hassio_discovery_patch_1
Update HA Discovery Payloads
2024-11-10 15:12:06 -06:00
sfeakes f62f7f786c 2.5.0 (Dev 0.3) 2024-11-10 15:06:29 -06:00
shbatm 1b785e0cb8 Merge branch 'hass_updates' into hassio_discovery_patch_1 2024-11-08 19:10:47 -06:00
shbatm eede6d129a Use enum sensor for service mode instead of binary
Fix quotes for enum sensor

remove value_template for battery

Reverse battery payloads
2024-11-08 19:05:57 -06:00
shbatm a972030fd0 Add battery sensor (and ignore build files)
Fix missing argument

Fix other missing argument
2024-11-08 19:05:54 -06:00
shbatm e52bc7aada Use enum sensor for service mode instead of binary
Fix quotes for enum sensor
2024-11-08 19:05:38 -06:00
shbatm e81569c3ee Add current heater/FP action to Home Assistant discovery 2024-11-08 12:26:53 -06:00
sfeakes 6bba1462a4 2.5.0 (Dev 0.2) 2024-10-20 16:34:15 -05:00
sfeakes c1675b94e3
Merge pull request #356 from Stormalong/mqtt_state_class
Add MQTT state class
2024-10-20 16:21:48 -05:00
Eric Stern 74b149afbc Merge branch 'master' into mqtt_state_class 2024-10-20 11:56:14 -07:00
Eric Stern e88f21569a add state_class to sensor so Home Assistant will treat them as statistics 2024-10-20 11:50:46 -07:00
sfeakes cbf65679bc Updates for 2.5.0 2024-10-12 17:37:26 -05:00
sfeakes 32f3d8b042 2.4.1 (Dev 0.2) 2024-09-29 19:47:04 -05:00
sfeakes d447c13777 Update 2024-09-20 16:49:41 -05:00
sfeakes e0109d738b Update 2024-09-17 17:48:06 -05:00
sfeakes e3dc3dbabb Version 2.4.1 2024-09-13 17:44:48 -05:00
sfeakes 2d717fbe74 2.4.1 2024-09-07 16:35:46 -05:00
sfeakes f4e67c8a4f Release 2.4.0 2024-09-07 11:14:44 -05:00
sfeakes e47aaeff78 update 2024-09-07 10:13:24 -05:00
sfeakes 375f66437e Update 2024-09-07 09:53:13 -05:00
sfeakes eee110b043 2.3.9 development 2024-09-06 18:32:20 -05:00
sfeakes bdbe315c10 Release 2.3.8 2024-09-03 18:43:26 -05:00
sfeakes d945d438d7 update 2024-09-01 18:47:21 -05:00
sfeakes dba4f553b7 update 2024-09-01 16:00:46 -05:00
sfeakes d4397f8f27 update 2024-09-01 12:00:42 -05:00
sfeakes 336224a66c update 2024-08-31 18:01:12 -05:00
sfeakes 47820a1ef9 Updates 2024-08-25 18:31:58 -05:00
sfeakes cb605b94a6 Updates 2024-08-16 18:02:03 -05:00
sfeakes a54f55c60b
Merge pull request #319 from Stormalong/fix_scheduler_web_api
Fix crash when trying to set schedule via web api
2024-07-04 08:45:39 -05:00
Eric Stern 3f9a5d130d fixed action_web_request passing the wrong pointer to save_schedules_js
fixed pass_mg_body segfaulting if body->len is greater than the size of buf[]
cleaned up code in save_schedules_js
2024-07-03 16:07:52 -07:00
sfeakes b684f920a3 Updates 2024-06-18 18:53:05 -05:00
sfeakes a3b8546557 Cleanup 2024-06-14 17:49:41 -05:00
sfeakes 461b204a22 Code Cleanup 2024-06-14 17:48:08 -05:00
sfeakes 60e6541d09 Refactor & Code Cleanup 2024-06-14 17:11:57 -05:00
sfeakes 40e2c38ce0 Version 2.3.7 2024-06-11 16:36:00 -05:00
sfeakes 748f6f0bfa Updates 2.3.7 2024-06-11 15:54:52 -05:00
sfeakes c5c04e30e4
Delete release/aqualinkd.test.pda.conf 2024-06-11 15:06:06 -05:00
sfeakes 3c2b67b37d Updates 2.3.7 2024-06-11 09:08:01 -05:00
sfeakes 7cd5846864
Merge pull request #312 from ballle98/dev/iaqt_page_button_name
ballle98/AqualinkD#93: iaqt_page_button.name should be char[] not *char[]
2024-06-11 09:05:09 -05:00
sfeakes 2f7c001059
Merge branch 'master' into dev/iaqt_page_button_name 2024-06-11 09:04:53 -05:00
sfeakes b85f3b5620 Update README.md 2024-06-10 17:29:32 -05:00
sfeakes b09bd63b83 Updates for 2.3.7 2024-06-10 17:27:01 -05:00
Lee Ballard 2af8f552ef ballle98/AqualinkD#93: iaqt_page_button.name should be char[] not *char[] 2024-06-10 14:52:12 -05:00
sfeakes 0520aee4a0 Update README.md 2024-05-31 19:13:42 -05:00
sfeakes decec48293 Update 2024-05-31 16:42:13 -05:00
sfeakes 09029c9202 Update aqualinkd.conf 2024-05-31 13:17:46 -05:00
sfeakes bbb5ff3c86 Update 2024-05-31 13:15:53 -05:00
sfeakes f3943043f5 update 2024-05-30 18:40:32 -05:00
sfeakes 7a27d80b73 Update 3.2.6 2024-05-30 15:46:40 -05:00
sfeakes 322924617d
Merge pull request #299 from sfeakes/Development
Create docker-compose.yml
2024-05-23 15:47:54 -05:00
sfeakes 08f23243d8 Create docker-compose.yml 2024-05-23 15:47:26 -05:00
sfeakes 10bd3a4dcb
Merge pull request #298 from sfeakes/Development
Development
2024-05-23 15:28:12 -05:00
sfeakes fdd549196e Merge branch 'Development' of github.com:sfeakes/AqualinkD into Development 2024-05-23 15:15:18 -05:00
sfeakes c70f4cea21 Update README.md 2024-05-23 14:01:37 -05:00
sfeakes f78c0f43e7 Docker update 2024-05-23 12:23:58 -05:00
sfeakes 8efb82a94f Create Dockerfile 2024-05-23 10:55:29 -05:00
sfeakes d7b1fc8d9b Update 2024-05-23 10:53:03 -05:00
sfeakes dbbd0dda0c Update v3.2.5 2024-05-22 18:57:33 -05:00
sfeakes dd72a3c8da Delete state 2024-05-22 18:05:06 -05:00
sfeakes b2d0a165b1 Update README.md 2024-05-22 18:04:02 -05:00
sfeakes 33aa440eff Delete aqualinkd 2024-05-22 17:59:42 -05:00
sfeakes 3732ec81c6 Update 2024-05-22 17:36:16 -05:00
sfeakes 767745c97d Update 2024-05-21 17:06:31 -05:00
sfeakes 675b0f52d1 Update 2024-05-13 18:45:18 -05:00
sfeakes 7c37a7cc23 update 2024-05-13 17:34:46 -05:00
sfeakes 868cb8b790 update 2024-05-09 16:07:20 -05:00
sfeakes d042fd481a Few bugs left to fix
Most of work adding aqualinktouch protocol to PDA is complete.
2024-05-05 17:29:37 -05:00
sfeakes 54c8584c3f update 2024-05-03 21:39:17 -05:00
sfeakes 1365e76628 update 2024-04-29 06:58:15 -05:00
sfeakes 0c156be8a6
Update iaqtouch.c 2024-04-25 11:01:25 -05:00
sfeakes 9697969de0 first pass 2024-04-25 07:08:24 -05:00
sfeakes df0c529261 update 2024-04-24 08:30:26 -05:00
sfeakes a033fc3fbf
Merge pull request #276 from sfeakes/Simulator-dev
Simulator dev
2024-04-21 20:06:34 -05:00
sfeakes 9d580cb3d3 Final Update for 2.3.4 2024-04-21 19:58:44 -05:00
sfeakes 6768ca5d90 Updates 2024-04-20 22:11:05 -05:00
sfeakes 69f9a5c176 Update for simulators 2024-04-19 20:18:31 -05:00
sfeakes 61a09820b6 Update 2024-04-19 14:06:00 -05:00
sfeakes c3e0e319b4
Update controller.html 2024-04-16 10:52:52 -05:00
sfeakes 22adf0642f
Update controller.html 2024-04-16 10:47:24 -05:00
sfeakes 4e4afc1071
Update aq_scheduler.c 2024-04-16 10:21:48 -05:00
sfeakes 54b44b6c70
Update aqualinkd.c 2024-04-16 10:03:18 -05:00
sfeakes 41c5f69fd6
Update Makefile 2024-04-16 09:58:50 -05:00
sfeakes 8fbdabeb54
Update net_services.c 2024-04-16 09:44:27 -05:00
sfeakes 6e5d4be956
Update Makefile 2024-04-16 08:16:48 -05:00
sfeakes 218d427b0e
Update aq_scheduler.c 2024-04-16 08:15:26 -05:00
sfeakes 921980cac6
Update aqualinkd.c 2024-04-16 07:46:37 -05:00
sfeakes 07cbff0104
Update config.c 2024-04-16 07:38:31 -05:00
sfeakes d4bc8227eb
Update config.h 2024-04-16 07:38:05 -05:00
sfeakes aac882dca9 Update net_services.c 2024-04-15 18:49:39 -05:00
sfeakes a98dcd62d3 update 2024-04-15 17:49:55 -05:00
sfeakes 17b3f853a5 Upodate 2024-04-15 17:32:08 -05:00
sfeakes 25e3e80f5a
Update config.c 2024-04-15 15:00:14 -05:00
sfeakes 052d44c404
Update config.h 2024-04-15 14:59:19 -05:00
sfeakes f5a530700f
Update aqualinkd.c 2024-04-15 14:58:19 -05:00
sfeakes 7cca4215aa Fix issues with pr249 2024-03-30 15:33:37 -05:00
sfeakes 1f8001e939 Updates 2.3.3 2024-03-30 13:17:13 -05:00
sfeakes ad8a07cebd Merge branch 'master' of github.com:sfeakes/AqualinkD 2024-03-30 13:10:25 -05:00
sfeakes 28a50be715
Merge pull request #249 from ballle98/dev/pda-swg
Fixes for PDA SWG issues
2024-03-30 13:05:48 -05:00
sfeakes 64bc1c3e5f Delete aq_panel.x.c 2024-03-30 12:40:53 -05:00
sfeakes 4aedcc722d Merge branch 'master' of github.com:sfeakes/AqualinkD 2024-03-30 12:06:02 -05:00
sfeakes ac48a5e677 Update aq_serial.c 2024-03-30 12:01:08 -05:00
sfeakes 82367eb42c Version 2.3.3
Version 2.3.3
2024-03-30 11:51:31 -05:00
sfeakes 42064d1880
Merge pull request #260 from ballle98/dev/min_frame_wait
With raspberry pi 4 aqualinkd is responding too fast and panel may reset
2024-03-30 11:33:58 -05:00
Lee Ballard 6417b59a30 ballle98/AqualinkD#87: loopover_devices :- can't goto PM_EQUIPTMENT_CONTROL menu 2023-11-14 13:33:10 -06:00
Lee Ballard a62b7fe03e fix ballle98/AqualinkD#81: Add support for boost and freeze protect to equiptment_update_cycle
compiler warning
2023-08-31 10:43:46 -05:00
Lee Ballard 79cf3e8c94 ballle98/AqualinkD#81: Add support for boost and freeze protect to equiptment_update_cycle 2023-08-28 15:17:46 -05:00
Lee Ballard 636892e494 ballle98/AqualinkD#69 JandyDvce: Ignoring set SWG device to state '0x0c', state is unknown
Signed-off-by: Lee Ballard <ballle98@gmail.com>
2023-08-28 15:13:38 -05:00
Lee Ballard de5c062eb4 ballle98/AqualinkD#65: SWG percentage setting not working for PDA 2023-08-28 15:12:54 -05:00
sfeakes 796bb3e2b0 Create aqualinkd 2023-08-06 05:51:01 -05:00
sfeakes 3ad7b71b49 update binary 2023-08-06 05:50:50 -05:00
sfeakes a97d058dda Release 2.3.2 2023-07-08 11:14:44 -05:00
sfeakes 2d0647ab8d PDA VSP update 2023-06-24 14:15:52 -05:00
sfeakes 94320139f1 Update 2023-06-24 01:19:36 -05:00
sfeakes 642bfc3383 Update 2023-06-23 19:38:59 -05:00
sfeakes 4a6c948a6a Version 2.3.2 2023-06-23 15:16:30 -05:00
sfeakes 1e1e825829
Merge pull request #174 from ballle98/dev/bullseye_warnings
ballle98/AqualinkD#58: compiler warnings seen after bullseye update
2023-06-23 15:13:44 -05:00
Lee Ballard 487685d24b ballle98/AqualinkD#75: Compiler warning is Version 2.3.0e 2023-06-23 14:38:29 -05:00
sfeakes b7ad1eac03
Merge pull request #229 from ballle98/dev/win_make
sfeakes/AqualinkD#221 windows build fix for MKDIR
2023-06-23 14:26:47 -05:00
Lee Ballard 07dc69ad60 sfeakes/AqualinkD#221 make clean broken on windows
sfeakes/AqualinkD#127 windows build - cat: /etc/os-release: No such file or directory
2023-06-23 14:18:12 -05:00
sfeakes 57978e4419
Update serial_logger.c 2023-06-21 15:20:42 -05:00
sfeakes 000ccca55f update 2023-06-20 19:29:25 -05:00
sfeakes 09e665bebc update 2023-06-20 19:02:06 -05:00
sfeakes 77b24688aa Update 2023-06-20 13:33:27 -05:00
sfeakes d86e25cd35 Quick fix 2023-06-19 22:23:22 -05:00
sfeakes 5aab2be415 RM issue 2023-06-19 21:40:26 -05:00
sfeakes 52d336d2a4 Update 2023-06-19 19:20:35 -05:00
sfeakes 78fe018e70 Release 2.3.1 2023-06-19 19:15:45 -05:00
sfeakes 7fa7b558e5
Merge pull request #150 from ballle98/dev/spa_heater_104
ballle98/AqualinkD#56: trouble setting SPA temp to 104 using openhab
2023-06-16 14:02:27 -05:00
sfeakes c1a4eef97d Added back 2023-06-07 18:15:19 -05:00
sfeakes 77c8e290d5 Quick update 2023-06-04 20:26:39 -05:00
sfeakes 1315145444 Update with new options 2023-06-04 17:04:20 -05:00
sfeakes 0780f94a2b V 2.3.0f 2023-06-04 16:17:48 -05:00
sfeakes 9f91af03ce V2.3.0e 2023-05-30 18:14:04 -05:00
sfeakes 1cb3be4ede
Merge pull request #94 from ballle98/dev/pda-set-time
ballle98/AqualinkD#13: add time sync
2023-05-29 13:20:19 -05:00
Lee Ballard ec726a7395 ballle98/AqualinkD#46: Character highlight should not update highlight index 2023-05-28 21:52:13 -05:00
Lee Ballard b789b4e3c1 ballle98/AqualinkD#13: add time sync 2023-05-28 21:51:15 -05:00
sfeakes a2a925ed96 Fixed logging issue 2023-05-22 21:12:12 -05:00
sfeakes 6649effe9d Doc update 2023-05-21 10:10:42 -05:00
sfeakes 557ad9f5fb Version 2.3.0e 2023-05-21 10:03:27 -05:00
sfeakes 4070750173 Doc change 2023-05-17 12:53:05 -05:00
sfeakes 4b8e739788 Version 2.3.0d 2023-05-17 12:51:00 -05:00
sfeakes 26d2064055 Version 2.3.0c 2023-05-16 17:27:45 -05:00
sfeakes b661ad1b76 removed merge conflicts 2023-05-16 12:42:31 -05:00
sfeakes d65201268e Version 2.3.0a 2023-05-16 12:17:21 -05:00
sfeakes aea987e4cb Updates 2023-05-16 11:17:42 -05:00
sf a146f1d171 Updates 2023-05-16 11:13:42 -05:00
sf 6f16015593 Version 2.3.0 2023-05-14 18:15:48 -05:00
sf 00b3eaabe5 Version 2.3.0 2023-05-14 17:29:59 -05:00
sf 4159143a74 Version 2.3.0 2023-05-14 17:03:35 -05:00
sf e38b8d85d3 Deleted the file from the git repository 2023-05-14 16:55:21 -05:00
sf 009b1c5ccb Deleted the file from the git repository 2023-05-14 16:50:06 -05:00
sf b867641581 Deleted the file from the git repository 2023-05-14 16:45:13 -05:00
sf 0bc6033bee Version 2.3.0 2023-05-14 16:41:22 -05:00
sf d4beb3d1d0 Version 2.3.0 2023-05-14 16:40:14 -05:00
sf 3156756927 Version 2.3.0 2023-05-14 16:35:13 -05:00
sfeakes c4d9472870
Merge pull request #156 from ballle98/dev/deb_11_bullseye_build_fail
ballle98/AqualinkD#57: Unable to make on new Pi Zero
2022-07-12 18:01:39 +01:00
Lee Ballard c5a41f0fc1 ballle98/AqualinkD#57: Unable to make on new Pi Zero 2022-02-28 09:21:29 -06:00
Lee Ballard 3c38444325 ballle98/AqualinkD#56: trouble setting SPA temp to 104 using openhab 2021-07-16 22:44:25 -05:00
sfeakes e360cbac83
Merge pull request #131 from vziukas/master
Background image url can have query params
2020-10-02 10:20:54 -05:00
Vytas 644cad9eee
Background image url can have query params 2020-10-02 10:12:46 -05:00
sfeakes 728237048e
Merge pull request #98 from ballle98/dev/overflow-web-request
ballle98/AqualinkD#37: main process exited, code=exited, status=1/FAI…
2020-09-14 16:20:18 -05:00
Lee Ballard 69de4dc1c5 ballle98/AqualinkD#37: main process exited, code=exited, status=1/FAILURE 2020-09-14 15:43:28 -05:00
sfeakes 42586e50f4
Merge pull request #120 from agprimatic/master
Update net_services.c
2020-09-13 13:57:30 -05:00
sfeakes 564269eb2f
Merge pull request #124 from rckforme/master
auto-set temperature units for pda systems
2020-09-13 13:50:54 -05:00
Robert Keller 79cf07c724 auto-set temperature units for pda systems 2020-09-12 11:34:02 -07:00
Ag Primatic 0f00067615
Update net_services.c
Fixed increment string compare starting location in action_mqtt_message()
2020-09-06 16:43:54 -04:00
sfeakes 174340657a Version 2.2.2a 2020-08-30 12:36:58 -05:00
sfeakes 7ee8a21a58 Version 2.2.2 2020-08-29 16:37:05 -05:00
sfeakes b344b08417 Version 2.2.2 2020-08-29 16:35:03 -05:00
sfeakes f3a7cb2c21 Merge branch 'master' of github.com:sfeakes/AqualinkD 2020-08-28 14:19:19 -05:00
sfeakes 0f78aa7c56 Version 2.2.1 2020-08-28 14:12:38 -05:00
sfeakes a13d626689
Merge pull request #116 from ktownsend-personal/master
HASSIO example revisions
2020-08-02 12:03:49 -05:00
ktownsend-personal b94880f3db
Update HASSIO.Implementation.txt
revised freeze protect comment
2020-08-01 01:11:48 -05:00
ktownsend-personal 87a788a175
Update HASSIO.Implementation.txt
The groups were unnecessary for my implementation. Also, the lights were moved to the switches to match the HASSIO convention that light devices are dimmable, but these are not dimmable and should be switches.
2020-08-01 01:07:25 -05:00
ktownsend-personal f15054c02c
Update HASSIO.Implementation.txt
Fixed topic typo
2020-08-01 01:04:41 -05:00
ktownsend-personal 6b685fda8e
Update HASSIO.Implementation.txt
Sync with what I ended up with on my system. Fixed some errors and added icons, device types, and delay binary sensors + delay attribute on the related switches.
2020-08-01 00:58:13 -05:00
sfeakes 6c9749f3e4 Version 2.2.0c 2020-07-26 14:28:58 -05:00
sfeakes f4b7bd91ff Version 2.2.0b 2020-07-19 16:18:29 -05:00
shaun feakes fb88438651 update 2020-07-19 12:25:24 -05:00
shaun feakes 6ca4f00359 added file 2020-07-19 12:02:14 -05:00
sfeakes a7968003ea Version 2.2.0a 2020-07-19 11:20:18 -05:00
sfeakes 36435205b2 Version 2.2.0 2020-07-18 11:37:19 -05:00
sfeakes 2ddcfa7f8a Version 2.1.0 2020-06-20 11:09:54 -05:00
shaun feakes d2c5df8e00 . 2020-06-07 09:14:12 -05:00
shaun feakes 85274f0098 V2.0.1 2020-06-07 09:13:12 -05:00
shaun feakes 50c48ef6b1 2.0.0b 2020-06-06 11:36:04 -05:00
shaun feakes 41ebef22e3 typo 2020-05-31 19:41:40 -05:00
shaun feakes fcd828c818 V2.0.0a 2020-05-31 19:35:17 -05:00
sfeakes 03a9b62ca1 v1.3.9c 2019-10-23 14:21:29 -05:00
sfeakes 098b97247c v1.3.9b 2019-10-23 13:58:17 -05:00
sfeakes c1fa2b3340 v1.3.9a 2019-10-23 10:16:00 -05:00
sfeakes f06c29a62f Version 1.3.9 2019-10-18 12:13:41 -05:00
sfeakes 435e526f4c Version 1.3.9 2019-10-18 12:07:42 -05:00
sfeakes ecfa618235 Version 1.3.9 2019-10-18 12:00:08 -05:00
sfeakes 259c7bfb43 Version 1.3.9 2019-10-18 11:53:49 -05:00
sfeakes 9da96ecc5f Version 1.3.8a 2019-10-18 08:52:20 -05:00
sfeakes 62f776fa31 Version 1.3.8 2019-10-16 17:53:09 -05:00
sfeakes 51a8b686a5 Version 1.3.7 2019-10-13 10:07:14 -05:00
sfeakes 96c81fb8be Version 1.3.6 2019-09-10 16:29:45 -05:00
sfeakes 3063a86332 . 2019-08-25 16:24:42 -05:00
sfeakes 6a45f92a00 . 2019-08-25 16:18:07 -05:00
sfeakes 1793a625ac . 2019-08-25 16:03:27 -05:00
sfeakes 15267fcb1d . 2019-08-25 15:59:38 -05:00
sfeakes e0e9be86c0 . 2019-08-25 15:58:55 -05:00
sfeakes 107fff314a Version 1.3.6 2019-08-25 15:57:51 -05:00
sfeakes ed66368ad7
Merge pull request #90 from stunney/master
Adding required header file as per open() docs
2019-08-24 16:56:32 -05:00
S. Tunney 894d0e5512
Fixing compiler warning on uninitialized local variables
msgS1 and msgS2 should be initialized to avoid warning(s) further below such as on line 1270.
2019-08-24 16:44:44 -04:00
sfeakes f63da6d6d0 . 2019-08-24 09:54:09 -05:00
sfeakes ef033c5a93 Version 1.3.5d 2019-08-24 09:49:43 -05:00
S. Tunney f3006abdee
Adding required header file as per open() docs
In compiling on a platform (onion omega) I ran into an undeclared compiler error for O_CREAT, O_WRONLY, etc.
The docs state that these are defined in <fcntl.h> per:
https://pubs.opengroup.org/onlinepubs/007908799/xsh/open.html

Adding this include allows for at least compilation out of the box for MIPS platform.  Not sure about runtime yet.
2019-08-23 19:47:03 -04:00
sfeakes 200e0cd821 Version 1.3.5c 2019-08-23 18:14:14 -05:00
shaun feakes 5f6eb03d85 Merge branch 'master' of github.com:sfeakes/AqualinkD 2019-08-22 15:06:25 -05:00
shaun feakes aae3346b92 . 2019-08-22 15:06:04 -05:00
sfeakes 118381ca18
Merge pull request #87 from ballle98/dev/pda-shift-42
ballle98/AqualinkD#42: memcpy-param-overlap in PDA Shift processing
2019-08-22 07:47:30 -05:00
Lee Ballard fab8a4d035 ballle98/AqualinkD#42: memcpy-param-overlap in PDA Shift processing 2019-08-22 07:24:15 -05:00
sfeakes e13deacf5c Version 1.3.5b 2019-08-18 15:54:10 -05:00
sfeakes 30fff5c21f Version 1.3.5a 2019-08-17 09:04:37 -05:00
shaun feakes 05ecbe97e4 HASSIO update 2019-08-11 13:23:28 -05:00
sfeakes 90b2c19d62 Version 1.3.5 2019-08-11 12:05:31 -05:00
sfeakes 303600f4df
Merge pull request #84 from ballle98/dev/faster-pda
Dev/faster pda
2019-08-09 09:36:28 -05:00
sfeakes 9616d987f5
Merge pull request #77 from ballle98/dev/mutex_cond_init
ballle98/AqualinkD#31: mutex init and cond init called multiple times
2019-08-09 09:31:23 -05:00
sfeakes 99028e65bd
Merge pull request #79 from ballle98/dev/pda-set-freeze-protect
ballle98/AqualinkD#34: set Freeze_Protect not supported with PDA mode…
2019-08-09 09:30:50 -05:00
sfeakes ab5c0b15f4
Merge pull request #76 from ballle98/dev/make-debug
ballle98/AqualinkD#30: make DEBUG=true broken
2019-08-09 09:28:13 -05:00
Lee Ballard a26b720fc6 ballle98/AqualinkD#24: Improve performance of 1.3.x PDA actions to be same or better than 1.2.x
Change-Id: Id897581ef90b217c3e0be50780b3fbc8ddf0136e
2019-08-07 16:40:32 -05:00
Lee Ballard f899da07a7 ballle98/AqualinkD#30: make DEBUG=true broken
Change-Id: I63b4a01da2fde81f5a4db48aaa1ef7cbc21c12d1
2019-08-07 14:22:15 -05:00
Lee Ballard c39423a96a ballle98/AqualinkD#34: set Freeze_Protect not supported with PDA mode control panel 2019-08-07 13:07:07 -05:00
sfeakes d125acfd86 version issue 2019-07-30 08:48:26 -05:00
Lee Ballard fee1ac7b8b ballle98/AqualinkD#31: mutex init and cond init called multiple times 2019-07-26 13:50:06 -05:00
Lee Ballard f1c1004cd8 ballle98/AqualinkD#30: make DEBUG=true broken
Change-Id: I63b4a01da2fde81f5a4db48aaa1ef7cbc21c12d1
2019-07-24 13:44:40 -05:00
sfeakes d6533885ec Version 1.3.4a 2019-07-19 15:53:07 -05:00
sfeakes b5b37c188d Version 1.3.4a 2019-07-19 14:13:21 -05:00
sfeakes 3863b53233 Version 1.3.4 2019-07-15 16:55:46 -05:00
sfeakes c2fe658d17 Version 1.3.3f 2019-07-14 09:39:15 -05:00
sfeakes c28ef1709c Version 1.3.3d 2019-07-01 09:16:00 -05:00
sfeakes bff5bbdd93 Typo 2019-06-29 11:37:32 -05:00
sfeakes b65aabaccb Version 1.3.3c 2019-06-29 09:52:41 -05:00
sfeakes ddb99ee523 PDA update 2019-06-27 18:07:28 -05:00
sfeakes ab81beed0e . 2019-06-27 17:45:55 -05:00
sfeakes a45807264f v1.3.3a 2019-06-27 17:18:44 -05:00
sfeakes fda48cc459
Merge pull request #67 from ballle98/dev/debug-active-time
logging active time of threads to assist in making them faster
2019-06-27 11:00:59 -05:00
sfeakes 63ab4f0afa
Merge pull request #71 from ballle98/dev/spa-setting-pool
ballle98/AqualinkD#23: spa heat setpoint request is setting pool
2019-06-27 09:32:01 -05:00
sfeakes 6a58565c14
Merge pull request #66 from ballle98/dev/win-make-clean
using rm -f $(wildcard *.o) instead of *.o so make clean on windows will
2019-06-27 09:31:51 -05:00
Lee Ballard bc7afd749b ballle98/AqualinkD#23: spa heat setpoint request is setting pool 2019-06-18 21:23:51 -05:00
Lee_Ballard 89d44372d3 logging active time of threads to assist in making them faster 2019-06-14 11:44:27 -05:00
Lee Ballard e54d998a02 using rm -f $(wildcard *.o) instead of *.o so make clean on windows will
work

see https://stackoverflow.com/questions/11056077/rm-f-and-makefile
2019-06-14 10:13:57 -05:00
shaun feakes 9aa1aa743f . 2019-06-09 16:42:02 -05:00
shaun feakes b4925aa2f3 . 2019-06-09 16:36:28 -05:00
shaun feakes 2302420ea4 . 2019-06-08 15:38:15 -05:00
shaun feakes cb0e8e0beb . 2019-06-08 15:01:27 -05:00
shaun feakes 07c0a27762 V1.3.3 2019-06-08 14:34:47 -05:00
shaun feakes 93d97b2008 V1.3.2c 2019-06-07 18:42:39 -05:00
shaun feakes 55bbacd4cd V 1.3.2c 2019-06-07 18:41:48 -05:00
shaun feakes 38727eb0a3 update 2019-06-07 17:54:20 -05:00
shaun feakes 26a98c159c Update 2019-06-07 16:31:06 -05:00
sfeakes 1d5000355d Updated 2019-06-05 11:45:09 -05:00
sfeakes d0e5db5d73 Updated 2019-06-05 11:41:38 -05:00
sfeakes c5b1869e77
Delete ._.DS_Store 2019-06-03 08:45:43 -05:00
shaun feakes 9a68a8161d V1.3.1 2019-05-31 18:08:45 -05:00
sfeakes 4f3fc5927c Update to serial logging 2019-05-29 08:49:08 -05:00
shaun feakes ce49ef85eb . 2019-05-25 14:13:37 -05:00
shaun feakes 1ba6dcfc16 typo 2019-05-25 14:11:52 -05:00
shaun feakes d884f2e37c Version 1.3.0 2019-05-25 11:52:36 -05:00
shaun feakes d0289b79c3 . 2019-05-18 09:23:03 -05:00
shaun feakes 51e4e633ac Updates to fix mongoose issues 2019-05-18 09:21:52 -05:00
shaun feakes b60ec3ac55 . 2019-05-17 19:50:39 -05:00
shaun feakes f25f0e6c50 Updates for 1.2.6e 2019-05-17 19:48:20 -05:00
shaun feakes 67af8df66d update 2019-03-01 10:05:15 -06:00
shaun feakes 4ddb6bba15 Fix conflicts in merge 2019-03-01 09:45:59 -06:00
sfeakes c81c924b54
Merge pull request #47 from tcm0116/master
Add service mode check
2019-03-01 09:37:24 -06:00
sfeakes 9acea4a263
Merge branch 'master' into master 2019-03-01 09:34:48 -06:00
shaun feakes 7772b1f053 Release updates 2019-03-01 09:22:03 -06:00
Thomas Moore 0be571f346 Add service mode check 2019-02-10 18:18:08 -05:00
Thomas Moore be9af7abe7 Add report_zero_pool_temp option 2019-02-10 18:18:07 -05:00
shaun feakes b8c94caa80 . 2019-01-24 19:10:42 -06:00
sfeakes 50252b5239
Merge pull request #44 from ballle98/master
ballle98/AqualinkD#6: filter pump status is going on and off when spa
2019-01-24 13:23:59 -06:00
Lee_Ballard 19a49a1ba7 ballle98/AqualinkD#6: filter pump status is going on and off when spa
mode is on

Additional fix to clean up SPA status when read from main PDA menu.  The
flashing is for filter pump and not for SPA
2019-01-24 12:53:05 -06:00
sfeakes faed1580ce Updated 2019-01-24 11:00:04 -06:00
sfeakes 6ddd64f29f Updates 2019-01-24 08:56:58 -06:00
sfeakes 5bd4776fad
Merge pull request #39 from ballle98/master
fix #1: pda "spa" label is matching "spa light"
2019-01-24 08:25:22 -06:00
sfeakes add496d781 . 2019-01-24 08:23:20 -06:00
shaun feakes 3d7ee5a89f freeze protect bug fix 2019-01-23 20:32:22 -06:00
Lee_Ballard 9cdea56423 fix #6: filter pump status is going on and off when spa mode is on 2019-01-23 19:54:11 -06:00
Lee_Ballard bda401e04d fix #1: pda "spa" label is matching "spa light"
https://github.com/ballle98/AqualinkD/issues/1
2019-01-23 19:54:11 -06:00
shaun feakes 0bf72d3f5f Update 2019-01-20 18:27:59 -06:00
sfeakes 98d6cc5fd2 . 2018-12-04 13:14:38 -06:00
sfeakes 9bb71baa46 . 2018-12-04 12:20:53 -06:00
sfeakes 6b3093d225 . 2018-12-04 12:10:28 -06:00
sfeakes 4d62667349 . 2018-12-04 12:07:20 -06:00
sfeakes 5c677a8bc4 . 2018-12-04 12:05:10 -06:00
sfeakes 81db7f06cb . 2018-12-04 12:01:41 -06:00
sfeakes e90d078a8d . 2018-12-04 12:00:42 -06:00
sfeakes 5c06053637 . 2018-12-04 11:57:19 -06:00
sfeakes 228064fef8 . 2018-12-04 11:45:09 -06:00
sfeakes 92579b09de . 2018-12-04 11:41:28 -06:00
sfeakes 286d05f8d2 . 2018-12-04 11:40:35 -06:00
sfeakes 4ba64ae35f . 2018-12-04 11:40:05 -06:00
sfeakes 7bc38d1454 . 2018-12-04 11:39:33 -06:00
sfeakes 43db1c4ba2 . 2018-12-04 11:34:39 -06:00
sfeakes 7554d57d80 . 2018-12-04 11:29:49 -06:00
sfeakes 42c332a735 . 2018-12-04 11:25:33 -06:00
sfeakes daba424365 . 2018-12-04 11:23:00 -06:00
sfeakes a4ca5f0046 Install Scripts 2018-12-04 10:58:26 -06:00
243 changed files with 53604 additions and 13906 deletions

494
Makefile
View File

@ -1,120 +1,442 @@
#
# Options
#
# make // standard build aqualinkd and serial_logger
# make debug // Compule standard aqualinkd binary just with debugging
# make aqdebug // Compile with extra aqualink debug information like timings
# make slog // Serial logger
# make <other> // not documenting
#
# define the C compiler to use
# Valid flags for AQ_FLAGS
#AQ_RS16 = true
AQ_PDA = true
#AQ_ONETOUCH = true
#AQ_IAQTOUCH = true
AQ_MANAGER = true
#AQ_RS_EXTRA_OPTS = false
#AQ_CONTAINER = false // this is for compiling for containers
#AQ_MEMCMP = true // Not implimented correctly yet.
# Turn off threadded net services
AQ_NO_THREAD_NETSERVICE = false
# define the C compiler(s) to use
CC = gcc
CC_ARM64 = aarch64-linux-gnu-gcc
CC_ARMHF = arm-linux-gnueabihf-gcc
CC_AMD64 = x86_64-linux-gnu-gcc
LIBS := -lpthread -lm
#LIBS := -lpthread -lwebsockets
#LIBS := -lpthread -lm
#LIBS := -l pthread -l m
#LIBS := -l pthread -l m -static # Take out -static, just for dev
# from documentation -lrt would be needed for glibc 2.17 & prior (debug clock realtime messages), but seems to be needed for armhf 2.24
LIBS := -lpthread -lm -lrt
# debug of not
#$DBG = -g
$DBG =
# Standard compile flags
GCCFLAGS = -Wall -O3
#GCCFLAGS = -O3
#GCCFLAGS = -Wall -O3 -Wextra
#GCCFLAGS = -Wl,--gc-sections,--print-gc-sections
#GCCFLAGS = -Wall -O3 -ffunction-sections -fdata-sections
# USe below to remove unused functions and global variables.
#LFLAGS = -Wl,--gc-sections,--print-gc-sections
#GCCFLAGS = -Wall -ffunction-sections -fdata-sections
# Standard debug flags
DGCCFLAGS = -Wall -O0 -g
# define any compile-time flags
GCCFLAGS = -Wall
# Aqualink Debug flags
#DBGFLAGS = -g -O0 -Wall -fsanitize=address -D AQ_DEBUG -D AQ_TM_DEBUG
DBGFLAGS = -g -O0 -Wall -D AQ_DEBUG -D AQ_TM_DEBUG
#CFLAGS = -Wall -g -lpthread -lwiringPi -lm -I.
#CFLAGS = -Wall -g $(LIBS) -I/usr/local/include/ -L/usr/local/lib/
#CFLAGS = -Wall -g $(LIBS) -std=gnu11 -I/nas/data/Development/Raspberry/aqualink/libwebsockets-2.0-stable/lib -L/nas/data/Development/Raspberry/aqualink/libwebsockets-2.0-stable/lib
#CFLAGS = -Wall -g $(LIBS)
#CFLAGS = -Wall -g $(LIBS) -std=gnu11
CFLAGS = $(GCCFLAGS) $(DBG) $(LIBS) -D MG_DISABLE_MD5 -D MG_DISABLE_HTTP_DIGEST_AUTH -D MG_DISABLE_MD5 -D MG_DISABLE_JSON_RPC
#CFLAGS = -Wall $(DBG) $(LIBS) -D MG_DISABLE_MQTT -D MG_DISABLE_MD5 -D MG_DISABLE_HTTP_DIGEST_AUTH -D MG_DISABLE_MD5 -D MG_DISABLE_JSON_RPC
# Mongoose flags
#MGFLAGS = -D MG_DISABLE_MD5 -D MG_DISABLE_HTTP_DIGEST_AUTH -D MG_DISABLE_MD5 -D MG_DISABLE_JSON_RPC
# Mongoose 6.18 flags
MGFLAGS = -D MG_ENABLE_HTTP_SSI=0 -D MG_ENABLE_DIRECTORY_LISTING=0 -D MG_ENABLE_HTTP_CGI=0
#MGFLAGS =
INCLUDES = -I/nas/data/Development/Raspberry/aqualink/aqualinkd
# Detect OS and set some specifics
ifeq ($(OS),Windows_NT)
# Windows Make.
MKDIR = mkdir
FixPath = $(subst /,\,$1)
else
UNAME_S := $(shell uname -s)
# Linux
ifeq ($(UNAME_S),Linux)
MKDIR = mkdir -p
FixPath = $1
# Get some system information
# PI_OS_VERSION = $(shell cat /etc/os-release | grep VERSION= | cut -d\" -f2)
# $(info OS: $(PI_OS_VERSION) )
# GLIBC_VERSION = $(shell ldd --version | grep ldd)
# $(info GLIBC build with: $(GLIBC_VERSION) )
# $(info GLIBC Prefered : 2.24-11+deb9u1 2.24 )
endif
# OSX
ifeq ($(UNAME_S),Darwin)
MKDIR = mkdir -p
FixPath = $1
endif
endif
# Add inputs and outputs from these tool invocations to the build variables
# define the C source files
SRCS = aqualinkd.c utils.c config.c aq_serial.c init_buttons.c aq_programmer.c net_services.c json_messages.c pda_menu.c mongoose.c
# Main source files
SRCS = aqualinkd.c utils.c config.c aq_serial.c aq_panel.c aq_programmer.c allbutton.c allbutton_aq_programmer.c net_services.c json_messages.c rs_msg_utils.c\
onetouch.c onetouch_aq_programmer.c iaqtouch.c iaqtouch_aq_programmer.c iaqualink.c\
devices_jandy.c packetLogger.c devices_pentair.c color_lights.c serialadapter.c aq_timer.c aq_scheduler.c web_config.c\
serial_logger.c mongoose.c hassio.c simulator.c sensors.c aq_systemutils.c timespec_subtract.c
SL_SRC = serial_logger.c aq_serial.c utils.c
PDA_SRC = pda_test.c pda_menu.c aq_serial.c utils.c
#AL_SRC = aquarite_logger.c aq_serial.c utils.c
#AR_SRC = aquarite/aquarited.c aquarite/ar_net_services.c aquarite/ar_config.c aq_serial.c utils.c mongoose.c json_messages.c config.c
OBJS = $(SRCS:.c=.o)
SL_OBJS = $(SL_SRC:.c=.o)
PDA_OBJS = $(PDA_SRC:.c=.o)
#AL_OBJS = $(AL_SRC:.c=.o)
#AR_OBJS = $(AR_SRC:.c=.o)
AQ_FLAGS =
# Add source and flags depending on protocols to support.
ifeq ($(AQ_PDA), true)
SRCS := $(SRCS) pda.c pda_menu.c pda_aq_programmer.c
AQ_FLAGS := $(AQ_FLAGS) -D AQ_PDA
endif
#ifeq ($(AQ_ONETOUCH), true)
# SRCS := $(SRCS) onetouch.c onetouch_aq_programmer.c
# AQ_FLAGS := $(AQ_FLAGS) -D AQ_ONETOUCH
#endif
#ifeq ($(AQ_IAQTOUCH), true)
# SRCS := $(SRCS) iaqtouch.c iaqtouch_aq_programmer.c iaqualink.c
# AQ_FLAGS := $(AQ_FLAGS) -D AQ_IAQTOUCH
#endif
#ifeq ($(AQ_RS16), true)
# AQ_FLAGS := $(AQ_FLAGS) -D AQ_RS16
#endif
ifeq ($(AQ_MEMCMP), true)
AQ_FLAGS := $(AQ_FLAGS) -D AQ_MEMCMP
endif
ifeq ($(AQ_MANAGER), true)
AQ_FLAGS := $(AQ_FLAGS) -D AQ_MANAGER
LIBS := $(LIBS) -lsystemd
# aq_manager requires threads, so make sure that's turned on.
ifeq ($(AQ_NO_THREAD_NETSERVICE), true)
# Show error
$(warning AQ_MANAGER requires threads, ignoring AQ_NO_THREAD_NETSERVICE)
endif
else
# No need for serial_logger without aq_manager
SRCS := $(filter-out serial_logger.c, $(SRCS))
# no threadded net service only valid without aq manager.
ifeq ($(AQ_NO_THREAD_NETSERVICE), true)
AQ_FLAGS := $(AQ_FLAGS) -D AQ_NO_THREAD_NETSERVICE
endif
endif
# Put all flags together.
CFLAGS = $(GCCFLAGS) $(AQ_FLAGS) $(MGFLAGS)
DFLAGS = $(DGCCFLAGS) $(AQ_FLAGS) $(MGFLAGS)
DBG_CFLAGS = $(DBGFLAGS) $(AQ_FLAGS) $(MGFLAGS)
# Other sources.
DBG_SRC = $(SRCS) debug_timer.c
SL_SRC = serial_logger.c aq_serial.c utils.c packetLogger.c rs_msg_utils.c timespec_subtract.c
DD_SRC = dummy_device.c aq_serial.c utils.c packetLogger.c rs_msg_utils.c timespec_subtract.c
DR_SRC = dummy_reader.c aq_serial.c utils.c packetLogger.c rs_msg_utils.c timespec_subtract.c
# Build durectories
SRC_DIR := ./source
OBJ_DIR := ./build
DBG_OBJ_DIR := $(OBJ_DIR)/debug
SL_OBJ_DIR := $(OBJ_DIR)/slog
DD_OBJ_DIR := $(OBJ_DIR)/dummydevice
DR_OBJ_DIR := $(OBJ_DIR)/dummyreader
INCLUDES := -I$(SRC_DIR)
# define path for obj files per architecture
OBJ_DIR_ARMHF := $(OBJ_DIR)/armhf
OBJ_DIR_ARM64 := $(OBJ_DIR)/arm64
OBJ_DIR_AMD64 := $(OBJ_DIR)/amd64
SL_OBJ_DIR_ARMHF := $(OBJ_DIR_ARMHF)/slog
SL_OBJ_DIR_ARM64 := $(OBJ_DIR_ARM64)/slog
SL_OBJ_DIR_AMD64 := $(OBJ_DIR_AMD64)/slog
# append path to source
SRCS := $(patsubst %.c,$(SRC_DIR)/%.c,$(SRCS))
DBG_SRC := $(patsubst %.c,$(SRC_DIR)/%.c,$(DBG_SRC))
SL_SRC := $(patsubst %.c,$(SRC_DIR)/%.c,$(SL_SRC))
DD_SRC := $(patsubst %.c,$(SRC_DIR)/%.c,$(DD_SRC))
DR_SRC := $(patsubst %.c,$(SRC_DIR)/%.c,$(DR_SRC))
# append path to obj files per architecture
OBJ_FILES := $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(SRCS))
DBG_OBJ_FILES := $(patsubst $(SRC_DIR)/%.c,$(DBG_OBJ_DIR)/%.o,$(DBG_SRC))
SL_OBJ_FILES := $(patsubst $(SRC_DIR)/%.c,$(SL_OBJ_DIR)/%.o,$(SL_SRC))
DD_OBJ_FILES := $(patsubst $(SRC_DIR)/%.c,$(DD_OBJ_DIR)/%.o,$(DD_SRC))
DR_OBJ_FILES := $(patsubst $(SRC_DIR)/%.c,$(DR_OBJ_DIR)/%.o,$(DR_SRC))
OBJ_FILES_ARMHF := $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR_ARMHF)/%.o,$(SRCS))
OBJ_FILES_ARM64 := $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR_ARM64)/%.o,$(SRCS))
OBJ_FILES_AMD64 := $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR_AMD64)/%.o,$(SRCS))
SL_OBJ_FILES_ARMHF := $(patsubst $(SRC_DIR)/%.c,$(SL_OBJ_DIR_ARMHF)/%.o,$(SL_SRC))
SL_OBJ_FILES_ARM64 := $(patsubst $(SRC_DIR)/%.c,$(SL_OBJ_DIR_ARM64)/%.o,$(SL_SRC))
SL_OBJ_FILES_AMD64 := $(patsubst $(SRC_DIR)/%.c,$(SL_OBJ_DIR_AMD64)/%.o,$(SL_SRC))
#OBJ_FILES := $(patsubst %.c,$(OBJ_DIR)/%.o,$(SRCS))
#DBG_OBJ_FILES := $(patsubst %.c,$(DBG_OBJ_DIR)/%.o,$(DBG_SRC))
#SL_OBJ_FILES := $(patsubst %.c,$(SL_OBJ_DIR)/%.o,$(SL_SRC))
#OBJ_FILES_ARMHF := $(patsubst %.c,$(OBJ_DIR_ARMHF)/%.o,$(SRCS))
#OBJ_FILES_ARM64 := $(patsubst %.c,$(OBJ_DIR_ARM64)/%.o,$(SRCS))
#OBJ_FILES_AMD64 := $(patsubst %.c,$(OBJ_DIR_AMD64)/%.o,$(SRCS))
#SL_OBJ_FILES_ARMHF := $(patsubst %.c,$(SL_OBJ_DIR_ARMHF)/%.o,$(SL_SRC))
#SL_OBJ_FILES_ARM64 := $(patsubst %.c,$(SL_OBJ_DIR_ARM64)/%.o,$(SL_SRC))
#SL_OBJ_FILES_AMD64 := $(patsubst %.c,$(SL_OBJ_DIR_AMD64)/%.o,$(SL_SRC))
#MG_OBJ_FILES := $(patsubst %.c,$(OBJ_DIR)/%.o,$(MG_SRC))
# define the executable file
MAIN = ./release/aqualinkd
SLOG = ./release/serial_logger
PDA = ./release/pda_test
#AQUARITELOG = ./release/aquarite_logger
AQUARITED = ./release/aquarited
DEBG = ./release/aqualinkd-debug
DDEVICE = ./release/dummydevice
DREADER = ./release/dummyreader
all: $(MAIN)
@echo: $(MAIN) have been compiled
MAIN_ARM64 = ./release/aqualinkd-arm64
MAIN_ARMHF = ./release/aqualinkd-armhf
MAIN_AMD64 = ./release/aqualinkd-amd64
$(MAIN): $(OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $(MAIN) $(OBJS) $(LFLAGS) $(LIBS)
SLOG_ARM64 = ./release/serial_logger-arm64
SLOG_ARMHF = ./release/serial_logger-armhf
SLOG_AMD64 = ./release/serial_logger-amd64
#LOGR = ./release/log_reader
#PLAY = ./release/aqualinkd-player
# Rules with no targets
.PHONY: clean clean-buildfiles buildrelease release install
# Default target
.DEFAULT_GOAL := all
# Before the below works, you need to build the aqualinkd-releasebin docker for compiling.
# sudo docker build -f Dockerfile.releaseBinaries -t aqualinkd-releasebin .
# Something like below
#releasebuilddocker:
# sudo docker build -f ./docker/Dockerfile.releaseBinaries -t aqualinkd-releasebin .
# $(info Docker for building release binaries has been created)
release:
sudo docker run -it --mount type=bind,source=./,target=/build aqualinkd-releasebin make buildrelease
$(info Binaries for release have been built)
quick:
sudo docker run -it --mount type=bind,source=./,target=/build aqualinkd-releasebin make quickbuild
$(info Binaries for release have been built)
debugbinaries:
sudo docker run -it --mount type=bind,source=./,target=/build aqualinkd-releasebin make debugbuild
$(info Binaries for release have been built)
runindocker:
sudo docker run -it --mount type=bind,source=./,target=/build aqualinkd-releasebin make dockerbuildnrun
$(info Binaries for release have been built)
# This is run inside container Dockerfile.releaseBinariies (aqualinkd-releasebin)
buildrelease: clean armhf arm64
$(shell cd release && ln -s ./aqualinkd-armhf ./aqualinkd && ln -s ./serial_logger-armhf ./serial_logger)
# This is run inside container Dockerfile.releaseBinariies (aqualinkd-releasebin)
quickbuild: armhf arm64
$(shell cd release && [ ! -f "./aqualinkd-armhf" ] && ln -s ./aqualinkd-armhf ./aqualinkd && ln -s ./serial_logger-armhf ./serial_logger)
# This is run inside container Dockerfile.releaseBinariies (aqualinkd-releasebin)
dockerbuildnrun: ./release/aqualinkd
$(shell ./release/aqualinkd -d -c ./realease/aqualinkd.test.conf
debugbuild: CFLAGS = $(DFLAGS)
debugbuild: armhf arm64
$(shell cd release && [ ! -f "./aqualinkd-armhf" ] && ln -s ./aqualinkd-armhf ./aqualinkd && ln -s ./serial_logger-armhf ./serial_logger)
# Rules to pass to make.
all: $(MAIN) $(SLOG)
$(info $(MAIN) has been compiled)
$(info $(SLOG) has been compiled)
slog: $(SLOG)
@echo: $(SLOG) have been compiled
$(info $(SLOG) has been compiled)
$(SLOG): $(SL_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $(SLOG) $(SL_OBJS)
aqdebug: $(DEBG)
$(info $(DEBG) has been compiled)
pda: $(PDA)
@echo: $(PDA) have been compiled
dummydevice: $(DDEVICE)
$(info $(DDEVICE) has been compiled)
$(PDA): $(PDA_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $(PDA) $(PDA_OBJS)
dummyreader: $(DREADER)
$(info $(DREADER) has been compiled)
# Container, add container flag and compile
container: CFLAGS := $(CFLAGS) -D AQ_CONTAINER
container: $(MAIN) $(SLOG)
$(info $(MAIN) has been compiled (** For Container use **))
$(info $(SLOG) has been compiled (** For Container use **))
container-arm64: CC := $(CC_ARM64)
container-arm64: container
container-amd64: CC := $(CC_AMD64)
container-amd64: container
# armhf
armhf: CC := $(CC_ARMHF)
armhf: $(MAIN_ARMHF) $(SLOG_ARMHF)
$(info $(MAIN_ARMHF) has been compiled)
$(info $(SLOG_ARMHF) has been compiled)
# arm64
arm64: CC := $(CC_ARM64)
arm64: $(MAIN_ARM64) $(SLOG_ARM64)
$(info $(MAIN_ARM64) has been compiled)
$(info $(SLOG_ARM64) has been compiled)
# amd64
amd64: CC := $(CC_AMD64)
amd64: $(MAIN_AMD64) $(SLOG_AMD64)
$(info $(MAIN_AMD64) has been compiled)
$(info $(SLOG_AMD64) has been compiled)
#debug, Just change compile flags and call MAIN
debug: CFLAGS = $(DFLAGS)
debug: $(MAIN) $(SLOG)
$(info $(MAIN) has been compiled (** DEBUG **))
$(info $(SLOG) has been compiled (** DEBUG **))
#install: $(MAIN)
install:
./release/install.sh from-make
#aquaritelog: $(AQUARITELOG)
# @echo: $(AQUARITELOG) have been compiled
# Rules to compile
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
#$(AQUARITELOG): $(AL_OBJS)
# $(CC) $(CFLAGS) $(INCLUDES) -o $(AQUARITELOG) $(AL_OBJS)
$(DBG_OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(DBG_OBJ_DIR)
$(CC) $(DBG_CFLAGS) $(INCLUDES) -c -o $@ $<
aquarited: $(AQUARITED)
@echo: $(AQUARITED) have been compiled
$(SL_OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(SL_OBJ_DIR)
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
$(AQUARITED): $(AR_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $(AQUARITED) $(AR_OBJS)
$(DD_OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(DD_OBJ_DIR)
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
# this is a suffix replacement rule for building .o's from .c's
# it uses automatic variables $<: the name of the prerequisite of
# the rule(a .c file) and $@: the name of the target of the rule (a .o file)
# (see the gnu make manual section about automatic variables)
.c.o:
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
$(DR_OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(DR_OBJ_DIR)
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
clean:
$(RM) *.o *~ $(MAIN) $(MAIN_U)
$(OBJ_DIR_ARMHF)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR_ARMHF)
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
depend: $(SRCS)
makedepend $(INCLUDES) $^
$(SL_OBJ_DIR_ARMHF)/%.o: $(SRC_DIR)/%.c | $(SL_OBJ_DIR_ARMHF)
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
$(OBJ_DIR_ARM64)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR_ARM64)
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
$(SL_OBJ_DIR_ARM64)/%.o: $(SRC_DIR)/%.c | $(SL_OBJ_DIR_ARM64)
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
$(OBJ_DIR_AMD64)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR_AMD64)
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
$(SL_OBJ_DIR_AMD64)/%.o: $(SRC_DIR)/%.c | $(SL_OBJ_DIR_AMD64)
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
# Rules to link
$(MAIN): $(OBJ_FILES)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(LIBS)
$(MAIN_ARM64): $(OBJ_FILES_ARM64)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(LIBS)
$(MAIN_ARMHF): $(OBJ_FILES_ARMHF)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(LIBS)
$(MAIN_AMD64): $(OBJ_FILES_AMD64)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(LIBS)
$(DEBG): $(DBG_OBJ_FILES)
$(CC) $(DBG_CFLAGS) $(INCLUDES) -o $@ $^ $(LIBS)
$(SLOG): CFLAGS := $(CFLAGS) -D SERIAL_LOGGER
$(SLOG): $(SL_OBJ_FILES)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(LIBS)
$(SLOG_ARMHF): CFLAGS := $(CFLAGS) -D SERIAL_LOGGER
$(SLOG_ARMHF): $(SL_OBJ_FILES_ARMHF)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(LIBS)
$(SLOG_ARM64): CFLAGS := $(CFLAGS) -D SERIAL_LOGGER
$(SLOG_ARM64): $(SL_OBJ_FILES_ARM64)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(LIBS)
$(SLOG_AMD64): CFLAGS := $(CFLAGS) -D SERIAL_LOGGER
$(SLOG_AMD64): $(SL_OBJ_FILES_AMD64)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(LIBS)
$(DDEVICE): CFLAGS := $(CFLAGS) -D SERIAL_LOGGER -D DUMMY_DEVICE
$(DDEVICE): $(DD_OBJ_FILES)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(LIBS)
$(DREADER): CFLAGS := $(CFLAGS) -D SERIAL_LOGGER -D DUMMY_DEVICE -D DUMMY_READER
$(DREADER): $(DR_OBJ_FILES)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(LIBS)
# Rules to make object directories.
$(OBJ_DIR):
$(MKDIR) $(call FixPath,$@)
$(SL_OBJ_DIR):
$(MKDIR) $(call FixPath,$@)
$(DD_OBJ_DIR):
$(MKDIR) $(call FixPath,$@)
$(DR_OBJ_DIR):
$(MKDIR) $(call FixPath,$@)
$(DBG_OBJ_DIR):
$(MKDIR) $(call FixPath,$@)
$(OBJ_DIR_ARMHF):
$(MKDIR) $(call FixPath,$@)
$(SL_OBJ_DIR_ARMHF):
$(MKDIR) $(call FixPath,$@)
$(OBJ_DIR_ARM64):
$(MKDIR) $(call FixPath,$@)
$(SL_OBJ_DIR_ARM64):
$(MKDIR) $(call FixPath,$@)
$(OBJ_DIR_AMD64):
$(MKDIR) $(call FixPath,$@)
$(SL_OBJ_DIR_AMD64):
$(MKDIR) $(call FixPath,$@)
# Clean rules
clean: clean-buildfiles
$(RM) *.o *~ $(MAIN) $(MAIN_U) $(PLAY) $(PL_EXOBJ) $(DEBG) $(DDEVICE) $(DREADER)
$(RM) $(wildcard *.o) $(wildcard *~) $(MAIN) $(MAIN_ARM64) $(MAIN_ARMHF) $(MAIN_AMD64) $(SLOG) $(DDEVICE) $(SLOG_ARM64) $(SLOG_ARMHF) $(SLOG_AMD64) $(MAIN_U) $(PLAY) $(PL_EXOBJ) $(LOGR) $(PLAY) $(DEBG)
clean-buildfiles:
$(RM) $(wildcard *.o) $(wildcard *~) $(OBJ_FILES) $(DBG_OBJ_FILES) $(SL_OBJ_FILES) $(DD_OBJ_FILES) $(DR_OBJ_FILES) $(OBJ_FILES_ARMHF) $(OBJ_FILES_ARM64) $(OBJ_FILES_AMD64) $(SL_OBJ_FILES_ARMHF) $(SL_OBJ_FILES_ARM64) $(SL_OBJ_FILES_AMD64)
install: $(MAIN)
./release/install.sh
# All Target
#all: aqualinkd
#
# Tool invocations
#aqualinkd: $(OBJS) $(USER_OBJS)
# @echo 'Building target: $@'
# @echo 'Invoking: GCC C Linker'
# gcc -L/home/perry/workspace/libwebsockets/Debug -pg -o"aqualinkd" $(OBJS) $(USER_OBJS) $(LIBS)
# @echo 'Finished building target: $@'
# @echo ' '
#
# Other Targets
#clean:
# -$(RM) $(OBJS)$(C_DEPS)$(EXECUTABLES) aqualinkd
# -@echo ' '
#
#.PHONY: all clean dependents
#.SECONDARY:
#
#-include ../makefile.targets

814
README.md
View File

@ -1,7 +1,9 @@
# Aqualinkd
linux daemon to control Aqualink RS pool controllers. Provides web UI, MQTT client & HTTP API endpoints. So you can control your pool equiptment from any phone/tablet or computer, and should work with just about Home control systems, including Apple HomeKit, Samsung, Alexa, Google, etc home hubs.
Linux daemon to control Aqualink RS pool controllers. Provides web UI, MQTT client & HTTP API endpoints. Control your pool equipment from any phone/tablet or computer. Is also compatible with most Home control systems including Apple HomeKit, Home Assistant, Samsung, Alexa, Google, etc.
<br>
Binaries are supplied for Raspberry Pi both 32 & 64 bit OS, Has been, and can be compiled for many different SBC's, and a Docker is also available.
### It does not, and will never provide any layer of security. NEVER directly expose the device running this software to the outside world, only indirectly through the use of Home Automation hub's or other securty measures, e.g. VPNs.
### It does not, and will never provide any layer of security. NEVER directly expose the device running this software to the outside world; only indirectly through the use of Home Automation hub's or other security measures. e.g. VPNs.
## Donation
@ -9,21 +11,32 @@ If you like this project, you can buy me a cup of coffee :)
<br>
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SEGN9UNS38TXJ)
## AqualinkD forum now open
http://aqualinkd.freeforums.net
(Please use this for questions / issues / problems)
For Bugs use issues link on top of page
## Please see Wiki for install instructions
https://github.com/sfeakes/AqualinkD/wiki
## AqualinkD new home
AqualinkD has grown over the years and now has multiple repositories for software / hardware. We are brining them all together under one organization. [AqualinkD home page (under construction)](https://www.aqualinkd.com) -or- [AqualinkD organization](https://github.com/aqualinkd).<br>
AqualinkD will always be open source and so will every associated repository. Nothing will change from that perspective. You will always be able to run this on cheap off the shelf hardware.
Information on Control panel versions and upgrading the chips.<br>
https://github.com/sfeakes/AqualinkD/wiki/Upgrading-Jandy-Aqualink-PDA-to-RS-panel
Started to document what I know about the Jandy RS485 protocol.<br>
https://github.com/sfeakes/AqualinkD/wiki/Jandy-Aqualink-RS485-protocol
## AqualinkD discussions
## Builtin WEB Interface(s).
* Please use github Discussions for questions (Link at top of page).
https://github.com/aqualinkd/AqualinkD/discussions
* For Bugs, please use issues link on top of page. ( please add AqualinkD version to posts )
https://github.com/aqualinkd/AqualinkD/issues
## Please see Wiki for installation instructions
https://github.com/aqualinkd/AqualinkD/wiki
<!--
For information on Control panel versions and upgrading the chips.<br>
https://github.com/aqualinkd/AqualinkD/wiki/Upgrading-Jandy-Aqualink-PDA-to-RS-panel
-->
<!--
Here's where I started to document what I know about the Jandy RS485 protocol.<br>
https://github.com/aqualinkd/AqualinkD/wiki/Jandy-Aqualink-RS485-protocol
-->
## AqualinkD built in WEB Interface(s).
<table width="100%" border="0" cellpadding="20px">
<tr><th width="50%">Default web interface</th><th wifth="50%">Simple web interface</img></th><tr>
@ -31,91 +44,488 @@ https://github.com/sfeakes/AqualinkD/wiki/Jandy-Aqualink-RS485-protocol
<tr><td colspan="2">
Both Interfaces
<ul>
<li>If you load the web page in a mobile device browser, then save to desktop an app will be created for you.</li>
<li>Order and options shown are configurable</li>
<li>If loading the web page in a mobile device browser, you will need to save to desktop where an app will be created for you.</li>
<li>The order and options shown are configurable for your individual needs and/or preferences.</li>
</ul>
</td></tr>
<tr><td colspan="2">
Default Interfaces
<ul>
<li>The layout & functionality are a from Appple HomeKit interface, only this works in any browser or mobile device.</li>
<li>Customizable tile icons & background image. (can hide any tile)</li>
<li>Thermostst, SWG & Light tiles have more options (like setting heater setpoint, light mode etc) that can be accessed with a long press</li>
<li>Support live background imags (ie poll camera for still image every X seconds)</li>
<li>The layout and functionality are from the Apple HomeKit interface. This works in any browser or on any mobile device.</li>
<li>Customizable tile icons & background images. (Tiles not used can be hidden).</li>
<li>Thermostat, Switch, SWG & Light tiles have more options (ie: setting heater temperature, timers, salt generating percentage and light mode etc). These options are accessible by pressing and holding the tile icon.</li>
<li>Supports live background images (ie: poll camera for still image every X seconds).</li>
</ul>
</td></tr>
<tr><td colspan="2">
In web browser/tablet
<ul>
<img src="extras/web_ui2.png?raw=true" width="800">
</td></tr>
</table>
### Old web UI, (Still in release if you prefer it)
<img src="extras/web_ui.png?raw=true" width="700"></img><br>
### Simulator
Designed to mimic AqualinkRS6 All Button keypad, and just like the keypad you can use it to completley configure the master control panel<br>
<img src="extras/simulator.png?raw=true" width="550">
### Simulators
Designed to mimic AqualinkRS devices, used to fully configure the master control panel<br>
<img src="extras/onetouch_sim.png?raw=true">
<img src="extras/allbutton_sim.png?raw=true">
### In Apple Home app.
<img src="extras/HomeKit2.png?raw=true" width="800"></img>
* (Salt Water Generator is configured as Thermostat as it's the closest homekit accessory type, so &deg;=% and Cooling=Generating)
* Full support for homekit scenes, so can make a "Spa scene" to turn spa on, set spa heater particular temperature, turn spa blower on, etc etc)
* (NOTE: Salt Water Generator is configured as a Thermostat. It is the closest homekit accessory type; so &deg;=% and Cooling=Generating).
* Full support for homekit scenes: ie: Create a "Spa scene" to: "turn spa on, set spa heater to X temperature and turn spa blower on", etc etc).
### In Home Assistant
<img src="extras/HomeAssistant.png?raw=true" width="600"></img>
<img src="extras/HASSIO.png?raw=true" width="800"></img>
## All Web interfaces.
* http://aqualink.ip/ <- (New UI)
* http://aqualink.ip/old <- (If you prefer the old UI, this is not maintained)
* http://aqualink.ip/simple.html <- (Anothr opion if you don't like the above)
* http://aqualink.ip/simulator.html <- (RS8 All Button Control Panel simulator)
* http://aqualink.ip/ <- (Standard WEB UI
* http://aqualink.ip/simple.html <- (Simple opion if you don't like the above)
* http://aqualink.ip/simulator.html <- (Displays all simulators in one page with tabs)
* http://aqualink.ip/aqmanager.html <- (Manage AqualinkD configuration & runtime)
* http://aqualink.ip/allbutton_sim.html <- (All Button Simulator)
* http://aqualink.ip/onetouch_sim.html <- (One Touch Simulator)
* http://aqualink.ip/aquapda_sim.html <- (PDA simulator)
#<a name="release"></a>
# ToDo (future release)
* Create iAqualink Touch Simulator
* AqualinkD to self configure. (Done for ID's, need to do for Panel type/size)
* Support for (non Jandy) external ORP and Ph sensors
<!--
* NEED TO FIX for PDA and iAQT protocol.
* Not always doing on/off
* Heaters are slow to turn on, need to hit extra button
* Spa turns on Spa Heat (first button on home page ???)
* SWG Stays on
* serial_logger
* Add wiki documentation
* about Heat vs Heater
* Panel version
* can't use iaquatouch panel / wireless
-->
# Call for Help.
* The only Jandy devices I have not decoded yet are LX heater & Chemical Feeder. If you have either of these devices and are willing to post some logs, please let me know, or post in the [Discussions area](https://github.com/aqualinkd/AqualinkD/discussions)
<!--
NEED TO FIX FOR THIS RELEASE.
* check panel version reported against config.
* pickup speed faster on iaqualinktouch after change
* with iaqualink2 no need to poll iaqtouch devices as frequently
* update documentation on how vbutton / vpump / pump_max & min / enable_iauqalink2
* check rs serial adapter is active if light color mode 11 is used.
* Check SWG messages like "#1 TruClear", see log in this post https://github.com/aqualinkd/AqualinkD/discussions/388
* Heat Pump / Chiller for OneTouch
* Update Mongoose
* PDA Crap.
* pda_aq_programmer line 702/703 color light.
* pda_aq_programmer line 782 back menu testing in pda_init
* FIX Panel name in config to accept output from panel actual name
* Move all programming threads over to using struct programmerArgs.
* Use set_allbutton_light_dimmer for all lights (ie color lights)
-->
# Updates in 2.6.11 (Sept 14 2025)
* Cleaned up exit codes.
* Fixed delay in shutting down when no data received on RS485
* Updated serial_logger to detect & overcome Jandy panel firmware issues
# Updates in 2.6.10 (Sept 1 2025)
* UI now supports UOM for external sensors
* Updates to VSP status
* Fix for Jandy Panel Rev T dimmer lights bug.
* Autoconfig changes for older panels.
# Updates in 2.6.9 (July 26 2025)
* Config fixes for 0x33 ID / PDA
* Changes to virtual buttons and light modes
* Updates to aqmanager & config editor
* Updates to jandy device logging & heatpump performance.
* Increased performance of external sensors
* Added unit of measure for External sensors
* External sensors now have rexexp support (good for onewire devices)
# Updates in 2.6.8 (June 29 2025)
* Fixed some UI bugs, added download config option
* Changes to config options & config editor
* Heatpump / chiller updates
* PDA updates (detect temperature units, other small changes)
# Updates in 2.6.7 (May 23 2025)
* Fixed bug with iaqualink protocol when no virtual buttons configured.
* Updated RS timing debug messages.
# Updates in 2.6.6 (May 23 2025)
* Fixed some HTTP response codes.
* Added checks for protocols vs panel revision.
* Fixed auto_configure for panel REV I & K.
* Updates to install scripts.
* Update to WebUI dimmers.
# Updates in 2.6.5 (May 5 2025)
* Changes to virtual buttons.
# Updates in 2.6.4 (Apr 28 2025)
* Fix docker crash where journal not configured correctly.
* Updates to config editor.
* Increased number of virtual buttons. (previous limitation only effected RS 16 panels).
* Changed to dimmable lights.
# Updates in 2.6.3 (April 13 2025)
* AqualinkD can how self-update directly from github. use `Upgrade AqualinkD` button in Aqmanager
* New install and remote_install scripts.
* Changed MQTT posting frequency when Timers are enabled for better
* Updates for new repo location. [AqualinkD organization](https://github.com/aqualinkd/AqualinkD)
* Please use the following to upgrade
* `curl -fsSL https://install.aqualinkd.com | sudo bash -s -- latest`
* -or-
* `curl -fsSL https://raw.githubusercontent.com/aqualinkd/AqualinkD/master/release/remote_install.sh | sudo bash -s -- latest`
# Updates in 2.6.1 (Mar 26 2025)
* Added External Sensors to Web UI & HomeKit.
* Added Heat Pump / Chiller Thermostat to Web UI & HomeKit.
* Fixed some bugs in Configuration Editor.
* Link device/virtual/onetouch button with SWG BOOST. (Allows you to set VSP RPM when in Boost mode)
# Updates in 2.6.0 (Mar 22 2025)
* Added configuration editor in aqmanager. [Wiki - AQManager](https://github.com/aqualinkd/AqualinkD/wiki#AQManager)
* Can now self-configure on startup. set `device_id=0xFF`
* Added scheduling of pump after events (Power On, Freeze Protect, Boost)
* Fixed HA bug for thermostats not converting to °C when HA is set to display °C.
* Added support for monitoring SBC system sensors, like CPU / GPU / Board (CPU temp being most useful).
* Reduced load on panel over AqualinkTouch protocol.
* Fixed higher than normal CPU load when leaving aqmanager open and sending no messages (leaving aqmanager open for over 14days).
* Reworked PDA sleep mode.
* Added support for Heat Pump / Chiller support.
# Updates in 2.5.1 (Dec 7 2024)
* Patch
# Updates in 2.5.0 (Nov 16 2024)
* PDA panel Rev 6.0 or newer that do not have a Jandy iAqualink device attached can use the AqualinkTouch protocol rather than PDA protocol.
* This is faster, more reliable and does not intefear with the physical PDA device (like existing implimentation)
* Please consider this very much BETA at the moment.
* use `device_id=0x33` in aqualinkd.conf.
* PDA panel Rev 6.0 of newer WITH a Jandy iAqualink device attached can use `read_RS485_iAqualink = yes` to speed up device state change detection.
* Added MQTT vsp_pump/speed/set for setting speed (RPM/GPM) by %, for automation hubs.
* Added full dimmer range support for dimmable lights (not limited to 0,25,50,75,100 anymore)
* Added vsp and dimmer to Hassio and homebridge-aqualinkd plugin as full range dimmer controls.
* Added color lights & dimmer to Hassio as selectors.
* Updated UI for support fullrange dimmer.
* cleaned up code for spa_mode and spa for newer pannels.
* Allow VSP to be asigned to virtual button.
* Fixed bug with timer not starting.
* Increase Speed of detecting device state changes.
* Added iAqualink2 protocol support.
* Chaged color light logic.
* Faster OneTouch device support.
# Updates in Release 2.4.0 (Sept 7 2024)
* <b>WARNING</b> Breaking change if you use dimmer (please change button_??_lightMode from 6 to 10)
* Fixed bugs with particular Jandy panel versions and color lights.
* Added support for more color lights, and sped up programming
* Code & Repo refactor
* Decoded more Pentair VSP pump status.
* Changed VSP pump status handling (display more in web UI).
* VSP Pump status & other attributes in HASSIO.
* Dual temperature sensors supported.
* Updates to serial_logger.
* Changes to aqmanager for adding more options for decoding protocols.
* Support for packets changes from panels REV Yg
* Support VSP by label (not pump number), REV Yg
* Added support for One Touch Buttons & Virtual Buttons.
* look at `virtual_button??_label` in aqualinkd.conf
* have to use AqualinkTouch protocol for `extended_device_id` 0x31->0x33 in aqualinkd.conf
# Updates in Release 2.3.8 (Sept 3 2024)
* Release removed. Bug with light program 0.
# Updates in Release 2.3.7 (Jun 11 2024)
* Fix for Pentair VSP losing connection & bouncing SWG to 0 and back.
* Added more VSP data (Mode, Status, Pressure Curve, both RPM & GPM) for all Pentair Pumps (VS/VF/VSF).
* Few updates to HomeAssistant integration.
* Will now convert from C to F so setting `convert_mqtt_temp_to_c` doesn't effect hassio anymore
* Added VSP support to change RPM/GPM (uses fan type since hassio doesn't support RPM, so it's a % setting or the full RPM or GPM range of your pump)
* Updates to serial_logger.
* Few updates to UI.
* Will display both RPM & GPM for VSP (space providing)
* Fix freeze protect button in UI not showing enabled.
* Few updates to AQmanager UI.
# Update in Release 2.3.6 (May 31 2024)
* No functionality changes
* Build & Docker changes
* Going forward AqualinkD will release binaries for both armhf & arm64
* armhf = any Pi (or equiv board) running 32 bit Debain based OS, stretch or newer
* arm64 = Pi3/4/2w running 64 bit Debain based OS, buster or newer
# Update in Release 2.3.5 (May 23 2024)
* Added Home Assistant integration through MQTT discover
* Please read the Home Assistant section of the [Wiki - HASSIO](https://github.com/aqualinkd/AqualinkD/wiki#HASSIO)
* There are still some enhacments to come on this.
* Included Docker into main releases
* Please read Docker section of the [Wiki - Docker](https://github.com/aqualinkd/AqualinkD/wiki#Docker)
* Added support for reading extended information for Jandy JXi heaters.
* Added Color Light to iAqualinkTouch protocol.
* Fixed issue mqtt_timed_update (1~2 min rather than between 2 & 20 min)
# Update in Release 2.3.4 (May 13 2024)
* Changes for Docker
* Updated simulator code base and added new simulators for AllButton, OneTouch & PDA.
* <aqualinkd.ip>/allbutton_sim.html
* <aqualinkd.ip>/onetouch_sim.html
* <aqualinkd.ip>/aquapda_sim.html
* On PDA only panel AqualinkD has to share the same ID with the PDA simulator. Therefore for AqualinkD will not respond to commands while simulator is active.
* Now you can completely program the control panel with the simulators removing the need to have Jandy device.
# Update in Release 2.3.3 (May 30 2024)
* Introduced Aqualink Manager UI http://aqualink.ip/aqmanager.html
* [AqualinkD Manager](https://github.com/aqualinkd/AqualinkD/wiki#AQManager)
* Moved logging into systemd/journal (journalctl) from syslog
* [AqualinkD Log](https://github.com/aqualinkd/AqualinkD/wiki#Log)
* Updated to scheduler
* [AqualinkD Scheduler](https://github.com/aqualinkd/AqualinkD/wiki#Scheduler)
* Introduced RS485 frame delay / timer.
* Improve PDA panels reliability (PDA pannels are slower than RS panels)
* Potentially fixed Pentair VSP / SWG problems since Pentair VSP use a different protocol, this will allow a timed delay for the VSP to post a status messages. Seems to only effect RS485 bus when both a Pentair VSP and Jandy SWG are present.
* Add ```rs485_frame_delay = 4``` to /etc/aqualinkd.conf, 4 is number of milliseconds between frames, 0 will turn off ie no pause.
* PDA Changes to support SWG and Boot.
# Update in Release 2.3.2 (Jul 8 2023)
* Added support for VSP on panel versions REV 0.1 & 0.2
* Can change heater sliver min/max values in web UI. `./web/config.js`
* Added reading ePump RPM/Watts directly from RS485 bus.
# Update in Release 2.3.1 (Jun 23 2023)
* Changed a lot of logic around different protocols.
* Added low latency support for FTDI usb driver.
* AqualinkD will find out the fastest way to change something depending on the protocols available.
* Added scheduler (click time in web ui). supports full calendar year (ie seasons), See wiki for details.
* Added timers for devices (ie can turn on Pump for x minutes), Long press on device in WebUI.
* Timers supported in MQTT/API.
* Support for external chem feeders can post to AqualinkD (pH & ORP shown in webUI / homekit etc)
* Add support for dimmers.
* Extended SWG status now in web UI.
* Serial logging / error checking enhancements.
* Added simulator back. (+ Improved UI).
* Fix issue with incorrect device state after duplicate MQTT messages being sent in rapid succession ( < 0.5 second).
* Found a workaround for panel firmware bug in iAqualink Touch protocol where VSP updates sometimes got lost.
* Fix bug in IntelliBrite color lights
* Install script checks for cron and it's config (needed for scheduler)
* serial-logger will now give recommended values for aqualinkd.conf
* Lock the serial port to stop duplicate process conflict.
* Lots of code cleanup & modifying ready for AqualinkD Management console in a future release.
# Update in Release 2.2.2
* Fixed some Web UI bugs
* Color lights now quicker when selecting existing color mode.
# Update in Release 2.2.1
* Supports serial adapter protocol `rssa_device_id`, (provides instant heater setpoint changes & setpoint increment)
* Can use seperate threads for network & RS485 interface. (optimisation for busy RS485 bus implimentations)
* Display messages now posted to MQTT
* Finilized all pre-repease work in 2.2.0(a,b,c)
* Optimized the USB2RS485 Serial adapter logic.
* Logging bitmasks to focus debugging information
* Serial Logger changes to test for speed / errors & busy network bus
* Changed raw RS485 device reading, to specific devices rather than all
* Change `read_all_devices` & `read_pentair_packets` to `read_RS485_swg`, `read_RS485_ePump`, `read_RS485_vsfPump`
* Since you can now target specific device, I've reverted back to displaying the real information of the device & not trying to hide it like the panel does. please see wiki config section for more details
* Can link Spa Mode and Spa Heater (web UI only). add `var link_spa_and_spa_heater = true;` to config.js
# Update in (Pre) Release 2.2.0c
* Cleaned up Makefile (and adding debug timings).
* Changed loggin infrastructure.
* Added expermental options for Pi4.
* 2.2.0a (had some issues with compiler optimisation), please don't use or compile yourself.
* Fixed RS-4 panel bug.
* Fixed some AqualinkTouch programming issues.
* Increased timeout for startup probe.
* This release WILL require you to make aqualinkd.conf changes. <b>Make sure to read wiki section https://github.com/aqualinkd/AqualinkD/wiki#Version_2</b>
* Extensive work to reduce CPU cycles and unnesessary logic.
* iAqualink Touch protocol supported for VSP & extended programming.
* This protocol is a lot faster for programming, ID's are between 0x38 & 0x3B `extended_device_id`, use Serial_logger to find valid ID.
* If your panel supports this it's strongly recomended to use it and also set `extended_device_id_programming=yes`
* New panel config procedure VERY dependant on panel type, you must change config, must set `panel_type` in config.
* Buttons (specifically) heaters will be different from previous versions. (all info in wiki link above)
* Simulator support removed for the moment.
* Few changes in RS protocol and timings.
* Fixed bug with Watts display for VSP.
* Fixed bug with colored lights.
* RS16 panels no longer require `extended_device_id` to be set
* More compile flags. See notes in wiki on compiling.
* Extensive SWG logic changes.
* AqualinkD startup changed to fix some 'systemctl restart' issues.
* More detailed API replys.
# Update in Release 2.1.0
* Big update, lots of core changes, <b>please read wiki section https://github.com/aqualinkd/AqualinkD/wiki#Version_2</b>
* Full Variable Speed Pump support. (Can read,set & change RPM,GPM)
* Full support for all Colored Lights (even if Jandy Control Panel doesn't support them)
* Chemlink pH & ORP now supported. (along with posting MQTT information)
* There are some configuration changes, make sure to read wiki (link above)
* RS12 & RS16 Panels now supported. (RS16 will also need `extended_device_id` set for full AUXB5-B8 support)
* New UI option(s) `turn_on_sensortiles = true` & `show_vsp_gpm=false` in `config.js`
* Added compile flags. If you make your own aqualinkd and have no need for PDA or RS16 support, edit the Makefile.
* Completley new API.
# Update in Release 1.3.9a
* Improved Debugging for serial.
* Added panel Timeout mode support to UI and MQTT
* Fixed SWG bug while in Service & Timeout modes
* Cleanded up SWG code and MQTT status messages for SWG and SWG/enabled
* Fixed SWG bounce when changing SWG %
# Update in Release 1.3.8
* Fixed PDA mode from 1.3.7
* Added SWG Boost to PDA
* More updates to protocol code for Jandy and Pentair.
# Update in Release 1.3.7
* PDA SUPPORT IS BROKEN IN 1.3.7 DON'T UPGRADE IF YOU'RE USING PDA Mode
* PDA Note:- Due to changes to speed up programming the control panel, PDA mode does not function correctly, I will come back and fix this, but I don't have the time for this release.
* SWG updates
* Simulator update
* Added boost functionality for SWG. (Web UI & MQTT only, not Apple homekit yet)
* MQTT boost status is aqualinkd/SWG/Boost
* MQTT boost on/off is aqualinkd/SWG/Boost/set
* Web UI, long press on SWG icon for boost & percent options
* Simple Web Ui, extra button called Boost
* Changed how programming works. (Please test fully things like, changing heater setpoints, SWG percent etc, be prepared to role back)
* Added raw RS485 logging
# Update in Release 1.3.6
* Can now debug inline from a web ui. (http://aqualinkd.ip.address/debug.html)
* Fix SWG in homekit sometimes displaying wrong value. Note to Homekit users, Upgrading to 1.3.5c (and above) will add an aditional SWG PPM tile, (look in default room). You'll need to update homebridge-aqualinkd to 0.0.8 (or later) to remove the old PPM tile (or delete you homebridge cache). This is due to a bug in homebridge-aqualinkd < 0.0.7 that didn't delete unused tiles.
* Logic for SWG RS486 checksum_errors.
* Fixed pentair packet logging, missing last byte.
* Support for two programmable lights. (Note must update your aqualinkd.conf).
* Can now display warnings and errors in the web UI (as well as log).
* Fix memory issue with PDA.
* Better support for "single device mode" on PDA.
* Fix memory leak in web UI with some browsers.
* Changes for better portability when compiling on other systems.
# Update in Release 1.3.5
* Fixed SWG bug showing off/0% every ~15 seconds (introduced in 1.3.3).
* PDA updates for freeze protect/SWG and general speed increase.
## Update in Release 1.3.4 (a)
* Logging changes.
* Fix issues in programming mode.
* Update to simulation mode.
* Changed to serial logger.
* PDA changes for SWG and Setpoints.
## Update in Release 1.3.3 (a,b,c,e,f)
* Incremental PDA fixes/enhancements.
* SWG bug fix.
## Update in Release 1.3.3
* AqualinkD will now automaticaly find a usable ID if not specifically configured.
* Support for reading (up to 4) Variable Speed Pump info and assigning per device. (Please see wiki for new config options).
* <span style="color:red">*At present only Pentair VSP is supported, if you have Jandy VSP (ePump) and are willing to do some testing, please post on forum as I'd like to get this supported as well.*</span>
* For VSP you will need to check configuration for `read_all_devices = yes` & `read_pentair_packets = yes` and assign RS485 Pump ID to Device ID in configuration. serial_logger should find ID's for you.
* WebUI will display Pump RPM only. RPM, Watts and GPH information is also available from MQTT & API.
## Update in Release 1.3.2c
* Miscellaneous bug fixes and buffer overrun (could cause core dump).
* VSP update & Pantair device support.
## Update in Release 1.3.1
* Changed the way PDA mode will sleep.
* Added preliminary support for Variable Speed Pumps. (Many limitations on support).
* Added int status to Web API.
## Update in Release 1.3.0
* Large update for PDA only control panels (Majority of this is ballle98 work)
* Can distinguish between AquaPalm and PDA supported control panels.
* PDA Freeze & Heater setpoints are now supported.
* Added PDA Sleep mode so AqualinkD can work in conjunction with a real Jandy PDA.
* Speeded up many PDA functions.
* Fixed many PDA bugs.
* Non PDA specific updates :-
* Can get button labels from control panel (not in PDA mode).
* RS485 Logging so users can submit information on Variable Speed Pumps & other devices for future support.
* Force SWG status on startup, rather than wait for pump to turn on.
* General bug fixes and improved code in many areas.
## Update in Release 1.2.6f
* Solution to overcome bug in Mosquitto 1.6.
* Fixed Salt Water Generator when % was set to 0.
* Added support for different SWG % for pool & spa. (SWG reports and sets the mode that is currently active).
* Increased speed of SWG messages.
* Few other bug fixes (Thanks to ballle98).
## Update in Release 1.2.6e (This is a quick update, please only use if you need one of the items below.)
* Unstable update.
## Update in Release 1.2.6c
* Fixed some merge issues.
* Added MQTT topic for delayed start on buttons.
* Removed MQTT flash option for delayed start (never worked well anyway).
## Update in Release 1.2.6b
* Added MQTT topic for full SWG status (MQTT section in see wiki).
* Configured option to turn on/off listening to extended device information.
* Added service mode topic to MQTT (Thanks to tcm0116).
* Added report zero pool temp (Thanks to tcm0116).
## Update in Release 1.2.6a
* More PDA fixes (Thanks to ballle98).
* Fix in MQTT requests to change temperature when temperature units are unkown.
## Update in Release 1.2.6
* Fix for PDA with SPA messages. (Thanks to ballle98).
* Added report 0 for pool temperature when not available. (Thanks to tcm0116).
## Update in Release 1.2.5a
* Fix bug for MQTT freeze protect.
## Update in Release 1.2.4
* Small fix for Freeze Protect.
## Update in Release 1.2.3
* Fix for setpoints on "Pool Only" configurations.
## Update in Release 1.2.2
* Support for Spa OR Pool OLNY mode with setpoints, (previous setpoints expected Spa & Pool mode)
* Added support for MQTT Last Will Message
* Fix spelling errors will effect your conficuration, and the install.sh script will not overwrite.
* Please compare /var/www/aqualinkd/config.js to the new one, you will need to manualy edit or overide
* MQTT spelling for enabled is now accurate, so anything using the /enabled message will nee to be changed
* homekit will need to be changed. Please see the new homekit2mqtt.json or modify your existing one.
* Support for Spa OR Pool ONLY mode with setpoints; (previous setpoints expected Spa & Pool mode)
* Added support for MQTT Last Will Message.
* NOTE: Fixed spelling errors will effect your configuration and the install.sh script will not overwrite.
* Please compare /var/www/aqualinkd/config.js to the new one, you will need to manually edit or overide.
* MQTT spelling for "enabled" is now accurate, so anything using the /enabled message will need to be changed.
* Homekit will also need to be changed. Please see the new homekit2mqtt.json or modify your existing one.
## Updates in Release 1.2
* PDA support in BETA. (Please see WiKi for details)
* Fixed bug in posting Heater Emables topics to MQTT. (order was reversed)
* Serial read change. (detect escaped DTX in packet, 1 in 10000 chance or happening)
* PDA support in BETA. (Please see WiKi for details).
* Fixed bug in posting Heater enables topics to MQTT. (order was reversed).
* Serial read change. (Detect escaped DTX in packet, 1 in 10000 chance of happening).
## Updates in Release 1.1
* Changed the way AqualinkD reads USB, fixes the checksum & serial read too small errors that happened on some RS485 networks.
* Figex bug in SWG would read "high voltage" and not "check cell"
* Figex bug in SWG would read "high voltage" and not "check cell".
## Updates in release 1.0e
* Web UI out of Beta
* MQTT fix setpoints
* Web UI out of Beta.
* MQTT fix setpoints.
* Simulator is now more stable.
* updates to serial logger
* UI updates
* bug fix in MQTT_flash (still not prefect fix)
* Updates to serial logger.
* UI updates.
* Bug fix in MQTT_flash (still not perfect).
## Updates in Release 1.0c
* New Simple interface.
* New Simpler interface.
* Start of a RS8 Simulator :-
* So you can program the AqualinkRS form a web interface and not control panel.
* Please make sure all other browsers & tabs are not using AqualinkD. it doesn't support multiple devices when in simulator mode.
* You can now program the AqualinkRS from a web interface and not just the control panel.
* Please make sure all other browsers and tabs are not using AqualinkD as it does not support multiple devices when in simulator mode.
* Fixed a few bugs.
* -- Release 1.0b --
* NEW WEB UI !!!!!!!!!!!!! (in beta)
* Flash buttons on/off in homekit for enabeling / disabeling / cooldown period as they do on control panel
* Full SWG support (setting %, not just reporting current state). Also reports Salt Cell status such as (no flow, low salt, high curent, clean cell, low voltage, water temp low, check PCB)
* Update to thermostats, colors are now correct in homekit, green=enabeled, orange=heating, blue=cooling (SWG only)
* NEW WEB UI !!!!!!!!!!!!! (in beta).
* Flash buttons on/off in homekit for enabeling/disabling/cooldown period as they do on the control panel.
* Full SWG support (setting %, not just reporting current state). Also reports Salt Cell status such as (no flow, low salt, high curent, clean cell, low voltage, water temp low, check PCB).
* Update to thermostats, colors are now correct in homekit, green=enabeled, orange=heating, blue=cooling (SWG only).
* Light show program mode should now support most vendors lights.
* config changes for (spa temp as pool temp / light program mode options / enable homekit button flash)
* updated to serial_logger.
* freeze protect, heater temperature & SWG set-points have been added to support for standard HTTP requests (MQTT & WS always had support)
* Configuration changes for: Spa temp as pool temp/light program mode options/enable homekit button flash.
* Updated to serial_logger.
* Freeze protect, heater temperature and SWG set-points have been added to support for standard HTTP requests (MQTT & WS always had support).
# Please see Wiki for install instructions
https://github.com/sfeakes/AqualinkD/wiki
https://github.com/aqualinkd/AqualinkD/wiki
#
# Aqualink Versions tested
This was designed for Jandy Aqualink RS, so should work with AqualinkRS and iAqualink Combo controll panels. At the moment it will not work with Aqualink PDA / AquaPalm and NON Combo iAqualink.
Below are varified versions (But should work with any AqualinkRS) :-
This was designed for Jandy Aqualink RS, so does work with AqualinkRS and iAqualink Combo control panels. It will work with Aqualink PDA/AquaPalm; but with limitations.
AqualinkD is known to work with Panel Versions from Rev H to the Latest Rev Yg
<!--
Below are verified versions, but should work with any AqualinkRS :-
| Version | Notes |
@ -128,288 +538,10 @@ Below are varified versions (But should work with any AqualinkRS) :-
| Jandy AquaLinkRS B0029223 REV T.2 | Everything working |
| Jandy AquaLinkRS B0029235 REV T.1 | Everything working |
| Jandy iAqualink E0260801 REV R | Everything working |
| AquaLink PDA / AquaPalm | Beta available, Limited functionality, please see WiKi.|
| AquaLink PDA / AquaPalm | Works, please see WiKi for limitations|
If you have tested a version not listed here, please let me know by opening an issue
If you have tested a version not listed here, please let me know by opening an issue.
#
<!--
# TL;DR Install
* For new install or update existing install follow the same procedures. Your configuration file will not be overriden, so on an update check you have added new config options to your config, your old config will work, you just may not have the new features enabeled. If you have modified any files in the web directory then you should back them up before upgrading.
## Quick instal if you are using Raspberry PI
* There is a chance the pre-compiled binary will run, copy the git repo and run the install.sh script from the release directory. ie from the install directory `sudo ./release/install.sh`
* try to run it with :-
* `sudo aqualinkd -d -c /etc/aqualinkd.conf`
* If it runs, then start configuring it to your setup.
* If it fails, try making the execuatable, below are some instructions.
## Making & Install
* Depends on a linux computer connected to a USB 2 RS485 adapter, that's connected to your pool controller.
* Get a copy of the repo using git.
* run make
* run sudo make install
* edit `/etc/aqualinkd.conf` to your setup
* Test every works :-
* `sudo aqualinkd -d -c /etc/aqualinkd.conf`
* point a web browser to the IP of the box running aqualinkd
* If all is good enable as service
* sudo `systemctl start aqualinkd`
### Note:-
The install script is designed for systemd / systemctl to run as a service or daemon. If you are using init / init-d then don't run the install script, install manually and the init-d script to use is in the xtras directory.
Manual install for init-d systems
* copy ./release/aqualinkd to /usr/local/bin
* copy ./release/aqualinkd.conf to /etc
* copy ./release/aqualinkd.service.avahi to /etc/avahi/services/aqualinkd.service
* copy ./extras/aqualinkd.init.d to /etc/init-d/aqualink
* copy recuesivley ./web/* to /var/www/aqualinkd/
* sudo update-rc.d aqualinkd defaults
## New WEB interface(s)
* Doesn't work in MS Edge or MS Exploder browsers. (Firefox, Chrome, Safari & mobile versions of each) all seem to be fine, use the old or simple.html interface if using those browsers.
* use http://aqualink.ip.address/ to access new UI
* look in `<install_dir>/web/hk` hopefully customizing icon and background images are self explanatory.
* icons should be around 50x50 pixles and in PNG format, background any size in JPG or just delete the file if you want solid color.
* edit `<install_dir>/web/config.js` there are two arrays that can be modified
* `devices` List any device you want to see in there (commanent out the ones you don't), the odrer of that list will be the display order of devices.
* `light_program` If you have a programable light (and it's configured in `aqualinkd.conf`), then list the light modes here.
* Plenty of colors to change if you like.
# Aqualink Versions tested
This was designed for Jandy Aqualink RS, so should work with AqualinkRS and iAqualink Combo controll panels. At the moment it will not work with Aqualink PDA / AquaPalm and NON Combo iAqualink.
Below are varified versions (But should work witn any AqualinkRS) :-
| Version | Notes |
| --- | --- |
| JANDY AquaLinkRS 8157 REV MMM | Everything working |
| Jandy AquaLinkRS 8157 REV JJ | Everything working |
| Jandy AquaLinkRS B0029223 REV T.2 | Everything working |
| Jandy AquaLinkRS B0029235 REV T.1 | Everything working |
| Jandy iAqualink E0260801 REV R | All working, but sometimes programming is hit 'n miss. This is a combo board iAqualink & AqualinkRS |
| AquaLink PDA / AquaPalm | Not usable, work in progress.|
Please post details in issues section if you have one not listed above.
## Hardware
You will need a [USB2RS485](https://www.amazon.com/OctagonStar-Converter-Adapter-Interface-FT232RL/dp/B01LCFRR3E/) adapter connected to your pool equiptmeent RS buss interface. (If you have an inside controller mounted on your wall, this is usually best place, if not the outside control panel is the next best place). Then a computer running linux connected to that USB2RS485 adapter. Code is designed & developed for raspberry pi zero w, so any computer with that as a minimum should work.
Raspberry Pi Zero is the perfect device for this software. But all Raspberry PI's are inherently unstable devices to be running 24/7/365 in default Linux configrations. This is due to the way they use CF card, a power outage will generally cause CF card coruption. My recomendation is to use what's calles a "read only root" installation of Linux. Converting Raspbian to this is quite simple, but will require some Linux knoladge. There are two methods, one uses overlayfs and if you are not knolagable in Linux this is the easiest option. There are a some downsides to this method on a PI, so my prefered method is to simply use tmpfs on it's own without overlayfs ontop, this is easier to setup initially, but will probably require a few custom things to get right as some services will fail. Once you are up and running, You should search this topic, and there are a plenty of resources, and even some scripts the will do everything for you. But below are two links that explain the process.
[Good overlayfs tutorial on PI Forums](https://www.raspberrypi.org/forums/viewtopic.php?t=161416)
[My prefered way to impliment](https://hallard.me/raspberry-pi-read-only/)
I have my own scripts to do this for me, and probably won't ever document or publish them, but thay are very similar to the 2nd link above.
## Aqualinkd Configuration
Please see the [aqualinkd.conf]
(https://github.com/sfeakes/aqualinkd/blob/master/extras/aqualinkd.conf)
example in the release directory. Many things are turned off by default, and you may need to enable or configure them for your setup.
Main item to configure is the Aqualink RS485 address so it doesn't conflict with any existing control panels or equiptment. By default it's set to 0x0a which is the second usable address of an allbutton control panel. Included is a serial loging tool that can let you know all information on the RS485 buss, you can use this to find a good address.
```
make slog <-- will make the serial logging tool
./release/serial_logger /dev/ttyUSB0 <- is probably all you'll need to run.
--- example output ---
Notice: Logging serial information, please wait!
Notice: ID's found
Notice: ID 0x08 is in use
Notice: ID 0x60 is not used
Notice: ID 0x0a is not used <-- can use for Aqualinkd
Notice: ID 0x0b is not used <-- can use for Aqualinkd
Notice: ID 0x40 is not used
Notice: ID 0x41 is not used
Notice: ID 0x42 is not used
Notice: ID 0x43 is not used
Notice: ID 0x50 is not used
Notice: ID 0x58 is not used
Notice: ID 0x09 is not used <-- can use for Aqualinkd
Notice: ID 0x88 is in use
Any address listed with Aqualinkd can be used. This is the `device-id` address in the config file.
Other command like options for serial_ligger are :-
-d (print out every packet)
-p 1000 (log 1000 packets, default is 200)
```
Specifically, make sure you configure your MQTT, Pool Equiptment Labels & Domoticz ID's in there, looking at the file it should be self explanatory.
#
# Configuration with home automation hubs
AqualinkD offers 3 ways to connect to it from other devices MQTT, Web API's & Web Sockets.
* MQTT is a realtime pub/sub style event system and will require you to install a MQTT server, like Mosquitto
* WEB API's simple poll based way to get information and request something to be done
* WEB Sockets is realtime and realy only applicable for the WEB UI's included. it's not documented. If someone wants to use this, let me know and I can document it. (or look at the index.html and it should be easy to work out)
All Hubs will use either MQTT or WEB API to counicate with AqualinkD. MQTT is prefered, but it will depend on your setup and how you want to achieve the integration. The Generic MQTT and WEB API defails are below, followed by specific implimentations for diferent hubs.
All Hubs you will Create a device for each piece of pool equiptment you have, eg Filter Pump, Spa Mode, Pool Light, Cleaner, Water Temperature etc then configure that device to use either the MQTT or API endpoints listed below to both get the status and set the status for that device.
## MQTT
Aqualinkd supports generic MQTT implimentations, as well as specific Domoticz one described above.
To enable, simply configure the main topic in `aqualinkd.conf`.
```mqtt_aq_topic = aqualinkd```
Then status messages will be posted to the sub topics listed below, with appropiate information. Each button uses the standard Aqualink RS controller name, so Aux_1 might be your poool light.
Note, all Temperatures will be in C, if you have your pool controler configured to F aqualinkd will automatically do the conversion.
```
Temperatures simply be a number posted to the topic.
aqualinkd/Temperature/Air
aqualinkd/Temperature/Pool
aqualinkd/Temperature/Spa
aqualinkd/Pool_Heater/setpoint
aqualinkd/Spa_Heater/setpoint
Buttons will be a 1 or 0 for state. 1=on 0=off
aqualinkd/Filter_Pump
aqualinkd/Spa_Mode
aqualinkd/Aux_1
....Aux_2-6....
aqualinkd/Aux_7
aqualinkd/Pool_Heater (0 off, 1 on and heating)
aqualinkd/Pool_Heater/enabeled (0 off, 1 on but not heating - water has reached target temp)
aqualinkd/Spa_Heater (0 off, 1 on and heating )
aqualinkd/Spa_Heater/enabeled (0 off, 1 but not heating - water has reached target temp)
aqualinkd/SWG (0 off, 2 on generating salt)
aqualinkd/SWG/enabeled (0 off, 2 on but not generating salt - SWG reported no-flow or equiv)
Other Information (Salt Water Generator)
aqualinkd/SWG/Percent ( SWG Generating %, i.e. 50)
aqualinkd/SWG/PPM ( SWG Parts Per Million i.e. 3100)
aqualinkd/SWG/Percent_f (since we use a homekit thermostat for SWG and use degC as %, we need to pass degF for US phone)
```
To turn something on, or set information, simply add `set` to the end of the above topics, and post 1 or 0 in the message for a button, or a number for a setpoint. Topics Aqualinkd will act on.
```
aqualinkd/Filter_Pump/set
aqualinkd/Spa_Mode/set
aqualinkd/Aux_1/set
....Aux 2-6.../set
aqualinkd/Aux_7/set
aqualinkd/Pool_Heater/set
aqualinkd/Spa_Heater/set
aqualinkd/Pool_Heater/setpoint/set
aqualinkd/Spa_Heater/setpoint/set
aqualinkd/SWG/Percent/set
aqualinkd/SWG/Percent_f/set ( Set % as a degF to degC conversion for Homekit)
```
Example that would turn on the filter pump
```
aqualinkd/Filter_Pump/set 1
```
## Web API's
The following URL is an example of how to turn the pump on
```
http://aqualinkd.ip.address:port?command=Filter_Pump&value=on
```
each button ie command=xxxxx uses the default Aqualink name, and the value=xx is simply on or off. Valid options for command are :-
```
Filter_Pump
Spa_Mode
Aux_1
Aux_2
Aux_3
Aux_4
Aux_5
Aux_6
Aux_7
Pool_Heater
Spa_Heater
Solar_Heater
```
There are a few extended commands that will set Heater setpoints, Salt Water Generator %, and light mode (if configured), these are in the came form only the value is a number not a on/off
```
http://aqualinkd.ip.address:port?command=pool_htr_set_pnt&value=95
```
```
poollightmode
swg_percent
pool_htr_set_pnt
spa_htr_set_pnt
frz_protect_set_pnt
```
To get status that can be passed by any smart hub use the below url, a JSON string with the state of each device and current temprature will be returned.
Both return similar information, the `devices` will simply return more, but be harder to pass. So pick which souits your configuration better.
```
http://aqualinkd.ip.address:port?command=status
or
http://aqualinkd.ip.address:port?command=devices
```
The status of any device can be :-
```
on
off
enabled -> (for heaters, they are on but not heating)
flash -> (delayed On or Off has been inicitated by the control panel)
```
#
# Specific Hub integrations
## Apple HomeKit
For the moment, native Homekit support has been removed, it will be added back in the future under a different implimentation.
Recomended option for HomeKit support is to make use of the MQTT interface and use [HomeKit2MQTT](https://www.npmjs.com/package/homekit2mqtt) to bridge between Aqualinkd and you Apple (phone/tablet/tv & hub).
* If you don't already have an MQTT broker Installed, install one. Mosquitto is recomended, this can usually be installed with apt-get
* Install [HomeKit2MQTT](https://www.npmjs.com/package/homekit2mqtt). (see webpage for install)
* Then copy the [`homekit2mqtt.json`](https://github.com/sfeakes/aqualinkd/blob/master/extras/homekit2mqtt.json) configuration file found in the extras directory to your homekit2mqtt storage directory.
* If you want to run homekit2mqtt as a daemon service, there are files in the extras directory to help you do this.
* copy [`homekit2mqtt.service`] to `/etc/systemd/system/homekit2mqtt.service`
* copy [`homekit2mqtt.defaults`] to `/etc/defaults/homekit2mqtt`
* create directory `/var/lib/homekitmqtt`
* copy [`homekit2mqtt.json`](https://github.com/sfeakes/aqualinkd/blob/master/extras/homekit2mqtt.json) to `/var/lib/homekitmqtt`
* edit `/etc/defaults/homekit2mqtt` to change and homekit2mqtt config parameters.
* install and start service
* `sudo systemctl enable homekit2mqtt`
* `sudo systemctl daemon-reload`
* `sudo systemctl start homekit2mqtt`
You can of course use a myriad of other HomeKit bridges with the URL endpoints listed in the `All other hubs section`, or MQTT topics listed in the `MQTT` section. The majority of them (including HomeBridge the most popular) use Node and HAP-Node.JS, neither of which I am a fan of for the RaspberryPI. But HomeKit2MQTT seemed to have the least overhead of them all. So that's why the recomendation.
#
# Domoticz
With MQTT
* Enable MQTT in Domoticz, and install a MQTT broker.
* Create a virtual device for each piece of pool equiptment you have, eg Filter Pump, Spa Mode, Pool Light, Cleaner.
* Place the Domoticz IDX for each device in the aqualinkd.conf file under the appropiate button. Then modify each virtual device.
Without MQTT
* Follow generic hub setup.
#
# Home Assistant (HASS.IO)
Copy the sections from
[`HASSIO Implimentation.txt`](https://github.com/sfeakes/aqualinkd/blob/master/extras/HASSIO.implimentation.txt)
into your config.yaml
#
# MeteoHub
If you want to log/track water temps within MeteoHub, simple configure it to poll the below URL and water & air tempratures will be sent in a format native to MeteoHub.
```
http://aqualinkd.ip.address:port?command=mhstatus
```
t0 will be air temp and t1 water temp.
To use. copy the [meteohub-aq-plugin.sh script](https://github.com/sfeakes/aqualinkd/blob/master/extras/meteohub-aq-plugin.sh) from the extras directory to your meteohub box, edit the script and use your IP address in the line that makes the URL call, below.
```
wget -O /dev/stdout 'http://your.ip.address.here/?command=mhstatus' 2>/dev/null
```
In meteohub create a new weatherstation plug-in, plug-in path is the path to the above sctipt, and his save. 2 new sensors should now show up as thermo in the sensor page.
-->
# License
## Non Commercial Project
@ -420,7 +552,7 @@ See License.md for more details.
# Donation
If you like this project, you can buy me a cup of coffee :)
If you still like this project, please consider buying me a cup of coffee :)
<br>
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SEGN9UNS38TXJ)

View File

@ -1,46 +0,0 @@
#ifndef AQ_MQTT_H_
#define AQ_MQTT_H_
#define AIR_TEMP_TOPIC "Temperature/Air"
#define POOL_TEMP_TOPIC "Temperature/Pool"
#define SPA_TEMP_TOPIC "Temperature/Spa"
//#define POOL_SETPT_TOPIC "Pool_Heater/setpoint"
//#define SPA_SETPT_TOPIC "Spa_Heater/setpoint"
#define ENABELED_SUBT "/enabled"
#define SWG_TOPIC "SWG"
#define SWG_PPM_TOPIC SWG_TOPIC "/PPM"
#define SWG_PPM_F_TOPIC SWG_TOPIC "/PPM_f"
#define SWG_ENABELED_TOPIC SWG_TOPIC ENABELED_SUBT
#define SWG_PERCENT_TOPIC SWG_TOPIC "/Percent"
#define SWG_PERCENT_F_TOPIC SWG_TOPIC "/Percent_f"
#define SWG_SETPOINT_TOPIC SWG_TOPIC "/setpoint"
#define FREEZE_PROTECT "Freeze_Protect"
#define FREEZE_PROTECT_ENABELED FREEZE_PROTECT ENABELED_SUBT
#define POOL_THERMO_TEMP_TOPIC BTN_POOL_HTR "/Temperature"
#define SPA_THERMO_TEMP_TOPIC BTN_SPA_HTR "/Temperature"
/*
#define AIR_TEMPERATURE "Air"
#define POOL_TEMPERATURE "Pool_Water"
#define SPA_TEMPERATURE "Spa_Water"
*/
/*
#define AIR_TEMPERATURE_TOPIC AIR_TEMPERATURE "/Temperature"
#define POOL_TEMPERATURE_TOPIC POOL_TEMPERATURE "/Temperature"
#define SPA_TEMPERATURE_TOPIC SPA_TEMPERATURE "/Temperature"
*/
#define SWG_ON 2
#define SWG_OFF 0
#define MQTT_ON "1"
#define MQTT_OFF "0"
#define MQTT_LWM_TOPIC "Alive"
#endif // AQ_MQTT_H_

File diff suppressed because it is too large Load Diff

View File

@ -1,67 +0,0 @@
#ifndef AQ_PROGRAMMER_H_
#define AQ_PROGRAMMER_H_
// need to get the C values from aqualink manual and add those just incase
// someone has the controller set to C.
#define HEATER_MAX_F 104
#define HEATER_MIN_F 36
#define FREEZE_PT_MAX_F 42
#define FREEZE_PT_MIN_F 36
#define HEATER_MAX_C 40
#define HEATER_MIN_C 0
#define FREEZE_PT_MAX_C 5
#define FREEZE_PT_MIN_C 1
#define SWG_PERCENT_MAX 101
#define SWG_PERCENT_MIN 0
#define PTHREAD_ARG 25
#define LIGHT_MODE_BUFER PTHREAD_ARG
typedef enum {
AQP_NULL = -1,
AQ_GET_POOL_SPA_HEATER_TEMPS,
AQ_GET_FREEZE_PROTECT_TEMP,
AQ_SET_TIME,
AQ_SET_POOL_HEATER_TEMP,
AQ_SET_SPA_HEATER_TEMP,
AQ_SET_FRZ_PROTECTION_TEMP,
AQ_GET_DIAGNOSTICS_MODEL,
AQ_SEND_CMD,
AQ_GET_PROGRAMS,
AQ_SET_COLORMODE,
AQ_PDA_INIT,
AQ_SET_SWG_PERCENT,
AQ_PDA_DEVICE_STATUS,
AQ_PDA_DEVICE_ON_OFF
} program_type;
struct programmingThreadCtrl {
pthread_t thread_id;
//void *thread_args;
char thread_args[PTHREAD_ARG];
struct aqualinkdata *aq_data;
};
//void aq_programmer(program_type type, void *args, struct aqualinkdata *aq_data);
void aq_programmer(program_type type, char *args, struct aqualinkdata *aq_data);
void kick_aq_program_thread(struct aqualinkdata *aq_data);
unsigned char pop_aq_cmd(struct aqualinkdata *aq_data);
//bool push_aq_cmd(unsigned char cmd);
//void send_cmd(unsigned char cmd, struct aqualinkdata *aq_data);
//void cancel_menu(struct aqualinkdata *aq_data);
//void *set_aqualink_time( void *ptr );
//void *get_aqualink_pool_spa_heater_temps( void *ptr );
int get_aq_cmd_length();
int setpoint_check(int type, int value, struct aqualinkdata *aqdata);
#endif

View File

@ -1,663 +0,0 @@
/*
* Copyright (c) 2017 Shaun Feakes - All rights reserved
*
* You may use redistribute and/or modify this code under the terms of
* the GNU General Public License version 2 as published by the
* Free Software Foundation. For the terms of this license,
* see <http://www.gnu.org/licenses/>.
*
* You are free to use this software under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* https://github.com/sfeakes/aqualinkd
*/
#include <stdio.h>
#include <stdarg.h>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include <stdbool.h>
#include "aq_serial.h"
#include "utils.h"
#include "config.h"
//#define BLOCKING_MODE
static struct termios _oldtio;
//static struct aqconfig *_config_parameters;
//static struct aqconfig *_aqualink_config;
bool _pda_mode = false;
void set_pda_mode(bool mode)
{
if (mode)
logMessage(LOG_NOTICE, "AqualinkD is using PDA mode\n");
_pda_mode = mode;
}
bool pda_mode()
{
return _pda_mode;
}
void log_packet(int level, char *init_str, unsigned char* packet, int length)
{
if ( getLogLevel() < level) {
//logMessage(LOG_INFO, "Send '0x%02hhx'|'0x%02hhx' to controller\n", packet[5] ,packet[6]);
return;
}
int cnt;
int i;
char buff[MAXLEN];
cnt = sprintf(buff, "%s", init_str);
cnt += sprintf(buff+cnt, " | HEX: ");
//printHex(packet_buffer, packet_length);
for (i=0;i<length;i++)
cnt += sprintf(buff+cnt, "0x%02hhx|",packet[i]);
cnt += sprintf(buff+cnt, "\n");
logMessage(level, buff);
//logMessage(LOG_DEBUG_SERIAL, buff);
/*
int i;
char temp_string[64];
char message_buffer[MAXLEN];
sprintf(temp_string, "Send 0x%02hhx|", packet[0]);
strcpy(message_buffer, temp_string);
for (i = 1; i < length; i++) {
sprintf(temp_string, "0x%02hhx|", packet[i]);
strcat(message_buffer, temp_string);
}
strcat(message_buffer, "\n");
logMessage(LOG_DEBUG, message_buffer);
*/
}
const char* get_packet_type(unsigned char* packet , int length)
{
static char buf[15];
if (length <= 0 )
return "";
switch (packet[PKT_CMD]) {
case CMD_ACK:
return "Ack";
break;
case CMD_STATUS:
return "Status";
break;
case CMD_MSG:
return "Message";
break;
case CMD_MSG_LONG:
return "Lng Message";
break;
case CMD_PROBE:
return "Probe";
break;
case CMD_GETID:
return "GetID";
break;
case CMD_PERCENT:
return "AR %%";
break;
case CMD_PPM:
return "AR PPM";
break;
case CMD_PDA_0x05:
return "PDA Unknown";
break;
case CMD_PDA_HIGHLIGHT:
return "PDA Hlight";
break;
case CMD_PDA_CLEAR:
return "PDA Clear";
break;
case CMD_PDA_SHIFTLINES:
return "PDA Shiftlines";
break;
case CMD_PDA_HIGHLIGHTCHARS:
return "PDA C_HlightChar";
break;
default:
sprintf(buf, "Unknown '0x%02hhx'", packet[PKT_CMD]);
return buf;
break;
}
}
/*
Open and Initialize the serial communications port to the Aqualink RS8 device.
Arg is tty or port designation string
returns the file descriptor
*/
int init_serial_port(char* tty)
{
long BAUD = B9600;
long DATABITS = CS8;
long STOPBITS = 0;
long PARITYON = 0;
long PARITY = 0;
struct termios newtio; //place for old and new port settings for serial port
//int fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK);
int fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY);
if (fd < 0) {
logMessage(LOG_ERR, "Unable to open port: %s\n", tty);
return -1;
}
logMessage(LOG_DEBUG_SERIAL, "Openeded serial port %s\n",tty);
#ifdef BLOCKING_MODE
fcntl(fd, F_SETFL, 0);
newtio.c_cc[VMIN]= 1;
newtio.c_cc[VTIME]= 0;
logMessage(LOG_DEBUG_SERIAL, "Set serial port %s to blocking mode\n",tty);
#else
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK | O_NDELAY);
newtio.c_cc[VMIN]= 0;
newtio.c_cc[VTIME]= 1;
logMessage(LOG_DEBUG_SERIAL, "Set serial port %s to non blocking mode\n",tty);
#endif
/*
// Need to change this to flock, but that depends on good exit.
if ( ioctl(fd, TIOCEXCL) != 0) {
displayLastSystemError("Locking serial port.");
}
struct flock fl;
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
if (fcntl(fd, F_SETLK, &fl) == -1)
{
if (errno == EACCES || errno == EAGAIN)
{
displayLastSystemError("Locking serial port.");
}
else
{
displayLastSystemError("Unknown error Locking serial port.");
}
}
*/
tcgetattr(fd, &_oldtio); // save current port settings
// set new port settings for canonical input processing
newtio.c_cflag = BAUD | DATABITS | STOPBITS | PARITYON | PARITY | CLOCAL | CREAD;
newtio.c_iflag = IGNPAR;
newtio.c_lflag = 0; // ICANON;
newtio.c_oflag = 0;
tcflush(fd, TCIFLUSH);
tcsetattr(fd, TCSANOW, &newtio);
logMessage(LOG_DEBUG_SERIAL, "Set serial port %s io attributes\n",tty);
return fd;
}
/* close tty port */
void close_serial_port(int fd)
{
tcsetattr(fd, TCSANOW, &_oldtio);
close(fd);
logMessage(LOG_DEBUG_SERIAL, "Closed serial port\n");
}
// Generate and return checksum of packet.
int generate_checksum(unsigned char* packet, int length)
{
int i, sum, n;
n = length - 3;
sum = 0;
for (i = 0; i < n; i++)
sum += (int) packet[i];
return(sum & 0x0ff);
}
// Send an ack packet to the Aqualink RS8 master device.
// fd: the file descriptor of the serial port connected to the device
// command: the command byte to send to the master device, NUL if no command
//
// NUL = '\x00'
// DLE = '\x10'
// STX = '\x02'
// ETX = '\x03'
//
// masterAddr = '\x00' # address of Aqualink controller
//
//msg = DLE+STX+dest+cmd+args
//msg = msg+self.checksum(msg)+DLE+ETX
// DLE+STX+DEST+CMD+ARGS+CHECKSUM+DLE+ETX
void print_hex(char *pk, int length)
{
int i=0;
for (i=0;i<length;i++)
{
printf("0x%02hhx|",pk[i]);
}
printf("\n");
}
void test_cmd()
{
const int length = 11;
unsigned char ackPacket[] = { NUL, DLE, STX, DEV_MASTER, CMD_ACK, NUL, NUL, 0x13, DLE, ETX, NUL };
//send_cmd(fd, CMD_ACK, command);
print_hex((char *)ackPacket, length);
ackPacket[7] = generate_checksum(ackPacket, length-1);
print_hex((char *)ackPacket, length);
ackPacket[6] = 0x02;
ackPacket[7] = generate_checksum(ackPacket, length-1);
print_hex((char *)ackPacket, length);
}
void send_test_cmd(int fd, unsigned char destination, unsigned char b1, unsigned char b2, unsigned char b3)
{
const int length = 11;
unsigned char ackPacket[] = { NUL, DLE, STX, DEV_MASTER, CMD_ACK, NUL, NUL, 0x13, DLE, ETX, NUL };
//unsigned char ackPacket[] = { NUL, DLE, STX, DEV_MASTER, NUL, NUL, NUL, 0x13, DLE, ETX, NUL };
// Update the packet and checksum if command argument is not NUL.
ackPacket[3] = destination;
ackPacket[4] = b1;
ackPacket[5] = b2;
ackPacket[6] = b3;
ackPacket[7] = generate_checksum(ackPacket, length-1);
#ifdef BLOCKING_MODE
write(fd, ackPacket, length);
#else
int nwrite, i;
for (i=0; i<length; i += nwrite) {
nwrite = write(fd, ackPacket + i, length - i);
if (nwrite < 0)
logMessage(LOG_ERR, "write to serial port failed\n");
}
//logMessage(LOG_DEBUG_SERIAL, "Send %d bytes to serial\n",length);
//tcdrain(fd);
//logMessage(LOG_DEBUG, "Send '0x%02hhx' to '0x%02hhx'\n", command, destination);
#endif
log_packet(LOG_DEBUG_SERIAL, "Sent ", ackPacket, length);
}
void send_command(int fd, unsigned char destination, unsigned char b1, unsigned char b2, unsigned char b3)
{
const int length = 11;
unsigned char ackPacket[] = { NUL, DLE, STX, DEV_MASTER, CMD_ACK, NUL, NUL, 0x13, DLE, ETX, NUL };
//unsigned char ackPacket[] = { NUL, DLE, STX, DEV_MASTER, NUL, NUL, NUL, 0x13, DLE, ETX, NUL };
// Update the packet and checksum if command argument is not NUL.
ackPacket[3] = destination;
ackPacket[4] = b1;
ackPacket[5] = b2;
ackPacket[6] = b3;
ackPacket[7] = generate_checksum(ackPacket, length-1);
#ifdef BLOCKING_MODE
write(fd, ackPacket, length);
#else
int nwrite, i;
for (i=0; i<length; i += nwrite) {
nwrite = write(fd, ackPacket + i, length - i);
if (nwrite < 0)
logMessage(LOG_ERR, "write to serial port failed\n");
}
//logMessage(LOG_DEBUG_SERIAL, "Send %d bytes to serial\n",length);
//tcdrain(fd);
//logMessage(LOG_DEBUG, "Send '0x%02hhx' to '0x%02hhx'\n", command, destination);
#endif
if ( getLogLevel() >= LOG_DEBUG_SERIAL) {
char buf[30];
sprintf(buf, "Sent %8.8s ", get_packet_type(ackPacket+1 , length));
log_packet(LOG_DEBUG_SERIAL, buf, ackPacket, length);
}
}
void send_messaged(int fd, unsigned char destination, char *message)
{
const int length = 24;
int i;
//unsigned char ackPacket[] = { NUL, DLE, STX, DEV_MASTER, CMD_ACK, NUL, NUL, 0x13, DLE, ETX, NUL };
unsigned char msgPacket[] = { DLE,STX,DEV_MASTER,CMD_MSG,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,NUL,DLE,ETX };
//unsigned char ackPacket[] = { NUL, DLE, STX, DEV_MASTER, NUL, NUL, NUL, 0x13, DLE, ETX, NUL };
// Update the packet and checksum if command argument is not NUL.
msgPacket[2] = destination;
for (i=0; i < strlen(message) && i < AQ_MSGLEN; i++)
msgPacket[4+i] = message[i];
msgPacket[length-3] = generate_checksum(msgPacket, length-1);
#ifdef BLOCKING_MODE
write(fd, msgPacket, length);
#else
int nwrite;
for (i=0; i<length; i += nwrite) {
nwrite = write(fd, msgPacket + i, length - i);
if (nwrite < 0)
logMessage(LOG_ERR, "write to serial port failed\n");
}
//logMessage(LOG_DEBUG_SERIAL, "Send %d bytes to serial\n",length);
//tcdrain(fd);
//logMessage(LOG_DEBUG, "Send '0x%02hhx' to '0x%02hhx'\n", command, destination);
#endif
log_packet(LOG_DEBUG_SERIAL, "Sent ", msgPacket, length);
}
void _send_ack(int fd, unsigned char ack_type, unsigned char command);
void send_extended_ack(int fd, unsigned char ack_type, unsigned char command)
{
// ack_typ should only be ACK_NORMAL, ACK_SCREEN_BUSY, ACK_SCREEN_BUSY_DISPLAY
_send_ack(fd, ack_type, command);
}
void send_ack(int fd, unsigned char command)
{
if (! _pda_mode)
_send_ack(fd, ACK_NORMAL, command);
else
_send_ack(fd, ACK_PDA, command);
}
void _send_ack(int fd, unsigned char ack_type, unsigned char command)
{
const int length = 11;
// Default null ack with checksum generated, don't mess with it, just over right
unsigned char ackPacket[] = { NUL, DLE, STX, DEV_MASTER, CMD_ACK, NUL, NUL, 0x13, DLE, ETX, NUL };
//unsigned char ackPacket[] = { NUL, DLE, STX, DEV_MASTER, NUL, NUL, NUL, 0x13, DLE, ETX, NUL };
// Update the packet and checksum if command argument is not NUL.
if(command != NUL || ack_type != NUL) {
//ackPacket[5] = 0x00 normal, 0x03 some pause, 0x01 some pause ending (0x01 = Screen Busy (also return from logn message))
ackPacket[5] = ack_type;
ackPacket[6] = command;
ackPacket[7] = generate_checksum(ackPacket, length-1);
// NULL out the command byte if it is the same. Difference implies that
// a new command has come in, and is awaiting processing.
/*
if(aqualink_cmd == command) {
aqualink_cmd = NUL;
}
*/
// In debug mode, log the packet to the private log file.
//log_packet(ackPacket, length);
}
// Send the packet to the master device.
//write(fd, ackPacket, length);
//logMessage(LOG_DEBUG, "Send '0x%02hhx' to controller\n", command);
#ifdef BLOCKING_MODE
write(fd, ackPacket, length);
#else
int nwrite, i;
for (i=0; i<length; i += nwrite) {
nwrite = write(fd, ackPacket + i, length - i);
if (nwrite < 0)
logMessage(LOG_ERR, "write to serial port failed\n");
}
logMessage(LOG_DEBUG_SERIAL, "Send %d bytes to serial\n",length);
//tcdrain(fd);
#endif
log_packet(LOG_DEBUG_SERIAL, "Sent ", ackPacket, length);
}
int get_packet(int fd, unsigned char* packet)
{
unsigned char byte;
int bytesRead;
int index = 0;
bool endOfPacket = FALSE;
bool packetStarted = FALSE;
bool lastByteDLE = FALSE;
int retry = 0;
// Read packet in byte order below
// DLE STX ........ ETX DLE
// sometimes we get ETX DLE and no start, so for now just ignoring that. Seem to be more applicable when busy RS485 traffic
while (!endOfPacket) {
bytesRead = read(fd, &byte, 1);
if (bytesRead < 0 && errno == EAGAIN && packetStarted == FALSE && lastByteDLE == FALSE) {
// We just have nothing to read
return 0;
} else if (bytesRead < 0 && errno == EAGAIN) {
// If we are in the middle of reading a packet, keep going
if (retry > 20) {
logMessage(LOG_WARNING, "Serial read timeout\n");
log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
return 0;
}
retry++;
delay(10);
} else if (bytesRead == 1) {
//printf("Byte 0x%02hhx\n",byte);
if (lastByteDLE==TRUE && byte == NUL) {
// Check for DLE | NULL (that's escape DLE so delete the NULL)
//printf("IGNORE THIS PACKET\n");
lastByteDLE = FALSE;
} else if (lastByteDLE==TRUE) {
if (index == 0)
index++;
packet[index] = byte;
index++;
if (byte == STX && packetStarted == FALSE) {
packetStarted = TRUE;
} else if (byte == ETX && packetStarted == TRUE) {
endOfPacket = TRUE;
}
} else if (packetStarted) {
packet[index] = byte;
index++;
} else if (byte == DLE && packetStarted == FALSE) {
packet[index] = byte;
}
// // reset index incase we have EOP before start
if (packetStarted == FALSE)
index=0;
if (byte == DLE) {
lastByteDLE = TRUE;
} else {
lastByteDLE = FALSE;
}
} else if(bytesRead < 0) {
// Got a read error. Wait one millisecond for the next byte to
// arrive.
logMessage(LOG_WARNING, "Read error: %d - %s\n", errno, strerror(errno));
if(errno == 9) {
// Bad file descriptor. Port has been disconnected for some reason.
// Return a -1.
return -1;
}
delay(100);
}
// Break out of the loop if we exceed maximum packet
// length.
if (index >= AQ_MAXPKTLEN) {
logMessage(LOG_WARNING, "Serial packet too large\n");
log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
break;
}
}
//logMessage(LOG_DEBUG, "Serial checksum, length %d got 0x%02hhx expected 0x%02hhx\n", index, packet[index-3], generate_checksum(packet, index));
if (generate_checksum(packet, index) != packet[index-3]){
logMessage(LOG_WARNING, "Serial read bad checksum, ignoring\n");
log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
return 0;
} else if (index < AQ_MINPKTLEN && packetStarted) { //NSF. Sometimes we get END sequence only, so just ignore.
//} else if (index < AQ_MINPKTLEN) { //NSF. Sometimes we get END sequence only, so just ignore.
logMessage(LOG_WARNING, "Serial read too small\n");
log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
return 0;
}
logMessage(LOG_DEBUG_SERIAL, "Serial read %d bytes\n",index);
// Return the packet length.
return index;
}
// Reads the bytes of the next incoming packet, and
// returns when a good packet is available in packet
// fd: the file descriptor to read the bytes from
// packet: the unsigned char buffer to store the bytes in
// returns the length of the packet
int get_packet_old(int fd, unsigned char* packet)
{
unsigned char byte;
int bytesRead;
int index = 0;
int endOfPacket = FALSE;
int packetStarted = FALSE;
int foundDLE = FALSE;
bool started = FALSE;
int retry=0;
while (!endOfPacket) {
//printf("Read loop %d\n",++i);
bytesRead = read(fd, &byte, 1);
if (bytesRead < 0 && errno == EAGAIN && started == FALSE) {
// We just have nothing to read
return 0;
} else if (bytesRead < 0 && errno == EAGAIN) {
// If we are in the middle of reading a packet, keep going
if (retry > 20) {
logMessage(LOG_WARNING, "Serial read timeout\n");
log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
return 0;
}
retry++;
delay(10);
} else if (bytesRead == 1) {
started = TRUE;
//if (bytesRead == 1) {
if (byte == DLE) {
// Found a DLE byte. Set the flag, and record the byte.
foundDLE = TRUE;
packet[index] = byte;
}
else if (byte == STX && foundDLE == TRUE) {
// Found the DLE STX byte sequence. Start of packet detected.
// Reset the DLE flag, and record the byte.
foundDLE = FALSE;
packetStarted = TRUE;
packet[index] = byte;
}
else if (byte == NUL && foundDLE == TRUE) {
// Found the DLE NUL byte sequence. Detected a delimited data byte.
// Reset the DLE flag, and decrement the packet index to offset the
// index increment at the end of the loop. The delimiter, [NUL], byte
// is not recorded.
foundDLE = FALSE;
//trimmed = true;
index--;
}
else if (byte == ETX && foundDLE == TRUE) {
// Found the DLE ETX byte sequence. End of packet detected.
// Reset the DLE flag, set the end of packet flag, and record
// the byte.
foundDLE = FALSE;
packetStarted = FALSE;
endOfPacket = TRUE;
packet[index] = byte;
}
else if (packetStarted == TRUE) {
// Found a data byte. Reset the DLE flag just in case it is set
// to prevent anomalous detections, and record the byte.
foundDLE = FALSE;
packet[index] = byte;
}
else {
// Found an extraneous byte. Probably a NUL between packets.
// Ignore it, and decrement the packet index to offset the
// index increment at the end of the loop.
index--;
}
// Finished processing the byte. Increment the packet index for the
// next byte.
index++;
// Break out of the loop if we exceed maximum packet
// length.
if (index >= AQ_MAXPKTLEN) {
break;
}
}
else if(bytesRead < 0) {
// Got a read error. Wait one millisecond for the next byte to
// arrive.
logMessage(LOG_WARNING, "Read error: %d - %s\n", errno, strerror(errno));
if(errno == 9) {
// Bad file descriptor. Port has been disconnected for some reason.
// Return a -1.
return -1;
}
delay(100);
}
}
if (generate_checksum(packet, index) != packet[index-3]){
logMessage(LOG_WARNING, "Serial read bad checksum, ignoring\n");
log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
return 0;
} else if (index < AQ_MINPKTLEN) {
logMessage(LOG_WARNING, "Serial read too small\n");
log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
return 0;
}
logMessage(LOG_DEBUG_SERIAL, "Serial read %d bytes\n",index);
// Return the packet length.
return index;
}

View File

@ -1,204 +0,0 @@
#ifndef AQ_SERIAL_H_
#define AQ_SERIAL_H_
#include <termios.h>
#define CONNECTION_ERROR "ERROR No connection to RS control panel"
// packet offsets
#define PKT_DEST 2
#define PKT_CMD 3
#define PKT_DATA 4
#define PKT_STATUS_BYTES 5
#define DEV_MASTER 0x00
#define SWG_DEV_ID 0x50
// PACKET DEFINES
#define NUL 0x00
#define DLE 0x10
#define STX 0x02
#define ETX 0x03
#define AQ_MINPKTLEN 5
#define AQ_MAXPKTLEN 64
#define AQ_PSTLEN 5
#define AQ_MSGLEN 16
#define AQ_MSGLONGLEN 128
#define AQ_TADLEN 13
/* COMMANDS */
#define CMD_PROBE 0x00
#define CMD_ACK 0x01
#define CMD_STATUS 0x02
#define CMD_MSG 0x03
#define CMD_MSG_LONG 0x04
/* ACK RETURN COMMANDS */
#define ACK_NORMAL 0x00
#define ACK_SCREEN_BUSY 0x01 // Seems to be busy but can cache a message,
#define ACK_SCREEN_BUSY_BLOCK 0x03 // Seems to be don't send me shit.
#define ACK_PDA 0x40
/* AquaRite commands */
#define CMD_GETID 0x14 // May be remote control control
#define CMD_PERCENT 0x11 // Set Percent
#define CMD_PPM 0x16 // Received PPM
/* PDA KEY CODES */ // Just plating at the moment
#define KEY_PDA_UP 0x06
#define KEY_PDA_DOWN 0x05
#define KEY_PDA_BACK 0x02
#define KEY_PDA_SELECT 0x04
#define KEY_PDA_PGUP 0x01
#define KEY_PDA_PGDN 0x03
/* KEY/BUTTON CODES */
#define KEY_PUMP 0x02
#define KEY_SPA 0x01
#define KEY_AUX1 0x05
#define KEY_AUX2 0x0a
#define KEY_AUX3 0x0f
#define KEY_AUX4 0x06
#define KEY_AUX5 0x0b
#define KEY_AUX6 0x10
#define KEY_AUX7 0x15
#define KEY_POOL_HTR 0x12
#define KEY_SPA_HTR 0x17
#define KEY_SOLAR_HTR 0x1c
#define KEY_MENU 0x09
#define KEY_CANCEL 0x0e
#define KEY_LEFT 0x13
#define KEY_RIGHT 0x18
#define KEY_HOLD 0x19
#define KEY_OVERRIDE 0x1e
#define KEY_ENTER 0x1d
#define BTN_PUMP "Filter_Pump"
#define BTN_SPA "Spa_Mode"
#define BTN_AUX1 "Aux_1"
#define BTN_AUX2 "Aux_2"
#define BTN_AUX3 "Aux_3"
#define BTN_AUX4 "Aux_4"
#define BTN_AUX5 "Aux_5"
#define BTN_AUX6 "Aux_6"
#define BTN_AUX7 "Aux_7"
#define BTN_POOL_HTR "Pool_Heater"
#define BTN_SPA_HTR "Spa_Heater"
#define BTN_SOLAR_HTR "Solar_Heater"
#define BTN_PDA_PUMP "FILTER PUMP"
#define BTN_PDA_SPA "SPA"
#define BTN_PDA_AUX1 "AUX1"
#define BTN_PDA_AUX2 "AUX2"
#define BTN_PDA_AUX3 "AUX3"
#define BTN_PDA_AUX4 "AUX4"
#define BTN_PDA_AUX5 "AUX5"
#define BTN_PDA_AUX6 "AUX6"
#define BTN_PDA_AUX7 "AUX7"
#define BTN_PDA_POOL_HTR "POOL HEAT"
#define BTN_PDA_SPA_HTR "SPA HEAT"
#define BTN_PDA_SOLAR_HTR "EXTRA AUX"
#define BUTTON_LABEL_LENGTH 20
#define TOTAL_LEDS 20
// Index starting at 1
#define POOL_HTR_LED_INDEX 15
#define SPA_HTR_LED_INDEX 17
#define SOLAR_HTR_LED_INDEX 19
#define LNG_MSG_SERVICE_ACTIVE "SERVICE MODE IS ACTIVE"
#define LNG_MSG_POOL_TEMP_SET "POOL TEMP IS SET TO"
#define LNG_MSG_SPA_TEMP_SET "SPA TEMP IS SET TO"
#define LNG_MSG_FREEZE_PROTECTION_SET "FREEZE PROTECTION IS SET TO"
#define LNG_MSG_CLEANER_DELAY "CLEANER WILL TURN ON AFTER SAFETY DELAY"
#define LNG_MSG_BATTERY_LOW "BATTERY LOW"
#define MSG_AIR_TEMP "AIR TEMP"
#define MSG_POOL_TEMP "POOL TEMP"
#define MSG_SPA_TEMP "SPA TEMP"
#define MSG_AIR_TEMP_LEN 8
#define MSG_POOL_TEMP_LEN 9
#define MSG_SPA_TEMP_LEN 8
// Will get water temp rather than pool in some cases. not sure if it's REV specific or device (ie no spa) specific yet
#define MSG_WATER_TEMP "WATER TEMP"
#define MSG_WATER_TEMP_LEN 10
#define LNG_MSG_WATER_TEMP1_SET "TEMP1 (HIGH TEMP) IS SET TO"
#define LNG_MSG_WATER_TEMP2_SET "TEMP2 (LOW TEMP) IS SET TO"
#define LNG_MSG_FREEZE_PROTECTION_ACTIVATED "FREEZE PROTECTION ACTIVATED"
#define MSG_SWG_PCT "AQUAPURE" // AquaPure 55%
#define MSG_SWG_PPM "SALT" // Salt 3000 PPM
#define MSG_SWG_PCT_LEN 8
#define MSG_SWG_PPM_LEN 4
/* AQUAPURE SWG */
// These are madeup.
#define SWG_STATUS_OFF 0xFF
#define SWG_STATUS_UNKNOWN -128
// These are actual from RS485
#define SWG_STATUS_ON 0x00
#define SWG_STATUS_NO_FLOW 0x01 // no flow 0x01
#define SWG_STATUS_LOW_SALT 0x02 // low salt 0x02
#define SWG_STATUS_VLOW_SALT 0x04 // very low salt 0x04
#define SWG_STATUS_CLEAN_CELL 0x08 // clean cell 0x10
#define SWG_STATUS_TURNING_OFF 0x09 // turning off 0x09
#define SWG_STATUS_HIGH_CURRENT 0x10 // high current 0x08
#define SWG_STATUS_LOW_VOLTS 0x20 // low voltage 0x20
#define SWG_STATUS_LOW_TEMP 0x40 // low watertemp 0x40
#define SWG_STATUS_CHECK_PCB 0x80 // check PCB 0x80
#define CMD_PDA_0x05 0x05
#define CMD_PDA_HIGHLIGHT 0x08
#define CMD_PDA_CLEAR 0x09
#define CMD_PDA_SHIFTLINES 0x0F
#define CMD_PDA_HIGHLIGHTCHARS 0x10
typedef enum {
ON,
OFF,
FLASH,
ENABLE,
LED_S_UNKNOWN
} aqledstate;
typedef struct aqualinkled
{
//int number;
aqledstate state;
} aqled;
// Battery Status Identifiers
enum {
OK = 0,
LOW
};
int init_serial_port(char* tty);
void close_serial_port(int file_descriptor);
void set_pda_mode(bool mode);
bool pda_mode();
int generate_checksum(unsigned char* packet, int length);
void send_ack(int file_descriptor, unsigned char command);
void send_extended_ack(int fd, unsigned char ack_type, unsigned char command);
//void send_cmd(int file_descriptor, unsigned char cmd, unsigned char args);
int get_packet(int file_descriptor, unsigned char* packet);
//void close_serial_port(int file_descriptor, struct termios* oldtio);
//void process_status(void const * const ptr);
void process_status(unsigned char* ptr);
const char* get_packet_type(unsigned char* packet , int length);
void send_test_cmd(int fd, unsigned char destination, unsigned char b1, unsigned char b2, unsigned char b3);
void send_command(int fd, unsigned char destination, unsigned char b1, unsigned char b2, unsigned char b3);
void send_messaged(int fd, unsigned char destination, char *message);
#endif // AQ_SERIAL_H_

View File

@ -1,104 +0,0 @@
#ifndef AQUALINK_H_
#define AQUALINK_H_
#include <pthread.h>
#include <stdbool.h>
#include "aq_serial.h"
#include "aq_programmer.h"
#define TIME_CHECK_INTERVAL 3600
//#define TIME_CHECK_INTERVAL 600
#define ACCEPTABLE_TIME_DIFF 120
#define MAX_ZERO_READ_BEFORE_RECONNECT 500
#define TOTAL_BUTTONS 12
#define TEMP_UNKNOWN -999
//#define UNKNOWN TEMP_UNKNOWN
#define DATE_STRING_LEN 30
enum {
FAHRENHEIT,
CELSIUS,
UNKNOWN
};
typedef struct aqualinkkey
{
//int number;
//aqledstate *state;
aqled *led;
char *label;
char *name;
char *pda_label;
unsigned char code;
int dz_idx;
} aqkey;
//typedef struct ProgramThread ProgramThread; // Definition is later
struct programmingthread {
pthread_t *thread_id;
pthread_mutex_t thread_mutex;
pthread_cond_t thread_cond;
program_type ptype;
//void *thread_args;
};
typedef enum action_type {
NO_ACTION = -1,
POOL_HTR_SETOINT,
SPA_HTR_SETOINT,
FREEZE_SETPOINT,
SWG_SETPOINT
} action_type;
struct action {
action_type type;
time_t requested;
int value;
//char value[10];
};
struct aqualinkdata
{
//char crap[AQ_MSGLEN];
char version[AQ_MSGLEN];
char date[AQ_MSGLEN];
char time[AQ_MSGLEN];
//char datestr[DATE_STRING_LEN];
char last_message[AQ_MSGLONGLEN+1]; // NSF just temp for PDA crap
//char *last_message; // Be careful using this, can get core dumps.
char last_display_message[AQ_MSGLONGLEN+1];
//bool display_last_message;
unsigned char raw_status[AQ_PSTLEN];
aqled aqualinkleds[TOTAL_LEDS];
aqkey aqbuttons[TOTAL_BUTTONS];
int air_temp;
int pool_temp;
int spa_temp;
int temp_units;
bool single_device; // Pool or Spa only, not Pool & Spa (Thermostat setpoints are different)
int battery;
//int freeze_protection;
int frz_protect_set_point;
int pool_htr_set_point;
int spa_htr_set_point;
//unsigned char aq_command;
struct programmingthread active_thread;
struct action unactioned;
int swg_percent;
int swg_ppm;
unsigned char ar_swg_status;
int swg_delayed_percent;
bool simulate_panel;
aqledstate frz_protect_state;
//bool last_msg_was_status;
//bool ar_swg_connected;
};
#endif

File diff suppressed because it is too large Load Diff

592
config.c
View File

@ -1,592 +0,0 @@
/*
* Copyright (c) 2017 Shaun Feakes - All rights reserved
*
* You may use redistribute and/or modify this code under the terms of
* the GNU General Public License version 2 as published by the
* Free Software Foundation. For the terms of this license,
* see <http://www.gnu.org/licenses/>.
*
* You are free to use this software under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* https://github.com/sfeakes/aqualinkd
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <libgen.h>
#include <sys/ioctl.h>
//#include <sys/socket.h>
//#include <sys/time.h>
//#include <syslog.h>
//#include <unistd.h>
#include <netdb.h>
//#include <linux/if.h>
//#include <sys/types.h>
#include <unistd.h>
#include <net/if.h>
#include "config.h"
#include "utils.h"
#include "aq_serial.h"
#define MAXCFGLINE 256
char *generate_mqtt_id(char *buf, int len);
/*
* initialize data to default values
*/
void init_parameters (struct aqconfig * parms)
{
//char *p;
parms->serial_port = DEFAULT_SERIALPORT;
parms->log_level = DEFAULT_LOG_LEVEL;
parms->socket_port = DEFAULT_WEBPORT;
parms->web_directory = DEFAULT_WEBROOT;
//parms->device_id = strtoul(DEFAULT_DEVICE_ID, &p, 16);
parms->device_id = strtoul(DEFAULT_DEVICE_ID, NULL, 16);
//sscanf(DEFAULT_DEVICE_ID, "0x%x", &parms->device_id);
parms->override_freeze_protect = FALSE;
parms->mqtt_dz_sub_topic = DEFAULT_MQTT_DZ_OUT;
parms->mqtt_dz_pub_topic = DEFAULT_MQTT_DZ_IN;
parms->mqtt_aq_topic = DEFAULT_MQTT_AQ_TP;
parms->mqtt_server = DEFAULT_MQTT_SERVER;
parms->mqtt_user = DEFAULT_MQTT_USER;
parms->mqtt_passwd = DEFAULT_MQTT_PASSWD;
parms->dzidx_air_temp = TEMP_UNKNOWN;
parms->dzidx_pool_water_temp = TEMP_UNKNOWN;
parms->dzidx_spa_water_temp = TEMP_UNKNOWN;
//parms->dzidx_pool_thermostat = TEMP_UNKNOWN; // removed until domoticz has a better virtual thermostat
//parms->dzidx_spa_thermostat = TEMP_UNKNOWN; // removed until domoticz has a better virtual thermostat
parms->light_programming_mode = 0;
parms->light_programming_initial_on = 15;
parms->light_programming_initial_off = 12;
parms->light_programming_button = 0;
parms->deamonize = true;
parms->log_file = '\0';
parms->pda_mode = false;
parms->convert_mqtt_temp = true;
parms->convert_dz_temp = true;
parms->report_zero_spa_temp = false;
generate_mqtt_id(parms->mqtt_ID, MQTT_ID_LEN);
}
char *cleanalloc(char*str)
{
char *result;
str = cleanwhitespace(str);
result = (char*)malloc(strlen(str)+1);
strcpy ( result, str );
//printf("Result=%s\n",result);
return result;
}
/*
char *cleanallocindex(char*str, int index)
{
char *result;
int i;
int found = 1;
int loc1=0;
int loc2=strlen(str);
for(i=0;i<loc2;i++) {
if ( str[i] == ';' ) {
found++;
if (found == index)
loc1 = i;
else if (found == (index+1))
loc2 = i;
}
}
if (found < index)
return NULL;
// Trim leading & trailing spaces
loc1++;
while(isspace(str[loc1])) loc1++;
loc2--;
while(isspace(str[loc2])) loc2--;
// Allocate and copy
result = (char*)malloc(loc2-loc1+2*sizeof(char));
strncpy ( result, &str[loc1], loc2-loc1+1 );
result[loc2-loc1+1] = '\0';
return result;
}
*/
// Find the first network interface with valid MAC and put mac address into buffer upto length
bool mac(char *buf, int len)
{
struct ifreq s;
int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
struct if_nameindex *if_nidxs, *intf;
if_nidxs = if_nameindex();
if (if_nidxs != NULL)
{
for (intf = if_nidxs; intf->if_index != 0 || intf->if_name != NULL; intf++)
{
strcpy(s.ifr_name, intf->if_name);
if (0 == ioctl(fd, SIOCGIFHWADDR, &s))
{
int i;
if ( s.ifr_addr.sa_data[0] == 0 &&
s.ifr_addr.sa_data[1] == 0 &&
s.ifr_addr.sa_data[2] == 0 &&
s.ifr_addr.sa_data[3] == 0 &&
s.ifr_addr.sa_data[4] == 0 &&
s.ifr_addr.sa_data[5] == 0 ) {
continue;
}
for (i = 0; i < 6 && i * 2 < len; ++i)
{
sprintf(&buf[i * 2], "%02x", (unsigned char)s.ifr_addr.sa_data[i]);
}
return true;
}
}
}
return false;
}
char *generate_mqtt_id(char *buf, int len) {
extern char *__progname; // glibc populates this
int i;
strncpy(buf, basename(__progname), len);
i = strlen(buf);
if (i < len) {
buf[i++] = '_';
// If we can't get MAC to pad mqtt id then use PID
if (!mac(&buf[i], len - i)) {
sprintf(&buf[i], "%.*d", (len-i), getpid());
}
}
buf[len] = '\0';
return buf;
}
/*
void readCfg_OLD (struct aqconfig *config_parameters, struct aqualinkdata *aqdata, char *cfgFile)
{
FILE * fp ;
char bufr[MAXCFGLINE];
//const char delim[2] = ";";
//char *buf;
//int line = 0;
//int tokenindex = 0;
char *b_ptr;
if( (fp = fopen(cfgFile, "r")) != NULL){
while(! feof(fp)){
if (fgets(bufr, MAXCFGLINE, fp) != NULL)
{
b_ptr = &bufr[0];
char *indx;
// Eat leading whitespace
while(isspace(*b_ptr)) b_ptr++;
if ( b_ptr[0] != '\0' && b_ptr[0] != '#')
{
indx = strchr(b_ptr, '=');
if ( indx != NULL)
{
if (strncasecmp (b_ptr, "socket_port", 11) == 0) {
//config_parameters->socket_port = cleanint(indx+1);
config_parameters->socket_port = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "serial_port", 11) == 0) {
config_parameters->serial_port = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "log_level", 9) == 0) {
config_parameters->log_level = text2elevel(cleanalloc(indx+1));
// should fee mem here
} else if (strncasecmp (b_ptr, "device_id", 9) == 0) {
config_parameters->device_id = strtoul(cleanalloc(indx+1), NULL, 16);
// should fee mem here
} else if (strncasecmp (b_ptr, "web_directory", 13) == 0) {
config_parameters->web_directory = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "log_file", 8) == 0) {
config_parameters->log_file = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "mqtt_address", 12) == 0) {
config_parameters->mqtt_server = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "mqtt_dz_sub_topic", 17) == 0) {
config_parameters->mqtt_dz_sub_topic = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "mqtt_dz_pub_topic", 17) == 0) {
config_parameters->mqtt_dz_pub_topic = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "mqtt_aq_topic", 13) == 0) {
config_parameters->mqtt_aq_topic = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "mqtt_user", 9) == 0) {
config_parameters->mqtt_user = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "mqtt_passwd", 11) == 0) {
config_parameters->mqtt_passwd = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "air_temp_dzidx", 14) == 0) {
config_parameters->dzidx_air_temp = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "pool_water_temp_dzidx", 21) == 0) {
config_parameters->dzidx_pool_water_temp = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "spa_water_temp_dzidx", 20) == 0) {
config_parameters->dzidx_spa_water_temp = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "light_programming_mode", 21) == 0) {
config_parameters->light_programming_mode = atof(cleanalloc(indx+1)); // should free this
} else if (strncasecmp (b_ptr, "light_programming_initial_on", 28) == 0) {
config_parameters->light_programming_initial_on = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "light_programming_initial_off", 29) == 0) {
config_parameters->light_programming_initial_off = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "light_programming_button", 21) == 0) {
config_parameters->light_programming_button = strtoul(indx+1, NULL, 10) - 1;
} else if (strncasecmp (b_ptr, "SWG_percent_dzidx", 17) == 0) {
config_parameters->dzidx_swg_percent = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "SWG_PPM_dzidx", 13) == 0) {
config_parameters->dzidx_swg_ppm = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "SWG_Status_dzidx", 14) == 0) {
config_parameters->dzidx_swg_status = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "override_freeze_protect", 23) == 0) {
config_parameters->override_freeze_protect = text2bool(indx+1);
} else if (strncasecmp (b_ptr, "pda_mode", 8) == 0) {
config_parameters->pda_mode = text2bool(indx+1);
set_pda_mode(config_parameters->pda_mode);
} else if (strncasecmp (b_ptr, "convert_mqtt_temp_to_c", 22) == 0) {
config_parameters->convert_mqtt_temp = text2bool(indx+1);
} else if (strncasecmp (b_ptr, "convert_dz_temp_to_c", 21) == 0) {
config_parameters->convert_dz_temp = text2bool(indx+1);
} else if (strncasecmp (b_ptr, "flash_mqtt_buttons", 18) == 0) {
config_parameters->flash_mqtt_buttons = text2bool(indx+1);
} else if (strncasecmp (b_ptr, "report_zero_spa_temp", 20) == 0) {
config_parameters->report_zero_spa_temp = text2bool(indx+1);
} else if (strncasecmp (b_ptr, "button_", 7) == 0) {
int num = strtoul(b_ptr+7, NULL, 10) - 1;
//logMessage (LOG_DEBUG, "Button %d\n", strtoul(b_ptr+7, NULL, 10));
if (strncasecmp (b_ptr+9, "_label", 6) == 0) {
//logMessage (LOG_DEBUG, " Label %s\n", cleanalloc(indx+1));
aqdata->aqbuttons[num].label = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr+9, "_dzidx", 6) == 0) {
//logMessage (LOG_DEBUG, " dzidx %d\n", strtoul(indx+1, NULL, 10));
aqdata->aqbuttons[num].dz_idx = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr+9, "_PDA_label", 10) == 0) {
//logMessage (LOG_DEBUG, " dzidx %d\n", strtoul(indx+1, NULL, 10));
aqdata->aqbuttons[num].pda_label = cleanalloc(indx+1);
}
}
}
//line++;
}
}
}
fclose(fp);
} else {
displayLastSystemError(cfgFile);
exit (EXIT_FAILURE);
}
}
*/
bool setConfigValue(struct aqconfig *config_parameters, struct aqualinkdata *aqdata, char *param, char *value) {
bool rtn = false;
if (strncasecmp(param, "socket_port", 11) == 0) {
config_parameters->socket_port = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "serial_port", 11) == 0) {
config_parameters->serial_port = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "log_level", 9) == 0) {
config_parameters->log_level = text2elevel(cleanalloc(value));
rtn=true;
} else if (strncasecmp(param, "device_id", 9) == 0) {
config_parameters->device_id = strtoul(cleanalloc(value), NULL, 16);
rtn=true;
} else if (strncasecmp(param, "web_directory", 13) == 0) {
config_parameters->web_directory = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "log_file", 8) == 0) {
config_parameters->log_file = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "mqtt_address", 12) == 0) {
config_parameters->mqtt_server = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "mqtt_dz_sub_topic", 17) == 0) {
config_parameters->mqtt_dz_sub_topic = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "mqtt_dz_pub_topic", 17) == 0) {
config_parameters->mqtt_dz_pub_topic = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "mqtt_aq_topic", 13) == 0) {
config_parameters->mqtt_aq_topic = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "mqtt_user", 9) == 0) {
config_parameters->mqtt_user = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "mqtt_passwd", 11) == 0) {
config_parameters->mqtt_passwd = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "air_temp_dzidx", 14) == 0) {
config_parameters->dzidx_air_temp = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "pool_water_temp_dzidx", 21) == 0) {
config_parameters->dzidx_pool_water_temp = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "spa_water_temp_dzidx", 20) == 0) {
config_parameters->dzidx_spa_water_temp = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "light_programming_mode", 21) == 0) {
config_parameters->light_programming_mode = atof(cleanalloc(value)); // should free this
rtn=true;
} else if (strncasecmp(param, "light_programming_initial_on", 28) == 0) {
config_parameters->light_programming_initial_on = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "light_programming_initial_off", 29) == 0) {
config_parameters->light_programming_initial_off = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "light_programming_button", 21) == 0) {
config_parameters->light_programming_button = strtoul(value, NULL, 10) - 1;
rtn=true;
} else if (strncasecmp(param, "SWG_percent_dzidx", 17) == 0) {
config_parameters->dzidx_swg_percent = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "SWG_PPM_dzidx", 13) == 0) {
config_parameters->dzidx_swg_ppm = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "SWG_Status_dzidx", 14) == 0) {
config_parameters->dzidx_swg_status = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "override_freeze_protect", 23) == 0) {
config_parameters->override_freeze_protect = text2bool(value);
rtn=true;
} else if (strncasecmp(param, "pda_mode", 8) == 0) {
config_parameters->pda_mode = text2bool(value);
set_pda_mode(config_parameters->pda_mode);
rtn=true;
} else if (strncasecmp(param, "convert_mqtt_temp_to_c", 22) == 0) {
config_parameters->convert_mqtt_temp = text2bool(value);
rtn=true;
} else if (strncasecmp(param, "convert_dz_temp_to_c", 20) == 0) {
config_parameters->convert_dz_temp = text2bool(value);
rtn=true;
} else if (strncasecmp(param, "flash_mqtt_buttons", 18) == 0) {
config_parameters->flash_mqtt_buttons = text2bool(value);
rtn=true;
} else if (strncasecmp(param, "report_zero_spa_temp", 20) == 0) {
config_parameters->report_zero_spa_temp = text2bool(value);
rtn=true;
}
// removed until domoticz has a better virtual thermostat
/*else if (strncasecmp (param, "pool_thermostat_dzidx", 21) == 0) {
config_parameters->dzidx_pool_thermostat = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp (param, "spa_thermostat_dzidx", 20) == 0) {
config_parameters->dzidx_spa_thermostat = strtoul(value, NULL, 10);
rtn=true;
} */
else if (strncasecmp(param, "button_", 7) == 0) {
int num = strtoul(param + 7, NULL, 10) - 1;
if (strncasecmp(param + 9, "_label", 6) == 0) {
aqdata->aqbuttons[num].label = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param + 9, "_dzidx", 6) == 0) {
aqdata->aqbuttons[num].dz_idx = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param + 9, "_PDA_label", 10) == 0) {
aqdata->aqbuttons[num].pda_label = cleanalloc(value);
rtn=true;
}
}
return rtn;
}
void readCfg (struct aqconfig *config_parameters, struct aqualinkdata *aqdata, char *cfgFile)
{
FILE * fp ;
char bufr[MAXCFGLINE];
//const char delim[2] = ";";
//char *buf;
//int line = 0;
//int tokenindex = 0;
char *b_ptr;
config_parameters->config_file = cleanalloc(cfgFile);
if( (fp = fopen(cfgFile, "r")) != NULL){
while(! feof(fp)){
if (fgets(bufr, MAXCFGLINE, fp) != NULL)
{
b_ptr = &bufr[0];
char *indx;
// Eat leading whitespace
while(isspace(*b_ptr)) b_ptr++;
if ( b_ptr[0] != '\0' && b_ptr[0] != '#')
{
indx = strchr(b_ptr, '=');
if ( indx != NULL)
{
if ( ! setConfigValue(config_parameters, aqdata, b_ptr, indx+1))
logMessage(LOG_ERR, "Unknown config parameter '%.*s'\n",strlen(b_ptr)-1, b_ptr);
}
}
}
}
fclose(fp);
} else {
/* error processing, couldn't open file */
displayLastSystemError(cfgFile);
exit (EXIT_FAILURE);
}
}
//DEBUG_DERIAL, DEBUG, INFO, NOTICE, WARNING, ERROR
char *errorlevel2text(int level)
{
switch(level) {
case LOG_DEBUG_SERIAL:
return "DEBUG_SERIAL";
break;
case LOG_DEBUG:
return "DEBUG";
break;
case LOG_INFO:
return "INFO";
break;
case LOG_NOTICE:
return "NOTICE";
break;
case LOG_WARNING:
return "WARNING";
break;
case LOG_ERR:
default:
return "ERROR";
break;
}
return "";
}
bool remount_root_ro(bool readonly) {
// NSF Check if config is RO_ROOT set
/*
if (readonly) {
logMessage(LOG_INFO, "reMounting root RO\n");
mount (NULL, "/", NULL, MS_REMOUNT | MS_RDONLY, NULL);
return true;
} else {
struct statvfs fsinfo;
statvfs("/", &fsinfo);
if ((fsinfo.f_flag & ST_RDONLY) == 0) // We are readwrite, ignore
return false;
logMessage(LOG_INFO, "reMounting root RW\n");
mount (NULL, "/", NULL, MS_REMOUNT, NULL);
return true;
}
*/
return true;
}
void writeCharValue (FILE *fp, char *msg, char *value)
{
if (value == NULL)
fprintf(fp, "#%s = \n",msg);
else
fprintf(fp, "%s = %s\n", msg, value);
}
void writeIntValue (FILE *fp, char *msg, int value)
{
if (value <= 0)
fprintf(fp, "#%s = \n",msg);
else
fprintf(fp, "%s = %d\n", msg, value);
}
bool writeCfg (struct aqconfig *config_parameters, struct aqualinkdata *aqdata)
{
FILE *fp;
int i;
bool fs = remount_root_ro(false);
fp = fopen(config_parameters->config_file, "w");
if (fp == NULL) {
logMessage(LOG_ERR, "Open config file failed '%s'\n", config_parameters->config_file);
remount_root_ro(true);
//fprintf(stdout, "Open file failed 'sprinkler.cron'\n");
return false;
}
fprintf(fp, "#***** AqualinkD configuration *****\n");
fprintf(fp, "socket_port = %s\n", config_parameters->socket_port);
fprintf(fp, "serial_port = %s\n", config_parameters->serial_port);
fprintf(fp, "device_id = 0x%02hhx\n", config_parameters->device_id);
writeCharValue(fp, "log_level", errorlevel2text(config_parameters->log_level));
writeCharValue(fp, "web_directory", config_parameters->web_directory);
writeCharValue(fp, "log_file", config_parameters->log_file);
fprintf(fp, "pda_mode = %s\n", bool2text(config_parameters->pda_mode));
fprintf(fp, "\n#** MQTT Configuration **\n");
writeCharValue(fp, "mqtt_address", config_parameters->mqtt_server);
writeCharValue(fp, "mqtt_dz_sub_topic", config_parameters->mqtt_dz_sub_topic);
writeCharValue(fp, "mqtt_dz_pub_topic", config_parameters->mqtt_dz_pub_topic);
writeCharValue(fp, "mqtt_aq_topic", config_parameters->mqtt_aq_topic);
writeCharValue(fp, "mqtt_user", config_parameters->mqtt_user);
writeCharValue(fp, "mqtt_passwd", config_parameters->mqtt_passwd);
fprintf(fp, "\n#** General **\n");
fprintf(fp, "convert_mqtt_temp_to_c = %s\n", bool2text(config_parameters->convert_mqtt_temp));
fprintf(fp, "override_freeze_protect = %s\n", bool2text(config_parameters->override_freeze_protect));
fprintf(fp, "flash_mqtt_buttons = %s\n", bool2text(config_parameters->flash_mqtt_buttons));
fprintf(fp, "report_zero_spa_temp = %s\n", bool2text(config_parameters->report_zero_spa_temp));
fprintf(fp, "\n#** Programmable light **\n");
if (config_parameters->light_programming_button <= 0) {
fprintf(fp, "#light_programming_button = %d\n", config_parameters->light_programming_button);
fprintf(fp, "#light_programming_mode = %f\n", config_parameters->light_programming_mode);
fprintf(fp, "#light_programming_initial_on = %d\n", config_parameters->light_programming_initial_on);
fprintf(fp, "#light_programming_initial_off = %d\n", config_parameters->light_programming_initial_off);
} else {
fprintf(fp, "light_programming_button = %d\n", config_parameters->light_programming_button);
fprintf(fp, "light_programming_mode = %f\n", config_parameters->light_programming_mode);
fprintf(fp, "light_programming_initial_on = %d\n", config_parameters->light_programming_initial_on);
fprintf(fp, "light_programming_initial_off = %d\n", config_parameters->light_programming_initial_off);
}
fprintf(fp, "\n#** Domoticz **\n");
fprintf(fp, "convert_dz_temp_to_c = %s\n", bool2text(config_parameters->convert_dz_temp));
writeIntValue(fp, "air_temp_dzidx", config_parameters->dzidx_air_temp);
writeIntValue(fp, "pool_water_temp_dzidx", config_parameters->dzidx_pool_water_temp);
writeIntValue(fp, "spa_water_temp_dzidx", config_parameters->dzidx_spa_water_temp);
writeIntValue(fp, "SWG_percent_dzidx", config_parameters->dzidx_swg_percent);
writeIntValue(fp, "SWG_PPM_dzidx", config_parameters->dzidx_swg_ppm);
writeIntValue(fp, "SWG_Status_dzidx", config_parameters->dzidx_swg_status);
fprintf(fp, "\n#** Buttons **\n");
for (i=0; i < TOTAL_BUTTONS; i++)
{
fprintf(fp, "button_%.2d_label = %s\n", i+1, aqdata->aqbuttons[i].label);
if (aqdata->aqbuttons[i].dz_idx > 0)
fprintf(fp, "button_%.2d_dzidx = %d\n", i+1, aqdata->aqbuttons[i].dz_idx);
if (aqdata->aqbuttons[i].pda_label != NULL)
fprintf(fp, "button_%.2d_PDA_label = %s\n", i+1, aqdata->aqbuttons[i].pda_label);
}
fclose(fp);
remount_root_ro(fs);
return true;
}

View File

@ -1,71 +0,0 @@
#ifndef CONFIG_H_
#define CONFIG_H_
#include "utils.h"
#include "aq_serial.h"
#include "aqualink.h"
#define DEFAULT_LOG_LEVEL 10
#define DEFAULT_WEBPORT "6580"
#define DEFAULT_WEBROOT "./"
#define DEFAULT_SERIALPORT "/dev/ttyUSB0"
#define DEFAULT_DEVICE_ID "0x0a"
#define DEFAULT_MQTT_DZ_IN NULL
#define DEFAULT_MQTT_DZ_OUT NULL
#define DEFAULT_MQTT_AQ_TP NULL
#define DEFAULT_MQTT_SERVER NULL
#define DEFAULT_MQTT_USER NULL
#define DEFAULT_MQTT_PASSWD NULL
#define MQTT_ID_LEN 20
struct aqconfig
{
char *config_file;
char *serial_port;
unsigned int log_level;
char *socket_port;
char *web_directory;
unsigned char device_id;
bool deamonize;
char *log_file;
char *mqtt_dz_sub_topic;
char *mqtt_dz_pub_topic;
char *mqtt_aq_topic;
char *mqtt_server;
char *mqtt_user;
char *mqtt_passwd;
char mqtt_ID[MQTT_ID_LEN];
int dzidx_air_temp;
int dzidx_pool_water_temp;
int dzidx_spa_water_temp;
int dzidx_swg_percent;
int dzidx_swg_ppm;
int dzidx_swg_status;
float light_programming_mode;
int light_programming_initial_on;
int light_programming_initial_off;
int light_programming_button;
bool override_freeze_protect;
bool pda_mode;
bool convert_mqtt_temp;
bool convert_dz_temp;
bool flash_mqtt_buttons;
bool report_zero_spa_temp;
//int dzidx_pool_thermostat; // Domoticz virtual thermostats are crap removed until better
//int dzidx_spa_thermostat; // Domoticz virtual thermostats are crap removed until better
//char mqtt_pub_topic[250];
//char *mqtt_pub_tp_ptr = mqtt_pub_topic[];
};
void init_parameters (struct aqconfig * parms);
//bool parse_config (struct aqconfig * parms, char *cfgfile);
//void readCfg (struct aqconfig *config_parameters, char *cfgFile);
void readCfg (struct aqconfig *config_parameters, struct aqualinkdata *aqualink_data, char *cfgFile);
bool writeCfg (struct aqconfig *config_parameters, struct aqualinkdata *aqdata);
bool setConfigValue(struct aqconfig *config_parameters, struct aqualinkdata *aqdata, char *param, char *value);
#endif

66
docker/Dockerfile Executable file
View File

@ -0,0 +1,66 @@
#####################################
#
# Build container
# The most basic build for aqualinkd
#
# env AQUALINKD_VERSION must be passed to this
#
#####################################
FROM debian:bookworm AS aqualinkd-build
#VOLUME ["/aqualinkd-build"]
RUN apt-get update && \
apt-get -y install curl make gcc libsystemd-dev
# Seup working dir
RUN mkdir /home/AqualinkD
WORKDIR /home/AqualinkD
ARG AQUALINKD_VERSION
RUN curl -sL "https://github.com/aqualinkd/AqualinkD/archive/refs/tags/$AQUALINKD_VERSION.tar.gz" | tar xz --strip-components=1
# Get latest release
#RUN curl -sL $(curl -s https://api.github.com/repos/aqualinkd/AqualinkD/releases/latest | grep "tarball_url" | cut -d'"' -f4) | tar xz --strip-components=1
# Build aqualinkd
RUN make clean && \
make container
#####################################
#
# Runtime container
#
#####################################
FROM debian:bookworm-slim AS aqualinkd
ARG AQUALINKD_VERSION
RUN apt-get update && \
apt-get install -y cron curl socat && \
apt-get clean
# Set cron to read local.d
RUN sed -i '/EXTRA_OPTS=.-l./s/^#//g' /etc/default/cron
# Add Open Container Initiative (OCI) annotations.
# See: https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.title="AqualinkD"
LABEL org.opencontainers.image.url="https://hub.docker.com/repository/docker/aqualinkd/aqualinkd/general"
LABEL org.opencontainers.image.source="https://github.com/aqualinkd/AqualinkD"
LABEL org.opencontainers.image.documentation="https://github.com/aqualinkd/AqualinkD"
LABEL org.opencontainers.image.version=$AQUALINKD_VERSION
COPY --from=aqualinkd-build /home/AqualinkD/release/aqualinkd /usr/local/bin/aqualinkd
COPY --from=aqualinkd-build /home/AqualinkD/release/serial_logger /usr/local/bin/serial_logger
COPY --from=aqualinkd-build /home/AqualinkD/web/ /var/www/aqualinkd/
COPY --from=aqualinkd-build /home/AqualinkD/release/aqualinkd.conf /etc/aqualinkd.conf
#COPY --from=aqualinkd-build /home/AqualinkD/docker/aqualinkd-docker.cmd /usr/local/bin/aqualinkd-docker
RUN curl -s -o /usr/local/bin/aqualinkd-docker https://raw.githubusercontent.com/aqualinkd/AqualinkD/master/docker/aqualinkd-docker.cmd && \
chmod +x /usr/local/bin/aqualinkd-docker
CMD ["sh", "-c", "/usr/local/bin/aqualinkd-docker"]

106
docker/Dockerfile.buildx Normal file
View File

@ -0,0 +1,106 @@
#####################################
#
# Create AqualinkD container for release (includes AMD64 and ARM64 for >Pi4 with 64 bit os and Linux PC)
# Build container for buildx
# This should support building on any host platform, but only supports output platform of amd64 & arm64
#
# Enable multi platform
# docker buildx create --use --platform=linux/arm64,linux/amd64 --name multi-platform-builder
# docker buildx inspect --bootstrap
#
# Build
# docker buildx build --platform=linux/amd64,linux/arm64 --output=./crap --file /Dockerfile.test -t aqualinkd-test .
# docker buildx build --platform=linux/amd64,linux/arm64 --file Dockerfile.test --output type=docker -t aqualinkd-test .
# docker build --file Dockerfile.test --progress=plain -t aqualinkd-test .
#
# adding --progress=plain helps with debug
#
# Clean the build env and start again
# docker buildx prune
# docker builder prune
#
#
# docker build -f ./Dockerfile.buildrelease .
#
#####################################
# Starting with base debian:bookworm and installing build-essential seems to be quicker than starting with gcc:bookworm
#FROM --platform=$BUILDPLATFORM gcc:12-bookworm AS aqualinkd-build
FROM --platform=$BUILDPLATFORM debian:bookworm AS aqualinkd-build
ARG BUILDARCH
ARG TARGETARCH
# Print all buildx variables
RUN echo "Build Arch $BUILDARCH" && \
echo "Tagert OS $TARGETOS"
# Setup build env, using toolchain for all builds, even native, since make this Dockerfile cleaner
# and no need to use bash if statments.
# Need to be careful on install order, so using two commands
RUN apt-get update && \
apt-get install -y \
make \
curl \
gcc-aarch64-linux-gnu \
gcc-x86-64-linux-gnu
RUN dpkg --add-architecture arm64 && \
dpkg --add-architecture amd64 && \
apt-get update && \
apt-get install -y \
libsystemd-dev:arm64 \
libsystemd-dev:amd64
RUN mkdir /home/AqualinkD
WORKDIR /home/AqualinkD
ARG AQUALINKD_VERSION
RUN curl -sL "https://github.com/aqualinkd/AqualinkD/archive/refs/tags/$AQUALINKD_VERSION.tar.gz" | tar xz --strip-components=1
# Get latest release
#RUN curl -sL $(curl -s https://api.github.com/repos/aqualinkd/AqualinkD/releases/latest | grep "tarball_url" | cut -d'"' -f4) | tar xz --strip-components=1
# Make AqualinkD
RUN make clean && \
make container-$TARGETARCH;
#####################################
#
# Runtime container(s)
#
#####################################
FROM debian:bookworm-slim AS aqualinkd
RUN apt-get update \
&& apt-get install -y cron curl socat
# Set cron to read local.d
RUN sed -i '/EXTRA_OPTS=.-l./s/^#//g' /etc/default/cron
#Add Open Container Initiative (OCI) annotations.
#See: https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.title="AqualinkD"
LABEL org.opencontainers.image.url="https://hub.docker.com/repository/docker/sfeakes/aqualinkd/general"
LABEL org.opencontainers.image.source="https://github.com/aqualinkd/AqualinkD"
LABEL org.opencontainers.image.documentation="https://github.com/aqualinkd/AqualinkD"
LABEL org.opencontainers.image.version=$AQUALINKD_VERSION
EXPOSE 80/tcp
COPY --from=aqualinkd-build /home/AqualinkD/release/aqualinkd /usr/local/bin/aqualinkd
COPY --from=aqualinkd-build /home/AqualinkD/release/serial_logger /usr/local/bin/serial_logger
COPY --from=aqualinkd-build /home/AqualinkD/web/ /var/www/aqualinkd/
COPY --from=aqualinkd-build /home/AqualinkD/release/aqualinkd.conf /etc/aqualinkd.conf
COPY --from=aqualinkd-build /home/AqualinkD/docker/aqualinkd-docker.cmd /usr/local/bin/aqualinkd-docker
RUN chmod +x /usr/local/bin/aqualinkd-docker
CMD ["sh", "-c", "/usr/local/bin/aqualinkd-docker"]

294
docker/Dockerfile.releaseBinaries Executable file
View File

@ -0,0 +1,294 @@
#####################################
#
# Build container to compile AqualnkD Release binaries (armhf and arm64)
#
# armhf is 32 bit armv6l (armhf) stretch and newer - work on all Pi's running 32bit (Pi1 to Pi4)
# arm64 is 64 bit aarch64 buster and newer - work on Pi3/Pi4/2w running 64bit os
#
# To build
# docker build -f Dockerfile.releaseBinaries -t aqualinkd-releasebin .
# For better debug logs use --progress=plain
# docker build --progress=plain -f Dockerfile.releaseBinaries -t aqualinkd-releasebin .
#
# To Run
# docker run -it --mount type=bind,source=./build,target=/build aqualinkd-releasebin bash
#
# clean method
# docker system prune
# docker buildx prune <- just build env
#
# armhf =
# COLLECT_GCC=arm-linux-gnueabihf-gcc
# COLLECT_LTO_WRAPPER=/opt/cross-pi-gcc/libexec/gcc/arm-linux-gnueabihf/6.3.0/lto-wrapper
# Target: arm-linux-gnueabihf
# Configured with: ../gcc-6.3.0/configure --prefix=/opt/cross-pi-gcc --target=arm-linux-gnueabihf --enable-languages=c,c++,fortran --with-arch=armv6 --with-fpu=vfp --with-float=hard --disable-multilib --enable-linker-build-id
# Thread model: posix
# gcc version 6.3.0 (GCC)
# GLIBC version 2.24
#
# arm64 =
# COLLECT_GCC=aarch64-linux-gnu-gcc
# COLLECT_LTO_WRAPPER=/usr/lib/gcc-cross/aarch64-linux-gnu/8/lto-wrapper
# Target: aarch64-linux-gnu
# Configured with: ../src/configure -v --with-pkgversion='Debian 8.3.0-2' --with-bugurl=file:///usr/share/doc/gcc-8/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-libquadmath --disable-libquadmath-support --enable-plugin --enable-default-pie --with-system-zlib --disable-libphobos --enable-multiarch --enable-fix-cortex-a53-843419 --disable-werror --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=aarch64-linux-gnu --program-prefix=aarch64-linux-gnu- --includedir=/usr/aarch64-linux-gnu/include
# Thread model: posix
# gcc version 8.3.0 (Debian 8.3.0-2)
# GLIBC 2.28-10+deb10u3
#####################################
FROM debian:buster
# ############
# Get arm64 build environment.
#
RUN apt-get update && \
apt-get install -y \
build-essential \
gcc-aarch64-linux-gnu \
binutils-arm-linux-gnueabi \
file
RUN dpkg --add-architecture arm64
RUN apt-get update && \
apt-get install -y libsystemd-dev:arm64
# ############
# Get armhf build environment
# prebuilt armhf doesn't support hard float, (or something that causes it to fail on armhf machines)
#RUN apt-get install -y \
# gcc-arm-linux-gnueabihf \
# binutils-arm-linux-gnueabihf
#RUN dpkg --add-architecture armhf
#RUN apt-get update && \
# apt-get install -y libsystemd-dev:armhf
# So we need to build arnhf our selves. Since we are doing that, using debian/rasbian stretch versions of
# everthing for best compatibality
ENV GCC_VERSION gcc-6.3.0
ENV GLIBC_VERSION glibc-2.24
ENV BINUTILS_VERSION binutils-2.28
ARG DEBIAN_FRONTEND=noninteractive
# Install some tools and compilers + clean up
RUN apt-get update && \
#apt-get install -y rsync git wget gcc-6 g++-6 cmake gdb gdbserver bzip2 && \
apt-get install -y rsync git wget cmake gdb gdbserver bzip2 && \
apt-get clean autoclean && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
# Use GCC 6 as the default
#RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 999 \
# && update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-6 999 \
# && update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-6 999 \
# && update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-6 999
# Add a user called `develop`
RUN useradd -ms /bin/bash develop
RUN echo "develop ALL=(ALL:ALL) ALL" >> /etc/sudoers
WORKDIR /home/develop
# Download and extract GCC
RUN wget https://ftp.gnu.org/gnu/gcc/${GCC_VERSION}/${GCC_VERSION}.tar.gz && \
tar xf ${GCC_VERSION}.tar.gz && \
rm ${GCC_VERSION}.tar.gz
# Download and extract LibC
RUN wget https://ftp.gnu.org/gnu/libc/${GLIBC_VERSION}.tar.bz2 && \
tar xjf ${GLIBC_VERSION}.tar.bz2 && \
rm ${GLIBC_VERSION}.tar.bz2
# Download and extract BinUtils
RUN wget https://ftp.gnu.org/gnu/binutils/${BINUTILS_VERSION}.tar.bz2 && \
tar xjf ${BINUTILS_VERSION}.tar.bz2 && \
rm ${BINUTILS_VERSION}.tar.bz2
# Download the GCC prerequisites
RUN cd ${GCC_VERSION} && contrib/download_prerequisites && rm *.tar.*
#RUN cd gcc-9.2.0 && contrib/download_prerequisites && rm *.tar.*
# Build BinUtils
RUN mkdir -p /opt/cross-pi-gcc
WORKDIR /home/develop/build-binutils
RUN ../${BINUTILS_VERSION}/configure \
--prefix=/opt/cross-pi-gcc --target=arm-linux-gnueabihf \
--with-arch=armv6 --with-fpu=vfp --with-float=hard \
--disable-multilib
RUN make -j$(nproc)
RUN make install
# Apply batch to GCC
# https://github.com/qca/open-ath9k-htc-firmware/issues/135
WORKDIR /home/develop
RUN sed -i '1474s/file ==/file[0] ==/' gcc-6.3.0/gcc/ubsan.c
# Build the first part of GCC
WORKDIR /home/develop/build-gcc
RUN ../${GCC_VERSION}/configure \
--prefix=/opt/cross-pi-gcc \
--target=arm-linux-gnueabihf \
--enable-languages=c,c++,fortran \
--with-arch=armv6 --with-fpu=vfp --with-float=hard \
--disable-multilib \
--enable-linker-build-id
RUN make -j$(nproc) 'LIMITS_H_TEST=true' all-gcc
RUN make install-gcc
ENV PATH=/opt/cross-pi-gcc/bin:${PATH}
# Install dependencies
RUN apt-get update && \
apt-get install -y gawk bison python3 && \
apt-get clean autoclean && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
# Download and install the Linux headers
WORKDIR /home/develop
# Should probably use below and change branch. Known to build with rpi-6.1.y or rpi-6.9.y rpi-6.12.y
#RUN git clone -b <branch> --depth=1 https://github.com/raspberrypi/linux
RUN git clone --depth=1 https://github.com/raspberrypi/linux
WORKDIR /home/develop/linux
ENV KERNEL=kernel7
RUN make ARCH=arm INSTALL_HDR_PATH=/opt/cross-pi-gcc/arm-linux-gnueabihf headers_install
# Build GLIBC
WORKDIR /home/develop/build-glibc
RUN ../${GLIBC_VERSION}/configure \
--prefix=/opt/cross-pi-gcc/arm-linux-gnueabihf \
--build=$MACHTYPE --host=arm-linux-gnueabihf --target=arm-linux-gnueabihf \
--with-arch=armv6 --with-fpu=vfp --with-float=hard \
--with-headers=/opt/cross-pi-gcc/arm-linux-gnueabihf/include \
--disable-multilib libc_cv_forced_unwind=yes
RUN make install-bootstrap-headers=yes install-headers
RUN make -j8 csu/subdir_lib
RUN install csu/crt1.o csu/crti.o csu/crtn.o /opt/cross-pi-gcc/arm-linux-gnueabihf/lib
RUN arm-linux-gnueabihf-gcc -nostdlib -nostartfiles -shared -x c /dev/null \
-o /opt/cross-pi-gcc/arm-linux-gnueabihf/lib/libc.so
RUN touch /opt/cross-pi-gcc/arm-linux-gnueabihf/include/gnu/stubs.h
# Continue building GCC
WORKDIR /home/develop/build-gcc
RUN make -j$(nproc) all-target-libgcc
RUN make install-target-libgcc
# Finish building GLIBC
WORKDIR /home/develop/build-glibc
RUN make -j$(nproc)
RUN make install
# Finish building GCC
WORKDIR /home/develop/build-gcc
RUN make -j$(nproc)
RUN make install
# Download systemd and it's dependancys.
RUN mkdir -p /home/develop/packages
WORKDIR /home/develop/packages
####################
# Manually libsystemd-dev and all it's depandancys
# Commented out ones are what I really want, but couldn;t find.
#####RUN wget https://archive.debian.org/debian/pool/main/s/systemd/libsystemd-dev_232-25+deb9u14_armhf.deb
#RUN wget https://archive.debian.org/debian/pool/main/s/systemd/libsystemd-dev_232-25+deb9u12_armhf.deb
#####RUN wget https://archive.debian.org/debian/pool/main/s/systemd/libsystemd0_232-25+deb9u14_armhf.deb
#RUN wget https://archive.debian.org/debian/pool/main/s/systemd/libsystemd0_232-25+deb9u12_armhf.deb
#RUN wget https://archive.debian.org/debian/pool/main/g/glibc/libc6_2.24-11+deb9u4_armhf.deb
#RUN wget https://archive.debian.org/debian/pool/main/libg/libgpg-error/libgpg-error0_1.26-2_armhf.deb
#####RUN wget https://archive.debian.org/debian/pool/main/x/xz-utils/liblzma5_5.2.2-1.2+deb9u1_armhf.deb
#RUN wget https://archive.debian.org/debian/pool/main/x/xz-utils/liblzma5_5.2.2-1.2+b1_armhf.deb
#RUN wget https://archive.debian.org/debian/pool/main/libs/libselinux/libselinux1_2.6-3+b3_armhf.deb
#####RUN wget https://archive.debian.org/debian/pool/main/libg/libgcrypt20//libgcrypt20_1.7.6-2+deb9u4_armhf.deb
#RUN wget https://archive.debian.org/debian/pool/main/libg/libgcrypt20//libgcrypt20_1.7.6-2+deb9u3_armhf.deb
#####RUN wget https://archive.debian.org/debian/pool/main/l/lz4/liblz4-1_0.0~r131-2+deb9u1_armhf.deb
#RUN wget https://archive.debian.org/debian/pool/main/l/lz4/liblz4-1_0.0~r131-2+b1_armhf.deb
#####RUN wget https://archive.debian.org/debian/pool/main/p/pcre3/libpcre3_2%3a8.39-3_armhf.deb
#RUN wget https://archive.debian.org/debian/pool/main/p/pcre3/libpcre3_8.39-3_armhf.deb
#
# Now we have all packaged, let's unpack them.
#
# Install all packages into /opt/cross-pi-gcc/arm-linux-gnueabihf
#RUN for file in *; do dpkg-deb -x $file /opt/cross-pi-gcc/arm-linux-gnueabihf; done
#
####################
# Rather than manually, Let's do some modifications to apt and get that working (kinda)
# Get just enough for apt-get and dpk to run. apt-get doesn't actually work, just enough to download
ENV APT_ROOT=/opt/cross-pi-gcc/apt-armhf
RUN mkdir -p $APT_ROOT
#RUN APT_ROOT=/opt/cross-pi-gcc/apt-armhf; export APT_ROOT
RUN mkdir -p $APT_ROOT/etc/apt/sources.list.d/
RUN mkdir -p $APT_ROOT/var/lib/dpkg/updates/
RUN mkdir -p $APT_ROOT/var/lib/dpkg/info
RUN mkdir -p $APT_ROOT/var/cache/apt/archives/partial
RUN mkdir -p $APT_ROOT/var/log/apt/
#mkdir -p $APT_ROOT/usr/share/
RUN echo "deb http://archive.debian.org/debian/ stretch main contrib non-free" > $APT_ROOT/etc/apt/sources.list
RUN echo "deb http://archive.debian.org/debian/ stretch-proposed-updates main contrib non-free" >> $APT_ROOT/etc/apt/sources.list
RUN echo "deb http://archive.debian.org/debian-security stretch/updates main contrib non-free" >> $APT_ROOT/etc/apt/sources.list
RUN touch $APT_ROOT/var/lib/dpkg/status
RUN ln -s /etc/apt/trusted.gpg.d $APT_ROOT/etc/apt/
RUN ln -s /etc/apt/preferences.d $APT_ROOT/etc/apt/
RUN ln -s /etc/apt/auth.conf.d $APT_ROOT/etc/apt/
# needed for download
RUN dpkg --add-architecture armhf
# needed for install
RUN dpkg --root=$APT_ROOT --add-architecture armhf
RUN apt -o Dir=$APT_ROOT update
RUN apt -o Dir=$APT_ROOT download libsystemd-dev:armhf \
libsystemd0:armhf \
libc6:armhf \
libgcrypt20:armhf \
liblz4-1:armhf \
liblzma5:armhf \
libselinux1:armhf \
libpcre3:armhf \
libgpg-error0:armhf
############
# Now we have all packaged, let's unpack them.
# Install all packages into /opt/cross-pi-gcc/arm-linux-gnueabihf
# Could use `dpkg --root=$APT_ROOT --force-all -i` in below, but extract works without any warnings.
RUN for file in *; do dpkg -x $file /opt/cross-pi-gcc/arm-linux-gnueabihf; done
# the above will ge installed in /opt/cross-pi-gcc/arm-linux-gnueabihf/lib/arm-linux-gnueabihf,
# and we need them in /opt/cross-pi-gcc/arm-linux-gnueabihf/lib/, so make come links.
WORKDIR /opt/cross-pi-gcc/arm-linux-gnueabihf/lib
RUN for file in ./arm-linux-gnueabihf/*; do ln -s $file ./`basename $file` 2> /dev/null; done; exit 0
# liblz4.so.1 is installed in a different directory, so link that as well.
RUN ln -s /opt/cross-pi-gcc/arm-linux-gnueabihf/usr/lib/arm-linux-gnueabihf/liblz4.so.1 /opt/cross-pi-gcc/arm-linux-gnueabihf/lib/liblz4.so.1
ENV C_INCLUDE_PATH=/opt/cross-pi-gcc/arm-linux-gnueabihf/usr/include
ENV PATH=$PATH:/opt/cross-pi-gcc/bin:/opt/cross-pi-gcc/libexec/gcc/arm-linux-gnueabihf/6.3.0/
RUN mkdir /build
WORKDIR /build
# Add a user called `build` uid 1001 & gid 10000
# You chould change RB_UID & RB_GID to what works on your build setup
ENV RB_USER=build
ENV RB_UID=1001
ENV RB_GID=1000
RUN groupadd -g $RB_GID $RB_USER 2> /dev/null; exit 0
RUN useradd $RB_USER -u $RB_UID -g $RB_GID -m -s /bin/bash
RUN echo "$RB_USER ALL=(ALL:ALL) ALL" >> /etc/sudoers
USER $RB_USER

16
docker/aqexec-pre.sh Normal file
View File

@ -0,0 +1,16 @@
#!/bin/bash
# Example file that will start SOCAT before AqualinkD in a docker
# to support EW-11 WIFI module for RS485 connection
#
# This file should be placed in the config aqualinkd directory defined
# in your docker-compose.yml (or equiv)
#
# MAKE SURE TO CHAGE THE IP BELOW (1.1.1.1)
# aqualinkd.cong should have serial_port=/dev/ttyEW11
echo "Starting SOCAT port binding....."
socat -d -d pty,link=/dev/ttyEW11,raw,ignoreeof TCP4:1.1.1.1:8899,ignoreeof &
sudo docker compose up
echo "Sleeping for SOCAT start....."
sleep 2s

50
docker/aqualinkd-docker.cmd Executable file
View File

@ -0,0 +1,50 @@
#!/bin/bash
# Script to start AqualinkD inside container.
CONFDIR=/aquadconf
AQUA_CONF=$CONFDIR/aqualinkd.conf
# Check we have a config directory
if [ -d "$CONFDIR" ]; then
# Check we have config file, if not copy default
if [ ! -f "$AQUA_CONF" ]; then
echo "Warning no aqualinkd.conf in $CONFDIR", using default
cp /etc/aqualinkd.conf $CONFDIR
fi
# Replace local filesystem config with mounted config
ln -sf "$AQUA_CONF" /etc/aqualinkd.conf
# If we have a web config, replace the local filesystem with mounted
if [ -f "$CONFDIR/config.js" ]; then
ln -sf "$CONFDIR/config.js" /var/www/aqualinkd/config.js
fi
# If don't have a cron file, create one
if [ ! -f "$CONFDIR/aqualinkd.schedule" ]; then
echo "#***** AUTO GENERATED DO NOT EDIT *****" > "$CONFDIR/aqualinkd.schedule"
fi
# link mounted cron file to local filesystem.
ln -sf "$CONFDIR/aqualinkd.schedule" /etc/cron.d/aqualinkd
chmod 644 "$CONFDIR/aqualinkd.schedule"
else
# No conig dir, show warning
echo "WARNING no config directory, AqualinkD starting with default config, no changes will be saved"
AQUA_CONF="/etc/aqualinkd.conf"
fi
# See if we have any execpre files to run.
if [[ -x "$CONFDIR/aqexec-pre.sh" ]]; then
"$CONFDIR/aqexec-pre.sh"
fi
# Start cron
service cron start
# Start AqualinkD not in daemon mode
/usr/local/bin/aqualinkd -d -c $AQUA_CONF

70
docker/buildx.sh Executable file
View File

@ -0,0 +1,70 @@
#!/bin/bash
#
# Script to build arm64 & amd64 containers that are published to docker.io
#
# This should never be used, unless you want to deploy AqualinkD docker containers to docer.io
# It's here incase someone taked over this repo because I'm no longer around
#
IMAGE=aqualinkd
DOCKER_HUB_NAME="docker.io/sfeakes"
LATEST_TAG=""
if [ $# -eq 0 ]
then
# Below is safer, but not supported on all platforms.
#VERSION=$(curl --silent "https://api.github.com/repos/sfeakes/AqualinkD/releases/latest" | grep -Po '"tag_name": "[^0-9|v|V]*\K.*?(?=")')
VERSION=$(curl --silent "https://api.github.com/repos/aqualinkd/AqualinkD/releases/latest" | grep "tag_name" | awk -F'"' '$0=$4')
LATEST_TAG="-t ${DOCKER_HUB_NAME}/${IMAGE}:latest"
else
VERSION=$1
fi
URL="https://github.com/aqualinkd/AqualinkD/archive/refs/tags/"$VERSION".tar.gz"
URL2="https://github.com/aqualinkd/AqualinkD/archive/refs/tags/v"$VERSION".tar.gz"
URL3="https://github.com/aqualinkd/AqualinkD/archive/refs/tags/V"$VERSION".tar.gz"
#BURL="https://github.com/aqualinkd/AqualinkD/archive/refs/heads/"$VERSION".tar.gz"
# Check version is accurate before running docker build
if ! curl --output /dev/null --silent --location --head --fail "$URL"; then
# Check if version tag has wrong case
if curl --output /dev/null --silent --location --head --fail "$URL2"; then
VERSION=v$VERSION
else
# Check if it's a branch
if curl --output /dev/null --silent --location --head --fail "$URL3"; then
VERSION=V$VERSION
else
echo "ERROR Can't build Docker container for $IMAGE $VERSION"
echo -e "Neither Version or Branch URLs:- \n $URL \n $URL2 \n $URL3"
exit 1
fi
fi
fi
# Check we are building a version not already on docker hub
DOCKER_TAGS=$(wget -q -O - "https://hub.docker.com/v2/namespaces/aqualinkd/repositories/aqualinkd/tags" | grep -o '"name": *"[^"]*' | grep -o '[^"]*$')
if echo $DOCKER_TAGS | grep -q $VERSION; then
echo "AqualinkD version $VERSION already exists on docker.io, are you sure you want to overide"
read -p "Are you sure? " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit
fi
fi
# Login first Run as root not with sudo on my build machine.
# cat ~/.docker.token | docker login --username sfeakes --password-stdin
# any errors clean build env `docker buildx prune``
echo "Building Docker container for $IMAGE using branch $VERSION"
docker buildx build --platform=linux/amd64,linux/arm64 \
--file Dockerfile.buildx \
-t ${DOCKER_HUB_NAME}/${IMAGE}:${VERSION} \
$LATEST_TAG \
--build-arg AQUALINKD_VERSION=${VERSION} \
--push .

37
docker/docker-compose.yml Executable file
View File

@ -0,0 +1,37 @@
services:
aqualinkd:
image: sfeakes/aqualinkd:latest
#build:
# context: https://github.com/aqualinkd/AqualinkD.git#master:docker
# args:
# AQUALINKD_VERSION: v2.3.6 # Make sure to change to correct version
# tags:
# - aqualinkd:v2.3.6
container_name: aqualinkd
ports:
- "6171:80"
volumes:
- type: bind # AqualinkD config directory
source: ./config
target: /aquadconf
read_only: false
- type: bind # systemd logging
source: /var/run/systemd/journal/socket
target: /var/run/systemd/journal/socket
read_only: false
- type: bind # systemd logging
source: /var/log/journal
target: /var/log/journal
read_only: true
- type: bind # time
source: /etc/localtime
target: /etc/localtime
read_only: true
- type: bind # timezone
source: /etc/timezone
target: /etc/timezone
read_only: true
devices: # Map
- "/dev/ttyUSB0:/dev/ttyUSB0"
logging:
driver: journald

View File

@ -1,320 +1,390 @@
#-----THESE Entries are entered in your Configuration.yaml
#---- AQUALINK THERMOSTATS (Freeze, Pool, Spa) -------------
climate:
- platform: honeywell
username: brian_sheedy@symantec.com
password: !secret honeywell_password
scan_interval: 2400
region: us
- platform: mqtt
unique_id: aqualink_pool_heater
name: Pool Heater
qos: 1
modes:
- 0
- 1
- "off"
- "heat"
send_if_off: true
# Uncomment if you want HA to set it to an inital temperature when it boots up.
# initial: 70
initial: 70
power_command_topic: "aqualinkd/Pool_Heater/set"
payload_on: "1"
payload_off: "0"
current_temperature_topic: "aqualinkd/Temperature/Pool"
min_temp: 70
max_temp: 90
mode_command_topic: "aqualinkd/Pool_Heater/set "
mode_state_topic: "aqualinkd/Pool_Heater"
mode_state_template: "{{ value_json }}"
temperature_command_topic: "aqualinkd/Pool_Heater/setpoint/set "
mode_command_topic: "aqualinkd/Pool_Heater/set"
mode_state_topic: "aqualinkd/Pool_Heater/enabled"
mode_state_template: >-
{% set values1 = { '0':'off', '1':'heat'} %}
{{ values1[value] if value in values1.keys() else 'off' }}
temperature_command_topic: "aqualinkd/Pool_Heater/setpoint/set"
temperature_state_topic: "aqualinkd/Pool_Heater/setpoint"
temperature_state_template: "{{ value_json }}"
- platform: mqtt
unique_id: aqualink_spa_heater
name: Spa Heater
modes:
- 0
- 1
- "off"
- "heat"
send_if_off: true
# Uncomment if you want HA to set it to an inital temperature when it boots up.
# initial: 100
initial: 100
power_command_topic: "aqualinkd/Spa_Heater/set"
payload_on: "1"
payload_off: "0"
current_temperature_topic: "aqualinkd/Temperature/Spa"
min_temp: 80
max_temp: 104
mode_command_topic: "aqualinkd/Spa_Heater/set "
mode_state_topic: "aqualinkd/Spa_Heater"
mode_state_template: "{{ value_json }}"
temperature_command_topic: "aqualinkd/Spa_Heater/setpoint/set "
mode_command_topic: "aqualinkd/Spa_Heater/set"
mode_state_topic: "aqualinkd/Spa_Heater/enabled"
mode_state_template: >-
{% set values2 = { '0':'off', '1':'heat'} %}
{{ values2[value] if value in values2.keys() else 'off' }}
temperature_command_topic: "aqualinkd/Spa_Heater/setpoint/set"
temperature_state_topic: "aqualinkd/Spa_Heater/setpoint"
temperature_state_template: "{{ value_json }}"
- platform: mqtt
unique_id: aqualink_freeze_protect
name: Freeze Protect
modes:
- 0
- 1
- "off"
- "cool"
send_if_off: true
# Uncomment if you want HA to set it to an inital temperature when it boots up.
# initial: 36
initial: 36
power_command_topic: "aqualinkd/Freeze_Protect/set"
payload_on: "1"
payload_off: "0"
current_temperature_topic: "aqualinkd/Temperature/Air"
min_temp: 36
min_temp: 34
max_temp: 42
mode_command_topic: "aqualinkd/Freeze_Protect/set "
mode_state_topic: "aqualinkd/Freeze_Protect"
mode_state_template: "{{ value_json }}"
temperature_command_topic: "aqualinkd/Freeze_Protect/setpoint/set "
mode_command_topic: "aqualinkd/Freeze_Protect/set"
mode_state_topic: "aqualinkd/Freeze_Protect/enabled"
mode_state_template: >-
{% set values = { '0':'off', '1':'cool'} %}
{{ values[value] if value in values.keys() else 'off' }}
temperature_command_topic: "aqualinkd/Freeze_Protect/setpoint/set"
temperature_state_topic: "aqualinkd/Freeze_Protect/setpoint"
temperature_state_template: "{{ value_json }}"
#---- AQUALINK GROUPS -------------
group:
ARS:
name: Pool
view: yes
entities:
- sensor.Air_Temp
- sensor.Pool_Temp
- sensor.Spa_Temp
- sensor.pool_check
- binary_sensor.Auto_Mode
- sensor.battery_check
- group.PL1
- group.SP1
- group.TC1
- group.SC1
PL1:
name: Pool Control
view: no
entities:
- switch.Filter_Pump
- switch.Cleaner
- switch.Low_Speed
- light.Pool_Light
- switch.Waterfall
- switch.Pool_Drain
- input_number.pooldrain
- switch.Pool_Fill
- input_number.poolfill
- switch.Pool_Mode
SP1:
name: Spa Control
view: no
entities:
- switch.Spa_Mode
- switch.Spa_Blower
- light.Spa_Light
TC1:
name: Temp Control
view: no
entities:
- binary_sensor.Pool_Heater_Enabled
- climate.Pool_Heater
- binary_sensor.Spa_Heater_Enabled
- climate.Spa_Heater
- binary_sensor.Freeze_Enabled
- climate.Freeze_Protect
SC1:
name: Automation
view: no
entities:
- script.summer_pool
- script.winter_pool
- script.waterfall
- script.spa
#---- AQUALINK LIGHTS -------------
Light:
- platform: mqtt
name: "Spa Light"
state_topic: "aqualinkd/Aux_4"
command_topic: "aqualinkd/Aux_4/set "
qos: 1
payload_on: "1"
payload_off: "0"
retain: false
- platform: mqtt
name: "Pool Light"
state_topic: "aqualinkd/Aux_5"
command_topic: "aqualinkd/Aux_5/set "
qos: 1
payload_on: "1"
payload_off: "0"
retain: false
#----AQUALINK SWITCHES
switch:
- platform: mqtt
name: "Freeze"
state_topic: "aqualinkd/Freeze_Protect"
command_topic: "aqualinkd/Freeze_Protect/set "
unique_id: aqualink_freeze_protect
name: "Freeze Protection"
state_topic: "aqualinkd/Freeze_Protect/enabled"
command_topic: "aqualinkd/Freeze_Protect/set" #NOTE: aqualinkd does not support toggling Freeze Protect due to being a menu option rather than a simple command. Use your RS controller to change the setting. This should be configured as a binary_sensor instead of a switch to make it more obvious but I kept it a switch in case the limitation changes in the future.
json_attributes_topic: "aqualinkd/Freeze_Protect"
json_attributes_template: "{{ {'active': value|int} | tojson }}"
qos: 1
payload_on: "0"
payload_off: "1"
payload_on: "1"
payload_off: "0"
retain: false
icon: mdi:snowflake
- platform: mqtt
unique_id: aqualink_filter_pump
name: "Filter Pump"
state_topic: "aqualinkd/Filter_Pump"
command_topic: "aqualinkd/Filter_Pump/set "
command_topic: "aqualinkd/Filter_Pump/set"
json_attributes_topic: "aqualinkd/Filter_Pump/delay"
json_attributes_template: "{{ {'delay': value|int} | tojson }}"
qos: 1
payload_on: "1"
payload_off: "0"
retain: false
- platform: mqtt
name: "Waterfall"
unique_id: aqualink_pool_light
name: "Pool Light"
state_topic: "aqualinkd/Solar_Heater"
command_topic: "aqualinkd/Solar_Heater/set "
command_topic: "aqualinkd/Solar_Heater/set"
json_attributes_topic: "aqualinkd/Solar_Heater/delay"
json_attributes_template: "{{ {'delay': value|int} | tojson }}"
qos: 1
payload_on: "1"
payload_off: "0"
retain: false
icon: mdi:lightbulb
- platform: mqtt
name: "Spa Mode"
state_topic: "aqualinkd/Spa_Mode"
command_topic: "aqualinkd/Spa_Mode/set "
unique_id: aqualink_spa
name: "Spa"
state_topic: "aqualinkd/Spa"
command_topic: "aqualinkd/Spa/set"
json_attributes_topic: "aqualinkd/Spa/delay"
json_attributes_template: "{{ {'delay': value|int} | tojson }}"
qos: 1
payload_on: "1"
payload_off: "0"
retain: false
icon: mdi:hot-tub
- platform: mqtt
unique_id: aqualink_cleaner
name: "Cleaner"
state_topic: "aqualinkd/Aux_1"
command_topic: "aqualinkd/Aux_1/set "
command_topic: "aqualinkd/Aux_1/set"
json_attributes_topic: "aqualinkd/Aux_1/delay"
json_attributes_template: "{{ {'delay': value|int} | tojson }}"
qos: 1
payload_on: "1"
payload_off: "0"
retain: false
icon: mdi:broom
- platform: mqtt
name: "Low Speed"
unique_id: aqualink_spa_blower
name: "Spa Blower"
state_topic: "aqualinkd/Aux_2"
command_topic: "aqualinkd/Aux_2/set "
command_topic: "aqualinkd/Aux_2/set"
json_attributes_topic: "aqualinkd/Aux_2/delay"
json_attributes_template: "{{ {'delay': value|int} | tojson }}"
qos: 1
payload_on: "1"
payload_off: "0"
retain: false
icon: mdi:fan
- platform: mqtt
name: "Spa Blower"
unique_id: aqualink_spa_light
name: "Spa Light"
state_topic: "aqualinkd/Aux_3"
command_topic: "aqualinkd/Aux_3/set "
command_topic: "aqualinkd/Aux_3/set"
json_attributes_topic: "aqualinkd/Aux_3/delay"
json_attributes_template: "{{ {'delay': value|int} | tojson }}"
qos: 1
payload_on: "1"
payload_off: "0"
retain: false
icon: mdi:lightbulb
- platform: mqtt
unique_id: aqualink_pool_heater
name: "Pool Heater"
state_topic: "aqualinkd/Pool_Heater"
command_topic: "aqualinkd/Pool_Heater/set "
state_topic: "aqualinkd/Pool_Heater/enabled"
command_topic: "aqualinkd/Pool_Heater/set"
json_attributes_topic: "aqualinkd/Pool_Heater"
json_attributes_template: "{{ {'active': value|int} | tojson }}"
qos: 1
payload_on: "1"
payload_off: "0"
retain: false
icon: mdi:fire
- platform: mqtt
unique_id: aqualink_spa_heater
name: "Spa Heater"
state_topic: "aqualinkd/Spa_Heater"
command_topic: "aqualinkd/Spa_Heater/set "
state_topic: "aqualinkd/Spa_Heater/enabled"
command_topic: "aqualinkd/Spa_Heater/set"
json_attributes_topic: "aqualinkd/Spa_Heater"
json_attributes_template: "{{ {'active': value|int} | tojson }}"
qos: 1
payload_on: "1"
payload_off: "0"
retain: false
icon: mdi:fire
#----AQUALINK SENSORS
sensor:
- platform: mqtt
unique_id: aqualink_pool_state
state_topic: "aqualinkd/Service_Mode"
name: "Aqualink Mode"
value_template: "{{ value }}"
icon: mdi:wrench
- platform: mqtt
unique_id: aqualink_air_temp
state_topic: "aqualinkd/Temperature/Air"
name: "Air Temp"
value_template: "{{ value_json }}"
unit_of_measurement: "°F"
unit_of_measurement: "°F"
device_class: temperature
- platform: mqtt
unique_id: aqualink_spa_temp
state_topic: "aqualinkd/Temperature/Spa"
name: "Spa Temp"
value_template: "{{ value_json }}"
unit_of_measurement: "°F"
unit_of_measurement: "°F"
device_class: temperature
- platform: mqtt
unique_id: aqualink_pool_temp
state_topic: "aqualinkd/Temperature/Pool"
name: "Pool Temp"
value_template: "{{ value_json }}"
unit_of_measurement: "°F"
unit_of_measurement: "°F"
device_class: temperature
- platform: mqtt
unique_id: aqualink_pool_heater_temp
state_topic: "aqualinkd/Pool_Heater/setpoint"
name: "Pool Temp Set"
name: "Pool Heater Temp"
value_template: "{{ value_json }}"
unit_of_measurement: "°F"
unit_of_measurement: "°F"
device_class: temperature
- platform: mqtt
unique_id: aqualink_spa_heater_temp
state_topic: "aqualinkd/Spa_Heater/setpoint"
name: "Spa Temp Set"
name: "Spa Heater Temp"
value_template: "{{ value_json }}"
unit_of_measurement: "°F"
- platform: command_line
name: Battery
command: 'curl -k --silent "http://YOUR_IP_ADDRESS:PORT?command=status" | jq -r ".battery"'
value_template: "{{ value }}"
unit_of_measurement: "°F"
device_class: temperature
- platform: template
# This simple sensor shows the current state of the Aqualink battery...
sensors:
battery_check:
#this sensor simulates a numeric state, but since we only get NORMAL or LOW from Aqualink we can't do a true range
aqualink_battery_level:
value_template: >-
{%- if states('sensor.Battery') == "ok" %}
OK
{%- if states('binary_sensor.aqualink_battery') == "off" %}
80
{% else %}
LOW
5
{%- endif %}
icon_template: >-
{%- if states('sensor.Battery') == "ok" %}
mdi:battery
{%- if states('binary_sensor.aqualink_battery') == "off" %}
mdi:battery
{% else %}
mdi:battery-alert
mdi:battery-alert
{%- endif %}
friendly_name: 'Battery'
friendly_name: 'Aqualink Battery Level'
unit_of_measurement: '%'
device_class: battery
#this sensor translates the service mode values
aqualink_run_mode:
value_template: >-
{%- if states('sensor.aqualink_mode') == '0' %}
AUTO
{% elif states('sensor.aqualink_mode') == '1' %}
SERVICE
{% elif states('sensor.aqualink_mode') == '2' %}
TIMEOUT
{%- endif %}
icon_template: >-
{%- if states('sensor.aqualink_mode') == '0' %}
mdi:robot
{% elif states('sensor.aqualink_mode') == '1' %}
mdi:cog
{% elif states('sensor.aqualink_mode') == '2' %}
mdi:clock
{%- endif %}
friendly_name: 'Aqualink Run Mode'
#----AQUALINK BINARY SENSORS
binary_sensor:
- platform: mqtt
state_topic: "aqualinkd/Pool_Heater/enabeled"
name: "Pool Heater Enabled"
qos: 0
payload_on: "1"
payload_off: "0"
- platform: mqtt
state_topic: "aqualinkd/Spa_Heater/enabeled"
name: "Spa Heater Enabled"
qos: 0
payload_on: "1"
payload_off: "0"
- platform: mqtt
state_topic: "aqualinkd/Freeze_Protect"
name: "Freeze Enabled"
qos: 0
payload_on: "1"
payload_off: "0"
- platform: mqtt
unique_id: aqualink_pool_heater
state_topic: "aqualinkd/Pool_Heater"
name: "Pool Heating"
qos: 0
payload_on: "1"
payload_off: "0"
- platform: mqtt
unique_id: aqualink_spa_heater
state_topic: "aqualinkd/Spa_Heater"
name: "Spa Heating"
qos: 0
payload_on: "1"
payload_off: "0"
- platform: mqtt
unique_id: aqualink_freeze_protection
state_topic: "aqualinkd/Freeze_Protect"
name: "Freeze Protecting"
qos: 0
payload_on: "1"
payload_off: "0"
- platform: mqtt
unique_id: aqualink_alive
state_topic: "aqualinkd/Alive"
name: "AqualinkD Alive"
qos: 0
payload_on: "1"
payload_off: "0"
- platform: mqtt
unique_id: aqualink_battery
state_topic: "aqualinkd/Battery"
name: "Aqualink Battery"
qos: 0
payload_on: "0"
payload_off: "1"
device_class: battery
- platform: mqtt
unique_id: filter_pump_delay
state_topic: "aqualinkd/Filter_Pump/delay"
name: "Filter Pump Delay"
qos: 0
payload_on: "1"
payload_off: "0"
- platform: mqtt
unique_id: spa_delay
state_topic: "aqualinkd/Spa/delay"
name: "Spa Delay"
qos: 0
payload_on: "1"
payload_off: "0"
- platform: mqtt
unique_id: pool_light_delay
state_topic: "aqualinkd/Solar_Heater/delay"
name: "Pool Light Delay"
qos: 0
payload_on: "1"
payload_off: "0"
- platform: mqtt
unique_id: cleaner_delay
state_topic: "aqualinkd/Aux_1/delay"
name: "Cleaner Delay"
qos: 0
payload_on: "1"
payload_off: "0"
- platform: mqtt
unique_id: spa_blower_delay
state_topic: "aqualinkd/Aux_2/delay"
name: "Spa Blower Delay"
qos: 0
payload_on: "1"
payload_off: "0"
- platform: mqtt
unique_id: spa_light_delay
state_topic: "aqualinkd/Aux_3/delay"
name: "Spa Light Delay"
qos: 0
payload_on: "1"
payload_off: "0"
#-----THESE Entries are entered in your automation.yaml file
#-----Automation Entries---------
- alias: 'Notify of Low Pool Battery'
initial_state: 'on'
trigger:
platform: state
entity_id: sensor.battery_check
from: 'OK'
to: 'LOW'
condition:
condition: time
after: '08:15:00'
before: '16:00:00'
action:
automation:
- alias: 'Notify of Low Pool Battery'
initial_state: 'on'
trigger:
platform: state
entity_id: sensor.battery_check
from: 'OK'
to: 'LOW'
condition:
condition: time
after: '08:15:00'
before: '16:00:00'
action:
# Change notify.ios.brianphone to whatever your phone name is...
- service: notify.ios_brianphone
data:
message: 'The Aqualink Pool Controller Battery is LOW'
title: 'Home Assistant'
- service: notify.ios_brianphone
data:
message: 'The Aqualink Pool Controller Battery is LOW'
title: 'Home Assistant'
- id: disable-heater-when-pool-off
alias: Disable heater when pool off
initial_state: on
trigger:
platform: state
entity_id: switch.filter_pump
from: 'on'
to: 'off'
action:
- service: switch.turn_off
entity_id:
- switch.pool_heater
- switch.spa_heater

BIN
extras/HASSIO.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

BIN
extras/HomeAssistant2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
extras/allbutton_sim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -1,24 +1,71 @@
#!/bin/bash
#
# Run using
# curl -s https://raw.githubusercontent.com/sfeakes/AqualinkD/master/extras/aqua.sh | bash -s <script parms> --
#
# list latest release version (This is zip install)
# curl --silent "https://api.github.com/repos/sfeakes/AqualinkD/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")'
#
# list latest dev version
# curl --silent "https://raw.githubusercontent.com/sfeakes/AqualinkD/master/version.h" | grep AQUALINKD_VERSION | cut -d '"' -f 2
#
CWD=$(cd)
cd ~
echo "This script has been updated!"
echo "Please use 'curl -fsSL https://raw.githubusercontent.com/aqualinkd/AqualinkD/master/release/remote_install.sh | sudo bash -s -- latest' to install AqualinkD"
BASE="./software/"
while true; do
read -p "Continue install with this script? (y/n): " response
case $response in
[yY] )
echo "Continuing..."
break
;;
[nN] )
echo "Exiting..."
exit 0
;;
* )
echo "Invalid input. Please enter y or n."
;;
esac
done
NAME="AqualinkD"
SOURCE_LOCATION="/extras/aqua.sh"
HOMEDIR=~
eval HOMEDIR=$HOMEDIR
BASE="$HOMEDIR/software/"
AQUA="$BASE/AqualinkD"
AQUA_GIT="$BASE/AqualinkD/.git"
echoerr() { printf "%s\n" "$*" >&2; }
function print_usage {
echoerr "No arguments supplied, please use use:-"
echoerr "$0 install <- Install from scratch, delete any existing install (except configuration files)"
echoerr "$0 upgrade <- Upgrade existing install"
echoerr "$0 clean <- Remove everything, including configuration files"
echoerr "$0 release <- Download & install latest $NAME stable release."
echoerr "$0 latest <- Download & install latest $NAME version. (development release)"
echoerr "$0 force_latest <- Force latest option (don't run any version checks)"
echoerr "$0 install <- Install to local filesystem, (config files will not be overwritten)"
echoerr "$0 clean <- Remove everything, including configuration files"
echoerr "$0 <option> downloadonly <- Download using one of above options, but don't unstall"
echoerr ""
}
function upgrade_self {
if [ -f "$AQUA/extras/aqua.sh" ]; then
cp "$AQUA/extras/aqua.sh" $0
#echo "NOT UPGRADING SELF, MAKE SURE TO EDIT BEFORE COMMIT"
#return;
LOC=$(realpath $0)
if [ "$LOC" == "/nas/data/Development/Raspberry/AqualinkD/extras/aqua.sh" ]; then
echo "NOT UPGRADING SELF, (Development environment)"
return
elif [ "$0" == "bash" ] || "$0" == "$AQUA/$SOURCE_LOCATION" ]; then
return
fi
if [ -f "$AQUA/$SOURCE_LOCATION" ]; then
cp "$AQUA/$SOURCE_LOCATION" $0
fi
}
@ -26,60 +73,155 @@ function git_install {
sudo apt-get install git
}
function local_version {
if [ -f $AQUA/version.h ]; then
echo `cat $AQUA/version.h | grep AQUALINKD_VERSION | cut -d '"' -f 2 | tr '\n' ' '`
else
echo 0
fi
}
function git_release_version {
echo `curl --silent "https://api.github.com/repos/sfeakes/AqualinkD/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")'`
}
function get_development_version {
echo `curl --silent "https://raw.githubusercontent.com/sfeakes/AqualinkD/master/version.h" | grep AQUALINKD_VERSION | cut -d '"' -f 2`
}
function remove {
cd ~
#cd ~
if [ -d "$AQUA" ]; then
rm -rf "$AQUA"
if [ -L "$AQUA" ]; then
unlink "$AQUA"
else
rm -rf "$AQUA"
fi
fi
}
function new_install {
function isvalid_gitinstall {
#cd "$AQUA"
isgit=$(git --git-dir "$AQUA_GIT" status 2>&1)
return $?
}
function development_version_download {
echo "Upgrading $NAME from $(local_version) to $(get_development_version)"
if isvalid_gitinstall; then
update
else
force_dev_download
fi
}
function force_dev_download {
remove
make-base
cd ~
if [ ! -d "$BASE" ]; then
mkdir -p $BASE
fi
cd $BASE
git clone https://github.com/sfeakes/AqualinkD.git
#cd $BASE
git clone "https://github.com/sfeakes/AqualinkD.git" "$AQUA"
}
function upgrade {
cd ~
cd $AQUA
git pull
function update {
#cd ~
#cd $AQUA
git --git-dir "$AQUA_GIT" pull
}
function clean {
cd ~
#cd ~
sudo "$AQUA/release/install.sh" "clean"
remove
}
function install {
cd ~
if [ ! -f "$AQUA/release/install.sh" ]; then
echo "ERROR Can not find install script, please download from git using either :-"
echo "$0 release"
echo "$0 latest"
exit 1
fi
echo "Installing $NAME"
#cd ~
sudo "$AQUA/release/install.sh"
}
function make-base {
if [ ! -d "$BASE" ]; then
mkdir -p $BASE
fi
if [ ! -d "$BASE" ]; then
echoerr "Error Can't create $BASE";
exit 1;
fi
}
function release_version_download {
version=$(local_version)
git_version=$(git_release_version)
if [ "v$version" != "$git_version" ]; then
echo "Installing $NAME $git_version"
make-base
#cd $BASE
# This is correct way, but tar is made with crap dir structure
#tar_url=$(curl --silent "https://api.github.com/repos/sfeakes/AqualinkD/releases/latest" | grep -Po '"tarball_url": "\K.*?(?=")')
#curl --silent -L "$tar_url" | tar xz
# Use this tar instead, it's correct dir structure
curl --silent -L "https://github.com/sfeakes/AqualinkD/archive/$git_version.tar.gz" | tar xz -C "$BASE"
ver=$(echo $git_version | sed 's/^[^0-9]*//' )
if [ -d "$AQUA" ]; then
if [ -d "$AQUA-$version" ]; then
mv "$AQUA" "$AQUA-$RANDOM"
else
mv "$AQUA" "$AQUA-$version"
fi
fi
if [ -L "$AQUA" ]; then
unlink "$AQUA"
fi
ln -s "$BASE/AqualinkD-$ver" "$AQUA"
else
echo "Local $NAME version $version is latest, not downloading"
fi
}
function check_git_installed {
# Check git is installed
command -v git >/dev/null 2>&1 || { echoerr "git is not installed. Installing"; git_install; }
command -v git >/dev/null 2>&1 || { echoerr "git is not installed. Aborting"; exit 1; }
}
function check_curl_installed {
command -v curl >/dev/null 2>&1 || { echoerr "curl is not installed. Installing"; exit 1; }
}
# Check something was passed
if [[ $# -eq 0 ]]; then
print_usage
exit
fi
# Check git is installed
command -v git >/dev/null 2>&1 || { echoerr "git is not installed. Installing"; git_install; }
command -v git >/dev/null 2>&1 || { echoerr "git is not installed. Aborting"; exit 1; }
# Pass command line
if [ "$1" == "install" ]; then
new_install
elif [ "$1" == "upgrade" ]; then
upgrade
if [ "$1" == "release" ]; then
check_curl_installed
release_version_download
elif [ "$1" == "latest" ]; then
check_curl_installed
check_git_installed
development_version_download
elif [ "$1" == "force_latest" ]; then
check_curl_installed
check_git_installed
force_dev_download
elif [ "$1" == "install" ]; then
echo -n ""
# install
elif [ "$1" == "clean" ]; then
clean
exit
@ -88,11 +230,24 @@ else
exit;
fi
install
if [ "$2" == "downloadonly" ]; then
echo "No install, download only"
else
install
fi
echo "Installed "`cat $AQUA/version.h | cut -d '"' -f 2 | tr '\n' ' '`
upgrade_self
cd $CWD
#cd $CWD
echo "Please make sure to read release notes https://github.com/sfeakes/AqualinkD/blob/master/README.md#release"
echo ""
echo "Please make sure to read release notes https://github.com/sfeakes/AqualinkD/blob/master/README.md#release"
if [ "$0" == "bash" ]; then
# Was probably run from curl
echo -n "Source directory "
echo $AQUA | tr -s '/' '/'
echo -n "To run this script in the future, "
echo $AQUA/$SOURCE_LOCATION | tr -s '/' '/'
fi

52
extras/color-lights.txt Normal file
View File

@ -0,0 +1,52 @@
Color Name Jandy Colors Jandy LED SAm/SAL Color Logic IntelliBrite
----------------------------------------------------------------------------------------------
Color Splash 11 8
Alpine White 1 1
Sky Blue 2 2
Cobalt Blue 3 3
Caribbean Blu 4 4
Spring Green 5 5
Emerald Green 6 6
Emerald Rose 7 7
Magenta 8 8
Garnet Red 9 9
Violet 10 10
Slow Splash 11
Fast Splash 12
USA!!! 13
Fat Tuesday 14
Disco Tech 15
White 1
Light Green 2
Green 3
Cyan 4
Blue 5
Lavender 6
Magenta 7
Light Magenta
Voodoo Lounge 1
Deep Blue Sea 2
Afternoon Skies 3
Afternoon Sky 4
Emerald 5
Sangria 6
Cloud White 7
Twilight 8
Tranquility 9
Gemstone 10
USA! 11
Mardi Gras 12
Cool Cabaret 13
SAm 1
Party 2
Romance 3
Caribbean 4
American 5
Cal Sunset 6
Royal 7
Blue 8
Green 9
Red 10
White 11
Magenta 12

26
extras/kill_aqualinkd.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash
#
# /*
# * Copyright (c) 2017 Shaun Feakes - All rights reserved
# *
# * You may use redistribute and/or modify this code under the terms of
# * the GNU General Public License version 2 as published by the
# * Free Software Foundation. For the terms of this license,
# * see <http://www.gnu.org/licenses/>.
# *
# * You are free to use this software under the terms of the GNU General
# * Public License, but WITHOUT ANY WARRANTY; without even the implied
# * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# * See the GNU General Public License for more details.
# *
# * https://github.com/aqualinkd/aqualinkd
# */
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
fi
kill -9 `ps -ef | grep aqualinkd | awk '{printf "%s ",$2}'`

BIN
extras/onetouch_sim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

81
extras/pi_health.sh Executable file
View File

@ -0,0 +1,81 @@
#!/bin/bash
#
# /*
# * Copyright (c) 2017 Shaun Feakes - All rights reserved
# *
# * You may use redistribute and/or modify this code under the terms of
# * the GNU General Public License version 2 as published by the
# * Free Software Foundation. For the terms of this license,
# * see <http://www.gnu.org/licenses/>.
# *
# * You are free to use this software under the terms of the GNU General
# * Public License, but WITHOUT ANY WARRANTY; without even the implied
# * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# * See the GNU General Public License for more details.
# *
# * https://github.com/aqualinkd/aqualinkd
# */
MAX_TEMP=78
WARNING_TEMP=72
#Flag Bits
UNDERVOLTED=0x1
CAPPED=0x2
THROTTLED=0x4
HAS_UNDERVOLTED=0x10000
HAS_CAPPED=0x20000
HAS_THROTTLED=0x40000
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
fi
echomsg() { if [ -t 1 ]; then echo "$@" 1>&2; fi; }
reportThermal() {
if ((($1)!=0)) ; then
echomsg "ERROR : $2"
else
echomsg "$2 : OK"
fi
}
TEMP=$(awk '{printf("%.1f\n",$1/1e3)}' /sys/class/thermal/thermal_zone0/temp 2>/dev/null )
if [ $? -ne 0 ]; then
echomsg "Error: getting CPU temperature"
else
echomsg "CPU temperature: $TEMP"
if (( $(awk 'BEGIN {print ("'$TEMP'" >= "'$MAX_TEMP'")}') )); then
echomsg "Error: Over Temperature $TEMP"
elif (( $(awk 'BEGIN {print ("'$TEMP'" >= "'$WARNING_TEMP'")}') )); then
echomsg "Warning: Temperature $TEMP"
fi
fi
VOLTS=$(vcgencmd measure_volts | grep -Eo '[0-9]+([.][0-9]+)?')
echomsg "CPU Voltage: $VOLTS"
#Get Status, extract hex
STATUS=$(vcgencmd get_throttled)
if [[ "$STATUS" == *'Command not registered'* ]]; then
echomsg "Error: Update Pi to enable 'vcgencmd get_throttled' command"
exit;
fi
STATUS=${STATUS#*=}
if [ $? -ne 0 ]; then
echomsg "Error getting vcgencmd information"
exit 1;
fi
reportThermal "$STATUS&UNDERVOLTED" "Undervolt" undervolt
reportThermal "$STATUS&HAS_UNDERVOLTED" "Undervolt history" has_undervolt
reportThermal "$STATUS&THROTTLED" "Throttled" throttled
reportThermal "$STATUS&HAS_THROTTLED" "Throttled history" has_throttled
reportThermal "$STATUS&CAPPED" "Frequency Capped" freqcap
reportThermal "$STATUS&HAS_CAPPED" "Frequency Capped history" has_freqcap

View File

@ -4,9 +4,22 @@
PROCESSNAME=aqualinkd
MYPID=`pidof $PROCESSNAME`
echo "=======";
echo PID:$MYPID
echo "--------"
if [ $? -ne 0 ]; then
MYPID=$(pidof "$PROCESSNAME-arm64")
if [ $? -ne 0 ]; then
MYPID=$(pidof "$PROCESSNAME-armhf")
fi
fi
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
fi
echo "===================";
echo "Process : $PROCESSNAME"
echo "PID : $MYPID"
echo "-------------------"
Rss=`echo 0 $(cat /proc/$MYPID/smaps | grep Rss | awk '{print $2}' | sed 's#^#+#') | bc;`
Shared=`echo 0 $(cat /proc/$MYPID/smaps | grep Shared | awk '{print $2}' | sed 's#^#+#') | bc;`
Private=`echo 0 $(cat /proc/$MYPID/smaps | grep Private | awk '{print $2}' | sed 's#^#+#') | bc;`
@ -20,6 +33,9 @@ echo "Shared " $Shared
echo "Private " $Private
echo "Swap " $Swap
echo "Pss " $Pss
echo "=================";
echo "===================";
echo "Mem " $Mem
echo "=================";
echo "===================";
ps -p $MYPID -o %cpu,%mem,cmd
echo "===================";

20
extras/show_realtime.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
#
PROCESSNAME=aqualinkd
MYPID=`pidof $PROCESSNAME`
if [ $? -ne 0 ]; then
MYPID=$(pidof "$PROCESSNAME-arm64")
if [ $? -ne 0 ]; then
MYPID=$(pidof "$PROCESSNAME-armhf")
fi
fi
#if [[ $EUID -ne 0 ]]; then
# echo "This script must be run as root"
# exit 1
#fi
top -H -p $MYPID

38
extras/start_pump_reboot.sh Executable file
View File

@ -0,0 +1,38 @@
#!/bin/bash
# Pass time between as #1 and #2 using 24 hour
# example crontab entry to start pump if system boots is between 6am and 11pm
#
# @reboot /path/start_pump_reboot 6 23
#
startAfter=$1
startBefore=$2
SLEEP_BETWEEN_TRIES=5 # 5 seconds
TRIES=5 # 5 tries
# Wait for AqualinkD to come up and connect to panel
sleep 30
hour=$(date +%H)
function turn_on() {
curl -s -S -o /dev/null http://localhost:80/api/Filter_Pump/set -d value=1 -X PUT --fail
rtn=$?
echo $rtn
return $rtn
}
#echo "Hour=$hour Ater=$startAfter Before=$startBefore"
# Remember 11:45 is 11, so don't use <= for startBefore
if (($hour >= $startAfter && $hour < $startBefore )); then
x=1
while [ $x -le $TRIES ] && [ $(turn_on) -gt 0 ]; do
sleep $SLEEP_BETWEEN_TRIES
x=$(( $x + 1 ))
done
fi

120
extras/sync-lights.sh Executable file
View File

@ -0,0 +1,120 @@
#!/bin/bash
#
# To re-synchronize your lights,
# turn the switch on,
# then back off,
# then wait between 11 and 14 seconds and turn the switch back on.
# When the lights come back on, they should enter program #1, and be synchronized.
# Different way to sync
# To re-synchronize your lights, start with the lights off and follow the steps below:
# 1. Turn lights on.
# 2. Turn off light for between 11-15 seconds,.
# 3. Turn lights on.
# Different again
# https://images-na.ssl-images-amazon.com/images/I/A1aZApuRWQS.pdf
# Manual
# https://hayward-pool-assets.com/assets/documents/pools/pdf/manuals/colorlogic-installation-operation.pdf
#
# Warm up time
# https://www.troublefreepool.com/threads/pool-lights-wont-sync-up.177510/
URL="http://pool/api/Pool%20Light/set"
function delay {
echo "Sleeping $1 seconds"
sleep $1
}
function on {
echo -n "Turning light on. "
curl -X PUT -d value=1 $URL
echo ""
}
function off {
echo -n "Turning light off. "
curl -X PUT -d value=0 $URL
echo ""
}
# This is light mode check
function mode_check {
on
delay 120
off
delay 12
on
delay 1
off
delay 12
on
delay 1
off
delay 12
on
delay 1
off
delay 12
on
echo "Lights should be flashing Red and White, power down for at least 1 minute"
}
# This is color recync. or color program.
function resync {
on
delay 70
off
delay 12
on
echo "Lights should be acting the same color mode"
}
function change_mode {
off
delay 12
on
echo "Lights should be acting the same color mode"
}
function mode {
on
delay 1
off
delay 13
on
delay 1
off
delay 13
on
delay 1
off
delay 13
on
echo "Lights should blink red"
}
resync
read -p "Y/N " input
while [ "$input" == "n" ]; do
change_mode
read -p "Y/N " input
done
echo "Leave Lights off for at least 2 mins"
off
exit
#mode_check
resync

Binary file not shown.

Before

Width:  |  Height:  |  Size: 413 KiB

BIN
extras/web_ui2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -1,146 +0,0 @@
/*
* Copyright (c) 2017 Shaun Feakes - All rights reserved
*
* You may use redistribute and/or modify this code under the terms of
* the GNU General Public License version 2 as published by the
* Free Software Foundation. For the terms of this license,
* see <http://www.gnu.org/licenses/>.
*
* You are free to use this software under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* https://github.com/sfeakes/aqualinkd
*/
#include "config.h"
#include "domoticz.h"
/*
* Link LED numbers to buttons, this is valid for RS8 and below, RS10 and above are different
* need to update this code in future.
*/
void initButtons(struct aqualinkdata *aqdata)
{
aqdata->aqbuttons[0].led = &aqdata->aqualinkleds[7-1];
aqdata->aqbuttons[0].led->state = LED_S_UNKNOWN;
aqdata->aqbuttons[0].label = BTN_PUMP;
//aqdata->aqbuttons[0].label = "Filter Pump";
aqdata->aqbuttons[0].name = BTN_PUMP;
//aqdata->aqbuttons[0].code = (unsigned char *)KEY_PUMP;
aqdata->aqbuttons[0].code = KEY_PUMP;
aqdata->aqbuttons[0].dz_idx = DZ_NULL_IDX;
aqdata->aqbuttons[0].pda_label = BTN_PDA_PUMP;
aqdata->aqbuttons[1].led = &aqdata->aqualinkleds[6-1];
aqdata->aqbuttons[1].led->state = LED_S_UNKNOWN;
aqdata->aqbuttons[1].label = BTN_SPA;
//aqdata->aqbuttons[1].label = "Spa Mode";
aqdata->aqbuttons[1].name = BTN_SPA;
//aqdata->aqbuttons[1].code = (unsigned char *)KEY_SPA;
aqdata->aqbuttons[1].code = KEY_SPA;
aqdata->aqbuttons[1].dz_idx = DZ_NULL_IDX;
aqdata->aqbuttons[1].pda_label = BTN_PDA_SPA;
aqdata->aqbuttons[2].led = &aqdata->aqualinkleds[5-1];
aqdata->aqbuttons[2].led->state = LED_S_UNKNOWN;
aqdata->aqbuttons[2].label = BTN_AUX1;
//aqdata->aqbuttons[2].label = "Cleaner";
aqdata->aqbuttons[2].name = BTN_AUX1;
//aqdata->aqbuttons[2].code = (unsigned char *)KEY_AUX1;
aqdata->aqbuttons[2].code = KEY_AUX1;
aqdata->aqbuttons[2].dz_idx = DZ_NULL_IDX;
aqdata->aqbuttons[2].pda_label = BTN_PDA_AUX1;
//aqdata->aqbuttons[2].pda_label = "CLEANER";
aqdata->aqbuttons[3].led = &aqdata->aqualinkleds[4-1];
aqdata->aqbuttons[3].led->state = LED_S_UNKNOWN;
aqdata->aqbuttons[3].label = BTN_AUX2;
//aqdata->aqbuttons[3].label = "Waterfall";
aqdata->aqbuttons[3].name = BTN_AUX2;
//aqdata->aqbuttons[3].code = (unsigned char *)KEY_AUX2;
aqdata->aqbuttons[3].code = KEY_AUX2;
aqdata->aqbuttons[3].dz_idx = DZ_NULL_IDX;
aqdata->aqbuttons[3].pda_label = BTN_PDA_AUX2;
aqdata->aqbuttons[4].led = &aqdata->aqualinkleds[3-1];
aqdata->aqbuttons[4].led->state = LED_S_UNKNOWN;
aqdata->aqbuttons[4].label = BTN_AUX3;
//aqdata->aqbuttons[4].label = "Spa Blower";
aqdata->aqbuttons[4].name = BTN_AUX3;
//aqdata->aqbuttons[4].code = (unsigned char *)KEY_AUX3;
aqdata->aqbuttons[4].code = KEY_AUX3;
aqdata->aqbuttons[4].dz_idx = DZ_NULL_IDX;
aqdata->aqbuttons[4].pda_label = BTN_PDA_AUX3;
aqdata->aqbuttons[5].led = &aqdata->aqualinkleds[9-1];
aqdata->aqbuttons[5].led->state = LED_S_UNKNOWN;
aqdata->aqbuttons[5].label = BTN_AUX4;
//aqdata->aqbuttons[5].label = "Pool Light";
aqdata->aqbuttons[5].name = BTN_AUX4;
//aqdata->aqbuttons[5].code = (unsigned char *)KEY_AUX4;
aqdata->aqbuttons[5].code = KEY_AUX4;
aqdata->aqbuttons[5].dz_idx = DZ_NULL_IDX;
aqdata->aqbuttons[5].pda_label = BTN_PDA_AUX4;
aqdata->aqbuttons[6].led = &aqdata->aqualinkleds[8-1];
aqdata->aqbuttons[6].led->state = LED_S_UNKNOWN;
aqdata->aqbuttons[6].label = BTN_AUX5;
//aqdata->aqbuttons[6].label = "Spa Light";
aqdata->aqbuttons[6].name = BTN_AUX5;
//aqdata->aqbuttons[6].code = (unsigned char *)KEY_AUX5;
aqdata->aqbuttons[6].code = KEY_AUX5;
aqdata->aqbuttons[6].dz_idx = DZ_NULL_IDX;
aqdata->aqbuttons[6].pda_label = BTN_PDA_AUX5;
aqdata->aqbuttons[7].led = &aqdata->aqualinkleds[12-1];
aqdata->aqbuttons[7].led->state = LED_S_UNKNOWN;
aqdata->aqbuttons[7].label = BTN_AUX6;
aqdata->aqbuttons[7].name = BTN_AUX6;
//aqdata->aqbuttons[7].code = (unsigned char *)KEY_AUX6;
aqdata->aqbuttons[7].code = KEY_AUX6;
aqdata->aqbuttons[7].dz_idx = DZ_NULL_IDX;
aqdata->aqbuttons[7].pda_label = BTN_PDA_AUX6;
aqdata->aqbuttons[8].led = &aqdata->aqualinkleds[1-1];
aqdata->aqbuttons[8].led->state = LED_S_UNKNOWN;
aqdata->aqbuttons[8].label = BTN_AUX7;
aqdata->aqbuttons[8].name = BTN_AUX7;
//aqdata->aqbuttons[8].code = (unsigned char *)KEY_AUX7;
aqdata->aqbuttons[8].code = KEY_AUX7;
aqdata->aqbuttons[8].dz_idx = DZ_NULL_IDX;
aqdata->aqbuttons[8].pda_label = BTN_PDA_AUX7;
aqdata->aqbuttons[9].led = &aqdata->aqualinkleds[15-1];
aqdata->aqbuttons[9].led->state = LED_S_UNKNOWN;
aqdata->aqbuttons[9].label = BTN_POOL_HTR;
//aqdata->aqbuttons[9].label = "Heater";
aqdata->aqbuttons[9].name = BTN_POOL_HTR;
//aqdata->aqbuttons[9].code = (unsigned char *)KEY_POOL_HTR;
aqdata->aqbuttons[9].code = KEY_POOL_HTR;
aqdata->aqbuttons[9].dz_idx = DZ_NULL_IDX;
aqdata->aqbuttons[9].pda_label = BTN_PDA_POOL_HTR;
aqdata->aqbuttons[10].led = &aqdata->aqualinkleds[17-1];
aqdata->aqbuttons[10].led->state = LED_S_UNKNOWN;
aqdata->aqbuttons[10].label = BTN_SPA_HTR;
//aqdata->aqbuttons[10].label = "Heater";
aqdata->aqbuttons[10].name = BTN_SPA_HTR;
//aqdata->aqbuttons[10].code = (unsigned char *)KEY_SPA_HTR;
aqdata->aqbuttons[10].code = KEY_SPA_HTR;
aqdata->aqbuttons[10].dz_idx = DZ_NULL_IDX;
aqdata->aqbuttons[10].pda_label = BTN_PDA_SPA_HTR;
aqdata->aqbuttons[11].led = &aqdata->aqualinkleds[19-1];
aqdata->aqbuttons[11].led->state = LED_S_UNKNOWN;
aqdata->aqbuttons[11].label = BTN_SOLAR_HTR;
//aqdata->aqbuttons[11].label = "Solar Heater";
aqdata->aqbuttons[11].name = BTN_SOLAR_HTR;
//aqdata->aqbuttons[11].code = (unsigned char *)KEY_SOLAR_HTR;
aqdata->aqbuttons[11].code = KEY_SOLAR_HTR;
aqdata->aqbuttons[11].dz_idx = DZ_NULL_IDX;
aqdata->aqbuttons[11].pda_label = BTN_PDA_SOLAR_HTR;
}

View File

@ -1,12 +0,0 @@
#ifndef INIT_BUTTONS_H_
#define INIT_BUTTONS_H_
#define PUMP_INDEX 0
#define SPA_INDEX 1
void initButtons(struct aqualinkdata *aqdata);
#define TOTAL_BUTONS 12
#endif

View File

@ -1,520 +0,0 @@
/*
* Copyright (c) 2017 Shaun Feakes - All rights reserved
*
* You may use redistribute and/or modify this code under the terms of
* the GNU General Public License version 2 as published by the
* Free Software Foundation. For the terms of this license,
* see <http://www.gnu.org/licenses/>.
*
* You are free to use this software under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* https://github.com/sfeakes/aqualinkd
*/
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "aqualink.h"
#include "config.h"
//#include "aq_programmer.h"
#include "utils.h"
//#include "web_server.h"
#include "json_messages.h"
#include "domoticz.h"
#include "aq_mqtt.h"
#include "version.h"
//#define test_message "{\"type\": \"status\",\"version\": \"8157 REV MMM\",\"date\": \"09/01/16 THU\",\"time\": \"1:16 PM\",\"temp_units\": \"F\",\"air_temp\": \"96\",\"pool_temp\": \"86\",\"spa_temp\": \" \",\"battery\": \"ok\",\"pool_htr_set_pnt\": \"85\",\"spa_htr_set_pnt\": \"99\",\"freeze_protection\": \"off\",\"frz_protect_set_pnt\": \"0\",\"leds\": {\"pump\": \"on\",\"spa\": \"off\",\"aux1\": \"off\",\"aux2\": \"off\",\"aux3\": \"off\",\"aux4\": \"off\",\"aux5\": \"off\",\"aux6\": \"off\",\"aux7\": \"off\",\"pool_heater\": \"off\",\"spa_heater\": \"off\",\"solar_heater\": \"off\"}}"
//#define test_labels "{\"type\": \"aux_labels\",\"aux1_label\": \"Cleaner\",\"aux2_label\": \"Waterfall\",\"aux3_label\": \"Spa Blower\",\"aux4_label\": \"Pool Light\",\"aux5_label\": \"Spa Light\",\"aux6_label\": \"Unassigned\",\"aux7_label\": \"Unassigned\"}"
//#define test_message "{\"type\": \"status\",\"version\":\"xx\",\"time\":\"xx\",\"air_temp\":\"0\",\"pool_temp\":\"0\",\"spa_temp\":\"0\",\"pool_htr_set_pnt\":\"0\",\"spa_htr_set_pnt\":\"0\",\"frz_protect_set_pnt\":\"0\",\"temp_units\":\"f\",\"battery\":\"ok\",\"leds\":{\"Filter_Pump\": \"on\",\"Spa_Mode\": \"on\",\"Aux_1\": \"on\",\"Aux_2\": \"on\",\"Aux_3\": \"on\",\"Aux_4\": \"on\",\"Aux_5\": \"on\",\"Aux_6\": \"on\",\"Aux_7\": \"on\",\"Pool_Heater\": \"on\",\"Spa_Heater\": \"on\",\"Solar_Heater\": \"on\"}}"
//{"type": "aux_labels","Pool Pump": "Pool Pump","Spa Mode": "Spa Mode","Cleaner": "Aux 1","Waterfall": "Aux 2","Spa Blower": "Aux 2","Pool Light": "Aux 4","Spa Light ": "Aux 5","Aux 6": "Aux 6","Aux 7": "Aux 7","Heater": "Heater","Heater": "Heater","Solar Heater": "Solar Heater","(null)": "(null)"}
//SPA WILL TURN OFF AFTER COOL DOWN CYCLE
const char* getStatus(struct aqualinkdata *aqdata)
{
if (aqdata->active_thread.thread_id != 0 && !aqdata->simulate_panel) {
return JSON_PROGRAMMING;
}
if (aqdata->last_message != NULL && stristr(aqdata->last_message, "SERVICE") != NULL ) {
return JSON_SERVICE;
}
if (aqdata->last_display_message[0] != '\0')
return aqdata->last_display_message;
/*
if (aqdata->display_last_message == true) {
return aqdata->last_message;
}
*/
return JSON_READY;
}
int build_mqtt_status_JSON(char* buffer, int size, int idx, int nvalue, float tvalue/*char *svalue*/)
{
memset(&buffer[0], 0, size);
int length = 0;
if (tvalue == TEMP_UNKNOWN) {
length = sprintf(buffer, "{\"idx\":%d,\"nvalue\":%d,\"svalue\":\"\"}", idx, nvalue);
} else {
length = sprintf(buffer, "{\"idx\":%d,\"nvalue\":%d,\"stype\":\"SetPoint\",\"svalue\":\"%.2f\"}", idx, nvalue, tvalue);
}
buffer[length] = '\0';
return strlen(buffer);
}
int build_mqtt_status_message_JSON(char* buffer, int size, int idx, int nvalue, char *svalue)
{
memset(&buffer[0], 0, size);
int length = 0;
//json.htm?type=command&param=udevice&idx=IDX&nvalue=LEVEL&svalue=TEXT
length = sprintf(buffer, "{\"idx\":%d,\"nvalue\":%d,\"svalue\":\"%s\"}", idx, nvalue, svalue);
buffer[length] = '\0';
return strlen(buffer);
}
int build_aqualink_error_status_JSON(char* buffer, int size, char *msg)
{
//return snprintf(buffer, size, "{\"type\": \"error\",\"status\":\"%s\"}", msg);
return snprintf(buffer, size, "{\"type\": \"status\",\"status\":\"%s\",\"version\":\"xx\",\"time\":\"xx\",\"air_temp\":\"0\",\"pool_temp\":\"0\",\"spa_temp\":\"0\",\"pool_htr_set_pnt\":\"0\",\"spa_htr_set_pnt\":\"0\",\"frz_protect_set_pnt\":\"0\",\"temp_units\":\"f\",\"battery\":\"ok\",\"leds\":{\"Filter_Pump\": \"off\",\"Spa_Mode\": \"off\",\"Aux_1\": \"off\",\"Aux_2\": \"off\",\"Aux_3\": \"off\",\"Aux_4\": \"off\",\"Aux_5\": \"off\",\"Aux_6\": \"off\",\"Aux_7\": \"off\",\"Pool_Heater\": \"off\",\"Spa_Heater\": \"off\",\"Solar_Heater\": \"off\"}}", msg);
}
char *LED2text(aqledstate state)
{
switch (state) {
case ON:
return JSON_ON;
break;
case OFF:
return JSON_OFF;
break;
case FLASH:
return JSON_FLASH;
break;
case ENABLE:
return JSON_ENABLED;
break;
case LED_S_UNKNOWN:
default:
return "unknown";
break;
}
}
int build_device_JSON(struct aqualinkdata *aqdata, int programable_switch, char* buffer, int size, bool homekit)
{
memset(&buffer[0], 0, size);
int length = 0;
int i;
length += sprintf(buffer+length, "{\"type\": \"devices\"");
length += sprintf(buffer+length, ",\"date\":\"%s\"",aqdata->date );//"09/01/16 THU",
length += sprintf(buffer+length, ",\"time\":\"%s\"",aqdata->time );//"1:16 PM",
if ( aqdata->temp_units == FAHRENHEIT )
length += sprintf(buffer+length, ",\"temp_units\":\"%s\"",JSON_FAHRENHEIT );
else if ( aqdata->temp_units == CELSIUS )
length += sprintf(buffer+length, ",\"temp_units\":\"%s\"", JSON_CELSIUS);
else
length += sprintf(buffer+length, ",\"temp_units\":\"%s\"",JSON_UNKNOWN );
length += sprintf(buffer+length, ", \"devices\": [");
for (i=0; i < TOTAL_BUTTONS; i++)
{
if ( strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0 && aqdata->pool_htr_set_point != TEMP_UNKNOWN) {
length += sprintf(buffer+length, "{\"type\": \"setpoint_thermo\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"spvalue\": \"%.*f\", \"value\": \"%.*f\" },",
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,
aqdata->aqbuttons[i].led->state==ON?JSON_ON:JSON_OFF,
LED2text(aqdata->aqbuttons[i].led->state),
((homekit)?2:0),
((homekit && aqdata->temp_units==FAHRENHEIT)?degFtoC(aqdata->pool_htr_set_point):aqdata->pool_htr_set_point),
((homekit)?2:0),
((homekit && aqdata->temp_units==FAHRENHEIT)?degFtoC(aqdata->pool_temp):aqdata->pool_temp));
} else if ( strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name)==0 && aqdata->spa_htr_set_point != TEMP_UNKNOWN) {
length += sprintf(buffer+length, "{\"type\": \"setpoint_thermo\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"spvalue\": \"%.*f\", \"value\": \"%.*f\" },",
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,
aqdata->aqbuttons[i].led->state==ON?JSON_ON:JSON_OFF,
LED2text(aqdata->aqbuttons[i].led->state),
((homekit)?2:0),
((homekit && aqdata->temp_units==FAHRENHEIT)?degFtoC(aqdata->spa_htr_set_point):aqdata->spa_htr_set_point),
((homekit)?2:0),
((homekit && aqdata->temp_units==FAHRENHEIT)?degFtoC(aqdata->spa_temp):aqdata->spa_temp));
} else if (programable_switch > 0 && programable_switch == i) {
length += sprintf(buffer+length, "{\"type\": \"switch_program\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\" },",
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,
aqdata->aqbuttons[i].led->state==ON?JSON_ON:JSON_OFF,
LED2text(aqdata->aqbuttons[i].led->state));
} else {
length += sprintf(buffer+length, "{\"type\": \"switch\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\" },",
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,
aqdata->aqbuttons[i].led->state==ON?JSON_ON:JSON_OFF,
LED2text(aqdata->aqbuttons[i].led->state));
}
}
if ( aqdata->frz_protect_set_point != TEMP_UNKNOWN && aqdata->air_temp != TEMP_UNKNOWN) {
length += sprintf(buffer+length, "{\"type\": \"setpoint_freeze\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"spvalue\": \"%.*f\", \"value\": \"%.*f\" },",
FREEZE_PROTECT,
"Freeze Protection",
JSON_OFF,
JSON_ENABLED,
((homekit)?2:0),
((homekit && aqdata->temp_units==FAHRENHEIT)?degFtoC(aqdata->frz_protect_set_point):aqdata->frz_protect_set_point),
((homekit)?2:0),
((homekit && aqdata->temp_units==FAHRENHEIT)?degFtoC(aqdata->air_temp):aqdata->air_temp));
}
if ( aqdata->swg_percent != TEMP_UNKNOWN ) {
length += sprintf(buffer+length, "{\"type\": \"setpoint_swg\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"spvalue\": \"%.*f\", \"value\": \"%.*f\" },",
SWG_TOPIC,
"Salt Water Generator",
aqdata->ar_swg_status == 0x00?JSON_ON:JSON_OFF,
aqdata->ar_swg_status == 0x00?JSON_ON:JSON_OFF,
((homekit)?2:0),
((homekit)?degFtoC(aqdata->swg_percent):aqdata->swg_percent),
((homekit)?2:0),
((homekit)?degFtoC(aqdata->swg_percent):aqdata->swg_percent));
//length += sprintf(buffer+length, "{\"type\": \"value\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"value\": \"%d\" },",
length += sprintf(buffer+length, "{\"type\": \"value\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"value\": \"%.*f\" },",
((homekit)?SWG_PERCENT_F_TOPIC:SWG_PERCENT_TOPIC),
"Salt Water Generator Percent",
"on",
((homekit)?2:0),
((homekit)?degFtoC(aqdata->swg_percent):aqdata->swg_percent));
}
if ( aqdata->swg_ppm != TEMP_UNKNOWN ) {
length += sprintf(buffer+length, "{\"type\": \"value\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"value\": \"%d\" },",
SWG_PPM_TOPIC,
"Salt Level PPM",
"on",
aqdata->swg_ppm);
}
length += sprintf(buffer+length, "{\"type\": \"temperature\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"value\": \"%.*f\" },",
AIR_TEMP_TOPIC,
/*AIR_TEMPERATURE,*/
"Pool Air Temperature",
"on",
((homekit)?2:0),
((homekit && aqdata->temp_units==FAHRENHEIT)?degFtoC(aqdata->air_temp):aqdata->air_temp));
length += sprintf(buffer+length, "{\"type\": \"temperature\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"value\": \"%.*f\" },",
POOL_TEMP_TOPIC,
/*POOL_TEMPERATURE,*/
"Pool Water Temperature",
"on",
((homekit)?2:0),
((homekit && aqdata->temp_units==FAHRENHEIT)?degFtoC(aqdata->air_temp):aqdata->pool_temp));
length += sprintf(buffer+length, "{\"type\": \"temperature\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"value\": \"%.*f\" }",
SPA_TEMP_TOPIC,
/*SPA_TEMPERATURE,*/
"Spa Water Temperature",
"on",
((homekit)?2:0),
((homekit && aqdata->temp_units==FAHRENHEIT)?degFtoC(aqdata->air_temp):aqdata->spa_temp));
length += sprintf(buffer+length, "]}");
logMessage(LOG_DEBUG, "WEB: homebridge used %d of %d", length, size);
buffer[length] = '\0';
return strlen(buffer);
//return length;
}
int build_aqualink_status_JSON(struct aqualinkdata *aqdata, char* buffer, int size)
{
//strncpy(buffer, test_message, strlen(test_message)+1);
//return strlen(test_message);
//char buffer2[600];
memset(&buffer[0], 0, size);
int length = 0;
int i;
length += sprintf(buffer+length, "{\"type\": \"status\"");
length += sprintf(buffer+length, ",\"status\":\"%s\"",getStatus(aqdata) );
//length += sprintf(buffer+length, ",\"message\":\"%s\"",aqdata->message );
length += sprintf(buffer+length, ",\"version\":\"%s\"",aqdata->version );//8157 REV MMM",
length += sprintf(buffer+length, ",\"aqualinkd_version\":\"%s\"", AQUALINKD_VERSION ); //1.0b,
length += sprintf(buffer+length, ",\"date\":\"%s\"",aqdata->date );//"09/01/16 THU",
length += sprintf(buffer+length, ",\"time\":\"%s\"",aqdata->time );//"1:16 PM",
//length += sprintf(buffer+length, ",\"air_temp\":\"%d\"",aqdata->air_temp );//"96",
//length += sprintf(buffer+length, ",\"pool_temp\":\"%d\"",aqdata->pool_temp );//"86",
//length += sprintf(buffer+length, ",\"spa_temp\":\"%d\"",aqdata->spa_temp );//" ",
length += sprintf(buffer+length, ",\"pool_htr_set_pnt\":\"%d\"",aqdata->pool_htr_set_point );//"85",
length += sprintf(buffer+length, ",\"spa_htr_set_pnt\":\"%d\"",aqdata->spa_htr_set_point );//"99",
//length += sprintf(buffer+length, ",\"freeze_protection":\"%s\"",aqdata->frz_protect_set_point );//"off",
length += sprintf(buffer+length, ",\"frz_protect_set_pnt\":\"%d\"",aqdata->frz_protect_set_point );//"0",
if ( aqdata->air_temp == TEMP_UNKNOWN )
length += sprintf(buffer+length, ",\"air_temp\":\" \"");
else
length += sprintf(buffer+length, ",\"air_temp\":\"%d\"",aqdata->air_temp );
if ( aqdata->pool_temp == TEMP_UNKNOWN )
length += sprintf(buffer+length, ",\"pool_temp\":\" \"");
else
length += sprintf(buffer+length, ",\"pool_temp\":\"%d\"",aqdata->pool_temp );
if ( aqdata->spa_temp == TEMP_UNKNOWN )
length += sprintf(buffer+length, ",\"spa_temp\":\" \"");
else
length += sprintf(buffer+length, ",\"spa_temp\":\"%d\"",aqdata->spa_temp );
if ( aqdata->swg_percent != TEMP_UNKNOWN )
length += sprintf(buffer+length, ",\"swg_percent\":\"%d\"",aqdata->swg_percent );
if ( aqdata->swg_ppm != TEMP_UNKNOWN )
length += sprintf(buffer+length, ",\"swg_ppm\":\"%d\"",aqdata->swg_ppm );
if ( aqdata->temp_units == FAHRENHEIT )
length += sprintf(buffer+length, ",\"temp_units\":\"%s\"",JSON_FAHRENHEIT );
else if ( aqdata->temp_units == CELSIUS )
length += sprintf(buffer+length, ",\"temp_units\":\"%s\"", JSON_CELSIUS);
else
length += sprintf(buffer+length, ",\"temp_units\":\"%s\"",JSON_UNKNOWN );
if (aqdata->battery == OK)
length += sprintf(buffer+length, ",\"battery\":\"%s\"",JSON_OK );//"ok",
else
length += sprintf(buffer+length, ",\"battery\":\"%s\"",JSON_LOW );//"ok",
length += sprintf(buffer+length, ",\"leds\":{" );
for (i=0; i < TOTAL_BUTTONS; i++)
{
char *state;
switch (aqdata->aqbuttons[i].led->state)
{
case ON:
state = JSON_ON;
break;
case OFF:
case LED_S_UNKNOWN:
state = JSON_OFF;
break;
case FLASH:
state = JSON_FLASH;
break;
case ENABLE:
state = JSON_ENABLED;
break;
}
length += sprintf(buffer+length, "\"%s\": \"%s\"", aqdata->aqbuttons[i].name, state);
if (i+1 < TOTAL_BUTTONS)
length += sprintf(buffer+length, "," );
}
if ( aqdata->swg_percent != TEMP_UNKNOWN ) {
length += sprintf(buffer+length, ", \"%s\": \"%s\"", SWG_TOPIC, aqdata->ar_swg_status == 0x00?JSON_ON:JSON_OFF);
}
//NSF Need to come back and read what the display states when Freeze protection is on
if ( aqdata->frz_protect_set_point != TEMP_UNKNOWN ) {
length += sprintf(buffer+length, ", \"%s\": \"%s\"", FREEZE_PROTECT, JSON_ENABLED);
}
length += sprintf(buffer+length, "}}" );
buffer[length] = '\0';
/*
buffer[length] = '\0';
strncpy(buffer2, test_message, strlen(test_message)+1);
for (i=0; i < strlen(buffer); i++) {
logMessage (LOG_DEBUG, "buffer[%d] = '%c' | '%c'\n",i,buffer[i],buffer2[i]);
}
logMessage (LOG_DEBUG, "JSON Size %d\n",strlen(buffer));
printf("%s\n",buffer);
for (i=strlen(buffer); i > strlen(buffer)-10; i--) {
logMessage (LOG_DEBUG, "buffer[%d] = '%c'\n",i,buffer[i]);
}
for (i=10; i >= 0; i--) {
logMessage (LOG_DEBUG, "buffer[%d] = '%c'\n",i,buffer[i]);
}
//return length-1;
logMessage (LOG_DEBUG, "JSON Size %d\n",strlen(buffer2));
printf("%s\n",buffer2);
for (i=strlen(buffer2); i > strlen(buffer2)-10; i--) {
logMessage (LOG_DEBUG, "buffer[%d] = '%c'\n",i,buffer2[i]);
}
for (i=10; i >= 0; i--) {
logMessage (LOG_DEBUG, "buffer[%d] = '%c'\n",i,buffer2[i]);
}
//return strlen(test_message);
*/
//printf("Buffer = %d, JSON = %d",size ,strlen(buffer));
return strlen(buffer);
}
int build_aux_labels_JSON(struct aqualinkdata *aqdata, char* buffer, int size)
{
memset(&buffer[0], 0, size);
int length = 0;
int i;
length += sprintf(buffer+length, "{\"type\": \"aux_labels\"");
for (i=0; i < TOTAL_BUTTONS; i++)
{
length += sprintf(buffer+length, ",\"%s\": \"%s\"", aqdata->aqbuttons[i].name, aqdata->aqbuttons[i].label);
}
length += sprintf(buffer+length, "}");
return length;
//printf("%s\n",buffer);
//return strlen(buffer);
}
// WS Received '{"parameter":"SPA_HTR","value":99}'
// WS Received '{"command":"KEY_HTR_POOL"}'
// WS Received '{"command":"GET_AUX_LABELS"}'
bool parseJSONwebrequest(char *buffer, struct JSONwebrequest *request)
{
int i=0;
int found=0;
bool reading = false;
request->first.key = NULL;
request->first.value = NULL;
request->second.key = NULL;
request->second.value = NULL;
int length = strlen(buffer);
while ( i < length )
{
//printf ("Reading %c",buffer[i]);
switch (buffer[i]) {
case '{':
case '"':
case '}':
case ':':
case ',':
case ' ':
// Ignore space , : if reading a string
if (reading == true && buffer[i] != ' ' && buffer[i] != ',' && buffer[i] != ':'){
//printf (" <- END");
reading = false;
buffer[i] = '\0';
found++;
}
break;
default:
if (reading == false) {
//printf (" <- START");
reading = true;
switch(found) {
case 0:
request->first.key = &buffer[i];
break;
case 1:
request->first.value = &buffer[i];
break;
case 2:
request->second.key = &buffer[i];
break;
case 3:
request->second.value = &buffer[i];
break;
}
}
break;
}
//printf ("\n");
if (found >= 4)
break;
i++;
}
return true;
}
bool parseJSONmqttrequest(const char *str, size_t len, int *idx, int *nvalue, char *svalue) {
int i = 0;
int found = 0;
svalue[0] = '\0';
for (i = 0; i < len && str[i] != '\0'; i++) {
if (str[i] == '"') {
if (strncmp("\"idx\"", (char *)&str[i], 5) == 0) {
i = i + 5;
for (; str[i] != ',' && str[i] != '\0'; i++) {
if (str[i] == ':') {
*idx = atoi(&str[i + 1]);
found++;
}
}
//if (*idx == 45)
// printf("%s\n",str);
} else if (strncmp("\"nvalue\"", (char *)&str[i], 8) == 0) {
i = i + 8;
for (; str[i] != ',' && str[i] != '\0'; i++) {
if (str[i] == ':') {
*nvalue = atoi(&str[i + 1]);
found++;
}
}
} else if (strncmp("\"svalue1\"", (char *)&str[i], 9) == 0) {
i = i + 9;
for (; str[i] != ',' && str[i] != '\0'; i++) {
if (str[i] == ':') {
while(str[i] == ':' || str[i] == ' ' || str[i] == '"' || str[i] == '\'') i++;
int j=i+1;
while(str[j] != '"' && str[j] != '\'' && str[j] != ',' && str[j] != '}') j++;
strncpy(svalue, &str[i], ((j-i)>DZ_SVALUE_LEN?DZ_SVALUE_LEN:(j-i)));
svalue[((j-i)>DZ_SVALUE_LEN?DZ_SVALUE_LEN:(j-i))] = '\0'; // Simply force the last termination
found++;
}
}
}
if (found >= 4) {
return true;
}
}
}
// Just incase svalue is not found, we really don;t care for most devices.
if (found >= 2) {
return true;
}
return false;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +0,0 @@
#ifndef NET_SERVICES_H_
#define NET_SERVICES_H_
#define GET_RTN_OK "Ok"
#define GET_RTN_UNKNOWN "Unknown command"
#define GET_RTN_NOT_CHANGED "Not Changed"
#define GET_RTN_ERROR "Error"
//void main_server();
//void main_server_TEST(struct aqualinkdata *aqdata, char *s_http_port);
//bool start_web_server(struct mg_mgr *mgr, struct aqualinkdata *aqdata, char *port, char* web_root);
bool start_net_services(struct mg_mgr *mgr, struct aqualinkdata *aqdata, struct aqconfig *aqconfig);
void broadcast_aqualinkstate(struct mg_connection *nc);
void broadcast_aqualinkstate_error(struct mg_connection *nc, char *msg);
#endif // WEB_SERVER_H_

View File

@ -1,100 +0,0 @@
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "pda_menu.h"
#include "aq_serial.h"
#include "utils.h"
int _hlightindex = -1;
char _menu[PDA_LINES][AQ_MSGLEN+1];
void print_menu()
{
int i;
for (i=0; i < PDA_LINES; i++)
printf("PDA Line %d = %s\n",i,_menu[i]);
if (_hlightindex > -1)
printf("PDA highlighted line = %d = %s\n",_hlightindex,_menu[_hlightindex]);
}
int pda_m_hlightindex()
{
return _hlightindex;
}
char *pda_m_hlight()
{
return pda_m_line(_hlightindex);
}
char *pda_m_line(int index)
{
if (index >= 0 && index < PDA_LINES)
return _menu[index];
else
return "-"; // Just return something bad so I can use string comparison with no null check
// return NULL;
}
pda_menu_type pda_m_type()
{
if (strncmp(_menu[1],"AIR ", 5) == 0)
return PM_MAIN;
else if (strncmp(_menu[0],"EQUIPMENT STATUS", 16) == 0)
return PM_EQUIPTMENT_STATUS;
else if (strncmp(_menu[0]," EQUIPMENT ", 16) == 0)
return PM_EQUIPTMENT_CONTROL;
else if (strncmp(_menu[0]," MAIN MENU ", 16) == 0)
return PM_SETTINGS;
//else if ((_menu[0] == '\0' && _hlightindex == -1) || strncmp(_menu[4], "POOL MODE", 9) == 0 )// IF we are building the main menu this may be valid
else if (strncmp(_menu[4], "POOL MODE", 9) == 0 )
return PM_BUILDING_MAIN;
return PM_UNKNOWN;
}
/*
--- Main Menu ---
Line 0 =
Line 1 = AIR
(Line 4 first, Line 2 last, Highligh when finished)
--- Equiptment Status ---
Line 0 = EQUIPMENT STATUS
(Line 0 is first. No Highlight, everything in list is on)
--- Equiptment on/off menu --
Line 0 = EQUIPMENT
(Line 0 is first. Highlight when complete)
*/
bool process_pda_menu_packet(unsigned char* packet, int length)
{
bool rtn = true;
switch (packet[PKT_CMD]) {
case CMD_PDA_CLEAR:
_hlightindex = -1;
memset(_menu, 0, PDA_LINES * (AQ_MSGLEN+1));
break;
case CMD_MSG_LONG:
if (packet[PKT_DATA] < 10) {
memset(_menu[packet[PKT_DATA]], 0, AQ_MSGLEN);
strncpy(_menu[packet[PKT_DATA]], (char*)packet+PKT_DATA+1, AQ_MSGLEN);
_menu[packet[PKT_DATA]][AQ_MSGLEN] = '\0';
}
if (getLogLevel() >= LOG_DEBUG){print_menu();}
break;
case CMD_PDA_HIGHLIGHT:
_hlightindex = packet[4],_menu[packet[4]];
if (getLogLevel() >= LOG_DEBUG){print_menu();}
break;
case CMD_PDA_SHIFTLINES:
memcpy(_menu[1], _menu[2], (PDA_LINES-1) * (AQ_MSGLEN+1) );
if (getLogLevel() >= LOG_DEBUG){print_menu();}
break;
}
return rtn;
}

View File

@ -1,25 +0,0 @@
#ifndef PDA_MENU_H_
#define PDA_MENU_H_
#define PDA_LINES 10 // There is only 9 lines, but add buffer to make shifting easier
typedef enum pda_menu_type {
PM_UNKNOWN,
PM_MAIN,
PM_SETTINGS,
PM_EQUIPTMENT_CONTROL,
PM_EQUIPTMENT_STATUS,
PM_BUILDING_MAIN
} pda_menu_type;
bool pda_mode();
void set_pda_mode(bool val);
bool process_pda_menu_packet(unsigned char* packet, int length);
int pda_m_hlightindex();
char *pda_m_hlight();
char *pda_m_line(int index);
pda_menu_type pda_m_type();
#endif

View File

@ -1,313 +0,0 @@
#define _GNU_SOURCE // for strcasestr
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "pda_menu.h"
#include "aq_serial.h"
#include "utils.h"
#define SLOG_MAX 80
#define PACKET_MAX 600
/*
typedef enum used {
yes,
no,
unknown
} used;
*/
#define PDA_ID 0x60
//#define PDA_ID 0x58
#define PDA_LINES 10 // There is only 9 lines, but add buffer to make shifting easier
/*
typedef struct serial_id_log {
unsigned char ID;
bool inuse;
} serial_id_log;
*/
bool _keepRunning = true;
//int _lineindex = -1;
//char _menu[PDA_LINES][AQ_MSGLEN+1];
//bool _readMenu = false;
//unsigned char _goodID[] = {0x0a, 0x0b, 0x08, 0x09};
unsigned char _filter = PDA_ID;
void intHandler(int dummy) {
_keepRunning = false;
logMessage(LOG_NOTICE, "Stopping!");
}
/*
bool canUse(unsigned char ID) {
int i;
for (i = 0; i < strlen((char *)_goodID); i++) {
if (ID == _goodID[i])
return true;
}
return false;
}
*/
void printHex(char *pk, int length)
{
int i=0;
for (i=0;i<length;i++)
{
printf("0x%02hhx|",pk[i]);
}
}
void printPacket(unsigned char ID, unsigned char *packet_buffer, int packet_length)
{
//if (_filter != 0x00 && ID != _filter && packet_buffer[PKT_DEST] != _filter )
// return;
if (_filter != 0x00) {
if ( packet_buffer[PKT_DEST]==0x00 && ID != _filter )
return;
if ( packet_buffer[PKT_DEST]!=0x00 && packet_buffer[PKT_DEST] != _filter )
return;
}
if (packet_buffer[PKT_DEST] != 0x00)
printf("\n");
printf("%4.4s 0x%02hhx of type %8.8s", (packet_buffer[PKT_DEST]==0x00?"From":"To"), (packet_buffer[PKT_DEST]==0x00?ID:packet_buffer[PKT_DEST]), get_packet_type(packet_buffer, packet_length));
printf(" | HEX: ");
printHex((char *)packet_buffer, packet_length);
if (packet_buffer[PKT_CMD] == CMD_MSG || packet_buffer[PKT_CMD] == CMD_MSG_LONG) {
printf(" Message : ");
//fwrite(packet_buffer + 4, 1, AQ_MSGLEN+1, stdout);
fwrite(packet_buffer + 4, 1, packet_length-7, stdout);
}
//if (packet_buffer[PKT_DEST]==0x00)
// printf("\n\n");
//else
printf("\n");
}
/*
int addLine(unsigned char *packet_buffer)
{
//strcpy (_menu[20], ;
if (packet_buffer[PKT_DATA] < 10) {
memset(_menu[packet_buffer[PKT_DATA]], 0, AQ_MSGLEN);
strncpy(_menu[packet_buffer[PKT_DATA]], (char*)packet_buffer+PKT_DATA+1, AQ_MSGLEN);
_menu[packet_buffer[PKT_DATA]][AQ_MSGLEN+1] = '\0';
//printf ("Added line\n");
} else if (packet_buffer[PKT_DATA] == 0x82) {
printf("AIR TEMP = %s\n",(char*)packet_buffer+PKT_DATA+1);
} else if (packet_buffer[PKT_DATA] == 0x40) {
printf("TIME = %s\n",(char*)packet_buffer+PKT_DATA+1);
}
{
int i;
for (i=0; i < PDA_LINES; i++)
printf("Line %d = %s\n",i,_menu[i]);
printf("Highlight = %d = %s\n",_lineindex,_menu[_lineindex]);
}
return 0;
}
*/
/*
Line 2 = AquaPure 60%
Line 3 = SALT 3200 PPM
Line 4 = FILTER PUMP
Line 5 = SPA HEAT ENA
Line 4 = POOL MODE ON
Line 5 = POOL HEATER OFF
Line 6 = SPA MODE OFF
Line 7 = SPA HEATER OFF
Line 1 = FILTER PUMP ON
Line 2 = SPA OFF
Line 3 = POOL HEAT OFF
Line 4 = SPA HEAT OFF
Line 5 = CLEANER ON
Line 6 = AUX2 OFF
Line 7 = AUX3 OFF
Line 8 = AUX4 OFF
*/
bool process_pda_packet(unsigned char* packet, int length)
{
bool rtn = true;
process_pda_menu_packet(packet, length);
switch (packet[PKT_CMD]) {
case CMD_MSG_LONG: {
char *msg = (char*)packet+PKT_DATA+1;
if (packet[PKT_DATA] == 0x82) { // Air & Water temp is always this ID
// message = "73` 66`"
//_aqualink_data.temp_units = FAHRENHEIT; // Force FAHRENHEIT
//_aqualink_data.air_temp = atoi(msg);
//_aqualink_data.pool_temp = atoi(msg+7);
printf("Air Temp = %d | Water Temp = %d\n",atoi(msg),atoi(msg+7));
} else if (packet[PKT_DATA] == 0x40) {
// message " SAT 8:46AM"
//NSF strcpy(_aqualink_data.time, msg);
if (msg[8] == ' ')
printf("TIME = %.*s\n",6,msg+9);
else
printf("TIME = %.*s\n",7,msg+8);
// NSF strcpy(_aqualink_data.date, msg);
if (msg[4] == ' ')
printf("DAY = %.*s\n",3,msg+5);
else
printf("DAY = %.*s\n",3,msg+4);
// NSF Come back and change the above to correctly check date and time in future
} else if (pda_m_hlightindex() == -1) { // There is a chance this is a message we are interested in.
char *index;
if( (index = strcasestr(msg, MSG_SWG_PCT)) != NULL) {
int aq = atoi(index + strlen(MSG_SWG_PCT));
printf("Aquapure PERCENT message %d\n", aq);
}
if( (index = strcasestr(msg, MSG_SWG_PPM)) != NULL) {
int aq = atoi(index + strlen(MSG_SWG_PPM));
printf("Aquapure SALT message %d\n", aq);
}
} else if (strcmp(pda_m_line(0), " EQUIPMENT ") != 0) {
// Check message for status of device
}
}
break;
}
return rtn;
}
int main(int argc, char *argv[]) {
int rs_fd;
int packet_length;
unsigned char packet_buffer[AQ_MAXPKTLEN];
unsigned char lastID;
int i = 0;
//bool found;
//serial_id_log slog[SLOG_MAX];
//int sindex = 0;
int received_packets = 0;
int logPackets = PACKET_MAX;
int logLevel = LOG_NOTICE;
unsigned char NextMsg = NUL;
//int logLevel;
//char buffer[256];
//bool idMode = true;
if (getuid() != 0) {
fprintf(stderr, "ERROR %s Can only be run as root\n", argv[0]);
return EXIT_FAILURE;
}
if (argc < 2 || access( argv[1], F_OK ) == -1 ) {
fprintf(stderr, "ERROR, first param must be valid serial port, ie:-\n\t%s /dev/ttyUSB0\n\n", argv[0]);
fprintf(stderr, "Optional parameters are -d (debug) & -p <number> (log # packets) & -i <ID> ie:=\n\t%s /dev/ttyUSB0 -d -p 1000 -i 0x08\n\n", argv[0]);
return 1;
}
for (i = 2; i < argc; i++) {
if (strcmp(argv[i], "-d") == 0) {
logLevel = LOG_DEBUG;
} else if (strcmp(argv[i], "-p") == 0 && i+1 < argc) {
logPackets = atoi(argv[i+1]);
} else if (strcmp(argv[i], "-i") == 0 && i+1 < argc) {
unsigned int n;
sscanf(argv[i+1], "0x%2x", &n);
_filter = n;
logLevel = LOG_DEBUG; // no point in filtering on ID if we're not going to print it.
}
}
setLoggingPrms(logLevel, false, false);
rs_fd = init_serial_port(argv[1]);
signal(SIGINT, intHandler);
signal(SIGTERM, intHandler);
logMessage(LOG_NOTICE, "Logging serial information!\n");
if (logLevel < LOG_DEBUG)
printf("Please wait.");
while (_keepRunning == true) {
if (rs_fd < 0) {
logMessage(LOG_ERR, "ERROR, serial port disconnect\n");
}
packet_length = get_packet(rs_fd, packet_buffer);
if (packet_length == -1) {
// Unrecoverable read error. Force an attempt to reconnect.
logMessage(LOG_ERR, "ERROR, on serial port\n");
_keepRunning = false;
} else if (packet_length == 0) {
// Nothing read
} else if (packet_length > 0) {
//logMessage(LOG_DEBUG_SERIAL, "Received Packet for ID 0x%02hhx of type %s\n", packet_buffer[PKT_DEST], get_packet_type(packet_buffer, packet_length));
if (logLevel > LOG_NOTICE)
printPacket(lastID, packet_buffer, packet_length);
if (packet_buffer[PKT_DEST] == PDA_ID) {
if (packet_buffer[PKT_CMD] == CMD_STATUS) {
send_extended_ack(rs_fd, 0x40, NextMsg);
NextMsg = NUL;
} else {
//printf("****** Send ACK *******\n");
send_extended_ack(rs_fd, ACK_PDA, NUL);
}
process_pda_packet(packet_buffer, packet_length);
switch (packet_buffer[PKT_CMD]) {
case CMD_MSG_LONG:
if (strcasestr(pda_m_line(0), "EQUIPMENT STATUS") != NULL) {
//NextMsg = KEY_PDA_BACK;
//printf("****** queue BACK *******\n");
}
//if (_readMenu)
// addLine(packet_buffer);
break;
}
}
char *selection = pda_m_hlight();
if (selection != NULL && strcmp(selection, "EQUIPMENT ON/OFF") != 0) {
NextMsg = KEY_PDA_DOWN;
} else if (selection != NULL && strcmp(selection, "EQUIPMENT ON/OFF") == 0) {
NextMsg = KEY_PDA_SELECT;
}
lastID = packet_buffer[PKT_DEST];
received_packets++;
}
if (logPackets != 0 && received_packets >= logPackets) {
_keepRunning = false;
}
//sleep(1);
}
return 0;
}

Binary file not shown.

1
release/aqualinkd Symbolic link
View File

@ -0,0 +1 @@
./aqualinkd-armhf

BIN
release/aqualinkd-arm64 Executable file

Binary file not shown.

BIN
release/aqualinkd-armhf Executable file

Binary file not shown.

View File

@ -1,12 +1,13 @@
# aqualinkd.conf
#
# The directory where the web files are stored
web_directory=/var/www/aqualinkd/
# The socket port that the daemon listens to
# If you change this from 80, remember to update aqualink.service.avahi
socket_port=80
# Log to file, comment out if you do not want to log to file
#log_file=/var/log/aqualinkd.log
# The serial port the daemon access to read the Aqualink RS8
serial_port=/dev/ttyUSB0
# The log level. [DEBUG_DERIAL, DEBUG, INFO, NOTICE, WARNING, ERROR]
# Pick the highest level, and all levels below will be sent to syslog.
@ -16,118 +17,304 @@ web_directory=/var/www/aqualinkd/
# so, NOTICE also prints WARNING & ERROR
# DEBUG_SERIAL would print everything possible
log_level=DEBUG
#log_level=DEBUG
#log_level=INFO
#log_level=NOTICE
log_level=NOTICE
#log_level=WARNING
# The socket port that the daemon listens to
# If you change this from 80, remember to update aqualink.service.avahi
socket_port=80
# The directory where the web files are stored
web_directory=/var/www/aqualinkd/
# The serial port the daemon access to read the Aqualink RS8
serial_port=/dev/ttyUSB0
# Your RS panel size. ie 4, 6, 8, 12 or 16 relates to RS4, RS6, RS8, RS12 or RS16.
# VERY important that you select 12 or 16, if you have either of those size panels.
# Also don't think setting a 12 when you have a 8 will give you 4 more accessories to control, it won't the
# panel information is needed as different panels use different bits within the RS protocol for status and key
# presses.
# serial_logger will get the panel type string if you don't know it, below are examples.
# Must be in format `XX-N ????` (XX=RS or PD, N=Circuits, ????=Combo or Only or Dual)
panel_type = RS-8 Combo
#panel_type = PD-8 Combo
#panel_type = RS-16 Combo
#panel_type = RS-2/14 Dual
#panel_type = RS-4 Combo
#panel_type = RS-8 Only
#
# If serial_logger doesn't give you a type string in the format above, you can use the next options to set the specifics.
#
# panel_type_size = (6, 8, 10, 12, 14 or 16) (Number of supported accessories / buttons)
# panel_type_combo = (yes or no) (combo panels support BOTH pool & spa)
# panel_type_dual = (yes or no) (dual circuit panel)
# panel_type_pda = (yes or no) (PDA panel. only set this if you have to. Panel ONLY supports the PDA protocol)
# panel_type_rs = (yes or no) (RS panel. Panel Supports all protocols)
# The ID of the Aqualink terminal device. Devices probed by RS8 master are:
# 08-0b, 10-13, 18-1b, 20-23, 28-2b, 30-33, 38-3b, 40-43
# Working RS ID's are 0x0a 0x0b 0x09 0x08 <- 0x08 is usually taken
# If your panel is a PDA only model, then PDA device ID's are 0x60, 0x61, 0x62, 0x63.
# (These are NOT recomended to use unless you absolutly have no other option)
# Use 0xFF to let Aqualink auto configure all the ID's device_id, rssa_device_id, extended_device_id
device_id=0xFF
# The ID of Jandy SerialInterface device. These is only one usable ID, if serial_logger
# picks up your panel supports this, uncomment, as it will speed up heater setpoints & RS16 panels.
#rssa_device_id=0x48
# The ID for extended settings to allow for faster programming
# VARIABLE SPEED PUMP are only supported with this option.
# Do not enable this if you don't use either, you'll just waste memory and cpu cycles
# Valid ID's are 0x40, 0x41, 0x42 & 0x43. for ONE Touch
# Valid ID's are 0x30, 0x31, 0x32 & 0x33. for Aqualink Touch
#extended_device_id=0x31
# If using 0x30 to 0x33 for extended_device_id, then enable below if you want to use virtual buttons
#enable_iaqualink=yes
# Read information from these devices directly from the RS485 bus as well as control panel. This will
# give you quicker updates and more information.
# swg = Salt Water Generator
# ePump = Jandy ePump or ePump AC
# vsfPump = Pentair VS,VF,VSF pump
# JXi = Jandy JXi heater (might also be LXi heaters)
# LX = Jandy LX & LT heaters
# Chem = Jandy Chemical Feeder
# iAqualink = Read iAqualink2 (wifi device). Only relevant in PDA mode IF you have iAqualink2/3 device
# HeatPump = Heatpumps.
#read_RS485_swg = yes
#read_RS485_ePump = yes
#read_RS485_vsfPump = yes
#read_RS485_JXi = yes
#read_RS485_LX = yes
#read_RS485_Chem = yes
#read_RS485_iAqualink = yes
#read_RS485_HeatPump = yes
# AqualinkD will start with no extra devices by default, and once it notices the device it will add it.
# This is not so good for automation hubs (Homekit / HomeAssistant etc), these options will force AqualinkD
# to start with these devides.
#force_swg = yes
#force_ps_setpoints = yes
#force_frzprotect_setpoints = yes
#force_chem_feeder = yes
#force_chiller = yes
# Enable AqualinkD scheduler.
# A version of cron that supports cron.d must be installed for the scheduler to work.
# If you used the install script and didn;t receive any cron warnings, you should be good to go.
enable_scheduler = yes
# Check if button_01 (usually Pump) is scheduled to run after an event that may have turned it off, and set it to run.
# Only for RS panels, Will not work for PDA panles.
# Example below is if pump is off due to power reset, freezeprotect or swg boots is turned off between 6am and 11pm then turn the pump on.
# You can leave scheduler_check_pumpon_hour & scheduler_check_pumpoff_hour commented out and AqualinkD will try to find the hours from the actual schedule
# that's been set in scheduler. This only works if you have the same schedule for every day of the week.
#event_check_use_scheduler_times = NO
#event_poweron_check_pump = YES
#event_freezeprotectoff_check_pump = YES
#event_boostoff_check_pump = YES
#event_check_pumpon_hour = 6
#event_check_pumpoff_hour = 24
# This last one will link a button to SWG boost mode. When in boost mode, you usually have a problem that warrants running the pump faster.
# So you can assign a virtual/one touch button to a particular pump RMP, and then turn it on with this option. (it will also turn it off when boost is finished)
#event_booston_check_device = Fast Pump
# Set the RS485 adapter into low latency mode (of supported)
ftdi_low_latency=YES
# Will change how RS485 / Serial works, Only use if asked to for problem solving purposes.
# Delay between RS485 frame (set or packets that make up a command), reply too quickly can
# cause slow panels (like PDA only) issues, reply too slowly and the control panel will think we are
# dead.
# ~40 and we will be replying too slowley, so keep below that.
# 10~20 is about what most device reply in. But 0-4 works well.
# Recomended to set to at least 4 for PDA panels.
#rs485_frame_delay=10
# Keep the panel time synced with systemtime. Make sure to set systemtime / NTP correctly.
sync_panel_time = yes
# Display any warnings in web UI
display_warnings_in_web = yes
# If equiptment is in freeze protect mode some commands like pump_off / spa_on are
# ignored. You can force these to work by setting the below.
override_freeze_protect = no
# Confert Deg F to Deg C when posting to Domoticz or MQTT.
convert_mqtt_temp_to_c = yes
convert_dz_temp_to_c = yes
# Flash MQTT button on/off like control panel does in cool down mode.
flash_mqtt_buttons = no
#override_freeze_protect = yes
# default is to use pool water temp as spa water temp when spa is off (and there for not able to report water temp)
# enable below to report 0 as the spa temp when spa is off.
# This is for MQTT cnnections only, WEB socket and WEB API always report TEMP_UNKNOWN (-999) allowing the consumer to
# decide how to report.
report_zero_spa_temp = no
# decide how to report.
report_zero_spa_temp = yes
# When pool or spa is off, report 0deg for water temp. If set to no, last known value will be used.
report_zero_pool_temp = yes
# mqtt stuff
#mqtt_address = localhost:1883
#mqtt_user = someusername
#mqtt_passwd = somepassword
#mqtt_dz_pub_topic = domoticz/in
#mqtt_dz_sub_topic = domoticz/out
#mqtt_aq_topic = aqualinkd
# Put AqualinkD to sleep when in PDA mode after inactivity.
# Ignore if you are not using PDA mode.
# If you have Jandy PDA then this MUST be set to yes as the controller can only support one PDA.
# If you don't have a Jandy PDA leave this at no as AqualinkD will be a lot quicker.
#pda_sleep_mode = yes
# The id of the Aqualink terminal device. Devices probed by RS8 master are:
# 08-0b, 10-13, 18-1b, 20-23, 28-2b, 30-33, 38-3b, 40-43
# Working RS ID's are 0x0a 0x0b 0x09 0x08 <- 0x08 is usually taken
device_id=0x0a
# Button inxed light probramming button is assigned to. (look at your button labels below)
light_programming_button = 0
# Lights can be programmed by control panel or AqualinkD (if controlpanel doesn't support specific light or light mode you want)
# IF YOU WANT AQUALINKD TO PROGRAM THE LIGHT, IT MUST NOT BE CONFIGURED AS A COLOR LIGHT IN THE JANDY CONTROL PANEL.
# Light probramming mode. 0=safe mode, but slow.
# any number greater is seconds to wait between button presses.
# 0.4 seems to be the minimum. (workd for light modes below 10 presses)
# 0.6 seems to work about 95% of the time, but above 20 presses can be hit or miss.
# 0 will simply wait for the controler to send the response back before sending the next, so is equivelent to about 1.2
light_programming_mode=0
#light_programming_mode=0
# Light programming assumes light needs to be on before sending pulse (above setting)
# If the light is off when request is made to change "light show", then the below value are used
light_programming_initial_on=15
#light_programming_initial_on=15
# Turn the light off for below time before start programmig puleses.
light_programming_initial_off=12
#light_programming_initial_off=12
# Everything below here, if it ends with dzidx, then that's the ID for domoticz,
# so not needed if you are not suing dooticz.
# Domoticz ID's for temps.
air_temp_dzidx=0
pool_water_temp_dzidx=0
spa_water_temp_dzidx=0
SWG_percent_dzidx=0
SWG_PPM_dzidx=0
# If AqualinkD is programming the lights (and not control panel), set the light names / modes below/.
#light_program_01=Voodoo Lounge - show
#light_program_02=Blue Sea
#light_program_03=Royal Blue
#light_program_04=Afternoon Skies
#light_program_05=Aqua Green
#light_program_06=Emerald
#light_program_07=Cloud White
#light_program_08=Warm Red
#light_program_09=Flamingo
#light_program_10=Vivid Violet
#light_program_11=Sangria
#light_program_12=Twilight - show
#light_program_13=Tranquility - show
#light_program_14=Gemstone - show
#light_program_15=USA - show
#light_program_16=Mardi Gras - show
#light_program_17=Cool Cabaret - show
# Labels for standard butons (shown in web UI), and domoticz idx's
button_01_label=Filter Pump
#button_01_dzidx=37
#button_01_PDA_label=FILTER PUMP
button_02_label=Spa Mode
#button_02_dzidx=38
#button_02_PDA_label=SPA
button_03_label=Cleaner
#button_03_dzidx=39
#button_03_PDA_label=AUX1
button_04_label=Waterfall
#button_04_dzidx=40
#button_04_PDA_label=AUX2
# These are all the button labels / options / pump and light configurations you want to use.
# Simply change these to your setup, valid options for wach button are :-
# None of these are mandatory unless you have PDA or RS16 panel, then _label is mandatory
# button_??_label=Filter Pump <Label you want to see>
# button_??_dzidx=37 <Domoticz IDX>
# button_??_pumpID=0x60 <RS485 ID of VSP>
# button_??_pumpIndex=1 <Pump index Jandy panel is configured to use>
# button_??_pumpType=Pentair VF <Pump Type, one of the folowing :- JANDY ePUMP, Pentair VF, Pentair VS>
# button_??_pumpName=My Pump <Panel Rev Y supports renaming VSP, use the name here>
# button_??_lightMode=4 <Color light mode>
#
# In most cases the label is just what you want to see in web UI/MQTT/API. ie you don't need to use Jandy's labeling. There are 2 exaeptions to the labeling listed below
# 1) If using PDA mode, The Labels below are of the utmost importance, the labels MUST exactly match the labels in the "EQUIPTMENT ON/OFF" menu of the PDA device.
# 2) RS 16 Panels have no protocol bit representation for AUXB5 to AUXB8, only text, so as with PDA Those labels MUST match the control panel.
# Use NONE for label if you don't have anything connected to that circuit and don't want to see the button in the UI.
#
# Below is an example of how different Panels map into the buttons.
#
# | RS-4 Combo | RS-6 Combo | RS-6 Only | RS-8 Combo | RS-2/6 Dual | RS-2/10 Dual | RS-16 Combo |
# ----------------------------------------------------------------------------------------------------------
# Button_01 | Filter Pump | Filter Pump | Filter Pump | Filter Pump | Filter Pump | Filter Pump | Filter Pump |
# Button_02 | Spa | Spa | Aux_1 | Spa | Spa | Spa | Spa |
# Button_03 | Aux 1 | Aux 1 | Aux 2 | Aux 1 | Aux 1 | Aux 1 | Aux 1 |
# Button_04 | Aux 2 | Aux 2 | Aux 3 | Aux 2 | Aux 2 | Aux 2 | Aux 2 |
# Button_05 | Aux 3 | Aux 3 | Aux 4 | Aux 3 | Aux 3 | Aux 3 | Aux 3 |
# Button_06 | Pool Heater | Aux 4 | Aux 5 | Aux 4 | Aux 4 | Aux 4 | Aux 4 |
# Button_07 | Spa Heater | Aux 5 | Temp 1 | Aux 5 | Aux 5 | Aux 5 | Aux 5 |
# Button_08 | Solar Heater | Pool Heater | Temp 2 | Aux 6 | Aux 6 | Aux 6 | Aux 6 |
# Button_09 | | Spa Heater | Solar Heater | Aux 7 | Pool Heater | Aux B1 | Aux 7 |
# Button_10 | | Solar Heater | | Pool Heater | Spa Heater | Aux B2 | Aux B1 |
# Button_11 | | | | Spa Heater | Solar Heater | Aux B3 | Aux B2 |
# Button_12 | | | | Solar Heater | | Aux B4 | Aux B3 |
# Button_13 | | | | | | Pool Heater | Aux B4 |
# Button_14 | | | | | | Spa Heater | Aux B5 |
# Button_15 | | | | | | Solar Heater | Aux B6 |
# Button_16 | | | | | | | Aux B7 |
# Button_17 | | | | | | | Aux B8 |
# Button_18 | | | | | | | Pool Heater |
# Button_19 | | | | | | | Spa Heater |
# Button_20 | | | | | | | Solar Heater |
button_05_label=Spa Blower
#button_05_dzidx=41
#button_05_PDA_label=AUX3
#
# Optional, ( button_01_pumpID & button_01_pumpIndex )
# If you have a Variable Speed Pump, then assign the RS485 ID to the button below so RPM/GPH/WATTS are displayed
# Format is button_01_pumpID=0x60. Leave blank if you don't have a VSP.
# Pentair pump ID's
# 0x60 to 0x6F (0x60, 0x61 0x62, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F)
# Jandy pump ID's
# 0x78, 0x79, 0x7A, 0x7B
#
# button_01_pumpIndex=1
# If you have assigned this pump an index number in your Aqualink control panel, (Between 1 & 4), put it here for VSP, RPM, Primp information to be captured.
#
# button_xx_lightMode = (0=Aqualink program, 1=Jandy, 2=Jandy LED, 3=SAm/SAL, 4=Color Logic, 5=Intellibrite, 6=Hayw Univ Color, 7,8,9(future), 10=Dimmer, 11=Full Range Dimmer)
#
# Below are settings for standard buttons on RS-8 Combo panel used as example.
#
#button_01_label=Filter Pump
#button_01_pumpIndex=1
#button_01_pumpID=0x78
#button_01_pumpName=Intelliflo VS 1
#button_01_pumpType=Pentair VS
button_06_label=Pool Light
#button_06_dzidx=42
#button_06_PDA_label=AUX4
#button_02_label=Spa
#button_03_label=Cleaner
#button_04_label=Waterfall
#button_05_label=Spa Blower
button_07_label=Spa Light
#button_07_dzidx=43
#button_07_PDA_label=AUX5
#button_06_label=Pool Light
#button_06_lightMode=2
button_08_label=NONE
#button_08_dzidx=NONE
#button_08_PDA_label=AUX6
#button_07_label=Spa Light
#button_07_lightMode=2
button_09_label=NONE
#button_09_dzidx=NONE
#button_09_PDA_label=AUX7
#button_08_label=NONE\
#button_09_label=NONE
#button_10_label=Pool Heater
#button_11_label=Spa Heater
#button_12_label=Solar Heater
button_10_label=Pool Heater
#button_10_dzidx=44
#button_09_PDA_label=POOL HEAT
# Virtual buttons.
# To use these you must have extended_device_id set to AqualnkTouch protocol, ie 0x31, 0x31, 0x32, 0x33
# These are the One Touch buttons. By default below are the labels
# Panels rev Yg and newer support custom virtual buttons as well, simply add these here
# Add the ones you would like to use below, making sure to
# a) Sequential order of the button number starting 01
# b) Label must be IDENTICAL to how it's displayed on a AqualinkTouch device (or web)
#
#virtual_button_01_label=Spa Mode
#virtual_button_02_label=Clean Mode
#virtual_button_03_label = OneTouch 4
#virtual_button_04_label = OneTouch 5
#virtual_button_05_label = OneTouch 6
button_11_label=Spa Heater
#button_11_dzidx=56
#button_09_PDA_label=SPA HEAT
# Sensors.
# All Raspberry Pi's (and most other SBC) report CPU temp. Most report to /sys/class/thermal/thermal_zone0/temp,
# you can monitor these and AqualinkD will post the information to MQTT.
# These will depend a lot on the board & OS you are running.
# the "factor" is the number the sensor is multiplied by to get an accurate result. example below is (millidegrees Celsius to Celsius)
button_12_label=Solar Heater
#button_12_dzidx=NONE
#button_09_PDA_label=EXTRA AUX
# Poll tile in seconds
#sensor_poll_time=30
#sensor_01_path = /sys/class/thermal/thermal_zone0/temp
#sensor_01_label = CPU
#sensor_01_factor = 0.001
#sensor_01_uom=°C
# Boards like Radxa Zero3 have others sensors like below.
#sensor_02_path = /sys/class/thermal/thermal_zone1/temp
#sensor_02_label = GPU
#sensor_02_factor = 0.001
#sensor_02_uom=°C
# Linux load average
#sensor_03_path = /proc/loadavg
#sensor_03_label = CPU load
#sensor_03_factor = 100
#sensor_03_uom = %
#sensor_03_regexp = ([0-9|\.]*)\s

View File

@ -1,136 +0,0 @@
# aqualinkd.conf
#
# The directory where the web files are stored
#web_directory=/var/www/aqualinkd/
web_directory=/nas/data/Development/Raspberry/AqualinkD/web
# Log to file, comment out if you do not want to log to file
#log_file=/var/log/aqualinkd.log
#log_file=/nas/data/Development/Raspberry/AqualinkD/scratch/pda.log
# The log level. [DEBUG, INFO, NOTICE, WARNING, ERROR]
# Pick the highest level, and all levels below will be sent to syslog.
# your syslog settings may be set to only display messages above a certian level
# in which case make sure you use the log_file settings to capture everything
# you want when debugging
# so, NOTICE also prints WARNING & ERROR
# DEBUG would print everything possible
#log_level=DEBUG_SERIAL
log_level=DEBUG
#log_level=INFO
#log_level=NOTICE
# The socket port that the daemon listens to
# If you change this from 80, remember to update aqualink.service.avahi
socket_port=80
# The serial port the daemon access to read the Aqualink RS8
serial_port=/dev/ttyUSB0
override_freeze_protect = no
# mqtt stuff
mqtt_address = trident:1883
#mqtt_user = someusername
#mqtt_passwd = somepassword
#mqtt_dz_pub_topic = domoticz/in
#mqtt_dz_sub_topic = domoticz/out
mqtt_aq_topic = aqualinkd_test
# The id of the Aqualink terminal device. Devices probed by RS8 master are:
# 08-0b, 10-13, 18-1b, 20-23, 28-2b, 30-33, 38-3b, 40-43
#
# Working RS 0x0a 0x0b 0x09 0x08
#device_id=0x09
device_id=0x60
pda_mode = yes
convert_mqtt_temp_to_c = yes
convert_dz_temp_to_c = yes
# Bug, leave at no for the moment
flash_mqtt_buttons = yes
# by default use pool temp as spa temp when spa is off, enable below to report 0 as spa temp when off.
report_zero_spa_temp = yes
# Button inxed light probramming button is assigned to. (look at your button labels below)
light_programming_button = 6
# Light probramming mode. 0=safe mode, but slow.
# any number greater is seconds to wait between button presses.
# 0.4 seems to be the minimum. (workd for light modes below 10 presses)
# 0.6 seems to work about 95% of the time, but above 20 presses can be hit or miss.
# 0 will simply wait for the controler to send the response back before sending the next, so is equivelent to about 1.2
light_programming_mode=0
# Light programming assumes light needs to be on before sending pulse (above setting)
# If the light is off when request is made to change "light show", then the below value are used
light_programming_initial_on=15
# Turn the light off for below time before start programmig puleses.
light_programming_initial_off=12
# Domoticz ID's for temps.
air_temp_dzidx=13
pool_water_temp_dzidx=14
spa_water_temp_dzidx=15
#SWG_percent_dzidx=998
#SWG_PPM_dzidx=999
SWG_percent_dzidx=153
SWG_PPM_dzidx=152
SWG_Status_dzidx=157
# Labels for standard butons (shown in web UI), and domoticz idx's
button_01_label=Filter Pump
#button_01_dzidx=37
button_01_PDA_label=FILTER PUMP
button_02_label=Spa Mode
#button_02_dzidx=38
button_02_PDA_label=SPA
button_03_label=Cleaner
#button_03_dzidx=39
button_03_PDA_label=CLEANER
button_04_label=Waterfall
#button_04_dzidx=40
button_04_PDA_label=AUX2
button_05_label=Spa Blower
#button_05_dzidx=41
button_05_PDA_label=AUX3
button_06_label=Pool Light
#button_06_dzidx=42
button_06_PDA_label=AUX4
button_07_label=NONE
#button_07_dzidx=43
button_07_PDA_label=AUX5
button_08_label=NONE
#button_08_dzidx=NONE
button_08_PDA_label=AUX6
button_09_label=NONE
#button_09_dzidx=NONE
button_09_PDA_label=AUX7
button_10_label=Pool Heater
#button_10_dzidx=44
button_09_PDA_label=POOL HEAT
button_11_label=Spa Heater
#button_11_dzidx=56
button_09_PDA_label=SPA HEAT
button_12_label=Solar Heater
#button_12_dzidx=NONE
button_09_PDA_label=EXTRA AUX

0
release/aqualinkd.service Executable file → Normal file
View File

View File

@ -1,32 +0,0 @@
# aqualinkd - aqualinkd job file
#
# check with `initctl check-config aqualinkd`
#
description "aqualink RS daemon service"
author "Me <myself@i.com>"
# Stanzas
#
# Stanzas control when and how a process is started and stopped
# See a list of stanzas here: http://upstart.ubuntu.com/wiki/Stanzas#respawn
# When to start the service
start on runlevel [2345]
# When to stop the service
stop on runlevel [016]
# Automatically restart process if crashed
respawn
# Essentially lets upstart know the process will detach itself to the background
expect fork
# Run before process
#pre-start script
# [ -d /var/run/myservice ] || mkdir -p /var/run/myservice
# echo "Put bash code here"
#end script
# Start the process
exec /usr/local/bin/aqualinkd -c /etc/aqualinkd/aqualinkd.conf

View File

@ -5,6 +5,7 @@
BUILD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PARENT_COMMAND=$(ps -o comm= $PPID 2>/dev/null)
SERVICE="aqualinkd"
@ -21,26 +22,170 @@ DEFLocation="/etc/default"
WEBLocation="/var/www/aqualinkd/"
MDNSLocation="/etc/avahi/services/"
SOURCEBIN=$BIN
LOG_SYSTEMD=1 # 1=false in bash, 0=true
REMOUNT_RO=1
TRUE=0
FALSE=1
_logfile=""
_frommake=$FALSE
_ignorearch=$FALSE
_nosystemd=$FALSE
log()
{
echo "$*"
if [[ -n "$_logfile" ]]; then
echo "$*" >> "$_logfile"
fi
if [[ $LOG_SYSTEMD -eq 0 ]]; then
logger -p local0.notice -t aqualinkd.upgrade "Upgrade: $*"
# Below is same as above but will only wotrk on journald (leaving it here if we use that rater then file)
#echo $* | systemd-cat -t aqualinkd_upgrade -p info
#echo "$*" >> "$OUTPUT"
fi
}
printHelp()
{
echo "$0"
echo "ignorearch (don't check OS architecture, install what was made from Makefile)"
echo "--arch <arch> (install specific OS architecture - armhf | arm64)"
echo "--logfile <filename> (log to file)"
}
#log "Called $0 with $*"
while [[ $# -gt 0 ]]; do
case "$1" in
--logfile)
shift
_logfile="$1"
;;
--arch | --forcearch)
shift
#_forcearch="$1"
if [[ -n "$1" ]]; then
_ignorearch=$TRUE
SOURCEBIN=$BIN-$1
else
log "--arch requires parameter eg. ( --arch armhf | --arch arm64 )"
fi
;;
from-make)
_frommake=$TRUE
;;
ignorearch)
_ignorearch=$TRUE
;;
nosystemd)
_nosystemd=$TRUE
;;
help | -help | --help | -h)
printHelp
exit $TRUE;
;;
*)
echo "Unknown argument: $1"
printHelp;
exit $FALSE;
;;
esac
shift
done
if ! tty > /dev/null 2>&1 || [ "$1" = "syslog" ]; then
# No stdin, probably called from upgrade script
LOG_SYSTEMD=0 # Set logger to systemd
fi
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
log "This script must be run as root"
exit 1
fi
if [[ $(mount | grep " / " | grep "(ro,") ]]; then
echo "Root filesystem is readonly, can't install"
if [ "$_nosystemd" -eq $FALSE ]; then
if [[ $(mount | grep " / " | grep "(ro,") ]]; then
if mount / -o remount,rw &>/dev/null; then
# can mount RW.
#mount / -o remount,rw &>/dev/null
log "Root filesystem is readonly, remounted RW"
REMOUNT_RO=0;
else
log "Root filesystem is readonly, can't install"
exit 1
fi
fi
fi
# Figure out what system we are on and set correct binary.
# If we have been called from make, this is a custom build and install, so ignore check.
#if [ "$PARENT_COMMAND" != "make" ] && [ "$1" != "from-make" ] && [ "$1" != "ignorearch" ]; then
if [ "$PARENT_COMMAND" != "make" ] && [ "$_frommake" -eq $FALSE ] && [ "$_ignorearch" -eq $FALSE ]; then
# Use arch or uname -a to get above.
# dpkg --print-architecture
# Exit if we can't find systemctl
command -v dpkg >/dev/null 2>&1 || { log -e "Can't detect system architecture, Please check path to 'dpkg' or install manually.\n"\
"Or run '$0 ignorearch'" >&2; exit 1; }
ARCH=$(dpkg --print-architecture)
BINEXT=""
case $ARCH in
arm64)
log "Arch is $ARCH, Using 64bit AqualinkD"
BINEXT="-arm64"
;;
armhf)
log "Arch is $ARCH, Using 32bit AqualinkD"
BINEXT="-armhf"
;;
*)
if [ -f $BUILD/$SOURCEBIN-$ARCH ]; then
log "Arch $ARCH is not officially supported, but we found a suitable binary"
BINEXT="-$ARCH"
else
log "Arch $ARCH is unknown, Default to using 32bit HF AqualinkD, you may need to manually try ./release/aqualnkd_arm64"
BINEXT=""
fi
;;
esac
# Need to check BINEXISTS
if [ -f $BUILD/$SOURCEBIN$BINEXT ]; then
SOURCEBIN=$BIN$BINEXT
elif [ -f $BUILD/$SOURCEBIN ]; then
# Not good
log "Can't find correct aqualnkd binary for $ARCH, '$BUILD/$SOURCEBIN$BINEXT' using '$BUILD/$SOURCEBIN' ";
fi
fi
# Exit if we can't find binary
if [ ! -f $BUILD/$SOURCEBIN ]; then
log "Can't find aqualnkd binary `$BUILD/$SOURCEBIN` ";
exit 1
fi
# Exit if we can't find systemctl
command -v systemctl >/dev/null 2>&1 || { echo "This script needs systemd's systemctl manager, Please check path or install manually" >&2; exit 1; }
command -v systemctl >/dev/null 2>&1 || { log "This script needs systemd's systemctl manager, Please check path or install manually" >&2; exit 1; }
# stop service, hide any error, as the service may not be installed yet
systemctl stop $SERVICE > /dev/null 2>&1
SERVICE_EXISTS=$(echo $?)
if [ "$_nosystemd" -eq $FALSE ]; then
# stop service, hide any error, as the service may not be installed yet
systemctl stop $SERVICE > /dev/null 2>&1
SERVICE_EXISTS=$(echo $?)
fi
# Clean everything if requested.
if [ "$1" == "clean" ]; then
echo "Deleting install"
log "Deleting install"
systemctl disable $SERVICE > /dev/null 2>&1
if [ -f $BINLocation/$BIN ]; then
rm -f $BINLocation/$BIN
fi
@ -53,6 +198,9 @@ if [ "$1" == "clean" ]; then
if [ -f $DEFLocation/$DEF ]; then
rm -f $DEFLocation/$DEF
fi
if [ -f /etc/cron.d/aqualinkd ]; then
rm -f /etc/cron.d/aqualinkd
fi
if [ -d $WEBLocation ]; then
rm -rf $WEBLocation
fi
@ -60,30 +208,59 @@ if [ "$1" == "clean" ]; then
exit
fi
# Check cron.d options
if systemctl is-active --quiet cron.service; then
if [ ! -d "/etc/cron.d" ]; then
log "The version of cron installed may not support chron.d, if so AqualinkD Scheduler will not work"
fi
else
log "Please install cron, if not the AqualinkD Scheduler will not work"
fi
# V2.3.9 & V2.6.0 has kind-a breaking change for config.js, so check existing and rename if needed
# we added Aux_V? to the button list
if [ -f "$WEBLocation/config.js" ]; then
# Test is if has AUX_V1 in file AND "Spa" is in file (Spa_mode changed to Spa)
# Version 2.6.0 added Chiller as well
if ! grep -q '"Aux_V1"' "$WEBLocation/config.js" ||
! grep -q '"Spa"' "$WEBLocation/config.js" ||
! grep -q '"Chiller"' "$WEBLocation/config.js" ||
! grep -q '"Aux_S1"' "$WEBLocation/config.js"; then
dateext=`date +%Y%m%d_%H_%M_%S`
log "AqualinkD web config is old, making copy to $WEBLocation/config.js.$dateext"
log "Please make changes to new version $WEBLocation/config.js"
mv $WEBLocation/config.js $WEBLocation/config.js.$dateext
fi
fi
# copy files to locations, but only copy cfg if it doesn;t already exist
cp $BUILD/$BIN $BINLocation/$BIN
cp $BUILD/$SOURCEBIN $BINLocation/$BIN
cp $BUILD/$SRV $SRVLocation/$SRV
if [ -f $CFGLocation/$CFG ]; then
echo "Config exists, did not copy new config, you may need to edit existing! $CFGLocation/$CFG"
log "AqualinkD config exists, did not copy new config, you may need to edit existing! $CFGLocation/$CFG"
else
cp $BUILD/$CFG $CFGLocation/$CFG
fi
if [ -f $DEFLocation/$DEF ]; then
echo "Defaults exists, did not copy new defaults to $DEFLocation/$DEF"
log "AqualinkD defaults exists, did not copy new defaults to $DEFLocation/$DEF"
else
cp $BUILD/$DEF.defaults $DEFLocation/$DEF
fi
if [ -f $MDNSLocation/$MDNS ]; then
echo "Avahi/mDNS defaults exists, did not copy new defaults to $MDNSLocation/$MDNS"
log "Avahi/mDNS defaults exists, did not copy new defaults to $MDNSLocation/$MDNS"
else
if [ -d "$MDNSLocation" ]; then
cp $BUILD/$MDNS.avahi $MDNSLocation/$MDNS
else
echo "Avahi/mDNS may not be installed, not copying $MDNSLocation/$MDNS"
log "Avahi/mDNS may not be installed, not copying $MDNSLocation/$MDNS"
fi
fi
@ -92,18 +269,43 @@ if [ ! -d "$WEBLocation" ]; then
fi
if [ -f "$WEBLocation/config.js" ]; then
echo "$WEBLocation/config.js exists, did not copy overight, please make sure to update manually"
rsync -avq --exclude='config.js' $BUILD/../web/* $WEBLocation
log "AqualinkD web config exists, did not copy new config, you may need to edit existing $WEBLocation/config.js "
if command -v "rsync" &>/dev/null; then
rsync -avq --exclude='config.js' $BUILD/../web/* $WEBLocation
else
# This isn;t the right way to do it, but seems to work.
shopt -s extglob
`cp -r "$BUILD/../web/"!(*config.js) "$WEBLocation"`
shopt -u extglob
# Below should work, but doesn't.
#shopt -s extglob
#cp -r "$BUILD/../web/"!(*config.js) "$WEBLocation"
#shopt -u extglob
fi
else
cp -r $BUILD/../web/* $WEBLocation
fi
# remount root ro
if [[ $REMOUNT_RO -eq 0 ]]; then
mount / -o remount,ro &>/dev/null
log "Root filesystem remounted RO"
fi
if [ "$_nosystemd" -eq $TRUE ]; then
exit 0
fi
systemctl enable $SERVICE
systemctl daemon-reload
if [ $SERVICE_EXISTS -eq 0 ]; then
echo "Starting daemon $SERVICE"
log "Starting daemon $SERVICE"
systemctl start $SERVICE
else
log "Please edit $CFGLocation/$CFG, then start AqualinkD service with `sudo systemctl start aqualinkd`"
fi

Binary file not shown.

345
release/remote_install.sh Executable file
View File

@ -0,0 +1,345 @@
#!/bin/bash
#
# run from curl or local will give different results.
# curl -fsSL https://raw.githubusercontent.com/aqualinkd/AqualinkD/master/release/remote_install.sh | sudo bash -s -- latest
# ./upgrade.sh latest
#
# To get good / bad exit code from both curl and bash, use below. It will exit current term so be careful.
# curl -fsSL "https://raw.githubusercontent.com/aqualinkd/AqualinkD/master/release/remote_install.sh" | ( sudo bash && exit 0 ) || exit $?
REQUIRED_SPACE_MB=18 # Need 17MB, use 18
TRUE=0
FALSE=1
REPO="https://api.github.com/repos/AqualinkD/AqualinkD"
#REPO="https://api.github.com/repos/sfeakes/AqualinkD"
INSTALLED_BINARY="/usr/local/bin/aqualinkd"
# Can't use $0 since this script is usually piped into bash
SELF="remote_install.sh"
REL_VERSION=""
DEV_VERSION=""
INSTALLED_VERSION=""
TEMP_INSTALL="/tmp/aqualinkd"
OUTPUT="/tmp/aqualinkd_upgrade.log"
FROM_CURL=$FASE
SYSTEMD_LOG=$FALSE
# Remember not to use (check for terminal, as it may not exist when pipe to bash)
# ie. if [ -t 0 ]; then
# We can get called from no path, so find external commands
if command -v "systemd-cat" &>/dev/null; then
SYSTEMD_LOG=$TRUE
fi
log()
{
echo "$*"
if [ "$SYSTEMD_LOG" -eq $TRUE ]; then
# For some unknown reason, only way below works from aqualinkd process is adding "&>> "$OUTPUT""
echo "Upgrade: $*" | systemd-cat -t aqualinkd -p info &>> "$OUTPUT"
else
logger -p local0.notice -t aqualinkd "Upgrade: $*"
fi
echo "$*" 2>/dev/null >> "$OUTPUT"
}
logerr()
{
echo "Error: $*" >&2
logger -p local0.err -t aqualinkd "Upgrade1: $*" &>> "$OUTPUT"
if [ "$SYSTEMD_LOG" -eq $TRUE ]; then
# For some unknown reason, only way below works from aqualinkd process is adding "&>> "$OUTPUT""
echo "Upgrade: $*" | systemd-cat -t aqualinkd -p err &>> "$OUTPUT"
else
logger -p local0.err -t aqualinkd "Upgrade: $*"
fi
echo "ERROR: $*" 2>/dev/null >> "$OUTPUT"
}
function check_tool() {
cmd=$1
if ! command -v "$cmd" &>/dev/null
then
log "Command '$cmd' could not be found!"
return "$FALSE"
fi
return "$TRUE"
}
function latest_release_version {
REL_VERSION=$(curl -fsSL "$REPO/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")')
if [[ "$REL_VERSION" != "" ]]; then
return "$TRUE"
else
return "$FALSE"
fi
}
function latest_development_version {
#DEV_VERSION=$(curl --silent -L "https://raw.githubusercontent.com/sfeakes/AqualinkD/master/version.h" | grep AQUALINKD_VERSION | cut -d '"' -f 2)
DEV_VERSION=$(curl -fsSL -H "Accept: application/vnd.github.raw" "$REPO/contents/source/version.h" | grep AQUALINKD_VERSION | cut -d '"' -f 2)
if [[ "$DEV_VERSION" != "" ]]; then
return "$TRUE"
else
return "$FALSE"
fi
}
function installed_version {
if [ -f "$INSTALLED_BINARY" ]; then
if check_tool strings &&
check_tool grep &&
check_tool awk &&
check_tool tr; then
INSTALLED_VERSION=$(strings "$INSTALLED_BINARY" | grep sw_version | awk -v RS="," -v FS=":" '/sw_version/{print $2;exit;}' | tr -d ' "' )
log "Current installed version $INSTALLED_VERSION"
fi
else
log "AqualinkD is not installed"
fi
if [[ "$INSTALLED_VERSION" != "" ]]; then
return "$TRUE"
else
return "$FALSE"
fi
}
function check_system_arch {
ARCH=$(dpkg --print-architecture)
case $ARCH in
arm64 |\
armhf)
return "$TRUE"
;;
*)
logerr "System arch is $ARCH, this is not supported by AqualinkD"
return "$FALSE";
;;
esac
}
function check_can_upgrade {
#version=$1
output=""
# Check we have needed commands.
# curl, dpkg, systemctl
if ! command -v curl &>/dev/null; then output+="Command 'curl' not found, please check it's installed and in path\n"; fi
if ! command -v dpkg &>/dev/null; then output+="Command 'dpkg' not found, please check it's installed and in path\n"; fi
if ! command -v systemctl &>/dev/null; then output+="Command 'systemctl' not found, please check it's installed and in path\n"; fi
# Check root is rw
if mount | grep " / " | grep -q "(ro,"; then
# check if can remount rw.
if mount / -o remount,rw &>/dev/null; then
# can mount RW.
mount / -o remount,ro &>/dev/null
else
output+="Root filesystem is readonly & failed to remount read write, can't upgrade";
fi
fi
# Check we can get the latest version
if ! latest_release_version; then output+="Couldn't find latest version on github"; fi
# Check we can get the latest version
if command -v dpkg &>/dev/null; then
if ! check_system_arch; then output+="System Architecture not supported!"; fi
fi
# Check free diskspace
mkdir -p "$TEMP_INSTALL"
free_space_mb=$(df -mP "$TEMP_INSTALL" 2>/dev/null | awk 'NR==2{print $4}' )
# Check if the df command was successful and if free_space_mb is a number
if [ -z "$free_space_mb" ] || ! [[ "$free_space_mb" =~ ^[0-9]+$ ]]; then
output+="Could not retrieve free space for directory: $TEMP_INSTALL"
else
if [ "$free_space_mb" -lt "$REQUIRED_SPACE_MB" ]; then
output+="Not enough disk space on directory: $TEMP_INSTALL! (Required $REQUIRED_SPACE_MB MB)"
fi
fi
if [[ "$output" == "" ]] && [[ "$REL_VERSION" != "" ]]; then
return "$TRUE"
else [[ "$output" != "" ]]
logerr "$output";
return "$FALSE"
fi
return "$TRUE"
}
function download_latest_release {
mkdir -p "$TEMP_INSTALL"
tar_url=$(curl -fsSL "$REPO/releases/latest" | grep -Po '"tarball_url": "\K.*?(?=")')
if [[ "$tar_url" == "" ]]; then return "$FALSE"; fi
curl -fsSL "$tar_url" | tar xz --strip-components=1 --directory="$TEMP_INSTALL"
if [ $? -ne 0 ]; then return "$FALSE"; fi
return "$TRUE";
}
function download_latest_development {
mkdir -p "$TEMP_INSTALL"
tar_url="$REPO/tarball/master"
curl -fsSL "$tar_url" | tar xz --strip-components=1 --directory="$TEMP_INSTALL"
if [ $? -ne 0 ]; then return "$FALSE"; fi
return "$TRUE";
}
function download_version {
tar_url=$(curl -fsSL "$REPO/releases" | awk 'match($0,/.*"tarball_url": "(.*\/tarball\/.*)".*/)' | grep $1\" | awk -F '"' '{print $4}')
if [[ ! -n "$tar_url" ]]; then
return $"$FALSE"
fi
mkdir -p "$TEMP_INSTALL"
curl -fsSL "$tar_url" | tar xz --strip-components=1 --directory="$TEMP_INSTALL"
if [ $? -ne 0 ]; then return "$FALSE"; fi
return "$TRUE";
}
function get_all_versions {
curl -fsSL "$REPO/releases" | awk 'match($0,/.*"tarball_url": "(.*\/tarball\/.*)".*/)' | awk -F '/' '{split($NF,a,"\""); print a[1]}'
}
function run_install_script {
if [ ! -f "$TEMP_INSTALL/release/install.sh" ]; then
logerr "Can not find install script $TEMP_INSTALL/release/install.sh"
return "$FALSE"
fi
log "Installing AqualinkD $1"
# Can't run in background as it'll cleanup / delete files before install.
temp_file=$(mktemp)
nohup "$TEMP_INSTALL/release/install.sh" "--logfile" "$temp_file" >> "$OUTPUT" 2>&1
cat $temp_file
rm $temp_file
#nohup "/nas/data/Development/Raspberry/AqualinkD/release/install.sh" >> "$OUTPUT" 2>&1 &
#nohup "$TEMP_INSTALL/release/install.sh" >> "$OUTPUT" 2>&1 &
}
function remove_install {
curl -fsSL -H "Accept: application/vnd.github.raw+json" "$REPO/contents/install/install.sh" | sudo bash clean
}
function cleanup {
rm -rf "$TEMP_INSTALL"
}
####################################################
#
# Main
#
# See if we are called from curl or local dir.
# with curl no tty input and script name wil be blank.
if ! tty > /dev/null 2>&1; then
script=$(basename "$0")
if [ "$script" == "bash" ] || [ "$script" == "" ]; then
#echo "$(basename "$0") Script is likely running from curl"
# We don't actualy use this, but may in the future to leave it here
FROM_CURL=$TRUE
fi
fi
if [[ $EUID -ne 0 ]]; then
logerr "This script must be run as root"
exit 1
fi
#if [ ! -t 0 ]; then
#Don't use log function here as we will cleanout the file if it exists.
# Can't use $0 below as this script might be piped into bash from curl
echo "$SELF - $(date) " 2>/dev/null > "$OUTPUT"
#fi
if check_can_upgrade; then
installed_version
if [[ "$INSTALLED_VERSION" != "" ]]; then
log "Current AqualinkD installation $INSTALLED_VERSION"
log "System OK to upgrade AqualinkD to $REL_VERSION"
else
log "System OK to install AqualinkD $REL_VERSION"
fi
#exit $TRUE;
else
logerr "Can not upgrade, Please fix error(s)!"
exit $FALSE;
fi
case $1 in
check|checkupgradable)
# We have already done the check, and returned false at this point, so return true.
exit $TRUE
;;
development)
if ! latest_development_version; then logerr "getting development version";exit "$FALSE"; fi
if ! download_latest_development; then logerr "downloading latest development";exit "$FALSE"; fi
run_install_script "$REL_VERSION"
cleanup
;;
# Add Delete / remove / clean.
clean|delete|remove)
if ! remove_install; then logerr "Removing install";exit "$FALSE"; fi
log "Removed install"
;;
list|versions)
get_all_versions
;;
v*)
if ! download_version $1; then logerr "downloading version $1";exit "$FALSE"; fi
run_install_script "$REL_VERSION"
cleanup
;;
-h|help|h)
echo "AqualinkD Installation script"
echo "$SELF <- download and install latest AqualinkD version"
echo "$SELF latest <- download and install latest AqualinkD version"
echo "$SELF development <- download and install latest AqualinkD development version"
echo "$SELF clean <- Remove AqualinkD"
echo "$SELF list <- List available versions to install"
echo "$SELF v1.0.0 <- install AqualinkD v1.0.0 (use list option to see available versions)"
;;
latest|*)
if ! download_latest_release; then logerr "downloading latest"; exit "$FALSE"; fi
run_install_script "$REL_VERSION"
cleanup
;;
esac
exit
# List all versions
# curl -fsSL https://api.github.com/repos/sfeakes/aqualinkd/releases | awk 'match($0,/.*"html_url": "(.*\/releases\/tag\/.*)".*/)'
# curl -fsSL "https://api.github.com/repos/sfeakes/AqualinkD/releases" | awk 'match($0,/.*"tarball_url": "(.*\/tarball\/.*)".*/)' | awk -F '"' '{print $4}'

Binary file not shown.

1
release/serial_logger Symbolic link
View File

@ -0,0 +1 @@
./serial_logger-armhf

BIN
release/serial_logger-arm64 Executable file

Binary file not shown.

BIN
release/serial_logger-armhf Executable file

Binary file not shown.

View File

@ -1,235 +0,0 @@
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "aq_serial.h"
#include "utils.h"
#define SLOG_MAX 80
#define PACKET_MAX 600
/*
typedef enum used {
yes,
no,
unknown
} used;
*/
typedef struct serial_id_log {
unsigned char ID;
bool inuse;
} serial_id_log;
bool _keepRunning = true;
unsigned char _goodID[] = {0x0a, 0x0b, 0x08, 0x09};
unsigned char _goodPDAID[] = {0x60, 0x61, 0x62, 0x63};
unsigned char _filter = 0x00;
void intHandler(int dummy) {
_keepRunning = false;
logMessage(LOG_NOTICE, "Stopping!");
}
void advance_cursor() {
static int pos=0;
char cursor[4]={'/','-','\\','|'};
printf("%c\b", cursor[pos]);
fflush(stdout);
pos = (pos+1) % 4;
}
bool canUse(unsigned char ID) {
int i;
for (i = 0; i < 4; i++) {
if (ID == _goodID[i])
return true;
}
return false;
}
char* canUseExtended(unsigned char ID) {
int i;
for (i = 0; i < 4; i++) {
if (ID == _goodID[i])
return " <-- can use for Aqualinkd";
}
for (i = 0; i < 4; i++) {
if (ID == _goodPDAID[i])
return " <-- can use for Aqualinkd (PDA mode only)";
}
return "";
}
void printHex(char *pk, int length)
{
int i=0;
for (i=0;i<length;i++)
{
printf("0x%02hhx|",pk[i]);
}
}
void printPacket(unsigned char ID, unsigned char *packet_buffer, int packet_length)
{
//if (_filter != 0x00 && ID != _filter && packet_buffer[PKT_DEST] != _filter )
// return;
if (_filter != 0x00) {
if ( packet_buffer[PKT_DEST]==0x00 && ID != _filter )
return;
if ( packet_buffer[PKT_DEST]!=0x00 && packet_buffer[PKT_DEST] != _filter )
return;
}
if (packet_buffer[PKT_DEST] != 0x00)
printf("\n");
printf("%4.4s 0x%02hhx of type %8.8s", (packet_buffer[PKT_DEST]==0x00?"From":"To"), (packet_buffer[PKT_DEST]==0x00?ID:packet_buffer[PKT_DEST]), get_packet_type(packet_buffer, packet_length));
printf(" | HEX: ");
printHex((char *)packet_buffer, packet_length);
if (packet_buffer[PKT_CMD] == CMD_MSG || packet_buffer[PKT_CMD] == CMD_MSG_LONG) {
printf(" Message : ");
//fwrite(packet_buffer + 4, 1, AQ_MSGLEN+1, stdout);
fwrite(packet_buffer + 4, 1, packet_length-7, stdout);
}
//if (packet_buffer[PKT_DEST]==0x00)
// printf("\n\n");
//else
printf("\n");
}
int main(int argc, char *argv[]) {
int rs_fd;
int packet_length;
unsigned char packet_buffer[AQ_MAXPKTLEN];
unsigned char lastID;
int i = 0;
bool found;
serial_id_log slog[SLOG_MAX];
int sindex = 0;
int received_packets = 0;
int logPackets = PACKET_MAX;
int logLevel = LOG_NOTICE;
//int logLevel;
//char buffer[256];
//bool idMode = true;
if (getuid() != 0) {
fprintf(stderr, "ERROR %s Can only be run as root\n", argv[0]);
return EXIT_FAILURE;
}
if (argc < 2 || access( argv[1], F_OK ) == -1 ) {
fprintf(stderr, "ERROR, first param must be valid serial port, ie:-\n\t%s /dev/ttyUSB0\n\n", argv[0]);
fprintf(stderr, "Optional parameters are -d (debug) & -p <number> (log # packets) & -i <ID> ie:=\n\t%s /dev/ttyUSB0 -d -p 1000 -i 0x08\n\n", argv[0]);
return 1;
}
for (i = 2; i < argc; i++) {
if (strcmp(argv[i], "-d") == 0) {
logLevel = LOG_DEBUG;
} else if (strcmp(argv[i], "-p") == 0 && i+1 < argc) {
logPackets = atoi(argv[i+1]);
} else if (strcmp(argv[i], "-i") == 0 && i+1 < argc) {
unsigned int n;
sscanf(argv[i+1], "0x%2x", &n);
_filter = n;
logLevel = LOG_DEBUG; // no point in filtering on ID if we're not going to print it.
}
}
setLoggingPrms(logLevel, false, false);
rs_fd = init_serial_port(argv[1]);
signal(SIGINT, intHandler);
signal(SIGTERM, intHandler);
logMessage(LOG_NOTICE, "Logging serial information!\n");
if (logLevel < LOG_DEBUG)
printf("Please wait.");
while (_keepRunning == true) {
if (rs_fd < 0) {
logMessage(LOG_ERR, "ERROR, serial port disconnect\n");
}
packet_length = get_packet(rs_fd, packet_buffer);
if (packet_length == -1) {
// Unrecoverable read error. Force an attempt to reconnect.
logMessage(LOG_ERR, "ERROR, on serial port\n");
_keepRunning = false;
} else if (packet_length == 0) {
// Nothing read
} else if (packet_length > 0) {
//logMessage(LOG_DEBUG_SERIAL, "Received Packet for ID 0x%02hhx of type %s\n", packet_buffer[PKT_DEST], get_packet_type(packet_buffer, packet_length));
if (logLevel > LOG_NOTICE)
printPacket(lastID, packet_buffer, packet_length);
if (packet_buffer[PKT_DEST] != DEV_MASTER) {
found = false;
for (i = 0; i <= sindex; i++) {
if (slog[i].ID == packet_buffer[PKT_DEST]) {
found = true;
break;
}
}
if (found != true && sindex < SLOG_MAX) {
slog[sindex].ID = packet_buffer[PKT_DEST];
slog[sindex].inuse = false;
sindex++;
}
}
if (packet_buffer[PKT_DEST] == DEV_MASTER /*&& packet_buffer[PKT_CMD] == CMD_ACK*/) {
//logMessage(LOG_DEBUG_SERIAL, "ID is in use 0x%02hhx %x\n", lastID, lastID);
for (i = 0; i <= sindex; i++) {
if (slog[i].ID == lastID) {
slog[i].inuse = true;
break;
}
}
}
lastID = packet_buffer[PKT_DEST];
received_packets++;
}
if (logPackets != 0 && received_packets >= logPackets) {
_keepRunning = false;
}
if (logLevel < LOG_DEBUG)
advance_cursor();
//sleep(1);
}
logMessage(LOG_DEBUG, "\n");
if (sindex >= SLOG_MAX)
logMessage(LOG_ERR, "Ran out of storage, some ID's were not captured, please increase SLOG_MAX and recompile\n");
logMessage(LOG_NOTICE, "ID's found\n");
for (i = 0; i <= sindex; i++) {
//logMessage(LOG_NOTICE, "ID 0x%02hhx is %s %s\n", slog[i].ID, (slog[i].inuse == true) ? "in use" : "not used",
// (slog[i].inuse == false && canUse(slog[i].ID) == true)? " <-- can use for Aqualinkd" : "");
logMessage(LOG_NOTICE, "ID 0x%02hhx is %s %s\n", slog[i].ID, (slog[i].inuse == true) ? "in use" : "not used",
(slog[i].inuse == false)?canUseExtended(slog[i].ID):"");
}
return 0;
}

783
source/allbutton.c Normal file
View File

@ -0,0 +1,783 @@
#define _GNU_SOURCE 1 // for strcasestr & strptime
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "aqualink.h"
#include "allbutton.h"
#include "rs_msg_utils.h"
#include "devices_jandy.h"
#include "allbutton_aq_programmer.h"
#include "color_lights.h"
#include "aq_scheduler.h"
/* Below can also be called from serialadapter.c */
void processLEDstate(struct aqualinkdata *aq_data, unsigned char *packet, logmask_t from)
{
int i = 0;
int byte;
int bit;
if (memcmp(aq_data->raw_status, packet + 4, AQ_PSTLEN) != 0) {
aq_data->updated = true;
LOG(from,LOG_DEBUG, "Processing LEDs status CHANGED\n");
} else {
LOG(from,LOG_DEBUG, "Processing LEDs status\n");
// Their is no point in continuing here, so we could return if wanted.
// But for the moment, we don't need to speed up anything.
}
memcpy(aq_data->raw_status, packet + 4, AQ_PSTLEN);
//debuglogPacket(ALLB_LOG, );
for (byte = 0; byte < 5; byte++)
{
for (bit = 0; bit < 8; bit += 2)
{
if (((aq_data->raw_status[byte] >> (bit + 1)) & 1) == 1)
aq_data->aqualinkleds[i].state = FLASH;
else if (((aq_data->raw_status[byte] >> bit) & 1) == 1)
aq_data->aqualinkleds[i].state = ON;
else
aq_data->aqualinkleds[i].state = OFF;
//LOG(from,LOG_DEBUG,"Led %d state %d",i+1,aq_data->aqualinkleds[i].state);
i++;
}
}
// Reset enabled state for heaters, as they take 2 led states
if (aq_data->aqualinkleds[POOL_HTR_LED_INDEX - 1].state == OFF && aq_data->aqualinkleds[POOL_HTR_LED_INDEX].state == ON)
aq_data->aqualinkleds[POOL_HTR_LED_INDEX - 1].state = ENABLE;
if (aq_data->aqualinkleds[SPA_HTR_LED_INDEX - 1].state == OFF && aq_data->aqualinkleds[SPA_HTR_LED_INDEX].state == ON)
aq_data->aqualinkleds[SPA_HTR_LED_INDEX - 1].state = ENABLE;
if (aq_data->aqualinkleds[SOLAR_HTR_LED_INDEX - 1].state == OFF && aq_data->aqualinkleds[SOLAR_HTR_LED_INDEX].state == ON)
aq_data->aqualinkleds[SOLAR_HTR_LED_INDEX - 1].state = ENABLE;
/*
for (i=0; i < TOTAL_BUTTONS; i++) {
LOG(from,LOG_NOTICE, "%s = %d", aq_data->aqbuttons[i].name, aq_data->aqualinkleds[i].state);
}
*/
#ifdef CLIGHT_PANEL_FIX // Use state from RSSD protocol for color light if it's on.
for (int i=0; i < aq_data->num_lights; i++) {
if ( aq_data->lights[i].RSSDstate == ON && aq_data->lights[i].button->led->state != ON ) {
aq_data->lights[i].button->led->state = aq_data->lights[i].RSSDstate;
//LOG(from,LOG_WARNING,"Fix Jandy bug, color light '%s' is on, setting status to match!\n", aq_data->lights[i].button->label);
}
// Below is for aqualinkd programmable light, set color mode to last if something else turns it on or off.
if (aq_data->lights[i].lightType == LC_PROGRAMABLE && ! in_light_programming_mode(aq_data)) {
if (aq_data->lights[i].button->led->state == OFF && aq_data->lights[i].currentValue != 0) {
set_currentlight_value(&aq_data->lights[i], 0);
//LOG(ALLB_LOG,LOG_NOTICE,"****** SET LIGHT MODE 0 ******\n");
} else if (aq_data->lights[i].button->led->state == ON && aq_data->lights[i].currentValue == 0 && aq_data->lights[i].lastValue != 0) {
set_currentlight_value(&aq_data->lights[i], aq_data->lights[i].lastValue);
//LOG(ALLB_LOG,LOG_NOTICE,"****** SET LIGHT MODE %d ******\n",aq_data->lights[i].lastValue);
}
}
}
#endif
}
void setUnits(char *msg, struct aqualinkdata *aq_data)
{
char buf[AQ_MSGLEN*3];
rsm_strncpy(buf, (unsigned char *)msg, AQ_MSGLEN*3, AQ_MSGLONGLEN);
//ascii(buf, msg);
LOG(ALLB_LOG,LOG_DEBUG, "Getting temp units from message '%s', looking at '%c'\n", buf, buf[strlen(buf) - 1]);
if (msg[strlen(msg) - 1] == 'F')
aq_data->temp_units = FAHRENHEIT;
else if (msg[strlen(msg) - 1] == 'C')
aq_data->temp_units = CELSIUS;
else
aq_data->temp_units = UNKNOWN;
LOG(ALLB_LOG,LOG_INFO, "Temp Units set to %d (F=0, C=1, Unknown=2)\n", aq_data->temp_units);
}
// Defined as int16_t so 16 bits to mask
#define MSG_FREEZE (1 << 0) // 1
#define MSG_SERVICE (1 << 1) // 1
#define MSG_SWG (1 << 2)
#define MSG_BOOST (1 << 3)
#define MSG_TIMEOUT (1 << 4)
#define MSG_RS13BUTTON (1 << 5)
#define MSG_RS14BUTTON (1 << 6)
#define MSG_RS15BUTTON (1 << 7)
#define MSG_RS16BUTTON (1 << 8)
#define MSG_BATTERY_LOW (1 << 9)
#define MSG_SWG_DEVICE (1 << 10)
#define MSG_LOOP_POOL_TEMP (1 << 11)
#define MSG_LOOP_SPA_TEMP (1 << 12)
int16_t RS16_endswithLEDstate(char *msg, struct aqualinkdata *aq_data)
{
char *sp;
int i;
aqledstate state = LED_S_UNKNOWN;
//if (_aqconfig_.rs_panel_size < 16)
if (PANEL_SIZE() < 16)
return false;
sp = strrchr(msg, ' ');
if( sp == NULL )
return false;
if (strncasecmp(sp, " on", 3) == 0)
state = ON;
else if (strncasecmp(sp, " off", 4) == 0)
state = OFF;
else if (strncasecmp(sp, " enabled", 8) == 0) // Total guess, need to check
state = ENABLE;
else if (strncasecmp(sp, " no idea", 8) == 0) // need to figure out these states
state = FLASH;
if (state == LED_S_UNKNOWN)
return false;
// Only need to start at Aux B5->B8 (12-15)
// Loop over only aqdata->aqbuttons[13] to aqdata->aqbuttons[16]
for (i = aq_data->rs16_vbutton_start; i <= aq_data->rs16_vbutton_end; i++) {
//TOTAL_BUTTONS
if ( stristr(msg, aq_data->aqbuttons[i].label) != NULL) {
aq_data->aqbuttons[i].led->state = state;
LOG(ALLB_LOG,LOG_INFO, "Set %s to %d\n", aq_data->aqbuttons[i].label, aq_data->aqbuttons[i].led->state);
// Return true should be the result, but in the if we want to continue to display message
//return true;
if (i == 13)
return MSG_RS13BUTTON;
else if (i == 14)
return MSG_RS14BUTTON;
else if (i == 15)
return MSG_RS15BUTTON;
else if (i == 16)
return MSG_RS16BUTTON;
else
{
LOG(ALLB_LOG,LOG_ERR, "RS16 Button Set error %s to %d, %d is out of scope\n", aq_data->aqbuttons[i].label, aq_data->aqbuttons[i].led->state, i);
return false;
}
}
}
return false;
}
void _processMessage(char *message, struct aqualinkdata *aq_data, bool reset);
void processMessage(char *message, struct aqualinkdata *aq_data)
{
_processMessage(message, aq_data, false);
}
void processMessageReset(struct aqualinkdata *aq_data)
{
_processMessage(NULL, aq_data, true);
}
void _processMessage(char *message, struct aqualinkdata *aq_data, bool reset)
{
char *msg;
static bool _initWithRS = false;
//static bool _gotREV = false;
//static int freeze_msg_count = 0;
//static int service_msg_count = 0;
//static int swg_msg_count = 0;
//static int boost_msg_count = 0;
static int16_t msg_loop = 0;
static aqledstate default_frz_protect_state = OFF;
static bool boostInLastLoop = false;
// NSF replace message with msg
int16_t rs16;
//msg = stripwhitespace(message);
//strcpy(aq_data->last_message, msg);
//LOG(ALLB_LOG,LOG_INFO, "RS Message :- '%s'\n", msg);
// Check long messages in this if/elseif block first, as some messages are similar.
// ie "POOL TEMP" and "POOL TEMP IS SET TO" so want correct match first.
//
//if (stristr(msg, "JANDY AquaLinkRS") != NULL) {
if (!reset) {
msg = stripwhitespace(message);
strcpy(aq_data->last_message, msg);
LOG(ALLB_LOG,LOG_INFO, "RS Message :- '%s'\n", msg);
// Just set this to off, it will re-set since it'll be the only message we get if on
aq_data->service_mode_state = OFF;
} else {
//aq_data->display_message = NULL;
aq_data->last_display_message[0] = ' ';
aq_data->last_display_message[1] = '\0';
// Anything that wasn't on during the last set of messages, turn off
if ((msg_loop & MSG_FREEZE) != MSG_FREEZE) {
if (aq_data->frz_protect_state != default_frz_protect_state) {
LOG(ALLB_LOG,LOG_INFO, "Freeze protect turned off\n");
event_happened_set_device_state(AQS_FRZ_PROTECT_OFF, aq_data);
// Add code to check Pump if to turn it on (was scheduled) ie time now is inbetween ON / OFF schedule
}
aq_data->frz_protect_state = default_frz_protect_state;
}
if ((msg_loop & MSG_SERVICE) != MSG_SERVICE &&
(msg_loop & MSG_TIMEOUT) != MSG_TIMEOUT ) {
aq_data->service_mode_state = OFF; // IF we get this message then Service / Timeout is off
}
if ( ((msg_loop & MSG_SWG_DEVICE) != MSG_SWG_DEVICE) && aq_data->swg_led_state != LED_S_UNKNOWN) {
// No Additional SWG devices messages like "no flow"
if ((msg_loop & MSG_SWG) != MSG_SWG && aq_data->aqbuttons[PUMP_INDEX].led->state == OFF )
setSWGdeviceStatus(aq_data, ALLBUTTON, SWG_STATUS_OFF);
else
setSWGdeviceStatus(aq_data, ALLBUTTON, SWG_STATUS_ON);
}
// If no AQUAPURE message, either (no SWG, it's set 0, or it's off).
if ((msg_loop & MSG_SWG) != MSG_SWG && aq_data->swg_led_state != LED_S_UNKNOWN ) {
if (aq_data->swg_percent != 0 || aq_data->swg_led_state == ON) {
// Something is wrong here. Let's check pump, if on set SWG to 0, if off turn SWE off
if ( aq_data->aqbuttons[PUMP_INDEX].led->state == OFF) {
LOG(ALLB_LOG,LOG_INFO, "No AQUAPURE message in cycle, pump is off so setting SWG to off\n");
setSWGoff(aq_data);
} else {
LOG(ALLB_LOG,LOG_INFO, "No AQUAPURE message in cycle, pump is on so setting SWG to 0%%\n");
changeSWGpercent(aq_data, 0);
}
} else if (isIAQT_ENABLED == false && isONET_ENABLED == false && READ_RSDEV_SWG == false ) {
//We have no other way to read SWG %=0, so turn SWG on with pump
if ( aq_data->aqbuttons[PUMP_INDEX].led->state == ON) {
LOG(ALLB_LOG,LOG_INFO, "No AQUAPURE message in cycle, pump is off so setting SWG to off\n");
//changeSWGpercent(aq_data, 0);
setSWGenabled(aq_data);
}
}
// NSF Need something to catch startup when SWG=0 so we set it to enabeled.
// when other ways/protocols to detect SWG=0 are turned off.
}
if ((msg_loop & MSG_LOOP_POOL_TEMP) != MSG_LOOP_POOL_TEMP && aq_data->pool_temp != TEMP_UNKNOWN ) {
aq_data->pool_temp = TEMP_UNKNOWN;
}
if ((msg_loop & MSG_LOOP_SPA_TEMP) != MSG_LOOP_SPA_TEMP && aq_data->spa_temp != TEMP_UNKNOWN ) {
aq_data->spa_temp = TEMP_UNKNOWN;
}
/*
// AQUAPURE=0 we never get that message on ALLBUTTON so don't turn off unless filter pump if off
if ((msg_loop & MSG_SWG) != MSG_SWG && aq_data->aqbuttons[PUMP_INDEX].led->state == OFF ) {
//aq_data->ar_swg_status = SWG_STATUS_OFF;
setSWGoff(aq_data);
}
*/
if ((msg_loop & MSG_BOOST) != MSG_BOOST) {
if (aq_data->boost == true || boostInLastLoop == true) {
LOG(ALLB_LOG,LOG_INFO, "Boost turned off\n");
event_happened_set_device_state(AQS_BOOST_OFF, aq_data);
// Add code to check Pump if to turn it on (was scheduled) ie time now is inbetween ON / OFF schedule
}
aq_data->boost = false;
aq_data->boost_msg[0] = '\0';
aq_data->boost_duration = 0;
boostInLastLoop = false;
//if (aq_data->swg_percent >= 101)
// aq_data->swg_percent = 0;
}
if ((msg_loop & MSG_BATTERY_LOW) != MSG_BATTERY_LOW)
aq_data->battery = OK;
//if ( _aqconfig_.rs_panel_size >= 16) {
//if ( (int)PANEL_SIZE >= 16) { // NSF No idea why this fails on RS-4, but it does. Come back and find out why
if ( PANEL_SIZE() >= 16 ) {
//printf("Panel size %d What the fuck am I doing here\n",PANEL_SIZE());
if ((msg_loop & MSG_RS13BUTTON) != MSG_RS13BUTTON)
aq_data->aqbuttons[13].led->state = OFF;
if ((msg_loop & MSG_RS14BUTTON) != MSG_RS14BUTTON)
aq_data->aqbuttons[14].led->state = OFF;
if ((msg_loop & MSG_RS15BUTTON) != MSG_RS15BUTTON)
aq_data->aqbuttons[15].led->state = OFF;
if ((msg_loop & MSG_RS16BUTTON) != MSG_RS16BUTTON)
aq_data->aqbuttons[16].led->state = OFF;
}
msg_loop = 0;
return;
}
if (stristr(msg, LNG_MSG_BATTERY_LOW) != NULL)
{
aq_data->battery = LOW;
msg_loop |= MSG_BATTERY_LOW;
strcpy(aq_data->last_display_message, msg); // Also display the message on web UI
}
else if (stristr(msg, LNG_MSG_POOL_TEMP_SET) != NULL)
{
//LOG(ALLB_LOG,LOG_DEBUG, "**************** pool htr long message: %s", &message[20]);
aq_data->pool_htr_set_point = atoi(message + 20);
if (aq_data->temp_units == UNKNOWN)
setUnits(msg, aq_data);
}
else if (stristr(msg, LNG_MSG_SPA_TEMP_SET) != NULL)
{
//LOG(ALLB_LOG,LOG_DEBUG, "spa htr long message: %s", &message[19]);
aq_data->spa_htr_set_point = atoi(message + 19);
if (aq_data->temp_units == UNKNOWN)
setUnits(msg, aq_data);
}
else if (stristr(msg, LNG_MSG_FREEZE_PROTECTION_SET) != NULL)
{
//LOG(ALLB_LOG,LOG_DEBUG, "frz protect long message: %s", &message[28]);
aq_data->frz_protect_set_point = atoi(message + 28);
aq_data->frz_protect_state = ENABLE;
default_frz_protect_state = ENABLE;
if (aq_data->temp_units == UNKNOWN)
setUnits(msg, aq_data);
}
else if (strncasecmp(msg, MSG_AIR_TEMP, MSG_AIR_TEMP_LEN) == 0)
{
aq_data->air_temp = atoi(msg + MSG_AIR_TEMP_LEN);
if (aq_data->temp_units == UNKNOWN)
setUnits(msg, aq_data);
}
else if (strncasecmp(msg, MSG_POOL_TEMP, MSG_POOL_TEMP_LEN) == 0)
{
msg_loop |= MSG_LOOP_POOL_TEMP;
aq_data->pool_temp = atoi(msg + MSG_POOL_TEMP_LEN);
if (aq_data->temp_units == UNKNOWN)
setUnits(msg, aq_data);
}
else if (strncasecmp(msg, MSG_SPA_TEMP, MSG_SPA_TEMP_LEN) == 0)
{
msg_loop |= MSG_LOOP_SPA_TEMP;
aq_data->spa_temp = atoi(msg + MSG_SPA_TEMP_LEN);
if (aq_data->temp_units == UNKNOWN)
setUnits(msg, aq_data);
}
// NSF If get water temp rather than pool or spa in some cases, then we are in Pool OR Spa ONLY mode
else if (strncasecmp(msg, MSG_WATER_TEMP, MSG_WATER_TEMP_LEN) == 0)
{
aq_data->pool_temp = atoi(msg + MSG_WATER_TEMP_LEN);
aq_data->spa_temp = atoi(msg + MSG_WATER_TEMP_LEN);
if (aq_data->temp_units == UNKNOWN)
setUnits(msg, aq_data);
if (isSINGLE_DEV_PANEL != true)
{
changePanelToMode_Only();
LOG(ALLB_LOG,LOG_ERR, "AqualinkD set to 'Combo Pool & Spa' but detected 'Only Pool OR Spa' panel, please change config\n");
}
}
else if (stristr(msg, LNG_MSG_WATER_TEMP1_SET) != NULL)
{
aq_data->pool_htr_set_point = atoi(message + 28);
if (aq_data->temp_units == UNKNOWN)
setUnits(msg, aq_data);
if (isSINGLE_DEV_PANEL != true)
{
changePanelToMode_Only();
LOG(ALLB_LOG,LOG_ERR, "AqualinkD set to 'Combo Pool & Spa' but detected 'Only Pool OR Spa' panel, please change config\n");
}
}
else if (stristr(msg, LNG_MSG_WATER_TEMP2_SET) != NULL)
{
aq_data->spa_htr_set_point = atoi(message + 27);
if (aq_data->temp_units == UNKNOWN)
setUnits(msg, aq_data);
if (isSINGLE_DEV_PANEL != true)
{
changePanelToMode_Only();
LOG(ALLB_LOG,LOG_ERR, "AqualinkD set to 'Combo Pool & Spa' but detected 'Only Pool OR Spa' panel, please change config\n");
}
}
else if (stristr(msg, LNG_MSG_SERVICE_ACTIVE) != NULL)
{
if (aq_data->service_mode_state == OFF)
LOG(ALLB_LOG,LOG_NOTICE, "AqualinkD set to Service Mode\n");
aq_data->service_mode_state = ON;
msg_loop |= MSG_SERVICE;
//service_msg_count = 0;
}
else if (stristr(msg, LNG_MSG_TIMEOUT_ACTIVE) != NULL)
{
if (aq_data->service_mode_state == OFF)
LOG(ALLB_LOG,LOG_NOTICE, "AqualinkD set to Timeout Mode\n");
aq_data->service_mode_state = FLASH;
msg_loop |= MSG_TIMEOUT;
//service_msg_count = 0;
}
else if (stristr(msg, LNG_MSG_FREEZE_PROTECTION_ACTIVATED) != NULL)
{
msg_loop |= MSG_FREEZE;
//aq_data->frz_protect_state = default_frz_protect_state;
aq_data->frz_protect_state = ON;
//freeze_msg_count = 0;
strcpy(aq_data->last_display_message, msg); // Also display the message on web UI
}
else if (ENABLE_CHILLER && (stristr(msg,"Chiller") != NULL || stristr(msg,"Heat Pump") != NULL)) {
processHeatPumpDisplayMessage(msg, aq_data); // This doesn;t exist yet
}
/* // Not sure when to do with these for the moment, so no need to compile in the test.
else if (stristr(msg, LNG_MSG_CHEM_FEED_ON) != NULL) {
}
else if (stristr(msg, LNG_MSG_CHEM_FEED_OFF) != NULL) {
}
*/
else if (msg[2] == '/' && msg[5] == '/' && msg[8] == ' ')
{ // date in format '08/29/16 MON'
strcpy(aq_data->date, msg);
}
else if (stristr(msg, MSG_SWG_PCT) != NULL)
{
if (strncasecmp(msg, MSG_SWG_PCT, MSG_SWG_PCT_LEN) == 0 && strncasecmp(msg, "AQUAPURE HRS", 12) != 0) {
changeSWGpercent(aq_data, atoi(msg + MSG_SWG_PCT_LEN));
}
else if (strncasecmp(msg, "AQUAPURE HRS", 12) != 0 && strncasecmp(msg, "SET AQUAPURE", 12) != 0)
{
if (strcasestr(msg, MSG_SWG_NO_FLOW) != NULL)
setSWGdeviceStatus(aq_data, ALLBUTTON, SWG_STATUS_NO_FLOW);
else if (strcasestr(msg, MSG_SWG_LOW_SALT) != NULL)
setSWGdeviceStatus(aq_data, ALLBUTTON, SWG_STATUS_LOW_SALT);
else if (strcasestr(msg, MSG_SWG_HIGH_SALT) != NULL)
setSWGdeviceStatus(aq_data, ALLBUTTON, SWG_STATUS_HI_SALT);
else if (strcasestr(msg, MSG_SWG_FAULT) != NULL)
setSWGdeviceStatus(aq_data, ALLBUTTON, SWG_STATUS_GENFAULT);
//setSWGdeviceStatus(aq_data, ALLBUTTON, SWG_STATUS_CHECK_PCB);
// Any of these messages want to display.
strcpy(aq_data->last_display_message, msg);
msg_loop |= MSG_SWG_DEVICE;
}
msg_loop |= MSG_SWG;
}
else if (strncasecmp(msg, MSG_SWG_PPM, MSG_SWG_PPM_LEN) == 0)
{
aq_data->swg_ppm = atoi(msg + MSG_SWG_PPM_LEN);
msg_loop |= MSG_SWG;
}
else if ((msg[1] == ':' || msg[2] == ':') && msg[strlen(msg) - 1] == 'M')
{ // time in format '9:45 AM'
strcpy(aq_data->time, msg);
// Setting time takes a long time, so don't try until we have all other programmed data.
if (_initWithRS == true && strlen(aq_data->date) > 1 && checkAqualinkTime() != true)
{
LOG(ALLB_LOG,LOG_NOTICE, "RS time is NOT accurate '%s %s', re-setting on controller!\n", aq_data->time, aq_data->date);
aq_programmer(AQ_SET_TIME, NULL, aq_data);
}
else if (_initWithRS == false || _aqconfig_.sync_panel_time == false)
{
LOG(ALLB_LOG,LOG_DEBUG, "RS time '%s %s' not checking\n", aq_data->time, aq_data->date);
}
else if (_initWithRS == true)
{
LOG(ALLB_LOG,LOG_DEBUG, "RS time is accurate '%s %s'\n", aq_data->time, aq_data->date);
}
// If we get a time message before REV, the controller didn't see us as we started too quickly.
/* Don't need to check this anymore with the check for probe before startup.
if (_gotREV == false)
{
LOG(ALLB_LOG,LOG_NOTICE, "Getting control panel information\n", msg);
aq_programmer(AQ_GET_DIAGNOSTICS_MODEL, NULL, aq_data);
_gotREV = true; // Force it to true just incase we don't understand the model#
}
*/
}
else if (strstr(msg, " REV ") != NULL || strstr(msg, " REV. ") != NULL)
{ // '8157 REV MMM'
// A master firmware revision message.
strcpy(aq_data->version, msg);
rsm_get_revision(aq_data->revision, aq_data->version, strlen(aq_data->version));
setPanelInformationFromPanelMsg(aq_data, msg, PANEL_CPU | PANEL_REV, ALLBUTTON);
//setBoardCPURevision(aq_data, aq_data->version, strlen(aq_data->version), ALLB_LOG);
//_gotREV = true;
LOG(ALLB_LOG,LOG_DEBUG, "Control Panel version %s\n", aq_data->version);
LOG(ALLB_LOG,LOG_DEBUG, "Control Panel revision %s\n", aq_data->revision);
if (_initWithRS == false)
{
//LOG(ALLBUTTON,LOG_NOTICE, "Standard protocol initialization complete\n");
queueGetProgramData(ALLBUTTON, aq_data);
event_happened_set_device_state(AQS_POWER_ON, aq_data);
//queueGetExtendedProgramData(ALLBUTTON, aq_data, _aqconfig_.use_panel_aux_labels);
_initWithRS = true;
}
}
else if (stristr(msg, " TURNS ON") != NULL)
{
LOG(ALLB_LOG,LOG_NOTICE, "Program data '%s'\n", msg);
}
else if (_aqconfig_.override_freeze_protect == TRUE && strncasecmp(msg, "Press Enter* to override Freeze Protection with", 47) == 0)
{
//send_cmd(KEY_ENTER, aq_data);
//aq_programmer(AQ_SEND_CMD, (char *)KEY_ENTER, aq_data);
aq_send_allb_cmd(KEY_ENTER);
}
// Process any button states (fake LED) for RS12 and above keypads
// Text will be button label on or off ie Aux_B2 off or WaterFall off
//else if ( _aqconfig_.rs_panel_size >= 16 && (rs16 = RS16_endswithLEDstate(msg)) != 0 )
else if (PANEL_SIZE() >= 16 && (rs16 = RS16_endswithLEDstate(msg, aq_data)) != 0 )
{
msg_loop |= rs16;
// Do nothing, just stop other else if statments executing
// make sure we also display the message.
// Note we only get ON messages here, Off messages will not be sent if something else turned it off
// use the Onetouch or iAqua equiptment page for off.
strcpy(aq_data->last_display_message, msg);
}
else if (((msg[4] == ':') || (msg[6] == ':')) && (strncasecmp(msg, "AUX", 3) == 0) )
{ // Should probable check we are in programming mode.
// 'Aux3: No Label'
// 'Aux B1: No Label'
int labelid;
int ni = 3;
if (msg[4] == 'B') { ni = 5; }
labelid = atoi(msg + ni);
if (labelid > 0 && _aqconfig_.use_panel_aux_labels == true)
{
if (ni == 5)
labelid = labelid + 8;
else
labelid = labelid + 1;
// Aux1: on panel = Button 3 in aqualinkd (button 2 in array)
if (strncasecmp(msg+ni+3, "No Label", 8) != 0) {
aq_data->aqbuttons[labelid].label = prittyString(cleanalloc(msg+ni+2));
LOG(ALLB_LOG,LOG_NOTICE, "AUX ID %s label set to '%s'\n", aq_data->aqbuttons[labelid].name, aq_data->aqbuttons[labelid].label);
} else {
LOG(ALLB_LOG,LOG_NOTICE, "AUX ID %s has no control panel label using '%s'\n", aq_data->aqbuttons[labelid].name, aq_data->aqbuttons[labelid].label);
}
//aq_data->aqbuttons[labelid + 1].label = cleanalloc(msg + 5);
}
}
// BOOST POOL 23:59 REMAINING
else if ( (strncasecmp(msg, "BOOST POOL", 10) == 0) && (strcasestr(msg, "REMAINING") != NULL) ) {
// Ignore messages if in programming mode. We get one of these turning off for some strange reason.
if (in_programming_mode(aq_data) == false) {
if (aq_data->boost == false || boostInLastLoop == false) {
event_happened_set_device_state(AQS_BOOST_ON, aq_data);
}
snprintf(aq_data->boost_msg, 6, "%s", &msg[11]);
aq_data->boost_duration = rsm_HHMM2min(aq_data->boost_msg);
aq_data->boost = true;
msg_loop |= MSG_BOOST;
msg_loop |= MSG_SWG;
boostInLastLoop = true;
//convert_boost_to_duration(aq_data->boost_msg)
//if (aq_data->ar_swg_status != SWG_STATUS_ON) {aq_data->ar_swg_status = SWG_STATUS_ON;}
if (aq_data->swg_percent != 101) {changeSWGpercent(aq_data, 101);}
//boost_msg_count = 0;
//if (aq_data->active_thread.thread_id == 0)
strcpy(aq_data->last_display_message, msg); // Also display the message on web UI if not in programming mode
}
}
else
{
LOG(ALLB_LOG,LOG_DEBUG_SERIAL, "Ignoring '%s'\n", msg);
//aq_data->display_message = msg;
//if (in_programming_mode(aq_data) == false && aq_data->simulate_panel == false &&
if (in_programming_mode(aq_data) == false &&
stristr(msg, "JANDY AquaLinkRS") == NULL &&
//stristr(msg, "PUMP O") == NULL &&// Catch 'PUMP ON' and 'PUMP OFF' but not 'PUMP WILL TURN ON'
strncasecmp(msg, "PUMP O", 6) != 0 &&// Catch 'PUMP ON' and 'PUMP OFF' but not 'PUMP WILL TURN ON'
stristr(msg, "MAINTAIN") == NULL && // Catch 'MAINTAIN TEMP IS OFF'
stristr(msg, "0 PSI") == NULL /* // Catch some erronious message on test harness
stristr(msg, "CLEANER O") == NULL &&
stristr(msg, "SPA O") == NULL &&
stristr(msg, "AUX") == NULL*/
)
{ // Catch all AUX1 AUX5 messages
//aq_data->display_last_message = true;
strcpy(aq_data->last_display_message, msg);
//rsm_strncpy(aq_data->last_display_message, (unsigned char *)msg, AQ_MSGLONGLEN, AQ_MSGLONGLEN);
}
}
// Send every message if we are in simulate panel mode
//if (aq_data->simulate_panel)
// strcpy(aq_data->last_display_message, msg);
//rsm_strncpy(aq_data->last_display_message, (unsigned char *)msg, AQ_MSGLONGLEN, AQ_MSGLONGLEN);
//ascii(aq_data->last_display_message, msg);
//LOG(ALLB_LOG,LOG_INFO, "RS Message loop :- '%d'\n", msg_loop);
// We processed the next message, kick any threads waiting on the message.
//printf ("Message kicking\n");
kick_aq_program_thread(aq_data, ALLBUTTON);
}
bool process_allbutton_packet(unsigned char *packet, int length, struct aqualinkdata *aq_data)
{
bool rtn = false;
//static unsigned char last_packet[AQ_MAXPKTLEN];
static unsigned char last_checksum;
static char message[AQ_MSGLONGLEN + 1];
static int processing_long_msg = 0;
// Check packet against last check if different.
// Should only use the checksum, not whole packet since it's status messages.
/*
if ( packet[PKT_CMD] == CMD_STATUS && (memcmp(packet, last_packet, length) == 0))
{
LOG(ALLB_LOG,LOG_DEBUG_SERIAL, "RS Received duplicate, ignoring.\n", length);
return rtn;
}
else
{
memcpy(last_packet, packet, length);
aq_data->last_packet_type = packet[PKT_CMD];
rtn = true;
}
*/
aq_data->last_packet_type = packet[PKT_CMD];
if ( packet[PKT_CMD] == CMD_STATUS && packet[length-3] == last_checksum && ! in_programming_mode(aq_data) )
{
LOG(ALLB_LOG,LOG_DEBUG_SERIAL, "RS Received duplicate, ignoring.\n", length);
return false;
}
else
{
last_checksum = packet[length-3];
rtn = true;
}
if (processing_long_msg > 0 && packet[PKT_CMD] != CMD_MSG_LONG)
{
processing_long_msg = 0;
//LOG(ALLB_LOG,LOG_ERR, "RS failed to receive complete long message, received '%s'\n",message);
//LOG(ALLB_LOG,LOG_DEBUG, "RS didn't finished receiving of MSG_LONG '%s'\n",message);
processMessage(message, aq_data);
}
LOG(ALLB_LOG,LOG_DEBUG_SERIAL, "RS Received packet type 0x%02hhx length %d.\n", packet[PKT_CMD], length);
switch (packet[PKT_CMD])
{
case CMD_ACK:
//LOG(ALLB_LOG,LOG_DEBUG_SERIAL, "RS Received ACK length %d.\n", length);
break;
case CMD_STATUS:
//LOG(ALLB_LOG,LOG_DEBUG, "RS Received STATUS length %d.\n", length);
//debuglogPacket(ALLB_LOG, packet, length, true, true);
//memcpy(aq_data->raw_status, packet + 4, AQ_PSTLEN);
//processLEDstate(aq_data);
processLEDstate(aq_data, packet, ALLB_LOG);
/* NSF Take this out, and use the ALLButton loop cycle to determin if we get spa/pool temp
messages. Works better for dual equiptment when both pool & spa pumps and dual temp sensors */
/*
if (aq_data->aqbuttons[PUMP_INDEX].led->state == OFF)
{
aq_data->pool_temp = TEMP_UNKNOWN;
aq_data->spa_temp = TEMP_UNKNOWN;
//aq_data->spa_temp = _aqconfig_.report_zero_spa_temp?-18:TEMP_UNKNOWN;
}
else if (aq_data->aqbuttons[SPA_INDEX].led->state == OFF && isSINGLE_DEV_PANEL != true)
{
//aq_data->spa_temp = _aqconfig_.report_zero_spa_temp?-18:TEMP_UNKNOWN;
aq_data->spa_temp = TEMP_UNKNOWN;
}
else if (aq_data->aqbuttons[SPA_INDEX].led->state == ON && isSINGLE_DEV_PANEL != true)
{
aq_data->pool_temp = TEMP_UNKNOWN;
}
*/
// COLOR MODE programming relies on state changes, so let any threads know
//if (aq_data->active_thread.ptype == AQ_SET_LIGHTPROGRAM_MODE) {
if ( in_light_programming_mode(aq_data) ) {
kick_aq_program_thread(aq_data, ALLBUTTON);
}
break;
case CMD_MSG:
case CMD_MSG_LONG:
{
int index = packet[PKT_DATA]; // Will get 0x00 for complete message, 0x01 for start on long message 0x05 last of long message
//printf("RSM received message at index %d '%.*s'\n",index,AQ_MSGLEN,(char *)packet + PKT_DATA + 1);
if (index <= 1){
memset(message, 0, AQ_MSGLONGLEN + 1);
//strncpy(message, (char *)packet + PKT_DATA + 1, AQ_MSGLEN);
rsm_strncpy(message, packet + PKT_DATA + 1, AQ_MSGLONGLEN, AQ_MSGLEN);
processing_long_msg = index;
//LOG(ALLB_LOG,LOG_ERR, "Message %s\n",message);
} else {
//strncpy(&message[(processing_long_msg * AQ_MSGLEN)], (char *)packet + PKT_DATA + 1, AQ_MSGLEN);
//rsm_strncpy(&message[(processing_long_msg * AQ_MSGLEN)], (unsigned char *)packet + PKT_DATA + 1, AQ_MSGLONGLEN, AQ_MSGLEN);
rsm_strncpy(&message[( (index-1) * AQ_MSGLEN)], (unsigned char *)packet + PKT_DATA + 1, AQ_MSGLONGLEN, AQ_MSGLEN);
//LOG(ALLB_LOG,LOG_ERR, "Long Message %s\n",message);
if (++processing_long_msg != index) {
LOG(ALLB_LOG,LOG_DEBUG, "Long message index %d doesn't match buffer %d\n",index,processing_long_msg);
//printf("RSM Long message index %d doesn't match buffer %d\n",index,processing_long_msg);
}
#ifdef PROCESS_INCOMPLETE_MESSAGES
kick_aq_program_thread(aq_data, ALLBUTTON);
#endif
}
if (index == 0 || index == 5) {
//printf("RSM process message '%s'\n",message);
// MOVED FROM LINE 701 see if less errors
//kick_aq_program_thread(aq_data, ALLBUTTON);
LOG(ALLB_LOG,LOG_DEBUG, "Processing Message - '%s'\n",message);
processMessage(message, aq_data); // This will kick thread
}
}
break;
case CMD_PROBE:
LOG(ALLB_LOG,LOG_DEBUG, "RS Received PROBE length %d.\n", length);
//LOG(ALLB_LOG,LOG_INFO, "Synch'ing with Aqualink master device...\n");
rtn = false;
break;
case CMD_MSG_LOOP_ST:
LOG(ALLB_LOG,LOG_INFO, "RS Received message loop start\n");
processMessageReset(aq_data);
rtn = false;
break;
default:
LOG(ALLB_LOG,LOG_INFO, "RS Received unknown packet, 0x%02hhx\n", packet[PKT_CMD]);
rtn = false;
break;
}
return rtn;
}

8
source/allbutton.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef ALLBUTTON_H_
#define ALLBUTTON_H_
void processLEDstate(struct aqualinkdata *aq_data, unsigned char *packet, logmask_t from);
bool process_allbutton_packet(unsigned char *packet, int length, struct aqualinkdata *aq_data);
#endif //ALLBUTTON_H_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
#ifndef ALLBUTTON_PROGRAMMER_H_
#define ALLBUTTON_PROGRAMMER_H_
void *set_allbutton_pool_heater_temps( void *ptr );
void *set_allbutton_spa_heater_temps( void *ptr );
void *set_allbutton_freeze_heater_temps( void *ptr );
void *set_allbutton_time( void *ptr );
void *get_allbutton_pool_spa_heater_temps( void *ptr );
void *get_allbutton_programs( void *ptr );
void *get_allbutton_freeze_protect_temp( void *ptr );
void *get_allbutton_diag_model( void *ptr );
void *get_allbutton_aux_labels( void *ptr );
//void *threadded_send_cmd( void *ptr );
void *set_allbutton_light_programmode( void *ptr );
void *set_allbutton_light_colormode( void *ptr );
void *set_allbutton_light_dimmer( void *ptr );
void *set_allbutton_SWG( void *ptr );
void *set_allbutton_boost( void *ptr );
unsigned char pop_allb_cmd(struct aqualinkdata *aq_data);
void aq_send_allb_cmd(unsigned char cmd);
#endif //ALLBUTTON_PROGRAMMER_H_

87
source/aq_mqtt.h Normal file
View File

@ -0,0 +1,87 @@
#ifndef AQ_MQTT_H_
#define AQ_MQTT_H_
#define AIR_TEMP_TOPIC "Temperature/Air"
#define POOL_TEMP_TOPIC "Temperature/Pool"
#define SPA_TEMP_TOPIC "Temperature/Spa"
//#define POOL_SETPT_TOPIC "Pool_Heater/setpoint"
//#define SPA_SETPT_TOPIC "Spa_Heater/setpoint"
#define SERVICE_MODE_TOPIC "Service_Mode"
#define DISPLAY_MSG_TOPIC "Display_Message"
#define ENABELED_SUBT "/enabled"
#define SWG_TOPIC "SWG"
#define SWG_PPM_TOPIC SWG_TOPIC "/PPM"
#define SWG_PPM_F_TOPIC SWG_TOPIC "/PPM_f"
#define SWG_ENABELED_TOPIC SWG_TOPIC ENABELED_SUBT
#define SWG_PERCENT_TOPIC SWG_TOPIC "/Percent"
#define SWG_PERCENT_F_TOPIC SWG_TOPIC "/Percent_f"
#define SWG_SETPOINT_TOPIC SWG_TOPIC "/setpoint"
#define SWG_EXTENDED_TOPIC SWG_TOPIC "/fullstatus"
#define SWG_BOOST_TOPIC SWG_TOPIC "/Boost"
#define SWG_BOOST_DURATION_TOPIC SWG_BOOST_TOPIC "/duration"
#define SWG_STATUS_MSG_TOPIC SWG_TOPIC "/Display_Message"
#define CHEM_TOPIC "CHEM"
#define CHEM_PH_TOPIC CHEM_TOPIC "/pH"
#define CHRM_PH_F_TOPIC CHEM_TOPIC "/pH_f"
#define CHEM_ORP_TOPIC CHEM_TOPIC "/ORP"
#define CHRM_ORP_F_TOPIC CHEM_TOPIC "/ORP_f"
#define LXI_TOPIC "LXi"
#define LXI_STATUS LXI_TOPIC "/Status"
#define LXI_ERROR_CODE LXI_TOPIC "/Error"
#define LXI_ERROR_MESSAGE LXI_TOPIC "/Error_Message"
#define FREEZE_PROTECT "Freeze_Protect"
#define FREEZE_PROTECT_ENABELED FREEZE_PROTECT ENABELED_SUBT
#define CHILLER "Chiller"
#define CHILLER_ENABELED CHILLER ENABELED_SUBT
#define BATTERY_STATE "Battery"
//#define POOL_THERMO_TEMP_TOPIC BTN_POOL_HTR "/Temperature"
//#define SPA_THERMO_TEMP_TOPIC BTN_SPA_HTR "/Temperature"
//#define PUMP_TOPIC "Pump_"
#define PUMP_RPM_TOPIC "/RPM"
#define PUMP_GPM_TOPIC "/GPM"
#define PUMP_WATTS_TOPIC "/Watts"
#define PUMP_MODE_TOPIC "/Mode"
#define PUMP_STATUS_TOPIC "/Status"
#define PUMP_PPC_TOPIC "/PPC"
#define PUMP_SPEED_TOPIC "/Speed"
#define LIGHT_PROGRAM_TOPIC "/program"
#define LIGHT_DIMMER_VALUE_TOPIC "/brightness"
#define SENSOR_TOPIC "Sensor"
/*
#define AIR_TEMPERATURE "Air"
#define POOL_TEMPERATURE "Pool_Water"
#define SPA_TEMPERATURE "Spa_Water"
*/
/*
#define AIR_TEMPERATURE_TOPIC AIR_TEMPERATURE "/Temperature"
#define POOL_TEMPERATURE_TOPIC POOL_TEMPERATURE "/Temperature"
#define SPA_TEMPERATURE_TOPIC SPA_TEMPERATURE "/Temperature"
*/
#define SWG_ON 2
#define SWG_OFF 0
#define MQTT_FLASH "2"
#define MQTT_ON "1"
#define MQTT_OFF "0"
#define MQTT_COOL MQTT_FLASH
#define MQTT_LWM_TOPIC "Alive"
#endif // AQ_MQTT_H_

1680
source/aq_panel.c Normal file

File diff suppressed because it is too large Load Diff

143
source/aq_panel.h Normal file
View File

@ -0,0 +1,143 @@
#ifndef AQ_PANEL_H_
#define AQ_PANEL_H_
#include "config.h"
#include "aqualink.h"
#define PUMP_INDEX 0
#define SPA_INDEX 1
/*
#define POOL_HEAT_INDEX 9
#define SPA_HEAT_INDEX 10
*/
//#define VBUTTON_ONETOUCH_RSSD 0xFF
//#define VBUTTON_RSSD 0xFE
// Defined as int16_t so 16 bits to mask
#define RSP_4 (1 << 0) // 1
#define RSP_6 (1 << 1) // 16
#define RSP_8 (1 << 2) // 4
#define RSP_10 (1 << 3) // 2
#define RSP_12 (1 << 4) // 32
#define RSP_14 (1 << 5) // 8
#define RSP_16 (1 << 6) // 64
#define RSP_COMBO (1 << 7) // 128
#define RSP_SINGLE (1 << 8) // 128
#define RSP_DUAL_EQPT (1 << 9) // 128
#define RSP_RS (1 << 10) // 128
#define RSP_PDA (1 << 11) // 128
#define RSP_ONET (1 << 12) // 128
#define RSP_IAQT (1 << 13) // 128
#define RSP_RSSA (1 << 14) // 128
#define RSP_EXT_PROG (1 << 15) // 128
// ....Remeber no more for int16_t.......
// Bitmask for pannel support against board rev
// used in getPanelSupport()
#define RSP_SUP_ONET (1 << 0) // OneTouch
#define RSP_SUP_AQLT (1 << 1) // Aqualink Touch
#define RSP_SUP_ONET_EARLY (1 << 14) // OneTouch REV O uses different tpage for VSP
#define RSP_SUP_IAQL (1 << 2) // iAqualink Wifi (1.0/2.0)
#define RSP_SUP_IAQL3 (1 << 15) // iAqualink WiFi (3.0)
#define RSP_SUP_RSSA (1 << 3) // RS Serial Adapter
#define RSP_SUP_VSP (1 << 4) // Variable Speed Pumps
#define RSP_SUP_CHEM (1 << 5) // chem feeder
#define RSP_SUP_TSCHEM (1 << 6) // true sense chem reader
#define RSP_SUP_SWG (1 << 7) // Salt water generator
#define RSP_SUP_CLIT (1 << 8) // color lights
#define RSP_SUP_DLIT (1 << 9) // dimmer lights
#define RSP_SUP_VBTN (1 << 10) // Virtual button
#define RSP_SUP_PLAB (1 << 11) // Pump VSP by Label and not number
#define RSP_SUP_HPCHIL (1 << 12) // Heat Pump chiller
#define RSP_SUP_PCDOC (1 << 13) // PC Dock
#define PANEL_CPU (1 << 0)
#define PANEL_REV (1 << 1)
#define PANEL_STRING (1 << 2)
uint8_t setPanelInformationFromPanelMsg(struct aqualinkdata *aqdata, const char *input, uint8_t type, emulation_type source);
//bool setPanelStringFromPanelMsg(struct aqualinkdata *aqdata, const char *src, int src_len, logmask_t from);
//bool setBoardCPURevisionFromPanelMsg (struct aqualinkdata *aqdata, const char *src, int src_len, logmask_t from);
//void initButtons(struct aqualinkdata *aqdata);
void setPanelByName(struct aqualinkdata *aqdata, const char *str);
void setPanel(struct aqualinkdata *aqdata, bool rs, int size, bool combo, bool dual);
const char* getPanelString();
const char* getShortPanelString();
bool panel_device_request(struct aqualinkdata *aqdata, action_type type, int deviceIndex, int value, request_source source);
void updateLightProgram(struct aqualinkdata *aqdata, int value, clight_detail *light);
void updateButtonLightProgram(struct aqualinkdata *aqdata, int value, int button);
int getWaterTemp(struct aqualinkdata *aqdata);
void changePanelToMode_Only();
void addPanelOneTouchInterface();
void addPanelIAQTouchInterface();
void addPanelRSserialAdapterInterface();
void changePanelToExtendedIDProgramming();
int getPumpDefaultSpeed(pump_detail *pump, bool max);
int getPumpSpeedAsPercent(pump_detail *pump);
int convertPumpPercentToSpeed(pump_detail *pump, int value); // This is probable only needed internally
uint16_t getPanelSupport( char *rev_string, int rev_len);
aqkey *addVirtualButton(struct aqualinkdata *aqdata, char *label, int vindex);
bool setVirtualButtonLabel(aqkey *button, const char *label);
bool setVirtualButtonAltLabel(aqkey *button, const char *label);
clight_detail *getProgramableLight(struct aqualinkdata *aqdata, int button);
pump_detail *getPumpDetail(struct aqualinkdata *aqdata, int button);
//void panneltest();
#define isPDA_PANEL ((_aqconfig_.paneltype_mask & RSP_PDA) == RSP_PDA)
#define isRS_PANEL ((_aqconfig_.paneltype_mask & RSP_RS) == RSP_RS)
#define isCOMBO_PANEL ((_aqconfig_.paneltype_mask & RSP_COMBO) == RSP_COMBO)
#define isSINGLE_DEV_PANEL ((_aqconfig_.paneltype_mask & RSP_SINGLE) == RSP_SINGLE)
#define isDUAL_EQPT_PANEL ((_aqconfig_.paneltype_mask & RSP_DUAL_EQPT) == RSP_DUAL_EQPT)
#define isONET_ENABLED ((_aqconfig_.paneltype_mask & RSP_ONET) == RSP_ONET)
#define isIAQT_ENABLED ((_aqconfig_.paneltype_mask & RSP_IAQT) == RSP_IAQT)
#define isRSSA_ENABLED ((_aqconfig_.paneltype_mask & RSP_RSSA) == RSP_RSSA)
#define isEXTP_ENABLED ((_aqconfig_.paneltype_mask & RSP_EXT_PROG) == RSP_EXT_PROG)
#define isIAQL_ACTIVE ((_aqconfig_.extended_device_id2 != NUL))
#define isVS_PUMP(mask) ((mask & VS_PUMP) == VS_PUMP)
#define isPLIGHT(mask) ((mask & PROGRAM_LIGHT) == PROGRAM_LIGHT)
#define isVBUTTON(mask) ((mask & VIRTUAL_BUTTON) == VIRTUAL_BUTTON)
#define isVBUTTON_ALTLABEL(mask) ((mask & VIRTUAL_BUTTON_ALT_LABEL) == VIRTUAL_BUTTON_ALT_LABEL)
#define isVBUTTON_CHILLER(mask) ((mask & VIRTUAL_BUTTON_CHILLER) == VIRTUAL_BUTTON_CHILLER)
int PANEL_SIZE();
//
//#define PANEL_SIZE PANEL_SIZE()
/*
#define PANEL_SIZE ((_aqconfig_.paneltype_mask & RSP_4) == RSP_4)?4:(\
((_aqconfig_.paneltype_mask & RSP_6) == RSP_6)?6:(\
((_aqconfig_.paneltype_mask & RSP_8) == RSP_8)?8:(\
((_aqconfig_.paneltype_mask & RSP_10) == RSP_10)?10:(\
((_aqconfig_.paneltype_mask & RSP_12) == RSP_12)?12:(\
((_aqconfig_.paneltype_mask & RSP_14) == RSP_14)?14:(\
((_aqconfig_.paneltype_mask & RSP_16) == RSP_16)?16:0))))))
*/
// If we need to increase virtual buttons, then increase below.
#define VIRTUAL_BUTTONS 8 // This is the only parameter to change if we need more virtual buttons.
#define TOTAL_BUTTONS 20+VIRTUAL_BUTTONS // Biggest jandy panel is 20 buttons (RS16)
#define TOTAL_LEDS TOTAL_BUTTONS+4 // Only 20 exist in control panel, but need space for the 4 extra buttons on RS16 panel, + every virtual button
// This needs to be called AFTER and as well as initButtons
void initButtons_RS16(struct aqualinkdata *aqdata);
#endif

1098
source/aq_programmer.c Normal file

File diff suppressed because it is too large Load Diff

227
source/aq_programmer.h Normal file
View File

@ -0,0 +1,227 @@
#ifndef AQ_PROGRAMMER_H_
#define AQ_PROGRAMMER_H_
#include <pthread.h>
//#include "aqualink.h"
#define PROGRAMMING_POLL_DELAY_TIME 10
//#define PROGRAMMING_POLL_DELAY_TIME 2
//#define PROGRAMMING_POLL_DELAY_TIME 5
#define PROGRAMMING_POLL_COUNTER 200
// need to get the C values from aqualink manual and add those just incase
// someone has the controller set to C.
#define HEATER_MAX_F 104
#define HEATER_MIN_F 36
#define FREEZE_PT_MAX_F 42
#define FREEZE_PT_MIN_F 34
#define CHILLER_MAX_F 104
#define CHILLER_MIN_F 34
#define HEATER_MAX_C 40
#define HEATER_MIN_C 0
#define FREEZE_PT_MAX_C 5
#define FREEZE_PT_MIN_C 1
#define CHILLER_MAX_C 40
#define CHILLER_MIN_C 0
#define SWG_PERCENT_MAX 101
#define SWG_PERCENT_MIN 0
#define PTHREAD_ARG 25
#define LIGHT_MODE_BUFER PTHREAD_ARG
typedef enum emulation_type{
SIM_NONE = -1,
ALLBUTTON,
RSSADAPTER,
ONETOUCH,
IAQTOUCH,
AQUAPDA, // AQUAPALM and PDA are taken as specific type.
IAQUALNK, // iAqualink (wifi extra ID)
JANDY_DEVICE, // Very rarley used.
SIMULATOR
} emulation_type;
typedef enum {
AQP_NULL = -1,
// ********* Generic Programming options, these are Allbutton by Default
AQ_GET_POOL_SPA_HEATER_TEMPS,
AQ_GET_FREEZE_PROTECT_TEMP,
AQ_SET_TIME,
AQ_SET_POOL_HEATER_TEMP,
AQ_SET_SPA_HEATER_TEMP,
AQ_SET_FRZ_PROTECTION_TEMP,
AQ_GET_DIAGNOSTICS_MODEL,
AQ_GET_PROGRAMS,
AQ_SET_LIGHTPROGRAM_MODE,
AQ_SET_LIGHTCOLOR_MODE,
AQ_SET_LIGHTDIMMER,
AQ_SET_SWG_PERCENT,
AQ_GET_AUX_LABELS,
AQ_SET_BOOST,
AQ_SET_PUMP_RPM,
AQ_SET_PUMP_VS_PROGRAM,
AQ_SET_CHILLER_TEMP,
// ******** PDA Delimiter make sure to change MAX/MIN below
AQ_PDA_INIT,
AQ_PDA_WAKE_INIT,
AQ_PDA_DEVICE_STATUS,
AQ_PDA_DEVICE_ON_OFF,
AQ_PDA_AUX_LABELS,
AQ_PDA_SET_BOOST,
AQ_PDA_SET_SWG_PERCENT,
AQ_PDA_GET_AUX_LABELS,
AQ_PDA_SET_POOL_HEATER_TEMPS,
AQ_PDA_SET_SPA_HEATER_TEMPS,
AQ_PDA_SET_FREEZE_PROTECT_TEMP,
AQ_PDA_SET_TIME,
AQ_PDA_GET_POOL_SPA_HEATER_TEMPS,
AQ_PDA_GET_FREEZE_PROTECT_TEMP,
AQ_PDA_SET_LIGHT_MODE,
// ******** OneTouch Delimiter make sure to change MAX/MIN below
AQ_SET_ONETOUCH_PUMP_RPM,
AQ_SET_ONETOUCH_MACRO,
AQ_GET_ONETOUCH_SETPOINTS,
AQ_SET_ONETOUCH_POOL_HEATER_TEMP,
AQ_SET_ONETOUCH_SPA_HEATER_TEMP,
AQ_GET_ONETOUCH_FREEZEPROTECT,
AQ_SET_ONETOUCH_FREEZEPROTECT,
AQ_SET_ONETOUCH_TIME,
AQ_SET_ONETOUCH_BOOST,
AQ_SET_ONETOUCH_SWG_PERCENT,
// ******** iAqalink Touch Delimiter make sure to change MAX/MIN below
AQ_SET_IAQTOUCH_PUMP_RPM,
AQ_SET_IAQTOUCH_PUMP_VS_PROGRAM,
AQ_GET_IAQTOUCH_VSP_ASSIGNMENT,
AQ_GET_IAQTOUCH_SETPOINTS,
AQ_GET_IAQTOUCH_FREEZEPROTECT,
AQ_SET_IAQTOUCH_FREEZEPROTECT,
AQ_GET_IAQTOUCH_AUX_LABELS,
AQ_SET_IAQTOUCH_SWG_PERCENT,
AQ_SET_IAQTOUCH_SWG_BOOST,
AQ_SET_IAQTOUCH_SET_TIME,
AQ_SET_IAQTOUCH_DEVICE_ON_OFF,
AQ_SET_IAQTOUCH_ONETOUCH_ON_OFF,
AQ_SET_IAQTOUCH_POOL_HEATER_TEMP,
AQ_SET_IAQTOUCH_SPA_HEATER_TEMP,
AQ_SET_IAQTOUCH_CHILLER_TEMP,
AQ_SET_IAQLINK_POOL_HEATER_TEMP, // Same as above but using iAqualink not AqualinkTouch
AQ_SET_IAQLINK_SPA_HEATER_TEMP, // Same as above but using iAqualink not AqualinkTouch
AQ_SET_IAQLINK_CHILLER_TEMP,
AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE,
// ******** RS Serial Adapter Delimiter make sure to change MAX/MIN below
AQ_GET_RSSADAPTER_SETPOINTS,
AQ_SET_RSSADAPTER_POOL_HEATER_TEMP,
AQ_SET_RSSADAPTER_SPA_HEATER_TEMP,
AQ_ADD_RSSADAPTER_POOL_HEATER_TEMP,
AQ_ADD_RSSADAPTER_SPA_HEATER_TEMP,
// ******** Delimiter make sure to change MAX/MIN below
} program_type;
//#define AQ_SET_CHILLER_TEMP AQ_SET_IAQTOUCH_CHILLER_TEMP
#define AQP_GENERIC_MIN AQ_GET_POOL_SPA_HEATER_TEMPS
#define AQP_GENERIC_MAX AQ_SET_PUMP_VS_PROGRAM
#define AQP_ALLBUTTON_MIN AQ_GET_POOL_SPA_HEATER_TEMPS
#define AQP_ALLBUTTONL_MAX AQ_SET_BOOST
#define AQP_PDA_MIN AQ_PDA_INIT
#define AQP_PDA_MAX AQ_SET_ONETOUCH_SWG_PERCENT
#define AQP_ONETOUCH_MIN AQ_SET_ONETOUCH_PUMP_RPM
#define AQP_ONETOUCH_MAX AQ_SET_ONETOUCH_SWG_PERCENT
#define AQP_IAQTOUCH_MIN AQ_SET_IAQTOUCH_PUMP_RPM
#define AQP_IAQTOUCH_MAX AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE
#define AQP_RSSADAPTER_MIN AQ_GET_RSSADAPTER_SETPOINTS
#define AQP_RSSADAPTER_MAX AQ_ADD_RSSADAPTER_SPA_HEATER_TEMP
struct aqualinkdata;
typedef struct aqualinkkey aqkey;
/*
struct aqualinkkey;
typedef struct aqualinkkey aqkey;
*/
/*
struct aqualinkdata;
struct programmingThreadCtrl;
*/
struct programmerArgs {
aqkey *button;
int value;
int alt_value;
//char cval[PTHREAD_ARG];
};
struct programmingThreadCtrl {
pthread_t thread_id;
//void *thread_args;
struct programmerArgs pArgs;
char thread_args[PTHREAD_ARG];
struct aqualinkdata *aq_data;
};
typedef enum pump_type {
PT_UNKNOWN = -1,
EPUMP, // = ePump AC & Jandy ePUMP
VSPUMP, // = Intelliflo VS
VFPUMP // = Intelliflo VF (GPM)
} pump_type;
//void aq_programmer(program_type type, void *args, struct aqualinkdata *aq_data);
void aq_programmer(program_type type, char *args, struct aqualinkdata *aq_data);
// Below is NEW version of above.
void aq_program(program_type r_type, aqkey *button, int value, int value2, struct aqualinkdata *aq_data);
//void kick_aq_program_thread(struct aqualinkdata *aq_data);
void kick_aq_program_thread(struct aqualinkdata *aq_data, emulation_type source_type);
bool in_programming_mode(struct aqualinkdata *aq_data);
bool in_ot_programming_mode(struct aqualinkdata *aq_data);
bool in_iaqt_programming_mode(struct aqualinkdata *aq_data);
bool in_swg_programming_mode(struct aqualinkdata *aq_data);
bool in_light_programming_mode(struct aqualinkdata *aq_data);
bool in_allb_programming_mode(struct aqualinkdata *aq_data);
//void aq_send_cmd(unsigned char cmd);
void queueGetProgramData(emulation_type source_type, struct aqualinkdata *aq_data);
//void queueGetExtendedProgramData(emulation_type source_type, struct aqualinkdata *aq_data, bool labels);
//unsigned char pop_aq_cmd(struct aqualinkdata *aq_data);
void waitForSingleThreadOrTerminate(struct programmingThreadCtrl *threadCtrl, program_type type);
void cleanAndTerminateThread(struct programmingThreadCtrl *threadCtrl);
void force_queue_delete(); // NSF This needs to be deleted (come back and fix)
//bool push_aq_cmd(unsigned char cmd);
//void send_cmd(unsigned char cmd, struct aqualinkdata *aq_data);
//void cancel_menu(struct aqualinkdata *aq_data);
//void *set_aqualink_time( void *ptr );
//void *get_aqualink_pool_spa_heater_temps( void *ptr );
//int get_aq_cmd_length();
int setpoint_check(int type, int value, struct aqualinkdata *aqdata);
int RPM_check(pump_type type, int value, struct aqualinkdata *aqdata);
//int RPM_check(int type, int value, struct aqualinkdata *aqdata);
const char *ptypeName(program_type type);
const char *programtypeDisplayName(program_type type);
#endif

406
source/aq_scheduler.c Normal file
View File

@ -0,0 +1,406 @@
/*
* Copyright (c) 2017 Shaun Feakes - All rights reserved
*
* You may use redistribute and/or modify this code under the terms of
* the GNU General Public License version 2 as published by the
* Free Software Foundation. For the terms of this license,
* see <http://www.gnu.org/licenses/>.
*
* You are free to use this software under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* https://github.com/sfeakes/aqualinkd
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/mount.h>
#include <sys/statvfs.h>
#include <regex.h>
#include "mongoose.h"
#include "aqualink.h"
#include "aq_scheduler.h"
#include "config.h"
#include "aq_panel.h"
//#include "utils.h"
#include "aq_systemutils.h"
/*
Example /etc/cron.d/aqualinkd
01 10 1 * * curl localhost:80/api/Filter_Pump/set -d value=2 -X PUT
*/
bool passJson_scObj(const char* line, int length, aqs_cron *values)
{
int keystart=0;
//int keyend=0;
int valuestart=0;
int captured=0;
bool readingvalue=false;
bool invalue=false;
//char value;
values->enabled = true;
//LOG(SCHD_LOG,LOG_DEBUG, "Obj body:'%.*s'\n", length, line);
for (int i=0; i < length; i++) {
if (line[i] == '}') {
return (captured >= 7)?true:false;
} else if (line[i] == '"' && keystart==0 && invalue==false && readingvalue==false) {
keystart=i+1;
} else if (line[i] == '"' && keystart > 0 && invalue==false && readingvalue==false) {
//keyend=i;
} else if (line[i] == ':' && keystart > 0 ) {
invalue=true;
} else if (line[i] == '"' && invalue == true && readingvalue == false && keystart > 0 ) {
readingvalue=true;
valuestart=i+1;
} else if (line[i] == '"' && readingvalue == true) {
// i is end of key
if ( strncmp(&line[keystart], "enabled", 7) == 0) {
values->enabled = (line[valuestart]=='0'?false:true);
captured++;
} else if ( strncmp(&line[keystart], "min", 3) == 0) {
strncpy(values->minute, &line[valuestart], (i-valuestart) );
values->minute[i-valuestart] = '\0';
captured++;
} else if( strncmp(&line[keystart], "hour", 4) == 0) {
strncpy(values->hour, &line[valuestart], (i-valuestart) );
values->hour[i-valuestart] = '\0';
captured++;
} else if( strncmp(&line[keystart], "daym", 4) == 0) {
strncpy(values->daym, &line[valuestart], (i-valuestart) );
values->daym[i-valuestart] = '\0';
captured++;
} else if( strncmp(&line[keystart], "month", 5) == 0) {
strncpy(values->month, &line[valuestart], (i-valuestart) );
values->month[i-valuestart] = '\0';
captured++;
} else if( strncmp(&line[keystart], "dayw", 4) == 0) {
strncpy(values->dayw, &line[valuestart], (i-valuestart) );
values->dayw[i-valuestart] = '\0';
captured++;
} else if( strncmp(&line[keystart], "url", 3) == 0) {
strncpy(values->url, &line[valuestart], (i-valuestart) );
values->url[i-valuestart] = '\0';
captured++;
} else if( strncmp(&line[keystart], "value", 5) == 0) {
strncpy(values->value, &line[valuestart], (i-valuestart) );
values->value[i-valuestart] = '\0';
captured++;
}
keystart=0;
//keyend=0;
valuestart=0;
invalue=false;
readingvalue=false;
}
}
return (captured >= 7)?true:false;
}
int save_schedules_js(const char* inBuf, int inSize, char* outBuf, int outSize)
{
FILE *fp;
int i;
bool inarray = false;
aqs_cron cline;
bool fileexists = false;
bool fs = false;
if ( !_aqconfig_.enable_scheduler) {
LOG(SCHD_LOG,LOG_WARNING, "Schedules are disabled\n");
return sprintf(outBuf, "{\"message\":\"Error Schedules disabled\"}");
}
LOG(SCHD_LOG,LOG_NOTICE, "Saving Schedule:\n");
/*
bool fs = remount_root_ro(false);
if (access(CRON_FILE, F_OK) == 0)
fileexists = true;
fp = fopen(CRON_FILE, "w");
*/
fp = aq_open_file( CRON_FILE, &fs, &fileexists);
if (fp == NULL) {
LOG(SCHD_LOG,LOG_ERR, "Open file failed '%s'\n", CRON_FILE);
//remount_root_ro(true);
aq_close_file(fp, fs);
return sprintf(outBuf, "{\"message\":\"Error Saving Schedules\"}");
}
fprintf(fp, "#***** AUTO GENERATED DO NOT EDIT *****\n");
fprintf(fp, "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n");
LOG(SCHD_LOG,LOG_DEBUG, "Schedules Message body:\n'%.*s'\n", inSize, inBuf);
for (i=0; i < inSize; i++) {
if ( inBuf[i] == '[' ) {
inarray=true;
} else if ( inBuf[i] == ']' ) {
inarray=false;
} else if ( inarray && inBuf[i] == '{') {
passJson_scObj( &inBuf[i], (inSize-i), &cline);
LOG(SCHD_LOG,LOG_DEBUG, "Write to cron Min:%s Hour:%s DayM:%s Month:%s DayW:%s URL:%s Value:%s\n",cline.minute,cline.hour,cline.daym,cline.month,cline.dayw,cline.url,cline.value);
LOG(SCHD_LOG,LOG_INFO, "%s%s %s %s %s %s curl -s -S --show-error -o /dev/null localhost:%s%s -d value=%s -X PUT\n",(cline.enabled?"":"#"),cline.minute, cline.hour, cline.daym, cline.month, cline.dayw, _aqconfig_.socket_port, cline.url, cline.value);
fprintf(fp, "%s%s %s %s %s %s root curl -s -S --show-error -o /dev/null localhost:%s%s -d value=%s -X PUT\n",(cline.enabled?"":"#"),cline.minute, cline.hour, cline.daym, cline.month, cline.dayw, _aqconfig_.socket_port, cline.url, cline.value);
}
}
fprintf(fp, "#***** AUTO GENERATED DO NOT EDIT *****\n");
//fclose(fp);
// if we created file, change the permissions
if (!fileexists)
if ( chmod(CRON_FILE, S_IRUSR | S_IWUSR ) < 0 )
LOG(SCHD_LOG,LOG_ERR, "Could not change permissions on cron file %s, scheduling may not work\n",CRON_FILE);
//remount_root_ro(fs);
aq_close_file(fp, fs);
return sprintf(outBuf, "{\"message\":\"Saved Schedules\"}");
}
int build_schedules_js(char* buffer, int size)
{
memset(&buffer[0], 0, size);
FILE *fp;
char *line = NULL;
int length = 0;
int rc;
aqs_cron cline;
size_t len = 0;
ssize_t read_size;
regex_t regexCompiled;
if ( !_aqconfig_.enable_scheduler) {
LOG(SCHD_LOG,LOG_WARNING, "Schedules are disabled\n");
if (size > 0)
length += sprintf(buffer, "{\"message\":\"Error Schedules disabled\"}");
return length;
}
// Below works for curl but not /usr/bin/curl in command. NSF come back and fix the regexp
//char *regexString="([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s.*(/api/.*)\\s-d value=([^\\d]+)\\s(.*)";
// \d doesn't seem to be supported, so using [0-9]+ instead
//char *regexString="([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s.*(\\/api\\/.*\\/set).* value=([0-9]+).*";
//char *regexString="([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s.*(\\/api\\/.*\\/set).* value=([0-9]+).*";
const char *regexString="(#{0,1})([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s.*(\\/api\\/.*\\/set).* value=([0-9]+).*";
//char *regexString="([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s([^\\s]+)\\s.*(/api/.*/set).*value=([0-9]+).*";
size_t maxGroups = 15;
regmatch_t groupArray[maxGroups];
//static char buf[100];
if (size > 0)
length += sprintf(buffer+length,"{\"type\": \"schedules\",");
if (0 != (rc = regcomp(&regexCompiled, regexString, REG_EXTENDED))) {
LOG(SCHD_LOG,LOG_ERR, "regcomp() failed, returning nonzero (%d)\n", rc);
if (size > 0)
length += sprintf(buffer+length,"\"message\": \"Error reading schedules\"}");
return length;
}
fp = fopen(CRON_FILE, "r");
if (fp == NULL) {
LOG(SCHD_LOG,LOG_WARNING, "Open file failed '%s'\n", CRON_FILE);
if (size > 0)
length += sprintf(buffer+length,"\"message\": \"Error reading schedules\"}");
return length;
}
if (size > 0)
length += sprintf(buffer+length,"\"schedules\": [ ");
while ((read_size = getline(&line, &len, fp)) != -1) {
//printf("Read from cron:-\n %s", line);
//lc++;
//rc = regexec(&regexCompiled, line, maxGroups, groupArray, 0);
if (0 == (rc = regexec(&regexCompiled, line, maxGroups, groupArray, REG_EXTENDED))) {
// Group 1 is # (enable or not)
// Group 2 is minute
// Group 3 is hour
// Group 4 is day of month
// Group 5 is month
// Group 6 is day of week
// Group 7 is root
// Group 8 is curl
// Group 9 is URL
// Group 10 is value
if (groupArray[8].rm_so == (size_t)-1) {
if (size > 0) {
LOG(SCHD_LOG,LOG_ERR, "No matching information from cron file\n");
}
} else {
cline.enabled = (line[groupArray[1].rm_so] == '#')?false:true;
sprintf(cline.minute, "%.*s", (groupArray[2].rm_eo - groupArray[2].rm_so), (line + groupArray[2].rm_so));
sprintf(cline.hour, "%.*s", (groupArray[3].rm_eo - groupArray[3].rm_so), (line + groupArray[3].rm_so));
sprintf(cline.daym, "%.*s", (groupArray[4].rm_eo - groupArray[4].rm_so), (line + groupArray[4].rm_so));
sprintf(cline.month, "%.*s", (groupArray[5].rm_eo - groupArray[5].rm_so), (line + groupArray[5].rm_so));
sprintf(cline.dayw, "%.*s", (groupArray[6].rm_eo - groupArray[6].rm_so), (line + groupArray[6].rm_so));
sprintf(cline.url, "%.*s", (groupArray[9].rm_eo - groupArray[9].rm_so), (line + groupArray[9].rm_so));
sprintf(cline.value, "%.*s", (groupArray[10].rm_eo - groupArray[10].rm_so), (line + groupArray[10].rm_so));
if (size > 0) {
LOG(SCHD_LOG,LOG_INFO, "Read from cron. Enabled:%d Min:%s Hour:%s DayM:%s Month:%s DayW:%s URL:%s Value:%s\n",cline.enabled,cline.minute,cline.hour,cline.daym,cline.month,cline.dayw,cline.url,cline.value);
length += sprintf(buffer+length, "{\"enabled\":\"%d\", \"min\":\"%s\",\"hour\":\"%s\",\"daym\":\"%s\",\"month\":\"%s\",\"dayw\":\"%s\",\"url\":\"%s\",\"value\":\"%s\"},",
cline.enabled,
cline.minute,
cline.hour,
cline.daym,
cline.month,
cline.dayw,
cline.url,
cline.value);
}
//LOG(SCHD_LOG,LOG_DEBUG, "Read from cron Day %d | Time %d:%d | Zone %d | Runtime %d\n",day,hour,minute,zone,runtime);
// Test / get for pump start and end time
if (isAQS_USE_CRON_PUMP_TIME_ENABLED) {
// Could also check that dayw is *
if ( cline.enabled && strstr(cline.url, AQS_PUMP_URL ))
{
int value = strtoul(cline.value, NULL, 10);
int hour = strtoul(cline.hour, NULL, 10);
if (value == 0) {
if (hour > _aqconfig_.sched_chk_pumpoff_hour) // NSF this picks up the greatest offhour, (do we want the smallest???)
_aqconfig_.sched_chk_pumpoff_hour = hour;
} else if (value == 1){
if (hour < _aqconfig_.sched_chk_pumpon_hour || _aqconfig_.sched_chk_pumpon_hour == 0)
_aqconfig_.sched_chk_pumpon_hour = hour;
}
}
}
}
} else {
if (size > 0) {
LOG(SCHD_LOG,LOG_DEBUG, "regexp no match (%d) %s", rc, line);
}
}
}
if (size > 0) {
buffer[--length] = '\0';
length += sprintf(buffer+length,"]}\n");
}
fclose(fp);
regfree(&regexCompiled);
return length;
}
void get_cron_pump_times()
{
build_schedules_js(NULL, 0);
return;
}
bool event_happened_set_device_state(reset_event_type type, struct aqualinkdata *aq_data)
{
if (! isAQS_START_PUMP_EVENT_ENABLED) {
LOG(SCHD_LOG,LOG_DEBUG, "Event scheduler is not enabled\n");
return false;
}
// Check time is between hours.
bool scheduledOn = false;
if (isAQS_USE_CRON_PUMP_TIME_ENABLED) {
get_cron_pump_times();
LOG(SCHD_LOG,LOG_DEBUG, "Pump on times from scheduler are between hours %.2d & %.2d\n",_aqconfig_.sched_chk_pumpon_hour, _aqconfig_.sched_chk_pumpoff_hour);
}
/*
if (_aqconfig_.sched_chk_pumpon_hour == AQ_UNKNOWN || _aqconfig_.sched_chk_pumpoff_hour == AQ_UNKNOWN ) {
if ( CRON TURNED OFF ) {
LOG(SCHD_LOG,LOG_ERR, "No pump on / off times configures and cron scheduler not enabled, can't action event!");
return false;
}
get_cron_pump_times();
LOG(SCHD_LOG,LOG_DEBUG, "Pump on times from scheduler are between hours %.2d & %.2d\n",_aqconfig_.sched_chk_pumpon_hour, _aqconfig_.sched_chk_pumpoff_hour);
}
*/
time_t now = time(NULL);
struct tm *tm_struct = localtime(&now);
int hour = tm_struct->tm_hour;
if (hour >= _aqconfig_.sched_chk_pumpon_hour && hour < _aqconfig_.sched_chk_pumpoff_hour ) {
scheduledOn = true;
}
// Check event type.
switch(type){
case AQS_POWER_ON:
if (scheduledOn && isAQS_POWER_ON_ENABED && aq_data->aqbuttons[0].led->state == OFF) {
LOG(SCHD_LOG,LOG_INFO, "Powered on, schedule is set for pump running and pump is off, turning pump on\n");
panel_device_request(aq_data, ON_OFF, 0, true, NET_TIMER);
} else {
//LOG(SCHD_LOG,LOG_DEBUG, "Powered on, schedule is not set and/or pump is already on, leaving\n");
LOG(SCHD_LOG,LOG_DEBUG, "Powered on, schedule Pump on is %sset, time is %sbetween scheduled hours, Pump is %s, (not changing)\n",(isAQS_POWER_ON_ENABED?"":"not "),(scheduledOn?"":" not"), (aq_data->aqbuttons[0].led->state ==OFF?"Off":"On"));
}
break;
case AQS_FRZ_PROTECT_OFF:
if (scheduledOn && isAQS_FRZ_PROTECT_OFF_ENABED && aq_data->aqbuttons[0].led->state == OFF) {
LOG(SCHD_LOG,LOG_INFO, "Freeze Protect off, schedule is set for pump running and pump is off, turning pump on\n");
panel_device_request(aq_data, ON_OFF, 0, true, NET_TIMER);
} else {
//LOG(SCHD_LOG,LOG_DEBUG, "Freeze Protect off, schedule is not set and/or pump is already on, leaving\n");
LOG(SCHD_LOG,LOG_DEBUG, "Freeze Protect off, schedule Pump on is %sset, time is %sbetween scheduled hours, Pump is %s, (not changing)\n",(isAQS_FRZ_PROTECT_OFF_ENABED?"":"not "),(scheduledOn?"":" not"), (aq_data->aqbuttons[0].led->state ==OFF?"Off":"On"));
}
break;
case AQS_BOOST_OFF:
if (scheduledOn && isAQS_BOOST_OFF_ENABED && aq_data->aqbuttons[0].led->state == OFF) {
LOG(SCHD_LOG,LOG_INFO, "Boost off, schedule is set for pump running and pump is off, turning pump on\n");
panel_device_request(aq_data, ON_OFF, 0, true, NET_TIMER);
} else {
//LOG(SCHD_LOG,LOG_DEBUG, "Boost off, schedule is not set and/or pump is already on, leaving\n");
LOG(SCHD_LOG,LOG_DEBUG, "Boost off, schedule Pump on is %sset, time is %sbetween scheduled hours, Pump is %s, (not changing)\n",(isAQS_BOOST_OFF_ENABED?"":"not "),(scheduledOn?"":" not"), (aq_data->aqbuttons[0].led->state ==OFF?"Off":"On"));
}
if (aq_data->boost_linked_device != AQ_UNKNOWN && aq_data->boost_linked_device <= aq_data->total_buttons && aq_data->boost_linked_device >= 0) {
//aq_data->aqbuttons[aq_data->boost_linked_device].code
if (aq_data->aqbuttons[aq_data->boost_linked_device].led->state == OFF) {
panel_device_request(aq_data, ON_OFF, aq_data->boost_linked_device, false, NET_TIMER);
LOG(SCHD_LOG,LOG_INFO, "Boost off, Turing %s off\n",aq_data->aqbuttons[aq_data->boost_linked_device].label);
} else {
LOG(SCHD_LOG,LOG_INFO, "Boost off, %s is already off\n",aq_data->aqbuttons[aq_data->boost_linked_device].label);
}
}
break;
case AQS_BOOST_ON:
if (aq_data->boost_linked_device != AQ_UNKNOWN && aq_data->boost_linked_device <= aq_data->total_buttons && aq_data->boost_linked_device >= 0) {
//aq_data->aqbuttons[aq_data->boost_linked_device].code
if (aq_data->aqbuttons[aq_data->boost_linked_device].led->state == OFF) {
panel_device_request(aq_data, ON_OFF, aq_data->boost_linked_device, true, NET_TIMER);
LOG(SCHD_LOG,LOG_INFO, "Boost on, Turing %s on\n",aq_data->aqbuttons[aq_data->boost_linked_device].label);
} else {
LOG(SCHD_LOG,LOG_INFO, "Boost on, %s is already on\n",aq_data->aqbuttons[aq_data->boost_linked_device].label);
}
}
break;
}
return true;
}

66
source/aq_scheduler.h Normal file
View File

@ -0,0 +1,66 @@
#ifndef AQ_SCHEDULER_H_
#define AQ_SCHEDULER_H_
#include "config.h"
#define CRON_FILE "/etc/cron.d/aqualinkd"
#define CURL "curl"
#define CV_SIZE 20
typedef struct aqs_cron
{
int enabled;
char minute[CV_SIZE];
char hour[CV_SIZE];
char daym[CV_SIZE];
char month[CV_SIZE];
char dayw[CV_SIZE];
char url[CV_SIZE * 2];
char value[CV_SIZE];
} aqs_cron;
int build_schedules_js(char* buffer, int size);
int save_schedules_js(const char* inBuf, int inSize, char* outBuf, int outSize);
void get_cron_pump_times();
#define AQS_PUMP_URL BTN_PUMP "/set"
// All below AQS_ are the same mask, but don;t want CRON in the emum
#define AQS_USE_CRON_PUMP_TIME (1 << 0)
typedef enum reset_event_type{
AQS_POWER_ON = (1 << 1),
AQS_FRZ_PROTECT_OFF = (1 << 2),
AQS_BOOST_OFF = (1 << 3),
AQS_BOOST_ON = (1 << 4)
} reset_event_type;
#define isAQS_START_PUMP_EVENT_ENABLED ( ((_aqconfig_.schedule_event_mask & AQS_POWER_ON) == AQS_POWER_ON) || \
((_aqconfig_.schedule_event_mask & AQS_FRZ_PROTECT_OFF) == AQS_FRZ_PROTECT_OFF) || \
((_aqconfig_.schedule_event_mask & AQS_BOOST_OFF) == AQS_BOOST_OFF) || \
((_aqconfig_.schedule_event_mask & AQS_BOOST_ON) == AQS_BOOST_ON))
//#define isAQS_USE_PUMP_TIME_FROM_CRON_ENABLED !((_aqconfig_.schedule_event_mask & AQS_DONT_USE_CRON_PUMP_TIME) == AQS_DONT_USE_CRON_PUMP_TIME)
#define isAQS_USE_CRON_PUMP_TIME_ENABLED ((_aqconfig_.schedule_event_mask & AQS_USE_CRON_PUMP_TIME) == AQS_USE_CRON_PUMP_TIME)
#define isAQS_POWER_ON_ENABED ((_aqconfig_.schedule_event_mask & AQS_POWER_ON) == AQS_POWER_ON)
#define isAQS_FRZ_PROTECT_OFF_ENABED ((_aqconfig_.schedule_event_mask & AQS_FRZ_PROTECT_OFF) == AQS_FRZ_PROTECT_OFF)
#define isAQS_BOOST_OFF_ENABED ((_aqconfig_.schedule_event_mask & AQS_BOOST_OFF) == AQS_BOOST_OFF)
/*
typedef enum reset_event_type{
POWER_ON,
FREEZE_PROTECT_OFF,
BOOST_OFF
} reset_event_type;
*/
bool event_happened_set_device_state(reset_event_type type, struct aqualinkdata *aq_data);
//void read_schedules();
//void write_schedules();
#endif // AQ_SCHEDULER_H_

1433
source/aq_serial.c Normal file

File diff suppressed because it is too large Load Diff

616
source/aq_serial.h Normal file
View File

@ -0,0 +1,616 @@
#ifndef AQ_SERIAL_H_
#define AQ_SERIAL_H_
#include <termios.h>
#include <stdbool.h>
#include "aq_programmer.h" // Need this for function getJandyDeviceType due to enum defined their.
emulation_type getJandyDeviceType(unsigned char ID);
const char *getJandyDeviceName(emulation_type etype);
#define CONNECTION_ERROR "ERROR No connection to RS control panel"
#ifdef AQ_MANAGER
#define CONNECTION_RUNNING_SLOG "Running serial_logger, this will take some time"
#endif
//#define SERIAL_BLOCKING_TIME 50 // (1 to 255) in 1/10th second so 1 = 0.1 sec, 255 = 25.5 sec
#define SERIAL_BLOCKING_TIME 10
// Protocol types
#define PCOL_JANDY 0xFF
#define PCOL_PENTAIR 0xFE
#define PCOL_UNKNOWN 0xFD
// packet offsets
#define PKT_DEST 2
#define PKT_CMD 3
#define PKT_DATA 4
#define PKT_STATUS_BYTES 5
#define DEV_MASTER 0x00
#define SWG_DEV_ID 0x50
#define IAQ_DEV_ID 0x33
/* Few Device ID's in decimal for quick checking
# Pentair pump ID's
# 0x60 to 0x6F (0x60, 0x61 0x62, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F)
# Jandy pump ID's
# 0x78, 0x79, 0x7A, 0x7B
*/
#define PENTAIR_DEC_PUMP_MIN 96 // 0x60
#define PENTAIR_DEC_PUMP_MAX 111 // 0x6F
#define JANDY_DEC_SWG_MIN 80 // 0x50
#define JANDY_DEC_SWG_MAX 83 // 0x53
#define JANDY_DEC_PUMP_MIN 120 // 0x78
#define JANDY_DEC_PUMP_MAX 123 // 0x7b
// Have also seen epump at 0xe0 with panel rev W that supports more pumps
#define JANDY_DEC_PUMP2_MIN 224 // 0xe0
#define JANDY_DEC_PUMP2_MAX 228 // 0xe3 // (should be 0xEF?????) Their are probably more, but this is a guess
#define JANDY_DEC_JXI_MIN 104 // 0x68
#define JANDY_DEC_JXI_MAX 107 // 0x6B
#define JANDY_DEC_LX_MIN 56 // 0x38
#define JANDY_DEC_LX_MAX 59 // 0x3B
#define JANDY_DEC_CHEM_MIN 128 // 0x80
#define JANDY_DEC_CHEM_MAX 131 // 0x83
#define JANDY_DEV_IAQLN_MIN 0xa0 //
#define JANDY_DEV_IAQLN_MAX 0xa3 // 0
#define JANDY_DEV_AQLNK_MIN 0x30 //
#define JANDY_DEV_AQLNK_MAX 0x33 // 0
#define JANDY_DEV_HPUMP_MIN 0x70
#define JANDY_DEV_HPUMP_MAX 0x73
#define JANDY_DEV_JLIGHT_MIN 0xF0
#define JANDY_DEV_JLIGHT_MAX 0xF4 // 0xF4 is total guess.
/*
//===== Device ID's =====//
//=========================================================================//
DEV_MASTER_MASK = 0x00; // MASTER(S???0 00-03 0b0 0000 0XX //
DEV_CTL_MASK = 0x08; // HOME CONTROLLER (RS-8?) 08-0b 0b0 0001 0XX //
// 0x10; // XXXXX DEVICE 10-13 0b0 0010 0XX //
// 0x18; // XXXXX DEVICE 18-1b 0b0 0011 0XX //
DEV_SPA_MASK = 0x20; // SPA DEVICE 20-23 0b0 0100 0XX //
DEV_RPC_MASK = 0x28; // REMOTE POWER CENTER DEVICE 28-2b 0b0 0101 0XX //
DEV_AQUALINK_MASK = 0x30; // AQUALINK DEVICE 30-33 0b0 0110 0XX //
DEV_LX_HTR_MASK = 0x38; // LX HEATER 38-3b 0b0 0111 0XX //
DEV_ONETOUCH_MASK = 0x40; // XXXXX ONE TOUCH DEVICE 40-43 0b0 1000 0XX //
// 0x48; // XXXXX DEVICE 48-4b 0b0 1001 0XX //
DEV_AQUARITE_MASK = 0x50; // AQUARITE DEVICE 50-53 0b0 1010 0XX //
DEV_PCDOCK_MASK = 0x58; // PCDOCK DEVICE 58-5b 0b0 1011 0XX //
DEV_PDA_JDA_MASK = 0x60; // AQUAPALM DEVICE 60-63 0b0 1100 0XX //
DEV_LXI_LRZE_MASK = 0x68; // LXi/LZRE DEVICE 68-6b 0b0 1101 0XX //
DEV_HEATPUMP_MASK = 0x70; // HEAT PUMP DEVICE 70-73 0b0 1110 0XX //
JANDY_EPUMP_MASK = 0x78; // EPUMP DEVICE 78-7b 0b0 1111 0XX //
DEV_CHEMLINK_MASK = 0x80; // CHEMLINK DEVICE 80-83 0b1 0000 0XX //
Heater 0x88; // XXXXX DEVICE 88-8b 0b1 0001 0XX //
// 0x90; // XXXXX DEVICE 90-93 0b1 0010 0XX //
// 0x98; // XXXXX DEVICE 98-9b 0b1 0011 0XX //
DEV_AQUALINK_2_MASK = 0xA0; // AQUALINK 2 A0-A3 0b1 0100 0XX //
DEV_UNKNOWN_MASK = 0xF8; // Unknown mask, used to reset values
*/
// PACKET DEFINES Jandy
#define NUL 0x00
#define DLE 0x10
#define STX 0x02
#define ETX 0x03
// Pentair packet headder (first 4 bytes)
#define PP1 0xFF
#define PP2 0x00
#define PP3 0xFF
#define PP4 0xA5
#define PEN_DEV_MASTER 0x10
#define PEN_CMD_SPEED 0x01
#define PEN_CMD_REMOTECTL 0x04
#define PEN_CMD_POWER 0x06
#define PEN_CMD_STATUS 0x07
#define PEN_PKT_FROM 6
#define PEN_PKT_DEST 5
#define PEN_PKT_CMD 7
// Pentair VSP
#define PEN_MODE 10
#define PEN_DRIVE_STATE 11
#define PEN_HI_B_WAT 12
#define PEN_LO_B_WAT 13
#define PEN_HI_B_RPM 14
#define PEN_LO_B_RPM 15
#define PEN_FLOW 16
#define PEN_PPC 17 // Pump pressure curve
#define PEN_HI_B_STATUS 20 // The current status value of the pump. following values: ok, filterWarning, overCurrent, priming, systemBlocked, generalAlarm, powerOutage, overCurrent2, overVoltage, commLost
#define PEN_LO_B_STATUS 21
// END Pentair
#define AQ_MINPKTLEN 5
//#define AQ_MAXPKTLEN 64
//#define AQ_MAXPKTLEN 128 // Max 79 bytes so far, so 128 is a guess at the moment, just seen large packets from iAqualink
//#define AQ_MAXPKTLEN 256 // Still getting this at 128, so temp increase to 256 and print message over 128 in aq_serial.c
#define AQ_MAXPKTLEN 512 // Still getting this at 128, so temp increase to 256 and print message over 128 in aq_serial.c
#define AQ_MAXPKTLEN_SEND 32 // Out biggest send buffer
#define AQ_PSTLEN 5
#define AQ_MSGLEN 16
#define AQ_MSGLONGLEN 128
#define AQ_TADLEN 13
// For printing warning & debug messages for packets.
// The below are related to AQ_MAXPKTLEN
#define AQ_MAXPKTLEN_WARNING 128 // Print warning message if over this
//#define AQ_PACKET_PRINT_BUFFER 1400 // Must be at least AQ_MAXPKTLEN * 5 + 100
/* COMMANDS */
#define CMD_PROBE 0x00
#define CMD_ACK 0x01
#define CMD_STATUS 0x02
#define CMD_MSG 0x03
#define CMD_MSG_LONG 0x04
#define CMD_MSG_LOOP_ST 0x08
/* ACK RETURN COMMANDS */
/*
#define ACK_NORMAL 0x00
#define ACK_SCREEN_BUSY_SCROLL 0x01 // Seems to be busy displaying last message, but can cache next message,
#define ACK_SCREEN_BUSY_BLOCK 0x03 // Seems to be don't send me any more shit.
*/
// Some keypads use 0x00 some 0x80 (think it's something to do with version, but need to figure it out)
// But if you use 0x80 for ack then you get a start loop cycle CMD_MSG_LOOP_ST
#define ACK_NORMAL 0x80
#define ACK_SCREEN_BUSY_SCROLL 0x81 // Seems to be busy displaying last message, but can cache next message,
#define ACK_SCREEN_BUSY_BLOCK 0x83 // Seems to be don't send me any more shit.
// Remove this and fix all compile errors when get time.
#define ACK_SCREEN_BUSY ACK_SCREEN_BUSY_SCROLL
#define ACK_IAQ_TOUCH 0x00
#define ACK_PDA 0x40
#define ACK_ONETOUCH 0x80
#define ACK_ALLB_SIM 0x80 // Jandy's Allbutton simulator uses this and not ACK_NORMAL
#define ACK_ALLB_SIM_BUSY 0x81 // Jandy's Allbutton simulator uses this and not ACK_SCREEN_BUSY_SCROLL
/* ONE TOUCH KEYCODES */
#define KEY_ONET_UP 0x06
#define KEY_ONET_DOWN 0x05
#define KEY_ONET_SELECT 0x04
#define KEY_ONET_PAGE_UP 0x03 // Top
#define KEY_ONET_BACK 0x02 // Middle
#define KEY_ONET_PAGE_DN 0x01 // Bottom
#define KEY_ONET_SELECT_1 KEY_ONET_PAGE_UP
#define KEY_ONET_SELECT_2 KEY_ONET_BACK
#define KEY_ONET_SELECT_3 KEY_ONET_PAGE_DN
/* AquaRite commands */
#define CMD_GETID 0x14 // May be remote control control
#define CMD_PERCENT 0x11 // Set Percent
#define CMD_PPM 0x16 // Received PPM
/* LXi Heater commands */
#define CMD_JXI_PING 0x0c
#define CMD_JXI_STATUS 0x0d
/* PDA KEY CODES */ // Just plating at the moment
#define KEY_PDA_UP 0x06
#define KEY_PDA_DOWN 0x05
#define KEY_PDA_BACK 0x02
#define KEY_PDA_SELECT 0x04
//#define KEY_PDA_PGUP 0x01 // Think these are hot key #1
//#define KEY_PDA_PGDN 0x03 // Think these are hot key #2
/* KEY/BUTTON CODES */
#define KEY_PUMP 0x02
#define KEY_SPA 0x01
#define KEY_AUX1 0x05
#define KEY_AUX2 0x0a
#define KEY_AUX3 0x0f
#define KEY_AUX4 0x06
#define KEY_AUX5 0x0b
#define KEY_AUX6 0x10
#define KEY_AUX7 0x15
#define KEY_POOL_HTR 0x12
#define KEY_SPA_HTR 0x17
//#define KEY_SOLAR_HTR 0x1c
#define KEY_EXT_AUX 0x1c
#define KEY_MENU 0x09
#define KEY_CANCEL 0x0e
#define KEY_LEFT 0x13
#define KEY_RIGHT 0x18
#define KEY_HOLD 0x19
#define KEY_OVERRIDE 0x1e
#define KEY_ENTER 0x1d
//RS 12 & 16 are different from Aux4 to Aux7
#define KEY_RS16_AUX4 0x14
#define KEY_RS16_AUX5 0x03
#define KEY_RS16_AUX6 0x07
#define KEY_RS16_AUX7 0x06
// RS 12 & 16 have extra buttons
#define KEY_AUXB1 0x0b
#define KEY_AUXB2 0x10
#define KEY_AUXB3 0x15
#define KEY_AUXB4 0x1a
#define KEY_AUXB5 0x04
#define KEY_AUXB6 0x08
#define KEY_AUXB7 0x0d
#define KEY_AUXB8 0x0c
// End diff in RS12
#define BTN_PUMP "Filter_Pump"
#define BTN_SPA "Spa"
#define BTN_AUX1 "Aux_1"
#define BTN_AUX2 "Aux_2"
#define BTN_AUX3 "Aux_3"
#define BTN_AUX4 "Aux_4"
#define BTN_AUX5 "Aux_5"
#define BTN_AUX6 "Aux_6"
#define BTN_AUX7 "Aux_7"
#define BTN_POOL_HTR "Pool_Heater"
#define BTN_SPA_HTR "Spa_Heater"
//#define BTN_SOLAR_HTR "Solar_Heater"
#define BTN_EXT_AUX "Extra_Aux"
#define BTN_TEMP1_HTR "Temp1_Heater"
#define BTN_TEMP2_HTR "Temp2_Heater"
#define BTN_VAUX "Aux_V" // A number will be appended
#define BTN_AUXB1 "Aux_B1"
#define BTN_AUXB2 "Aux_B2"
#define BTN_AUXB3 "Aux_B3"
#define BTN_AUXB4 "Aux_B4"
#define BTN_AUXB5 "Aux_B5"
#define BTN_AUXB6 "Aux_B6"
#define BTN_AUXB7 "Aux_B7"
#define BTN_AUXB8 "Aux_B8"
#define BTN_PDA_PUMP "FILTER PUMP"
#define BTN_PDA_SPA "SPA"
#define BTN_PDA_AUX1 "AUX1"
#define BTN_PDA_AUX2 "AUX2"
#define BTN_PDA_AUX3 "AUX3"
#define BTN_PDA_AUX4 "AUX4"
#define BTN_PDA_AUX5 "AUX5"
#define BTN_PDA_AUX6 "AUX6"
#define BTN_PDA_AUX7 "AUX7"
#define BTN_PDA_POOL_HTR "POOL HEAT"
#define BTN_PDA_SPA_HTR "SPA HEAT"
//#define BTN_PDA_SOLAR_HTR "EXTRA AUX"
#define BTN_PDA_EXT_AUX "EXTRA AUX"
#define BUTTON_LABEL_LENGTH 20
// Index starting at 1
#define POOL_HTR_LED_INDEX 15
#define SPA_HTR_LED_INDEX 17
#define SOLAR_HTR_LED_INDEX 19
#define LNG_MSG_SERVICE_ACTIVE "SERVICE MODE IS ACTIVE"
#define LNG_MSG_TIMEOUT_ACTIVE "TIMEOUT MODE IS ACTIVE"
#define LNG_MSG_POOL_TEMP_SET "POOL TEMP IS SET TO"
#define LNG_MSG_SPA_TEMP_SET "SPA TEMP IS SET TO"
#define LNG_MSG_FREEZE_PROTECTION_SET "FREEZE PROTECTION IS SET TO"
#define LNG_MSG_CLEANER_DELAY "CLEANER WILL TURN ON AFTER SAFETY DELAY"
#define LNG_MSG_BATTERY_LOW "BATTERY LOW"
//#define LNG_MSG_FREEZE_PROTECTION_ACTIVATED "FREEZE PROTECTION ACTIVATED"
#define LNG_MSG_FREEZE_PROTECTION_ACTIVATED "FREEZE PROTECTION IS ACTIVATED"
// These are
#define LNG_MSG_CHEM_FEED_ON "CHEM FEED ON"
#define LNG_MSG_CHEM_FEED_OFF "CHEM FEED OFF"
#define MSG_AIR_TEMP "AIR TEMP"
#define MSG_POOL_TEMP "POOL TEMP"
#define MSG_SPA_TEMP "SPA TEMP"
#define MSG_AIR_TEMP_LEN 8
#define MSG_POOL_TEMP_LEN 9
#define MSG_SPA_TEMP_LEN 8
// Will get water temp rather than pool in some cases. not sure if it's REV specific or device (ie no spa) specific yet
#define MSG_WATER_TEMP "WATER TEMP"
#define MSG_WATER_TEMP_LEN 10
#define LNG_MSG_WATER_TEMP1_SET "TEMP1 (HIGH TEMP) IS SET TO"
#define LNG_MSG_WATER_TEMP2_SET "TEMP2 (LOW TEMP) IS SET TO"
/*
// All Messages listed in the manual, This is obviously not complete, but it's everything Jandy has published
BATTERY IS LOW, BATTERY LOCATED AT THE POWER CENTER
CLEANER CANNOT BE TURNED ON WHILE SPA IS ON
CLEANER CANNOT BE TURNED ON WHILE SPILLOVER IS ON
FREEZE PROTECTION ACTIVATED
SENSOR OPENED
POOL HEATER ENABLED
PUMP WILL REMAIN ON WHILE SPILLOVER IS ON
PUMP WILL TURN OFF AFTER COOL DOWN CYCLE
PUMP WILL TURN ON AFTER DELAY
SERVICE MODE IS ACTIVE
SENSOR SHORTED
SPA WILL TURN OFF AFTER COOL DOWN CYCLE
TIMED AUX ON, WILL TURN OFF AFTER 30 MINUTES
TIMEOUT MODE IS ACTIVE
SPILLOVER IS DISABLED WHILE SPA IS ON
*/
#define MSG_SWG_PCT "AQUAPURE" // AquaPure 55%
#define MSG_SWG_PPM "SALT" // Salt 3000 PPM
#define MSG_SWG_PCT_LEN 8
#define MSG_SWG_PPM_LEN 4
#define MSG_SWG_NO_FLOW "Check AQUAPURE No Flow"
#define MSG_SWG_LOW_SALT "Check AQUAPURE Low Salt"
#define MSG_SWG_HIGH_SALT "Check AQUAPURE High Salt"
#define MSG_SWG_FAULT "Check AQUAPURE General Fault"
#define MSG_PMP_RPM "RPM:"
#define MSG_PMP_WAT "Watts:"
#define MSG_PMP_GPM "GPM:"
/* AQUAPURE SWG */
// These are madeup.
//#define SWG_STATUS_OFF 0xFF
//#define SWG_STATUS_OFFLINE 0xFE
//#define SWG_STATUS_UNKNOWN -128 // Idiot. unsigned char....Derr.
#define SWG_STATUS_OFF 0xFF // Documented this as off in API, so don't change.
#define SWG_STATUS_UNKNOWN 0xFE
#define SWG_STATUS_GENFAULT 0xFD //This is displayed in the panel, so adding it
// These are actual from RS485
#define SWG_STATUS_ON 0x00
#define SWG_STATUS_NO_FLOW 0x01 // no flow 0x01
#define SWG_STATUS_LOW_SALT 0x02 // low salt 0x02
//#define SWG_STATUS_VLOW_SALT 0x04 // very low salt 0x04
#define SWG_STATUS_HI_SALT 0x04 // high salt 0x04
#define SWG_STATUS_CLEAN_CELL 0x08 // clean cell 0x10
#define SWG_STATUS_TURNING_OFF 0x09 // turning off 0x09
#define SWG_STATUS_HIGH_CURRENT 0x10 // high current 0x08
#define SWG_STATUS_LOW_VOLTS 0x20 // low voltage 0x20
#define SWG_STATUS_LOW_TEMP 0x40 // low watertemp 0x40
#define SWG_STATUS_CHECK_PCB 0x80 // check PCB 0x80
// Other SWG codes not deciphered yes 0x03 & 0x0b seem to be messages when salt is low and turning on / off
#define CMD_PDA_0x04 0x04 // No idea, might be building menu
#define CMD_PDA_0x05 0x05 // No idea
#define CMD_PDA_0x1B 0x1b
#define CMD_PDA_HIGHLIGHT 0x08
#define CMD_PDA_CLEAR 0x09
#define CMD_PDA_SHIFTLINES 0x0F
#define CMD_PDA_HIGHLIGHTCHARS 0x10
/* ePump */
#define CMD_EPUMP_STATUS 0x1F
#define CMD_EPUMP_RPM 0x44
#define CMD_EPUMP_WATTS 0x45
// One Touch commands
//#define CMD_PDA_0x04 0x04 // No idea, might be building menu
/* iAqualink */
#define CMD_IAQ_PAGE_MSG 0x25
#define CMD_IAQ_TABLE_MSG 0x26 // ??? Some form of table populate
#define CMD_IAQ_PAGE_BUTTON 0x24
#define CMD_IAQ_PAGE_START 0x23 // Start a new menu // wait for 0x28 before sending anything
#define CMD_IAQ_PAGE_END 0x28 // Some kind a finished
#define CMD_IAQ_STARTUP 0x29 // Startup message
#define CMD_IAQ_POLL 0x30 // Poll message or ready to receive command
#define CMD_IAQ_CTRL_READY 0x31 // Get this when we can send big control command
#define CMD_IAQ_PAGE_CONTINUE 0x40 // Seems we get this on AUX device page when there is another page, keeps circuling through pages.
#define CMD_IAQ_TITLE_MESSAGE 0x2d // This is what the product name is set to (Jandy RS) usually
//#define CMD_IAQ_VSP_ERROR 0x2c // Error when setting speed too high
#define CMD_IAQ_MSG_LONG 0x2c // This this is display popup message. Next 2 bytes 0x00|0x01 = wait and then 0x00|0x00 clear
// If
#define CMD_IAQ_MAIN_STATUS 0x70
#define CMD_IAQ_1TOUCH_STATUS 0x71
#define CMD_IAQ_AUX_STATUS 0x72 // Get this on AqualinkTouch protocol when iAqualink protocol sends 0x18 (get aux status I assume)
#define CMD_IAQ_CMD_READY 0x73 // iAqualink ready to receive command
/*
#define CMD_IAQ_MSG_3 0x2d // Equiptment status message??
#define CMD_IAQ_0x31 0x31 // Some pump speed info
*/
#define ACK_CMD_READY_CTRL 0x80 // Send this before sending big control command
#define KEY_IAQTCH_HOME 0x01
#define KEY_IAQTCH_MENU 0x02
#define KEY_IAQTCH_ONETOUCH 0x03
#define KEY_IAQTCH_HELP 0x04
#define KEY_IAQTCH_BACK 0x05
#define KEY_IAQTCH_STATUS 0x06
#define KEY_IAQTCH_PREV_PAGE 0x20
#define KEY_IAQTCH_NEXT_PAGE 0x21
#define KEY_IAQTCH_OK 0x01 //HEX: 0x10|0x02|0x00|0x01|0x00|0x01|0x14|0x10|0x03|. OK BUTTON
#define KEY_IAQTCH_PREV_PAGE_ALTERNATE 0x1d // System setup prev
#define KEY_IAQTCH_NEXT_PAGE_ALTERNATE 0x1e // System setup next
// PAGE1 (Horosontal keys) (These are duplicate so probable delete)
#define KEY_IAQTCH_HOMEP_KEY01 0x11
#define KEY_IAQTCH_HOMEP_KEY02 0x12
#define KEY_IAQTCH_HOMEP_KEY03 0x13
#define KEY_IAQTCH_HOMEP_KEY04 0x14
#define KEY_IAQTCH_HOMEP_KEY05 0x15
#define KEY_IAQTCH_HOMEP_KEY06 0x16
#define KEY_IAQTCH_HOMEP_KEY07 0x17
#define KEY_IAQTCH_HOMEP_KEY08 0x18 // Other Devices (may not be able to change)
// Numbering is colum then row.
#define KEY_IAQTCH_KEY01 0x11 // Column 1 row 1
#define KEY_IAQTCH_KEY02 0x12 // column 1 row 2
#define KEY_IAQTCH_KEY03 0x13 // column 1 row 3
#define KEY_IAQTCH_KEY04 0x14 // column 1 row 4
#define KEY_IAQTCH_KEY05 0x15 // column 1 row 5
#define KEY_IAQTCH_KEY06 0x16 // column 2 row 1
#define KEY_IAQTCH_KEY07 0x17 // column 2 row 2
#define KEY_IAQTCH_KEY08 0x18 // column 2 row 3
#define KEY_IAQTCH_KEY09 0x19 // column 2 row 4
#define KEY_IAQTCH_KEY10 0x1a // column 2 row 5
#define KEY_IAQTCH_KEY11 0x1b // column 3 row 1
#define KEY_IAQTCH_KEY12 0x1c // column 3 row 2
#define KEY_IAQTCH_KEY13 0x1d // column 3 row 3
#define KEY_IAQTCH_KEY14 0x1e // column 3 row 4
#define KEY_IAQTCH_KEY15 0x1f // column 3 row 5
#define IAQ_PAGE_HOME 0x01
#define IAQ_PAGE_STATUS 0x5b
#define IAQ_PAGE_STATUS2 0x2a // Something get this for Status rather than 0x5b
#define IAQ_PAGE_DEVICES 0x36
#define IAQ_PAGE_DEVICES2 0x35
#define IAQ_PAGE_DEVICES3 0x51
#define IAQ_PAGE_SET_TEMP 0x39
#define IAQ_PAGE_MENU 0x0f
#define IAQ_PAGE_SET_VSP 0x1e
#define IAQ_PAGE_SET_TIME 0x4b
#define IAQ_PAGE_SET_DATE 0x4e
#define IAQ_PAGE_SET_SWG 0x30
#define IAQ_PAGE_SET_BOOST 0x1d
#define IAQ_PAGE_SET_QBOOST 0x3f
#define IAQ_PAGE_ONETOUCH 0x4d
#define IAQ_PAGE_COLOR_LIGHT 0x48
#define IAQ_PAGE_SYSTEM_SETUP 0x14
#define IAQ_PAGE_SYSTEM_SETUP2 0x49
#define IAQ_PAGE_SYSTEM_SETUP3 0x4a
#define IAQ_PAGE_VSP_SETUP 0x2d
#define IAQ_PAGE_FREEZE_PROTECT 0x11
#define IAQ_PAGE_LABEL_AUX 0x32
#define IAQ_PAGE_HELP 0x0c
#define IAQ_PAGE_SERVICEMODE 0x5e // Also Timeout
#define IAQ_PAGE_DEVICES_REV_Yg 0x0a // Panel rev Yg (and maybe others use this)
//#define IAQ_PAGE_START_BOOST 0x3f
//#define IAQ_PAGE_DEGREES 0xFF // Added this as never want to actually select the page, just go to it.
#define RSSA_DEV_STATUS 0x13
#define RSSA_DEV_READY 0x07 // Ready to receive change command
// For the moment, rest of RS_RA are in serialadapter.h
// Errors from get_packet
#define AQSERR_READ -1 // General fileIO read error
#define AQSERR_TIMEOUT -2 // Timeout
#define AQSERR_CHKSUM -3 // Checksum failed
#define AQSERR_2LARGE -4 // Buffer Overflow
#define AQSERR_2SMALL -5 // Not enough read
// At the moment just used for next ack
typedef enum {
DRS_NONE,
DRS_SWG,
DRS_EPUMP,
DRS_JXI,
DRS_LX,
DRS_CHEM,
DRS_HEATPUMP,
DRS_JLIGHT
} rsDeviceType;
/*
typedef enum {
ON,
OFF,
FLASH,
ENABLE,
LED_S_UNKNOWN
} aqledstate;
typedef struct aqualinkled
{
//int number;
aqledstate state;
} aqled;
*/
// Battery Status Identifiers
enum {
OK = 0,
LOW
};
typedef enum {
JANDY,
PENTAIR,
P_UNKNOWN
} protocolType;
int init_serial_port(const char* tty);
int init_blocking_serial_port(const char* tty);
//int init_readahead_serial_port(const char* tty);
void close_serial_port(int file_descriptor);
void close_blocking_serial_port();
bool serial_blockingmode();
//#ifdef AQ_PDA
//void set_pda_mode(bool mode);
//bool pda_mode();
//#endif
int generate_checksum(unsigned char* packet, int length);
protocolType getProtocolType(const unsigned char* packet);
bool check_jandy_checksum(unsigned char* packet, int length);
bool check_pentair_checksum(unsigned char* packet, int length);
void send_ack(int file_descriptor, unsigned char command);
void send_extended_ack(int fd, unsigned char ack_type, unsigned char command);
//void send_cmd(int file_descriptor, unsigned char cmd, unsigned char args);
int get_packet(int file_descriptor, unsigned char* packet);
//int get_packet_lograw(int fd, unsigned char* packet);
int is_valid_port(int fd);
//int get_packet_new(int fd, unsigned char* packet);
//int get_packet_new_lograw(int fd, unsigned char* packet);
//void close_serial_port(int file_descriptor, struct termios* oldtio);
//void process_status(void const * const ptr);
void process_status(unsigned char* ptr);
const char* get_packet_type(const unsigned char* packet , int length);
/*
void set_onetouch_enabled(bool mode);
bool onetouch_enabled();
void set_iaqtouch_enabled(bool mode);
bool iaqtouch_enabled();
bool VSP_enabled();
void set_extended_device_id_programming(bool mode);
bool extended_device_id_programming();
*/
void send_jandy_command(int fd, unsigned char *packet_buffer, int size);
void send_pentair_command(int fd, unsigned char *packet_buffer, int size);
void send_command(int fd, unsigned char *packet_buffer, int size);
/*
#ifdef ONETOUCH
void set_onetouch_mode(bool mode);
bool onetouch_mode();
#endif
*/
//void send_test_cmd(int fd, unsigned char destination, unsigned char b1, unsigned char b2, unsigned char b3);
//void send_command(int fd, unsigned char destination, unsigned char b1, unsigned char b2, unsigned char b3);
//void send_messaged(int fd, unsigned char destination, char *message);
#endif // AQ_SERIAL_H_

246
source/aq_systemutils.c Normal file
View File

@ -0,0 +1,246 @@
/*
* Copyright (c) 2017 Shaun Feakes - All rights reserved
*
* You may use redistribute and/or modify this code under the terms of
* the GNU General Public License version 2 as published by the
* Free Software Foundation. For the terms of this license,
* see <http://www.gnu.org/licenses/>.
*
* You are free to use this software under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* https://github.com/sfeakes/aqualinkd
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mount.h>
#include <sys/statvfs.h>
#include <sys/wait.h>
#include "aqualink.h"
#include "aq_scheduler.h"
bool remount_root_ro(bool readonly)
{
#ifdef AQ_CONTAINER
// In container this is pointless
return false;
#endif
if (readonly)
{
LOG(AQUA_LOG, LOG_INFO, "reMounting root RO\n");
mount(NULL, "/", NULL, MS_REMOUNT | MS_RDONLY, NULL);
return true;
}
else
{
struct statvfs fsinfo;
statvfs("/", &fsinfo);
if ((fsinfo.f_flag & ST_RDONLY) == 0) // We are readwrite, ignore
return false;
LOG(AQUA_LOG, LOG_INFO, "reMounting root RW\n");
mount(NULL, "/", NULL, MS_REMOUNT, NULL);
return true;
}
}
FILE *aq_open_file(char *filename, bool *ro_root, bool *created_file)
{
FILE *fp;
*ro_root = remount_root_ro(false);
if (access(filename, F_OK) == 0)
{
*created_file = true;
}
fp = fopen(filename, "w");
if (fp == NULL)
{
remount_root_ro(*ro_root);
}
return fp;
}
bool aq_close_file(FILE *file, bool ro_root)
{
if (file != NULL)
fclose(file);
return remount_root_ro(ro_root);
}
#define BUFFER_SIZE 4096
bool copy_file(const char *source_path, const char *destination_path)
{
bool ro_root = remount_root_ro(false);
FILE *source_file = fopen(source_path, "rb");
if (source_file == NULL)
{
LOG(AQUA_LOG, LOG_ERR, "Error opening source file: %s\n", source_path);
remount_root_ro(ro_root);
return false;
}
FILE *destination_file = fopen(destination_path, "wb");
if (destination_file == NULL)
{
LOG(AQUA_LOG, LOG_ERR, "Error opening source file: %s\n", destination_path);
fclose(source_file);
remount_root_ro(ro_root);
return false;
}
char buffer[BUFFER_SIZE];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, source_file)) > 0)
{
if (fwrite(buffer, 1, bytes_read, destination_file) != bytes_read)
{
LOG(AQUA_LOG, LOG_ERR, "Error writing to destination file: %s\n", destination_path);
fclose(source_file);
fclose(destination_file);
remount_root_ro(ro_root);
return false;
}
}
if (ferror(source_file))
{
LOG(AQUA_LOG, LOG_ERR, "Error reading from source: %s\n", source_path);
fclose(source_file);
fclose(destination_file);
remount_root_ro(ro_root);
return false;
}
fclose(source_file);
fclose(destination_file);
remount_root_ro(ro_root);
return true;
}
bool run_aqualinkd_upgrade(bool onlycheck)
{
int pipe_curl_to_bash[2];
pid_t pid_curl, pid_bash;
//char *curl_args[] = {"curl", "-fsSl", "http://tiger/scratch/remote_install.sh", NULL};
char *curl_args[] = {"curl", "-fsSl", "-H", "Accept: application/vnd.github.raw", "https://api.github.com/repos/AqualinkD/AqualinkD/contents/release/remote_install.sh", NULL};
char *bash_args[] = {"bash", "-s", "--", "check", NULL};
int status_curl, status_bash;
if (!onlycheck) {
bash_args[3] = NULL;
}
if (pipe(pipe_curl_to_bash) == -1)
{
LOG(AQUA_LOG, LOG_ERR,"Upgrade error, opening pipe");
return false;
}
// Fork for curl
pid_curl = fork();
if (pid_curl == -1)
{
LOG(AQUA_LOG, LOG_ERR,"Upgrade error, fork (curl)");
return false;
}
if (pid_curl == 0)
{ // Child process (curl)
close(pipe_curl_to_bash[0]);
dup2(pipe_curl_to_bash[1], STDOUT_FILENO);
close(pipe_curl_to_bash[1]);
execvp("curl", curl_args);
LOG(AQUA_LOG, LOG_ERR,"Upgrade error, execvp (curl)");
return false;
}
// Fork for bash
pid_bash = fork();
if (pid_bash == -1)
{
LOG(AQUA_LOG, LOG_ERR,"Upgrade error, fork (bash)");
return false;
}
if (pid_bash == 0)
{ // Child process (bash)
close(pipe_curl_to_bash[1]);
dup2(pipe_curl_to_bash[0], STDIN_FILENO);
close(pipe_curl_to_bash[0]);
execvp("bash", bash_args);
LOG(AQUA_LOG, LOG_ERR,"Upgrade error, execvp (bash)");
return false;
}
// Parent process
close(pipe_curl_to_bash[0]);
close(pipe_curl_to_bash[1]);
// Wait for curl and get its exit status
if (waitpid(pid_curl, &status_curl, 0) == -1)
{
LOG(AQUA_LOG, LOG_ERR,"Upgrade error, waitpid (curl)");
return false;
}
// Wait for bash and get its exit status
if (waitpid(pid_bash, &status_bash, 0) == -1)
{
LOG(AQUA_LOG, LOG_ERR,"Upgrade error, waitpid (bash)");
return false;
}
// Check the exit status of curl
if (WIFEXITED(status_curl))
{
//printf("curl exited with status: %d\n", WEXITSTATUS(status_curl));
if (WEXITSTATUS(status_curl) != 0) {
LOG(AQUA_LOG, LOG_ERR,"Upgrade error, curl exited with status: %d\n", WEXITSTATUS(status_curl));
return false;
}
}
else if (WIFSIGNALED(status_curl))
{
//printf("curl terminated by signal: %d\n", WTERMSIG(status_curl));
LOG(AQUA_LOG, LOG_ERR,"Upgrade error, curl terminated by signal: %d\n", WTERMSIG(status_curl));
return false;
}
// Check the exit status of bash
if (WIFEXITED(status_bash))
{
//printf("bash exited with status: %d\n", WEXITSTATUS(status_bash));
if (WEXITSTATUS(status_bash) != 0) {
LOG(AQUA_LOG, LOG_ERR,"Upgrade error, bash exited with status: %d\n", WEXITSTATUS(status_bash));
return false;
}
}
else if (WIFSIGNALED(status_bash))
{
//printf("bash terminated by signal: %d\n", WTERMSIG(status_bash));
LOG(AQUA_LOG, LOG_ERR,"Upgrade error, bash terminated by signal: %d\n", WTERMSIG(status_bash));
return false;
}
//printf("Command execution complete.\n");
LOG(AQUA_LOG, LOG_NOTICE, "AqualinkD is upgrading!");
return true;
}

12
source/aq_systemutils.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef AQ_FILESYSTEM_H_
#define AQ_FILESYSTEM_H_
FILE *aq_open_file( char *filename, bool *ro_root, bool* created_file);
bool aq_close_file(FILE *file, bool ro_root);
bool copy_file(const char *source_path, const char *destination_path);
bool run_aqualinkd_upgrade(bool onlycheck);
#endif //AQ_FILESYSTEM_H_

228
source/aq_timer.c Normal file
View File

@ -0,0 +1,228 @@
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include "aqualink.h"
#include "utils.h"
#include "aq_timer.h"
struct timerthread {
pthread_t thread_id;
pthread_mutex_t thread_mutex;
pthread_cond_t thread_cond;
aqkey *button;
int deviceIndex;
struct aqualinkdata *aq_data;
int duration_min;
struct timespec timeout;
time_t started_at;
struct timerthread *next;
struct timerthread *prev;
};
/*volatile*/ static struct timerthread *_timerthread_ll = NULL;
void *timer_worker( void *ptr );
struct timerthread *find_timerthread(aqkey *button)
{
struct timerthread *t_ptr;
if (_timerthread_ll != NULL) {
for (t_ptr = _timerthread_ll; t_ptr != NULL; t_ptr = t_ptr->next) {
if (t_ptr->button == button) {
return t_ptr;
}
}
}
return NULL;
}
int get_timer_left(aqkey *button)
{
struct timerthread *t_ptr = find_timerthread(button);
if (t_ptr != NULL) {
time_t now = time(0);
double seconds = difftime(now, t_ptr->started_at);
return (int) ((t_ptr->duration_min - (seconds / 60)) +0.5) ;
}
return 0;
}
void clear_timer(struct aqualinkdata *aq_data, /*aqkey *button,*/ int deviceIndex)
{
//struct timerthread *t_ptr = find_timerthread(button);
struct timerthread *t_ptr = find_timerthread(&aq_data->aqbuttons[deviceIndex]);
if (t_ptr != NULL) {
LOG(TIMR_LOG, LOG_INFO, "Clearing timer for '%s'\n",t_ptr->button->name);
t_ptr->duration_min = 0;
pthread_cond_broadcast(&t_ptr->thread_cond);
}
}
void start_timer(struct aqualinkdata *aq_data, /*aqkey *button,*/ int deviceIndex, int duration)
{
aqkey *button = &aq_data->aqbuttons[deviceIndex];
struct timerthread *t_ptr = find_timerthread(button);
if (t_ptr != NULL) {
LOG(TIMR_LOG, LOG_INFO, "Timer already active for '%s', resetting\n",t_ptr->button->name);
t_ptr->duration_min = duration;
pthread_cond_broadcast(&t_ptr->thread_cond);
return;
}
struct timerthread *tmthread = calloc(1, sizeof(struct timerthread));
tmthread->aq_data = aq_data;
tmthread->button = button;
tmthread->deviceIndex = deviceIndex;
tmthread->thread_id = 0;
tmthread->duration_min = duration;
tmthread->next = NULL;
tmthread->started_at = time(0); // This will get reset once we actually start. But need it here incase someone calls get_timer_left() before we start
if( pthread_create( &tmthread->thread_id , NULL , timer_worker, (void*)tmthread) < 0) {
LOG(TIMR_LOG, LOG_ERR, "could not create timer thread for button '%s'\n",button->name);
free(tmthread);
return;
}
if (_timerthread_ll == NULL) {
_timerthread_ll = tmthread;
_timerthread_ll->prev = NULL;
//LOG(TIMR_LOG, LOG_NOTICE, "Added Timer '%s' at beginning LL\n",_timerthread_ll->button->name);
}
else
{
for (t_ptr = _timerthread_ll; t_ptr->next != NULL; t_ptr = t_ptr->next) {} // Simply run to the end of the list
t_ptr->next = tmthread;
tmthread->prev = t_ptr;
//LOG(TIMR_LOG, LOG_NOTICE, "Added Timer '%s' at end LL \n",tmthread->button->name);
}
if ( tmthread->thread_id != 0 ) {
pthread_detach(tmthread->thread_id);
}
}
#define WAIT_TIME_BEFORE_ON_CHECK 1000
//#define WAIT_TIME_BEFORE_ON_CHECK 1000000 // 1 second
void *timer_worker( void *ptr )
{
struct timerthread *tmthread;
tmthread = (struct timerthread *) ptr;
int retval = 0;
int cnt=0;
LOG(TIMR_LOG, LOG_NOTICE, "Start timer for '%s'\n",tmthread->button->name);
// Add mask so we know timer is active
tmthread->button->special_mask |= TIMER_ACTIVE;
/*
#ifndef PRESTATE_ONOFF
delay(WAIT_TIME_BEFORE_ON_CHECK);
LOG(TIMR_LOG, LOG_DEBUG, "wait finished for button state '%s'\n",tmthread->button->name);
#endif
// device should be on, but check, ignore for PDA as that may not have been turned on yet
if (!isPDA_PANEL && tmthread->button->led->state == OFF) {
if ((tmthread->button->special_mask & PROGRAM_LIGHT) == PROGRAM_LIGHT && in_light_programming_mode(tmthread->aq_data)) {
LOG(TIMR_LOG, LOG_NOTICE, "Not turning on '%s' as programmer is\n",tmthread->button->name);
} else {
LOG(TIMR_LOG, LOG_NOTICE, "turning on '%s'\n",tmthread->button->name);
panel_device_request(tmthread->aq_data, ON_OFF, tmthread->deviceIndex, false, NET_TIMER);
}
}
*/
while (tmthread->button->led->state == OFF) {
LOG(TIMR_LOG, LOG_DEBUG, "waiting for button state '%s' to change\n",tmthread->button->name);
delay(WAIT_TIME_BEFORE_ON_CHECK);
if (cnt++ == 5 && !isPDA_PANEL) {
LOG(TIMR_LOG, LOG_NOTICE, "turning on '%s'\n",tmthread->button->name);
panel_device_request(tmthread->aq_data, ON_OFF, tmthread->deviceIndex, true, NET_TIMER);
} else if (cnt == 10) {
LOG(TIMR_LOG, LOG_ERR, "button state never turned on'%s'\n",tmthread->button->name);
break;
}
}
pthread_mutex_lock(&tmthread->thread_mutex);
do {
if (retval != 0) {
LOG(TIMR_LOG, LOG_ERR, "pthread_cond_timedwait failed for '%s', error %d %s\n",tmthread->button->name,retval,strerror(retval));
break;
} else if (tmthread->duration_min <= 0) {
//LOG(TIMR_LOG, LOG_INFO, "Timer has been reset to 0 for '%s'\n",tmthread->button->name);
break;
}
clock_gettime(CLOCK_REALTIME, &tmthread->timeout);
tmthread->timeout.tv_sec += (tmthread->duration_min * 60);
tmthread->started_at = time(0);
LOG(TIMR_LOG, LOG_INFO, "Will turn off '%s' in %d minutes\n",tmthread->button->name, tmthread->duration_min);
} while ((retval = pthread_cond_timedwait(&tmthread->thread_cond, &tmthread->thread_mutex, &tmthread->timeout)) != ETIMEDOUT);
pthread_mutex_unlock(&tmthread->thread_mutex);
LOG(TIMR_LOG, LOG_NOTICE, "End timer for '%s'\n",tmthread->button->name);
// We need to detect if we ended on time or were killed.
// If killed the device is probable off (or being set to off), so we should probably poll a few times before turning off.
// Either that of change ap_panel to not turn off device if timer is set.
//LOG(TIMR_LOG, LOG_NOTICE, "End timer duration '%d'\n",tmthread->duration_min);
// if duration_min is 0 we were killed, if not we got here on timeout, so turn off device.
if (tmthread->duration_min != 0 && tmthread->button->led->state != OFF) {
LOG(TIMR_LOG, LOG_INFO, "Timer waking turning '%s' off\n",tmthread->button->name);
panel_device_request(tmthread->aq_data, ON_OFF, tmthread->deviceIndex, false, NET_TIMER);
} else if (tmthread->button->led->state == OFF) {
LOG(TIMR_LOG, LOG_INFO, "Timer waking '%s' is already off\n",tmthread->button->name);
}
if (tmthread->button->led->state != OFF) {
// Need to wait
}
// remove mask so we know timer is dead
tmthread->button->special_mask &= ~ TIMER_ACTIVE;
if (tmthread->next != NULL && tmthread->prev != NULL){
// Middle of linked list
tmthread->next->prev = tmthread->prev;
tmthread->prev->next = tmthread->next;
//LOG(TIMR_LOG, LOG_NOTICE, "Removed Timer '%s' from middle LL\n",tmthread->button->name);
} else if (tmthread->next == NULL && tmthread->prev != NULL){
// end of linked list
tmthread->prev->next = NULL;
//LOG(TIMR_LOG, LOG_NOTICE, "Removed Timer '%s' from end LL\n",tmthread->button->name);
} else if (tmthread->next != NULL && tmthread->prev == NULL){
// beginning of linked list
_timerthread_ll = tmthread->next;
_timerthread_ll->prev = NULL;
//LOG(TIMR_LOG, LOG_NOTICE, "Removed Timer '%s' from beginning LL\n",tmthread->button->name);
} else if (tmthread->next == NULL && tmthread->prev == NULL){
// only item in list
_timerthread_ll = NULL;
//LOG(TIMR_LOG, LOG_NOTICE, "Removed Timer '%s' last LL\n",tmthread->button->name);
}
free(tmthread);
pthread_exit(0);
return ptr;
}

15
source/aq_timer.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef AQ_TIMER_H_
#define AQ_TIMER_H_
#include "aqualink.h"
void start_timer(struct aqualinkdata *aq_data, /*aqkey *button,*/ int deviceIndex, int duration);
int get_timer_left(aqkey *button);
void clear_timer(struct aqualinkdata *aq_data, /*aqkey *button,*/ int deviceIndex);
// Not best place for this, but leave it here so all requests are in net services, this is forward decleration of function in net_services.c
#ifdef AQ_PDA
void create_PDA_on_off_request(aqkey *button, bool isON);
#endif
#endif // AQ_TIMER_H_

449
source/aqualink.h Normal file
View File

@ -0,0 +1,449 @@
#ifndef AQUALINK_H_
#define AQUALINK_H_
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include "aq_serial.h"
#include "aq_programmer.h"
#include "sensors.h"
//#include "aq_panel.h" // Moved to later in file to overcome circular dependancy. (crappy I know)
#define isMASK_SET(bitmask, mask) ((bitmask & mask) == mask)
#define setMASK(bitmask, mask) (bitmask |= mask)
#define removeMASK(bitmask, mask) (bitmask &= ~mask)
#define SIGRESTART SIGUSR1
#define SIGRUPGRADE SIGUSR2
#ifdef AQ_NO_THREAD_NETSERVICE
#define DEFAULT_POLL_SPEED -1
#define DEFAULT_POLL_SPEED_NON_THREADDED 2
#endif
#define CLIGHT_PANEL_FIX // Overcome bug in some jandy panels where color light status of on is not in LED status
#define TIME_CHECK_INTERVAL 3600
//#define TIME_CHECK_INTERVAL 100 // DEBUG ONLY
#define ACCEPTABLE_TIME_DIFF 120
// Use these settings to test time
//#define TIME_CHECK_INTERVAL 100
//#define ACCEPTABLE_TIME_DIFF 10
#define MAX_ZERO_READ_BEFORE_RECONNECT_NONBLOCKING 100000 // 10k normally
#define MAX_ZERO_READ_BEFORE_RECONNECT_BLOCKING (25 / (SERIAL_BLOCKING_TIME / 10) ) // Want this to be 25 seconds, so it's depdand on how long the serial blocking is
// Time in ms to delay between read requests in non blocking serial port. Have to set something to stop CPU spiking.
#define NONBLOCKING_SERIAL_DELAY 2
// The below will change state of devices before that are actually set on the control panel, this helps
// with duplicate messages that come in quick succession that can catch the state before it happens.
//#define PRESTATE_ONOFF
#define PRESTATE_SWG_SETPOINT
//#define PRESTATE_HEATER_SETPOINT // This one is not implimented yet
void intHandler(int dummy);
bool isAqualinkDStopping();
#ifdef AQ_PDA
bool checkAqualinkTime(); // Only need to externalise this for PDA
#endif
// There are cases where SWG will read 80% in allbutton and 0% in onetouch/aqualinktouch, this will compile that in or out
//#define READ_SWG_FROM_EXTENDED_ID
//#define TOTAL_BUTTONS 12
/*
#ifndef AQ_RS16
#define TOTAL_BUTTONS 12
#else
#define TOTAL_BUTTONS 20
#define RS16_VBUTTONS_START 13 // RS16 panel has 4 buttons with no LED's, so list them for manual matching to RS messages
#define RS16_VBUTTONS_END 16 // RS16 panel has 4 buttons with no LED's, so list them for manual matching to RS messages
#endif
*/
#define TEMP_UNKNOWN -999
#define TEMP_REFRESH -998
#define AQ_UNKNOWN TEMP_UNKNOWN
//#define UNKNOWN TEMP_UNKNOWN
#define DATE_STRING_LEN 30
#define MAX_PUMPS 4
#define MAX_LIGHTS 4
#define MAX_SENSORS 4
bool isVirtualButtonEnabled();
#define PUMP_RPM_MAX 3450
#define PUMP_RPM_MIN 600
#define PUMP_GPM_MAX 130
#define PUMP_GPM_MIN 15
/*
typedef enum temperatureUOM {
FAHRENHEIT,
CELSIUS,
UNKNOWN
} temperatureUOM;
*/
typedef enum {
ON,
OFF,
FLASH,
ENABLE,
LED_S_UNKNOWN
} aqledstate;
typedef struct aqualinkled
{
//int number;
aqledstate state;
} aqled;
typedef struct aqualinkkey
{
//int number;
//aqledstate ledstate; // In the future there is no need to aqled struct so move code over to this.
aqled *led;
char *label;
char *name;
//#ifdef AQ_PDA
// char *pda_label;
//#endif
unsigned char code;
unsigned char rssd_code;
int dz_idx;
uint8_t special_mask;
void *special_mask_ptr;
} aqkey;
//#include "aq_programmer.h"
// special_mask for above aqualinkkey structure.
#define VS_PUMP (1 << 0)
#define PROGRAM_LIGHT (1 << 1)
#define TIMER_ACTIVE (1 << 2)
//#define DIMMER_LIGHT (1 << 3) // NOT USED (Use PROGRAM_LIGHT or type LC_DIMMER)
#define VIRTUAL_BUTTON (1 << 4)
// Below are types of VIRT_BUTTON, SO VIRT_BUTTON must also be set
#define VIRTUAL_BUTTON_ALT_LABEL (1 << 5)
#define VIRTUAL_BUTTON_CHILLER (1 << 6)
//typedef struct ProgramThread ProgramThread; // Definition is later
struct programmingthread {
pthread_t *thread_id;
pthread_mutex_t thread_mutex;
pthread_cond_t thread_cond;
program_type ptype;
//void *thread_args;
};
/*
struct programmerArgs {
aqkey *button;
int value;
//char cval[PTHREAD_ARG];
};
struct programmingThreadCtrl {
pthread_t thread_id;
//void *thread_args;
struct programmerArgs pArgs;
char thread_args[PTHREAD_ARG];
struct aqualinkdata *aq_data;
};
*/
/*
typedef enum panel_status {
CONNECTED,
CHECKING_CONFIG,
CONECTING,
LOOKING_IDS,
STARTING,
SERIAL_ERROR, // Errors that stop reading serial port should be below this line
NO_IDS_ERROR,
} panel_status;
*/
typedef enum action_type {
NO_ACTION = -1,
POOL_HTR_SETPOINT,
SPA_HTR_SETPOINT,
FREEZE_SETPOINT,
CHILLER_SETPOINT,
SWG_SETPOINT,
SWG_BOOST,
PUMP_RPM,
PUMP_VSPROGRAM,
POOL_HTR_INCREMENT, // Setpoint add value (can be negative)
SPA_HTR_INCREMENT, // Setpoint add value
ON_OFF,
TIMER,
LIGHT_MODE,
LIGHT_BRIGHTNESS,
DATE_TIME
} action_type;
struct action {
action_type type;
time_t requested;
int value;
int id; // Only used for Pumps at the moment.
//char value[10];
};
// Moved to aq_programmer to stop circular dependancy
/*
typedef enum pump_type {
PT_UNKNOWN = -1,
EPUMP,
VSPUMP,
VFPUMP
} pump_type;
*/
/*
typedef enum simulator_type {
SIM_NONE,
SIM_ALLB,
SIM_ONET,
SIM_PDA,
SIM_IAQT
} simulator_type;
*/
// Set Point types
typedef enum SP_TYPE{
SP_POOL,
SP_SPA,
SP_CHILLER
} SP_TYPE;
//#define PUMP_PRIMING -1
//#define PUMP_OFFLINE -2
//#define PUMP_ERROR -3
#define PUMP_OFF_RPM 0
#define PUMP_OFF_GPM PUMP_OFF_RPM
#define PUMP_OFF_WAT PUMP_OFF_RPM
// FUTURE VSP STATUS, keep panel status and RS485 status seperate
typedef enum panel_vsp_status
{
PS_OK = 0, // Start at 0 to match actual status from RS, but go down from their.
PS_OFF = -1,
PS_PRIMING = -2,
PS_OFFLINE = -3,
PS_ERROR = -4
} panel_vsp_status;
#define PUMP_NAME_LENGTH 30
// Overall Status of Aqualinkd
#define CONNECTED ( 1 << 0 ) // All is good (every other mask should be cleared)
#define NOT_CONNECTED ( 1 << 2 ) // Serial Error maybe rename
#define AUTOCONFIGURE_ID ( 1 << 3 )
#define AUTOCONFIGURE_PANEL ( 1 << 4 )
#define CHECKING_CONFIG ( 1 << 5 )
//#define LOOKING_IDS ( 1 << 6 )
#define CONNECTING ( 1 << 7 )
#define ERROR_NO_DEVICE_ID ( 1 << 8 ) // maybe covered in NOT_CONNECTED
#define ERROR_SERIAL ( 1 << 9 )
typedef struct pumpd
{
int rpm;
int gpm;
int watts;
int maxSpeed; // Max rpm or gpm depending on pump
int minSpeed;
unsigned char pumpID;
int pumpIndex;
char pumpName[PUMP_NAME_LENGTH];
//char *pumpName;
pump_type pumpType;
//int buttonID;
protocolType prclType;
aqkey *button;
//bool updated;
// Other VSP values read directly from RS485
int mode; // 0 local control, 1 remote control
//int driveState; // Haven't figured out what this is yet
int status;
panel_vsp_status pStatus; // FUTURE VSP STATUS,
int pressureCurve;
} pump_detail;
// color light modes (Aqualink program, Jandy, Jandy LED, SAm/SAL, Color Logic, Intellibrite)
typedef enum clight_type {
LC_PROGRAMABLE=0,
LC_JANDY,
LC_JANDYLED,
LC_SAL,
LC_CLOGIG,
LC_INTELLIB,
LC_HAYWCL,
LC_JANDYINFINATE, // was SPARE_1 (Infinate watercolors LED)
LC_SPARE_2,
LC_SPARE_3,
LC_DIMMER, // use 0, 25, 50, 100
LC_DIMMER2, // use range 0 to 100
NUMBER_LIGHT_COLOR_TYPES // This is used to size and count so add more prior to this
} clight_type;
/*
typedef enum {
MD_CHILLER,
MD_HEATPUMP
} heatmump_mode;
*/
typedef struct vbuttond
{
char *altlabel;
bool in_alt_mode; // Example if altlabel="chiller", if last seen was chiller message this is true.
//heatmump_mode chiller_mode;
// Add any other special params for virtual button
} vbutton_detail;
typedef enum {
NET_MQTT=0,
NET_API,
NET_WS,
NET_DZMQTT,
NET_TIMER, // Timer or Scheduler (eg poweron/freezeprotect check)
UNACTION_TIMER
} request_source;
typedef struct clightd
{
clight_type lightType;
aqkey *button;
unsigned char lightID; // RS485 ID (only Jandy infinate watercolor)
int currentValue;
int lastValue; // Used for AqualinkD self programming
aqledstate RSSDstate; // state from rs serial adapter
} clight_detail;
#include "aq_panel.h"
struct aqualinkdata
{
//panel_status panelstatus;
uint16_t status_mask;
char version[AQ_MSGLEN*2]; // Will be replaced by below in future
char revision[AQ_MSGLEN]; // Will be replaced by below in future
// The below 4 are set (sometimes) but not used yet
char panel_rev[AQ_MSGLEN]; // From panel
char panel_cpu[AQ_MSGLEN]; // From panel
char panel_string[AQ_MSGLEN]; // This is from actual PANEL not aqualinkd's config
uint16_t panel_support_options;
char date[AQ_MSGLEN];
char time[AQ_MSGLEN];
char last_message[AQ_MSGLONGLEN+1]; // Last ascii message from panel - allbutton (or PDA) protocol
char last_display_message[AQ_MSGLONGLEN+1]; // Last message to display in web UI
bool is_display_message_programming;
aqled aqualinkleds[TOTAL_LEDS];
aqkey aqbuttons[TOTAL_BUTTONS];
unsigned short total_buttons;
unsigned short virtual_button_start;
int air_temp;
int pool_temp;
int spa_temp;
int temp_units;
//bool single_device; // Pool or Spa only, not Pool & Spa (Thermostat setpoints are different)
int battery;
int frz_protect_set_point;
int pool_htr_set_point;
int spa_htr_set_point;
int swg_percent;
int swg_ppm;
int chiller_set_point;
aqkey *chiller_button;
//heatmump_mode chiller_mode;
unsigned char ar_swg_device_status; // Actual state
unsigned char heater_err_status;
aqledstate swg_led_state; // Display state for UI's
aqledstate service_mode_state;
aqledstate frz_protect_state;
//aqledstate chiller_state;
int num_pumps;
pump_detail pumps[MAX_PUMPS];
int num_lights;
clight_detail lights[MAX_LIGHTS];
bool boost;
char boost_msg[10];
int boost_duration; // need to remove boost message and use this
int boost_linked_device;
float ph;
int orp;
// Below this line is not state related. (Future just do a mem compare for change)
//aqkey *orderedbuttons[TOTAL_BUTTONS]; // Future to reduce RS4,6,8,12,16 & spa buttons
//unsigned short total_ordered_buttons;
unsigned char last_packet_type;
int swg_delayed_percent;
//bool simulate_panel; // NSF remove in future
unsigned char simulator_packet[AQ_MAXPKTLEN+1];
bool simulator_packet_updated;
int simulator_packet_length;
//bool simulator_active; // should be redundant with other two
unsigned char simulator_id;
//simulator_type simulator_active;
emulation_type simulator_active;
bool aqManagerActive;
int open_websockets;
struct programmingthread active_thread;
struct action unactioned;
unsigned char raw_status[AQ_PSTLEN];
// Multiple threads update this value.
volatile bool updated;
char self[AQ_MSGLEN*2];
int num_sensors;
external_sensor sensors[MAX_SENSORS];
#ifdef AQ_MANAGER
volatile bool run_slogger;
int slogger_packets;
bool slogger_debug;
char slogger_ids[20];
#endif
int rs16_vbutton_start;
int rs16_vbutton_end;
#ifdef AQ_PDA
int pool_heater_index;
int spa_heater_index;
int solar_heater_index;
#endif
// Timing for DEBUG
#ifdef AQ_DEBUG
struct timespec last_active_time;
struct timespec start_active_time;
#endif
// Overcome color light bug, by reconnecting allbutton panel.
//bool reconnectAllButton;
};
#endif

1588
source/aqualinkd.c Normal file

File diff suppressed because it is too large Load Diff

363
source/color_lights.c Normal file
View File

@ -0,0 +1,363 @@
#include <stdio.h>
#include <string.h>
//#define COLOR_LIGHTS_C_
#include "color_lights.h"
/*
Jandy Colors
Jandy LED Light
Sam/SL
Color Logic
Intelibright
Haywood Universal Color
*/
bool isShowMode(const char *mode);
/****** This list MUST be in order of clight_type enum *******/
char *_color_light_options[NUMBER_LIGHT_COLOR_TYPES][LIGHT_COLOR_OPTIONS] =
//char *_color_light_options[NUMBER_LIGHT_COLOR_TYPES][LIGHT_COLOR_OPTIONS] =
{
// AqualnkD Colors ignored as no names in control panel.
{ "Off", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18" },
{ // Jandy Color
"Off",
"Alpine White", // 0x41
"Sky Blue",
"Cobalt Blue",
"Caribbean Blue",
"Spring Green",
"Emerald Green",
"Emerald Rose",
"Magenta",
"Garnet Red",
"Violet",
"Color Splash"
},
{ // Jandy LED
"Off",
"Alpine White",
"Sky Blue",
"Cobalt Blue",
"Caribbean Blue",
"Spring Green",
"Emerald Green",
"Emerald Rose",
"Magenta",
"Violet",
"Slow Splash",
"Fast Splash",
"USA", // America the Beautiful <- Think this is Infinite Water Colors. Need to check what version that changed.
"Fat Tuesday",
"Disco Tech"
},
{ // SAm/SAL
"Off",
"White",
"Light Green",
"Green",
"Cyan",
"Blue",
"Lavender",
"Magenta"
},
{ // Color Logic
"Off",
"Voodoo Lounge", // 0x41 (both home and sim)
"Deep Blue Sea", // 0x42 (both gome and sim)
"Afternoon Skies", // 0x44 home // 0x43 sim // 'Afternoon Sky' on allbutton, Skies on iaqtouch
"Emerald", // 0x44
"Sangria",
"Cloud White", // 0x46
"Twilight", // 0x4c (home panel) // 0x47
"Tranquility", // 0x4d (home panel) // 0x48
"Gemstone", // 0x4e (home panel) // 0x49 (simulator)
"USA", // 0x4f (home panel) // 0x4a (simulator)
"Mardi Gras", // 0x50 (home panel) // 0x4b (simulator)
"Cool Cabaret" // 0x51 (home panel) // 0x4c
},
{ // IntelliBrite
"Off",
"SAm",
"Party",
"Romance",
"Caribbean",
"American",
"Cal Sunset",
"Royal",
"Blue",
"Green",
"Red",
"White",
"Magenta"
},
{ // Haywood Universal Color
"Off",
"Voodoo Lounge", // 0x41 (both home and sim) // Looks like 28 + <value or index> = 0x41 = 1st index
"Deep Blue Sea", // 0x42 (both gome and sim)
"Royal Blue", // // 0x43 home // non
"Afternoon Skies", // 0x44 home // 0x43 sim // 'Afternoon Sky' on allbutton, Skies on iaqtouch
"Aqua Green", //
"Emerald", // 0x44
"Cloud White", // 0x46
"Warm Red", //
"Flamingo", //
"Vivid Violet", //
"Sangria", // 0x4b (home panel) // Non existant
"Twilight", // 0x4c (home panel) // 0x47
"Tranquility", // 0x4d (home panel) // 0x48
"Gemstone", // 0x4e (home panel) // 0x49 (simulator)
"USA", // 0x4f (home panel) // 0x4a (simulator)
"Mardi Gras", // 0x50 (home panel) // 0x4b (simulator)
"Cool Cabaret" // 0x51 (home panel) // 0x4c
},
{// Jandy Infinate Water Colors (RS485)
"Off",
"Alpine White",
"Sky Blue",
"Cobalt Blue",
"Caribbean Blue",
"Spring Green",
"Emerald Green",
"Emerald Rose",
"Ruby Red", // Added over Jandy LED
"Magenta",
"Violet",
"Slow Splash",
"Fast Splash",
"America The Beautiful", // America the Beautiful <- Think this is Infinite Water Colors. Need to check what version that changed.
"Fat Tuesday",
"Disco Tech"
},
{/*Spare 2*/},
{/*Spare 3*/},
{ // Dimmer // From manual this is 0 for off, 128+<value%> so 153 = 25% = 0x99
"Off",
"25%", // 0x99 (simulator) = 153 dec
"50%", // 0xb2 (simulator) = 178 dec same as (0x99 + 25)
"75%", // 0xcb (simulator) = 203 dec
"100%" // 0xe4 = 228 dec
},
{/* Dimmer with full range */}
};
// DON'T FORGET TO CHANGE #define DIMMER_LIGHT_INDEX 10 in color_lights.h
/*
void deleteLightOption(int type, int index)
{
int arrlen = LIGHT_COLOR_OPTIONS;
memmove(_color_light_options[type]+index, _color_light_options[type]+index+1, (--arrlen - index) * sizeof *_color_light_options[type]);
}
void setColorLightsPanelVersion(uint8_t supported)
{
static bool set = false;
if (set)
return;
if ((supported & REP_SUP_CLIT4) == REP_SUP_CLIT4)
return; // Full panel support, no need to delete anything
//deleteLightOption(4, 11); // Color Logic "Sangria"
deleteLightOption(4, 9); // Color Logic "Vivid Violet",
deleteLightOption(4, 8); // Color Logic "Flamingo"
deleteLightOption(4, 7); // Color Logic "Warm Red",
deleteLightOption(4, 4); // Color Logic "Aqua Green"
deleteLightOption(4, 2); // Color Logic "Royal Blue"
set = true;
}
*/
void clear_aqualinkd_light_modes()
{
//_color_light_options[0] = _aqualinkd_custom_colors;
for (int i=0; i < LIGHT_COLOR_OPTIONS; i++) {
_color_light_options[0][i] = NULL;
//_color_light_options[0][i] = i;
//_color_light_options[0][i] = _aqualinkd_custom_colors[i];
}
}
bool set_aqualinkd_light_mode_name(char *name, int index, bool isShow)
{
static bool reset = false;
// Reset all options only once.
if (!reset) {
reset = true;
for (int i=1; i<LIGHT_COLOR_OPTIONS; i++) {
_color_light_options[0][i] = NULL;
}
}
// TODO NSF check isShow and add a custom one if needed
_color_light_options[0][index] = name;
return true;
}
const char *get_aqualinkd_light_mode_name(int index, bool *isShow)
{
// if index 1 is "1" then none are set.
if ( _color_light_options[0][1] == NULL || strcmp(_color_light_options[0][1], "1") == 0) {
return NULL;
}
*isShow = isShowMode(_color_light_options[0][index]);
return _color_light_options[0][index];
}
const char *get_currentlight_mode_name(clight_detail light, emulation_type protocol)
{
/*
if (light.lightType == LC_PROGRAMABLE && light.button->led->state == OFF) {
return "Off";
}
*/
// Programmable light that's on but no mode, just return blank
if (light.lightType == LC_PROGRAMABLE && light.button->led->state == ON && light.currentValue == 0) {
return "";
}
if (light.currentValue < 0 || light.currentValue > LIGHT_COLOR_OPTIONS ){
return "";
}
if (_color_light_options[light.lightType][light.currentValue] == NULL) {
return "";
}
// Rename any modes depending on emulation type
if (protocol == ALLBUTTON) {
if (strcmp(_color_light_options[light.lightType][light.currentValue],"Afternoon Skies") == 0) {
return "Afternoon Sky";
}
}
return _color_light_options[light.lightType][light.currentValue];
}
// This should not be uses for getting current lightmode name since it doesn;t have full logic
const char *light_mode_name(clight_type type, int index, emulation_type protocol)
{
if (index < 0 || index > LIGHT_COLOR_OPTIONS ){
return "";
}
if (_color_light_options[type][index] == NULL) {
return "";
}
// Rename any modes depending on emulation type
if (protocol == ALLBUTTON) {
if (strcmp(_color_light_options[type][index],"Afternoon Skies") == 0) {
return "Afternoon Sky";
}
}
return _color_light_options[type][index];
}
bool isShowMode(const char *mode)
{
if (mode == NULL)
return false;
if (strcmp(mode, "Color Splash") == 0 ||
strcmp(mode, "Slow Splash") == 0 ||
strcmp(mode, "Fast Splash") == 0 ||
strcmp(mode, "Fat Tuesday") == 0 ||
strcmp(mode, "Disco Tech") == 0 ||
strcmp(mode, "Voodoo Lounge") == 0 ||
strcmp(mode, "Twilight") == 0 ||
strcmp(mode, "Tranquility") == 0 ||
strcmp(mode, "Gemstone") == 0 ||
strcmp(mode, "USA") == 0 ||
strcmp(mode, "Mardi Gras") == 0 ||
strcmp(mode, "Cool Cabaret") == 0 ||
strcmp(mode, "SAm") == 0 ||
strcmp(mode, "Party") == 0 ||
strcmp(mode, "Romance") == 0 ||
strcmp(mode, "Caribbean") == 0 ||
strcmp(mode, "American") == 0 ||
strcmp(mode, "Cal Sunset") == 0)
return true;
else
return false;
}
void set_currentlight_value(clight_detail *light, int index)
{
// Dimmer 2 has different values (range 1 to 100)
if (light->lightType == LC_DIMMER2) {
if (index < 0 || index > 100)
light->currentValue = 0;
else
light->currentValue = index;
} else {
// We want to leave the last color, so if 0 don't do anything, but set to 0 if bad value
if (index <= 0 || index > LIGHT_COLOR_OPTIONS) {
light->currentValue = 0;
} else if (index > 0 && index < LIGHT_COLOR_OPTIONS) {
light->currentValue = index;
//light->lastValue = index;
}
}
}
// Used for dynamic config JS
int build_color_lights_js(struct aqualinkdata *aqdata, char* buffer, int size)
{
memset(&buffer[0], 0, size);
int length = 0;
int i, j;
length += sprintf(buffer+length, "var _light_program = [];\n");
if ( _color_light_options[0][1] == NULL || strcmp(_color_light_options[0][1], "1") == 0) {
length += sprintf(buffer+length, "_light_program[0] = [];\n");
i=1;
} else {
i=0;
}
for (; i < NUMBER_LIGHT_COLOR_TYPES; i++) {
length += sprintf(buffer+length, "_light_program[%d] = [ ", i);
for (j=1; j < LIGHT_COLOR_OPTIONS; j++) { // Start a 1 since index 0 is blank
if (_color_light_options[i][j] != NULL)
length += sprintf(buffer+length, "\"%s%s\",", _color_light_options[i][j], (isShowMode(_color_light_options[i][j])?" - Show":"") );
}
buffer[--length] = '\0';
length += sprintf(buffer+length, "];\n");
}
return length;
}
int build_color_light_jsonarray(int index, char* buffer, int size)
{
memset(&buffer[0], 0, size);
int i;
int length=0;
for (i=0; i < LIGHT_COLOR_OPTIONS; i++) { // Start a 1 since index 0 is blank
if (_color_light_options[index][i] != NULL) {
length += sprintf(buffer+length, "\"%s\",", _color_light_options[index][i] );
}
}
buffer[--length] = '\0';
return length;
}

111
source/color_lights.h Normal file
View File

@ -0,0 +1,111 @@
#ifndef COLOR_LIGHTS_H_
#define COLOR_LIGHTS_H_
#include "aqualink.h"
#include "aq_programmer.h"
#define LIGHT_COLOR_NAME 16
#define LIGHT_COLOR_OPTIONS 19
//#define LIGHT_DIMER_OPTIONS 4
//#define LIGHT_COLOR_TYPES LC_DIMMER+1
// The status returned from RS Serial Adapter has this added as a base.
#define RSSD_COLOR_LIGHT_OFFSET 64
#define RSSD_DIMMER_LIGHT_OFFSET 128
//#define DIMMER_LIGHT_TYPE_INDEX 10
/*
// color light modes (Aqualink program, Jandy, Jandy LED, SAm/SAL, Color Logic, Intellibrite)
typedef enum clight_type {
LC_PROGRAMABLE=0,
LC_JANDY,
LC_JANDYLED,
LC_SAL,
LC_CLOGIG,
LC_INTELLIB
} clight_type;
*/
//const char *light_mode_name(clight_type type, int index);
const char *get_currentlight_mode_name(clight_detail light, emulation_type protocol);
const char *light_mode_name(clight_type type, int index, emulation_type protocol);
int build_color_lights_js(struct aqualinkdata *aqdata, char* buffer, int size);
int build_color_light_jsonarray(int index, char* buffer, int size);
void clear_aqualinkd_light_modes();
void set_currentlight_value(clight_detail *light, int index);
bool set_aqualinkd_light_mode_name(char *name, int index, bool isShow);
const char *get_aqualinkd_light_mode_name(int index, bool *isShow);
//char *_color_light_options_[LIGHT_COLOR_TYPES][LIGHT_COLOR_OPTIONS][LIGHT_COLOR_NAME];
#endif //COLOR_LIGHTS_H_
/*
Rev T.2 has the below
Jandy colors <- Same
Jandy LED <- Same
SAm/Sal <- Same
IntelliBrite <- Same
Hayw Univ Col <- Color Logic
*/
/*
Color Name Jandy Colors Jandy LED SAm/SAL Color Logic IntelliBrite dimmer
---------------------------------------------------------------------------------------------------------
Color Splash 11 8
Alpine White 1 1
Sky Blue 2 2
Cobalt Blue 3 3
Caribbean Blu 4 4
Spring Green 5 5
Emerald Green 6 6
Emerald Rose 7 7
Magenta 8 8
Garnet Red 9 9
Violet 10 10
Slow Splash 11
Fast Splash 12
USA!!! 13
Fat Tuesday 14
Disco Tech 15
White 1
Light Green 2
Green 3
Cyan 4
Blue 5
Lavender 6
Magenta 7
Light Magenta
Voodoo Lounge 1
Deep Blue Sea 2
Afternoon Skies 3
Afternoon Sky
Emerald 4
Sangria 5
Cloud White 6
Twilight 7
Tranquility 8
Gemstone 9
USA! 10
Mardi Gras 11
Cool Cabaret 12
SAm 1
Party 2
Romance 3
Caribbean 4
American 5
Cal Sunset 6
Royal 7
Blue 8
Green 9
Red 10
White 11
Magenta 12
25% 1
50% 2
75% 3
100% 4
*/

2512
source/config.c Normal file

File diff suppressed because it is too large Load Diff

326
source/config.h Normal file
View File

@ -0,0 +1,326 @@
#ifndef CONFIG_H_
#define CONFIG_H_
#include "utils.h"
#include "aq_serial.h"
#include "aqualink.h"
//#define DEFAULT_LOG_LEVEL 10
#define DEFAULT_LOG_LEVEL LOG_NOTICE
//#define DEFAULT_WEBPORT "6580"
//#define DEFAULT_WEBROOT "./"
#define DEFAULT_WEBPORT "80"
#define DEFAULT_WEBROOT "/var/www/aqualinkd/"
#define DEFAULT_SERIALPORT "/dev/ttyUSB0"
#define DEFAULT_DEVICE_ID "0x0a"
#define DEFAULT_MQTT_DZ_IN NULL // "domoticz/in"
#define DEFAULT_MQTT_DZ_OUT NULL // "domoticz/out"
#define DEFAULT_HASS_DISCOVER "homeassistant"
#define DEFAULT_MQTT_AQ_TP "aqualinkd"
#define DEFAULT_MQTT_SERVER NULL
#define DEFAULT_MQTT_USER NULL
#define DEFAULT_MQTT_PASSWD NULL
//#define DEFAULT_SWG_ZERO_IGNORE_COUNT 0
#define MQTT_ID_LEN 18 // 20 seems to kill mosquitto 1.6
// For aqconfig.read_RS485_devmask
#define READ_RS485_SWG (1 << 0) // 1 SWG
#define READ_RS485_JAN_PUMP (1 << 1) // 2 Jandy Pump
#define READ_RS485_PEN_PUMP (1 << 2) // 4 Pentair Pump
#define READ_RS485_JAN_JXI (1 << 3) // Jandy JX & LXi heater
#define READ_RS485_JAN_LX (1 << 4) // Jandy LX heater
#define READ_RS485_JAN_CHEM (1 << 5) // Jandy Chemical Feeder
#define READ_RS485_IAQUALNK (1 << 6) // Read iAqualink messages
#define READ_RS485_HEATPUMP (1 << 7) // Read HeatPump messages
#define READ_RS485_JLIGHT (1 << 8) // Read Jandy infinite watercolor light
#define MAX_RSSD_LOG_FILTERS 4
struct aqconfig
{
char *config_file;
char *serial_port;
unsigned int log_level;
char *socket_port;
char *web_directory;
unsigned char device_id;
unsigned char rssa_device_id;
int16_t paneltype_mask;
unsigned char extended_device_id;
unsigned char extended_device_id2;
bool extended_device_id_programming;
bool enable_iaqualink;
//bool enable_RS_device_value_print;
bool deamonize;
#ifndef AQ_MANAGER // Need to uncomment and clean up referances in future.
char *log_file;
#endif
char *mqtt_dz_sub_topic;
char *mqtt_dz_pub_topic;
char *mqtt_aq_topic;
char *mqtt_hass_discover_topic;
char *mqtt_server;
char *mqtt_user;
char *mqtt_passwd;
bool mqtt_hass_discover_use_mac;
char mqtt_ID[MQTT_ID_LEN+1];
int dzidx_air_temp;
int dzidx_pool_water_temp;
int dzidx_spa_water_temp;
int dzidx_swg_percent;
int dzidx_swg_ppm;
int dzidx_swg_status;
float light_programming_mode;
int light_programming_initial_on;
int light_programming_initial_off;
bool override_freeze_protect;
#ifdef AQ_PDA
bool pda_sleep_mode;
#endif
bool convert_mqtt_temp;
bool convert_dz_temp;
bool report_zero_spa_temp;
bool report_zero_pool_temp;
//bool read_all_devices;
//bool read_pentair_packets;
uint8_t read_RS485_devmask;
bool use_panel_aux_labels; // Took this option out of config
uint8_t force_device_devmask;
//int swg_zero_ignore; // This can be removed since this was due to VSP that's been fixed.
bool display_warnings_web;
bool log_protocol_packets; // Read & Write as packets
bool log_raw_bytes; // Read as bytes
unsigned char RSSD_LOG_filter[MAX_RSSD_LOG_FILTERS];
//bool log_raw_RS_bytes;
bool mqtt_timed_update;
bool sync_panel_time;
bool enable_scheduler;
int8_t schedule_event_mask; // Was int16_t, but no need
int sched_chk_pumpon_hour;
int sched_chk_pumpoff_hour;
char *sched_chk_booston_device;
bool ftdi_low_latency;
int frame_delay;
bool device_pre_state;
bool save_debug_log_masks;
bool save_light_programming_value;
int sensor_poll_time;
#ifdef AQ_NO_THREAD_NETSERVICE
int rs_poll_speed; // Need to remove
bool thread_netservices; // Need to remove
#endif
};
#ifndef CONFIG_C
extern struct aqconfig _aqconfig_;
#else
struct aqconfig _aqconfig_;
#endif
#define READ_RSDEV_SWG ((_aqconfig_.read_RS485_devmask & READ_RS485_SWG) == READ_RS485_SWG)
#define READ_RSDEV_ePUMP ((_aqconfig_.read_RS485_devmask & READ_RS485_JAN_PUMP) == READ_RS485_JAN_PUMP)
#define READ_RSDEV_vsfPUMP ((_aqconfig_.read_RS485_devmask & READ_RS485_PEN_PUMP) == READ_RS485_PEN_PUMP)
#define READ_RSDEV_JXI ((_aqconfig_.read_RS485_devmask & READ_RS485_JAN_JXI) == READ_RS485_JAN_JXI)
#define READ_RSDEV_LX ((_aqconfig_.read_RS485_devmask & READ_RS485_JAN_LX) == READ_RS485_JAN_LX)
#define READ_RSDEV_CHEM ((_aqconfig_.read_RS485_devmask & READ_RS485_JAN_CHEM) == READ_RS485_JAN_CHEM)
#define READ_RSDEV_iAQLNK ((_aqconfig_.read_RS485_devmask & READ_RS485_IAQUALNK) == READ_RS485_IAQUALNK)
#define READ_RSDEV_HPUMP ((_aqconfig_.read_RS485_devmask & READ_RS485_HEATPUMP) == READ_RS485_HEATPUMP)
#define READ_RSDEV_JLIGHT ((_aqconfig_.read_RS485_devmask & READ_RS485_JLIGHT) == READ_RS485_JLIGHT)
#define isPDA_IAQT (_aqconfig_.device_id == 0x33)
//#define isPDA ((_aqconfig_.paneltype_mask & RSP_PDA) == RSP_PDA)
#define FORCE_SWG_SP (1 << 0)
#define FORCE_POOLSPA_SP (1 << 1)
#define FORCE_FREEZEPROTECT_SP (1 << 2)
#define FORCE_CHEM_FEEDER (1 << 3)
#define FORCE_CHILLER (1 << 4)
#define ENABLE_SWG ((_aqconfig_.force_device_devmask & FORCE_SWG_SP) == FORCE_SWG_SP)
#define ENABLE_HEATERS ((_aqconfig_.force_device_devmask & FORCE_POOLSPA_SP) == FORCE_POOLSPA_SP)
#define ENABLE_FREEZEPROTECT ((_aqconfig_.force_device_devmask & FORCE_FREEZEPROTECT_SP) == FORCE_FREEZEPROTECT_SP)
#define ENABLE_CHEM_FEEDER ((_aqconfig_.force_device_devmask & FORCE_CHEM_FEEDER) == FORCE_CHEM_FEEDER)
#define ENABLE_CHILLER ((_aqconfig_.force_device_devmask & FORCE_CHILLER) == FORCE_CHILLER)
/*
#ifndef CONFIG_C
#ifdef AQUALINKD_C
extern struct aqconfig _aqconfig_;
#else
extern const struct aqconfig _aqconfig_;
#endif
#endif
*/
void init_parameters (struct aqconfig * parms);
//bool parse_config (struct aqconfig * parms, char *cfgfile);
//void readCfg (struct aqconfig *config_parameters, char *cfgFile);
//void readCfg (struct aqconfig *config_parameters, struct aqualinkdata *aqualink_data, char *cfgFile);
void read_config(struct aqualinkdata *aqdata, char *cfgFile);
void init_config();
bool writeCfg (struct aqualinkdata *aqdata);
bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value);
bool mac(char *buf, int len, bool useDelimiter);
char *cleanalloc(char *str);
char *ncleanalloc(char *str, int length);
const char *pumpType2String(pump_type ptype);
int save_config_js(const char* inBuf, int inSize, char* outBuf, int outSize, struct aqualinkdata *aqdata);
void check_print_config (struct aqualinkdata *aqdata);
typedef enum cfg_value_type{
CFG_STRING,
CFG_INT,
CFG_FLOAT,
CFG_HEX,
CFG_BOOL,
CFG_BITMASK,
CFG_SPECIAL
} cfg_value_type;
#define CFG_PERSISTANT (1 << 0) // Don't free memory, things referance the pointer
#define CFG_GRP_ADVANCED (1 << 1) // Show in group advanced
#define CFG_READONLY (1 << 2) // Don't show in UI, but do write to CFG file. (Maybe display in UI but no edit)
#define CFG_HIDE (1 << 3) // Don't show in any UI listing, don't write to CFG file.
//#define CFG_READONLY (1 << 4) // Don't show in UI, but do write to CFG file.
#define CFG_PASSWD_MASK (1 << 4) // Mask password with *****
#define CFG_FORCE_RESTART (1 << 5) // Force aqualinkd to restart
#define CFG_ALLOW_BLANK (1 << 6) // Allow blank entry
#define CFG_GREYED_OUT (1 << 7) // Greyout in UI, show but not editable
//#define CFG_ (1 << 3)
// Text to show when CFG_PASSWD_MASK is set
#define PASSWD_MASK_TEXT "********"
#define isMASKSET(mask, bit) ((mask & bit) == bit)
typedef struct cfgParam {
void *value_ptr;
void *default_value;
cfg_value_type value_type;
uint8_t config_mask;
char *name;
char *valid_values;
uint8_t mask;
//bool advanced;
} cfgParam;
#ifndef CONFIG_C
extern cfgParam _cfgParams[];
extern int _numCfgParams;
#else
cfgParam _cfgParams[100];
int _numCfgParams;
#endif // CONFIG_C
// Below are missed
//RSSD_LOG_filter
//debug_log_mask
#define CFG_V_BOOL "[\"Yes\", \"No\"]"
#define CFG_N_serial_port "serial_port"
#define CFG_N_log_level "log_level"
#define CFG_V_log_level "[\"DEBUG_SERIAL\", \"DEBUG\", \"INFO\", \"NOTICE\", \"WARNING\", \"ERROR\"]"
#define CFG_N_socket_port "socket_port"
#define CFG_N_web_directory "web_directory"
#define CFG_N_device_id "device_id"
#define CFG_V_device_id "[\"0x0a\", \"0x0b\", \"0x09\", \"0x08\", \"0x60\", \"0x33\", \"0xFF\"]"
#define CFG_V_device_id_RS "[\"0x0a\", \"0x0b\", \"0x09\", \"0x08\", \"0xFF\"]"
#define CFG_V_device_id_PDA "[\"0x60\", \"0x33\", \"0xFF\"]"
#define CFG_N_rssa_device_id "rssa_device_id"
#define CFG_V_rssa_device_id "[\"0x00\", \"0x48\"]"
#define CFG_N_RSSD_LOG_filter "RSSD_LOG_filter"
#define CFG_C_RSSD_LOG_filter 15
#define CFG_N_panel_type "panel_type"
#define CFG_N_extended_device_id "extended_device_id"
#define CFG_V_extended_device_id "[\"0x00\", \"0x30\", \"0x31\", \"0x32\", \"0x33\", \"0x40\", \"0x41\", \"0x42\", \"0x43\"]"
#define CFG_N_sync_panel_time "sync_panel_time"
#define CFG_N_extended_device_id_programming "extended_device_id_programming"
#define CFG_N_enable_iaqualink "enable_iaqualink"
#define CFG_N_log_file "log_file"
#define CFG_N_mqtt_aq_topic "mqtt_aq_topic"
#define CFG_N_mqtt_server "mqtt_address"
#define CFG_N_mqtt_user "mqtt_user"
#define CFG_N_mqtt_passwd "mqtt_passwd"
#define CFG_N_mqtt_hass_discover_topic "mqtt_ha_discover_topic"
#define CFG_N_mqtt_hass_discover_use_mac "mqtt_ha_discover_use_mac"
#define CFG_N_mqtt_timed_update "mqtt_timed_update"
#define CFG_N_mqtt_dz_sub_topic "mqtt_dz_sub_topic"
#define CFG_N_mqtt_dz_pub_topic "mqtt_dz_pub_topic"
#define CFG_N_dzidx_air_temp "dzidx_air_temp"
#define CFG_N_dzidx_pool_water_temp "dzidx_pool_water_temp"
#define CFG_N_dzidx_spa_water_temp "dzidx_spa_water_temp"
#define CFG_N_dzidx_swg_percent "dzidx_SWG_percent"
#define CFG_N_dzidx_swg_ppm "dzidx_SWG_PPM"
#define CFG_N_dzidx_swg_status "dzidx_SWG_Status"
#define CFG_N_light_programming_mode "light_programming_mode"
#define CFG_N_light_programming_initial_on "light_programming_initial_on"
#define CFG_N_light_programming_initial_off "light_programming_initial_off"
#define CFG_N_override_freeze_protect "override_freeze_protect"
#define CFG_N_pda_sleep_mode "pda_sleep_mode"
#define CFG_N_convert_mqtt_temp "mqtt_convert_temp_to_c"
#define CFG_N_convert_dz_temp "dz_convert_temp_to_c"
#define CFG_N_report_zero_spa_temp "report_zero_spa_temp"
#define CFG_N_report_zero_pool_temp "report_zero_pool_temp"
#define CFG_N_read_RS485_devmask "read_RS485_devmask"
#define CFG_N_use_panel_aux_labels "use_panel_aux_labels"
#define CFG_N_force_swg "force_swg"
#define CFG_N_force_ps_setpoints "force_ps_setpoints"
#define CFG_N_force_frzprotect_setpoints "force_frzprotect_setpoints"
#define CFG_N_force_chem_feeder "force_chem_feeder"
#define CFG_N_force_chiller "force_chiller"
#define CFG_N_display_warnings_web "display_warnings_web"
#define CFG_N_log_protocol_packets "log_protocol_packets"
#define CFG_N_device_pre_state "device_pre_state"
#define CFG_N_read_RS485_swg "read_RS485_swg"
#define CFG_N_read_RS485_ePump "read_RS485_ePump"
#define CFG_N_read_RS485_vsfPump "read_RS485_vsfPump"
#define CFG_N_read_RS485_JXi "read_RS485_JXi"
#define CFG_N_read_RS485_LX "read_RS485_LX"
#define CFG_N_read_RS485_Chem "read_RS485_Chem"
#define CFG_N_read_RS485_iAqualink "read_RS485_iAqualink"
#define CFG_N_read_RS485_HeatPump "read_RS485_HeatPump"
#define CFG_N_enable_scheduler "enable_scheduler"
#define CFG_N_event_check_poweron "event_poweron_check_pump"
#define CFG_N_event_check_freezeprotectoff "event_freezeprotectoff_check_pump"
#define CFG_N_event_check_boostoff "event_boostoff_check_pump"
#define CFG_N_event_check_pumpon_hour "event_check_pumpon_hour"
#define CFG_N_event_check_pumpoff_hour "event_check_pumpoff_hour"
#define CFG_N_event_check_usecron "event_check_use_scheduler_times"
#define CFG_N_event_check_booston_device "event_booston_check_device"
#define CFG_N_ftdi_low_latency "ftdi_low_latency"
#define CFG_N_rs485_frame_delay "rs485_frame_delay"
#define CFG_N_save_debug_log_masks "save_debug_log_masks"
#define CFG_N_save_light_programming_value "save_light_programming_value"
//#define CFG_V_UOM "[\"°C\", \"°F\", \"K\", \"Hz\", \"GHz\", \"Pa\", \"0x41\", \"hPa\", \"bar\", \"mbar\", \"inHg\", \"psi\", \"L\", \"mL\", \"m³\", \"ft³\", \"fl. oz.\", \"m³/h\", \"ft³/m\"]"
#endif

75
source/debug_timer.c Normal file
View File

@ -0,0 +1,75 @@
#ifdef AQ_TM_DEBUG
#include <stdio.h>
#include <string.h>
#include "debug_timer.h"
#include "utils.h"
#include "timespec_subtract.h"
#define NUM_DEBUG_TIMERS 10
static struct timespec _start_time[NUM_DEBUG_TIMERS];
static int _timeid=0;
void init_aqd_timer(int *timeid)
{
int i=0;
for(i=0; i < NUM_DEBUG_TIMERS; i++) {
_start_time[i].tv_sec = 0;
_start_time[i].tv_nsec = 0;
}
}
void start_aqd_timer(int *id)
{
bool maxloop=false;
// Just a sanity check, should be redundant.
if (_timeid >= NUM_DEBUG_TIMERS)
_timeid = 0;
while (_start_time[_timeid].tv_sec != 0 && _start_time[_timeid].tv_nsec != 0) {
if (++_timeid >= NUM_DEBUG_TIMERS) {
if (maxloop) { // Means we ranover loop twice
LOG(DBGT_LOG,LOG_ERR,"Ranout of debug timers\n");
*id = -1;
return;
} else {
_timeid = 0;
maxloop=true;
}
}
}
clock_gettime(CLOCK_REALTIME, &_start_time[_timeid++]);
*id = _timeid-1;
//return _timeid-1;
}
void clear_aqd_timer(int timeid) {
_start_time[timeid].tv_sec = 0;
_start_time[timeid].tv_nsec = 0;
}
void stop_aqd_timer(int timeid, int16_t from, char *message)
{
if (timeid < 0 || timeid >= NUM_DEBUG_TIMERS) {
LOG(from,LOG_ERR,"Invalid timeid '%d' for message '%s'\n", timeid, message);
return;
}
static struct timespec now;
static struct timespec elapsed;
clock_gettime(CLOCK_REALTIME, &now);
timespec_subtract(&elapsed, &now, &_start_time[timeid]);
//DBGT_LOG
//LOG(from,LOG_NOTICE, "%s %d.%02ld sec (%08ld ns)\n", message, elapsed.tv_sec, elapsed.tv_nsec / 1000000L, elapsed.tv_nsec);
LOG(DBGT_LOG,LOG_DEBUG, "%s %s %d.%02ld sec (%08ld ns)\n", logmask2name(from), message, elapsed.tv_sec, elapsed.tv_nsec / 1000000L, elapsed.tv_nsec);
// We've used it so free it.
clear_aqd_timer(timeid);
}
#endif

27
source/debug_timer.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef DEBUG_TIMER_H_
#define DEBUG_TIMER_H_
int timespec_subtract (struct timespec *result, const struct timespec *x, const struct timespec *y);
#ifdef AQ_TM_DEBUG
#include <time.h>
#include <stdint.h>
void init_aqd_timer();
//int timespec_subtract (struct timespec *result, const struct timespec *x, const struct timespec *y);
void stop_aqd_timer(int timeid, int16_t from, char *message);
void start_aqd_timer(int *timeid);
void clear_aqd_timer(int timeid);
#define DEBUG_TIMER_START(x) start_aqd_timer(x)
#define DEBUG_TIMER_STOP(x, y, z) stop_aqd_timer(x, y, z)
#define DEBUG_TIMER_CLEAR(x) clear_aqd_timer(x)
//#define DEBUG_TIMER_START1() int t; start_aqd_timer(&t)
//#define DEBUG_TIMER_STOP1(y, z) stop_aqd_timer(t, y, z)
#else
#define DEBUG_TIMER_START(x)
#define DEBUG_TIMER_STOP(x, y, z)
#define DEBUG_TIMER_CLEAR(x)
#endif
#endif //DEBUG_TIMER_H_

1350
source/devices_jandy.c Normal file

File diff suppressed because it is too large Load Diff

48
source/devices_jandy.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef AQUAPURE_H_
#define AQUAPURE_H_
#include <stdbool.h>
#include "aqualink.h"
bool processJandyPacket(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
bool processPacketToSWG(unsigned char *packet, int packet_length, struct aqualinkdata *aqdata/*, int swg_zero_ignore*/);
bool processPacketFromSWG(unsigned char *packet, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to);
bool processPacketToJandyPump(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
bool processPacketFromJandyPump(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to);
void processMissingAckPacketFromSWG(unsigned char destination, struct aqualinkdata *aqdata, const unsigned char previous_packet_to);
void processMissingAckPacketFromJandyPump(unsigned char destination, struct aqualinkdata *aqdata, const unsigned char previous_packet_to);
bool processPacketFromJandyJXiHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to );
bool processPacketToJandyJXiHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
bool processPacketFromJandyLXHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to );
bool processPacketToJandyLXHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
bool processPacketFromJandyChemFeeder(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to );
bool processPacketToJandyChemFeeder(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
bool processPacketToHeatPump(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
bool processPacketFromHeatPump(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to);
bool processPacketToJandyLight(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
bool processPacketFromJandyLight(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to);
void get_swg_status_mqtt(struct aqualinkdata *aqdata, char *message, int *status, int *dzalert);
aqledstate get_swg_led_state(struct aqualinkdata *aqdata);
bool changeSWGpercent(struct aqualinkdata *aqdata, int percent);
void setSWGpercent(struct aqualinkdata *aqdata, int percent);
void setSWGoff(struct aqualinkdata *aqdata);
void setSWGenabled(struct aqualinkdata *aqdata);
bool setSWGboost(struct aqualinkdata *aqdata, bool on);
void setSWGdeviceStatus(struct aqualinkdata *aqdata, emulation_type requester, unsigned char status);
void getJandyHeaterError(struct aqualinkdata *aqdata, char *message);
void getJandyHeaterErrorMQTT(struct aqualinkdata *aqdata, char *message);
int getPumpStatus(int pumpIndex, struct aqualinkdata *aqdata);
void processHeatPumpDisplayMessage(char *msg, struct aqualinkdata *aqdata);
#endif // AQUAPURE_H_

249
source/devices_pentair.c Normal file
View File

@ -0,0 +1,249 @@
/*
* Copyright (c) 2017 Shaun Feakes - All rights reserved
*
* You may use redistribute and/or modify this code under the terms of
* the GNU General Public License version 2 as published by the
* Free Software Foundation. For the terms of this license,
* see <http://www.gnu.org/licenses/>.
*
* You are free to use this software under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* https://github.com/sfeakes/aqualinkd
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "aqualink.h"
#include "aq_serial.h"
#include "devices_pentair.h"
#include "utils.h"
#include "packetLogger.h"
bool processPentairPacket(unsigned char *packet, int packet_length, struct aqualinkdata *aqdata)
{
bool changedAnything = false;
int i;
// Only log if we are pentair debug move and not serial (otherwise it'll print twice)
if (getLogLevel(DPEN_LOG) == LOG_DEBUG && getLogLevel(RSSD_LOG) < LOG_DEBUG ) {
char buff[1024];
beautifyPacket(buff, 1024, packet, packet_length, true);
LOG(DPEN_LOG,LOG_DEBUG, "%s", buff);
}
//ID's 96 to 111 = Pentair (or 0x60 to 0x6F)
// Need to find a better way to support pump index
//static int pumpIndex = 1;
// Status from pump
if ( packet[PEN_PKT_CMD] == PEN_CMD_STATUS && packet[PEN_PKT_FROM] >= PENTAIR_DEC_PUMP_MIN && packet[PEN_PKT_FROM] <= PENTAIR_DEC_PUMP_MAX ){
// We have Pentair Pump packet, let's see if it's configured.
//printf("PUMP\n");
for (i = 0; i < MAX_PUMPS; i++) {
if ( aqdata->pumps[i].prclType == PENTAIR && aqdata->pumps[i].pumpID == packet[PEN_PKT_FROM] ) {
// We found the pump.
LOG(DPEN_LOG, LOG_INFO, "Pentair Pump 0x%02hhx Status message = RPM %d | WATTS %d | GPM %d | Mode %d | DriveState %d | Status %d | PresureCurve %d\n",
packet[PEN_PKT_FROM],
(packet[PEN_HI_B_RPM] * 256) + packet[PEN_LO_B_RPM],
(packet[PEN_HI_B_WAT] * 256) + packet[PEN_LO_B_WAT],
packet[PEN_FLOW],
packet[PEN_MODE],
packet[PEN_DRIVE_STATE],
(packet[PEN_HI_B_STATUS] * 256) + packet[PEN_LO_B_STATUS],
packet[PEN_PPC]);
aqdata->pumps[i].rpm = (packet[PEN_HI_B_RPM] * 256) + packet[PEN_LO_B_RPM];
aqdata->pumps[i].watts = (packet[PEN_HI_B_WAT] * 256) + packet[PEN_LO_B_WAT];
aqdata->pumps[i].gpm = packet[PEN_FLOW];
aqdata->pumps[i].mode = packet[PEN_MODE];
//aqdata->pumps[i].driveState = packet[PEN_DRIVE_STATE];
aqdata->pumps[i].status = (packet[PEN_HI_B_STATUS] * 256) + packet[PEN_LO_B_STATUS];
aqdata->pumps[i].pressureCurve = packet[PEN_PPC];
// This is for RS485 mode only (no info from OneTouch or iAqualinkTouch)
if (!isONET_ENABLED && !isIAQT_ENABLED) {
if ( /*aqdata->pumps[i].mode > 0 ||*/ aqdata->pumps[i].rpm > 0 || aqdata->pumps[i].gpm > 0 || aqdata->pumps[i].watts > 0) {
aqdata->pumps[i].pStatus = PS_OK;
} else {
aqdata->pumps[i].pStatus = PS_OFF;
}
}
changedAnything = true;
break;
}
if (changedAnything != true) {
LOG(DPEN_LOG, LOG_NOTICE, "Pentair Pump found at ID 0x%02hhx values RPM %d | WATTS %d | PGM %d | Mode %d | DriveState %d | Status %d | PresureCurve %d\n",
packet[PEN_PKT_FROM],
(packet[PEN_HI_B_RPM] * 256) + packet[PEN_LO_B_RPM],
(packet[PEN_HI_B_WAT] * 256) + packet[PEN_LO_B_WAT],
packet[PEN_FLOW],
packet[PEN_MODE],
packet[PEN_DRIVE_STATE],
(packet[PEN_HI_B_STATUS] * 256) + packet[PEN_LO_B_STATUS],
packet[PEN_PPC]);
}
}
//
}
// Set RPM/GPM to pump
else if (packet[PEN_PKT_CMD] == PEN_CMD_SPEED && packet[PEN_PKT_DEST] >= PENTAIR_DEC_PUMP_MIN && packet[PEN_PKT_DEST] <= PENTAIR_DEC_PUMP_MAX) {
//(msg.extractPayloadByte(2) & 32) >> 5 === 0 ? 'RPM' : 'GPM';
bool isRPM = (packet[11] & 32) >> 5 == 0?true:false;
if (isRPM) {
LOG(DPEN_LOG, LOG_INFO,"Pentair Pump 0x%02hhx request to set RPM to %d\n",packet[PEN_PKT_DEST], (packet[11] * 256) + packet[12]);
} else {
LOG(DPEN_LOG, LOG_INFO,"Pentair Pump 0x%02hhx request to set GPM to %d\n",packet[PEN_PKT_DEST],packet[11]);
}
}
// Set power to pump
else if (packet[PEN_PKT_CMD] == PEN_CMD_POWER && packet[PEN_PKT_DEST] >= PENTAIR_DEC_PUMP_MIN && packet[PEN_PKT_DEST] <= PENTAIR_DEC_PUMP_MAX) {
if (packet[9] == 0x0A) {
LOG(DPEN_LOG, LOG_INFO,"Pentair Pump 0x%02hhx request set power ON\n",packet[PEN_PKT_DEST]);
} else {
LOG(DPEN_LOG, LOG_INFO,"Pentair Pump 0x%02hhx request set power OFF\n",packet[PEN_PKT_DEST]);
}
}
return changedAnything;
}
/*
VSP Pump Status.
Are you sure it is a VS pump because the flow rate byte(7)(PEN_FLOW) is only valid for VSF and VF pumps?
Mode 0=local control, 1=remote control
DriveState = no idea
Pressure Curve = see manual
Status = below
(packet[PEN_HI_B_STATUS] * 256) + packet[PEN_LO_B_STATUS];
Guess at the status would be.
0 ok ( ok for VSF, looks like off for VF / VS? )
1 ok ( ok for VF / VS?, not sure on VSF )
2 filter warning
4 Overcurrent condition
8 Priming
16 System blocked
32 General alarm
64 Overtemp condition
128 Power outage
256 Overcurrent condition 2
512 Overvoltage condition
1024 Unspecified Error
2048 Unspecified Error
4096 Unspecified Error
8192 Unspecified Error
16384 Unspecified Error
32768 Communication failure
// Below was pulled from another project. 0 doesn;t seem to be accurate.
// 0 is OK on VSF pump
[0, { name: 'off', desc: 'Off' }], // When the pump is disconnected or has no power then we simply report off as the status. This is not the recommended wiring
// for a VS/VF pump as is should be powered at all times. When it is, the status will always report a value > 0.
[1, { name: 'ok', desc: 'Ok' }], // Status is always reported when the pump is not wired to a relay regardless of whether it is on or not
// as is should be if this is a VS / VF pump. However if it is wired to a relay most often filter, the pump will report status
// 0 if it is not running. Essentially this is no error but it is not a status either.
[2, { name: 'filter', desc: 'Filter warning' }],
[3, { name: 'overcurrent', desc: 'Overcurrent condition' }],
[4, { name: 'priming', desc: 'Priming' }],
[5, { name: 'blocked', desc: 'System blocked' }],
[6, { name: 'general', desc: 'General alarm' }],
[7, { name: 'overtemp', desc: 'Overtemp condition' }],
[8, { name: 'power', dec: 'Power outage' }],
[9, { name: 'overcurrent2', desc: 'Overcurrent condition 2' }],
[10, { name: 'overvoltage', desc: 'Overvoltage condition' }],
[11, { name: 'error11', desc: 'Unspecified Error 11' }],
[12, { name: 'error12', desc: 'Unspecified Error 12' }],
[13, { name: 'error13', desc: 'Unspecified Error 13' }],
[14, { name: 'error14', desc: 'Unspecified Error 14' }],
[15, { name: 'error15', desc: 'Unspecified Error 15' }],
[16, { name: 'commfailure', desc: 'Communication failure' }]
*/
/*
Removed as iAqualink has a sleep mode, Keeping code to use as stub for other devices.
*/
#ifdef DO_NOT_COMPILE
bool processiAqualinkMsg(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata)
{
bool changedAnything = false;
static char lastmessage[AQ_MSGLONGLEN];
//static char message[AQ_MSGLONGLEN + 1];
static int pumpIndex = 1;
/*
Jandy ePumpTM DC,
Jandy ePumpTM AC,
IntelliFlo 1 VF,
IntelliFlo VS
Pump type are like // Not sure how to read this accuratly.
"Jandy ePUMP 1"
"Intelliflo VS 1"
RPM message always comes after the above, so maybe saving last string
then when see RPM go back to get pump number.
' RPM: 2950'
' Watts: 1028'
' GPM: 1028'
*/
if (packet_buffer[9] == 'R' && packet_buffer[10] == 'P' && packet_buffer[11] == 'M' && packet_buffer[12] == ':') {
pumpIndex = atoi((char *) &lastmessage[14]);
if ( pumpIndex < aqdata->num_pumps && pumpIndex < 0) {
pumpIndex = 1;
logMessage(LOG_ERR, "Can't find pump index for messsage '%.*s' in string '%.*s' using %d\n",AQ_MSGLEN, packet_buffer+4, AQ_MSGLEN, lastmessage, pumpIndex);
}
aqdata->pumps[pumpIndex-1].rpm = atoi((char *) &packet_buffer[13]);
logMessage(LOG_DEBUG, "Read message '%.*s' from iAqualink device\n",AQ_MSGLEN, packet_buffer+4);
changedAnything = true;
}
else if (packet_buffer[9] == 'G' && packet_buffer[10] == 'P' && packet_buffer[11] == 'H' && packet_buffer[12] == ':') {
aqdata->pumps[pumpIndex-1].gph = atoi((char *) &packet_buffer[13]);
logMessage(LOG_DEBUG, "Read message '%.*s' from iAqualink device\n",AQ_MSGLEN, packet_buffer+4);
changedAnything = true;
}
else if (packet_buffer[7] == 'W' && packet_buffer[8] == 'a' && packet_buffer[9] == 't' && packet_buffer[10] == 't' && packet_buffer[11] == 's' && packet_buffer[12] == ':') {
//printf("Punp %d, Watts = %d\n", pumpIndex, atoi((char *) &packet_buffer[13]));
aqdata->pumps[pumpIndex-1].watts = atoi((char *) &packet_buffer[13]);
logMessage(LOG_DEBUG, "Read message '%.*s' from iAqualink device\n",AQ_MSGLEN, packet_buffer+4);
changedAnything = true;
}
//printf("Message : '");
//fwrite(packet_buffer + 4, 1, packet_length-7, stdout);
//printf("'\n");
strncpy(lastmessage, (char *)&packet_buffer[4], packet_length-7);
return changedAnything;
}
#endif

9
source/devices_pentair.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef PEN_MESSAGES_H_
#define PEN_MESSAGES_H_
#include <stdbool.h>
bool processPentairPacket(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
#endif // PEN_MESSAGES_H_

253
source/dummy_device.c Normal file
View File

@ -0,0 +1,253 @@
/*
*
* Program to simulate devices to help debug messages.
* Not in release code / binary for AqualinkD
*
*/
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <fcntl.h>
#include <time.h>
// #include "serial_logger.h"
#include "aq_serial.h"
#include "utils.h"
#include "packetLogger.h"
#include "rs_msg_utils.h"
#define CONFIG_C // Make us look like config.c when we load config.h so we get globals.
#include "config.h"
#define SWG
//#define HEATPUMP
#ifdef SWG
unsigned char DEVICE_ID = 0x50;
#else
unsigned char DEVICE_ID = 0x70;
#endif
bool _keepRunning = true;
int _rs_fd;
void intHandler(int dummy)
{
_keepRunning = false;
LOG(SLOG_LOG, LOG_NOTICE, "Stopping!\n");
}
bool isAqualinkDStopping() {
return !_keepRunning;
}
void process_swg_packet(unsigned char *packet_buffer, const int packet_length);
void process_heatpump_packet(unsigned char *packet_buffer, const int packet_length);
int main(int argc, char *argv[])
{
int logLevel = LOG_INFO;
int packet_length;
unsigned char packet_buffer[AQ_MAXPKTLEN];
int blankReads = 0;
bool returnError = false;
if (argc < 2 || access(argv[1], F_OK) == -1)
{
fprintf(stderr, "ERROR, first param must be valid serial port, ie:-\n\t%s /dev/ttyUSB0\n\n", argv[0]);
return 1;
}
setLoggingPrms(logLevel, false, NULL);
LOG(SLOG_LOG, LOG_NOTICE, "Starting %s\n", basename(argv[0]));
// _rs_fd = init_serial_port(argv[1]);
_rs_fd = init_blocking_serial_port(argv[1]);
if (_rs_fd < 0)
{
LOG(SLOG_LOG, LOG_ERR, "Unable to open port: %s\n", argv[1]);
displayLastSystemError(argv[1]);
return -1;
}
signal(SIGINT, intHandler);
signal(SIGTERM, intHandler);
// Force all packets to be printed.
addDebugLogMask(RSSD_LOG);
_aqconfig_.RSSD_LOG_filter[0] = DEVICE_ID;
while (_keepRunning)
{
if (_rs_fd < 0)
{
LOG(SLOG_LOG, LOG_ERR, "ERROR, serial port disconnect\n");
_keepRunning = false;
}
packet_length = get_packet(_rs_fd, packet_buffer);
if (packet_length == AQSERR_READ)
{
// Unrecoverable read error. Force an attempt to reconnect.
LOG(SLOG_LOG, LOG_ERR, "ERROR, on serial port! Please check %s\n", argv[1]);
_keepRunning = false;
returnError = true;
}
else if (packet_length == AQSERR_TIMEOUT)
{
// Unrecoverable read error. Force an attempt to reconnect.
LOG(SLOG_LOG, LOG_ERR, "ERROR, Timeout on serial port, nothing read! Please check %s\n", argv[1]);
_keepRunning = false;
returnError = true;
}
else if (packet_length < 0)
{
// Error condition
if (packet_length == AQSERR_CHKSUM)
{
LOG(SLOG_LOG, LOG_WARNING, "Checksum error\n");
}
else if (packet_length == AQSERR_2LARGE)
{
LOG(SLOG_LOG, LOG_WARNING, "Packet too large error\n");
}
else if (packet_length == AQSERR_2SMALL)
{
LOG(SLOG_LOG, LOG_WARNING, "Packet too small error\n");
}
else
{
LOG(SLOG_LOG, LOG_WARNING, "Unknown error reading packet\n");
}
}
else if (packet_length == 0)
{
if (++blankReads > 10)
{
LOG(SLOG_LOG, LOG_ERR, "ERROR, too many blank reads! Please check %s\n", argv[1]);
_keepRunning = false;
returnError = true;
}
delay(1);
}
else if (packet_length > 0)
{
blankReads = 0;
//debuglogPacket(SLOG_LOG, packet_buffer, packet_length, true, true);
if (packet_buffer[PKT_DEST] == DEVICE_ID)
{
#ifdef SWG
process_swg_packet(packet_buffer, packet_length);
#else
process_heatpump_packet(packet_buffer, packet_length);
#endif
}
}
}
LOG(SLOG_LOG, LOG_NOTICE, "Stopping!\n");
close_serial_port(_rs_fd);
if (returnError)
return 1;
return 0;
}
/*
* Reply to message
*/
void process_heatpump_packet(unsigned char *packet_buffer, const int packet_length)
{
//LOG(SLOG_LOG, LOG_NOTICE, "Replying to packet 0x%02hhx!\n",packet_buffer[PKT_DEST]);
// reply to off 0x10|0x02|0x00|0x0d|0x40|0x00|0x00|0x5f|0x10|0x03|
static unsigned char hp_off[] = {0x00, 0x0d, 0x40, 0x00, 0x00};
static unsigned char hp_heat[] = {0x00, 0x0d, 0x48, 0x00, 0x00};
static unsigned char hp_cool[] = {0x00, 0x0d, 0x68, 0x00, 0x00};
if (packet_buffer[PKT_CMD] == CMD_PROBE)
{
send_ack(_rs_fd, 0x00);
LOG(SLOG_LOG, LOG_NOTICE, "Replied to Probe packet to 0x%02hhx with ACK\n",packet_buffer[PKT_DEST]);
}
else if (packet_buffer[3] == 0x0c)
{
if (packet_buffer[4] == 0x00)
{ // Off
send_jandy_command(_rs_fd, hp_off, 5);
LOG(SLOG_LOG, LOG_NOTICE, "Replied to OFF 0x%02hhx packet to 0x%02hhx with ACK\n",packet_buffer[4],packet_buffer[PKT_DEST]);
}
else if (packet_buffer[4] == 0x09) // Heat
{ // Enable
send_jandy_command(_rs_fd, hp_heat, 5);
LOG(SLOG_LOG, LOG_NOTICE, "Replied to HEAT 0x%02hhx packet to 0x%02hhx with ACK\n",packet_buffer[4],packet_buffer[PKT_DEST]);
}
else if (packet_buffer[4] == 0x29) // Cool
{ // Enable
send_jandy_command(_rs_fd, hp_cool, 5);
LOG(SLOG_LOG, LOG_NOTICE, "Replied to COOL 0x%02hhx packet to 0x%02hhx with ACK\n",packet_buffer[4],packet_buffer[PKT_DEST]);
}
else
{ // Enable
LOG(SLOG_LOG, LOG_ERR, "************* Unknown State Request 0x%02hhx *************",packet_buffer[4]);
LOG(SLOG_LOG, LOG_NOTICE, "NOT Replying to UNKNOWN 0x%02hhx packet to 0x%02hhx with ACK\n",packet_buffer[4],packet_buffer[PKT_DEST]);
//send_jandy_command(_rs_fd, hp_unknown, 5);
}
}
}
void process_swg_packet(unsigned char *packet_buffer, const int packet_length)
{
//(packet[3] = 0x16
//(packet[4] * 100) = PPM
static unsigned char swg_ack[] = {0x00,0x01,0x00,0x00};
static unsigned char swg_ppm[] = {0x00, 0x16, 0x1f, 0x00, 0x00, 0x00};
//static unsigned char swg_id[] = {0x00,0x03,0x00,0x49,0x6e,0x74,0x65,0x6c,0x6c,0x69,0x63,0x68,0x6c,0x6f,0x72,0x2d,0x2d,0x34,0x30};
//static unsigned char swg_id[] = {0x00,0x03,0x00,0x41,0x71,0x75,0x61,0x50,0x75,0x72,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
static unsigned char swg_id[] = {0x00,0x03,0x01,0x41,0x71,0x75,0x61,0x50,0x75,0x72,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
// 0x11 set SWG %
if (packet_buffer[PKT_CMD] == CMD_PROBE)
{
//send_ack(_rs_fd, 0x00);
send_jandy_command(_rs_fd, swg_ack, 4);
LOG(SLOG_LOG, LOG_NOTICE, "Replied to Probe packet to 0x%02hhx with ACK\n",packet_buffer[PKT_DEST]);
}
else if (packet_buffer[3] == 0x11)
{
//int percent = (int)packet_buffer[4];
send_jandy_command(_rs_fd, swg_ppm, 6);
LOG(SLOG_LOG, LOG_NOTICE, "Received %%=%d, Replied PPM=%d\n",(int)packet_buffer[4],swg_ppm[2] * 100);
}
else if (packet_buffer[3] == 0x14)
{
send_jandy_command(_rs_fd, swg_id, 19);
LOG(SLOG_LOG, LOG_NOTICE, "Receive ID request, replied\n");
}
else
{
LOG(SLOG_LOG, LOG_ERR, "************* Unknown Request 0x%02hhx *************",packet_buffer[3]);
}
}

188
source/dummy_reader.c Normal file
View File

@ -0,0 +1,188 @@
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <fcntl.h>
#include <time.h>
// #include "serial_logger.h"
#include "aq_serial.h"
#include "utils.h"
#include "packetLogger.h"
#include "rs_msg_utils.h"
#define CONFIG_C // Make us look like config.c when we load config.h so we get globals.
#include "config.h"
bool _keepRunning = true;
int _rs_fd;
void intHandler(int dummy)
{
_keepRunning = false;
LOG(SLOG_LOG, LOG_NOTICE, "Stopping!\n");
}
bool isAqualinkDStopping() {
return !_keepRunning;
}
int get_bytes(FILE *fd, unsigned char* buffer)
{
int packet_length = 0;
char line[4000];
char hex[6];
int i;
bool foundHex=false;
/*
const char *hex_string = "0xFF";
unsigned char value;
// Use strtoul to convert the hex string to an unsigned long
// The third argument, 16, specifies the base (hexadecimal)
unsigned long temp_value = strtoul(hex_string, NULL, 16);
// Cast the unsigned long to unsigned char
value = (unsigned char)temp_value;
*/
if ( fgets ( line, sizeof line, fd ) != NULL ) /* read a line */
{
packet_length=0;
for (i=0; i < strlen(line); i++)
{
if (line[i] == 'H' && line[i+1] == 'E' && line[i+2] == 'X' && line[i+3] == ':') {
foundHex=true;
i=i+4;
}
if (line[i] == '0' && line[i+1] == 'x' && foundHex) {
break;
}
if (i<=1 && line[i] == '0' && line[i+1] == 'x') {
//printf(stdout,"Line starting with hex\n");
break;
}
}
if (i == strlen(line)) {
//printf( "PLAYBACK No binary\n");
return 0;
} else {
//printf(" Transposed from index %d=",i);
//printf( "%s",line);
LOG(SLOG_LOG, LOG_DEBUG, "Read bytes %s", line);
}
for (i=i; i < strlen(line); i=i+5)
{
strncpy(hex, &line[i], 4);
hex[5] = '\0';
buffer[packet_length] = (int)strtol(hex, NULL, 16);
packet_length++;
}
packet_length--;
//printf("End Char = 0x%02hhx\n",buffer[packet_length-1]);
buffer[packet_length] = '\0';
return packet_length;
}
return -1;
}
bool createBinaryFile(char *dest, char *source) {
int size = 0;
unsigned char buffer[4000];
FILE *sfp = fopen ( source, "r" );
if ( sfp == NULL )
{
perror ( source ); /* why didn't the file open? */
return FALSE;
}
FILE *dfp = fopen ( dest, "wb" );
if ( dfp == NULL )
{
perror ( dest ); /* why didn't the file open? */
fclose(sfp);
return FALSE;
}
while (size != -1) {
size = get_bytes(sfp, buffer);
if (size > 0) {
//printf("GOT %d bytes\n",size);
//fputs(buffer, dfp);
fwrite(&buffer, sizeof(unsigned char), size, dfp);
//char pbuf[256];
//beautifyPacket(pbuf, 256, buffer, size, TRUE);
//printf("%s\n",pbuf);
}
//printf("-----------------\n");
//if (buffer[3] == 0x72) {
// print72pck(buffer, size);
//process_iAqualinkStatusPacket(buffer, size);
//}
}
fclose(sfp);
fclose(dfp);
return TRUE;
}
int main(int argc, char *argv[])
{
int logLevel = LOG_DEBUG;
int fp;
int packet_length;
unsigned char packet_buffer[AQ_MAXPKTLEN+1];
if (argc < 2 || access(argv[1], F_OK) == -1)
{
fprintf(stderr, "ERROR, first param must be valid filename\n");
return 1;
}
setLoggingPrms(logLevel, false, NULL);
LOG(SLOG_LOG, LOG_INFO, "Start reading %s\n", basename(argv[1]));
createBinaryFile("/tmp/tmp.tmp", argv[1]);
if ((fp = open("/tmp/tmp.tmp", O_RDONLY)) == -1)
{
fprintf(stderr, "Cannot open %s\n",argv[1]);
return 1;
}
//packet_length = get_packet(fp, packet_buffer);
while ((packet_length = get_packet(fp, packet_buffer)) != 0) {
LOG(SLOG_LOG, LOG_INFO, "Read %d bytes\n", packet_length);
}
/*
if (packet_length == 0)
{
LOG(SLOG_LOG, LOG_ERR, "Error Read, %d\n", packet_length);
close(fp);
return 1;
} else {
LOG(SLOG_LOG, LOG_INFO, "Read %d bytes\n", packet_length);
}
*/
LOG(SLOG_LOG, LOG_INFO, "Stopping!\n");
close(fp);
return 0;
}

337
source/epump.h Normal file
View File

@ -0,0 +1,337 @@
/*
Nothing seems to change these, need real pump to test
0x10|0x02|0x7a|0x44|0x00|0x58|0x1b|0x43|0x10|0x03|
0x10|0x02|0x7a|0x44|0x00|0x58|0x1b|0x43|0x10|0x03|
0x10|0x02|0x7a|0x44|0x00|0x58|0x1b|0x43|0x10|0x03|
0x10|0x02|0x7a|0x41|0xcd|0x10|0x03|
0x10|0x02|0x7a|0x44|0x00|0x58|0x1b|0x43|0x10|0x03|
0x10|0x02|0x7a|0x41|0xcd|0x10|0x03|
0x10|0x02|0x7a|0x44|0x00|0x58|0x1b|0x43|0x10|0x03|
0x10|0x02|0x7a|0x41|0xcd|0x10|0x03|
----------------
Set & Sataus WATTS. Looks like send to ePump type 0x45, return type 0xf1|0x45
JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x1d|0x05|0x9d|0x10|0x03|
JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f| 69| 0| 5| 29| 5|0x9d|0x10|0x03|
Jun-24-23 08:30:53 AM Debug: PDA: PDA Menu Line 4 = WATTS: 1309
Type 0x1f (command 0x45 or 69)
Watts = 5 * (256) + 29 = 1309 or Byte 8 * 265 + Byte 7
----------------
Set & Sataus RPM. Looks like send to ePump type 0x44, return type 0xf1|0x44
JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0xe8|0x35|0x00|0x92|0x10|0x03|
JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f| 68| 0| 232| 53| 0|0x92|0x10|0x03|
PDA Menu Line 3 = SET TO 3450 RPM
JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x50|0x2d|0x00|0xf2|0x10|0x03|
JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f| 68| 0| 80| 45| 0|0xf2|0x10|0x03|
PDA: PDA Menu Line 3 = SET TO 2900 RPM
JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f| 68| 0| 96| 39| 0|0xfc|0x10|0x03|
PDA: PDA Menu Line 3 = SET TO 2520 RPM
Type 0x1F and cmd 0x44 is RPM = 39 * (256) + 96 / 4 = 2520 or Byte 7 * 265 + Byte 6 / 4
Other commands on 0x1f are 67 & 68 (0x43 )
Some form of ping for being alive
Jun-23-23 17:41:00 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:41:00 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 09:01:29 AM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 09:01:29 AM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x04|0x00|0x7f|0x10|0x03|
Jun-23-23 09:01:35 AM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x42' | HEX: 0x10|0x02|0x78|0x42|0xcc|0x10|0x03|
Jun-23-23 09:01:35 AM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x42|0x00|0x55|0x10|0x03|
Jun-23-23 17:40:57 PM Debug: PDA: PDA Menu Line 2 = JANDY ePUMP 1
Jun-23-23 17:40:57 PM Debug: PDA: PDA Menu Line 3 = RPM: 2520
Jun-23-23 17:40:57 PM Debug: PDA: PDA Menu Line 4 = WATTS: 856
Jun-23-23 17:41:00 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:41:00 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:41:00 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:41:00 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:41:06 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:41:06 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:41:06 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:41:06 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:41:06 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:41:06 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:41:11 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:41:11 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:41:11 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:41:11 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:41:17 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:41:17 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x57|0x03|0xd5|0x10|0x03|
Jun-23-23 17:41:17 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:41:17 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:41:17 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:41:17 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:41:17 PM Debug: PDA: PDA Menu Line 2 = JANDY ePUMP 1
Jun-23-23 17:41:17 PM Debug: PDA: PDA Menu Line 3 = RPM: 2520
Jun-23-23 17:41:17 PM Debug: PDA: PDA Menu Line 4 = WATTS: 855
Jun-23-23 17:41:22 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:41:22 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:41:22 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:41:22 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:41:28 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:41:28 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:41:28 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:41:28 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:41:28 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:41:28 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:41:33 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:41:33 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:41:33 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:41:33 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:41:38 PM Debug: PDA: PDA Menu Line 2 = JANDY ePUMP 1
Jun-23-23 17:41:38 PM Debug: PDA: PDA Menu Line 3 = RPM: 2520
Jun-23-23 17:41:38 PM Debug: PDA: PDA Menu Line 4 = WATTS: 856
Jun-23-23 17:41:39 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:41:39 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:41:39 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:41:39 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:41:39 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:41:39 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:41:44 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:41:44 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:41:44 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:41:44 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:41:49 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:41:49 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:41:50 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:41:50 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:41:50 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:41:50 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:41:55 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:41:55 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:41:55 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:41:55 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:41:59 PM Debug: PDA: PDA Menu Line 2 = JANDY ePUMP 1
Jun-23-23 17:41:59 PM Debug: PDA: PDA Menu Line 3 = RPM: 2520
Jun-23-23 17:41:59 PM Debug: PDA: PDA Menu Line 4 = WATTS: 856
Jun-23-23 17:42:01 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:42:01 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:42:01 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:42:01 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:42:01 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:42:01 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:42:06 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:42:06 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:42:06 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:42:06 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:42:12 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:42:12 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:42:12 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:42:12 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:42:12 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:42:12 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:42:17 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:42:17 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:42:17 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:42:17 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:42:20 PM Debug: PDA: PDA Menu Line 2 = JANDY ePUMP 1
Jun-23-23 17:42:20 PM Debug: PDA: PDA Menu Line 3 = RPM: 2520
Jun-23-23 17:42:20 PM Debug: PDA: PDA Menu Line 4 = WATTS: 856
Jun-23-23 17:42:23 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:42:23 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x57|0x03|0xd5|0x10|0x03|
Jun-23-23 17:42:23 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:42:23 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:42:23 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:42:23 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:42:28 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:42:28 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:42:28 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:42:28 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:42:33 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:42:33 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:42:33 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:42:33 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:42:33 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:42:34 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:42:39 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:42:39 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:42:39 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:42:39 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:42:41 PM Debug: PDA: PDA Menu Line 2 = JANDY ePUMP 1
Jun-23-23 17:42:41 PM Debug: PDA: PDA Menu Line 3 = RPM: 2520
Jun-23-23 17:42:41 PM Debug: PDA: PDA Menu Line 4 = WATTS: 856
Jun-23-23 17:42:45 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:42:45 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x57|0x03|0xd5|0x10|0x03|
Jun-23-23 17:42:45 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:42:45 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:42:45 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:42:45 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:42:49 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:42:49 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:42:49 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:42:49 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:42:56 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:42:56 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x57|0x03|0xd5|0x10|0x03|
Jun-23-23 17:42:56 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:42:56 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:42:56 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:42:56 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:43:00 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:43:00 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:43:00 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:43:00 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:43:02 PM Debug: PDA: PDA Menu Line 2 = JANDY ePUMP 1
Jun-23-23 17:43:02 PM Debug: PDA: PDA Menu Line 3 = RPM: 2520
Jun-23-23 17:43:02 PM Debug: PDA: PDA Menu Line 4 = WATTS: 855
Jun-23-23 17:43:07 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:43:07 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:43:07 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:43:07 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:43:07 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:43:07 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:43:11 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:43:11 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:43:12 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:43:12 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:43:17 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:43:18 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:43:18 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:43:18 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:43:18 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:43:18 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:43:23 PM Debug: PDA: PDA Menu Line 2 = JANDY ePUMP 1
Jun-23-23 17:43:23 PM Debug: PDA: PDA Menu Line 3 = RPM: 2520
Jun-23-23 17:43:23 PM Debug: PDA: PDA Menu Line 4 = WATTS: 856
Jun-23-23 17:43:23 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:43:23 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:43:23 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:43:23 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:43:28 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:43:28 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:43:28 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:43:29 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:43:29 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:43:29 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:43:34 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:43:34 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:43:34 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:43:34 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:43:39 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:43:39 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:43:39 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:43:39 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:43:39 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:43:40 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:43:44 PM Debug: PDA: PDA Menu Line 2 = JANDY ePUMP 1
Jun-23-23 17:43:44 PM Debug: PDA: PDA Menu Line 3 = RPM: 2520
Jun-23-23 17:43:44 PM Debug: PDA: PDA Menu Line 4 = WATTS: 856
Jun-23-23 17:43:44 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:43:44 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:43:44 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:43:45 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:43:50 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:43:50 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:43:50 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:43:51 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:43:51 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:43:51 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:43:55 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:43:55 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:43:56 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:43:56 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:44:01 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:44:01 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:44:01 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:44:02 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:44:02 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:44:02 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:44:05 PM Debug: PDA: PDA Menu Line 2 = JANDY ePUMP 1
Jun-23-23 17:44:05 PM Debug: PDA: PDA Menu Line 3 = RPM: 2520
Jun-23-23 17:44:05 PM Debug: PDA: PDA Menu Line 4 = WATTS: 856
Jun-23-23 17:44:06 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:44:06 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:44:06 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:44:06 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:44:12 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:44:12 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:44:12 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:44:12 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:44:12 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:44:13 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:44:17 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:44:18 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:44:18 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:44:18 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:44:23 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:44:23 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:44:23 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:44:24 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:44:24 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:44:24 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:44:26 PM Debug: PDA: PDA Menu Line 2 = JANDY ePUMP 1
Jun-23-23 17:44:26 PM Debug: PDA: PDA Menu Line 3 = RPM: 2520
Jun-23-23 17:44:26 PM Debug: PDA: PDA Menu Line 4 = WATTS: 856
Jun-23-23 17:44:28 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:44:29 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:44:29 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:44:29 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:44:34 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:44:35 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:44:35 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:44:35 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:44:35 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:44:35 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:44:39 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:44:39 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:44:40 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:44:40 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:44:45 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:44:45 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:44:45 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:44:45 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:44:45 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:44:45 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:44:47 PM Debug: PDA: PDA Menu Line 2 = JANDY ePUMP 1
Jun-23-23 17:44:47 PM Debug: PDA: PDA Menu Line 3 = RPM: 2520
Jun-23-23 17:44:47 PM Debug: PDA: PDA Menu Line 4 = WATTS: 856
Jun-23-23 17:44:50 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:44:50 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:44:51 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:44:51 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
Jun-23-23 17:44:56 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x45' | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Jun-23-23 17:44:57 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x45|0x00|0x05|0x58|0x03|0xd6|0x10|0x03|
Jun-23-23 17:44:57 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x43' | HEX: 0x10|0x02|0x78|0x43|0xcd|0x10|0x03|
Jun-23-23 17:44:57 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x43|0x0b|0x00|0x00|0x00|0x7f|0x10|0x03|
Jun-23-23 17:44:57 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x46' | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Jun-23-23 17:44:57 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x20' | HEX: 0x10|0x02|0x00|0x20|0x46|0x00|0x00|0x03|0x30|0x30|0x32|0x30|0x00|0x00|0x3d|0x10|0x03|
Jun-23-23 17:45:01 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x44' | HEX: 0x10|0x02|0x78|0x44|0x00|0x60|0x27|0x55|0x10|0x03|
Jun-23-23 17:45:02 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Unknown '0x1f' | HEX: 0x10|0x02|0x00|0x1f|0x44|0x00|0x60|0x27|0x00|0xfc|0x10|0x03|
Jun-23-23 17:45:02 PM Debug: JandyDvce: To ePump: Read To 0x78 of type Unknown '0x41' | HEX: 0x10|0x02|0x78|0x41|0xcb|0x10|0x03|
Jun-23-23 17:45:02 PM Debug: JandyDvce: From ePump: Read To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x41|0x00|0x54|0x10|0x03|
*/

758
source/hassio.c Normal file
View File

@ -0,0 +1,758 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mongoose.h"
#include "aqualink.h"
#include "net_services.h"
#include "json_messages.h"
#include "aq_mqtt.h"
#include "rs_msg_utils.h"
#include "config.h"
#include "color_lights.h"
#include "version.h"
// NSF To change this to support multiple AqualinkD instances.
// In the identifyiers . " \"name\": \"AqualinkD\"," SHOULD BE "AqualinkD-(Instance 1)"
// each hardcode unique_id is "aqualinkd_%s" should be "aqualinkd-(Instance 1)_%s)"
// Obviously change the aqualinkd/ MQTT
// NSF Need to find a better way, this is not thread safe, so don;t want to expost it from net_services.h.
void send_mqtt(struct mg_connection *nc, const char *toppic, const char *message);
#define HASS_DEVICE "\"identifiers\": " \
"[\"" AQUALINKD_SHORT_NAME "\"]," \
" \"sw_version\": \"" AQUALINKD_VERSION "\"," \
" \"model\": \"" AQUALINKD_NAME "\"," \
" \"name\": \"AqualinkD\"," \
" \"manufacturer\": \"" AQUALINKD_SHORT_NAME "\"," \
"%s" \
" \"suggested_area\": \"pool\""
#define HASS_AVAILABILITY "\"payload_available\" : \"1\"," \
"\"payload_not_available\" : \"0\"," \
"\"topic\": \"%s/" MQTT_LWM_TOPIC "\""
const char *HASSIO_CLIMATE_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"climate\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"%s\","
"\"modes\": [\"off\", \"heat\"],"
"\"send_if_off\": true,"
"\"initial\": 36,"
"\"power_command_topic\": \"%s/%s/set\","
"\"payload_on\": \"1\","
"\"payload_off\": \"0\","
"\"current_temperature_topic\": \"%s/%s\","
"\"mode_command_topic\": \"%s/%s/set\","
"\"mode_state_topic\": \"%s/%s/enabled\","
"\"mode_state_template\": \"{%% set values = { '0':'off', '1':'heat'} %%}{{ values[value] if value in values.keys() else 'off' }}\","
"\"temperature_command_topic\": \"%s/%s/setpoint/set\","
"\"temperature_state_topic\": \"%s/%s/setpoint\","
"\"action_template\": \"{%% set values = { '0':'off', '1':'heating'} %%}{{ values[value] if value in values.keys() else 'off' }}\","
"\"action_topic\": \"%s/%s\","
/*"\"temperature_state_template\": \"{{ value_json }}\","*/
"\"min_temp\": %.2f,"
"\"max_temp\": %.2f,"
"\"temperature_unit\": \"%s\""
//"%s"
"}";
const char *HASSIO_FREEZE_PROTECT_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"climate\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"Freeze Protect\","
"\"modes\": [\"off\", \"auto\"],"
"\"send_if_off\": true,"
"\"initial\": 34,"
"\"payload_on\": \"1\","
"\"payload_off\": \"0\","
"\"current_temperature_topic\": \"%s/%s\","
"\"mode_state_topic\": \"%s/%s\","
"\"mode_state_template\": \"{%% set values = { '0':'off', '1':'auto'} %%}{{ values[value] if value in values.keys() else 'off' }}\","
"\"temperature_command_topic\": \"%s/%s/setpoint/set\","
"\"temperature_state_topic\": \"%s/%s/setpoint\","
"\"action_template\": \"{%% set values = { '0':'off', '1':'cooling'} %%}{{ values[value] if value in values.keys() else 'off' }}\","
"\"action_topic\": \"%s/%s\","
/*"\"temperature_state_template\": \"{{ value_json }}\""*/
"\"min_temp\": %0.2f,"
"\"max_temp\": %0.2f,"
"\"temperature_unit\": \"%s\""
//"%s"
"}";
/*
const char *HASSIO_CONVERT_CLIMATE_TOF = "\"temperature_state_template\": \"{{ (value | float(0) * 1.8 + 32 + 0.5) | int }}\","
"\"current_temperature_template\": \"{{ (value | float(0) * 1.8 + 32 + 0.5 ) | int }}\","
"\"temperature_command_template\": \"{{ ((value | float(0) -32 ) / 1.8 + 0.5) | int }}\"";
const char *HASSIO_NO_CONVERT_CLIMATE = "\"temperature_state_template\": \"{{ value_json }}\"";
*/
const char *HASSIO_SWG_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"humidifier\","
"\"device_class\": \"humidifier\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"Salt Water Generator\","
"\"state_topic\": \"%s/%s\","
"\"state_template\": \"{%% set values = { '0':'off', '2':'on'} %%}{{ values[value] if value in values.keys() else 'off' }}\","
"\"command_topic\": \"%s/%s/set\","
"\"current_humidity_topic\": \"%s/%s\","
"\"target_humidity_command_topic\": \"%s/%s/set\","
"\"target_humidity_state_topic\": \"%s/%s\","
"\"payload_on\": \"2\","
"\"payload_off\": \"0\","
"\"min_humidity\":0,"
"\"max_humidity\":100,"
"\"optimistic\": false"
"}";
const char *HASSIO_CHILLER_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"climate\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"Chiller\","
"\"modes\": [\"off\", \"cool\"],"
"\"send_if_off\": true,"
"\"initial\": 34,"
"\"power_command_topic\": \"%s/%s/set\"," // add
"\"payload_on\": \"2\","
"\"payload_off\": \"0\","
"\"current_temperature_topic\": \"%s/%s\","
"\"mode_command_topic\": \"%s/%s/set\"," // add
"\"mode_state_topic\": \"%s/%s\","
"\"mode_state_template\": \"{%% set values = { '0':'off', '2':'cool'} %%}{{ values[value] if value in values.keys() else 'off' }}\","
"\"temperature_command_topic\": \"%s/%s/setpoint/set\","
"\"temperature_state_topic\": \"%s/%s/setpoint\","
"\"action_template\": \"{%% set values = { '0':'off', '2':'cooling'} %%}{{ values[value] if value in values.keys() else 'off' }}\","
"\"action_topic\": \"%s/%s\","
/*"\"temperature_state_template\": \"{{ value_json }}\""*/
"\"min_temp\": %0.2f,"
"\"max_temp\": %0.2f,"
"\"temperature_unit\": \"%s\""
//"%s"
"}";
// Use Fan for VSP
// Need to change the max / min. These do NOT lomit the slider in hassio, only the MQTT limits.
// So the 0-100% should be 600-3450 RPM and 15-130 GPM (ie 1% would = 600 & 0%=off)
// (value-600) / (3450-600) * 100
// (value) / 100 * (3450-600) + 600
const char *HASSIO_VSP_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"fan\","
"\"unique_id\": \"aqualinkd_%s_%s\"," // filter_pump, RPM|GPM
"\"name\": \"%s Speed\"," // filter_pump
"\"state_topic\": \"%s/%s\"," // aqualinkd,filter_pump
"\"command_topic\": \"%s/%s/set\"," // aqualinkd,filter_pump
"\"json_attributes_topic\": \"%s/%s/delay\"," // aqualinkd,filter_pump
"\"json_attributes_template\": \"{{ {'delay': value|int} | tojson }}\","
"\"payload_on\": \"1\","
"\"payload_off\": \"0\","
"\"percentage_command_topic\": \"%s/%s/%s/set\"," // aqualinkd,filter_pump , RPM|GPM
"\"percentage_state_topic\": \"%s/%s/%s\"," // aqualinkd,filter_pump , RPM|GPM
//"\"percentage_value_template\": \"{%% if value | float(0) > %d %%} {{ (((value | float(0) - %d) / %d) * 100) | int }}{%% else %%} 1{%% endif %%}\"," // min,min,(max-min)
//"\"percentage_command_template\": \"{{ ((value | float(0) / 100) * %d) + %d | int }}\"," // (3450-130), 600
"\"speed_range_max\": 100,"
"\"speed_range_min\": 1" // 18|12 600rpm|15gpm
"}";
const char *HASSIO_DIMMER_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"light\","
"\"unique_id\": \"aqualinkd_%s\"," // Aux_5
"\"name\": \"%s\"," // Dimmer_name
"\"state_topic\": \"%s/%s\"," // aqualinkd,Aux_5
"\"command_topic\": \"%s/%s/set\"," // aqualinkd,Aux_5
"\"json_attributes_topic\": \"%s/%s/delay\"," // aqualinkd,Aux_5
"\"json_attributes_template\": \"{{ {'delay': value|int} | tojson }}\","
"\"payload_on\": \"1\","
"\"payload_off\": \"0\","
"\"brightness_command_topic\": \"%s/%s%s/set\"," // aqualinkd,Aux_5,/brightness
"\"brightness_state_topic\": \"%s/%s%s\"," // aqualinkd/Aux_5,/brightness
"\"brightness_scale\": 100"
"}";
const char *HASSIO_SELECTOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"select\","
"\"unique_id\": \"aqualinkd_%s_selector\"," // Aux_5
"\"name\": \"%s program\"," // light name
"\"state_topic\": \"%s/%s/program/name\"," // aqualinkd,Aux_5
"\"state_template\": \"{{ this.attributes.options(value) }}\","
"\"command_topic\": \"%s/%s/program/set\"," // aqualinkd,Aux_5
"\"options\": [ %s ]," // "Off", "Voodoo Lounge", "Deep Blue Sea"
"\"command_template\": \"{{ this.attributes.options.index(value) }}\","
"\"icon\": \"%s\""
"}";
// Need to add timer attributes to the switches, once figure out how to use in homeassistant
// ie aqualinkd/Filter_Pump/timer/duration
const char *HASSIO_SWITCH_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"switch\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"%s\","
"\"state_topic\": \"%s/%s\","
"\"command_topic\": \"%s/%s/set\","
"\"json_attributes_topic\": \"%s/%s/delay\","
"\"json_attributes_topic\": \"%s/%s/delay\","
"\"json_attributes_template\": \"{{ {'delay': value|int} | tojson }}\","
"\"payload_on\": \"1\","
"\"payload_off\": \"0\","
"\"icon\": \"%s\""
"}";
const char *HASSIO_TEMP_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"state_class\": \"measurement\","
"\"unique_id\": \"aqualinkd_%s_temp\","
"\"name\": \"%s Temp\","
"\"state_topic\": \"%s/%s\","
"\"value_template\": \"{{ value_json }}\","
"\"unit_of_measurement\": \"%s\","
"\"device_class\": \"temperature\","
"\"icon\": \"%s\""
"}";
const char *HASSIO_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"state_class\": \"measurement\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"%s\","
"\"state_topic\": \"%s/%s\","
"\"value_template\": \"{{ value_json }}\","
"\"unit_of_measurement\": \"%s\","
"\"icon\": \"%s\""
"}";
const char *HASSIO_SERVICE_MODE_ENUM_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"%s\","
"\"state_topic\": \"%s/%s\","
"\"device_class\": \"enum\","
"\"options\": [\"auto\",\"service\",\"timeout\"],"
"\"value_template\": \"{%% set values = { '0':'auto', '1':'service', '2':'timeout'} %%}{{ values[value] if value in values.keys() }}\","
"\"icon\": \"%s\""
"}";
const char *HASSIO_ONOFF_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"%s\","
"\"state_topic\": \"%s/%s\","
"\"payload_on\": \"1\","
"\"payload_off\": \"0\","
"\"value_template\": \"{%% set values = { '0':'off', '1':'on'} %%}{{ values[value] if value in values.keys() else 'off' }}\","
"\"icon\": \"%s\""
"}";
const char *HASSIO_PUMP_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"state_class\": \"measurement\","
"\"unique_id\": \"aqualinkd_%s%d_%s\","
"\"name\": \"%s %s %s\","
"\"state_topic\": \"%s/%s%s\","
"\"value_template\": \"{{ value_json }}\","
"\"unit_of_measurement\": \"%s\","
"\"icon\": \"mdi:pump\""
"}";
const char *HASSIO_BATTERY_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"binary_sensor\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"%s\","
"\"state_topic\": \"%s/%s\","
"\"payload_on\": \"0\","
"\"payload_off\": \"1\","
"\"device_class\": \"battery\""
"}";
// Same as above but no UOM
const char *HASSIO_PUMP_SENSOR_DISCOVER2 = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"state_class\": \"measurement\","
"\"unique_id\": \"aqualinkd_%s%d_%s\","
"\"name\": \"%s %s %s\","
"\"state_topic\": \"%s/%s%s\","
"\"value_template\": \"{{ value_json }}\","
"\"icon\": \"mdi:pump\""
"}";
const char *HASS_PUMP_MODE_TEMPLATE = "\"{% set values = { '0':'local control', '1':'remote controled'} %}{{ values[value] if value in values.keys() else 'unknown' }}\"";
const char *HASS_PUMP_STATUS_TEMPLATE = "\"{% set values = { "
"'-4':'Error',"
"'-3':'Offline',"
"'-2':'Priming',"
"'-1':'Off',"
"'0':'On',"
"'1':'Ok', "
"'2':'filter warning', "
"'4':'Overcurrent condition', "
"'8':'Priming', "
"'16':'System blocked', "
"'32':'General alarm', "
"'64':'Overtemp condition', "
"'128':'Power outage',"
"'256':'Overcurrent condition 2',"
"'512':'Overvoltage condition'} %}"
"{{ values[value] if value in values.keys() else 'Unspecified Error' }}\"";
const char *HASSIO_PUMP_TEXT_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"unique_id\": \"aqualinkd_%s%d_%s\","
"\"name\": \"%s %s %s\","
"\"state_topic\": \"%s/%s%s\","
"\"value_template\": %s,"
"\"icon\": \"mdi:pump\""
"}";
/*
Below doesn;t work (int and string values). Maybe try text sensor and add RPM/GPM to number
Or add seperate text sensor. (this would be better options, that way you can see priming AND rpm)
"value_template": "{% set values = { '-1':'priming', '-2':'offline', '-3':'error'} %}{{ values[value] if value in values.keys() else value }}",
*/
const char *HASSIO_TEXT_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"%s\","
"\"state_topic\": \"%s/%s\","
"\"icon\": \"mdi:card-text\""
"}";
const char *HASSIO_SWG_TEXT_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"unique_id\": \"%s\","
"\"name\": \"%s\","
"\"state_topic\": \"%s/%s\","
"\"payload_on\": \"0\","
"\"payload_off\": \"255\","
"\"value_template\": \"{%% set values = { '0':'Generating',"
"'1':'No flow', "
"'2':'low salt', "
"'4':'high salt', "
"'8':'clean cell', "
"'9':'turning off', "
"'16':'high current', "
"'32':'low volts', "
"'64':'low temp', "
"'128':'Check PCB',"
"'253':'General Fault',"
"'254':'Unknown',"
"'255':'off'} %%}"
"{{ values[value] if value in values.keys() else 'off' }}\","
"\"icon\": \"mdi:card-text\""
"}";
void publish_mqtt_hassio_discover(struct aqualinkdata *aqdata, struct mg_connection *nc)
{
if (_aqconfig_.mqtt_hass_discover_topic == NULL)
return;
int i;
char msg[JSON_STATUS_SIZE];
char topic[250];
char idbuf[128];
char connections[128];
if (_aqconfig_.mqtt_hass_discover_use_mac) {
char macaddress[20];
mac(macaddress, 20, true);
sprintf(connections, "\"connections\": [[\"mac\", \"%s\"]],", macaddress);
} else {
connections[0] = '\0';
}
LOG(NET_LOG,LOG_INFO, "MQTT: Publishing discover messages to '%s'\n", _aqconfig_.mqtt_hass_discover_topic);
for (i=0; i < aqdata->total_buttons; i++)
{
if (strcmp("NONE",aqdata->aqbuttons[i].label) != 0 ) {
// Heaters
if ( (strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0 && (ENABLE_HEATERS || aqdata->pool_htr_set_point != TEMP_UNKNOWN)) ||
(strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name)==0 && (ENABLE_HEATERS || aqdata->spa_htr_set_point != TEMP_UNKNOWN)) ) {
sprintf(msg,HASSIO_CLIMATE_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,(strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0)?POOL_TEMP_TOPIC:SPA_TEMP_TOPIC,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
//(_aqconfig_.convert_mqtt_temp?HASSIO_CONVERT_CLIMATE_TOF:HASSIO_NO_CONVERT_CLIMATE));
//(_aqconfig_.convert_mqtt_temp?degFtoC(36):36.00),
//(_aqconfig_.convert_mqtt_temp?degFtoC(104):104.00),
(_aqconfig_.convert_mqtt_temp?(float)HEATER_MIN_C:(float)HEATER_MIN_F),
(_aqconfig_.convert_mqtt_temp?(float)HEATER_MAX_C:(float)HEATER_MAX_F),
(_aqconfig_.convert_mqtt_temp?"C":"F"));
sprintf(topic, "%s/climate/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, aqdata->aqbuttons[i].name);
send_mqtt(nc, topic, msg);
} else if ( isPLIGHT(aqdata->aqbuttons[i].special_mask) && ((clight_detail *)aqdata->aqbuttons[i].special_mask_ptr)->lightType == LC_DIMMER2 ) {
// Dimmer
sprintf(msg,HASSIO_DIMMER_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,LIGHT_DIMMER_VALUE_TOPIC,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,LIGHT_DIMMER_VALUE_TOPIC);
sprintf(topic, "%s/light/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, aqdata->aqbuttons[i].name);
send_mqtt(nc, topic, msg);
} else if ( isPLIGHT(aqdata->aqbuttons[i].special_mask) ) {
// Color Lights & Dimmer as selector switch
// Build the
char buf[512];
build_color_light_jsonarray(((clight_detail *)aqdata->aqbuttons[i].special_mask_ptr)->lightType, buf, 512 );
sprintf(msg,HASSIO_SELECTOR_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
buf,
"mdi:lightbulb");
sprintf(topic, "%s/select/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, aqdata->aqbuttons[i].name);
send_mqtt(nc, topic, msg);
// Duplicate normal switch as we want a duplicate
sprintf(msg, HASSIO_SWITCH_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
"mdi:lightbulb");
sprintf(topic, "%s/switch/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, aqdata->aqbuttons[i].name);
send_mqtt(nc, topic, msg);
} else {
// Switches
//sprintf(msg,"{\"type\": \"switch\",\"unique_id\": \"%s\",\"name\": \"%s\",\"state_topic\": \"aqualinkd/%s\",\"command_topic\": \"aqualinkd/%s/set\",\"json_attributes_topic\": \"aqualinkd/%s/delay\",\"json_attributes_topic\": \"aqualinkd/%s/delay\",\"json_attributes_template\": \"{{ {'delay': value|int} | tojson }}\",\"payload_on\": \"1\",\"payload_off\": \"0\",\"qos\": 1,\"retain\": false}" ,
sprintf(msg, HASSIO_SWITCH_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
"mdi:toggle-switch-variant");
sprintf(topic, "%s/switch/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, aqdata->aqbuttons[i].name);
send_mqtt(nc, topic, msg);
}
}
}
// Freezeprotect
if ( ENABLE_FREEZEPROTECT || (aqdata->frz_protect_set_point != TEMP_UNKNOWN && aqdata->air_temp != TEMP_UNKNOWN) ) {
sprintf(msg, HASSIO_FREEZE_PROTECT_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
FREEZE_PROTECT,
_aqconfig_.mqtt_aq_topic,AIR_TEMP_TOPIC,
_aqconfig_.mqtt_aq_topic,FREEZE_PROTECT_ENABELED,
_aqconfig_.mqtt_aq_topic,FREEZE_PROTECT,
_aqconfig_.mqtt_aq_topic,FREEZE_PROTECT,
_aqconfig_.mqtt_aq_topic,FREEZE_PROTECT,
//(_aqconfig_.convert_mqtt_temp?HASSIO_CONVERT_CLIMATE_TOF:HASSIO_NO_CONVERT_CLIMATE));
//(_aqconfig_.convert_mqtt_temp?degFtoC(34):34.00),
//(_aqconfig_.convert_mqtt_temp?degFtoC(42):42.00),
(_aqconfig_.convert_mqtt_temp?(float)FREEZE_PT_MIN_C:(float)FREEZE_PT_MIN_F),
(_aqconfig_.convert_mqtt_temp?(float)FREEZE_PT_MAX_C:(float)FREEZE_PT_MAX_F),
(_aqconfig_.convert_mqtt_temp?"C":"F"));
sprintf(topic, "%s/climate/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, FREEZE_PROTECT);
send_mqtt(nc, topic, msg);
}
//if (ENABLE_CHILLER || (aqdata->chiller_set_point != TEMP_UNKNOWN && aqdata->chiller_state != LED_S_UNKNOWN) ) {
if ( (ENABLE_CHILLER || aqdata->chiller_set_point != TEMP_UNKNOWN ) && (aqdata->chiller_button != NULL) ) {
// USe freeze protect for the moment.
sprintf(msg, HASSIO_CHILLER_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
CHILLER,
_aqconfig_.mqtt_aq_topic,CHILLER,
_aqconfig_.mqtt_aq_topic,POOL_TEMP_TOPIC,
_aqconfig_.mqtt_aq_topic,CHILLER,
_aqconfig_.mqtt_aq_topic,CHILLER_ENABELED,
_aqconfig_.mqtt_aq_topic,CHILLER,
_aqconfig_.mqtt_aq_topic,CHILLER,
_aqconfig_.mqtt_aq_topic,CHILLER,
//(_aqconfig_.convert_mqtt_temp?HASSIO_CONVERT_CLIMATE_TOF:HASSIO_NO_CONVERT_CLIMATE));
//(_aqconfig_.convert_mqtt_temp?degFtoC(34):34.00),
//(_aqconfig_.convert_mqtt_temp?degFtoC(104):104.00),
(_aqconfig_.convert_mqtt_temp?(float)CHILLER_MIN_C:(float)CHILLER_MIN_F),
(_aqconfig_.convert_mqtt_temp?(float)CHILLER_MAX_C:(float)CHILLER_MAX_F),
(_aqconfig_.convert_mqtt_temp?"C":"F"));
sprintf(topic, "%s/climate/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, CHILLER);
send_mqtt(nc, topic, msg);
}
// SWG
if ( ENABLE_SWG || aqdata->swg_percent != TEMP_UNKNOWN ) {
sprintf(msg, HASSIO_SWG_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
SWG_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_PERCENT_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_PERCENT_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_PERCENT_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_PERCENT_TOPIC
);
sprintf(topic, "%s/humidifier/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, SWG_TOPIC);
send_mqtt(nc, topic, msg);
rsm_char_replace(idbuf, SWG_BOOST_TOPIC, "/", "_");
sprintf(msg, HASSIO_SWITCH_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
idbuf,
"SWG Boost",
_aqconfig_.mqtt_aq_topic,SWG_BOOST_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_BOOST_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_BOOST_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_BOOST_TOPIC,
"mdi:toggle-switch-variant");
sprintf(topic, "%s/switch/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, idbuf);
send_mqtt(nc, topic, msg);
rsm_char_replace(idbuf, SWG_PERCENT_TOPIC, "/", "_");
sprintf(msg, HASSIO_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,idbuf,"SWG Percent",_aqconfig_.mqtt_aq_topic,SWG_PERCENT_TOPIC, "%", "mdi:water-outline");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, idbuf);
send_mqtt(nc, topic, msg);
rsm_char_replace(idbuf, SWG_PPM_TOPIC, "/", "_");
sprintf(msg, HASSIO_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,idbuf,"SWG PPM",_aqconfig_.mqtt_aq_topic,SWG_PPM_TOPIC, "ppm", "mdi:water-outline");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, idbuf);
send_mqtt(nc, topic, msg);
rsm_char_replace(idbuf, SWG_EXTENDED_TOPIC, "/", "_");
sprintf(msg, HASSIO_SWG_TEXT_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,idbuf,"SWG Msg",_aqconfig_.mqtt_aq_topic,SWG_EXTENDED_TOPIC);
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, idbuf);
send_mqtt(nc, topic, msg);
}
// Temperatures
sprintf(msg, HASSIO_TEMP_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,"Pool","Pool",_aqconfig_.mqtt_aq_topic,POOL_TEMP_TOPIC,(_aqconfig_.convert_mqtt_temp?"°C":"°F"),"mdi:water-thermometer");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Pool");
send_mqtt(nc, topic, msg);
sprintf(msg, HASSIO_TEMP_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,"Spa","Spa",_aqconfig_.mqtt_aq_topic,SPA_TEMP_TOPIC,(_aqconfig_.convert_mqtt_temp?"°C":"°F"),"mdi:water-thermometer");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Spa");
send_mqtt(nc, topic, msg);
sprintf(msg, HASSIO_TEMP_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,"Air","Air",_aqconfig_.mqtt_aq_topic,AIR_TEMP_TOPIC,(_aqconfig_.convert_mqtt_temp?"°C":"°F"),"mdi:thermometer");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Air");
send_mqtt(nc, topic, msg);
// VSP Pumps
for (i=0; i < aqdata->num_pumps; i++) {
char units[] = "Speed";
// Create a FAN for pump against the button it' assigned to
// In the future maybe change this to the pump# or change the sensors to button???
sprintf(msg, HASSIO_VSP_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
aqdata->pumps[i].button->name,units,
aqdata->pumps[i].button->label,
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name,
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name,
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name,
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name,units,
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name,units);
sprintf(topic, "%s/fan/aqualinkd/aqualinkd_%s_%s/config", _aqconfig_.mqtt_hass_discover_topic, aqdata->pumps[i].button->name, units);
send_mqtt(nc, topic, msg);
// Create sensors for each pump, against it's pump number
int pn=i+1;
if (aqdata->pumps[i].pumpType==VFPUMP || aqdata->pumps[i].pumpType==VSPUMP) {
// We have GPM info
sprintf(msg, HASSIO_PUMP_SENSOR_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
"Pump",pn,"GPM",
aqdata->pumps[i].button->label,(rsm_strncasestr(aqdata->pumps[i].button->label,"pump",strlen(aqdata->pumps[i].button->label))!=NULL)?"":"Pump","GPM",
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name ,PUMP_GPM_TOPIC,
"GPM");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s%d_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Pump",pn,"GPM");
send_mqtt(nc, topic, msg);
if (READ_RSDEV_vsfPUMP ) {
// All Pentair hame some other info we gather.
sprintf(msg, HASSIO_PUMP_SENSOR_DISCOVER2,
connections,
_aqconfig_.mqtt_aq_topic,
"Pump",pn,"PPC",
aqdata->pumps[i].button->label,(rsm_strncasestr(aqdata->pumps[i].button->label,"pump",strlen(aqdata->pumps[i].button->label))!=NULL)?"":"Pump","Presure Curve",
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name ,PUMP_PPC_TOPIC);
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s%d_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Pump",pn,"PPC");
send_mqtt(nc, topic, msg);
/*
sprintf(msg, HASSIO_PUMP_SENSOR_DISCOVER2,
_aqconfig_.mqtt_aq_topic,
"Pump",pn,"Mode",
aqdata->pumps[i].button->label,(rsm_strncasestr(aqdata->pumps[i].button->label,"pump",strlen(aqdata->pumps[i].button->label))!=NULL)?"":"Pump","Mode",
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name ,PUMP_MODE_TOPIC);
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s%d_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Pump",pn,"Mode");
send_mqtt(nc, topic, msg);
*/
sprintf(msg, HASSIO_PUMP_TEXT_SENSOR_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
"Pump",pn,"Mode",
aqdata->pumps[i].button->label,(rsm_strncasestr(aqdata->pumps[i].button->label,"pump",strlen(aqdata->pumps[i].button->label))!=NULL)?"":"Pump","Mode",
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name ,PUMP_MODE_TOPIC,
HASS_PUMP_MODE_TEMPLATE);
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s%d_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Pump",pn,"Mode");
send_mqtt(nc, topic, msg);
}
}
sprintf(msg, HASSIO_PUMP_TEXT_SENSOR_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
"Pump",pn,"Status",
aqdata->pumps[i].button->label,(rsm_strncasestr(aqdata->pumps[i].button->label,"pump",strlen(aqdata->pumps[i].button->label))!=NULL)?"":"Pump","Status",
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name ,PUMP_STATUS_TOPIC,
HASS_PUMP_STATUS_TEMPLATE);
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s%d_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Pump",pn,"Status");
send_mqtt(nc, topic, msg);
// All pumps have the below.
sprintf(msg, HASSIO_PUMP_SENSOR_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
"Pump",pn,"RPM",
aqdata->pumps[i].button->label,(rsm_strncasestr(aqdata->pumps[i].button->label,"pump",strlen(aqdata->pumps[i].button->label))!=NULL)?"":"Pump","RPM",
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name ,PUMP_RPM_TOPIC,
"RPM");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s%d_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Pump",pn,"RPM");
send_mqtt(nc, topic, msg);
sprintf(msg, HASSIO_PUMP_SENSOR_DISCOVER,
connections,
_aqconfig_.mqtt_aq_topic,
"Pump",pn,"Watts",
aqdata->pumps[i].button->label,(rsm_strncasestr(aqdata->pumps[i].button->label,"pump",strlen(aqdata->pumps[i].button->label))!=NULL)?"":"Pump","Watts",
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name ,PUMP_WATTS_TOPIC,
"Watts");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s%d_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Pump",pn,"Watts");
send_mqtt(nc, topic, msg);
}
// Chem feeder (ph/orp)
if (ENABLE_CHEM_FEEDER || aqdata->ph != TEMP_UNKNOWN) {
rsm_char_replace(idbuf, CHEM_PH_TOPIC, "/", "_");
sprintf(msg, HASSIO_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,idbuf,"Water Chemistry pH",_aqconfig_.mqtt_aq_topic,CHEM_PH_TOPIC, "pH", "mdi:water-outline");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, idbuf);
send_mqtt(nc, topic, msg);
}
if (ENABLE_CHEM_FEEDER || aqdata->orp != TEMP_UNKNOWN) {
rsm_char_replace(idbuf, CHEM_ORP_TOPIC, "/", "_");
sprintf(msg, HASSIO_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,idbuf,"Water Chemistry ORP",_aqconfig_.mqtt_aq_topic,CHEM_ORP_TOPIC, "orp", "mdi:water-outline");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, idbuf);
send_mqtt(nc, topic, msg);
}
// Misc stuff
sprintf(msg, HASSIO_SERVICE_MODE_ENUM_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,SERVICE_MODE_TOPIC,"Service Mode",_aqconfig_.mqtt_aq_topic,SERVICE_MODE_TOPIC, "mdi:account-wrench");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, SERVICE_MODE_TOPIC);
send_mqtt(nc, topic, msg);
/* // Leave below if we decide to go back to a text box
sprintf(msg, HASSIO_TEXT_DISCOVER,DISPLAY_MSG_TOPIC,"Display Messages",_aqconfig_.mqtt_aq_topic,DISPLAY_MSG_TOPIC);
sprintf(topic, "%s/text/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, DISPLAY_MSG_TOPIC);
*/
// It actually works better posting this to sensor and not text.
sprintf(msg, HASSIO_TEXT_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,DISPLAY_MSG_TOPIC,"Display Msg",_aqconfig_.mqtt_aq_topic,DISPLAY_MSG_TOPIC);
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, DISPLAY_MSG_TOPIC);
send_mqtt(nc, topic, msg);
sprintf(msg, HASSIO_BATTERY_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,BATTERY_STATE,BATTERY_STATE,_aqconfig_.mqtt_aq_topic,BATTERY_STATE);
sprintf(topic, "%s/binary_sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic,BATTERY_STATE);
send_mqtt(nc, topic, msg);
for (i=0; i < aqdata->num_sensors; i++) {
//sprintf(idbuf, "%s_%s","sensor",aqdata->sensors[i].label);
//sprintf(topic, "%s/%s",SENSOR_TOPIC,aqdata->sensors[i].label);
sprintf(topic, "%s/%s",SENSOR_TOPIC,aqdata->sensors[i].ID);
rsm_char_replace(idbuf, topic, "/", "_");
//sprintf(msg, HASSIO_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,idbuf,aqdata->sensors[i].label,_aqconfig_.mqtt_aq_topic,topic, "°C", "mdi:thermometer");
// Use HASSIO_TEMP_SENSOR_DISCOVER over HASSIO_SENSOR_DISCOVER since it has device class temperature and HA will convert automatically.
//sprintf(msg, HASSIO_TEMP_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,idbuf,aqdata->sensors[i].label,_aqconfig_.mqtt_aq_topic,topic, "°C", "mdi:thermometer");
sprintf(msg, HASSIO_TEMP_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,idbuf,aqdata->sensors[i].label,_aqconfig_.mqtt_aq_topic,topic, "°C", "mdi:thermometer");
if (aqdata->sensors[i].uom != NULL) {
if (isUomTemperature(aqdata->sensors[i].uom) ) {
sprintf(msg, HASSIO_TEMP_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,idbuf,aqdata->sensors[i].label,_aqconfig_.mqtt_aq_topic,topic, aqdata->sensors[i].uom, "mdi:thermometer");
} else {
sprintf(msg, HASSIO_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,idbuf,aqdata->sensors[i].label,_aqconfig_.mqtt_aq_topic,topic, aqdata->sensors[i].uom, "mdi:thermometer-water");
}
} else {
sprintf(msg, HASSIO_TEMP_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,idbuf,aqdata->sensors[i].label,_aqconfig_.mqtt_aq_topic,topic, "°C", "mdi:thermometer");
}
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, idbuf);
send_mqtt(nc, topic, msg);
}
}

8
source/hassio.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef HASSIO_H_
#define HASSIO_H_
void publish_mqtt_hassio_discover(struct aqualinkdata *aqdata, struct mg_connection *nc);
#endif // HASSIO_H_

1654
source/iaqtouch.c Normal file

File diff suppressed because it is too large Load Diff

1701
source/iaqtouch.h Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
#ifndef IAQ_TOUCH_PROGRAMMER_H_
#define IAQ_TOUCH_PROGRAMMER_H_
unsigned char pop_iaqt_cmd(unsigned char receive_type);
void set_iaq_cansend(bool cansend);
bool iaqt_queue_cmd(unsigned char cmd);
void *set_aqualink_iaqtouch_pump_rpm( void *ptr );
void *set_aqualink_iaqtouch_vsp_assignments( void *ptr );
void *get_aqualink_iaqtouch_setpoints( void *ptr );
void *get_aqualink_iaqtouch_freezeprotect( void *ptr );
void *get_aqualink_iaqtouch_aux_labels( void *ptr );
void *set_aqualink_iaqtouch_swg_percent( void *ptr );
void *set_aqualink_iaqtouch_swg_boost( void *ptr );
void *set_aqualink_iaqtouch_spa_heater_temp( void *ptr );
void *set_aqualink_iaqtouch_pool_heater_temp( void *ptr );
void *set_aqualink_iaqtouch_chiller_temp( void *ptr );
void *set_aqualink_iaqtouch_time( void *ptr );
void *set_aqualink_iaqtouch_pump_vs_program( void *ptr );
void *set_aqualink_iaqtouch_light_colormode( void *ptr );
void *set_aqualink_iaqtouch_device_on_off( void *ptr ); // For PDA only
void *set_aqualink_iaqtouch_onetouch_on_off( void *ptr );
int ref_iaqt_control_cmd(unsigned char **cmd);
void rem_iaqt_control_cmd(unsigned char *cmd);
#endif // IAQ_TOUCH_PROGRAMMER_H_

723
source/iaqualink.c Normal file
View File

@ -0,0 +1,723 @@
/*
* Copyright (c) 2017 Shaun Feakes - All rights reserved
*
* You may use redistribute and/or modify this code under the terms of
* the GNU General Public License version 2 as published by the
* Free Software Foundation. For the terms of this license,
* see <http://www.gnu.org/licenses/>.
*
* You are free to use this software under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* https://github.com/sfeakes/aqualinkd
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "aq_serial.h"
#include "aqualink.h"
#include "iaqualink.h"
#include "packetLogger.h"
#include "aq_serial.h"
#include "serialadapter.h"
#include "rs_msg_utils.h"
#define IAQUA_QLEN 20
typedef struct iaqulnkcmd
{
unsigned char command[20];
int length;
} iaqualink_cmd;
iaqualink_cmd _iqaua_queue[IAQUA_QLEN];
unsigned char _std_cmd[2];
int _iaqua_q_length = 0;
bool _aqua_last_cmdfrom_queue = false;
unsigned char _cmd_readyCommand[] = {0x3f, 0x20};
unsigned char _fullcmd[] = {0x00, 0x24, 0x73, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
bool push_iaqualink_cmd(unsigned char *cmd, int length) {
if (_iaqua_q_length >= IAQUA_QLEN ) {
LOG(IAQL_LOG,LOG_ERR, "Queue overflow, last command ignored!\n");
return false;
}
memcpy(_iqaua_queue[_iaqua_q_length].command, cmd, length);
_iqaua_queue[_iaqua_q_length].length = length;
_iaqua_q_length++;
LOG(IAQL_LOG, LOG_INFO, "Queue cmd, size %d, queu length=%d\n",length, _iaqua_q_length);
//LOG(IAQL_LOG,LOG_DEBUG, "Added to message queue, position %d 0x%02hhx|0x%02hhx|0x%02hhx|0x%02hhx\n",_rssa_q_length-1,_rssa_queue[_rssa_q_length-1][0],_rssa_queue[_rssa_q_length-1][1],_rssa_queue[_rssa_q_length-1][2],_rssa_queue[_rssa_q_length-1][3]);
return true;
}
//unsigned char *get_iaqualink_cmd(unsigned char source_message_type, unsigned char *dest_message, int *len) {
int get_iaqualink_cmd(unsigned char source_message_type, unsigned char **dest_message) {
//LOG(IAQL_LOG, LOG_INFO, "Caculating cmd\n");
_aqua_last_cmdfrom_queue = false;
_std_cmd[0] = source_message_type;
_std_cmd[1] = 0x00;
*dest_message = _std_cmd;
int len = 2;
if (source_message_type == 0x73 && _iaqua_q_length > 0)
{ // Send big/long message
if ( _iqaua_queue[0].length >= 19 ) {
*dest_message = _iqaua_queue[0].command;
len = _iqaua_queue[0].length;
_aqua_last_cmdfrom_queue = true;
} else {
LOG(IAQL_LOG,LOG_WARNING,"Next command in queue is not full command, ignoring\n");
}
}
else if (source_message_type == 0x53 && _iaqua_q_length > 0)
{ // Send small command
if ( _iqaua_queue[0].length <= 2 ) {
*dest_message = _iqaua_queue[0].command;
len = _iqaua_queue[0].length;
_aqua_last_cmdfrom_queue = true;
} else {
LOG(IAQL_LOG,LOG_WARNING,"Next command in queue is too large, ignoring\n");
LOG(IAQL_LOG,LOG_ERR,"Re sending get prepare frame\n");
*dest_message = _cmd_readyCommand;
}
}
//LOG(IAQL_LOG, LOG_INFO, "Return cmd size %d\n",len);
return len;
}
void remove_iaqualink_cmd() {
if (_iaqua_q_length > 0 && _aqua_last_cmdfrom_queue == true) {
LOG(IAQL_LOG,LOG_DEBUG, "Remove from message queue, length %d\n",_iaqua_q_length-1);
memmove(&_iqaua_queue[0], &_iqaua_queue[1], (sizeof(iaqualink_cmd)) * _iaqua_q_length);
_iaqua_q_length--;
}
}
unsigned char iAqalnkDevID(aqkey *button) {
// For the 3 actual vbuttons that exist, we have already set their codes so just return it with no conversion
if ( ((button->special_mask & VIRTUAL_BUTTON) == VIRTUAL_BUTTON) && button->rssd_code != NUL ) {
return button->rssd_code;
}
switch (button->rssd_code) {
case RS_SA_PUMP:
return IAQ_PUMP;
break;
case RS_SA_SPA:
return IAQ_SPA;
break;
case RS_SA_POOLHT:
return IAQ_POOL_HTR;
break;
case RS_SA_SPAHT:
return IAQ_SPA_HTR;
break;
case RS_SA_AUX1:
return IAQ_AUX1;
break;
case RS_SA_AUX2:
return IAQ_AUX2;
break;
case RS_SA_AUX3:
return IAQ_AUX3;
break;
case RS_SA_AUX4:
return IAQ_AUX4;
break;
case RS_SA_AUX5:
return IAQ_AUX5;
break;
case RS_SA_AUX6:
return IAQ_AUX6;
break;
case RS_SA_AUX7:
return IAQ_AUX7;
break;
case RS_SA_AUX8:
return IAQ_AUXB1;
break;
case RS_SA_AUX9:
return IAQ_AUXB2;
break;
case RS_SA_AUX10:
return IAQ_AUXB3;
break;
case RS_SA_AUX11:
return IAQ_AUXB4;
break;
case RS_SA_AUX12:
return IAQ_AUXB5;
break;
case RS_SA_AUX13:
return IAQ_AUXB6;
break;
case RS_SA_AUX14:
return IAQ_AUXB7;
break;
case RS_SA_AUX15:
return IAQ_AUXB8;
break;
}
return 0xFF;
}
void lastchecksum(unsigned char *packet, int length)
{
if (getLogLevel(IAQL_LOG) >= LOG_DEBUG)
{
static unsigned char last70checksum = 0x00;
static unsigned char last71checksum = 0x00;
static unsigned char last72checksum = 0x00;
switch (packet[PKT_CMD])
{
case 0x70:
if (last70checksum != packet[length - 3] && last70checksum != 0x00)
{
LOG(IAQL_LOG, LOG_INFO, "*****************************************\n");
LOG(IAQL_LOG, LOG_INFO, "******* CHECKSUM CHANGED for 0x70 *******\n");
LOG(IAQL_LOG, LOG_INFO, "*****************************************\n");
}
last70checksum = packet[length - 3];
break;
case 0x71:
if (last71checksum != packet[length - 3] && last71checksum != 0x00)
{
LOG(IAQL_LOG, LOG_INFO, "*****************************************\n");
LOG(IAQL_LOG, LOG_INFO, "******* CHECKSUM CHANGED for 0x71 *******\n");
LOG(IAQL_LOG, LOG_INFO, "*****************************************\n");
}
last71checksum = packet[length - 3];
break;
case 0x72:
if (last72checksum != packet[length - 3] && last72checksum != 0x00)
{
LOG(IAQL_LOG, LOG_INFO, "*****************************************\n");
LOG(IAQL_LOG, LOG_INFO, "******* CHECKSUM CHANGED for 0x72 *******\n");
LOG(IAQL_LOG, LOG_INFO, "*****************************************\n");
}
last72checksum = packet[length - 3];
break;
}
}
}
/*
All taken from panel Yg, but only heater setpoints seem to work.
Only setpoints seem to work,
Can't get to work on T2 panel
RPM to 2750
Bit 6 = 0x5e
Bit 10 * 256 + Bit 11
Bit 7 or 9 probably pump index.
HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x5e|0x04|0x00|0x01|0x0a|0xbe|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0xd5|0x10|0x03|
RPM to 2995
HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x5e|0x04|0x00|0x01|0x0b|0xb3|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0xcb|0x10|0x03|
Can't get to work on T2 panel
SWG 50%
Byte 6 = 0x19
Byte 7 = 50%
Byte 9 & 10 ????
HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x19|0x32|0x00|0x18|0x01|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x0e|0x10|0x03|
SWG 51%
Byte 7 = 51%
HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x19|0x33|0x00|0x18|0x01|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x0f|0x10|0x03|
Works on T2
Spa Setpoint 102
Byte 6 = 0x06
Byte 8 = 0x66=102
HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x06|0x00|0x66|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x16|0x10|0x03|
Works on T2
Pool Setpoint 72
Byte 6 = 0x05
Byte 8 = 0x48=72
HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x05|0x00|0x48|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0xf7|0x10|0x03|
*/
void set_iaqualink_aux_state(aqkey *button, bool isON) {
_fullcmd[4] = iAqalnkDevID(button);
if (_fullcmd[4] != 0xFF) {
push_iaqualink_cmd(_cmd_readyCommand, 2);
push_iaqualink_cmd(_fullcmd, 19);
} else {
LOG(IAQL_LOG, LOG_ERR, "Couldn't find iaqualink keycode for button %s\n",button->label);
}
// reset
_fullcmd[4] = 0x00;
}
// AQ_SET_IAQTOUCH_CHILLER_TEMP
// AQ_SET_IAQLINK_CHILLER_TEMP //
//void set_iaqualink_heater_setpoint(int value, bool isPool) {
void set_iaqualink_heater_setpoint(int value, SP_TYPE type) {
if (type == SP_POOL) {
_fullcmd[4] = 0x05;
_fullcmd[6] = value;
} else if (type == SP_SPA) {
_fullcmd[4] = 0x06;
_fullcmd[6] = value;
} else if (type == SP_CHILLER) {
// Note This doesn;t work on T2, but does on Yg. Turned off in aq_programmer
_fullcmd[4] = 0x1f;
_fullcmd[6] = 0x4b;
_fullcmd[8] = 0x63;
_fullcmd[10] = value;
} else {
LOG(IAQL_LOG, LOG_ERR, "Didn't understand setpoint type %d\n",type);
return;
}
// byte[4] = 0x1f, byte[6]=0x4b, byte[8]=0x53, byte[10]=value // for Chiller
// Should check value is valid here.
//_fullcmd[6] = value;
push_iaqualink_cmd(_cmd_readyCommand, 2);
push_iaqualink_cmd(_fullcmd, 19);
// reset
_fullcmd[4] = 0x00;
_fullcmd[6] = 0x00;
_fullcmd[8] = 0x00;
_fullcmd[10] = 0x00;
}
void iAqSetButtonState(struct aqualinkdata *aq_data, int index, const unsigned char byte)
{
if ( aq_data->aqbuttons[index].led->state != OFF && byte == 0x00) {
aq_data->aqbuttons[index].led->state = OFF;
LOG(IAQL_LOG, LOG_INFO, "Set %s off\n",aq_data->aqbuttons[index].label);
} else if ( aq_data->aqbuttons[index].led->state != ON && byte == 0x01) {
aq_data->aqbuttons[index].led->state = ON;
LOG(IAQL_LOG, LOG_INFO, "Set %s on\n",aq_data->aqbuttons[index].label);
} else if ( aq_data->aqbuttons[index].led->state != ENABLE && byte == 0x03) {
aq_data->aqbuttons[index].led->state = ENABLE;
LOG(IAQL_LOG, LOG_INFO, "Set %s enabled\n",aq_data->aqbuttons[index].label);
}
}
/*
Status packets are requested on iAqualink ID 0xA? but received on AqualinkTouch ID 0x3?
They are also sent when iAqualink is connected and a device changes.
So good to catch in PDA mode when a physical iAqualink device is connected to PDA panel.
packet has cmd of 0x70, 0x71, 0x72
*/
bool process_iAqualinkStatusPacket(unsigned char *packet, int length, struct aqualinkdata *aq_data)
{
if (packet[PKT_CMD] == CMD_IAQ_MAIN_STATUS)
{
logPacket(IAQL_LOG, LOG_INFO, packet, length, true);
int startIndex = 4 + 1;
int numberBytes = packet[4];
int offsetIndex = startIndex + numberBytes;
//bool foundSpaSP = false;
//bool foundWaterTemp = false;
//bool foundAirTemp = false;
for (int i = 0; i <= numberBytes; i++)
{
int byteType = packet[5 + i];
int byte = packet[offsetIndex + i];
char *label;
// Some panels have blanks for the last 3 buys, the first of which is "water temp" (not sure on others 0x20, 0x21)
// So if we saw 0x1d break loop if not force next as water temp.
/*
if (foundWaterTemp && i == numberBytes)
{
break;
}
else if (i == numberBytes)
{
byteType = 0x1d;
}*/
// Seems to be in order rather than type
label = " ";
if (byte != 0xff)
{
switch (i)
{
case 0:
// 0x00
label = "Filter Pump ";
if (byteType != 0x00)
label = "Filter Pump *";
break;
case 1:
// 0x01
label = "Pool Heater ";
if (byteType != 0x01)
label = "Pool Heater *";
break;
case 2:
// 0x02
label = "Spa ";
if (byteType != 0x02)
label = "Spa *";
break;
case 3:
// 0x03
label = "Spa Heater ";
if (byteType != 0x03)
label = "Spa Heater *";
break;
case 5:
// 0x06 good
label = "Pool Heater SP ";
if (byteType != 0x06)
label = "Pool Heater SP *";
break;
case 7:
// 0x08 good
// 0x09 (when spa is on???)
// 0x0e (good not sure)
label = "Spa Heater SP ";
if (byteType != 0x08)
label = "Spa Heater SP *";
break;
case 9:
// 0x0f good
label = "Air Temp ";
if (byteType != 0x0f)
label = "Air Temp *";
break;
case 11:
// 0x1d good
// 0x1c (good) ?? maybe spa
// 0x1f bad
label = "Water Temp ";
if (byteType != 0x1d)
label = "Water Temp *";
break;
}
}
/*
if (byteType == 0) {
label = "Filter Pump ";
if (isPDA_PANEL) { iAqSetButtonState(aq_data, 0, byte); }
} else if (byteType == 1) {
label = "Pool Heater "; // 0x00 off 0x01=on&heating, 0x03=enabled
if (isPDA_PANEL) { iAqSetButtonState(aq_data, aq_data->pool_heater_index , byte); }
} else if (byteType == 2) {
label = "Spa ";
} else if (byteType == 3) {
label = "Spa Heater "; // 0x01=on&heating, 0x03=ena
if (isPDA_PANEL) { iAqSetButtonState(aq_data, aq_data->spa_heater_index , byte); }
} else if (byteType == 6) {
label = "Pool Htr setpoint";
} else if (byteType == 8 || byteType == 9) {// 8 usually, also get 9 & 14 (different spa/heater modes not sorted out yet. 14 sometimes blank as well)
label = "Spa Htr setpoint ";
foundSpaSP=true;
} else if ( (byteType == 12) && foundSpaSP==false && byte != 0) { // Sometimes 14
label = "Spa Htr setpoint ";
} else if ( (byteType == 14 || byteType == 15 || byteType == 26) && byte != 0 && byte != 255 && foundAirTemp == false ) {// 0x0f
label = "Air Temp "; // we also see this as 14 (RS16) ONLY
foundAirTemp = true;
} else if ( (byteType == 27 || byteType == 28 || byteType == 29) && byte != 0 && byte != 255 && foundWaterTemp == false) {
// Last 3 bytes don't have type on some panels
label = "Water Temp ";
foundWaterTemp = true;
}
else
label = " ";
*/
LOG(IAQL_LOG, LOG_INFO, "%-17s = %3d | index=%d type=(%0.2d 0x%02hhx) value=0x%02hhx offset=%d\n", label, byte, i, byteType, byteType, byte, (offsetIndex + i));
}
LOG(IAQL_LOG, LOG_INFO, "Status from other protocols Pump %s, Spa %s, SWG %d, PumpRPM %d, PoolSP=%d, SpaSP=%d, WaterTemp=%d, AirTemp=%d\n",
aq_data->aqbuttons[0].led->state == OFF ? "Off" : "On ",
aq_data->aqbuttons[1].led->state == OFF ? "Off" : "On ",
aq_data->swg_percent,
aq_data->pumps[0].rpm,
aq_data->pool_htr_set_point,
aq_data->spa_htr_set_point,
(aq_data->aqbuttons[1].led->state == OFF ? aq_data->pool_temp : aq_data->spa_temp),
aq_data->air_temp);
}
else if (packet[PKT_CMD] == CMD_IAQ_1TOUCH_STATUS)
{
logPacket(IAQL_LOG, LOG_INFO, packet, length, true);
int numLabels = packet[4];
int start = numLabels + 4 + 1;
if (numLabels == 1)
{
// This just seem to list a ton of pump (names)
LOG(IAQL_LOG, LOG_INFO, "**** !!! haven't decoded above packet yet !!! *****\n");
return false;
}
for (int i = 0; i < numLabels; i++)
{
int status = packet[start];
int length = packet[start + 1];
int byteType = packet[5 + i];
LOG(IAQL_LOG, LOG_INFO, "%-15.*s = %s | index %d type=(%0.2d 0x%02hhx) status=0x%02hhx start=%d length=%d\n", length, &packet[start + 2], (status == 0x00 ? "Off" : "On "), i, byteType, byteType, status, start, length);
// Check against virtual onetouch buttons.
// NSF This needs to check strlingth, ie "Spa Mode" vs "Spa"
if ( aq_data->virtual_button_start > 0 ) {
for (int bi=aq_data->virtual_button_start ; bi < aq_data->total_buttons ; bi++) {
//LOG(IAQL_LOG, LOG_INFO, "Check %s against %s\n",(char *)&packet[start + 2], aq_data->aqbuttons[bi].label);
if (rsm_strcmp((char *)&packet[start + 2], aq_data->aqbuttons[bi].label) == 0) {
//LOG(IAQL_LOG, LOG_INFO, "Status for %s is %s\n",aq_data->aqbuttons[bi].label,(status == 0x00 ? "Off" : "On "));
// == means doesn;t match, RS 1=on 0=off / LED enum 1=off 0=on
if (aq_data->aqbuttons[bi].led->state == status) {
LOG(IAQL_LOG, LOG_INFO, "Updated Status for %s is %s\n",aq_data->aqbuttons[bi].label,(status == 0x00 ? "Off" : "On "));
aq_data->aqbuttons[bi].led->state = (status == 0x00 ? OFF:ON);
aq_data->updated = true;
}
}
}
start = start + packet[start + 1] + 2;
}
}
}
else if (packet[PKT_CMD] == CMD_IAQ_AUX_STATUS)
{
logPacket(IAQL_LOG, LOG_INFO, packet, length, true);
// Look at notes in iaqualink.c for how this packet is made up
// Since this is so similar to above CMD_IAQ_1TOUCH_STATUS, we should look at using same logic for both.
int start = packet[4];
start = start + 5;
for (int i = start; i < length - 3; i = i)
{
int status = i;
int labelstart = status + 5;
int labellen = packet[status + 4];
if (labelstart + labellen < length)
{
LOG(IAQL_LOG, LOG_INFO, "%-15.*s = %s | bit1=0x%02hhx bit2=0x%02hhx bit3=0x%02hhx bit4=0x%02hhx\n", labellen, &packet[labelstart], (packet[status] == 0x00 ? "Off" : "On "), packet[status], packet[status + 1], packet[status + 2], packet[status + 3]);
}
if (isPDA_PANEL) {
for (int bi=2 ; bi < aq_data->total_buttons ; bi++) {
if (rsm_strcmp((char *)&packet[labelstart], aq_data->aqbuttons[bi].label) == 0) {
if (aq_data->aqbuttons[bi].led->state == packet[status]) {
LOG(IAQL_LOG, LOG_INFO, "Updated Status for %s is %s\n",aq_data->aqbuttons[bi].label,(packet[status] == 0x00 ? "Off" : "On "));
aq_data->aqbuttons[bi].led->state = (packet[status] == 0x00 ? OFF:ON);
aq_data->updated = true;
}
//LOG(IAQL_LOG, LOG_INFO, "Match %s to %.*s state(aqd=%d pnl=%d)\n",aq_data->aqbuttons[bi].label, labellen, (char *)&packet[labelstart], aq_data->aqbuttons[bi].led->state, packet[status]);
}
}
}
i = labelstart + labellen;
}
}
return true;
}
bool process_iaqualink_packet(unsigned char *packet, int length, struct aqualinkdata *aq_data)
{
lastchecksum(packet, length);
unsigned char cmd_getMainstatus[] = {0x3f, 0x08};
unsigned char cmd_getTouchstatus[] = {0x3f, 0x10};
unsigned char cmd_getAuxstatus[] = {0x3f, 0x18};
//unsigned char cmd_readyCommand[] = {0x3f, 0x20};
//unsigned char fullcmd[] = {0x00, 0x24, 0x73, 0x01, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// byte 4 is ID. 0x00 pump, 0x02 spa etc
if (packet[PKT_DEST] == _aqconfig_.extended_device_id2)
{
static int cnt = 0;
//static unsigned char ID = 0;
//static cur_swg = 0;
//static unsigned char sendid = 0x00;
if (packet[PKT_CMD] == 0x53)
{
cnt++;
if (cnt == 20) { // 20 is probably too low, should increase. (only RS16 and below)
cnt=0;
/*
sendid=sendid==0x18?0x60:0x18;
_fullcmd[4] = sendid;
push_iaqualink_cmd(_cmd_readyCommand, 2);
push_iaqualink_cmd(_fullcmd, 19);
_fullcmd[4] = 0x00;
*/
push_iaqualink_cmd(cmd_getMainstatus, 2);
push_iaqualink_cmd(cmd_getTouchstatus, 2);
push_iaqualink_cmd(cmd_getAuxstatus, 2);
/*
LOG(IAQL_LOG, LOG_INFO,"*****************************************\n");
LOG(IAQL_LOG, LOG_INFO,"********** Send %d 0x%02hhx ************\n",ID,ID);
LOG(IAQL_LOG, LOG_INFO,"*****************************************\n");
_fullcmd[4] = ID++;
push_iaqualink_cmd(_cmd_readyCommand, 2);
push_iaqualink_cmd(_fullcmd, 19);
push_iaqualink_cmd(cmd_getMainstatus, 2);
push_iaqualink_cmd(cmd_getTouchstatus, 2);
push_iaqualink_cmd(cmd_getAuxstatus, 2);
push_iaqualink_cmd(_cmd_readyCommand, 2);
push_iaqualink_cmd(_fullcmd, 19);
*/
//fullcmd[4] = ID;
//fullcmd[4] = ID;
//fullcmd[5] = 0x32;
//fullcmd[7] = ID;
//fullcmd[8] = 0x01;
//set_iaqualink_heater_setpoint(50, true);
//push_iaqualink_cmd(cmd_getMainstatus, 2);
//push_iaqualink_cmd(cmd_getTouchstatus, 2);
//push_iaqualink_cmd(cmd_getAuxstatus, 2);
/*
if (aq_data->swg_percent != cur_swg && cur_swg != 0) {
LOG(IAQL_LOG, LOG_INFO,"*****************************************\n");
LOG(IAQL_LOG, LOG_INFO,"********** SWG Changed to %d ************\n",aq_data->swg_percent);
LOG(IAQL_LOG, LOG_INFO,"*****************************************\n");
exit(0);
}
cur_swg = aq_data->swg_percent;
LOG(IAQL_LOG, LOG_INFO,"******* QUEUE SWG Comand of %d | 0x%02hhx *************\n",ID,ID);
ID++;*/
}
}
// Packets sent to iAqualink protocol
// debuglogPacket(IAQL_LOG, packet, length, true, true);
}
else if (packet[PKT_DEST] == _aqconfig_.extended_device_id || (isPDA_PANEL && packet[PKT_DEST] == _aqconfig_.device_id) )
{
process_iAqualinkStatusPacket(packet, length, aq_data);
}
return true;
}
#ifdef DONOTCOMPILE
// This is onle here temporarly until we figure out the protocol.
void send_iaqualink_ack(int rs_fd, unsigned char *packet_buffer)
{
/*
Probe | HEX: 0x10|0x02|0xa3|0x00|0xb5|0x10|0x03|
Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x00|0x13|0x10|0x03|
Unknown '0x61' | HEX: 0x10|0x02|0xa3|0x61|0x00|0x00|0x00|0x04|0x00|0x27|0x41|0x10|0x03|
Ack | HEX: 0x10|0x02|0x00|0x01|0x61|0x00|0x74|0x10|0x03|
Unknown '0x50' | HEX: 0x10|0x02|0xa3|0x50|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x00|0x25|0x10|0x03|
Ack | HEX: 0x10|0x02|0x00|0x01|0x50|0x00|0x63|0x10|0x03|
Unknown '0x51' | HEX: 0x10|0x02|0xa3|0x51|0x00|0x06|0x10|0x03|
Ack | HEX: 0x10|0x02|0x00|0x01|0x51|0x00|0x64|0x10|0x03|
Unknown '0x59' | HEX: 0x10|0x02|0xa3|0x59|0x00|0x0e|0x10|0x03|
Ack | HEX: 0x10|0x02|0x00|0x01|0x59|0x00|0x6c|0x10|0x03|
Unknown '0x52' | HEX: 0x10|0x02|0xa3|0x52|0x00|0x07|0x10|0x03|
Ack | HEX: 0x10|0x02|0x00|0x01|0x52|0x00|0x65|0x10|0x03|
Unknown '0x53' | HEX: 0x10|0x02|0xa3|0x53|0x08|0x10|0x03|
Ack | HEX: 0x10|0x02|0x00|0x01|0x3f|0x00|0x52|0x10|0x03|
Use byte 3 as return ack, except for 0x53=0x3f
*/
if (packet_buffer[PKT_CMD] == CMD_PROBE)
{
LOG(IAQL_LOG, LOG_INFO, "Got probe on '0x%02hhx' 2nd iAqualink Protocol\n", packet_buffer[PKT_DEST]);
send_extended_ack(rs_fd, packet_buffer[PKT_CMD], 0x00);
}
else if (packet_buffer[PKT_CMD] == 0x53)
{
static int cnt = 0;
cnt++;
if (cnt == 10)
{
//cnt = 5;
LOG(IAQL_LOG, LOG_INFO, "Sent accept next packet Comand\n");
send_extended_ack(rs_fd, 0x3f, 0x20);
}
if (cnt == 20)
{
LOG(IAQL_LOG, LOG_INFO, "Sending get status\n");
send_extended_ack(rs_fd, 0x3f, 0x08);
}
else if (cnt == 22)
{
LOG(IAQL_LOG, LOG_INFO, "Sending get other status\n");
send_extended_ack(rs_fd, 0x3f, 0x10);
}
else if (cnt == 24)
{
LOG(IAQL_LOG, LOG_INFO, "Sending get aux button status\n");
send_extended_ack(rs_fd, 0x3f, 0x18);
}
else
{
// Use 0x3f
if (cnt > 24)
{
cnt = 0;
}
send_extended_ack(rs_fd, 0x3f, 0x00);
}
// send_jandy_command(rs_fd, get_rssa_cmd(packet_buffer[PKT_CMD]), 4);
}
else if (packet_buffer[PKT_CMD] == 0x73)
{
static int id = 10;
// unsigned char pb1[] = {PCOL_JANDY,0x10,0x02,0x00,0x24,0x73,0x01,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc2,0x10,0x03,0x00};
// unsigned char pb2[] = {PCOL_JANDY,0x10,0x02,0x00,0x24,0x73,0x01,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcb,0x10,0x03,0x00};
// 0x21 turns on filter_pump and aux 1
//unsigned char pb3[] = {0x00, 0x24, 0x73, 0x01, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char swg[] = {0x00, 0x24, 0x73, 0x01, 0x19, 0x32, 0x00, 0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
//unsigned char swg[] = {0x00, 0x24, 0x73, 0x01, 0x19, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char rpm[] = {0x00,0x24,0x73,0x01,0x5e,0x04,0x00,0x01,0x0a,0xbe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd5,0x10,0x03};
//pb3[4] = id++;
//swg[5] = ++id;
LOG(IAQL_LOG, LOG_INFO, "*** Sending SWG dec=%d hex=0x%02hhx\n", swg[5], swg[5]);
// send_packet(rs_fd, pb2, 25);
send_jandy_command(rs_fd, swg, 19);
}
else
{
// Use packet_buffer[PKT_CMD]
send_extended_ack(rs_fd, packet_buffer[PKT_CMD], 0x00);
}
}
#endif

419
source/iaqualink.h Normal file
View File

@ -0,0 +1,419 @@
#ifndef AQ_IAQUALINK_H_
#define AQ_IAQUALINK_H_
//void send_iaqualink_ack(int rs_fd, unsigned char *packet_buffer);
int get_iaqualink_cmd(unsigned char source_message_type, unsigned char **dest_message);
void remove_iaqualink_cmd();
bool process_iaqualink_packet(unsigned char *packet, int length, struct aqualinkdata *aq_data);
bool process_iAqualinkStatusPacket(unsigned char *packet, int length, struct aqualinkdata *aq_data);
void set_iaqualink_aux_state(aqkey *button, bool isON);
void set_iaqualink_heater_setpoint(int value, SP_TYPE type);
// Send the below commands to turn on/off (toggle)
// This is the button in pButton. (byte 6 in below)
// iAq pButton | HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x21|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0xcb|0x10|0x03|
#define IAQ_PUMP 0x00
#define IAQ_POOL_HTR 0x01
#define IAQ_SPA 0x02
#define IAQ_SPA_HTR 0x03
//....... some missing ....
//#define IAQ_ALL_OFF 0x10
//#define IAQ_SPA_MODE 0x11
//#define IAQ_CLEAN_MODE 0x12
#define IAQ_ONETOUCH_1 0x10
#define IAQ_ONETOUCH_2 0x11
#define IAQ_ONETOUCH_3 0x12
#define IAQ_ONETOUCH_4 0x13 // Im gessing
#define IAQ_ONETOUCH_5 0x14 // Im gessing
#define IAQ_ONETOUCH_6 0x15 // Im gessing
#define IAQ_ALL_OFF IAQ_ONETOUCH_1 // Using default name
#define IAQ_SPA_MODE IAQ_ONETOUCH_2 // Using default name
#define IAQ_CLEAN_MODE IAQ_ONETOUCH_3 // Using default name
#define IAD_SWG 0x19
//....... some missing ....
#define IAQ_AUX1 0x21 //0x25 RS16 & 12 // AUX5
#define IAQ_AUX2 0x22 //0x26 RS16
#define IAQ_AUX3 0x23 //0x27 RS16
#define IAQ_AUX4 0x24 //0x28 RS16
#define IAQ_AUX5 0x25 //0x29 RS16
#define IAQ_AUX6 0x26 //0x2a RS16
#define IAQ_AUX7 0x27 //0x2b RS16
#define IAQ_AUXB1 0x28 //0x2c RS16
#define IAQ_AUXB2 0x29 //0x2d RS16
#define IAQ_AUXB3 0x2a //0x2e RS16
#define IAQ_AUXB4 0x2b //0x2f RS16
#define IAQ_AUXB5 0x2c //0x30 RS16
#define IAQ_AUXB6 0x2d //0x31 RS16
#define IAQ_AUXB7 0x2e //0x32 RS16
#define IAQ_AUXB8 0x2f //0x33 RS16
/*
#define IAQ_AUX1 0x25 //0x25 RS16 & 12 // AUX5
#define IAQ_AUX2 0x26 //0x26 RS16
#define IAQ_AUX3 0x27 //0x27 RS16
#define IAQ_AUX4 0x28 //0x28 RS16
#define IAQ_AUX5 0x29 //0x29 RS16
#define IAQ_AUX6 0x2a //0x2a RS16
#define IAQ_AUX7 0x2b //0x2b RS16
#define IAQ_AUXB1 0x2c //0x2c RS16
#define IAQ_AUXB2 0x2d //0x2d RS16
#define IAQ_AUXB3 0x2e //0x2e RS16
#define IAQ_AUXB4 0x2f //0x2f RS16
#define IAQ_AUXB5 0x30 //0x30 RS16
#define IAQ_AUXB6 0x31 //0x31 RS16
#define IAQ_AUXB7 0x32 //0x32 RS16
#define IAQ_AUXB8 0x33 //0x33 RS16
*/
//... Looks like there are C & D buttons
/* I got this when sending dec=53 hex=0x35 as the button, all of a sudden got extra buttons in the aux status message send to AqualinkTouch protocol
Not sure on ordering BUT dec=57 hex=0x39 = button D2 / dec=58 hex=0x3a = D3
Notice: iAQ Touch: Label Aux C1 = On
Notice: iAQ Touch: Label Aux C2 = Off
Notice: iAQ Touch: Label Aux C3 = Off
Notice: iAQ Touch: Label Aux C4 = Off
Notice: iAQ Touch: Label Aux C5 = Off
Notice: iAQ Touch: Label Aux C6 = On
Notice: iAQ Touch: Label Aux C7 = On
Notice: iAQ Touch: Label Aux C8 = On
Notice: iAQ Touch: Label Aux D1 = On
Notice: iAQ Touch: Label Aux D2 = On
Notice: iAQ Touch: Label Aux D3 = On
Notice: iAQ Touch: Label Aux D4 = On
Notice: iAQ Touch: Label Aux D5 = On
Notice: iAQ Touch: Label Aux D6 = On
Notice: iAQ Touch: Label Aux D7 = On
Notice: iAQ Touch: Label Aux D8 = On
*/
#define IAQ_AUXD2 0x39
#define IAQ_AUXD3 0x3a // 58 in dec
#define IAQ_AUXD4 // 59 in dec
#define IAQ_AUXD5 // 60 in dec
#define IAQ_AUXD6 // 61 in dec
#define IAQ_AUXD7 // 62 in dec
#define IAQ_AUXD8 // 63 in dec (but this is VAUX1)
//... Need to add Vitrual buttons...
#define IAQ_VAUX1 0x3f
#define IAQ_PUMP_RPM 0x5e
#endif
/*
Read Jandy packet To 0xa3 of type Unknown '0x53' | HEX: 0x10|0x02|0xa3|0x53|0x08|0x10|0x03|
Read Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x3f|0x00|0x52|0x10|0x03|
Below get's sent to AqualinkTouch with iAqualink is enabled.
End of message is board cpu and panel type.
Read Jandy packet To 0x33 of type Unknown '0x70' | HEX: 0x10|0x02|0x33|0x70|0x0d|0x00|0x01|0x02|0x03|0x05|0x06|0x07|0x0e|0x0f|0x1a|0x1d|0x20|0x21|0x00|0x00|0x00|0x00|0x00|0x48|0x00|0x66|0x00|0x50|0x00|0x00|0x00|0xff|0x42|0x30|0x33|0x31|0x36|0x38|0x32|0x33|0x20|0x52|0x53|0x2d|0x34|0x20|0x43|0x6f|0x6d|0x62|0x6f|0x00|0x00|0x4b|0x10|0x03|
*/
/*
----
Retrieve poll 0x53
Reply ack w/ command 0x01|0x3f|0x20. (0x20 command)
Retrieve 0x73 command Same ID iAqualink
Reply iAQ pButton ( turn something on / set something)
Retrieve poll 0x53
Reply ack w/ command 0x01|0x3f|0x08. (0x08 command)
Retrieve 0x70 command on AqualinkTouch ID
Reply iAQ pButton (status / last part of frame is board cpu)
-- Byte 4 = offset.
-- Byte 21 look like spa heat 0x00=off, 0x03=enabled
-- offest+ 8 spa setpoint
-- offset+ 10 air temp
-- offest+ 13 pool temp
-- offset+ 14 SWG% ?????
-- Byte 27 looks like air temp
-- Byte 28 air temp????
-- Byte 30 pool temp
-- Byte 34 looks like SWG%
-- after byte 0xff is board
Retrieve poll 0x53
Reply ack w/ command 0x01|0x3f|0x10. (0x10 command)
Retrieve 0x71 command on AqualinkTouch ID
Reply iAQ pButton (not sure)
Byte 4 & 5 seems to indicate a different sub type
0x71|0x01|0x19 = status about pool / spa / speed 1,2,3,4 / pool & spa heat
0x71|0x01|0x1a = no idea
0x71|0x01|0x17 = Pump numbers (doesn't seem to have any status, just pump names)
0x71|0x06|0x01 = Touch like spa mode / all off
0x71|0x07|0x01 = Same as above. but more trailing packets.
Retrieve poll 0x53
Reply ack w/ command 0x3f 0x18. (0x18 is command)
Retrieve 0x72 command on AqualinkTouch ID
Get a massive packet Aux status
----
Retrieve poll 0x53
Reply ack w/ command 0x01|0x3f|0x20. (0x20 command)
Retrieve 0x72 command
Reply iAQ pButton (turn something on / set something). Below are examples.
RPM to 2750
Bit 10 * 256 + Bit 11
Bit 6 & 7 probably pump index.
HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x5e|0x04|0x00|0x01|0x0a|0xbe|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0xd5|0x10|0x03|
RPM to 2995
HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x5e|0x04|0x00|0x01|0x0b|0xb3|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0xcb|0x10|0x03|
SWG 50%
Byte 7 = 50%
HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x19|0x32|0x00|0x18|0x01|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x0e|0x10|0x03|
SWG 51%
Byte 7 = 51%
HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x19|0x33|0x00|0x18|0x01|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x0f|0x10|0x03|
---------
Send command 0x18 to 0xa1 returns below to 0x31. Looks like status of aux devices.
Retrieve poll 0x53
Reply ack w/ command 0x3f 0x18. (0x18 is command)
Get a massive packet back, of aux status
Filter pump does not effect the below.
Heater on/off does not effect below.
HEater setpoints does not effect.
Only Aux on/off seem to effect the status.
4th bit tells you where to start,
after start 4 bits are status
last bit of status tell you chars in label.
after label repeat start 4 bits.
For Aux1 10th bit 0=off 1=on 11 to 14 15 to 19=name
Aux2 19th
Aux3 28
Aux4 37
In Below Aux 3 color light / Aux 4 dimmer / Aux 5 color light (different type to aux3)
Aux 1 off / Aux 3 on
HEX: 0x10|0x02|0x31|0x72|0x05|0x01|0x02|0x03|0x04|0x05|0x00|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x31|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x32|0x01|0x07|0x02|0x03|0x04|0x41|0x75|0x78|0x33|0x00|0x01|0x01|0x00|0x04|0x41|0x75|0x78|0x34|0x00|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x35|0xd5|0x10|0x03|
Dec | 16| 2| 49| 114| 5| 1| 2| 3| 4| 5| 0| 1| 0| 0| 4| 65| 117| 120| 49| 1| 1| 0| 0| 4| 65| 117| 120| 50| 1| 7| 2| 3| 4| 65| 117| 120| 51| 0| 1| 1| 0| 4| 65| 117| 120| 52| 0| 1| 0| 0| 4| 65| 117| 120| 53| 213| 16| 3
Ascii | | | 1| r| | | | | | | | | | | | A| u| x| 1| | | | | | A| u| x| 2| | | | | | A| u| x| 3| | | | | | A| u| x| 4| | | | | | A| u| x| 5| | |
Aux 1 on / Aux 3 on
| HEX: 0x10|0x02|0x31|0x72|0x05|0x01|0x02|0x03|0x04|0x05|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x31|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x32|0x01|0x07|0x02|0x03|0x04|0x41|0x75|0x78|0x33|0x00|0x01|0x01|0x00|0x04|0x41|0x75|0x78|0x34|0x00|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x35|0xd6|0x10|0x03|
Dec | 16| 2| 49| 114| 5| 1| 2| 3| 4| 5| 1| 1| 0| 0| 4| 65| 117| 120| 49| 1| 1| 0| 0| 4| 65| 117| 120| 50| 1| 7| 2| 3| 4| 65| 117| 120| 51| 0| 1| 1| 0| 4| 65| 117| 120| 52| 0| 1| 0| 0| 4| 65| 117| 120| 53| 214| 16| 3
Ascii | | | 1| r| | | | | | | | | | | | A| u| x| 1| | | | | | A| u| x| 2| | | | | | A| u| x| 3| | | | | | A| u| x| 4| | | | | | A| u| x| 5| | |
Aux 1 off Aux3 to different light color
Hex |0x10|0x02|0x31|0x72|0x05|0x01|0x02|0x03|0x04|0x05|0x00|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x31|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x32|0x00|0x07|0x02|0x03|0x04|0x41|0x75|0x78|0x33|0x00|0x01|0x01|0x00|0x04|0x41|0x75|0x78|0x34|0x00|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x35|0xd4|0x10|0x03|
Dec | 16| 2| 49| 114| 5| 1| 2| 3| 4| 5| 0| 1| 0| 0| 4| 65| 117| 120| 49| 1| 1| 0| 0| 4| 65| 117| 120| 50| 0| 7| 2| 3| 4| 65| 117| 120| 51| 0| 1| 1| 0| 4| 65| 117| 120| 52| 0| 1| 0| 0| 4| 65| 117| 120| 53| 212| 16| 3
Ascii | | | 1| r| | | | | | | | | | | | A| u| x| 1| | | | | | A| u| x| 2| | | | | | A| u| x| 3| | | | | | A| u| x| 4| | | | | | A| u| x| 5| | |
RS16 panel.
HEX: 0x10|0x02|0x31|0x72|0x0f|0x01|0x02|0x03|0x04|0x05|0x06|0x07|0x08|0x09|0x0a|0x0b|0x0c|0x0d|0x0e|0x0f|0x00|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x31|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x32|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x33|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x34|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x35|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x36|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x37|0x01|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x31|0x01|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x32|0x01|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x33|0x01|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x34|0x01|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x35|0x01|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x36|0x01|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x37|0x01|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x38|0x77|0x10|0x03|
Dec | 16| 2| 49| 114| 15| 1| 2| 3| 4| 5| 6| 7| 8| 9| 10| 11| 12| 13| 14| 15| 0| 1| 0| 0| 4| 65| 117| 120| 49| 1| 1| 0| 0| 4| 65| 117| 120| 50| 1| 1| 0| 0| 4| 65| 117| 120| 51| 1| 1| 0| 0| 4| 65| 117| 120| 52| 1| 1| 0| 0| 4| 65| 117| 120| 53| 1| 1| 0| 0| 4| 65| 117| 120| 54| 1| 1| 0| 0| 4| 65| 117| 120| 55| 1| 1| 0| 0| 6| 65| 117| 120| 32| 66| 49| 1| 1| 0| 0| 6| 65| 117| 120| 32| 66| 50| 1| 1| 0| 0| 6| 65| 117| 120| 32| 66| 51| 1| 1| 0| 0| 6| 65| 117| 120| 32| 66| 52| 1| 1| 0| 0| 6| 65| 117| 120| 32| 66| 53| 1| 1| 0| 0| 6| 65| 117| 120| 32| 66| 54| 1| 1| 0| 0| 6| 65| 117| 120| 32| 66| 55| 1| 1| 0| 0| 6| 65| 117| 120| 32| 66| 56| 119| 16| 3
Ascii | | | 1| r| | | | | | | | | | | | | | | | | | | | | | A| u| x| 1| | | | | | A| u| x| 2| | | | | | A| u| x| 3| | | | | | A| u| x| 4| | | | | | A| u| x| 5| | | | | | A| u| x| 6| | | | | | A| u| x| 7| | | | | | A| u| x| | B| 1| | | | | | A| u| x| | B| 2| | | | | | A| u| x| | B| 3| | | | | | A| u| x| | B| 4| | | | | | A| u| x| | B| 5| | | | | | A| u| x| | B| 6| | | | | | A| u| x| | B| 7| | | | | | A| u| x| | B| 8| w| |
Hex |0x10|0x02|0x31|0x72|
*/
/* Startup sequences
RS16 combo
Debug: RS Serial: Read Jandy packet To 0xa1 of type Probe | HEX: 0x10|0x02|0xa1|0x00|0xb3|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x00|0x13|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x61' | HEX: 0x10|0x02|0xa1|0x61|0x00|0x00|0x00|0x01|0x00|0x1d|0x32|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x61|0x00|0x74|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x50' | HEX: 0x10|0x02|0xa1|0x50|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x00|0x23|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x50|0x00|0x63|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x51' | HEX: 0x10|0x02|0xa1|0x51|0x31|0x42|0x41|0x36|0x32|0x38|0x32|0x35|0x42|0x37|0x43|0x36|0x39|0x41|0x34|0x43|0x00|0xa2|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x51|0x00|0x64|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x59' | HEX: 0x10|0x02|0xa1|0x59|0x00|0x0c|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x59|0x00|0x6c|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x52' | HEX: 0x10|0x02|0xa1|0x52|0x00|0x05|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x52|0x00|0x65|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type iAqalnk Poll | HEX: 0x10|0x02|0xa1|0x53|0x06|0x10|0x03|
RS8 Combo rev T.2
Debug: RS Serial: Read Jandy packet To 0xa1 of type Probe | HEX: 0x10|0x02|0xa1|0x00|0xb3|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x00|0x13|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x61' | HEX: 0x10|0x02|0xa1|0x61|0x00|0x00|0x00|0x01|0x00|0x1d|0x32|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x61|0x00|0x74|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x50' | HEX: 0x10|0x02|0xa1|0x50|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x00|0x23|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x50|0x00|0x63|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x51' | HEX: 0x10|0x02|0xa1|0x51|0x31|0x42|0x41|0x36|0x32|0x38|0x32|0x35|0x42|0x37|0x43|0x36|0x39|0x41|0x34|0x43|0x00|0xa2|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x51|0x00|0x64|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x59' | HEX: 0x10|0x02|0xa1|0x59|0x00|0x0c|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x59|0x00|0x6c|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x52' | HEX: 0x10|0x02|0xa1|0x52|0x00|0x05|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x52|0x00|0x65|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type iAqalnk Poll | HEX: 0x10|0x02|0xa1|0x53|0x06|0x10|0x03|
RS4 rev Yg
Notice: Serial Log:Read Jandy packet To 0xa3 of type Probe | HEX: 0x10|0x02|0xa3|0x00|0xb5|0x10|0x03|
Notice: Serial Log:Read Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x00|0x13|0x10|0x03|
Notice: Serial Log:Read Jandy packet To 0xa3 of type Unknown '0x61' | HEX: 0x10|0x02|0xa3|0x61|0x00|0x00|0x00|0x04|0x00|0x27|0x41|0x10|0x03|
Notice: Serial Log:Read Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x61|0x00|0x74|0x10|0x03|
Notice: Serial Log:Read Jandy packet To 0xa3 of type Unknown '0x50' | HEX: 0x10|0x02|0xa3|0x50|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x00|0x25|0x10|0x03|
Notice: Serial Log:Read Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x50|0x00|0x63|0x10|0x03|
Notice: Serial Log:Read Jandy packet To 0xa3 of type Unknown '0x51' | HEX: 0x10|0x02|0xa3|0x51|0x00|0x06|0x10|0x03|
Notice: Serial Log:Read Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x51|0x00|0x64|0x10|0x03|
Notice: Serial Log:Read Jandy packet To 0xa3 of type Unknown '0x59' | HEX: 0x10|0x02|0xa3|0x59|0x00|0x0e|0x10|0x03|
Notice: Serial Log:Read Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x59|0x00|0x6c|0x10|0x03|
Notice: Serial Log:Read Jandy packet To 0xa3 of type Unknown '0x52' | HEX: 0x10|0x02|0xa3|0x52|0x00|0x07|0x10|0x03|
Notice: Serial Log:Read Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x52|0x00|0x65|0x10|0x03|
Notice: Serial Log:Read Jandy packet To 0xa3 of type Unknown '0x53' | HEX: 0x10|0x02|0xa3|0x53|0x08|0x10|0x03|
Notice: Serial Log:Read Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x3f|0x00|0x52|0x10|0x03|
RS6 rev T2 RS8 (home)
Debug: RS Serial: Read Jandy packet To 0xa1 of type Probe | HEX: 0x10|0x02|0xa1|0x00|0xb3|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x00|0x13|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x61' | HEX: 0x10|0x02|0xa1|0x61|0x00|0x00|0x00|0x01|0x00|0x1d|0x32|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x61|0x00|0x74|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x50' | HEX: 0x10|0x02|0xa1|0x50|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x00|0x23|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x50|0x00|0x63|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x51' | HEX: 0x10|0x02|0xa1|0x51|0x31|0x42|0x41|0x36|0x32|0x38|0x32|0x35|0x42|0x37|0x43|0x36|0x39|0x41|0x34|0x43|0x00|0xa2|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x51|0x00|0x64|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x59' | HEX: 0x10|0x02|0xa1|0x59|0x01|0x0d|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x59|0x00|0x6c|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type Unknown '0x52' | HEX: 0x10|0x02|0xa1|0x52|0x03|0x08|0x10|0x03|
Debug: RS Serial: Write Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x52|0x00|0x65|0x10|0x03|
Debug: RS Serial: Read Jandy packet To 0xa1 of type iAqalnk Poll | HEX: 0x10|0x02|0xa1|0x53|0x06|0x10|0x03|
*/
/*
0x10|0x02|0x31|0x72|0x0f|0x01|0x02|0x03|0x04|0x05|0x06|0x07|0x08|0x09|0x0a|0x0b|0x0c|0x0d|0x0e|0x0f|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x31|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x32|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x33|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x34|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x35|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x36|0x01|0x01|0x00|0x00|0x04|0x41|0x75|0x78|0x37|0x00|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x31|0x00|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x32|0x00|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x33|0x00|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x34|0x00|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x35|0x00|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x36|0x00|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x37|0x00|0x01|0x00|0x00|0x06|0x41|0x75|0x78|0x20|0x42|0x38|0x70|0x10|0x03|
Only panel (RS8)
Air index 3 type 15 (pump off)
Air index 3 type 14 (pump off)
Air index 3 type 8 (pump on)
Water index 5 type 15 (pump on)
Temp2 ndex 8 type 20
always 1 | index=4 type=(15 0x0f)
Comb0
Spa setpoint index 8 type 14
Air index 8 type 26
*/
/*
Pump RPM
All taken from panel Yg, but only heater setpoints seem to work.
Only setpoints seem to work,
RPM to 2750
Bit 6 = 0x5e
Bit 10 * 256 + Bit 11
Bit 7 or 9 probably pump index.
HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x5e|0x04|0x00|0x01|0x0a|0xbe|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0xd5|0x10|0x03|
RPM to 2995
HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x5e|0x04|0x00|0x01|0x0b|0xb3|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0xcb|0x10|0x03|
Below is setting RPM to 2505
packet To 0xa3 of type iAqalnk Poll | HEX: 0x10|0x02|0xa3|0x53|0x08|0x10|0x03|
packet To 0x00 of type Ack w/ Command | HEX: 0x10|0x02|0x00|0x01|0x3f|0x20|0x72|0x10|0x03|
packet To 0x33 of type iAq Poll | HEX: 0x10|0x02|0x33|0x30|0x75|0x10|0x03|
packet To 0xa3 of type Unknown '0x73' | HEX: 0x10|0x02|0xa3|0x73|0x28|0x10|0x03|
packet To 0x00 of type iAqualnk sendCmd | HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x5e|0x04|0x00|0x01|0x09|0xc9|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0xdf|0x10|0x03|
packet To 0x33 of type iAq Poll | HEX: 0x10|0x02|0x33|0x30|0x75|0x10|0x03|
packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x00|0x13|0x10|0x03|
packet To 0x33 of type iAq 1Tch status | HEX: 0x10|0x02|0x33|0x71|0x07|0x01|0x02|0x03|0x04|0x05|0x0f|0x12|0x00|0x07|0x41|0x6c|0x6c|0x20|0x4f|0x46|0x46|0x00|0x08|0x53|0x70|0x61|0x20|0x4d|0x6f|0x64|0x65|0x00|0x0a|0x43|0x6c|0x65|0x61|0x6e|0x20|0x4d|0x6f|0x64|0x65|0x00|0x08|0x42|0x75|0x62|0x62|0x6c|0x65|0x72|0x73|0x00|0x0a|0x57|0x61|0x74|0x65|0x72|0x66|0x61|0x6c|0x6c|0x31|0x04|0x09|0xc9|0x0d|0x7a|0x06|0xa4|0x0a|0xbe|0x08|0xca|0x08|0xca|0x0a|0xbe|0x0a|0xbe|0x05|0xdc|0x04|0x01|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x6a|0x10|0x03|
All OFF = Off | index 0 type=(01 0x01) status=0x00 start=12 length=7
Spa Mode = Off | index 1 type=(02 0x02) status=0x00 start=21 length=8
Clean Mode = Off | index 2 type=(03 0x03) status=0x00 start=31 length=10
Bubblers = Off | index 3 type=(04 0x04) status=0x00 start=43 length=8
Waterfall1 = Off | index 4 type=(05 0x05) status=0x00 start=53 length=10
*** RPM is in the next bytes from iAq 1Tch status. But can't tell how to pull them since SWG is also here as well. ****
*/
/*
SWG
SWG 50%
Byte 6 = 0x19
Byte 7 = 50%
Byte 9 & 10 ????
HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x19|0x32|0x00|0x18|0x01|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x0e|0x10|0x03|
SWG 51%
Byte 7 = 51%
HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x19|0x33|0x00|0x18|0x01|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x0f|0x10|0x03|
Below is set SWG to 35
packet To 0xa3 of type iAqalnk Poll | HEX: 0x10|0x02|0xa3|0x53|0x08|0x10|0x03|
packet To 0x00 of type Ack w/ Command | HEX: 0x10|0x02|0x00|0x01|0x3f|0x20|0x72|0x10|0x03|
packet To 0x33 of type iAq Poll | HEX: 0x10|0x02|0x33|0x30|0x75|0x10|0x03|
packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x00|0x13|0x10|0x03|
packet To 0xa3 of type Unknown '0x73' | HEX: 0x10|0x02|0xa3|0x73|0x28|0x10|0x03|
packet To 0x00 of type iAqualnk sendCmd | HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x19|0x23|0x00|0x18|0x01|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0xff|0x10|0x03|
packet To 0x33 of type iAq Poll | HEX: 0x10|0x02|0x33|0x30|0x75|0x10|0x03|
packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x00|0x13|0x10|0x03|
packet To 0x33 of type iAq 1Tch status | HEX: 0x10|0x02|0x33|0x71|0x07|0x01|0x02|0x03|0x04|0x05|0x12|0x20|0x00|0x07|0x41|0x6c|0x6c|0x20|0x4f|0x46|0x46|0x00|0x08|0x53|0x70|0x61|0x20|0x4d|0x6f|0x64|0x65|0x00|0x0a|0x43|0x6c|0x65|0x61|0x6e|0x20|0x4d|0x6f|0x64|0x65|0x00|0x08|0x42|0x75|0x62|0x62|0x6c|0x65|0x72|0x73|0x00|0x0a|0x57|0x61|0x74|0x65|0x72|0x66|0x61|0x6c|0x6c|0x31|0x04|0x01|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x23|0x00|0x00|0x18|0x00|0x00|0x01|0x00|0xd3|0x10|0x03|
All OFF = Off | index 0 type=(01 0x01) status=0x00 start=12 length=7
Spa Mode = Off | index 1 type=(02 0x02) status=0x00 start=21 length=8
Clean Mode = Off | index 2 type=(03 0x03) status=0x00 start=31 length=10
Bubblers = Off | index 3 type=(04 0x04) status=0x00 start=43 length=8
Waterfall1 = Off | index 4 type=(05 0x05) status=0x00 start=53 length=10
*** SWG is in the next bytes from iAq 1Tch status. But can't tell how to pull them since RPM is also here as well. ****
*/
/*
This works on rev Yg, but doesn't seem to T.2
Set Heat Pump Chiller Setpoint.
Set to 94 (0x5e in type iAqualnk sendCmd)
Byte 12 in iAqualink sendCmd (sets set point)
Byte 6 from end iAq Main status (looks like return)
Set to 94 (0x5e in type iAqualink sentCmd)
packet To 0x33 of type iAq Poll | HEX: 0x10|0x02|0x33|0x30|0x75|0x10|0x03|
packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x00|0x13|0x10|0x03|
packet To 0xa3 of type Unknown '0x73' | HEX: 0x10|0x02|0xa3|0x73|0x28|0x10|0x03|
packet To 0x00 of type iAqualnk sendCmd | HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x1f|0x00|0x4b|0x00|0x63|0x00|0x5e|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0xd5|0x10|0x03|
packet To 0x33 of type iAq Main status | HEX: 0x10|0x02|0x33|0x70|0x11|0x00|0x01|0x02|0x03|0x05|0x06|0x07|0x08|0x0e|0x0f|0x1a|0x1d|0x1f|0x20|0x21|0x24|0x25|0x01|0x00|0x00|0x00|0x00|0x4b|0x00|0x63|0x00|0x59|0x00|0x4f|0x00|0x00|0x00|0x37|0x18|0x42|0x30|0x33|0x31|0x36|0x38|0x32|0x33|0x20|0x52|0x53|0x2d|0x34|0x20|0x43|0x6f|0x6d|0x62|0x6f|0x00|0x00|0x00|0x5e|0x00|0x37|0xfd|0x10|0x03|
Set to 92 (0x5c in type iAqualink sentCmd)
packet To 0x33 of type iAq Poll | HEX: 0x10|0x02|0x33|0x30|0x75|0x10|0x03|
packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x00|0x13|0x10|0x03|
packet To 0xa3 of type Unknown '0x73' | HEX: 0x10|0x02|0xa3|0x73|0x28|0x10|0x03|
packet To 0x00 of type iAqualnk sendCmd | HEX: 0x10|0x02|0x00|0x24|0x73|0x01|0x1f|0x00|0x4b|0x00|0x63|0x00|0x5c|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0x00|0xd3|0x10|0x03|
packet To 0x33 of type iAq Main status | HEX: 0x10|0x02|0x33|0x70|0x11|0x00|0x01|0x02|0x03|0x05|0x06|0x07|0x08|0x0e|0x0f|0x1a|0x1d|0x1f|0x20|0x21|0x24|0x25|0x01|0x00|0x00|0x00|0x00|0x4b|0x00|0x63|0x00|0x59|0x00|0x4f|0x00|0x00|0x00|0x37|0x18|0x42|0x30|0x33|0x31|0x36|0x38|0x32|0x33|0x20|0x52|0x53|0x2d|0x34|0x20|0x43|0x6f|0x6d|0x62|0x6f|0x00|0x00|0x00|0x5c|0x00|0x37|0xfb|0x10|0x03|
*/

1599
source/json_messages.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,16 @@
//FUNCTION PROTOTYPES
#define JSON_LABEL_SIZE 300
#define JSON_STATUS_SIZE 800
//#define JSON_LABEL_SIZE 300
//#define JSON_BUFFER_SIZE 4000
//#define JSON_STATUS_SIZE 1024
#define JSON_LABEL_SIZE 600
//#define JSON_BUFFER_SIZE 8192
//#define JSON_BUFFER_SIZE 10240
#define JSON_BUFFER_SIZE 12288
#define JSON_STATUS_SIZE 2048
#define JSON_SIMULATOR_SIZE 2048
#define JSON_MQTT_MSG_SIZE 100
#define JSON_ON "on"
@ -20,7 +28,8 @@
#define JSON_LOW "low"
#define JSON_PROGRAMMING "Programming"
#define JSON_SERVICE "Service"
#define JSON_SERVICE "Service Mode"
#define JSON_TIMEOUT "Timeout Mode"
#define JSON_READY "Ready"
struct JSONkeyvalue{
@ -32,16 +41,29 @@ struct JSONwebrequest {
struct JSONkeyvalue second;
struct JSONkeyvalue third;
};
struct JSONkvptr {
struct JSONkeyvalue kv[4];
};
const char* getAqualinkDStatusMessage(struct aqualinkdata *aqdata);
int build_aqualink_status_JSON(struct aqualinkdata *aqdata, char* buffer, int size);
int build_aux_labels_JSON(struct aqualinkdata *aqdata, char* buffer, int size);
bool parseJSONwebrequest(char *buffer, struct JSONwebrequest *request);
bool parseJSONrequest(char *buffer, struct JSONkvptr *request);
int build_logmsg_JSON(char *dest, int loglevel, const char *src, int dest_len, int src_len);
int build_mqtt_status_JSON(char* buffer, int size, int idx, int nvalue, float setpoint/*char *svalue*/);
bool parseJSONmqttrequest(const char *str, size_t len, int *idx, int *nvalue, char *svalue);
int build_aqualink_error_status_JSON(char* buffer, int size, char *msg);
int build_aqualink_error_status_JSON(char* buffer, int size, const char *msg);
int build_mqtt_status_message_JSON(char* buffer, int size, int idx, int nvalue, char *svalue);
int build_device_JSON(struct aqualinkdata *aqdata, int programable_switch, char* buffer, int size, bool homekit);
int build_aqualink_aqmanager_JSON(struct aqualinkdata *aqdata, char* buffer, int size);
//int build_device_JSON(struct aqualinkdata *aqdata, int programable_switch, char* buffer, int size, bool homekit);
//int build_device_JSON(struct aqualinkdata *aqdata, int programable_switch1, int programable_switch2, char* buffer, int size, bool homekit);
int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool homekit);
int build_aqualink_simulator_packet_JSON(struct aqualinkdata *aqdata, char* buffer, int size);
int build_aqualink_config_JSON(char* buffer, int size, struct aqualinkdata *aq_data);
char *LED2text(aqledstate state);
#endif /* JSON_MESSAGES_H_ */

Some files were not shown because too many files have changed in this diff Show More