Compare commits

...

161 Commits

Author SHA1 Message Date
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
216 changed files with 30677 additions and 13663 deletions

547
Makefile
View File

@ -1,29 +1,38 @@
#
# Options
#
# make // standard everything
# make debug // Give standard binary just with debugging
# 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
# make <other> // not documenting
#
# Valid flags for AQ_FLAGS
AQ_RS16 = true
#AQ_RS16 = true
AQ_PDA = true
AQ_ONETOUCH = true
AQ_IAQTOUCH = 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 to use
# 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 := -l pthread -l m
#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
# Standard compile flags
GCCFLAGS = -Wall -O3
@ -43,67 +52,82 @@ DBGFLAGS = -g -O0 -Wall -D AQ_DEBUG -D AQ_TM_DEBUG
#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 =
#MGFLAGS =
# Detect OS and set some specifics
ifeq ($(OS),Windows_NT)
# Windows Make.
RM = del /Q
MKDIR = mkdir
FixPath = $(subst /,\,$1)
else
UNAME_S := $(shell uname -s)
# Linux
ifeq ($(UNAME_S),Linux)
RM = rm -f
MKDIR = mkdir -p
FixPath = $1
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 )
# 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
# Main source files
SRCS = aqualinkd.c utils.c config.c aq_serial.c aq_panel.c aq_programmer.c net_services.c json_messages.c rs_msg_utils.c\
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\
mongoose.c
serial_logger.c mongoose.c hassio.c simulator.c sensors.c aq_systemutils.c timespec_subtract.c
AQ_FLAGS =
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_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
AQ_FLAGS := $(AQ_FLAGS) -D AQ_IAQTOUCH
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_RS16), true)
# AQ_FLAGS := $(AQ_FLAGS) -D AQ_RS16
#endif
ifeq ($(AQ_MEMCMP), true)
AQ_FLAGS := $(AQ_FLAGS) -D AQ_MEMCMP
endif
ifeq ($(AQ_NO_THREAD_NETSERVICE), true)
AQ_FLAGS := $(AQ_FLAGS) -D AQ_NO_THREAD_NETSERVICE
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
@ -114,27 +138,118 @@ 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
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
# 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
# Object files
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))
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))
# 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))
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
DEBG = ./release/aqualinkd-debug
DDEVICE = ./release/dummydevice
MAIN_ARM64 = ./release/aqualinkd-arm64
MAIN_ARMHF = ./release/aqualinkd-armhf
MAIN_AMD64 = ./release/aqualinkd-amd64
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)
# 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)
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)
@ -146,271 +261,155 @@ slog: $(SLOG)
aqdebug: $(DEBG)
$(info $(DEBG) has been compiled)
dummydevice: $(DDEVICE)
$(info $(DDEVICE) 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)
./release/install.sh
#install: $(MAIN)
install:
./release/install.sh from-make
# Rules to compile
$(OBJ_DIR)/%.o: %.c | $(OBJ_DIR)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
$(DBG_OBJ_DIR)/%.o: %.c | $(DBG_OBJ_DIR)
$(DBG_OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(DBG_OBJ_DIR)
$(CC) $(DBG_CFLAGS) $(INCLUDES) -c -o $@ $<
$(SL_OBJ_DIR)/%.o: %.c | $(SL_OBJ_DIR)
$(SL_OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(SL_OBJ_DIR)
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
$(DD_OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(DD_OBJ_DIR)
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
$(OBJ_DIR_ARMHF)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR_ARMHF)
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<
$(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) $(LIBS) -o $@ $^
$(MAIN): $(OBJ_FILES)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(LIBS)
$(DEBG): $(DBG_OBJ_FILES)
$(CC) $(DBG_CFLAGS) $(INCLUDES) $(LIBS) -o $@ $^
$(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) $(LIBS) -o $@ $^
$(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)
# Rules to make object directories.
$(OBJ_DIR):
$(MKDIR) $@
$(MKDIR) $(call FixPath,$@)
$(SL_OBJ_DIR):
$(MKDIR) $@
$(MKDIR) $(call FixPath,$@)
$(DD_OBJ_DIR):
$(MKDIR) $(call FixPath,$@)
$(DBG_OBJ_DIR):
$(MKDIR) $@
$(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
.PHONY: clean
clean:
$(RM) *.o *~ $(MAIN) $(MAIN_U) $(PLAY) $(PL_EXOBJ) $(DEBG)
$(RM) $(wildcard *.o) $(wildcard *~) $(OBJ_FILES) $(DBG_OBJ_FILES) $(SL_OBJ_FILES) $(MAIN) $(MAIN_U) $(PLAY) $(PL_EXOBJ) $(LOGR) $(PLAY) $(DEBG)
clean: clean-buildfiles
$(RM) *.o *~ $(MAIN) $(MAIN_U) $(PLAY) $(PL_EXOBJ) $(DEBG) $(DDEVICE)
$(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) $(OBJ_FILES_ARMHF) $(OBJ_FILES_ARM64) $(OBJ_FILES_AMD64) $(SL_OBJ_FILES_ARMHF) $(SL_OBJ_FILES_ARM64) $(SL_OBJ_FILES_AMD64)
define DO_NOT_USE
# OLD MAKEFILE, STILL NEED TO MOVE THE BELOW OVER TO NEW Makefile
LOGR = ./release/log_reader
PLAY = ./release/aqualinkd-player
#
# Options
#
# make // standard everything
# make debug // Give standard binary just with debugging
# make aqdebug // Compile with extra aqualink debug information like timings
# make slog // Serial logger
# make <other> // not documenting
#
# Valid flags for AQ_FLAGS
AQ_RS16 = true
AQ_PDA = true
AQ_ONETOUCH = true
AQ_IAQTOUCH = true
#AQ_MEMCMP = true // Not implimented correctly yet.
# Turn off threadded net services
AQ_NO_THREAD_NETSERVICE = false
# 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 )
# define the C compiler to use
CC = gcc
#LIBS := -lpthread -lm
LIBS := -l pthread -l m
#LIBS := -l pthread -l m -static # Take out -static, just for dev
# 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
# Standard debug flags
DGCCFLAGS = -Wall -O0 -g
# 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
# 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 =
# 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.c pda_menu.c \
# pda_aq_programmer.c devices_jandy.c onetouch.c onetouch_aq_programmer.c packetLogger.c devices_pentair.c color_lights.c mongoose.c
SRCS = aqualinkd.c utils.c config.c aq_serial.c aq_panel.c aq_programmer.c net_services.c json_messages.c rs_msg_utils.c\
devices_jandy.c packetLogger.c devices_pentair.c color_lights.c serialadapter.c aq_timer.c aq_scheduler.c web_config.c\
mongoose.c
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
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_NO_THREAD_NETSERVICE), true)
AQ_FLAGS := $(AQ_FLAGS) -D AQ_NO_THREAD_NETSERVICE
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 = timespec_subtract.c debug_timer.c
DBG_SRC = debug_timer.c
SL_SRC = serial_logger.c aq_serial.c utils.c packetLogger.c rs_msg_utils.c
LR_SRC = log_reader.c aq_serial.c utils.c packetLogger.c
PL_EXSRC = aq_serial.c
PL_EXOBJ = aq_serial_player.o
PL_SRC := $(filter-out aq_serial.c, $(SRCS))
OBJS = $(SRCS:.c=.o)
DBG_OBJS = $(DBG_SRC:.c=.o)
SL_OBJS = $(SL_SRC:.c=.o)
LR_OBJS = $(LR_SRC:.c=.o)
PL_OBJS = $(PL_SRC:.c=.o)
# define the executable file
MAIN = ./release/aqualinkd
SLOG = ./release/serial_logger
LOGR = ./release/log_reader
PLAY = ./release/aqualinkd-player
DEBG = ./release/aqualinkd-debug
all: $(MAIN) $(SLOG)
$(info $(MAIN) has been compiled)
$(info $(SLOG) has been compiled)
# debug, Just change compile flags and call MAIN
debug: CFLAGS = $(DFLAGS)
debug: $(MAIN)
$(info $(MAIN) has been compiled)
$(MAIN): $(OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $(MAIN) $(OBJS) $(LIBS)
$(info $(MAIN) has been compiled)
slog: $(SLOG)
$(info $(SLOG) has been compiled)
$(SLOG): CFLAGS := $(CFLAGS) -D SERIAL_LOGGER
$(SLOG): $(SL_OBJS)
# $(CC) $(CFLAGS) $(INCLUDES) -o $(SLOG) $(SL_OBJS)
$(CC) $(INCLUDES) -o $(SLOG) $(SL_OBJS)
#.PHONY: clean_slog_o
#clean_slog_o:
# $(RM) $(SL_OBJS)
#
#.PHONY: test
#test: $(SLOG)
#test: clean_slog_o
#test: $(MAIN)
# Shouldn't need to use any of these options unless you're developing.
aqdebug: $(DEBG)
$(info $(DEBG) has been compiled)
$(DEBG): CFLAGS = $(DBG_CFLAGS)
$(DEBG): $(OBJS) $(DBG_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $(DEBG) $(OBJS) $(DBG_OBJS) $(DBGFLAGS) $(LIBS)
logr: $(LOGR)
$(info $(LOGR) has been compiled)
$(LOGR): $(LR_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $(LOGR) $(LR_OBJS)
player: $(PLAY)
$(info $(PLAY) has been compiled)
$(PL_EXOBJ): $(PL_EXSRC)
$(CC) $(CFLAGS) -D PLAYBACK_MODE $(INCLUDES) -c $(PL_EXSRC) -o $(PL_EXOBJ)
$(PLAY): $(PL_OBJS) $(PL_EXOBJ)
$(CC) $(CFLAGS) $(INCLUDES) -o $(PLAY) $(PL_OBJS) $(PL_EXOBJ)
# Fof github publishing
.PHONY: git
git: clean $(MAIN) $(SLOG)
./release/git_version.sh
# 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 $@
.PHONY: clean
clean:
$(RM) *.o *~ $(MAIN) $(MAIN_U) $(PLAY) $(PL_EXOBJ) $(DEBG)
$(RM) $(wildcard *.o) $(wildcard *~) $(MAIN) $(MAIN_U) $(PLAY) $(PL_EXOBJ) $(LOGR) $(PLAY) $(DEBG)
depend: $(SRCS)
makedepend $(INCLUDES) $^
install: $(MAIN)
./release/install.sh
endef

219
README.md
View File

@ -1,7 +1,7 @@
# Aqualinkd
Linux daemon to control Aqualink RS pool controllers. Provides web UI, MQTT client & HTTP API endpoints. Control your pool equiptment from any phone/tablet or computer. Is also compatible with most Home control systems including Apple HomeKit, Samsung, Alexa, Google, etc.
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, Has bean, and can be compiled for many different SBC's.
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 security measures. e.g. VPNs.
@ -11,21 +11,29 @@ 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 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.
## AqualinkD discussions
* Please use github Discussions for for questions / issues / problems (Link at top of page).
https://github.com/sfeakes/AqualinkD/discussions
* 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/sfeakes/AqualinkD/issues
https://github.com/aqualinkd/AqualinkD/issues
## Please see Wiki for installation instructions
https://github.com/sfeakes/AqualinkD/wiki
https://github.com/aqualinkd/AqualinkD/wiki
<!--
For information on Control panel versions and upgrading the chips.<br>
https://github.com/sfeakes/AqualinkD/wiki/Upgrading-Jandy-Aqualink-PDA-to-RS-panel
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/sfeakes/AqualinkD/wiki/Jandy-Aqualink-RS485-protocol
https://github.com/aqualinkd/AqualinkD/wiki/Jandy-Aqualink-RS485-protocol
-->
## AqualinkD built in WEB Interface(s).
@ -56,9 +64,10 @@ https://github.com/sfeakes/AqualinkD/wiki/Jandy-Aqualink-RS485-protocol
</td></tr>
</table>
### Simulator
Designed to mimic AqualinkRS6 All Button keypad and (like the keypad) is used to fully 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>
@ -66,17 +75,180 @@ Designed to mimic AqualinkRS6 All Button keypad and (like the keypad) is used to
* 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/HomeAssistant2.png?raw=true" width="800"></img>
<img src="extras/HASSIO.png?raw=true" width="800"></img>
## All Web interfaces.
* 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 <- (RS8 All Button Control Panel simulator)
* http://aqualink.ip/debug.html <- (Turn on/off debug/serial debug & download logs)
* 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)
* Allow selecting of pre-defined VSP programs (Aqualink Touch & OneTouch protocols.)
* Add set time to OneTouch protocol.
* Create iAqualink Touch Simulator
* AqualinkD to self configure. (Done for ID's, need to do for Panel type/size)
<!--
* 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
* Try an auto-update
* Update Mongoose
-->
# Updates in 2.6.3
* 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
* 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
* 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.0
* 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
* <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
* Release removed. Bug with light program 0.
# Updates in Release 2.3.7
* 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
* 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
* 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
* 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
* 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
* 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
* Changed a lot of logic around different protocols.
@ -100,7 +272,7 @@ Designed to mimic AqualinkRS6 All Button keypad and (like the keypad) is used to
# Update in Release 2.2.2
* Fixed some Web UI bugs
* Color lights now quicker when selecting existing comor mode.
* 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)
@ -123,7 +295,7 @@ Designed to mimic AqualinkRS6 All Button keypad and (like the keypad) is used to
* 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/sfeakes/AqualinkD/wiki#Version_2</b>
* 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.
@ -140,7 +312,7 @@ Designed to mimic AqualinkRS6 All Button keypad and (like the keypad) is used to
* 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/sfeakes/AqualinkD/wiki#Version_2</b>
* 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)
@ -286,12 +458,15 @@ Designed to mimic AqualinkRS6 All Button keypad and (like the keypad) is used to
* 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 control panels. It will work with Aqualink PDA/AquaPalm and NON Combo iAqualink; but with limitations.
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 :-
@ -309,7 +484,7 @@ Below are verified versions, but should work with any AqualinkRS :-
If you have tested a version not listed here, please let me know by opening an issue.
#
-->
# License
## Non Commercial Project
All non commercial projects can be run using our open source code under GPLv2 licensing. As long as your project remains in this category there is no charge.

1235
aq_panel.c

File diff suppressed because it is too large Load Diff

View File

@ -1,78 +0,0 @@
#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
*/
// 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
//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();
bool panel_device_request(struct aqualinkdata *aqdata, action_type type, int deviceIndex, int value, request_source source);
void changePanelToMode_Only();
void addPanelOneTouchInterface();
void addPanelIAQTouchInterface();
void addPanelRSserialAdapterInterface();
void changePanelToExtendedIDProgramming();
//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)
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))))))
*/
#ifndef AQ_RS16
#define TOTAL_BUTTONS 12
#else
#define TOTAL_BUTTONS 20
// This needs to be called AFTER and as well as initButtons
void initButtons_RS16(struct aqualinkdata *aqdata);
#endif
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,285 +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 <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 "utils.h"
/*
Example /etc/cron.d/aqualinkd
01 10 1 * * curl localhost:80/api/Filter_Pump/set -d value=2 -X PUT
*/
bool remount_root_ro(bool readonly) {
// NSF Check if config is RO_ROOT set
if (readonly) {
LOG(SCHD_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(SCHD_LOG,LOG_INFO, "reMounting root RW\n");
mount (NULL, "/", NULL, MS_REMOUNT, NULL);
return true;
}
}
bool passJson_scObj(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(char* inBuf, int inSize, char* outBuf, int outSize)
{
int length=0;
FILE *fp;
int i;
bool inarray = false;
aqs_cron cline;
if ( !_aqconfig_.enable_scheduler) {
LOG(SCHD_LOG,LOG_WARNING, "Schedules are disabled\n");
length += sprintf(outBuf, "{\"message\":\"Error Schedules disabled\"}");
return length;
}
LOG(SCHD_LOG,LOG_NOTICE, "Saving Schedule:\n");
bool fs = remount_root_ro(false);
fp = fopen(CRON_FILE, "w");
if (fp == NULL) {
LOG(SCHD_LOG,LOG_ERR, "Open file failed '%s'\n", CRON_FILE);
remount_root_ro(true);
length += sprintf(outBuf, "{\"message\":\"Error Saving Schedules\"}");
return length;
}
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);
length += sprintf(outBuf, "{\"message\":\"Saved Schedules\"}");
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 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 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);
} else if ( inarray && inBuf[i] == '}') {
//inobj=false;
//objed=i;
}
}
fprintf(fp, "#***** AUTO GENERATED DO NOT EDIT *****\n");
fclose(fp);
remount_root_ro(fs);
return length;
}
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");
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]+).*";
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];
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);
length += sprintf(buffer+length,"\"message\": \"Error reading schedules\"}");
return length;
}
fp = fopen(CRON_FILE, "r");
if (fp == NULL) {
LOG(SCHD_LOG,LOG_ERR, "Open file failed '%s'\n", CRON_FILE);
length += sprintf(buffer+length,"\"message\": \"Error reading schedules\"}");
return length;
}
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) {
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));
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);
}
} else {
LOG(SCHD_LOG,LOG_DEBUG, "regexp no match (%d) %s\n", rc, line);
}
}
buffer[--length] = '\0';
length += sprintf(buffer+length,"]}\n");
fclose(fp);
regfree(&regexCompiled);
return length;
}

View File

@ -1,31 +0,0 @@
#ifndef AQ_SCHEDULER_H_
#define AQ_SCHEDULER_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(char* inBuf, int inSize, char* outBuf, int outSize);
//void read_schedules();
//void write_schedules();
#endif // AQ_SCHEDULER_H_

File diff suppressed because it is too large Load Diff

View File

@ -1,145 +0,0 @@
#include <stdio.h>
#include <string.h>
//#define COLOR_LIGHTS_C_
#include "color_lights.h"
/****** This list MUST be in order of clight_type enum *******/
const char *_color_light_options[NUMBER_LIGHT_COLOR_TYPES][LIGHT_COLOR_OPTIONS] =
{
// AqualnkD Colors ignored as no names in control panel.
{ "bogus" },
{ // Jandy Color
"Alpine White",
"Sky Blue",
"Cobalt Blue",
"Caribbean Blue",
"Spring Green",
"Emerald Green",
"Emerald Rose",
"Magenta",
"Violet",
"Color Splash"
},
{ // Jandy LED
"Alpine White",
"Sky Blue",
"Cobalt Blue",
"Caribbean Blue",
"Spring Green",
"Emerald Green",
"Emerald Rose",
"Magenta",
"Violet",
"Slow Splash",
"Fast Splash",
"USA",
"Fat Tuesday",
"Disco Tech"
},
{ // SAm/SAL
"White",
"Light Green",
"Green",
"Cyan",
"Blue",
"Lavender",
"Magenta"
},
{ // Color Logic
"Voodoo Lounge",
"Blue Sea",
"Royal Blue",
"Afternoon Skies",
//"Aqua Green",
"Emerald",
"Sangria",
"Cloud White",
//"Warm Red",
//"Flamingo",
//"Vivid Violet",
//"Sangria",
"Twilight",
"Tranquility",
"Gemstone",
"USA",
"Mardi Gras",
"Cool Cabaret"
},
{ // IntelliBrite
"SAm",
"Party",
"Romance",
"Caribbean",
"American",
"Cal Sunset",
"Royal",
"Blue",
"Green",
"Red",
"White",
"Magenta"
},
{ // Dimmer
"25%",
"50%",
"75%",
"100%"
}
};
const char *light_mode_name(clight_type type, int index)
{
return _color_light_options[type][index];
}
bool isShowMode(const char *mode)
{
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;
}
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");
length += sprintf(buffer+length, "_light_program[0] = light_program;\n");
for (i=1; i < NUMBER_LIGHT_COLOR_TYPES; i++) {
length += sprintf(buffer+length, "_light_program[%d] = [", i);
for (j=0; j < LIGHT_COLOR_OPTIONS; j++) {
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;
}

925
config.c
View File

@ -1,925 +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 <errno.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>
#define CONFIG_C
#include "config.h"
#include "utils.h"
#include "aq_serial.h"
#include "aq_panel.h"
#include "aqualink.h"
#define MAXCFGLINE 256
char *generate_mqtt_id(char *buf, int len);
pump_detail *getpump(struct aqualinkdata *aqdata, int button);
struct tmpPanelInfo {
int size;
bool rs;
bool combo;
bool dual;
};
struct tmpPanelInfo *_tmpPanel;
/*
* initialize data to default values
*/
void init_parameters (struct aqconfig * parms)
{
// Set default panel if it get's missed from config
_tmpPanel = malloc(sizeof(struct tmpPanelInfo));
_tmpPanel->size = 8;
_tmpPanel->rs = true;
_tmpPanel->combo = true;
_tmpPanel->dual = false;
clearDebugLogMask();
//int i;
//char *p;
//parms->rs_panel_size = 8;
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);
parms->rssa_device_id = NUL;
parms->RSSD_LOG_filter = NUL;
parms->paneltype_mask = 0;
#if defined AQ_ONETOUCH || defined AQ_IAQTOUCH
parms->extended_device_id = NUL;
parms->extended_device_id_programming = false;
#endif
//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_swg_percent = TEMP_UNKNOWN;
parms->dzidx_swg_ppm = 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_pool = TEMP_UNKNOWN;
//parms->light_programming_button_spa = TEMP_UNKNOWN;
parms->deamonize = true;
parms->log_file = '\0';
#ifdef AQ_PDA
parms->pda_sleep_mode = false;
#endif
//parms->onetouch_mode = false;
parms->convert_mqtt_temp = true;
parms->convert_dz_temp = true;
parms->report_zero_pool_temp = false;
parms->report_zero_spa_temp = false;
//parms->read_all_devices = true;
//parms->read_pentair_packets = false;
parms->read_RS485_devmask = 0;
parms->use_panel_aux_labels = false;
parms->force_swg = false;
parms->force_ps_setpoints = false;
//parms->swg_pool_and_spa = false;
parms->swg_zero_ignore = DEFAULT_SWG_ZERO_IGNORE_COUNT;
parms->display_warnings_web = false;
parms->log_protocol_packets = false; // Read & Write as packets write to file
parms->log_raw_bytes = false; // bytes read and write to file
parms->sync_panel_time = true;
// Default parameters for threading and USB blocking
parms->readahead_b4_write = false;
#ifdef AQ_NO_THREAD_NETSERVICE
parms->rs_poll_speed = DEFAULT_POLL_SPEED;
parms->thread_netservices = true;
#endif
parms->enable_scheduler = true;
parms->ftdi_low_latency = true;
parms->prioritize_ack = false;
generate_mqtt_id(parms->mqtt_ID, MQTT_ID_LEN);
}
char *cleanalloc(char*str)
{
if (str == NULL)
return 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) {
//_aqconfig_.socket_port = cleanint(indx+1);
_aqconfig_.socket_port = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "serial_port", 11) == 0) {
_aqconfig_.serial_port = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "log_level", 9) == 0) {
_aqconfig_.log_level = text2elevel(cleanalloc(indx+1));
// should fee mem here
} else if (strncasecmp (b_ptr, "device_id", 9) == 0) {
_aqconfig_.device_id = strtoul(cleanalloc(indx+1), NULL, 16);
// should fee mem here
} else if (strncasecmp (b_ptr, "web_directory", 13) == 0) {
_aqconfig_.web_directory = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "log_file", 8) == 0) {
_aqconfig_.log_file = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "mqtt_address", 12) == 0) {
_aqconfig_.mqtt_server = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "mqtt_dz_sub_topic", 17) == 0) {
_aqconfig_.mqtt_dz_sub_topic = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "mqtt_dz_pub_topic", 17) == 0) {
_aqconfig_.mqtt_dz_pub_topic = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "mqtt_aq_topic", 13) == 0) {
_aqconfig_.mqtt_aq_topic = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "mqtt_user", 9) == 0) {
_aqconfig_.mqtt_user = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "mqtt_passwd", 11) == 0) {
_aqconfig_.mqtt_passwd = cleanalloc(indx+1);
} else if (strncasecmp (b_ptr, "air_temp_dzidx", 14) == 0) {
_aqconfig_.dzidx_air_temp = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "pool_water_temp_dzidx", 21) == 0) {
_aqconfig_.dzidx_pool_water_temp = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "spa_water_temp_dzidx", 20) == 0) {
_aqconfig_.dzidx_spa_water_temp = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "light_programming_mode", 21) == 0) {
_aqconfig_.light_programming_mode = atof(cleanalloc(indx+1)); // should free this
} else if (strncasecmp (b_ptr, "light_programming_initial_on", 28) == 0) {
_aqconfig_.light_programming_initial_on = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "light_programming_initial_off", 29) == 0) {
_aqconfig_.light_programming_initial_off = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "light_programming_button", 21) == 0) {
_aqconfig_.light_programming_button = strtoul(indx+1, NULL, 10) - 1;
} else if (strncasecmp (b_ptr, "SWG_percent_dzidx", 17) == 0) {
_aqconfig_.dzidx_swg_percent = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "SWG_PPM_dzidx", 13) == 0) {
_aqconfig_.dzidx_swg_ppm = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "SWG_Status_dzidx", 14) == 0) {
_aqconfig_.dzidx_swg_status = strtoul(indx+1, NULL, 10);
} else if (strncasecmp (b_ptr, "override_freeze_protect", 23) == 0) {
_aqconfig_.override_freeze_protect = text2bool(indx+1);
} else if (strncasecmp (b_ptr, "pda_mode", 8) == 0) {
_aqconfig_.pda_mode = text2bool(indx+1);
set_pda_mode(_aqconfig_.pda_mode);
} else if (strncasecmp (b_ptr, "convert_mqtt_temp_to_c", 22) == 0) {
_aqconfig_.convert_mqtt_temp = text2bool(indx+1);
} else if (strncasecmp (b_ptr, "convert_dz_temp_to_c", 21) == 0) {
_aqconfig_.convert_dz_temp = text2bool(indx+1);
} else if (strncasecmp (b_ptr, "flash_mqtt_buttons", 18) == 0) {
_aqconfig_.flash_mqtt_buttons = text2bool(indx+1);
} else if (strncasecmp (b_ptr, "report_zero_pool_temp", 21) == 0) {
_aqconfig_.report_zero_pool_temp = text2bool(indx+1);
} else if (strncasecmp (b_ptr, "report_zero_spa_temp", 20) == 0) {
_aqconfig_.report_zero_spa_temp = text2bool(indx+1);
} else if (strncasecmp (b_ptr, "report_zero_pool_temp", 21) == 0) {
_aqconfig_.report_zero_pool_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 aqualinkdata *aqdata, char *param, char *value) {
bool rtn = false;
if (strncasecmp(param, "debug_log_mask", 14) == 0) {
addDebugLogMask(strtoul(value, NULL, 10));
rtn=true;
} else if (strncasecmp(param, "socket_port", 11) == 0) {
_aqconfig_.socket_port = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "serial_port", 11) == 0) {
_aqconfig_.serial_port = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "log_level", 9) == 0) {
_aqconfig_.log_level = text2elevel(cleanalloc(value));
rtn=true;
} else if (strncasecmp(param, "device_id", 9) == 0) {
_aqconfig_.device_id = strtoul(cleanalloc(value), NULL, 16);
rtn=true;
} else if (strncasecmp(param, "rssa_device_id", 14) == 0) {
_aqconfig_.rssa_device_id = strtoul(cleanalloc(value), NULL, 16);
rtn=true;
} else if (strncasecmp(param, "RSSD_LOG_filter", 15) == 0) {
_aqconfig_.RSSD_LOG_filter = strtoul(cleanalloc(value), NULL, 16);
rtn=true;
#if defined AQ_ONETOUCH || defined AQ_IAQTOUCH
} else if (strncasecmp (param, "extended_device_id_programming", 30) == 0) {
// Has to be before the below.
_aqconfig_.extended_device_id_programming = text2bool(value);
rtn=true;
} else if (strncasecmp(param, "extended_device_id", 9) == 0) {
_aqconfig_.extended_device_id = strtoul(cleanalloc(value), NULL, 16);
//_config_parameters.onetouch_device_id != 0x00
rtn=true;
#endif
} else if (strncasecmp(param, "panel_type_size", 15) == 0) {
_tmpPanel->size = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "panel_type_combo", 16) == 0) {
_tmpPanel->combo = text2bool(value);
rtn=true;
} else if (strncasecmp(param, "panel_type_dual", 15) == 0) {
_tmpPanel->dual = text2bool(value);
rtn=true;
} else if (strncasecmp(param, "panel_type_pda", 14) == 0) {
_tmpPanel->rs = !text2bool(value);
rtn=true;
} else if (strncasecmp(param, "panel_type_rs", 13) == 0) {
_tmpPanel->rs = text2bool(value);
rtn=true;
} else if (strncasecmp(param, "panel_type", 10) == 0) { // This must be last so it doesn't get picked up by other settings
setPanelByName(aqdata, cleanwhitespace(value));
rtn=true;
} else if (strncasecmp(param, "rs_panel_size", 13) == 0) {
LOG(AQUA_LOG,LOG_WARNING, "Config error, 'rs_panel_size' no longer supported, please use 'panel_type'\n");
_tmpPanel->size = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "web_directory", 13) == 0) {
_aqconfig_.web_directory = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "log_file", 8) == 0) {
_aqconfig_.log_file = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "mqtt_address", 12) == 0) {
_aqconfig_.mqtt_server = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "mqtt_dz_sub_topic", 17) == 0) {
_aqconfig_.mqtt_dz_sub_topic = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "mqtt_dz_pub_topic", 17) == 0) {
_aqconfig_.mqtt_dz_pub_topic = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "mqtt_aq_topic", 13) == 0) {
_aqconfig_.mqtt_aq_topic = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "mqtt_user", 9) == 0) {
_aqconfig_.mqtt_user = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "mqtt_passwd", 11) == 0) {
_aqconfig_.mqtt_passwd = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "air_temp_dzidx", 14) == 0) {
_aqconfig_.dzidx_air_temp = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "pool_water_temp_dzidx", 21) == 0) {
_aqconfig_.dzidx_pool_water_temp = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "spa_water_temp_dzidx", 20) == 0) {
_aqconfig_.dzidx_spa_water_temp = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "light_programming_mode", 21) == 0) {
_aqconfig_.light_programming_mode = atof(cleanalloc(value)); // should free this
rtn=true;
} else if (strncasecmp(param, "light_programming_initial_on", 28) == 0) {
_aqconfig_.light_programming_initial_on = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "light_programming_initial_off", 29) == 0) {
_aqconfig_.light_programming_initial_off = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "light_programming_button_spa", 28) == 0) {
LOG(AQUA_LOG,LOG_ERR, "Config error, 'light_programming_button_spa' no longer supported\n");
//_aqconfig_.light_programming_button_spa = strtoul(value, NULL, 10) - 1;
rtn=true;
} else if (strncasecmp(param, "light_programming_button", 24) == 0 ||
strncasecmp(param, "light_programming_button_pool", 29) == 0) {
LOG(AQUA_LOG,LOG_ERR, "Config error, 'light_programming_button' & 'light_programming_button_pool' are no longer supported\n");
//_aqconfig_.light_programming_button_pool = strtoul(value, NULL, 10) - 1;
rtn=true;
} else if (strncasecmp(param, "SWG_percent_dzidx", 17) == 0) {
_aqconfig_.dzidx_swg_percent = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "SWG_PPM_dzidx", 13) == 0) {
_aqconfig_.dzidx_swg_ppm = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "SWG_Status_dzidx", 14) == 0) {
_aqconfig_.dzidx_swg_status = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "override_freeze_protect", 23) == 0) {
_aqconfig_.override_freeze_protect = text2bool(value);
rtn=true;
#ifdef AQ_PDA
} else if (strncasecmp(param, "pda_mode", 8) == 0) {
LOG(AQUA_LOG,LOG_WARNING, "Config error, 'pda_mode' is no longer supported, please use rs_panel_type\n");
//_aqconfig_.pda_mode = text2bool(value);
//set_pda_mode(_aqconfig_.pda_mode);
_tmpPanel->rs = !text2bool(value);
rtn=true;
} else if (strncasecmp(param, "pda_sleep_mode", 8) == 0) {
_aqconfig_.pda_sleep_mode = text2bool(value);
rtn=true;
#endif
} else if (strncasecmp(param, "convert_mqtt_temp_to_c", 22) == 0) {
_aqconfig_.convert_mqtt_temp = text2bool(value);
rtn=true;
} else if (strncasecmp(param, "convert_dz_temp_to_c", 20) == 0) {
_aqconfig_.convert_dz_temp = text2bool(value);
rtn=true;
/*
} else if (strncasecmp(param, "flash_mqtt_buttons", 18) == 0) {
_aqconfig_.flash_mqtt_buttons = text2bool(value);
rtn=true;*/
} else if (strncasecmp(param, "report_zero_spa_temp", 20) == 0) {
_aqconfig_.report_zero_spa_temp = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "report_zero_pool_temp", 21) == 0) {
_aqconfig_.report_zero_pool_temp = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "read_all_devices", 16) == 0) {
LOG(AQUA_LOG,LOG_WARNING, "Config error, 'read_all_devices' is no longer supported, please using one or all of 'read_RS485_swg','read_RS485_ePump','read_RS485_vsfPump'\n");
if (text2bool(value)) {
_aqconfig_.read_RS485_devmask |= READ_RS485_SWG;
_aqconfig_.read_RS485_devmask |= READ_RS485_JAN_PUMP;
} else {
_aqconfig_.read_RS485_devmask &= ~READ_RS485_SWG;
_aqconfig_.read_RS485_devmask &= ~READ_RS485_JAN_PUMP;
}
rtn=true;
} else if (strncasecmp (param, "read_pentair_packets", 17) == 0) {
LOG(AQUA_LOG,LOG_WARNING, "Config error, 'read_all_devices' is no longer supported, please using 'read_pentair_pump'\n");
if (text2bool(value))
_aqconfig_.read_RS485_devmask |= READ_RS485_PEN_PUMP;
else
_aqconfig_.read_RS485_devmask &= ~READ_RS485_PEN_PUMP;
rtn=true;
} else if (strncasecmp (param, "read_RS485_swg", 14) == 0) {
if (text2bool(value))
_aqconfig_.read_RS485_devmask |= READ_RS485_SWG;
else
_aqconfig_.read_RS485_devmask &= ~READ_RS485_SWG;
rtn=true;
} else if (strncasecmp (param, "read_RS485_ePump", 16) == 0) {
if (text2bool(value))
_aqconfig_.read_RS485_devmask |= READ_RS485_JAN_PUMP;
else
_aqconfig_.read_RS485_devmask &= ~READ_RS485_JAN_PUMP;
rtn=true;
} else if (strncasecmp (param, "read_RS485_vsfPump", 16) == 0) {
if (text2bool(value))
_aqconfig_.read_RS485_devmask |= READ_RS485_PEN_PUMP;
else
_aqconfig_.read_RS485_devmask &= ~READ_RS485_PEN_PUMP;
rtn=true;
} else if (strncasecmp (param, "use_panel_aux_labels", 20) == 0) {
_aqconfig_.use_panel_aux_labels = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "force_SWG", 9) == 0) {
_aqconfig_.force_swg = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "force_ps_setpoints", 18) == 0) {
_aqconfig_.force_ps_setpoints = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "debug_RSProtocol_bytes", 22) == 0) {
_aqconfig_.log_raw_bytes = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "debug_RSProtocol_packets", 24) == 0) {
_aqconfig_.log_protocol_packets = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "swg_zero_ignore_count", 21) == 0) {
_aqconfig_.swg_zero_ignore = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp (param, "display_warnings_in_web", 23) == 0) {
_aqconfig_.display_warnings_web = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "serial_readahead_b4_write", 25) == 0) {
_aqconfig_.readahead_b4_write = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "mqtt_timed_update", 17) == 0) {
_aqconfig_.mqtt_timed_update = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "keep_paneltime_synced", 21) == 0) {
_aqconfig_.sync_panel_time = text2bool(value);
rtn=true;
#ifdef AQ_NO_THREAD_NETSERVICE
} else if (strncasecmp (param, "network_poll_speed", 18) == 0) {
LOG(AQUA_LOG,LOG_WARNING, "Config error, 'network_poll_speed' is no longer supported, using value for 'rs_poll_speed'\n");
_aqconfig_.rs_poll_speed = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp (param, "rs_poll_speed", 13) == 0) {
_aqconfig_.rs_poll_speed = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp (param, "thread_netservices", 18) == 0) {
_aqconfig_.thread_netservices = text2bool(value);
rtn=true;
#endif
} else if (strncasecmp (param, "enable_scheduler", 16) == 0) {
_aqconfig_.enable_scheduler = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "ftdi_low_latency", 16) == 0) {
_aqconfig_.ftdi_low_latency = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "prioritize_ack", 14) == 0) {
_aqconfig_.prioritize_ack = text2bool(value);
rtn=true;
}
else if (strncasecmp(param, "button_", 7) == 0) {
// Check we have inichalized panel information, if not use any settings we may have
if (_aqconfig_.paneltype_mask == 0)
setPanel(aqdata, _tmpPanel->rs, _tmpPanel->size, _tmpPanel->combo, _tmpPanel->dual);
int num = strtoul(param + 7, NULL, 10) - 1;
if (num > TOTAL_BUTTONS) {
LOG(AQUA_LOG,LOG_ERR, "Config error, button_%d is out of range\n",num+1);
rtn=false;
} else 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;
#ifdef AQ_PDA
} else if (strncasecmp(param + 9, "_PDA_label", 10) == 0) {
LOG(AQUA_LOG,LOG_WARNING, "Config error, 'button_%d_PDA_label' is no longer supported, please use 'button_%d_label'\n",num,num);
//aqdata->aqbuttons[num].pda_label = cleanalloc(value);
aqdata->aqbuttons[num].label = cleanalloc(value);
rtn=true;
#endif
} else if (strncasecmp(param + 9, "_lightMode", 10) == 0) {
if (aqdata->num_lights < MAX_LIGHTS) {
int type = strtoul(value, NULL, 10);
if (type < LC_PROGRAMABLE || type > NUMBER_LIGHT_COLOR_TYPES) {
LOG(AQUA_LOG,LOG_ERR, "Config error, unknown light mode '%s'\n",type);
} else {
aqdata->lights[aqdata->num_lights].button = &aqdata->aqbuttons[num];
aqdata->lights[aqdata->num_lights].lightType = type;
aqdata->num_lights++;
aqdata->aqbuttons[num].special_mask |= PROGRAM_LIGHT;
}
} else {
LOG(AQUA_LOG,LOG_ERR, "Config error, (colored|programmable) Lights limited to %d, ignoring %s'\n",MAX_LIGHTS,param);
}
rtn=true;
} else if (strncasecmp(param + 9, "_pumpID", 7) == 0) {
pump_detail *pump = getpump(aqdata, num);
if (pump != NULL) {
pump->pumpID = strtoul(cleanalloc(value), NULL, 16);
if ( (int)pump->pumpID <= PENTAIR_DEC_PUMP_MAX) {
pump->prclType = PENTAIR;
} else {
pump->prclType = JANDY;
//pump->pumpType = EPUMP; // For testing let the interface set this
}
} else {
LOG(AQUA_LOG,LOG_ERR, "Config error, VSP Pumps limited to %d, ignoring %s'\n",MAX_PUMPS,param);
}
rtn=true;
} else if (strncasecmp(param + 9, "_pumpIndex", 10) == 0) { //button_01_pumpIndex=1
pump_detail *pump = getpump(aqdata, num);
if (pump != NULL) {
pump->pumpIndex = strtoul(value, NULL, 10);
} else {
LOG(AQUA_LOG,LOG_ERR, "Config error, VSP Pumps limited to %d, ignoring %s'\n",MAX_PUMPS,param);
}
rtn=true;
}
/*
} else if (strncasecmp(param + 9, "_pumpID", 7) == 0) {
//aqdata->aqbuttons[num].pda_label = cleanalloc(value);
//96 to 111 = Pentair, 120 to 123 = Jandy
if (pi < MAX_PUMPS) {
aqdata->pumps[pi].button = &aqdata->aqbuttons[num];
aqdata->pumps[pi].pumpID = strtoul(cleanalloc(value), NULL, 16);
aqdata->pumps[pi].pumpIndex = pi+1;
//aqdata->pumps[pi].buttonID = num;
if (aqdata->pumps[pi].pumpID < 119)
aqdata->pumps[pi].ptype = PENTAIR;
else
aqdata->pumps[pi].ptype = JANDY;
pi++;
} else {
LOG(AQUA_LOG,LOG_ERR, "Config error, VSP Pumps limited to %d, ignoring %s'\n",MAX_PUMPS,param);
}
rtn=true;
} else if (strncasecmp(param + 9, "_pumpIndex", 10) == 0) { //button_01_pumpIndex=1
}*/
}
return rtn;
}
pump_detail *getpump(struct aqualinkdata *aqdata, int button)
{
//static int _pumpindex = 0;
//aqdata->num_pumps
int pi;
// Does it exist
for (pi=0; pi < aqdata->num_pumps; pi++) {
if (aqdata->pumps[pi].button == &aqdata->aqbuttons[button]) {
//printf ("Found pump %d\n",button);
return &aqdata->pumps[pi];
}
}
// Create new entry
if (aqdata->num_pumps < MAX_PUMPS) {
//printf ("Creating pump %d\n",button);
aqdata->aqbuttons[button].special_mask |= VS_PUMP;
aqdata->pumps[aqdata->num_pumps].button = &aqdata->aqbuttons[button];
aqdata->pumps[aqdata->num_pumps].pumpType = PT_UNKNOWN;
aqdata->pumps[aqdata->num_pumps].rpm = TEMP_UNKNOWN;
aqdata->pumps[aqdata->num_pumps].watts = TEMP_UNKNOWN;
aqdata->pumps[aqdata->num_pumps].gpm = TEMP_UNKNOWN;
aqdata->num_pumps++;
return &aqdata->pumps[aqdata->num_pumps-1];
}
return NULL;
}
void init_config()
{
init_parameters(&_aqconfig_);
}
//void readCfg (struct aqconfig *config_parameters, struct aqualinkdata *aqdata, char *cfgFile)
void read_config (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;
_aqconfig_.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(aqdata, b_ptr, indx+1))
LOG(AQUA_LOG,LOG_ERR, "Unknown config parameter '%.*s'\n",strlen(b_ptr)-1, b_ptr);
}
}
}
}
fclose(fp);
} else {
/* error processing, couldn't open file */
LOG(AQUA_LOG,LOG_ERR, "Error reading config file '%s'\n",cfgFile);
errno = EBADF;
displayLastSystemError("Error reading config file");
exit (EXIT_FAILURE);
}
free(_tmpPanel);
}
//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) {} // Dummy to stop compile warnings.
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;
}
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 aqualinkdata *aqdata)
{
LOG(AQUA_LOG,LOG_ERR, "writeCfg() not implimented\n");
/*
FILE *fp;
int i;
bool fs = remount_root_ro(false);
fp = fopen(_aqconfig_.config_file, "w");
if (fp == NULL) {
LOG(AQUA_LOG,LOG_ERR, "Open config file failed '%s'\n", _aqconfig_.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", _aqconfig_.socket_port);
fprintf(fp, "serial_port = %s\n", _aqconfig_.serial_port);
fprintf(fp, "device_id = 0x%02hhx\n", _aqconfig_.device_id);
fprintf(fp, "read_all_devices = %s", bool2text(_aqconfig_.read_all_devices));
writeCharValue(fp, "log_level", errorlevel2text(_aqconfig_.log_level));
writeCharValue(fp, "web_directory", _aqconfig_.web_directory);
writeCharValue(fp, "log_file", _aqconfig_.log_file);
fprintf(fp, "pda_mode = %s\n", bool2text(_aqconfig_.pda_mode));
fprintf(fp, "\n#** MQTT Configuration **\n");
writeCharValue(fp, "mqtt_address", _aqconfig_.mqtt_server);
writeCharValue(fp, "mqtt_dz_sub_topic", _aqconfig_.mqtt_dz_sub_topic);
writeCharValue(fp, "mqtt_dz_pub_topic", _aqconfig_.mqtt_dz_pub_topic);
writeCharValue(fp, "mqtt_aq_topic", _aqconfig_.mqtt_aq_topic);
writeCharValue(fp, "mqtt_user", _aqconfig_.mqtt_user);
writeCharValue(fp, "mqtt_passwd", _aqconfig_.mqtt_passwd);
fprintf(fp, "\n#** General **\n");
fprintf(fp, "convert_mqtt_temp_to_c = %s\n", bool2text(_aqconfig_.convert_mqtt_temp));
fprintf(fp, "override_freeze_protect = %s\n", bool2text(_aqconfig_.override_freeze_protect));
//fprintf(fp, "flash_mqtt_buttons = %s\n", bool2text(_aqconfig_.flash_mqtt_buttons));
fprintf(fp, "report_zero_spa_temp = %s\n", bool2text(_aqconfig_.report_zero_spa_temp));
fprintf(fp, "report_zero_pool_temp = %s\n", bool2text(_aqconfig_.report_zero_pool_temp));
fprintf(fp, "\n#** Programmable light **\n");
//if (_aqconfig_.light_programming_button_pool <= 0) {
// fprintf(fp, "#light_programming_button_pool = %d\n", _aqconfig_.light_programming_button_pool);
// fprintf(fp, "#light_programming_mode = %f\n", _aqconfig_.light_programming_mode);
// fprintf(fp, "#light_programming_initial_on = %d\n", _aqconfig_.light_programming_initial_on);
// fprintf(fp, "#light_programming_initial_off = %d\n", _aqconfig_.light_programming_initial_off);
//} else {
fprintf(fp, "light_programming_button_pool = %d\n", _aqconfig_.light_programming_button_pool);
fprintf(fp, "light_programming_button_spa = %d\n", _aqconfig_.light_programming_button_spa);
fprintf(fp, "light_programming_mode = %f\n", _aqconfig_.light_programming_mode);
fprintf(fp, "light_programming_initial_on = %d\n", _aqconfig_.light_programming_initial_on);
fprintf(fp, "light_programming_initial_off = %d\n", _aqconfig_.light_programming_initial_off);
//}
fprintf(fp, "\n#** Domoticz **\n");
fprintf(fp, "convert_dz_temp_to_c = %s\n", bool2text(_aqconfig_.convert_dz_temp));
writeIntValue(fp, "air_temp_dzidx", _aqconfig_.dzidx_air_temp);
writeIntValue(fp, "pool_water_temp_dzidx", _aqconfig_.dzidx_pool_water_temp);
writeIntValue(fp, "spa_water_temp_dzidx", _aqconfig_.dzidx_spa_water_temp);
writeIntValue(fp, "SWG_percent_dzidx", _aqconfig_.dzidx_swg_percent);
writeIntValue(fp, "SWG_PPM_dzidx", _aqconfig_.dzidx_swg_ppm);
writeIntValue(fp, "SWG_Status_dzidx", _aqconfig_.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;
}

131
config.h
View File

@ -1,131 +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_LOG_LEVEL LOG_NOTICE
#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 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
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;
#if defined AQ_ONETOUCH || defined AQ_IAQTOUCH
unsigned char extended_device_id;
bool extended_device_id_programming;
#endif
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;
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;
bool force_swg;
bool force_ps_setpoints;
int swg_zero_ignore;
bool display_warnings_web;
bool log_protocol_packets; // Read & Write as packets
bool log_raw_bytes; // Read as bytes
unsigned char RSSD_LOG_filter;
//bool log_raw_RS_bytes;
bool readahead_b4_write;
bool mqtt_timed_update;
bool sync_panel_time;
bool enable_scheduler;
bool ftdi_low_latency;
bool prioritize_ack;
#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 isPDA ((_aqconfig_.paneltype_mask & RSP_PDA) == RSP_PDA)
/*
#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);
char *cleanalloc(char*str);
#endif

View File

@ -1,482 +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 "devices_jandy.h"
#include "aq_serial.h"
#include "aqualink.h"
#include "utils.h"
#include "aq_mqtt.h"
#include "packetLogger.h"
/*
All button errors
'Check AQUAPURE No Flow'
'Check AQUAPURE Low Salt'
'Check AQUAPURE High Salt'
'Check AQUAPURE General Fault'
*/
static int _swg_noreply_cnt = 0;
bool processJandyPacket(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata)
{
static rsDeviceType interestedInNextAck = DRS_NONE;
static unsigned char previous_packet_to = NUL; // bad name, it's not previous, it's previous that we were interested in.
int rtn = false;
// We received the ack from a Jandy device we are interested in
if (packet_buffer[PKT_DEST] == DEV_MASTER && interestedInNextAck != DRS_NONE)
{
if (interestedInNextAck == DRS_SWG)
{
rtn = processPacketFromSWG(packet_buffer, packet_length, aqdata);
}
else if (interestedInNextAck == DRS_EPUMP)
{
rtn = processPacketFromJandyPump(packet_buffer, packet_length, aqdata);
}
interestedInNextAck = DRS_NONE;
previous_packet_to = NUL;
}
// We were expecting an ack from Jandy device but didn't receive it.
else if (packet_buffer[PKT_DEST] != DEV_MASTER && interestedInNextAck != DRS_NONE)
{
if (interestedInNextAck == DRS_SWG && aqdata->ar_swg_device_status != SWG_STATUS_OFF)
{ // SWG Offline
processMissingAckPacketFromSWG(previous_packet_to, aqdata);
}
else if (interestedInNextAck == DRS_EPUMP)
{ // ePump offline
processMissingAckPacketFromJandyPump(previous_packet_to, aqdata);
}
interestedInNextAck = DRS_NONE;
previous_packet_to = NUL;
}
else if (READ_RSDEV_SWG && packet_buffer[PKT_DEST] == SWG_DEV_ID)
{
interestedInNextAck = DRS_SWG;
rtn = processPacketToSWG(packet_buffer, packet_length, aqdata, _aqconfig_.swg_zero_ignore);
previous_packet_to = packet_buffer[PKT_DEST];
}
else if (READ_RSDEV_ePUMP && packet_buffer[PKT_DEST] >= JANDY_DEC_PUMP_MIN && packet_buffer[PKT_DEST] <= JANDY_DEC_PUMP_MAX)
{
interestedInNextAck = DRS_EPUMP;
rtn = processPacketToJandyPump(packet_buffer, packet_length, aqdata);
previous_packet_to = packet_buffer[PKT_DEST];
}
else
{
interestedInNextAck = DRS_NONE;
previous_packet_to = NUL;
}
return rtn;
}
bool processPacketToSWG(unsigned char *packet, int packet_length, struct aqualinkdata *aqdata, int swg_zero_ignore) {
static int swg_zero_cnt = 0;
bool changedAnything = false;
// Only read message from controller to SWG to set SWG Percent if we are not programming, as we might be changing this
if (packet[3] == CMD_PERCENT && aqdata->active_thread.thread_id == 0 && packet[4] != 0xFF) {
// In service or timeout mode SWG set % message is very strange. AR %% | HEX: 0x10|0x02|0x50|0x11|0xff|0x72|0x10|0x03|
// Not really sure what to do with this, just ignore 0xff / 255 for the moment. (if statment above)
// SWG can get ~10 messages to set to 0 then go back again for some reason, so don't go to 0 until 10 messages are received
if (swg_zero_cnt <= swg_zero_ignore && packet[4] == 0x00) {
LOG(DJAN_LOG, LOG_DEBUG, "Ignoring SWG set to %d due to packet packet count %d <= %d from control panel to SWG 0x%02hhx 0x%02hhx\n", (int)packet[4],
swg_zero_cnt, swg_zero_ignore, packet[4], packet[5]);
swg_zero_cnt++;
} else if (swg_zero_cnt > swg_zero_ignore && packet[4] == 0x00) {
if (aqdata->swg_percent != (int)packet[4]) {
//aqdata->swg_percent = (int)packet[4];
setSWGpercent(aqdata, (int)packet[4]);
changedAnything = true;
aqdata->updated = true;
LOG(DJAN_LOG, LOG_DEBUG, "Set SWG %% to %d from reading control panel packet sent to SWG (received %d messages)\n", aqdata->swg_percent, swg_zero_cnt);
}
// LOG(DJAN_LOG, LOG_DEBUG, "SWG set to %d due to packet packet count %d <= %d from control panel to SWG 0x%02hhx 0x%02hhx\n",
// (int)packet[4],swg_zero_cnt,SWG_ZERO_IGNORE_COUNT,packet[4],packet[5]); swg_zero_cnt++;
} else {
swg_zero_cnt = 0;
if (aqdata->swg_percent != (int)packet[4]) {
//aqdata->swg_percent = (int)packet[4];
setSWGpercent(aqdata, (int)packet[4]);
changedAnything = true;
aqdata->updated = true;
LOG(DJAN_LOG, LOG_DEBUG, "Set SWG %% to %d from control panel packet to SWG\n", aqdata->swg_percent);
}
// LOG(DJAN_LOG, LOG_DEBUG, "SWG set to %d due to packet from control panel to SWG 0x%02hhx 0x%02hhx\n",
// aqdata.swg_percent,packet[4],packet[5]);
}
if (aqdata->swg_percent > 100)
aqdata->boost = true;
else
aqdata->boost = false;
}
return changedAnything;
}
bool processPacketFromSWG(unsigned char *packet, int packet_length, struct aqualinkdata *aqdata) {
bool changedAnything = false;
_swg_noreply_cnt = 0;
if (packet[PKT_CMD] == CMD_PPM) {
//aqdata->ar_swg_device_status = packet[5];
setSWGdeviceStatus(aqdata, JANDY_DEVICE, packet[5]);
if (aqdata->swg_delayed_percent != TEMP_UNKNOWN && aqdata->ar_swg_device_status == SWG_STATUS_ON) { // We have a delayed % to set.
char sval[10];
snprintf(sval, 9, "%d", aqdata->swg_delayed_percent);
aq_programmer(AQ_SET_SWG_PERCENT, sval, aqdata);
LOG(DJAN_LOG, LOG_NOTICE, "Setting SWG %% to %d, from delayed message\n", aqdata->swg_delayed_percent);
aqdata->swg_delayed_percent = TEMP_UNKNOWN;
}
if ( (packet[4] * 100) != aqdata->swg_ppm ) {
aqdata->swg_ppm = packet[4] * 100;
LOG(DJAN_LOG, LOG_DEBUG, "Set SWG PPM to %d from SWG packet\n", aqdata->swg_ppm);
changedAnything = true;
aqdata->updated = true;
}
// logMessage(LOG_DEBUG, "Read SWG PPM %d from ID 0x%02hhx\n", aqdata.swg_ppm, SWG_DEV_ID);
}
return changedAnything;
}
void processMissingAckPacketFromSWG(unsigned char destination, struct aqualinkdata *aqdata)
{
// SWG_STATUS_UNKNOWN means we have never seen anything from SWG, so leave as is.
// IAQTOUCH & ONETOUCH give us AQUAPURE=0 but ALLBUTTON doesn't, so only turn off if we are not in extra device mode.
// NSF Need to check that we actually use 0 from IAQTOUCH & ONETOUCH
if ( aqdata->ar_swg_device_status != SWG_STATUS_UNKNOWN && isIAQT_ENABLED == false && isONET_ENABLED == false )
{
if ( _swg_noreply_cnt < 3 ) {
//_aqualink_data.ar_swg_device_status = SWG_STATUS_OFF;
//_aqualink_data.updated = true;
setSWGoff(aqdata);
_swg_noreply_cnt++; // Don't put in if, as it'll go past size limit
}
}
}
bool isSWGDeviceErrorState(unsigned char status)
{
if (status == SWG_STATUS_NO_FLOW ||
status == SWG_STATUS_CHECK_PCB ||
status == SWG_STATUS_LOW_TEMP ||
status == SWG_STATUS_HIGH_CURRENT ||
status == SWG_STATUS_NO_FLOW)
return true;
else
return false;
}
void setSWGdeviceStatus(struct aqualinkdata *aqdata, emulation_type requester, unsigned char status) {
if (aqdata->ar_swg_device_status == status)
return;
// If we get (ALLBUTTON, SWG_STATUS_CHECK_PCB), it sends this for many status, like clean cell.
// So if we are in one of those states, don't use it.
if (requester == ALLBUTTON && status == SWG_STATUS_CHECK_PCB ) {
if (aqdata->ar_swg_device_status > SWG_STATUS_ON &&
aqdata->ar_swg_device_status < SWG_STATUS_TURNING_OFF) {
LOG(DJAN_LOG, LOG_DEBUG, "Ignoreing set SWG device state to '0x%02hhx', request from %d\n", aqdata->ar_swg_device_status, requester);
return;
}
}
// Check validity of status and set as appropiate
switch (status) {
case SWG_STATUS_ON:
case SWG_STATUS_NO_FLOW:
case SWG_STATUS_LOW_SALT:
case SWG_STATUS_HI_SALT:
case SWG_STATUS_HIGH_CURRENT:
case SWG_STATUS_CLEAN_CELL:
case SWG_STATUS_LOW_VOLTS:
case SWG_STATUS_LOW_TEMP:
case SWG_STATUS_CHECK_PCB:
aqdata->ar_swg_device_status = status;
aqdata->swg_led_state = isSWGDeviceErrorState(status)?ENABLE:ON;
break;
case SWG_STATUS_OFF: // THIS IS OUR OFF STATUS, NOT AQUAPURE
case SWG_STATUS_TURNING_OFF:
aqdata->ar_swg_device_status = status;
aqdata->swg_led_state = OFF;
break;
default:
LOG(DJAN_LOG, LOG_WARNING, "Ignoring set SWG device to state '0x%02hhx', state is unknown\n", status);
return;
break;
}
LOG(DJAN_LOG, LOG_DEBUG, "Set SWG device state to '0x%02hhx', request from %d, LED state = %d\n", aqdata->ar_swg_device_status, requester, aqdata->swg_led_state);
}
/*
bool updateSWG(struct aqualinkdata *aqdata, emulation_type requester, aqledstate state, int percent)
{
switch (requester) {
case ALLBUTTON: // no insight into 0% (just blank)
break;
case ONETOUCH:
break;
case IAQTOUCH:
break;
case AQUAPDA:
break;
case JANDY_DEVICE:
break;
}
}
*/
bool setSWGboost(struct aqualinkdata *aqdata, bool on) {
if (!on) {
aqdata->boost = false;
aqdata->boost_msg[0] = '\0';
aqdata->swg_percent = 0;
} else {
aqdata->boost = true;
aqdata->swg_percent = 101;
}
return true;
}
// Only change SWG percent if we are not in SWG programming
bool changeSWGpercent(struct aqualinkdata *aqdata, int percent) {
if (in_swg_programming_mode(aqdata)) {
LOG(DJAN_LOG, LOG_DEBUG, "Ignoring set SWG %% to %d due to programming SWG\n", aqdata->swg_percent);
return false;
}
setSWGpercent(aqdata, percent);
return true;
}
void setSWGoff(struct aqualinkdata *aqdata) {
if (aqdata->ar_swg_device_status != SWG_STATUS_OFF || aqdata->swg_led_state != OFF)
aqdata->updated = true;
aqdata->ar_swg_device_status = SWG_STATUS_OFF;
aqdata->swg_led_state = OFF;
LOG(DJAN_LOG, LOG_DEBUG, "Set SWG to off\n");
}
void setSWGenabled(struct aqualinkdata *aqdata) {
if (aqdata->swg_led_state != ENABLE) {
aqdata->updated = true;
aqdata->swg_led_state = ENABLE;
LOG(DJAN_LOG, LOG_DEBUG, "Set SWG to Enable\n");
}
}
// force a Change SWG percent.
void setSWGpercent(struct aqualinkdata *aqdata, int percent) {
aqdata->swg_percent = percent;
aqdata->updated = true;
if (aqdata->swg_percent > 0) {
//LOG(DJAN_LOG, LOG_DEBUG, "swg_led_state=%d, swg_led_state=%d, isSWGDeviceErrorState=%d, ar_swg_device_status=%d\n",aqdata->swg_led_state, aqdata->swg_led_state, isSWGDeviceErrorState(aqdata->ar_swg_device_status),aqdata->ar_swg_device_status);
if (aqdata->swg_led_state == OFF || (aqdata->swg_led_state == ENABLE && ! isSWGDeviceErrorState(aqdata->ar_swg_device_status)) ) // Don't change ENABLE / FLASH
aqdata->swg_led_state = ON;
if (aqdata->ar_swg_device_status == SWG_STATUS_UNKNOWN)
aqdata->ar_swg_device_status = SWG_STATUS_ON;
} if ( aqdata->swg_percent == 0 ) {
if (aqdata->swg_led_state == ON)
aqdata->swg_led_state = ENABLE; // Don't change OFF
if (aqdata->ar_swg_device_status == SWG_STATUS_UNKNOWN)
aqdata->ar_swg_device_status = SWG_STATUS_ON; // Maybe this should be off
}
LOG(DJAN_LOG, LOG_DEBUG, "Set SWG %% to %d, LED=%d, FullStatus=0x%02hhx\n", aqdata->swg_percent, aqdata->swg_led_state, aqdata->ar_swg_device_status);
}
aqledstate get_swg_led_state(struct aqualinkdata *aqdata)
{
switch (aqdata->ar_swg_device_status) {
case SWG_STATUS_ON:
return (aqdata->swg_percent > 0?ON:ENABLE);
break;
case SWG_STATUS_NO_FLOW:
return ENABLE;
break;
case SWG_STATUS_LOW_SALT:
return (aqdata->swg_percent > 0?ON:ENABLE);
break;
case SWG_STATUS_HI_SALT:
return (aqdata->swg_percent > 0?ON:ENABLE);
break;
case SWG_STATUS_HIGH_CURRENT:
return (aqdata->swg_percent > 0?ON:ENABLE);
break;
case SWG_STATUS_TURNING_OFF:
return OFF;
break;
case SWG_STATUS_CLEAN_CELL:
return (aqdata->swg_percent > 0?ON:ENABLE);
return ENABLE;
break;
case SWG_STATUS_LOW_VOLTS:
return ENABLE;
break;
case SWG_STATUS_LOW_TEMP:
return ENABLE;
break;
case SWG_STATUS_CHECK_PCB:
return ENABLE;
break;
case SWG_STATUS_OFF: // THIS IS OUR OFF STATUS, NOT AQUAPURE
return OFF;
break;
default:
return (aqdata->swg_percent > 0?ON:ENABLE);
break;
}
}
void get_swg_status_msg(struct aqualinkdata *aqdata, char *message)
{
int tmp1;
int tmp2;
return get_swg_status_mqtt(aqdata, message, &tmp1, &tmp2);
}
void get_swg_status_mqtt(struct aqualinkdata *aqdata, char *message, int *status, int *dzalert)
{
switch (aqdata->ar_swg_device_status) {
// Level = (0=gray, 1=green, 2=yellow, 3=orange, 4=red)
case SWG_STATUS_ON:
*status = (aqdata->swg_percent > 0?SWG_ON:SWG_OFF);
sprintf(message, "AQUAPURE GENERATING CHLORINE");
*dzalert = 1;
break;
case SWG_STATUS_NO_FLOW:
*status = SWG_OFF;
sprintf(message, "AQUAPURE NO FLOW");
*dzalert = 2;
break;
case SWG_STATUS_LOW_SALT:
*status = (aqdata->swg_percent > 0?SWG_ON:SWG_OFF);
sprintf(message, "AQUAPURE LOW SALT");
*dzalert = 2;
break;
case SWG_STATUS_HI_SALT:
*status = (aqdata->swg_percent > 0?SWG_ON:SWG_OFF);
sprintf(message, "AQUAPURE HIGH SALT");
*dzalert = 3;
break;
case SWG_STATUS_HIGH_CURRENT:
*status = (aqdata->swg_percent > 0?SWG_ON:SWG_OFF);
sprintf(message, "AQUAPURE HIGH CURRENT");
*dzalert = 4;
break;
case SWG_STATUS_TURNING_OFF:
*status = SWG_OFF;
sprintf(message, "AQUAPURE TURNING OFF");
*dzalert = 0;
break;
case SWG_STATUS_CLEAN_CELL:
*status = (aqdata->swg_percent > 0?SWG_ON:SWG_OFF);
sprintf(message, "AQUAPURE CLEAN CELL");
*dzalert = 2;
break;
case SWG_STATUS_LOW_VOLTS:
*status = (aqdata->swg_percent > 0?SWG_ON:SWG_OFF);
sprintf(message, "AQUAPURE LOW VOLTAGE");
*dzalert = 3;
break;
case SWG_STATUS_LOW_TEMP:
*status = SWG_OFF;
sprintf(message, "AQUAPURE WATER TEMP LOW");
*dzalert = 2;
break;
case SWG_STATUS_CHECK_PCB:
*status = SWG_OFF;
sprintf(message, "AQUAPURE CHECK PCB");
*dzalert = 4;
break;
case SWG_STATUS_OFF: // THIS IS OUR OFF STATUS, NOT AQUAPURE
*status = SWG_OFF;
sprintf(message, "AQUAPURE OFF");
*dzalert = 0;
break;
default:
*status = (aqdata->swg_percent > 0?SWG_ON:SWG_OFF);
sprintf(message, "AQUAPURE UNKNOWN STATUS");
*dzalert = 4;
break;
}
}
bool processPacketToJandyPump(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata)
{
char msg[1000];
//logMessage(LOG_DEBUG, "Need to log ePump message here for future\n");
beautifyPacket(msg, packet_buffer, packet_length, true);
LOG(DJAN_LOG, LOG_DEBUG, "To ePump: %s\n", msg);
//find pump for message
if ( 1 == 0 /*SOME_DEBUG_TEST*/) {
int i;
for (i=0; i < aqdata->num_pumps; i++) {
if (aqdata->pumps[i].pumpID == packet_buffer[PKT_DEST]) {
LOG(DJAN_LOG, LOG_DEBUG, "Last panel info RPM:%d GPM:%d WATTS:%d\n", aqdata->pumps[i].rpm, aqdata->pumps[i].gpm, aqdata->pumps[i].watts);
break;
}
}
}
return false;
}
bool processPacketFromJandyPump(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata)
{
char msg[1000];
//logMessage(LOG_DEBUG, "Need to log ePump message here for future\n");
beautifyPacket(msg, packet_buffer, packet_length, true);
LOG(DJAN_LOG, LOG_DEBUG, "From ePump: %s\n", msg);
return false;
}
void processMissingAckPacketFromJandyPump(unsigned char destination, struct aqualinkdata *aqdata)
{
// Do nothing for the moment.
return;
}
/*
Messages to ePump so far.
Debug: To ePump: Jandy Packet | HEX: 0x10|0x02|0x78|0x42|0xcc|0x10|0x03|
Debug: To ePump: Jandy Packet | HEX: 0x10|0x02|0x78|0x44|0x00|0x10|0x27|0x05|0x10|0x03|
Debug: To ePump: Jandy Packet | HEX: 0x10|0x02|0x78|0x44|0x00|0x58|0x1b|0x41|0x10|0x03|
Debug: To ePump: Jandy Packet | HEX: 0x10|0x02|0x78|0x45|0x00|0x05|0xd4|0x10|0x03|
Debug: To ePump: Jandy Packet | HEX: 0x10|0x02|0x78|0x46|0x00|0x00|0x03|0xd3|0x10|0x03|
Debug: To ePump: Jandy Packet | HEX: 0x10|0x02|0x78|0x46|0x00|0x04|0x00|0xd4|0x10|0x03|
*/

View File

@ -1,27 +0,0 @@
#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);
bool processPacketToJandyPump(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
bool processPacketFromJandyPump(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
void processMissingAckPacketFromSWG(unsigned char destination, struct aqualinkdata *aqdata);
void processMissingAckPacketFromJandyPump(unsigned char destination, struct aqualinkdata *aqdata);
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);
#endif // AQUAPURE_H_

View File

@ -1,139 +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 <string.h>
#include "aqualink.h"
#include "aq_serial.h"
#include "devices_pentair.h"
#include "utils.h"
bool processPentairPacket(unsigned char *packet, int packet_length, struct aqualinkdata *aqdata)
{
bool changedAnything = false;
int i;
//ID's 96 to 111 = Pentair (or 0x60 to 0x6F)
// Need to find a better way to support pump index
//static int pumpIndex = 1;
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 Status message = RPM %d | WATTS %d\n",
(packet[PEN_HI_B_RPM] * 256) + packet[PEN_LO_B_RPM],
(packet[PEN_HI_B_WAT] * 256) + packet[PEN_LO_B_WAT]);
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];
changedAnything = true;
break;
}
if (changedAnything != true)
LOG(DPEN_LOG, LOG_NOTICE, "Pentair Pump found at ID 0x%02hhx with RPM %d | WATTS %d, but not configured, information ignored!\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]);
}
//
}
return changedAnything;
}
/*
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

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"]

105
docker/Dockerfile.buildx Normal file
View File

@ -0,0 +1,105 @@
#####################################
#
# 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 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"]

286
docker/Dockerfile.releaseBinaries Executable file
View File

@ -0,0 +1,286 @@
#####################################
#
# 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
#
# docker build -f Dockerfile.releaseBinaries -t aqualinkd-releasebin .
# docker run -it --mount type=bind,source=./build,target=/build aqualinkd-releasebin bash
#
# clean method
# docker system prune
#
# 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
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/sfeakes/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/sfeakes/AqualinkD/archive/refs/tags/"$VERSION".tar.gz"
URL2="https://github.com/sfeakes/AqualinkD/archive/refs/tags/v"$VERSION".tar.gz"
URL3="https://github.com/sfeakes/AqualinkD/archive/refs/tags/V"$VERSION".tar.gz"
#BURL="https://github.com/sfeakes/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/sfeakes/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

16
epump.h
View File

@ -1,16 +0,0 @@
/*
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|
*/

View File

@ -113,11 +113,11 @@ switch:
retain: false
icon: mdi:lightbulb
- platform: mqtt
unique_id: aqualink_spa_mode
name: "Spa Mode"
state_topic: "aqualinkd/Spa_Mode"
command_topic: "aqualinkd/Spa_Mode/set"
json_attributes_topic: "aqualinkd/Spa_Mode/delay"
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"
@ -318,9 +318,9 @@ binary_sensor:
payload_on: "1"
payload_off: "0"
- platform: mqtt
unique_id: spa_mode_delay
state_topic: "aqualinkd/Spa_Mode/delay"
name: "Spa Mode Delay"
unique_id: spa_delay
state_topic: "aqualinkd/Spa/delay"
name: "Spa Delay"
qos: 0
payload_on: "1"
payload_off: "0"

BIN
extras/HASSIO.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

BIN
extras/allbutton_sim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -10,6 +10,25 @@
# curl --silent "https://raw.githubusercontent.com/sfeakes/AqualinkD/master/version.h" | grep AQUALINKD_VERSION | cut -d '"' -f 2
#
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"
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"

View File

@ -13,7 +13,7 @@
# * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# * See the GNU General Public License for more details.
# *
# * https://github.com/sfeakes/aqualinkd
# * https://github.com/aqualinkd/aqualinkd
# */

BIN
extras/onetouch_sim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -13,7 +13,7 @@
# * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# * See the GNU General Public License for more details.
# *
# * https://github.com/sfeakes/aqualinkd
# * https://github.com/aqualinkd/aqualinkd
# */
MAX_TEMP=78

View File

@ -4,6 +4,13 @@
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

View File

@ -4,6 +4,13 @@
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

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

View File

@ -1,924 +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 <string.h>
#include "aq_serial.h"
#include "aqualink.h"
#include "utils.h"
#include "packetLogger.h"
#include "iaqtouch.h"
#include "iaqtouch_aq_programmer.h"
#include "aq_programmer.h"
#include "rs_msg_utils.h"
#include "devices_jandy.h"
void temp_debugprintExtraInfo(unsigned char *pk, int length);
#ifdef ATOUCH_TEST
void set_iaq_cansend(bool yes) {}
bool in_iaqt_programming_mode(struct aqualinkdata *aq_data) {return false;}
bool iaqt_queue_cmd(unsigned char cmd) {}
bool in_programming_mode(struct aqualinkdata *aq_data){return false;}
void queueGetProgramData(emulation_type source_type, struct aqualinkdata *aq_data){}
void kick_aq_program_thread(struct aqualinkdata *aq_data, emulation_type source_type){}
void aq_programmer(program_type type, char *args, struct aqualinkdata *aq_data){}
#endif
unsigned char _button_keys[] = { KEY_IAQTCH_KEY01,
KEY_IAQTCH_KEY02,
KEY_IAQTCH_KEY03,
KEY_IAQTCH_KEY04,
KEY_IAQTCH_KEY05,
KEY_IAQTCH_KEY06,
KEY_IAQTCH_KEY07,
KEY_IAQTCH_KEY08,
KEY_IAQTCH_KEY09,
KEY_IAQTCH_KEY10,
KEY_IAQTCH_KEY11,
KEY_IAQTCH_KEY12,
KEY_IAQTCH_KEY13,
KEY_IAQTCH_KEY14,
KEY_IAQTCH_KEY15};
#define IAQ_STATUS_PAGE_LINES 18
#define IAQ_PAGE_BUTTONS 24
#define IAQ_MSG_TABLE_LINES IAQ_STATUS_PAGE_LINES // No idea actual size, so just use this until figured out.
#define IAQT_TABLE_MSGLEN 32
unsigned char _currentPageLoading;
unsigned char _currentPage;
unsigned char _lastMsgType = 0x00;
//unsigned char _last_kick_type = -1;
int _deviceStatusLines = 0;
char _deviceStatus[IAQ_STATUS_PAGE_LINES][AQ_MSGLEN+1];
char _tableInformation[IAQ_MSG_TABLE_LINES][IAQT_TABLE_MSGLEN+1];
struct iaqt_page_button _pageButtons[IAQ_PAGE_BUTTONS];
// Need to cache these two pages, as only get updates after initial load.
struct iaqt_page_button _devicePageButtons[IAQ_PAGE_BUTTONS];
struct iaqt_page_button _devicePage2Buttons[IAQ_PAGE_BUTTONS];
struct iaqt_page_button _deviceSystemSetupButtons[IAQ_PAGE_BUTTONS];
unsigned char iaqtLastMsg()
{
return _lastMsgType;
}
void set_iaqtouch_lastmsg(unsigned char msgtype)
{
_lastMsgType = msgtype;
}
bool wasiaqtThreadKickTypePage()
{
switch(_lastMsgType) {
//case CMD_IAQ_PAGE_MSG:
//case CMD_IAQ_PAGE_BUTTON:
//case CMD_IAQ_PAGE_START:
case CMD_IAQ_PAGE_END:
return true;
break;
default:
return false;
break;
}
return false;
}
unsigned char iaqtCurrentPage()
{
return _currentPage;
}
const char *iaqtGetMessageLine(int index) {
if (index < IAQ_STATUS_PAGE_LINES)
return _deviceStatus[index];
return NULL;
}
const char *iaqtGetTableInfoLine(int index) {
if (index < IAQ_MSG_TABLE_LINES)
return _tableInformation[index];
return NULL;
}
struct iaqt_page_button *iaqtFindButtonByIndex(int index) {
//int i;
struct iaqt_page_button *buttons;
// NSF Need to merge this from iaqtFindButtonByLabel function
if (_currentPage == IAQ_PAGE_DEVICES )
buttons = _devicePageButtons;
else if (_currentPage == IAQ_PAGE_DEVICES2 )
buttons = _devicePage2Buttons;
else if (_currentPage == IAQ_PAGE_SYSTEM_SETUP )
buttons = _deviceSystemSetupButtons;
else
buttons = _pageButtons;
if (index>=0 && index < IAQ_PAGE_BUTTONS) {
return &buttons[index];
}
return NULL;
}
struct iaqt_page_button *iaqtFindButtonByLabel(char *label) {
int i;
struct iaqt_page_button *buttons;
if (_currentPage == IAQ_PAGE_DEVICES )
buttons = _devicePageButtons;
else if (_currentPage == IAQ_PAGE_DEVICES2 )
buttons = _devicePage2Buttons;
else if (_currentPage == IAQ_PAGE_SYSTEM_SETUP )
buttons = _deviceSystemSetupButtons;
else
buttons = _pageButtons;
for (i=0; i < IAQ_PAGE_BUTTONS; i++) {
//if (_pageButtons[i].state != 0 || _pageButtons[i].type != 0 || _pageButtons[i].unknownByte != 0)
if (rsm_strcmp((char *)buttons[i].name,label) == 0)
return &buttons[i];
}
return NULL;
}
int num2iaqtRSset (unsigned char* packetbuffer, int num, bool pad4unknownreason)
{
//unsigned int score = 42; // Works for score in [0, UINT_MAX]
//unsigned char tmp;
//printf ("num via printf: %u\n", num); // For validation
int bcnt = 0;
int digits = 0;
unsigned int div = 1;
unsigned int digit_count = 1;
while ( div <= num / 10 ) {
digit_count++;
div *= 10;
}
while ( digit_count > 0 ) {
packetbuffer[bcnt] = (num / div + 0x30); // 48 = 0x30 base number for some reason. (ie 48=0)
num %= div;
div /= 10;
digit_count--;
bcnt++;
}
for (digits = bcnt; bcnt < 6; bcnt++) { // Note setting digits to bcnt is correct. Saving current count to different int
if (bcnt == 4 && digits <= 3 ) // Less than 4 digits (<1000), need to add a 0x30
packetbuffer[bcnt] = 0x30;
else
packetbuffer[bcnt] = NUL;
}
return bcnt;
}
int char2iaqtRSset(unsigned char* packetbuffer, char *msg, int msg_len)
{
int bcnt=0;
for (bcnt=0; bcnt < msg_len; bcnt++ ){
packetbuffer[bcnt] = msg[bcnt];
}
packetbuffer[bcnt] = 0x00;
return ++bcnt;
}
void createDeviceUpdatePacket() {
unsigned char packets[AQ_MAXPKTLEN];
int cnt;
packets[0] = DEV_MASTER;
packets[1] = 0x24;
packets[2] = 0x31;
cnt = num2iaqtRSset(&packets[3], 1000, true);
for(cnt = cnt+3; cnt <= 18; cnt++)
packets[cnt] = 0xcd;
//printHex(packets, 19);
//printf("\n");
//send_jandy_command(NULL, packets, cnt);
}
void processPageMessage(unsigned char *message, int length)
{
if ( (int)message[PKT_IAQT_MSGINDX] >= IAQ_STATUS_PAGE_LINES ) {
LOG(IAQT_LOG,LOG_ERR, "Run out of IAQT message buffer, need %d have %d\n",(int)message[PKT_IAQT_MSGINDX],IAQ_STATUS_PAGE_LINES);
return;
}
// 2nd page of device status doesn;t gine us new page message
if (_currentPageLoading == IAQ_PAGE_STATUS || _currentPage == IAQ_PAGE_STATUS) {
//sprintf(_deviceStatus[(int)message[4]], message[5], AQ_MSGLEN);
//strncpy(_deviceStatus[(int)message[PKT_IAQT_MSGINDX]], (char *)message + PKT_IAQT_MSGDATA, AQ_MSGLEN);
rsm_strncpy(_deviceStatus[(int)message[PKT_IAQT_MSGINDX]], &message[PKT_IAQT_MSGDATA], AQ_MSGLEN, length-PKT_IAQT_MSGDATA-3);
} else {
//strncpy(_deviceStatus[(int)message[PKT_IAQT_MSGINDX]], (char *)message + PKT_IAQT_MSGDATA, AQ_MSGLEN);
rsm_strncpy(_deviceStatus[(int)message[PKT_IAQT_MSGINDX]], &message[PKT_IAQT_MSGDATA], AQ_MSGLEN, length-PKT_IAQT_MSGDATA-3);
//LOG(IAQT_LOG,LOG_ERR, "Request to assign message to unknown page,'%.*s'\n",AQ_MSGLEN,(char *)message + PKT_IAQT_MSGDATA);
}
//LOG(IAQT_LOG,LOG_DEBUG, "Message :- '%d' '%.*s'\n",(int)message[PKT_IAQT_MSGINDX], length-PKT_IAQT_MSGDATA-3, &message[PKT_IAQT_MSGDATA]);
}
void processTableMessage(unsigned char *message, int length)
{
if ( (int)message[5] < IAQ_MSG_TABLE_LINES )
rsm_strncpy(_tableInformation[(int)message[5]], &message[6], IAQT_TABLE_MSGLEN, length-PKT_IAQT_MSGDATA-3);
else
LOG(IAQT_LOG,LOG_ERR, "Run out of IAQT table buffer, need %d have %d\n",(int)message[5],IAQ_MSG_TABLE_LINES);
}
void processPageButton(unsigned char *message, int length)
{
struct iaqt_page_button *button;
int index = (int)message[PKT_IAQT_BUTINDX];
if (_currentPageLoading == IAQ_PAGE_DEVICES )
button = &_devicePageButtons[index];
else if (_currentPageLoading == IAQ_PAGE_DEVICES2 )
button = &_devicePage2Buttons[index];
else if (_currentPageLoading == IAQ_PAGE_SYSTEM_SETUP )
button = &_deviceSystemSetupButtons[index];
else
button = &_pageButtons[index];
button->state = message[PKT_IAQT_BUTSTATE];
button->type = message[PKT_IAQT_BUTTYPE];
button->unknownByte = message[PKT_IAQT_BUTUNKNOWN];
if (message[PKT_IAQT_BUTSTATE] == 0x0d)
button->keycode = message[PKT_IAQT_BUTTYPE];
else if (index < 15) {
button->keycode = _button_keys[index];
}
// This doesn't work with return which is 0x00
//strncpy(&button->name, (char *)message + PKT_IAQT_BUTDATA, AQ_MSGLEN);
rsm_strncpy_nul2sp((char *)button->name, &message[PKT_IAQT_BUTDATA], IAQT_MSGLEN, length-PKT_IAQT_BUTDATA-3);
LOG(IAQT_LOG,LOG_DEBUG, "Added Button %d %s\n",index,button->name);
/*
_pageButtons[index].state = (int)message[5];
_pageButtons[index].type = (int)message[7];
_pageButtons[index].unknownByte = (int)message[6];
// This doesn't work with return which is 0x00
strncpy(&_pageButtons[index].name, (char *)message + 8, AQ_MSGLEN);
LOG(IAQT_LOG,LOG_NOTICE, "Added Button %d %s\n",index,_pageButtons[index].name);
*/
}
// Log if we saw a pump in a device page cycle.
void iaqt_pump_update(struct aqualinkdata *aq_data, int updated) {
const int bitmask[MAX_PUMPS] = {1,2,4,8};
static unsigned char updates = '\0';
int i;
if (updated == -1) {
for(i=0; i < MAX_PUMPS; i++) {
if ((updates & bitmask[i]) != bitmask[i]) {
aq_data->pumps[i].rpm = PUMP_OFF_RPM;
aq_data->pumps[i].gpm = PUMP_OFF_GPM;
aq_data->pumps[i].watts = PUMP_OFF_WAT;
LOG(IAQT_LOG,LOG_DEBUG, "Clearing pump %d\n",i);
aq_data->updated =true;
}
}
updates = '\0';
} else if (updated >=0 && updated < MAX_PUMPS) {
updates |= bitmask[updated];
LOG(IAQT_LOG,LOG_DEBUG, "Got pump update message for pump %d\n",updated);
}
}
void passDeviceStatusPage(struct aqualinkdata *aq_data)
{
int i;
int pi;
pump_detail *pump = NULL;
//bool found_swg = false;
//int pump_index = 0;
for (i=0; i <IAQ_STATUS_PAGE_LINES; i++ ) {
//LOG(IAQT_LOG,LOG_NOTICE, "Passing message %.2d| %s\n",i,_deviceStatus[i]);
if (rsm_strcmp(_deviceStatus[i],"Intelliflo VS") == 0 ||
rsm_strcmp(_deviceStatus[i],"Intelliflo VF") == 0 ||
rsm_strcmp(_deviceStatus[i],"Jandy ePUMP") == 0 ||
rsm_strcmp(_deviceStatus[i],"ePump AC") == 0)
{
int pump_index = rsm_atoi(&_deviceStatus[i][14]);
if (pump_index <= 0)
pump_index = rsm_atoi(&_deviceStatus[i][10]); // ePump AC seems to display index in different position
for (pi=0; pi < aq_data->num_pumps; pi++) {
if (aq_data->pumps[pi].pumpIndex == pump_index) {
iaqt_pump_update(aq_data, pi); // Log that we saw a pump
pump = &aq_data->pumps[pi];
aq_data->updated =true;
if (pump->pumpType == PT_UNKNOWN){
if (rsm_strcmp(_deviceStatus[i],"Intelliflo VS") == 0)
pump->pumpType = VSPUMP;
else if (rsm_strcmp(_deviceStatus[i],"Intelliflo VF") == 0)
pump->pumpType = VFPUMP;
else if (rsm_strcmp(_deviceStatus[i],"Jandy ePUMP") == 0 ||
rsm_strcmp(_deviceStatus[i],"ePump AC") == 0)
pump->pumpType = EPUMP;
LOG(IAQT_LOG,LOG_DEBUG, "Pump %d set to type %s\n",pump->pumpIndex, (pump->pumpType==EPUMP?"Jandy ePUMP":(pump->pumpType==VFPUMP?"Intelliflo VF":"Intelliflo VS")) );
}
}
}
if (pump == NULL)
LOG(IAQT_LOG,LOG_WARNING, "Got pump message '%s' but can't find pump at index %d\n",_deviceStatus[i],pump_index);
continue;
} else if (rsm_strcmp(_deviceStatus[i],"RPM:") == 0) {
if (pump != NULL)
pump->rpm = rsm_atoi(&_deviceStatus[i][9]);
else
LOG(IAQT_LOG,LOG_WARNING, "Got pump message '%s' but can't find pump\n",_deviceStatus[i]);
continue;
} else if (rsm_strcmp(_deviceStatus[i],"GPM:") == 0) {
if (pump != NULL)
pump->gpm = rsm_atoi(&_deviceStatus[i][9]);
else
LOG(IAQT_LOG,LOG_WARNING, "Got pump message '%s' but can't find pump\n",_deviceStatus[i]);
continue;
} else if (rsm_strcmp(_deviceStatus[i],"Watts:") == 0) {
if (pump != NULL)
pump->watts = rsm_atoi(&_deviceStatus[i][9]);
else
LOG(IAQT_LOG,LOG_WARNING, "Got pump message '%s' but can't find pump\n",_deviceStatus[i]);
continue;
} else if (rsm_strcmp(_deviceStatus[i],"*** Priming ***") == 0) {
if (pump != NULL)
pump->rpm = PUMP_PRIMING;
else
LOG(IAQT_LOG,LOG_WARNING, "Got pump message '%s' but can't find pump\n",_deviceStatus[i]);
continue;
} else if (rsm_strcmp(_deviceStatus[i],"(Offline)") == 0) {
if (pump != NULL)
pump->rpm = PUMP_OFFLINE;
else
LOG(IAQT_LOG,LOG_WARNING, "Got pump message '%s' but can't find pump\n",_deviceStatus[i]);
continue;
} else if (rsm_strcmp(_deviceStatus[i],"(Priming Error)") == 0) {
if (pump != NULL)
pump->rpm = PUMP_ERROR;
else
LOG(IAQT_LOG,LOG_WARNING, "Got pump message '%s' but can't find pump\n",_deviceStatus[i]);
continue;
// Need to catch messages like
// *** Priming ***
// (Priming Error)
// (Offline)
} else {
pump = NULL;
}
if (rsm_strcmp(_deviceStatus[i],"Chemlink") == 0) {
/* Info: = Chemlink 1
Info: = ORP 750/PH 7.0 */
i++;
if (rsm_strcmp(_deviceStatus[i],"ORP") == 0) {
int orp = rsm_atoi(&_deviceStatus[i][4]);
char *indx = strchr(_deviceStatus[i], '/');
float ph = rsm_atof(indx+3);
if (aq_data->ph != ph || aq_data->orp != orp) {
aq_data->ph = ph;
aq_data->orp = orp;
}
LOG(IAQT_LOG,LOG_INFO, "Set Cemlink ORP = %d PH = %f from message '%s'\n",orp,ph,_deviceStatus[i]);
}
}
#ifdef READ_SWG_FROM_EXTENDED_ID
else if (rsm_strcmp(_deviceStatus[i],"AQUAPURE") == 0) {
//aq_data->swg_percent = rsm_atoi(&_deviceStatus[i][9]);
if (changeSWGpercent(aq_data, rsm_atoi(&_deviceStatus[i][9])))
LOG(IAQT_LOG,LOG_DEBUG, "Set swg %% to %d from message'%s'\n",aq_data->swg_percent,_deviceStatus[i]);
} else if (rsm_strcmp(_deviceStatus[i],"salt") == 0) {
aq_data->swg_ppm = rsm_atoi(&_deviceStatus[i][5]);
LOG(IAQT_LOG,LOG_DEBUG, "Set swg PPM to %d from message'%s'\n",aq_data->swg_ppm,_deviceStatus[i]);
} else if (rsm_strcmp(_deviceStatus[i],"Boost Pool") == 0) {
aq_data->boost = true;
// Let RS pickup time remaing message.
}
#endif
} // for
}
void debugPrintButtons(struct iaqt_page_button buttons[])
{
int i;
for (i=0; i < IAQ_PAGE_BUTTONS; i++) {
if (buttons[i].state != 0 || buttons[i].type != 0 || buttons[i].unknownByte != 0 || buttons[i].keycode != 0)
LOG(IAQT_LOG,LOG_INFO, "Button %.2d| %21.21s | type=0x%02hhx | state=0x%02hhx | unknown=0x%02hhx | keycode=0x%02hhx\n",i,buttons[i].name,buttons[i].type,buttons[i].state,buttons[i].unknownByte,buttons[i].keycode);
}
}
void processPage(struct aqualinkdata *aq_data)
{
int i;
LOG(IAQT_LOG,LOG_INFO, "Page: %s | 0x%02hhx\n",iaqt_page_name(_currentPage),_currentPage);
switch(_currentPage) {
case IAQ_PAGE_STATUS:
case IAQ_PAGE_STATUS2:
//LOG(IAQT_LOG,LOG_INFO, "Status Page:-\n");
for (i=0; i <IAQ_STATUS_PAGE_LINES; i++ )
if (strlen(_deviceStatus[i]) > 1)
LOG(IAQT_LOG,LOG_INFO, "Status page %.2d| %s\n",i,_deviceStatus[i]);
debugPrintButtons(_pageButtons);
passDeviceStatusPage(aq_data);
// If button 1 is type 0x02 then there is a next page. Since status page isn't used for programming, hit the next page button.
if (_pageButtons[1].type == 0x02)
iaqt_queue_cmd(KEY_IAQTCH_KEY02);
else
iaqt_pump_update(aq_data, -1); // Reset pumps.
break;
case IAQ_PAGE_DEVICES:
//LOG(IAQT_LOG,LOG_INFO, "Devices Page #1:-\n");
debugPrintButtons(_devicePageButtons);
break;
case IAQ_PAGE_DEVICES2:
//LOG(IAQT_LOG,LOG_INFO, "Devices Page #2:-\n");
debugPrintButtons(_devicePage2Buttons);
break;
case IAQ_PAGE_COLOR_LIGHT:
//LOG(IAQT_LOG,LOG_INFO, "Color Light Page :-\n");
debugPrintButtons(_pageButtons);
break;
case IAQ_PAGE_HOME:
//LOG(IAQT_LOG,LOG_INFO, "Home Page :-\n");
for (i=0; i <IAQ_STATUS_PAGE_LINES; i++ )
if (_deviceStatus[i][0] != 0)
LOG(IAQT_LOG,LOG_INFO, "Status page %.2d| %s\n",i,_deviceStatus[i]);
debugPrintButtons(_pageButtons);
break;
case IAQ_PAGE_SYSTEM_SETUP:
//LOG(IAQT_LOG,LOG_INFO, "System Setup :-\n");
debugPrintButtons(_deviceSystemSetupButtons);
break;
case IAQ_PAGE_SET_VSP:
debugPrintButtons(_pageButtons);
break;
default:
//LOG(IAQT_LOG,LOG_INFO, "** UNKNOWN PAGE 0x%02hhx **\n",_currentPage);
for (i=0; i <IAQ_STATUS_PAGE_LINES; i++ )
if (_deviceStatus[i][0] != 0)
LOG(IAQT_LOG,LOG_INFO, "Page %.2d| %s\n",i,_deviceStatus[i]);
debugPrintButtons(_pageButtons);
break;
}
for (i=0; i < IAQ_MSG_TABLE_LINES; i++) {
if (strlen((char *)_tableInformation[i]) > 0)
LOG(IAQT_LOG,LOG_INFO, "Table Messages %.2d| %s\n",i,_tableInformation[i]);
}
}
#define REQUEST_STATUS_POLL_COUNT 50
bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkdata *aq_data)
{
static int cnt = 0;
static bool gotStatus = true;
//char buff[1024];
if (packet[PKT_CMD] == CMD_IAQ_PAGE_START) {
LOG(IAQT_LOG,LOG_DEBUG, "Turning IAQ SEND off\n");
set_iaq_cansend(false);
_currentPageLoading = packet[PKT_IAQT_PAGTYPE];
_currentPage = NUL;
memset(_pageButtons, 0, IAQ_PAGE_BUTTONS * sizeof(struct iaqt_page_button));
memset(_deviceStatus, 0, sizeof(char) * IAQ_STATUS_PAGE_LINES * AQ_MSGLEN+1 );
memset(_tableInformation, 0, sizeof(char) * IAQ_MSG_TABLE_LINES * AQ_MSGLEN+1 );
// Fix bug with control panel where after a few hours status page disapears and you need to hit menu.
if (gotStatus == false)
gotStatus = true;
//[IAQ_STATUS_PAGE_LINES][AQ_MSGLEN+1];
} else if (packet[PKT_CMD] == CMD_IAQ_PAGE_END) {
set_iaq_cansend(true);
LOG(IAQT_LOG,LOG_DEBUG, "Turning IAQ SEND on\n");
if (_currentPageLoading != NUL) {
_currentPage = _currentPageLoading;
_currentPageLoading = NUL;
} else {
LOG(IAQT_LOG,LOG_DEBUG, "Page end message without proceding page start, ignoring!\n");
}
processPage(aq_data);
} else if (packet[PKT_CMD] == CMD_IAQ_TABLE_MSG) {
processTableMessage(packet, length);
} else if (packet[PKT_CMD] == CMD_IAQ_PAGE_MSG) {
processPageMessage(packet, length);
} else if (packet[PKT_CMD] == CMD_IAQ_PAGE_BUTTON) {
processPageButton(packet, length);
// Second page on status doesn't send start & end, but button is message, so use that to kick off next page.
if (_currentPage == IAQ_PAGE_STATUS) {
/* Notice: Added Button 1
* Notice: To 0x33 of type iAq pBut | HEX: 0x10|0x02|0x33|0x24|0x01|0x00|0x00|0x00|0x00|0x00|0x6a|0x10|0x03|
* Button | 1 | 0x00 | |-| |-| -off-
*
* SHOULD PROBABLY USE ABOVE TO CHECK.
*/
//if (packet[PKT_IAQT_BUTTYPE] == 0x02 )
processPage(aq_data);
}
// if we get a button with 0x00 state on Light Page, that's the end of page.
if (_currentPageLoading == IAQ_PAGE_COLOR_LIGHT) {
if (packet[7] == 0x00) {
//printf("** MANUAL PAGE END\n");
LOG(IAQT_LOG,LOG_DEBUG, "MANUAL PAGE END\n");
_currentPage = _currentPageLoading;
_currentPageLoading = NUL;
processPage(aq_data);
set_iaq_cansend(true);
// Also END page here, as you can send commands.
// NEED to rethink this approach. ie, selecting light needs to hold open while showing page, no page end, then select light color, then message "please wait", the finally done
}
}
}
if (packet[3] == 0x29) {
//printf("***** iAqualink Touch STARTUP Message ******* \n");
LOG(IAQT_LOG,LOG_DEBUG, "STARTUP Message\n");
queueGetProgramData(IAQTOUCH, aq_data);
//aq_programmer(AQ_SET_IAQTOUCH_SET_TIME, NULL, aq_data);
}
// Standard ack/poll not interested in printing or kicking threads
if (packet[3] == 0x30) {
//LOG(IAQT_LOG,LOG_DEBUG, "poll count %d\n",cnt);
// Load status page every 50 messages
if (cnt++ > REQUEST_STATUS_POLL_COUNT && in_programming_mode(aq_data) == false ) {
iaqt_queue_cmd(KEY_IAQTCH_STATUS);
gotStatus = false; // Reset if we got status page, for fix panel bug.
//aq_programmer(AQ_GET_IAQTOUCH_VSP_ASSIGNMENT, NULL, aq_data);
cnt = 0;
} else if (gotStatus == false && cnt > 3) {
// Fix bug with control panel where after a few hours status page disapears and you need to hit menu.
LOG(IAQT_LOG,LOG_INFO, "Overcomming Jandy control panel bug, (missing status, goto menu)\n",cnt);
iaqt_queue_cmd(KEY_IAQTCH_HOME);
iaqt_queue_cmd(KEY_IAQTCH_STATUS);
} else if (in_programming_mode(aq_data) == true) {
// Set count to something close to above, so we will pull latest info once programming has finished.
// This is goot for VSP GPM programming as it takes number of seconds to register once finished programming.
// -5 seems to be too quick for VSP/GPM so using 10
cnt = REQUEST_STATUS_POLL_COUNT - 10;
}
return false;
}
//debuglogPacket(IAQT_LOG ,packet, length);
//_lastMsgType = packet[PKT_CMD];
set_iaqtouch_lastmsg(packet[PKT_CMD]);
//debuglogPacket(IAQT_LOG ,packet, length);
//beautifyPacket(buff, packet, length);
//LOG(IAQT_LOG,LOG_DEBUG, "%s", buff);
//temp_debugprintExtraInfo(packet, length);
kick_aq_program_thread(aq_data, IAQTOUCH);
return false;
}
const char *iaqt_page_name(const unsigned char page)
{
switch (page){
case IAQ_PAGE_HOME:
return "HOME";
break;
case IAQ_PAGE_STATUS:
return "Status";
break;
case IAQ_PAGE_STATUS2:
return "Status (diff ID)";
break;
case IAQ_PAGE_DEVICES:
return "Devices #1";
break;
case IAQ_PAGE_DEVICES2:
return "Devices #2";
break;
case IAQ_PAGE_SET_TEMP:
return "Set Temp";
break;
case IAQ_PAGE_MENU:
return "MENU";
break;
case IAQ_PAGE_SET_VSP:
return "Set VSP";
break;
case IAQ_PAGE_SET_TIME:
return "Set Time";
break;
case IAQ_PAGE_SET_DATE:
return "Set Date";
break;
case IAQ_PAGE_SET_SWG:
return "Set Aquapure";
break;
case IAQ_PAGE_SET_BOOST:
return "Set Boost";
break;
case IAQ_PAGE_SET_QBOOST:
return "Set Quick Boost";
break;
case IAQ_PAGE_ONETOUCH:
return "OneTouch";
break;
case IAQ_PAGE_COLOR_LIGHT:
return "Color Lights";
break;
case IAQ_PAGE_SYSTEM_SETUP:
return "System Setup";
break;
case IAQ_PAGE_VSP_SETUP:
return "VSP Setup";
break;
case IAQ_PAGE_FREEZE_PROTECT:
return "Freeze Protect";
break;
case IAQ_PAGE_LABEL_AUX:
return "Label Aux";
break;
default:
return "** Unknown **";
break;
}
return "";
}
void temp_debugprintExtraInfo(unsigned char *pk, int length)
{
if (pk[PKT_CMD] == CMD_IAQ_PAGE_MSG) {
int i;
printf(" Message | %d | ",(int)pk[4]);
// Byte #4 is line index on status page at least
// 1 bytes unknown #4.
// Message starts at 5
for (i=5;i<length-3;i++)
printf("%c",pk[i]);
printf("\n");
}
else if (pk[PKT_CMD] == CMD_IAQ_TABLE_MSG) {
int i;
printf(" Table Msg | %d | ",(int)pk[5]);
for (i=6;i<length-3;i++)
printf("%c",pk[i]);
printf("\n");
}
else if (pk[PKT_CMD] == CMD_IAQ_PAGE_BUTTON) {
int i;
printf(" Button | %d | 0x%02hhx | ",(int)pk[4],pk[7]);
// byte 4 is button index.
// byte 5 is status 0x00 off (0x01, 0x02, 0x03)
// byte 6
// byte 7
// 6 & 7 0x01|0x00 for normal stuff
// 6 & 7 0x00|0x0c for lights with 7 incrementing.
// 7 = Key to send in ACK to select, at least for Light modes.
// (Might also be in pk5=0x0d) Think if pk6 = 0x01, pk7 not used, if pk=0x00 then pk7 is keycode to send if you select that option.
// Byte 7 0x03 PAGE DOWN / 0x02 PAGE UP
// 2 bytes unknown #5 and #6.
// Message starts at 8
// if pk[5] == 0x0d start at 8
for (i=8;i<length-3;i++) {
// If we get a 0x00 looks like a return.
if (pk[i] == 0x00)
printf(" |-| ");
printf("%c",pk[i]);
}
if (pk[5] == 0x00)
printf(" -off- ");
else if (pk[5] == 0xff)
printf(" -blank/unknown- ");
else if (pk[5] == 0x03)
printf(" -Enabeled- ");
else if (pk[5] == 0x01)
printf(" -on- ");
else if (pk[5] == 0x0d)
printf(" -Light something 0x0d|0x%02hhx|0x%02hhx %d - ",pk[6],pk[7],(int)pk[7]);
else
printf(" -?- 0x%02hhx ",pk[5]);
printf("\n");
}
else if (pk[PKT_CMD] == CMD_IAQ_PAGE_START) {
if (pk[4] == IAQ_PAGE_STATUS)
printf(" New Page | Status\n");
else if (pk[4] == IAQ_PAGE_HOME)
printf(" New Page | Home\n");
else if (pk[4] == IAQ_PAGE_DEVICES)
printf(" New Page | Other Devices\n");
else if (pk[4] == IAQ_PAGE_DEVICES2)
printf(" New Page | Other Devices page 2\n");
else if (pk[4] == IAQ_PAGE_SET_TEMP)
printf(" New Page | Set Temp\n");
else if (pk[4] == IAQ_PAGE_MENU)
printf(" New Page | MENU\n");
else if (pk[4] == IAQ_PAGE_SET_VSP)
printf(" New Page | VSP Adjust\n");
else if (pk[4] == IAQ_PAGE_SET_TIME)
printf(" New Page | Set Time\n");
else if (pk[4] == IAQ_PAGE_SET_DATE)
printf(" New Page | Set Date\n");
else if (pk[4] == IAQ_PAGE_SET_SWG)
printf(" New Page | Set Aquapure\n");
else if (pk[4] == IAQ_PAGE_SET_BOOST)
printf(" New Page | Set BOOST\n");
else if (pk[4] == IAQ_PAGE_SET_QBOOST)
printf(" New Page | Set Quick BOOST\n");
else if (pk[4] == IAQ_PAGE_ONETOUCH)
printf(" New Page | One Touch\n");
else if (pk[4] == IAQ_PAGE_COLOR_LIGHT)
printf(" New Page | Color Light\n");
else
printf(" New Page | unknown 0x%02hhx\n",pk[4]);
}
else if (pk[PKT_CMD] == CMD_IAQ_PAGE_END) {
printf(" New Page | Finished\n");
}
}
/*
Notice: Turning IAQ SEND off
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x23|0x5b|0xc1|0x10|0x03|
New Page | Status
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x2d|0x45|0x71|0x75|0x69|0x70|0x6d|0x65|0x6e|0x74|0x20|0x53|0x74|0x61|0x74|0x75|0x73|0x00|0xcc|0x10|0x03|
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x00|0x46|0x69|0x6c|0x74|0x65|0x72|0x20|0x50|0x75|0x6d|0x70|0x00|0x90|0x10|0x03|
Message | 0 | Filter Pump
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x01|0x53|0x6f|0x6c|0x61|0x72|0x20|0x48|0x65|0x61|0x74|0x20|0x45|0x4e|0x41|0x00|0x00|0x10|0x03|
Message | 1 | Solar Heat ENA
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x02|0x41|0x75|0x78|0x31|0x00|0xc9|0x10|0x03|
Message | 2 | Aux1
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x03|0x41|0x75|0x78|0x32|0x00|0xcb|0x10|0x03|
Message | 3 | Aux2
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x04|0x50|0x6f|0x6f|0x6c|0x20|0x4c|0x69|0x67|0x68|0x74|0x00|0x1e|0x10|0x03|
Message | 4 | Pool Light
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x05|0x41|0x75|0x78|0x36|0x00|0xd1|0x10|0x03|
Message | 5 | Aux6
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x06|0x4a|0x61|0x6e|0x64|0x79|0x20|0x65|0x50|0x55|0x4d|0x50|0x20|0x20|0x20|0x31|0x00|0xbc|0x10|0x03|
Message | 6 | Jandy ePUMP 1
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x07|0x20|0x20|0x20|0x20|0x52|0x50|0x4d|0x3a|0x20|0x32|0x37|0x35|0x30|0x00|0x06|0x10|0x03|
Message | 7 | RPM: 2750
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x08|0x20|0x20|0x57|0x61|0x74|0x74|0x73|0x3a|0x20|0x30|0x00|0x4d|0x10|0x03|
Message | 8 | Watts: 0
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x09|0x49|0x6e|0x74|0x65|0x6c|0x6c|0x69|0x66|0x6c|0x6f|0x20|0x56|0x46|0x20|0x32|0x00|0x91|0x10|0x03|
Message | 9 | Intelliflo VF 2
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x0a|0x20|0x20|0x20|0x20|0x52|0x50|0x4d|0x3a|0x20|0x32|0x32|0x35|0x30|0x00|0x04|0x10|0x03|
Message | 10 | RPM: 2250
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x0b|0x20|0x20|0x57|0x61|0x74|0x74|0x73|0x3a|0x20|0x31|0x30|0x30|0x00|0xb1|0x10|0x03|
Message | 11 | Watts: 100
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x0c|0x20|0x20|0x20|0x20|0x47|0x50|0x4d|0x3a|0x20|0x31|0x30|0x30|0x00|0xc3|0x10|0x03|
Message | 12 | GPM: 100
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x0d|0x49|0x6e|0x74|0x65|0x6c|0x6c|0x69|0x66|0x6c|0x6f|0x20|0x56|0x53|0x20|0x33|0x00|0xa3|0x10|0x03|
Message | 13 | Intelliflo VS 3
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x0e|0x28|0x4f|0x66|0x66|0x6c|0x69|0x6e|0x65|0x29|0x00|0x8a|0x10|0x03|
Message | 14 | (Offline)
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x0f|0x20|0x00|0x97|0x10|0x03|
Message | 15 |
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x10|0x20|0x00|0x98|0x10|0x03|
Message | 16 |
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x11|0x20|0x00|0x99|0x10|0x03|
Message | 17 |
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x24|0x01|0x00|0x00|0x00|0x00|0x00|0x68|0x10|0x03|
Button | 1 | |-| |-| |-| -off-
Notice: Turning IAQ SEND on
*/
/*
Notice: Turning IAQ SEND off
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x23|0x5b|0xc1|0x10|0x03|
New Page | Status
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x2d|0x45|0x71|0x75|0x69|0x70|0x6d|0x65|0x6e|0x74|0x20|0x53|0x74|0x61|0x74|0x75|0x73|0x00|0xcc|0x10|0x03|
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x00|0x46|0x69|0x6c|0x74|0x65|0x72|0x20|0x50|0x75|0x6d|0x70|0x00|0x90|0x10|0x03|
Message | 0 | Filter Pump
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x01|0x41|0x75|0x78|0x31|0x00|0xc8|0x10|0x03|
Message | 1 | Aux1
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x02|0x41|0x75|0x78|0x32|0x00|0xca|0x10|0x03|
Message | 2 | Aux2
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x03|0x50|0x6f|0x6f|0x6c|0x20|0x4c|0x69|0x67|0x68|0x74|0x00|0x1d|0x10|0x03|
Message | 3 | Pool Light
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x04|0x41|0x75|0x78|0x36|0x00|0xd0|0x10|0x03|
Message | 4 | Aux6
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x05|0x4a|0x61|0x6e|0x64|0x79|0x20|0x65|0x50|0x55|0x4d|0x50|0x20|0x20|0x20|0x31|0x00|0xbb|0x10|0x03|
Message | 5 | Jandy ePUMP 1
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x06|0x20|0x20|0x20|0x20|0x52|0x50|0x4d|0x3a|0x20|0x32|0x37|0x35|0x30|0x00|0x05|0x10|0x03|
Message | 6 | RPM: 2750
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x07|0x20|0x20|0x57|0x61|0x74|0x74|0x73|0x3a|0x20|0x30|0x00|0x4c|0x10|0x03|
Message | 7 | Watts: 0
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x08|0x49|0x6e|0x74|0x65|0x6c|0x6c|0x69|0x66|0x6c|0x6f|0x20|0x56|0x46|0x20|0x32|0x00|0x90|0x10|0x03|
Message | 8 | Intelliflo VF 2
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x09|0x20|0x20|0x20|0x20|0x52|0x50|0x4d|0x3a|0x20|0x32|0x32|0x35|0x30|0x00|0x03|0x10|0x03|
Message | 9 | RPM: 2250
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x0a|0x20|0x20|0x57|0x61|0x74|0x74|0x73|0x3a|0x20|0x31|0x30|0x33|0x00|0xb3|0x10|0x03|
Message | 10 | Watts: 103
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x0b|0x20|0x20|0x20|0x20|0x47|0x50|0x4d|0x3a|0x20|0x31|0x30|0x30|0x00|0xc2|0x10|0x03|
Message | 11 | GPM: 100
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x0c|0x49|0x6e|0x74|0x65|0x6c|0x6c|0x69|0x66|0x6c|0x6f|0x20|0x56|0x53|0x20|0x33|0x00|0xa2|0x10|0x03|
Message | 12 | Intelliflo VS 3
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x0d|0x20|0x20|0x20|0x20|0x52|0x50|0x4d|0x3a|0x20|0x31|0x37|0x35|0x30|0x00|0x0b|0x10|0x03|
Message | 13 | RPM: 1750
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x0e|0x20|0x20|0x57|0x61|0x74|0x74|0x73|0x3a|0x20|0x33|0x34|0x00|0x8a|0x10|0x03|
Message | 14 | Watts: 34
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x0f|0x41|0x51|0x55|0x41|0x50|0x55|0x52|0x45|0x20|0x33|0x30|0x25|0x00|0x83|0x10|0x03|
Message | 15 | AQUAPURE 30%
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x10|0x53|0x61|0x6c|0x74|0x20|0x33|0x38|0x30|0x30|0x20|0x50|0x50|0x4d|0x00|0x04|0x10|0x03|
Message | 16 | Salt 3800 PPM
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x25|0x11|0x20|0x00|0x99|0x10|0x03|
Message | 17 |
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x24|0x01|0x00|0x00|0x00|0x00|0x00|0x68|0x10|0x03|
Button | 1 | |-| |-| |-| -off-
Notice: Turning IAQ SEND on
*/
/*
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x24|0x01|0x00|0x00|0x00|0x00|0x00|0x68|0x10|0x03|
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x24|0x01|0x00|0x00|0x02|0x00|0x00|0x6a|0x10|0x03|
No next page
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x24|0x01|0x00|0x00|0x00|0x00|0x00|0x68|0x10|0x03|
Button | 1 | 0x00 | |-| |-| -off-
Get next page
Debug: To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x12|0x25|0x10|0x03|
Has next page
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x24|0x01|0x00|0x00|0x02|0x00|0x00|0x6a|0x10|0x03|
Button | 1 | 0x02 | |-| |-| -off-
Can go up
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x24|0x01|0x00|0x00|0x00|0x00|0x00|0x68|0x10|0x03|
Button | 1 | 0x00 | |-| |-| -off-
*/
/*
No Next page
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x24|0x01|0x00|0x00|0x00|0x00|0x00|0x68|0x10|0x03|
Button | 1 | 0x00 | |-| |-| -off-
Next Page
Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x24|0x01|0x00|0x00|0x02|0x00|0x00|0x6a|0x10|0x03|
Button | 1 | 0x02 | |-| |-| -off-
*/

View File

@ -1,164 +0,0 @@
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include "packetLogger.h"
#include "aq_serial.h"
#include "utils.h"
#include "config.h"
static FILE *_packetLogFile = NULL;
static FILE *_byteLogFile = NULL;
static bool _logfile_raw = false;
static bool _logfile_packets = false;
//static bool _includePentair = false;
static unsigned char _lastReadFrom = NUL;
void _logPacket(int16_t from, unsigned char *packet_buffer, int packet_length, bool error, bool force, bool is_read);
int _beautifyPacket(char *buff, unsigned char *packet_buffer, int packet_length, bool error, bool is_read);
//void startPacketLogger(bool debug_RSProtocol_packets) {
void startPacketLogger() {
// Make local copy of variables so we can turn on/off as needed.
_logfile_raw = _aqconfig_.log_raw_bytes;
_logfile_packets = _aqconfig_.log_protocol_packets;
}
void startPacketLogging(bool log_protocol_packets, bool log_raw_bytes)
{
_logfile_raw = log_raw_bytes;
_logfile_packets = log_protocol_packets;
}
void stopPacketLogger() {
if (_packetLogFile != NULL)
fclose(_packetLogFile);
if (_byteLogFile != NULL)
fclose(_byteLogFile);
_logfile_raw = false;
_logfile_packets = false;
}
// Log passed packets
void writePacketLog(char *buffer) {
if (!_logfile_packets)
return;
if (_packetLogFile == NULL)
_packetLogFile = fopen(RS485LOGFILE, "w");
if (_packetLogFile != NULL) {
fputs(buffer, _packetLogFile);
}
}
// Log Raw Bytes
void logPacketByte(unsigned char *byte)
{
if (!_logfile_raw)
return;
char buff[10];
if (_byteLogFile == NULL)
_byteLogFile = fopen(RS485BYTELOGFILE, "w");
if (_byteLogFile != NULL) {
sprintf(buff, "0x%02hhx|",*byte);
fputs( buff, _byteLogFile);
}
//}
}
/*
void logPacket(unsigned char *packet_buffer, int packet_length) {
_logPacket(RSSD_LOG, packet_buffer, packet_length, false, false);
}
*/
void logPacketRead(unsigned char *packet_buffer, int packet_length) {
_logPacket(RSSD_LOG, packet_buffer, packet_length, false, false, true);
}
void logPacketWrite(unsigned char *packet_buffer, int packet_length) {
_logPacket(RSSD_LOG, packet_buffer, packet_length, false, false, false);
}
void logPacketError(unsigned char *packet_buffer, int packet_length) {
_logPacket(RSSD_LOG, packet_buffer, packet_length, true, false, true);
}
void debuglogPacket(int16_t from, unsigned char *packet_buffer, int packet_length, bool is_read) {
if ( getLogLevel(from) >= LOG_DEBUG )
_logPacket(from, packet_buffer, packet_length, false, true, is_read);
}
void _logPacket(int16_t from, unsigned char *packet_buffer, int packet_length, bool error, bool force, bool is_read)
{
// No point in continuing if loglevel is < debug_serial and not writing to file
if ( force == false &&
error == false &&
getLogLevel(from) < LOG_DEBUG_SERIAL &&
/*_logfile_raw == false &&*/
_logfile_packets == false ) {
return;
}
if ( _aqconfig_.RSSD_LOG_filter != NUL ) {
if (is_read) {
_lastReadFrom = packet_buffer[PKT_DEST];
if ( is_read && _aqconfig_.RSSD_LOG_filter != packet_buffer[PKT_DEST]) {
return;
}
} else if (!is_read && _lastReadFrom != _aqconfig_.RSSD_LOG_filter) // Must be write
return;
/*
if ( is_read && _aqconfig_.RSSD_LOG_filter != packet_buffer[PKT_DEST]) {
return;
}
*/
}
char buff[1000];
_beautifyPacket(buff, packet_buffer, packet_length, error, is_read);
if (_logfile_packets)
writePacketLog(buff);
if (error == true)
LOG(from,LOG_WARNING, "%s", buff);
else {
if (force)
LOG(from,LOG_DEBUG, "%s", buff);
//else if (is_read && _aqconfig_.serial_debug_filter != NUL && _aqconfig_.serial_debug_filter == packet_buffer[PKT_DEST])
// LOG(from,LOG_NOTICE, "%s", buff);
else
LOG(from,LOG_DEBUG_SERIAL, "%s", buff);
}
}
int beautifyPacket(char *buff, unsigned char *packet_buffer, int packet_length, bool is_read)
{
return _beautifyPacket(buff, packet_buffer, packet_length, false, is_read);
}
int _beautifyPacket(char *buff, unsigned char *packet_buffer, int packet_length, bool error, bool is_read)
{
int i = 0;
int cnt = 0;
if (getProtocolType(packet_buffer)==PENTAIR) {
// Listing Jandy below if redundant. need to clean this up.
cnt = sprintf(buff, "%5.5s %s%8.8s Packet | HEX: ",(is_read?"Read":"Write"),(error?"BAD PACKET ":""),getProtocolType(packet_buffer)==PENTAIR?"Pentair":"Jandy");
} else {
cnt = sprintf(buff, "%5.5s %sTo 0x%02hhx of type %16.16s | HEX: ",(is_read?"Read":"Write"),(error?"BAD PACKET ":""), packet_buffer[PKT_DEST], get_packet_type(packet_buffer, packet_length));
}
for (i = 0; i < packet_length; i++)
cnt += sprintf(buff + cnt, "0x%02hhx|", packet_buffer[i]);
cnt += sprintf(buff + cnt, "\n");
return cnt;
}

View File

@ -1,37 +0,0 @@
#ifndef PDA_AQ_PROGRAMMER_H_
#define PDA_AQ_PROGRAMMER_H_
typedef enum pda_type {
AQUAPALM,
PDA
} pda_type;
void *get_aqualink_PDA_device_status( void *ptr );
void *set_aqualink_PDA_device_on_off( void *ptr );
void *set_aqualink_PDA_wakeinit( void *ptr );
void *set_aqualink_PDA_init( void *ptr );
bool set_PDA_aqualink_heater_setpoint(struct aqualinkdata *aq_data, int val, bool isPool);
bool set_PDA_aqualink_SWG_setpoint(struct aqualinkdata *aq_data, int val);
bool set_PDA_aqualink_freezeprotect_setpoint(struct aqualinkdata *aq_data, int val);
bool get_PDA_aqualink_pool_spa_heater_temps(struct aqualinkdata *aq_data);
bool get_PDA_freeze_protect_temp(struct aqualinkdata *aq_data);
bool get_PDA_aqualink_aux_labels(struct aqualinkdata *aq_data);
bool set_PDA_aqualink_boost(struct aqualinkdata *aq_data, bool val);
bool set_PDA_aqualink_time(struct aqualinkdata *aq_data);
// These are from aq_programmer.c , exposed here for PDA AQ PROGRAMMER
void send_cmd(unsigned char cmd);
bool push_aq_cmd(unsigned char cmd);
bool waitForMessage(struct aqualinkdata *aq_data, char* message, int numMessageReceived);
void waitfor_queue2empty();
void longwaitfor_queue2empty();
//void pda_programming_thread_check(struct aqualinkdata *aq_data);
#endif // AQ_PDA_PROGRAMMER_H_

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.
@ -21,15 +22,9 @@ web_directory=/var/www/aqualinkd/
log_level=NOTICE
#log_level=WARNING
# Display any ERROR & Warning messages in web interface.
display_warnings_in_web=true
# 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
# 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.
@ -59,7 +54,8 @@ panel_type = RS-8 Combo
# 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)
device_id=0x0a
# 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
@ -73,73 +69,37 @@ device_id=0x0a
# 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
# If you have extended_device_id set, then you can also use that ID for programming some features.
# This means that you can turn things on/off while AqualinkD is programming certian features.
# If you are using Aqualink Touch protocol for extended_device_id then this is highly recomended
# as it will speed up programming substantially. if One Touch it's 50/50.
#extended_device_id_programming = yes
# Read information from these devices directly from the RS485 bus as well as control panel.
# 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
# Keep the panel time synced with systemtime. Make sure to set systemtime / NTP correctly.
keep_paneltime_synced = 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
# Convert Deg F to Deg C when posting to Domoticz or MQTT.
# If using homebridge-aqualinkd convert_mqtt_temp_to_c must be set to yes.
convert_mqtt_temp_to_c = yes
convert_dz_temp_to_c = 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 = yes
# default is to not report changes to pool temp when the filter pump is off or in spa mode
# enable below to report 0 as the pool temp when the filter pump is off or when in spa mode.
# 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_pool_temp = no
# 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
# MQTT will only post updated information, this option AqualinkD will re-post all MQTT information every ~5 minutes.
#mqtt_timed_update = no
# Please see forum for this, only set to yes when logging information to support new devices. (or debugging protocol)
# Information will be written to /tmp/RS485.log & /tmp/RS485_raw.log respectively
#debug_RSProtocol_packets = no
#debug_RSProtocol_bytes = no
# Log any packets from this device.
#serial_debug_filter = 0x00
# Not documented. These are experimental. Will change how RS485 / Serial works, Only use if asked to for problem solving purposes.
#serial_readahead_b4_write = yes
#prioritize_ack = yes
# Get rid of the startup warning message about no low latency. BETTER option is to buy a better adapter.
#ftdi_low_latency = no
# Stop the SWG bounce from displaying when using VSP.
#swg_zero_ignore_count = 20
# 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.
@ -147,61 +107,107 @@ report_zero_pool_temp = no
# 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 = 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 = 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
# 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
# If you have a SWG connected to the control panel, set this to yes.
# AqualinkD can only detect a SWG if it's on, so after a restart you will not see/access a SWG until the the next time the pump is on.
force_SWG = no
# AqualinkD can take sime time to find heater setpoints (if they exist), This will force the pool & spa
# heaters to be listed as thermostats vs switches on startup, helps with homekit.
force_PS_setpoints = no
# Lights can be programmed by control panel or AqualinkD (if controlpanel doesn;t support specific light or light mode you want)
# 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
# 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
# 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.
# All below are Virtual Sensors
#air_temp_dzidx=0
#pool_water_temp_dzidx=0
#spa_water_temp_dzidx=0
#SWG_percent_dzidx=0
#SWG_PPM_dzidx=0
# Must be Virtual Alert Sensor
#SWG_Status_dzidx=0
# Use/find labels from Control Panel, these will overwrite the button_xx_label below,
# it noes NOT work in PDA mode, and it also considerable slows down AqualinkD startup process.
use_panel_aux_labels=no
# 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_??_lightMode=4 <Color light mode>
# 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.
@ -210,28 +216,28 @@ use_panel_aux_labels=no
#
# Below is an example of how different Panels map into the buttons.
#
# | 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 |
# Button_02 | Spa | Aux_1 | Spa | Spa | Spa | Spa |
# Button_03 | Aux 1 | Aux 2 | Aux 1 | Aux 1 | Aux 1 | Aux 1 |
# Button_04 | Aux 2 | Aux 3 | Aux 2 | Aux 2 | Aux 2 | Aux 2 |
# Button_05 | Aux 3 | Aux 4 | Aux 3 | Aux 3 | Aux 3 | Aux 3 |
# Button_06 | Aux 4 | Aux 5 | Aux 4 | Aux 4 | Aux 4 | Aux 4 |
# Button_07 | Aux 5 | Temp 1 | Aux 5 | Aux 5 | Aux 5 | Aux 5 |
# Button_08 | 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 |
# | 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 |
#
# Optional, ( button_01_pumpID & button_01_pumpIndex )
@ -245,32 +251,60 @@ use_panel_aux_labels=no
# 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=Dimmer)
#
# 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_label=Filter Pump
#button_01_pumpIndex=1
#button_01_pumpID=0x78
#button_01_pumpName=Intelliflo VS 1
#button_01_pumpType=Pentair VS
button_02_label=Spa Mode
#button_02_label=Spa
#button_03_label=Cleaner
#button_04_label=Waterfall
#button_05_label=Spa Blower
button_03_label=Cleaner
#button_06_label=Pool Light
#button_06_lightMode=2
button_04_label=Waterfall
#button_07_label=Spa Light
#button_07_lightMode=2
button_05_label=Spa Blower
#button_08_label=NONE\
#button_09_label=NONE
#button_10_label=Pool Heater
#button_11_label=Spa Heater
#button_12_label=Solar Heater
button_06_label=Pool Light
#button_05_lightMode=0
# 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_07_label=Spa Light
#button_05_lightMode=0
# 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_08_label=NONE
#sensor_01_path = /sys/class/thermal/thermal_zone0/temp
#sensor_01_label = CPU
#sensor_01_factor = 0.001
button_09_label=NONE
# 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
button_10_label=Pool Heater
button_11_label=Spa Heater
button_12_label=Solar Heater

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

View File

@ -1,282 +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
# 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
# AQUA_LOG 1
# NET_LOG 2
# AQRS_LOG 4
# ONET_LOG 8
# IAQT_LOG 16
# PDA_LOG 32
# RSSA_LOG 64
# DJAN_LOG 128
# DPEN_LOG 256
# RSSD_LOG 512 // Serial connection
# PROG_LOG 1024
# DBGT_LOG 2048
# TIMR_LOG 4096
#debug_log_mask = 1
#debug_log_mask = 2
#debug_log_mask = 4
#debug_log_mask = 8
#debug_log_mask = 16
#debug_log_mask = 32
#debug_log_mask = 64
#debug_log_mask = 256
#debug_log_mask = 512
#debug_log_mask = 1024
#debug_log_mask = 2048
#debug_log_mask = 4096
display_warnings_in_web = yes
# 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
#serial_port=/dev/ttyUSB1
#serial_port=/dev/null
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=0x0a
#device_id=0xFF # For testing one touch, don't use kaypad
#device_id=0x00
device_id=0x60
#rssa_device_id=0x48
# The ID for extended settings, These are ONE TOUCH MACROS & VARIABLE SPEED PUMP RPM
# 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.
# If you have a one touch remote do not use Ox40
#extended_device_id=0x43
#extended_device_id=0x31
# If you have extended_device_id set, then you can also use that ID for programming some features.
# This means that you can turn things on/off while AqualinkD is programming certian features.
# At the moment only heater setpoints & swg boost is on the extended device programming
#extended_device_id_programming = yes
#extended_device_id_programming = no
# Not documented
serial_readahead_b4_write = yes
mqtt_timed_update = no
thread_netservices = yes
rs_poll_speed = -1
#rs_poll_speed = 1
# 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.
#rs_panel_size = 12
#panel_type = RS-2/6 Dual
#panel_type = RS-4 Only
#panel_type = RS-4 Combo
#panel_type = RS-6 Only
#panel_type = RS-8 Combo
panel_type = PD-8 Combo
#panel_type = RS-16 Combo
#panel_type = RS-2/14 Dual
#panel_type = RS-8 Only
#panel_type_size = 8
#panel_type_combo = yes
#panel_type_dual = no
#panel_type_pda = no
#panel_type_rs = yes
#network_poll_speed = 1
keep_paneltime_synced = yes
#pda_mode = yes
#use_PDA_auxiliary = yes
# Read information from these devices directly from the RS485 bus as well as control panel.
#read_RS485_swg = yes
#read_RS485_ePump = no
#read_RS485_vsfPump = no
# F to C conversions
convert_mqtt_temp_to_c = yes
convert_dz_temp_to_c = 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
report_zero_pool_temp = no
# 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
# Try to use labels from Control Panel.
#use_panel_aux_labels=yes
# If you have a SWG, set this to yes. AqualinkD can only detect a SWG if it's on, so after a restart
# you will not see/access a SWG until the the next time the pump is on.
force_SWG = yes
# Please see forum for this, only set to yes when logging information to support
# new devices. Inflrmation will be written to /tmp/RS485.log & /tmp/RS485_raw.log respectively
debug_RSProtocol_packets = no
debug_RSProtocol_bytes = no
# 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
#
# | 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 |
# Button_02 | Spa | Aux_1 | Spa | Spa | Spa | Spa |
# Button_03 | Aux 1 | Aux 2 | Aux 1 | Aux 1 | Aux 1 | Aux 1 |
# Button_04 | Aux 2 | Aux 3 | Aux 2 | Aux 2 | Aux 2 | Aux 2 |
# Button_05 | Aux 3 | Aux 4 | Aux 3 | Aux 3 | Aux 3 | Aux 3 |
# Button_06 | Aux 4 | Aux 5 | Aux 4 | Aux 4 | Aux 4 | Aux 4 |
# Button_07 | Aux 5 | Temp 1 | Aux 5 | Aux 5 | Aux 5 | Aux 5 |
# Button_08 | 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 |
#
# Pentair pump ID's
# 0x60 to 0x6F (0x60, 0x61 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F)
# Jandy pump ID's
# 0x78, 0x79, 0x7A, 0x7B
# button_xx_lightMode = (0=Aqualink program, 1=Jandy, 2=Jandy LED, 3=SAm/SAL, 4=Color Logic, 5=Intellibrite)
# Labels for standard butons (shown in web UI), and domoticz idx's
button_01_label=Filter Pump
#button_01_dzidx=37
button_01_pumpID=0x78
#button_01_PDA_label=FILTER PUMP
button_01_pumpIndex=1
button_02_label=Spa Mode
#button_02_dzidx=38
#button_02_pumpID= 0x78
#button_02_pumpIndex=3
#button_02_PDA_label=SPA
button_03_label=Aux1
#button_03_dzidx=39
button_03_pumpID=0x61
button_03_pumpIndex=3
button_04_label=Aux2
#button_04_dzidx=40
button_04_pumpID=0x60
button_04_pumpIndex=2
button_05_label=Aux3
#button_05_dzidx=41
button_05_lightMode=4
button_06_label=Aux4
#button_06_dzidx=42
#button_06_lightMode=1
#button_06_pumpIndex=4
button_07_label=Aux5
#button_07_label=NONE
#button_07_dzidx=43
#button_07_lightMode=0
button_08_label=Aux6
#button_09_label=Fountain
button_10_label=Pool Heat
#button_10_dzidx=44
button_11_label=Spa Heat
#button_11_dzidx=56
#button_12_label=Solar Heater
#button_12_label=Solar Heater
#button_12_dzidx=NONE
# RS-8 & RS-6 STOP HERE
# This is for RS-12 & RS-16 only.
#button_12_label=Aux B1
#button_13_label=
#button_14_label=
#button_15_label=
#button_16_label=
#button_17_label=Pool Heater
#button_18_label=Spa Heater
#button_19_label=Solar Heater

View File

@ -5,6 +5,7 @@
BUILD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PARENT_COMMAND=$(ps -o comm= $PPID)
SERVICE="aqualinkd"
@ -21,18 +22,95 @@ 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
log()
{
echo "$*"
if [[ $LOG_SYSTEMD -eq 0 ]]; then
logger -p local0.notice -t aqualinkd "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
}
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 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
# 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
# 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
@ -40,7 +118,7 @@ SERVICE_EXISTS=$(echo $?)
# 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
@ -54,6 +132,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
@ -64,44 +145,62 @@ fi
# Check cron.d options
if [ ! -d "/etc/cron.d" ]; then
echo "The version of Cron may not support chron.d, if so AqualinkD Scheduler will not work"
echo "Please check before starting"
log "The version of Cron may not support chron.d, if so AqualinkD Scheduler will not work"
log "Please check before starting"
else
if [ -f "/etc/default/cron" ]; then
CD=$(cat /etc/default/cron | grep -v ^# | grep "\-l")
if [ -z "$CD" ]; then
echo "Please enabled cron.d support, if not AqualinkD Scheduler will not work"
echo "Edit /etc/default/cron and look for the -l option, usually in EXTRA_OPTS"
log "Please enabled cron.d support, if not AqualinkD Scheduler will not work"
log "Edit /etc/default/cron and look for the -l option, usually in EXTRA_OPTS"
fi
else
echo "Please make sure the version if Cron supports chron.d, if not the AqualinkD Scheduler will not work"
log "Please make sure the version if Cron supports chron.d, if not the AqualinkD Scheduler will not work"
fi
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"; 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 "AqualinkD 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 "AqualinkD 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
@ -110,20 +209,38 @@ if [ ! -d "$WEBLocation" ]; then
fi
if [ -f "$WEBLocation/config.js" ]; then
echo "AqualinkD web config exists, did not copy new config, you may need to edit existing $WEBLocation/config.js "
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
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
echo "Please edit $CFGLocation/$CFG, then start AqualinkD service"
log "Please edit $CFGLocation/$CFG, then start AqualinkD service with `sudo systemctl start aqualinkd`"
fi

332
release/remote_install.sh Executable file
View File

@ -0,0 +1,332 @@
#!/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
# Remember not to use (check for terminal, as it may not exist when pipe to bash)
# ie. if [ -t 0 ]; then
if command -v "systemd-cat" &>/dev/null; then SYSTEMD_LOG=$TRUE;fi
log()
{
echo "$*"
if [[ $SYSTEMD_LOG -eq $TRUE ]]; then
echo "Upgrade: $*" | systemd-cat -t aqualinkd -p info
else
logger -p local0.notice -t aqualinkd "Upgrade: $*"
fi
echo "$*" 2>/dev/null >> "$OUTPUT"
}
logerr()
{
echo "Error: $*" >&2
if [[ $SYSTEMD_LOG -eq $TRUE ]]; then
echo "Upgrade: $*" | systemd-cat -t aqualinkd -p err
else
logger -p local0.error -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.
nohup "$TEMP_INSTALL/release/install.sh" >> "$OUTPUT" 2>&1
#source "/nas/data/Development/Raspberry/AqualinkD/release/install.sh" &>> "$OUTPUT"
#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 ot 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,211 +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
*/
#define _GNU_SOURCE 1 // for strcasestr
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "utils.h"
/*
int check_panel_conf(char *panel)
{
"RS-16 Combo"
"PD-8 Only"
"PD-8 Combo"
"RS-2/14 Dual"
"RS-2/10 Dual"
"RS-16 Only"
"RS-12 Only"
"RS-16 Combo"
"RS-12 Combo"
"RS-2/6 Dual"
"RS-4 Only"
"RS-6 Only"
"RS-8 Only"
"RS-4 Combo"
"RS-6 Combo"
"RS-8 Combo"
}
*/
char *rsm_strstr(const char *haystack, const char *needle)
{
char *sp1 = (char *)haystack;
char *sp2 = (char *)needle;
//int i=0;
// Get rid of all padding
while(isspace(*sp1)) sp1++;
while(isspace(*sp2)) sp2++;
if (strlen(sp1) == 0 || strlen(sp2) == 0)
return NULL;
// Need to write this myself for speed
// Maybe use stristr from utils.c in the future. Needs a lot of testing.
//LOG(AQUA_LOG,LOG_DEBUG, "Compare (reset)%d chars of '%s' to '%s'\n",strlen(sp2),sp1,sp2);
return strcasestr(sp1, sp2);
}
char *rsm_strnstr(const char *haystack, const char *needle, int length)
{
// NEED TO WRITE THIS MYSELF. Same as below but limit length
return strcasestr(haystack, needle);
}
// Check s2 exists in s1
int rsm_strcmp(const char *haystack, const char *needle)
{
char *sp1 = (char *)haystack;
char *sp2 = (char *)needle;
//int i=0;
// Get rid of all padding
while(isspace(*sp1)) sp1++;
while(isspace(*sp2)) sp2++;
if (strlen(sp1) == 0 || strlen(sp2) == 0)
return -1;
// Need to write this myself for speed
//LOG(AQUA_LOG,LOG_DEBUG, "Compare (reset)%d chars of '%s' to '%s'\n",strlen(sp2),sp1,sp2);
return strncasecmp(sp1, sp2, strlen(sp2));
}
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
int rsm_strncmp(const char *haystack, const char *needle, int length)
{
char *sp1 = (char *)haystack;
char *sp2 = (char *)needle;
char *ep1 = (sp1+length);
//int i=0;
// Get rid of all padding
while(isspace(*sp1)) sp1++;
while(isspace(*sp2)) sp2++;
if (strlen(sp1) == 0 || strlen(sp2) == 0)
return -1;
// Work out last char in haystack
while(isspace(*ep1)) ep1--;
LOG(AQUA_LOG,LOG_DEBUG, "CHECK haystack SP1='%c' EP1='%c' SP2='%c' '%.*s' for '%s' length=%d\n",*sp1,*ep1,*sp2,(ep1-sp1)+1,sp1,sp2,(ep1-sp1)+1);
// Need to write this myself for speed
// Need to check if full length string (no space on end), that the +1 is accurate. MIN should do it
return strncasecmp(sp1, sp2, MIN((ep1-sp1)+1,length));
}
// NSF Check is this works correctly.
char *rsm_strncpycut(char *dest, const char *src, int dest_len, int src_len)
{
char *sp = (char *)src;
char *ep = (sp+dest_len);
while(isspace(*sp)) sp++;
while(isspace(*ep)) ep--;
int length=MIN((ep-sp)+1,dest_len);
memset(dest, '\0',dest_len);
return strncpy(dest, sp, length);
//dest[length] = '\0';
}
int _rsm_strncpy(char *dest, const unsigned char *src, int dest_len, int src_len, bool nulspace)
{
int i;
int end = dest_len < src_len ? dest_len:src_len;
//0x09 is Tab and means next field on table.
for(i=0; i < end; i++) {
//0x00 on button is space
//0x00 on message is end
if (src[i] == 0x00 && nulspace)
dest[i] = ' ';
else if (src[i] == 0x00 && !nulspace)
{
dest[i] = '\0';
break;
}
else if ( (src[i] < 32 || src[i] > 126) && src[1] != 10 ) // only printable chars
dest[i] = ' ';
else
dest[i] = src[i];
//printf("Char %c to %c\n",src[i],dest[i]);
}
//printf("--'%s'--\n",dest);
if (dest[i] != '\0') {
if (i < (dest_len-1))
i++;
dest[i] = '\0';
}
return i;
}
int rsm_strncpy(char *dest, const unsigned char *src, int dest_len, int src_len)
{
return _rsm_strncpy(dest, src, dest_len, src_len, false);
}
int rsm_strncpy_nul2sp(char *dest, const unsigned char *src, int dest_len, int src_len)
{
return _rsm_strncpy(dest, src, dest_len, src_len, true);
}
#define INT_MAX +2147483647
#define INT_MIN -2147483647
// atoi that can have blank start
int rsm_atoi(const char* str)
{
int sign = 1, base = 0, i = 0;
// if whitespaces then ignore.
if (str == NULL)
return -1;
while (str[i] == ' ') {
i++;
}
// checking for valid input
while (str[i] >= '0' && str[i] <= '9') {
// handling overflow test case
if (base > INT_MAX / 10 || (base == INT_MAX / 10 && str[i] - '0' > 7)) {
if (sign == 1)
return INT_MAX;
else
return INT_MIN;
}
base = 10 * base + (str[i++] - '0');
}
return base * sign;
}
// atof that can have blank start
float rsm_atof(const char* str)
{
int i=0;
while (str[i] == ' ') {
i++;
}
return atof(&str[i]);
}

View File

@ -1,14 +0,0 @@
#ifndef RS_MSG_UTILS_H_
#define RS_MSG_UTILS_H_
char *rsm_strstr(const char *haystack, const char *needle);
char *rsm_strnstr(const char *haystack, const char *needle, int length);
int rsm_strncpy(char *dest, const unsigned char *src, int dest_len, int src_len);
int rsm_strcmp(const char *s1, const char *s2);
int rsm_strncmp(const char *haystack, const char *needle, int length);
int rsm_strncpy_nul2sp(char *dest, const unsigned char *src, int dest_len, int src_len);
int rsm_atoi(const char* str);
float rsm_atof(const char* str);
char *rsm_strncpycut(char *dest, const char *src, int dest_len, int src_len);
#endif //RS_MSG_UTILS_H_

View File

@ -1,16 +0,0 @@
#ifndef SERIAL_LOGGER_H_
#define SERIAL_LOGGER_H_
/*
int logPackets = PACKET_MAX;
int logLevel = LOG_NOTICE;
bool panleProbe = true;
bool rsSerialSpeedTest = false;
//bool serialBlocking = true;
bool errorMonitor = false;
*/
//int serial_logger(int rs_fd, char *port_name);
int serial_logger(int rs_fd, char *port_name, int logPackets, int logLevel, bool panleProbe, bool rsSerialSpeedTest, bool errorMonitor);
#endif // SERIAL_LOGGER_H_

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,26 @@
#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_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_

View File

@ -23,6 +23,9 @@
#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"
@ -30,18 +33,36 @@
#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 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"
@ -58,6 +79,7 @@
#define MQTT_FLASH "2"
#define MQTT_ON "1"
#define MQTT_OFF "0"
#define MQTT_COOL MQTT_FLASH
#define MQTT_LWM_TOPIC "Alive"

1495
source/aq_panel.c Normal file

File diff suppressed because it is too large Load Diff

142
source/aq_panel.h Normal file
View File

@ -0,0 +1,142 @@
#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 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 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.
// NEED TO FIX, IF WE CHANGE TO ANOTHING OTHER THAN 0 CORE DUMP (we also had "panel_type = RS-16 Combo" in config)
// FIX IS PROBABLY MAKE SURE LEDS IS SAME OR MORE.
#define VIRTUAL_BUTTONS 0
//#define TOTAL_BUTTONS 12+VIRTUAL_BUTTONS
#define TOTAL_BUTTONS 20+VIRTUAL_BUTTONS // Biggest jandy panel
// This needs to be called AFTER and as well as initButtons
void initButtons_RS16(struct aqualinkdata *aqdata);
#endif

1067
source/aq_programmer.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -16,11 +16,15 @@
#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
@ -29,16 +33,20 @@
#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.
JANDY_DEVICE // Very rarley used.
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,
@ -46,19 +54,31 @@ typedef enum {
AQ_SET_SPA_HEATER_TEMP,
AQ_SET_FRZ_PROTECTION_TEMP,
AQ_GET_DIAGNOSTICS_MODEL,
//AQ_SEND_CMD,
AQ_GET_PROGRAMS,
AQ_SET_LIGHTPROGRAM_MODE,
AQ_SET_LIGHTCOLOR_MODE,
AQ_PDA_INIT,
AQ_SET_SWG_PERCENT,
AQ_PDA_DEVICE_STATUS,
AQ_PDA_DEVICE_ON_OFF,
AQ_GET_AUX_LABELS,
AQ_PDA_WAKE_INIT,
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,
// ******** OneTouch Delimiter make sure to change MAX/MIN below
AQ_SET_ONETOUCH_PUMP_RPM,
AQ_SET_ONETOUCH_MACRO,
AQ_GET_ONETOUCH_SETPOINTS,
@ -69,6 +89,7 @@ typedef enum {
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,
@ -78,17 +99,50 @@ typedef enum {
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_SET_TIME,
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
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 programmingThreadCtrl {
pthread_t thread_id;
//void *thread_args;
@ -96,11 +150,12 @@ struct programmingThreadCtrl {
struct aqualinkdata *aq_data;
};
typedef enum pump_type {
PT_UNKNOWN = -1,
EPUMP,
VSPUMP,
VFPUMP
EPUMP, // = ePump AC & Jandy ePUMP
VSPUMP, // = Intelliflo VS
VFPUMP // = Intelliflo VF (GPM)
} pump_type;
@ -114,15 +169,15 @@ 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 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);
//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() // Yes I want compiler warning if this is used.
void force_queue_delete(); // NSF This needs to be deleted (come back and fix)
//bool push_aq_cmd(unsigned char cmd);
@ -133,7 +188,7 @@ void cleanAndTerminateThread(struct programmingThreadCtrl *threadCtrl);
//void *set_aqualink_time( void *ptr );
//void *get_aqualink_pool_spa_heater_temps( void *ptr );
int get_aq_cmd_length();
//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);

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_

View File

@ -30,6 +30,8 @@
#include "utils.h"
#include "config.h"
#include "packetLogger.h"
#include "timespec_subtract.h"
#include "aqualink.h"
/*
Notes for serial usb speed
@ -45,6 +47,9 @@ ioctl(fd, TIOCSSERIAL, &serial);
*/
// Default to send command with leading NUL, this changes that
//#define SEND_CMD_WITH_TRAILING_NUL
//#define BLOCKING_MODE
static bool _blocking_mode = false;
@ -52,12 +57,115 @@ int _blocking_fds = -1;
static struct termios _oldtio;
static struct timespec _last_serial_read_time;
void send_packet(int fd, unsigned char *packet, int length);
//unsigned char getProtocolType(unsigned char* packet);
emulation_type getJandyDeviceType(unsigned char ID) {
// Using emulation_type from aqprogrammer. At some point may merge into one
// and call device type
if (ID >= 0x08 && ID <= 0x0B)
return ALLBUTTON;
if (ID >= 0x40 && ID <= 0x43)
return ONETOUCH;
if (ID >= 0x48 && ID <= 0x4B)
return RSSADAPTER;
if (ID >= 0x60 && ID <= 0x63)
return AQUAPDA;
if (ID >= 0x30 && ID <= 0x33)
return IAQTOUCH;
if (ID >= 0xa0 && ID <= 0xa3)
return IAQUALNK;
const char* get_packet_type(unsigned char* packet , int length)
/*
if (ID >= 0x00 && ID <= 0x03)
return MASTER;
if (ID >= 0x50 && ID <= 0x53)
return SWG;
if (ID >= 0x20 && ID <= 0x23)
return SPA_R;
if (ID >= 0x38 && ID <= 0x3B)
return LX_HEATER;
if (ID >= 0x58 && ID <= 0x5B)
return PC_DOCK;
if (ID >= 0x68 && ID <= 0x6B)
return JXI_HEATER;
//if (ID >= 0x70 && ID <= 0x73)
if (ID >= 0x78 && ID <= 0x7B)
return EPUMP;
if (ID >= 0x80 && ID <= 0x83)
return CHEM;
//if (ID == 0x08)
// return KEYPAD;
*/
return SIM_NONE;
}
const char *getJandyDeviceName(emulation_type etype) {
switch(etype){
case ALLBUTTON:
return "AllButton";
break;
case ONETOUCH:
return "OneTouch";
break;
case RSSADAPTER:
return "RS SerialAdapter";
break;
case IAQTOUCH:
return "iAqualinkTouch";
break;
case AQUAPDA:
return "PDA";
break;
default:
return "Unknown";
break;
}
}
const char* get_pentair_packet_type(unsigned char* packet , int length)
{
static char buf[15];
if (length <= 0 )
return "";
switch (packet[PEN_PKT_CMD]) {
case PEN_CMD_SPEED:
if (packet[PEN_PKT_DEST]== PEN_DEV_MASTER)
return "VSP SetSpeed rtn";
else
return "VSP SetSpeed";
break;
case PEN_CMD_REMOTECTL:
if (packet[PEN_PKT_DEST]== PEN_DEV_MASTER)
return "VSP RemoteCtl rtn";
else
return "VSP RemoteCtl";
break;
case PEN_CMD_POWER:
if (packet[PEN_PKT_DEST]== PEN_DEV_MASTER)
return "VSP SetPower rtn";
else
return "VSP SetPower";
break;
case PEN_CMD_STATUS:
if (packet[PEN_PKT_DEST]== PEN_DEV_MASTER)
return "VSP Status";
else
return "VSP GetStatus";
break;
default:
sprintf(buf, "Unknown '0x%02hhx'", packet[PEN_PKT_CMD]);
return buf;
break;
}
}
const char* get_jandy_packet_type(unsigned char* packet , int length)
{
static char buf[15];
@ -117,7 +225,10 @@ const char* get_packet_type(unsigned char* packet , int length)
return "iAq pMessage";
break;
case CMD_IAQ_PAGE_BUTTON:
return "iAq pButton";
if (packet[PKT_DEST] == 0x00) // To master is iAqualink2 send command
return "iAqualnk sendCmd";
else
return "iAq pButton";
break;
case CMD_IAQ_POLL:
return "iAq Poll";
@ -137,6 +248,21 @@ const char* get_packet_type(unsigned char* packet , int length)
case CMD_IAQ_STARTUP:
return "iAq init";
break;
case CMD_IAQ_TITLE_MESSAGE:
return "iAq ProductName";
break;
case CMD_IAQ_MSG_LONG:
return "iAq Popup message";
break;
case CMD_IAQ_MAIN_STATUS:
return "iAq Main status";
break;
case CMD_IAQ_1TOUCH_STATUS:
return "iAq 1Tch status";
break;
case CMD_IAQ_AUX_STATUS:
return "iAq AUX status";
break;
case RSSA_DEV_STATUS:
// This is a fail reply 0x10|0x02|0x48|0x13|0x02|0x00|0x10|0x00|0x7f|0x10|0x03|
// Rather than check all, just check 0x02 and checksum sin't I'm not sure 0x10 means faiure without 0x00 around it.
@ -148,6 +274,44 @@ const char* get_packet_type(unsigned char* packet , int length)
case RSSA_DEV_READY:
return "RSSA SendCommand";
break;
case CMD_EPUMP_STATUS:
if (packet[4] == CMD_EPUMP_RPM)
return "ePump RPM";
else if (packet[4] == CMD_EPUMP_WATTS)
return "ePump Watts";
else
return "ePump (unknown)";
break;
case CMD_EPUMP_RPM:
return "ePump set RPM";
break;
case CMD_EPUMP_WATTS:
return "ePump get Watts";
break;
case CMD_JXI_PING:
if (packet[4] == 0x19)
return "LXi heater on";
else // 0x11 is normal off
return "LXi heater ping";
break;
case CMD_JXI_STATUS:
if (packet[6] == 0x10)
return "LXi error";
else
return "LXi status";
break;
case 0x53:
return "iAqalnk Poll";
break;
case CMD_IAQ_CMD_READY:
return "iAqalnk rec ready";
break;
case CMD_IAQ_CTRL_READY:
return "iAq receive ready";
break;
default:
sprintf(buf, "Unknown '0x%02hhx'", packet[PKT_CMD]);
return buf;
@ -155,6 +319,16 @@ const char* get_packet_type(unsigned char* packet , int length)
}
}
const char* get_packet_type(unsigned char* packet , int length)
{
if (getProtocolType(packet)==PENTAIR) {
return get_pentair_packet_type(packet, length);
}
return get_jandy_packet_type(packet, length);
}
// Generate and return checksum of packet.
int generate_checksum(unsigned char* packet, int length)
{
@ -181,7 +355,7 @@ bool check_jandy_checksum(unsigned char* packet, int length)
LOG(RSSD_LOG,LOG_INFO, "Ignoring bad checksum, seems to be bug in Jandy protocol\n");
if (getLogLevel(RSSD_LOG) >= LOG_DEBUG) {
static char buf[1000];
beautifyPacket(buf,packet,length,true);
beautifyPacket(buf,1000, packet,length,true);
LOG(RSSD_LOG,LOG_DEBUG, "Packetin question %s\n",buf);
}
return true;
@ -319,24 +493,15 @@ int _init_serial_port(const char* tty, bool blocking, bool readahead);
int init_serial_port(const char* tty)
{
#ifdef AQ_NO_THREAD_NETSERVICE
if (_aqconfig_.rs_poll_speed < 0)
if (_aqconfig_.rs_poll_speed < 0) {
return init_blocking_serial_port(_aqconfig_.serial_port);
else if (_aqconfig_.readahead_b4_write)
return init_readahead_serial_port(_aqconfig_.serial_port);
else
return init_serial_port(_aqconfig_.serial_port);
}
#else
if (_aqconfig_.readahead_b4_write)
return init_readahead_serial_port(_aqconfig_.serial_port);
else
return init_blocking_serial_port(_aqconfig_.serial_port);
return init_blocking_serial_port(_aqconfig_.serial_port);
#endif
}
int init_readahead_serial_port(const char* tty)
{
return _init_serial_port(tty, false, true);
}
int init_blocking_serial_port(const char* tty)
{
_blocking_fds = _init_serial_port(tty, true, false);
@ -387,19 +552,28 @@ int lock_port(int fd, const char* tty)
int unlock_port(int fd)
{
if (ioctl(fd, TIOCNXCL) < 0) {
LOG(RSSD_LOG,LOG_ERR, "Failed to remove into exclusive mode (%d): %s\n", errno, strerror( errno ));
}
if (flock(fd, LOCK_UN) < 0) {
LOG(RSSD_LOG,LOG_ERR, "Can't unlock serial port (%d): %s\n",errno, strerror( errno ));
//if (!isAqualinkDStopping()) {
LOG(RSSD_LOG,LOG_ERR, "Can't unlock serial port (%d): %s\n",errno, strerror( errno ));
//}
return -1;
}
return 0;
}
int is_valid_port(int fd) {
return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}
// https://www.cmrr.umn.edu/~strupp/serial.html#2_5_2
// http://unixwiz.net/techtips/termios-vmin-vtime.html
//#define OLD_SERIAL_INIT
#ifndef OLD_SERIAL_INIT
// Unless AQ_RS_EXTRA_OPTS is defined, blocking will always be true
int _init_serial_port(const char* tty, bool blocking, bool readahead)
{
//B1200, B2400, B4800, B9600, B19200, B38400, B57600, B115200, B230400
@ -409,7 +583,9 @@ int _init_serial_port(const char* tty, bool blocking, bool readahead)
_blocking_mode = blocking;
int fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY);
//int fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY);
int fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY | O_CLOEXEC);
//int fd = open(tty, O_RDWR | O_NOCTTY | O_SYNC); // This is way to slow at reading
if (fd < 0) {
LOG(RSSD_LOG,LOG_ERR, "Unable to open port: %s, error %d\n", tty, errno);
@ -484,60 +660,7 @@ int _init_serial_port(const char* tty, bool blocking, bool readahead)
return fd;
}
#else //OLD_SERIAL_INIT
int _init_serial_port(const char* tty, bool blocking, bool readahead) // readahead ignored in this implimentation
{
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
_blocking_mode = blocking;
//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) {
LOG(RSSD_LOG,LOG_ERR, "Unable to open port: %s\n", tty);
return -1;
}
LOG(RSSD_LOG,LOG_DEBUG, "Openeded serial port %s\n",tty);
if (_blocking_mode) {
// http://unixwiz.net/techtips/termios-vmin-vtime.html
// Not designed behaviour, but it's what we need.
fcntl(fd, F_SETFL, 0);
newtio.c_cc[VMIN]= 1;
//newtio.c_cc[VTIME]= 0; // This will wait indefinatly. (not the best if panel / rs485 adapter is down)
newtio.c_cc[VTIME]= 50; // (1 to 255) 1 = 0.1 sec, 255 = 25.5 sec
LOG(RSSD_LOG,LOG_INFO, "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; // This should be 0 if we have readahead before write
LOG(RSSD_LOG,LOG_INFO, "Set serial port %s to non blocking mode\n",tty);
}
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);
LOG(RSSD_LOG,LOG_DEBUG, "Set serial port %s io attributes\n",tty);
return fd;
}
#endif
void close_blocking_serial_port()
@ -552,6 +675,11 @@ void close_blocking_serial_port()
/* close tty port */
void close_serial_port(int fd)
{
if ( fcntl(fd, F_GETFD, 0) == -1 || errno == EBADF ) {
// Looks like bad fd or already closed. return with no error since we can get called twice
return;
}
unlock_port(fd);
tcsetattr(fd, TCSANOW, &_oldtio);
close(fd);
@ -627,13 +755,15 @@ void send_pentair_command(int fd, unsigned char *packet_buffer, int size)
packet[++i] = NUL; // Checksum
packet[++i] = NUL; // Checksum
generate_pentair_checksum(&packet[1], i);
packet[++i] = NUL;
//packet[++i] = NUL;
//logPacket(packet, i);
send_packet(fd,packet,i);
send_packet(fd,packet,++i);
}
#ifndef SEND_CMD_WITH_TRAILING_NUL
void send_jandy_command(int fd, unsigned char *packet_buffer, int size)
{
unsigned char packet[AQ_MAXPKTLEN];
@ -648,6 +778,26 @@ void send_jandy_command(int fd, unsigned char *packet_buffer, int size)
packet[i] = packet_buffer[i-3];
}
packet[++i] = DLE;
packet[++i] = ETX;
packet[i-2] = generate_checksum(packet, i+1);
send_packet(fd,packet,++i);
}
#else
void send_jandy_command(int fd, unsigned char *packet_buffer, int size)
{
unsigned char packet[AQ_MAXPKTLEN];
int i=0;
packet[0] = DLE;
packet[1] = STX;
for (i=2; i-2 < size; i++) {
packet[i] = packet_buffer[i-2];
}
packet[++i] = DLE;
packet[++i] = ETX;
packet[++i] = NUL;
@ -656,7 +806,7 @@ void send_jandy_command(int fd, unsigned char *packet_buffer, int size)
send_packet(fd,packet,++i);
}
#endif
/*
unsigned char tp[] = {PCOL_PENTAIR, 0x07, 0x0F, 0x10, 0x08, 0x0D, 0x55, 0x55, 0x5B, 0x2A, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00};
send_command(0, tp, 19);
@ -712,6 +862,29 @@ void send_packet(int fd, unsigned char *packet, int length)
{
#endif
struct timespec elapsed_time;
struct timespec now;
if (_aqconfig_.frame_delay > 0) {
struct timespec min_frame_wait_time;
struct timespec frame_wait_time;
struct timespec remainder_time;
//struct timespec diff;
min_frame_wait_time.tv_sec = 0;
min_frame_wait_time.tv_nsec = _aqconfig_.frame_delay * 1000000;
do {
clock_gettime(CLOCK_REALTIME, &now);
timespec_subtract(&elapsed_time, &now, &_last_serial_read_time);
if (timespec_subtract(&frame_wait_time, &min_frame_wait_time, &elapsed_time)) {
break;
}
} while (nanosleep(&frame_wait_time, &remainder_time) != 0);
}
clock_gettime(CLOCK_REALTIME, &now);
if (_blocking_mode) {
//int nwrite = write(fd, packet, length);
//LOG(RSSD_LOG,LOG_DEBUG, "Serial write %d bytes of %d\n",nwrite,length);
@ -720,13 +893,6 @@ void send_packet(int fd, unsigned char *packet, int length)
if (nwrite != length)
LOG(RSSD_LOG, LOG_ERR, "write to serial port failed\n");
} else {
if (_aqconfig_.readahead_b4_write) {
if (cleanOutSerial(fd, false) != 0x00) {
LOG(RSSD_LOG, LOG_ERR, "ERROR on RS485, AqualinkD was too slow in replying to message! (please check for performance issues)\n");
cleanOutSerial(fd, true);
}
}
int nwrite, i;
for (i = 0; i < length; i += nwrite) {
nwrite = write(fd, packet + i, length - i);
@ -747,9 +913,10 @@ void send_packet(int fd, unsigned char *packet, int length)
*/
// MAYBE Change this back to debug serial
LOG(RSSD_LOG,LOG_DEBUG_SERIAL, "Serial write %d bytes\n",length-2);
//LOG(RSSD_LOG,LOG_DEBUG_SERIAL, "Serial write %d bytes\n",length-2);
//LOG(RSSD_LOG,LOG_DEBUG, "Serial write %d bytes, type 0x%02hhx cmd 0x%02hhx\n",length-2,packet[5],packet[6]);
logPacketWrite(&packet[1], length-2);
if (_aqconfig_.log_protocol_packets || getLogLevel(RSSD_LOG) >= LOG_DEBUG_SERIAL)
logPacketWrite(&packet[1], length-1);
/*
if (getLogLevel(PDA_LOG) == LOG_DEBUG) {
char buff[1024];
@ -769,33 +936,75 @@ void send_packet(int fd, unsigned char *packet, int length)
}*/
tcdrain(fd); // Make sure buffer has been sent.
//if (_aqconfig_.frame_delay > 0) {
#ifndef SERIAL_LOGGER
if (_aqconfig_.frame_delay > 0) {
timespec_subtract(&elapsed_time, &now, &_last_serial_read_time);
LOG(RSTM_LOG, LOG_DEBUG, "Time from recv to %s send is %.3f sec\n",
(_blocking_mode?"blocking":"non-blocking"),
roundf3(timespec2float(&elapsed_time)));
}
#endif
//}
}
#ifndef SEND_CMD_WITH_TRAILING_NUL
void _send_ack(int fd, unsigned char ack_type, unsigned char command)
{
const int length = 11;
//const int length = 11;
int length = 10;
// 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, CMD_ACK, NUL, NUL, 0x13, DLE, ETX };
// To overcome Pentair VSP bug in Jandy control panel, we need to NOT send trailing NUL on
// a normal ACK, but sent the trailing NUL on in ack with command.
// Always send trailing NUL causes VSP to loose connection
// Never sending trailing NUL causes come commands to be missed.
// 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);
ackPacket[7] = generate_checksum(ackPacket, length);
if (command == DLE) { // We shuld probably also check the ack type as well, just for future proofing.
// 0x10(DLE) that's not part of the headder or footer needs to be escaped AFTER with NUL, so shif everyting uo one
ackPacket[8] = ackPacket[7]; // move the caculated checksum
ackPacket[7] = NUL; // escape the DLE
ackPacket[9] = DLE; // add new end sequence
ackPacket[10] = ETX; // add new end sequence
ackPacket[10] = ETX; // add new end sequence
length = 11;
}
}
//printf("***Send ACK (%s) ***\n",(ack_type==ACK_NORMAL?"Normal":(ack_type==ACK_SCREEN_BUSY?"ScreenBusy":"ScreenBusyDisplay")) );
send_packet(fd, ackPacket, length);
}
#else
void _send_ack(int fd, unsigned char ack_type, unsigned char command)
{
//const int length = 11;
int length = 9;
// Default null ack with checksum generated, don't mess with it, just over right
unsigned char ackPacket[] = { DLE, STX, DEV_MASTER, CMD_ACK, NUL, NUL, 0x13, DLE, ETX, 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[4] = ack_type;
ackPacket[5] = command;
ackPacket[6] = generate_checksum(ackPacket, length);
if (command == DLE) { // We shuld probably also check the ack type as well, just for future proofing.
// 0x10(DLE) that's not part of the headder or footer needs to be escaped AFTER with NUL, so shif everyting uo one
ackPacket[7] = ackPacket[7]; // move the caculated checksum
ackPacket[6] = NUL; // escape the DLE
ackPacket[8] = DLE; // add new end sequence
ackPacket[9] = ETX; // add new end sequence
length = 10;
}
}
send_packet(fd, ackPacket, length);
}
#endif // SEND_CMD_WITH_TRAILING_NUL
void send_ack(int fd, unsigned char command)
{
@ -820,6 +1029,8 @@ int get_packet_lograw(int fd, unsigned char* packet)
}
int _get_packet(int fd, unsigned char* packet, bool rawlog)
*/
int get_packet(int fd, unsigned char* packet)
{
unsigned char byte = 0x00;
@ -834,6 +1045,8 @@ int get_packet(int fd, unsigned char* packet)
//bool lastByteDLE = false;
int PentairPreCnt = 0;
int PentairDataCnt = -1;
struct timespec packet_elapsed;
struct timespec packet_end_time;
memset(packet, 0, AQ_MAXPKTLEN);
@ -949,19 +1162,23 @@ int get_packet(int fd, unsigned char* packet)
} else if(bytesRead < 0) {
// Got a read error. Wait one millisecond for the next byte to
// arrive.
LOG(RSSD_LOG,LOG_WARNING, "Read error: %d - %s\n", errno, strerror(errno));
if(errno == 9) {
if (! isAqualinkDStopping() ) {
LOG(RSSD_LOG,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 AQSERR_READ;
return AQSERR_READ;
}
delay(100);
} else {
return 0;
}
delay(100);
}
// Break out of the loop if we exceed maximum packet
// length.
if (index >= AQ_MAXPKTLEN) {
LOG(RSSD_LOG,LOG_WARNING, "Serial packet too large\n");
LOG(RSSD_LOG,LOG_WARNING, "Serial packet too large for buffer, stopped reading\n");
logPacketError(packet, index);
//log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
return AQSERR_2LARGE;
@ -969,27 +1186,16 @@ int get_packet(int fd, unsigned char* packet)
}
}
// Clean out rest of buffer, make sure their is nothing else
/* Doesn't work for shit due to probe message speed, need to come back and re-think
if (_aqconfig_.readahead_b4_write) {
if (endOfPacket == true) {
do {
bytesRead = read(fd, &byte, 1);
//if (bytesRead==1) { LOG(RSSD_LOG,LOG_ERR, "Cleanout buffer read 0x%02hhx\n",byte); }
if (bytesRead==1 && byte != 0x00) {
LOG(RSSD_LOG,LOG_ERR, "SERIOUS ERROR on RS485, AqualinkD caught packet collision on bus, ignoring!\n");
LOG(RSSD_LOG,LOG_ERR, "Error Cleanout read 0x%02hhx\n",byte);
// Run the buffer out
do {
bytesRead = read(fd, &byte, 1);
if (bytesRead==1) { LOG(RSSD_LOG,LOG_ERR, "Error Cleanout read 0x%02hhx\n",byte); }
} while (bytesRead==1);
return 0;
}
} while (bytesRead==1);
// Report any unusual size packets.
if (index >= AQ_MAXPKTLEN_WARNING) {
// Aqualink2 packets 0x72 and 0x71 can be very large, so supress if it one of those.
if (packet[PKT_CMD] != CMD_IAQ_AUX_STATUS && packet[PKT_CMD] != CMD_IAQ_1TOUCH_STATUS) {
LOG(RSSD_LOG,LOG_WARNING, "Serial packet seems too large at length %d\n", index);
logPacketError(packet, index);
}
}
*/
//LOG(RSSD_LOG,LOG_DEBUG, "Serial checksum, length %d got 0x%02hhx expected 0x%02hhx\n", index, packet[index-3], generate_checksum(packet, index));
if (jandyPacketStarted) {
if (check_jandy_checksum(packet, index) != true){
@ -1006,25 +1212,50 @@ int get_packet(int fd, unsigned char* packet)
return AQSERR_CHKSUM;
}
}
/*
if (generate_checksum(packet, index) != packet[index-3]){
LOG(RSSD_LOG,LOG_WARNING, "Serial read bad checksum, ignoring\n");
log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
return 0;
} else*/ if (index < AQ_MINPKTLEN && (jandyPacketStarted || pentairPacketStarted) ) { //NSF. Sometimes we get END sequence only, so just ignore.
} else*/
if (index < AQ_MINPKTLEN && (jandyPacketStarted || pentairPacketStarted) ) { //NSF. Sometimes we get END sequence only, so just ignore.
LOG(RSSD_LOG,LOG_WARNING, "Serial read too small\n");
logPacketError(packet, index);
//log_packet(LOG_WARNING, "Bad receive packet ", packet, index);
return AQSERR_2SMALL;
}
LOG(RSSD_LOG,LOG_DEBUG_SERIAL, "Serial read %d bytes\n",index);
if (_aqconfig_.frame_delay > 0 || getLogLevel(RSTM_LOG) >= LOG_DEBUG) {
clock_gettime(CLOCK_REALTIME, &packet_end_time);
if (getLogLevel(RSTM_LOG) >= LOG_DEBUG) {
timespec_subtract(&packet_elapsed, &packet_end_time, &_last_serial_read_time);
/*
LOG(RSTM_LOG, LOG_NOTICE, "Start sec=%ld nsec=%ld\n",_last_serial_read_time.tv_sec,_last_serial_read_time.tv_nsec);
LOG(RSTM_LOG, LOG_NOTICE, "End sec=%ld nsec=%ld\n",packet_end_time.tv_sec,packet_end_time.tv_nsec);
LOG(RSTM_LOG, LOG_NOTICE, "Elapsed sec=%ld nsec=%ld Time between packets (%.3f sec)\n",packet_elapsed.tv_sec,packet_elapsed.tv_nsec, roundf3(timespec2float(&packet_elapsed)) );
*/
LOG(RSTM_LOG, LOG_DEBUG, "Time between packets (%.3f sec)\n", roundf3(timespec2float(&packet_elapsed)) );
}
//if (_aqconfig_.frame_delay > 0) {
memcpy(&_last_serial_read_time, &packet_end_time, sizeof(struct timespec));
//}
}
//clock_gettime(CLOCK_REALTIME, &_last_serial_read_time);
//}
//LOG(RSSD_LOG,LOG_DEBUG_SERIAL, "Serial read %d bytes\n",index);
if (_aqconfig_.log_protocol_packets || getLogLevel(RSSD_LOG) >= LOG_DEBUG_SERIAL)
logPacketRead(packet, index);
// Return the packet length.
return index;
}
#else // PLAYBACKMODE
// Need to re-write this if we ever use playback mode again. Pull info from aq_serial.old.c

View File

@ -5,8 +5,14 @@
#include <termios.h>
#include <stdbool.h>
#define CONNECTION_ERROR "ERROR No connection to RS control panel"
#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
@ -34,11 +40,57 @@
# Jandy pump ID's
# 0x78, 0x79, 0x7A, 0x7B
*/
#define PENTAIR_DEC_PUMP_MIN 96
#define PENTAIR_DEC_PUMP_MAX 111
#define JANDY_DEC_PUMP_MIN 120
#define JANDY_DEC_PUMP_MAX 123
#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 // 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
/*
//===== 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
@ -52,26 +104,48 @@
#define PP3 0xFF
#define PP4 0xA5
#define PEN_CMD_STATUS 0x07
#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
#define PEN_HI_B_RPM 14
#define PEN_LO_B_RPM 15
#define PEN_HI_B_WAT 12
#define PEN_LO_B_WAT 13
// 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 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
@ -119,6 +193,10 @@
#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
@ -139,7 +217,8 @@
#define KEY_AUX7 0x15
#define KEY_POOL_HTR 0x12
#define KEY_SPA_HTR 0x17
#define KEY_SOLAR_HTR 0x1c
//#define KEY_SOLAR_HTR 0x1c
#define KEY_EXT_AUX 0x1c
#define KEY_MENU 0x09
#define KEY_CANCEL 0x0e
#define KEY_LEFT 0x13
@ -148,7 +227,7 @@
#define KEY_OVERRIDE 0x1e
#define KEY_ENTER 0x1d
#ifdef AQ_RS16
//RS 12 & 16 are different from Aux4 to Aux7
#define KEY_RS16_AUX4 0x14
#define KEY_RS16_AUX5 0x03
@ -164,10 +243,10 @@
#define KEY_AUXB7 0x0d
#define KEY_AUXB8 0x0c
// End diff in RS12
#endif
#define BTN_PUMP "Filter_Pump"
#define BTN_SPA "Spa_Mode"
#define BTN_SPA "Spa"
#define BTN_AUX1 "Aux_1"
#define BTN_AUX2 "Aux_2"
#define BTN_AUX3 "Aux_3"
@ -177,12 +256,15 @@
#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_SOLAR_HTR "Solar_Heater"
#define BTN_EXT_AUX "Extra_Aux"
#define BTN_TEMP1_HTR "Temp1_Heater"
#define BTN_TEMP2_HTR "Temp2_Heater"
#ifdef AQ_RS16
#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"
@ -191,7 +273,7 @@
#define BTN_AUXB6 "Aux_B6"
#define BTN_AUXB7 "Aux_B7"
#define BTN_AUXB8 "Aux_B8"
#endif
#define BTN_PDA_PUMP "FILTER PUMP"
#define BTN_PDA_SPA "SPA"
@ -204,15 +286,15 @@
#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_SOLAR_HTR "EXTRA AUX"
#define BTN_PDA_EXT_AUX "EXTRA AUX"
#define BUTTON_LABEL_LENGTH 20
#ifndef AQ_RS16
#define TOTAL_LEDS 20
#else
//#define TOTAL_LEDS 20
#define TOTAL_LEDS 24 // Only 20 exist in control panel, but need space for the extra buttons on RS16 panel
#endif
// Index starting at 1
#define POOL_HTR_LED_INDEX 15
@ -229,6 +311,10 @@
//#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"
@ -284,8 +370,9 @@ SPILLOVER IS DISABLED WHILE SPA IS ON
//#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_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
@ -309,6 +396,10 @@ SPILLOVER IS DISABLED WHILE SPA IS ON
#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
@ -323,9 +414,16 @@ SPILLOVER IS DISABLED WHILE SPA IS ON
#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
@ -341,6 +439,10 @@ SPILLOVER IS DISABLED WHILE SPA IS ON
#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
@ -376,6 +478,7 @@ SPILLOVER IS DISABLED WHILE SPA IS ON
#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
@ -387,10 +490,18 @@ SPILLOVER IS DISABLED WHILE SPA IS ON
#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.
@ -411,7 +522,11 @@ SPILLOVER IS DISABLED WHILE SPA IS ON
typedef enum {
DRS_NONE,
DRS_SWG,
DRS_EPUMP
DRS_EPUMP,
DRS_JXI,
DRS_LX,
DRS_CHEM,
DRS_HEATPUMP
} rsDeviceType;
typedef enum {
@ -443,7 +558,7 @@ typedef enum {
int init_serial_port(const char* tty);
int init_blocking_serial_port(const char* tty);
int init_readahead_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();
@ -462,6 +577,7 @@ 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);
@ -493,4 +609,4 @@ bool onetouch_mode();
//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_
#endif // AQ_SERIAL_H_

247
source/aq_systemutils.c Normal file
View File

@ -0,0 +1,247 @@
/*
* 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/sfeakes/AqualinkD/contents/release/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_

View File

@ -16,6 +16,7 @@ struct timerthread {
pthread_mutex_t thread_mutex;
pthread_cond_t thread_cond;
aqkey *button;
int deviceIndex;
struct aqualinkdata *aq_data;
int duration_min;
struct timespec timeout;
@ -56,9 +57,10 @@ int get_timer_left(aqkey *button)
return 0;
}
void clear_timer(struct aqualinkdata *aq_data, aqkey *button)
void clear_timer(struct aqualinkdata *aq_data, /*aqkey *button,*/ int deviceIndex)
{
struct timerthread *t_ptr = find_timerthread(button);
//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);
@ -67,8 +69,9 @@ void clear_timer(struct aqualinkdata *aq_data, aqkey *button)
}
}
void start_timer(struct aqualinkdata *aq_data, aqkey *button, int duration)
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) {
@ -77,26 +80,15 @@ void start_timer(struct aqualinkdata *aq_data, aqkey *button, int duration)
pthread_cond_broadcast(&t_ptr->thread_cond);
return;
}
/*
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) {
LOG(TIMR_LOG, LOG_INFO, "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);
@ -122,19 +114,22 @@ void start_timer(struct aqualinkdata *aq_data, aqkey *button, int duration)
}
}
#define WAIT_TIME_BEFORE_ON_CHECK 1000 // 1 second
#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);
@ -145,9 +140,21 @@ void *timer_worker( void *ptr )
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 {
// crap way to do this, need to use net_service logic in teh future, but should never actually get here
LOG(TIMR_LOG, LOG_NOTICE, "turning on '%s'\n",tmthread->button->name);
aq_send_cmd(tmthread->button->code);
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;
}
}
@ -172,17 +179,24 @@ void *timer_worker( void *ptr )
LOG(TIMR_LOG, LOG_NOTICE, "End timer for '%s'\n",tmthread->button->name);
if (tmthread->button->led->state != OFF) {
LOG(TIMR_LOG, LOG_INFO, "Timer waking turning '%s' off\n",tmthread->button->name);
#ifdef AQ_PDA
if (isPDA_PANEL)
create_PDA_on_off_request(tmthread->button, false);
else
#endif
aq_send_cmd(tmthread->button->code);
} else {
// 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;

View File

@ -4,9 +4,9 @@
#include "aqualink.h"
void start_timer(struct aqualinkdata *aq_data, aqkey *button, int duration);
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);
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);

View File

@ -7,10 +7,15 @@
#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 SIGRESTART SIGUSR1
#define SIGRUPGRADE SIGUSR2
#ifdef AQ_NO_THREAD_NETSERVICE
#define DEFAULT_POLL_SPEED -1
@ -18,6 +23,8 @@
#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
@ -33,16 +40,19 @@
// 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_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 our
// 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
@ -57,11 +67,22 @@ bool checkAqualinkTime(); // Only need to externalise this for PDA
*/
#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
enum {
FAHRENHEIT,
@ -80,16 +101,21 @@ typedef struct aqualinkkey
// char *pda_label;
//#endif
unsigned char code;
unsigned char rssd_code;
int dz_idx;
uint8_t special_mask;
void *special_mask_ptr;
} aqkey;
// 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 YET
//#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 {
@ -100,13 +126,24 @@ struct programmingthread {
//void *thread_args;
};
/*
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_SETOINT,
SPA_HTR_SETOINT,
POOL_HTR_SETPOINT,
SPA_HTR_SETPOINT,
FREEZE_SETPOINT,
CHILLER_SETPOINT,
SWG_SETPOINT,
SWG_BOOST,
PUMP_RPM,
@ -116,6 +153,7 @@ typedef enum action_type {
ON_OFF,
TIMER,
LIGHT_MODE,
LIGHT_BRIGHTNESS,
DATE_TIME
} action_type;
@ -137,25 +175,78 @@ typedef enum pump_type {
} pump_type;
*/
#define PUMP_PRIMING -1
#define PUMP_OFFLINE -2
#define PUMP_ERROR -3
/*
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)
@ -166,20 +257,47 @@ typedef enum clight_type {
LC_SAL,
LC_CLOGIG,
LC_INTELLIB,
LC_DIMMER,
LC_HAYWCL,
LC_SPARE_1,
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} request_source;
NET_DZMQTT,
NET_TIMER, // Timer or Scheduler (eg poweron/freezeprotect check)
UNACTION_TIMER
} request_source;
typedef struct clightd
{
clight_type lightType;
aqkey *button;
int currentValue;
int lastValue; // Used for AqualinkD self programming
aqledstate RSSDstate; // state from rs serial adapter
} clight_detail;
@ -188,14 +306,26 @@ typedef struct clightd
struct aqualinkdata
{
char version[AQ_MSGLEN*2];
//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;
@ -207,16 +337,23 @@ struct aqualinkdata
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;
@ -225,7 +362,16 @@ struct aqualinkdata
//unsigned short total_ordered_buttons;
unsigned char last_packet_type;
int swg_delayed_percent;
bool simulate_panel;
//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;
@ -233,11 +379,22 @@ struct aqualinkdata
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
#ifdef AQ_RS16
int rs16_vbutton_start;
int rs16_vbutton_end;
#endif
#ifdef AQ_PDA
int pool_heater_index;
int spa_heater_index;
@ -248,7 +405,12 @@ struct aqualinkdata
struct timespec last_active_time;
struct timespec start_active_time;
#endif
// Overcome color light bug, by reconnecting allbutton panel.
//bool reconnectAllButton;
};
#endif

1505
source/aqualinkd.c Normal file

File diff suppressed because it is too large Load Diff

346
source/color_lights.c Normal file
View File

@ -0,0 +1,346 @@
#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",
"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
},
{/*Spare 1*/},
{/*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] = light_program;\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;
}

View File

@ -3,11 +3,18 @@
#define COLOR_LIGHTS_H_
#include "aqualink.h"
#include "aq_programmer.h"
#define LIGHT_COLOR_NAME 16
#define LIGHT_COLOR_OPTIONS 17
#define LIGHT_COLOR_OPTIONS 19
//#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 {
@ -19,15 +26,31 @@ typedef enum clight_type {
LC_INTELLIB
} clight_type;
*/
const char *light_mode_name(clight_type type, int index);
//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
---------------------------------------------------------------------------------------------------------

2203
source/config.c Normal file

File diff suppressed because it is too large Load Diff

373
source/config.h Normal file
View File

@ -0,0 +1,373 @@
#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 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;
#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 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_ (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_C_serial_port 11
#define CFG_N_log_level "log_level"
#define CFG_V_log_level "[\"DEBUG\", \"INFO\", \"NOTICE\", \"WARNING\", \"ERROR\"]"
#define CFG_C_log_level 9
#define CFG_N_socket_port "socket_port" // Change to Web_socket
#define CFG_C_socket_port 11
#define CFG_N_web_directory "web_directory"
#define CFG_C_web_directory 13
#define CFG_N_device_id "device_id"
#define CFG_V_device_id "[\"0x0a\", \"0x0b\", \"0x09\", \"0x08\", \"0x60\", \"0xFF\"]"
#define CFG_C_device_id 9
#define CFG_N_rssa_device_id "rssa_device_id"
#define CFG_V_rssa_device_id "[\"0x00\", \"0x48\"]"
#define CFG_C_rssa_device_id 14
#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_C_panel_type 10
#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_C_extended_device_id 18
#define CFG_N_sync_panel_time "sync_panel_time"
#define CFG_C_sync_panel_time 15
//#define CFG_N_extended_device_id2 "extended_device_id2"
//#define CFG_C_extended_device_id2 20
#define CFG_N_extended_device_id_programming "extended_device_id_programming"
#define CFG_C_extended_device_id_programming 30
#define CFG_N_enable_iaqualink "enable_iaqualink"
#define CFG_C_enable_iaqualink 16
#define CFG_N_log_file "log_file"
#define CFG_C_log_file 8
#define CFG_N_mqtt_aq_topic "mqtt_aq_topic"
#define CFG_C_mqtt_aq_topic 13
#define CFG_N_mqtt_server "mqtt_address"
#define CFG_C_mqtt_server 12
#define CFG_N_mqtt_user "mqtt_user"
#define CFG_C_mqtt_user 9
#define CFG_N_mqtt_passwd "mqtt_passwd"
#define CFG_C_mqtt_passwd 11
#define CFG_N_mqtt_hass_discover_topic "mqtt_ha_discover_topic"
#define CFG_C_mqtt_hass_discover_topic 24
#define CFG_N_mqtt_hass_discover_use_mac "mqtt_ha_discover_use_mac"
#define CFG_C_mqtt_hass_discover_use_mac 27
#define CFG_N_mqtt_timed_update "mqtt_timed_update"
#define CFG_C_mqtt_timed_update 17
//#define CFG_N_mqtt_ID "mqtt_ID"
//#define CFG_C_mqtt_ID 7
#define CFG_N_mqtt_dz_sub_topic "mqtt_dz_sub_topic"
#define CFG_C_mqtt_dz_sub_topic 17
#define CFG_N_mqtt_dz_pub_topic "mqtt_dz_pub_topic"
#define CFG_C_mqtt_dz_pub_topic 17
#define CFG_N_dzidx_air_temp "dzidx_air_temp"
#define CFG_C_dzidx_air_temp 14
#define CFG_N_dzidx_pool_water_temp "dzidx_pool_water_temp"
#define CFG_C_dzidx_pool_water_temp 21
#define CFG_N_dzidx_spa_water_temp "dzidx_spa_water_temp"
#define CFG_C_dzidx_spa_water_temp 20
#define CFG_N_dzidx_swg_percent "dzidx_SWG_percent"
#define CFG_C_dzidx_swg_percent 17
#define CFG_N_dzidx_swg_ppm "dzidx_SWG_PPM"
#define CFG_C_dzidx_swg_ppm 13
#define CFG_N_dzidx_swg_status "dzidx_SWG_Status"
#define CFG_C_dzidx_swg_status 16
#define CFG_N_light_programming_mode "light_programming_mode"
#define CFG_C_light_programming_mode 22
#define CFG_N_light_programming_initial_on "light_programming_initial_on"
#define CFG_C_light_programming_initial_on 28
#define CFG_N_light_programming_initial_off "light_programming_initial_off"
#define CFG_C_light_programming_initial_off 29
#define CFG_N_override_freeze_protect "override_freeze_protect"
#define CFG_C_override_freeze_protect 23
#define CFG_N_pda_sleep_mode "pda_sleep_mode"
#define CFG_C_pda_sleep_mode 14
#define CFG_N_convert_mqtt_temp "mqtt_convert_temp_to_c"
#define CFG_C_convert_mqtt_temp 22
#define CFG_N_convert_dz_temp "dz_convert_temp_to_c"
#define CFG_C_convert_dz_temp 20
#define CFG_N_report_zero_spa_temp "report_zero_spa_temp"
#define CFG_C_report_zero_spa_temp 20
#define CFG_N_report_zero_pool_temp "report_zero_pool_temp"
#define CFG_C_report_zero_pool_temp 21
#define CFG_N_read_RS485_devmask "read_RS485_devmask"
#define CFG_C_read_RS485_devmask 18
#define CFG_N_use_panel_aux_labels "use_panel_aux_labels"
#define CFG_C_use_panel_aux_labels 20
#define CFG_N_force_swg "force_swg"
#define CFG_C_force_swg 9
#define CFG_N_force_ps_setpoints "force_ps_setpoints"
#define CFG_C_force_ps_setpoints 18
#define CFG_N_force_frzprotect_setpoints "force_frzprotect_setpoints"
#define CFG_C_force_frzprotect_setpoints 26
#define CFG_N_force_chem_feeder "force_chem_feeder"
#define CFG_C_force_chem_feeder 17
#define CFG_N_force_chiller "force_chiller"
#define CFG_N_display_warnings_web "display_warnings_web"
#define CFG_C_display_warnings_web 20
#define CFG_N_log_protocol_packets "log_protocol_packets"
#define CFG_C_log_protocol_packets 20
#define CFG_N_device_pre_state "device_pre_state"
#define CFG_C_device_pre_state 16
#define CFG_N_read_RS485_swg "read_RS485_swg"
#define CFG_C_read_RS485_swg 14
#define CFG_N_read_RS485_ePump "read_RS485_ePump"
#define CFG_C_read_RS485_ePump 16
#define CFG_N_read_RS485_vsfPump "read_RS485_vsfPump"
#define CFG_C_read_RS485_vsfPump 18
#define CFG_N_read_RS485_JXi "read_RS485_JXi"
#define CFG_C_read_RS485_JXi 14
#define CFG_N_read_RS485_LX "read_RS485_LX"
#define CFG_C_read_RS485_LX 13
#define CFG_N_read_RS485_Chem "read_RS485_Chem"
#define CFG_C_read_RS485_Chem 15
#define CFG_N_read_RS485_iAqualink "read_RS485_iAqualink"
#define CFG_C_read_RS485_iAqualink 20
#define CFG_N_read_RS485_HeatPump "read_RS485_HeatPump"
#define CFG_N_enable_scheduler "enable_scheduler"
#define CFG_C_enable_scheduler 16
#define CFG_N_event_check_poweron "event_poweron_check_pump"
#define CFG_C_event_check_poweron 24
#define CFG_N_event_check_freezeprotectoff "event_freezeprotectoff_check_pump"
#define CFG_C_event_check_freezeprotectoff 33
#define CFG_N_event_check_boostoff "event_boostoff_check_pump"
#define CFG_C_event_check_boostoff 25
#define CFG_N_event_check_pumpon_hour "event_check_pumpon_hour"
#define CFG_C_event_check_pumpon_hour 23
#define CFG_N_event_check_pumpoff_hour "event_check_pumpoff_hour"
#define CFG_C_event_check_pumpoff_hour 24
#define CFG_N_event_check_usecron "event_check_use_scheduler_times"
#define CFG_C_event_check_usecron 32
#define CFG_N_event_check_booston_device "event_booston_check_device"
#define CFG_N_ftdi_low_latency "ftdi_low_latency"
#define CFG_C_ftdi_low_latency 16
#define CFG_N_rs485_frame_delay "rs485_frame_delay"
#define CFG_C_rs485_frame_delay 17
#endif

View File

@ -4,7 +4,7 @@
#include <string.h>
#include "debug_timer.h"
#include "utils.h"
//#include "timespec_subtract.h"
#include "timespec_subtract.h"
#define NUM_DEBUG_TIMERS 10
@ -68,52 +68,8 @@ void stop_aqd_timer(int timeid, int16_t from, char *message)
clear_aqd_timer(timeid);
}
/* Copyright (c) 1991, 1999 Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
* 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.
*/
/* Based on https://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html
Subtract the struct timespec values X and Y,
storing the result in RESULT.
Return 1 if the difference is negative, otherwise 0. */
//#include <string.h>
//#include "timespec_subtract.h"
int timespec_subtract (struct timespec *result, const struct timespec *x,
const struct timespec *y)
{
struct timespec tmp;
memcpy (&tmp, y, sizeof(struct timespec));
/* Perform the carry for the later subtraction by updating y. */
if (x->tv_nsec < tmp.tv_nsec)
{
int nsec = (tmp.tv_nsec - x->tv_nsec) / 1000000000 + 1;
tmp.tv_nsec -= 1000000000 * nsec;
tmp.tv_sec += nsec;
}
if (x->tv_nsec - tmp.tv_nsec > 1000000000)
{
int nsec = (x->tv_nsec - tmp.tv_nsec) / 1000000000;
tmp.tv_nsec += 1000000000 * nsec;
tmp.tv_sec -= nsec;
}
/* Compute the time remaining to wait.
tv_nsec is certainly positive. */
result->tv_sec = x->tv_sec - tmp.tv_sec;
result->tv_nsec = x->tv_nsec - tmp.tv_nsec;
/* Return 1 if result is negative. */
return x->tv_sec < tmp.tv_sec;
}
#endif

View File

@ -2,11 +2,13 @@
#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);
//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);

1198
source/devices_jandy.c Normal file

File diff suppressed because it is too large Load Diff

45
source/devices_jandy.h Normal file
View File

@ -0,0 +1,45 @@
#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);
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_

240
source/devices_pentair.c Normal file
View File

@ -0,0 +1,240 @@
/*
* 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];
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

208
source/dummy_device.c Normal file
View File

@ -0,0 +1,208 @@
/*
*
* 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"
unsigned char DEVICE_ID = 0x70;
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_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)
{
process_heatpump_packet(packet_buffer, packet_length);
}
}
}
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);
}
}
}

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|
*/

738
source/hassio.c Normal file
View File

@ -0,0 +1,738 @@
#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 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);
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(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

View File

@ -5,7 +5,7 @@
#define IAQT_MSGLEN 21
struct iaqt_page_button {
char *name[IAQT_MSGLEN];
char name[IAQT_MSGLEN];
unsigned char type; // 0x01 box, 0x08 icon wirlpool, 0x0b icon heater, 0x01 icon (main page), 0x07 light
unsigned char state;
unsigned char keycode;
@ -15,8 +15,9 @@ struct iaqt_page_button {
bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkdata *aq_data);
unsigned char iaqtThreadKickType();
unsigned char iaqtCurrentPage();
unsigned char iaqtCurrentPageLoading();
bool wasiaqtThreadKickTypePage();
struct iaqt_page_button *iaqtFindButtonByLabel(char *label);
struct iaqt_page_button *iaqtFindButtonByLabel(const char *label);
struct iaqt_page_button *iaqtFindButtonByIndex(int index);
const char *iaqtGetMessageLine(int index);
const char *iaqtGetTableInfoLine(int index);
@ -1697,4 +1698,4 @@ Jandy To 0x60 of type Probe | HEX: 0x10|0x02|0x60|0x00|0x72|0x10|0x03|
Jandy To 0x31 of type Unknown | HEX: 0x10|0x02|0x31|0x30|0x73|0x10|0x03|
Jandy From 0x31 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x00|0x13|0x10|0x03|
*/
*/

View File

@ -32,6 +32,7 @@
#include "config.h"
#include "devices_jandy.h"
#include "packetLogger.h"
#include "color_lights.h"
// System Page is obfiously fixed and not dynamic loaded, so set buttons to stop confustion.
@ -45,8 +46,6 @@
#define KEY_IAQTCH_LOCKOUT_PASSWD KEY_IAQTCH_KEY08
#define KEY_IAQTCH_SET_ACQUAPURE KEY_IAQTCH_KEY09
bool _cansend = false;
unsigned char _iaqt_pgm_command = NUL;
@ -98,6 +97,12 @@ void waitfor_iaqt_queue2empty()
delay(PROGRAMMING_POLL_DELAY_TIME);
}
// Initial startup can take some time, _cansend should be false during this time.
// If we start programming before we receive the first status page, nothing works, this forces that wait
while(_cansend == false) {
delay(PROGRAMMING_POLL_DELAY_TIME * 2);
}
if (_iaqt_pgm_command != NUL) {
// Wait for longer interval
while ( (_iaqt_pgm_command != NUL) && ( i++ < PROGRAMMING_POLL_COUNTER * 2 ) ) {
@ -125,7 +130,7 @@ void send_aqt_cmd(unsigned char cmd)
*
*/
unsigned char _iaqt_control_cmd[AQ_MAXPKTLEN];
unsigned char _iaqt_control_cmd[AQ_MAXPKTLEN_SEND];
int _iaqt_control_cmd_len;
@ -135,9 +140,9 @@ int ref_iaqt_control_cmd(unsigned char **cmd)
*cmd = _iaqt_control_cmd;
if ( getLogLevel(IAQT_LOG) >= LOG_DEBUG ) {
char buff[1000];
char buff[1024];
//sprintf("Sending control command:")
beautifyPacket(buff, _iaqt_control_cmd, _iaqt_control_cmd_len, false);
beautifyPacket(buff, 1024, _iaqt_control_cmd, _iaqt_control_cmd_len, false);
LOG(IAQT_LOG,LOG_DEBUG, "Sending commandsed : %s\n", buff);
}
@ -146,7 +151,7 @@ int ref_iaqt_control_cmd(unsigned char **cmd)
void rem_iaqt_control_cmd(unsigned char *cmd)
{
memset(_iaqt_control_cmd, 0, AQ_MAXPKTLEN * sizeof(unsigned char));
memset(_iaqt_control_cmd, 0, AQ_MAXPKTLEN_SEND * sizeof(unsigned char));
_iaqt_control_cmd_len = 0;
}
@ -167,9 +172,64 @@ bool waitfor_iaqt_ctrl_queue2empty()
}
return true;
}
/*
unsigned const char _waitfor_iaqt_nextPage(struct aqualinkdata *aq_data, int numMessageReceived) {
waitfor_iaqt_queue2empty();
int i=0;
pthread_mutex_lock(&aq_data->active_thread.thread_mutex);
while( ++i <= numMessageReceived)
{
//LOG(IAQT_LOG,LOG_DEBUG, "waitfor_iaqt_nextPage (%d of %d)\n",i,numMessageReceived);
pthread_cond_wait(&aq_data->active_thread.thread_cond, &aq_data->active_thread.thread_mutex);
if(wasiaqtThreadKickTypePage()) break;
}
LOG(IAQT_LOG,LOG_DEBUG, "waitfor_iaqt_nextPage finished in (%d of %d)\n",i,numMessageReceived);
pthread_mutex_unlock(&aq_data->active_thread.thread_mutex);
if(wasiaqtThreadKickTypePage())
return iaqtCurrentPage();
else
return NUL;
}
unsigned const char shortwaitfor_iaqt_nextPage(struct aqualinkdata *aq_data) {
return _waitfor_iaqt_nextPage(aq_data, 3);
}
*/
unsigned const char waitfor_iaqt_messages(struct aqualinkdata *aq_data, int numMessageReceived) {
//return _waitfor_iaqt_nextPage(aq_data, 30);
waitfor_iaqt_queue2empty();
int i=0;
LOG(IAQT_LOG,LOG_DEBUG, "waitfor_iaqt_messages (%d of %d)\n",i,numMessageReceived);
pthread_mutex_lock(&aq_data->active_thread.thread_mutex);
while( ++i <= numMessageReceived)
{
//LOG(IAQT_LOG,LOG_DEBUG, "waitfor_iaqt_nextPage (%d of %d)\n",i,numMessageReceived);
pthread_cond_wait(&aq_data->active_thread.thread_cond, &aq_data->active_thread.thread_mutex);
}
LOG(IAQT_LOG,LOG_DEBUG, "waitfor_iaqt_messages finished in (%d of %d)\n",i,numMessageReceived);
pthread_mutex_unlock(&aq_data->active_thread.thread_mutex);
return iaqtLastMsg();
}
unsigned const char waitfor_iaqt_nextPage(struct aqualinkdata *aq_data) {
//return _waitfor_iaqt_nextPage(aq_data, 30);
waitfor_iaqt_queue2empty();
@ -193,9 +253,10 @@ unsigned const char waitfor_iaqt_nextPage(struct aqualinkdata *aq_data) {
return iaqtCurrentPage();
else
return NUL;
}
unsigned const char waitfor_iaqt_nextMessage(struct aqualinkdata *aq_data, const unsigned char msg_type) {
waitfor_iaqt_queue2empty();
@ -222,7 +283,7 @@ typedef enum {icct_setrpm, icct_settime, icct_setdate} iaqtControlCmdYype;
// Type is always 0 at the moment, haven't found any
void queue_iaqt_control_command(iaqtControlCmdYype type, int num) {
//unsigned char packets[AQ_MAXPKTLEN];
//unsigned char packets[AQ_MAXPKTLEN_SEND];
//int cnt;
if (waitfor_iaqt_ctrl_queue2empty() == false)
@ -309,15 +370,28 @@ bool goto_iaqt_page(const unsigned char pageID, struct aqualinkdata *aq_data) {
if (pageID == IAQ_PAGE_DEVICES) {
send_aqt_cmd(KEY_IAQTCH_HOMEP_KEY08);
if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_DEVICES) {
unsigned char page = waitfor_iaqt_nextPage(aq_data);
if (page != IAQ_PAGE_DEVICES && page != IAQ_PAGE_DEVICES_REV_Yg) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find Device page\n");
return false;
}
}
LOG(IAQT_LOG, LOG_DEBUG, "IAQ Touch got to Device page\n");
return true;
} else if (pageID == IAQ_PAGE_ONETOUCH) {
send_aqt_cmd(KEY_IAQTCH_ONETOUCH);
if (iaqtCurrentPage() != IAQ_PAGE_ONETOUCH) {
unsigned char page = waitfor_iaqt_nextPage(aq_data);
if (page != IAQ_PAGE_ONETOUCH ) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find OneTouch page\n");
return false;
}
}
LOG(IAQT_LOG, LOG_DEBUG, "IAQ Touch got to OneTouch page\n");
return true;
} else if (pageID == IAQ_PAGE_MENU || pageID == IAQ_PAGE_SET_TEMP || pageID == IAQ_PAGE_SET_TIME || pageID == IAQ_PAGE_SET_SWG ||
pageID == IAQ_PAGE_SYSTEM_SETUP || pageID == IAQ_PAGE_FREEZE_PROTECT || pageID == IAQ_PAGE_LABEL_AUX || pageID == IAQ_PAGE_VSP_SETUP) {
pageID == IAQ_PAGE_SYSTEM_SETUP || pageID == IAQ_PAGE_FREEZE_PROTECT || pageID == IAQ_PAGE_LABEL_AUX ||
pageID == IAQ_PAGE_VSP_SETUP) {
// All other pages require us to go to Menu page
send_aqt_cmd(KEY_IAQTCH_MENU);
if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_MENU) {
@ -388,8 +462,14 @@ bool goto_iaqt_page(const unsigned char pageID, struct aqualinkdata *aq_data) {
button = iaqtFindButtonByLabel(menuText);
if (button == NULL) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find '%s' button on page setup\n", menuText);
return false;
//send_aqt_cmd(KEY_IAQTCH_NEXT_PAGE);
// Try Next Page
//unsigned char page = waitfor_iaqt_nextPage(aq_data);
//LOG(IAQT_LOG, LOG_ERR, "PAGE RETURN IS 0x%02hhx\n",page);
//if (waitfor_iaqt_nextPage(aq_data) != pageID) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find '%s' button on page setup\n", menuText);
return false;
//}
}
// send_aqt_cmd(KEY_IAQTCH_KEY01);
send_aqt_cmd(button->keycode);
@ -406,6 +486,250 @@ bool goto_iaqt_page(const unsigned char pageID, struct aqualinkdata *aq_data) {
return false;
}
void *set_aqualink_iaqtouch_device_on_off( void *ptr )
{
struct programmingThreadCtrl *threadCtrl;
threadCtrl = (struct programmingThreadCtrl *) ptr;
struct aqualinkdata *aq_data = threadCtrl->aq_data;
char *buf = (char*)threadCtrl->thread_args;
//char device_name[15];
struct iaqt_page_button *button;
unsigned int device = atoi(&buf[0]);
unsigned int state = atoi(&buf[5]);
waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_DEVICE_ON_OFF);
if (device > aq_data->total_buttons) {
LOG(IAQT_LOG,LOG_ERR, "(PDA mode) Device On/Off :- bad device number '%d'\n",device);
cleanAndTerminateThread(threadCtrl);
return ptr;
}
LOG(IAQT_LOG,LOG_INFO, "PDA Device On/Off, device '%s', state %d\n",aq_data->aqbuttons[device].label,state);
// See if it's on the current page
button = iaqtFindButtonByLabel(aq_data->aqbuttons[device].label);
if (button == NULL && isVBUTTON_ALTLABEL(aq_data->aqbuttons[device].special_mask) ) { // Try alt button name
button = iaqtFindButtonByLabel(((vbutton_detail *)aq_data->aqbuttons[device].special_mask_ptr)->altlabel);
}
if (button == NULL) {
// No luck, go to the device page
if ( goto_iaqt_page(IAQ_PAGE_DEVICES, aq_data) == false )
goto f_end;
button = iaqtFindButtonByLabel(aq_data->aqbuttons[device].label);
if (button == NULL && isVBUTTON_ALTLABEL(aq_data->aqbuttons[device].special_mask) ) { // Try alt button name
button = iaqtFindButtonByLabel(((vbutton_detail *)aq_data->aqbuttons[device].special_mask_ptr)->altlabel);
}
// If not found see if page has next
if (button == NULL && iaqtFindButtonByIndex(16)->type == 0x03 ) {
iaqt_queue_cmd(KEY_IAQTCH_NEXT_PAGE);
waitfor_iaqt_nextPage(aq_data);
// This will fail, since not looking at device page 2 buttons
button = iaqtFindButtonByLabel(aq_data->aqbuttons[device].label);
}
}
if (button == NULL) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find '%s' button on device list\n", aq_data->aqbuttons[device].label);
goto f_end;
}
send_aqt_cmd(button->keycode);
//LOG(IAQT_LOG, LOG_ERR, "******* CURRENT MENU '0x%02hhx' *****\n",iaqtCurrentPage());
// NSF NEED TO CHECK FOR POPUP MESSAGE, AND KILL IT.
//page IAQ_PAGE_SET_TEMP hit button 0
//page IAQ_PAGE_COLOR_LIGHT hit button ???????
// Heater popup can be cleared with a home button and still turn on.
// Color light can be cleared with a home button, but won;t turn on.
waitfor_iaqt_queue2empty();
//waitfor_iaqt_messages(aq_data,1);
// OR maybe use waitfor_iaqt_messages(aq_data,1)
// Turn spa on when pump off, ned to remove message "spa will turn on after safty delay", home doesn't clear.
send_aqt_cmd(KEY_IAQTCH_HOME);
// Turn spa off need to read message if heater is on AND hit ok......
f_end:
goto_iaqt_page(IAQ_PAGE_HOME, aq_data);
cleanAndTerminateThread(threadCtrl);
// just stop compiler error, ptr is not valid as it's just been freed
return ptr;
}
/*
// Not complete, and not needed with
void *set_aqualink_iaqtouch_onetouch_on_off( void *ptr )
{
struct programmingThreadCtrl *threadCtrl;
threadCtrl = (struct programmingThreadCtrl *) ptr;
struct aqualinkdata *aq_data = threadCtrl->aq_data;
char *buf = (char*)threadCtrl->thread_args;
//char device_name[15];
struct iaqt_page_button *button;
unsigned int device = atoi(&buf[0]);
unsigned int state = atoi(&buf[5]);
waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_ONETOUCH_ON_OFF);
LOG(IAQT_LOG,LOG_INFO, "OneTouch Device On/Off, device '%s', state %d\n",aq_data->aqbuttons[device].label,state);
if ( goto_iaqt_page(IAQ_PAGE_ONETOUCH, aq_data) == false )
goto f_end;
// See if it's on the current page
button = iaqtFindButtonByLabel(aq_data->aqbuttons[device].label);
f_end:
goto_iaqt_page(IAQ_PAGE_HOME, aq_data);
cleanAndTerminateThread(threadCtrl);
return ptr;
}
*/
void *set_aqualink_iaqtouch_light_colormode( void *ptr )
{
struct programmingThreadCtrl *threadCtrl;
threadCtrl = (struct programmingThreadCtrl *) ptr;
struct aqualinkdata *aq_data = threadCtrl->aq_data;
char *buf = (char*)threadCtrl->thread_args;
//char device_name[15];
struct iaqt_page_button *button;
const char *mode_name;
int val = atoi(&buf[0]);
int btn = atoi(&buf[5]);
int typ = atoi(&buf[10]);
bool use_current_mode = false;
bool turn_off = false;
waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE);
//char *buf = (char*)threadCtrl->thread_args;
if (btn < 0 || btn >= aq_data->total_buttons ) {
LOG(IAQT_LOG, LOG_ERR, "Can't program light mode on button %d\n", btn);
cleanAndTerminateThread(threadCtrl);
return ptr;
}
aqkey *key = &aq_data->aqbuttons[btn];
//unsigned char code = key->code;
// We also need to cater for light being ON AND changing the color mode. we have extra OK to hit.
if (val == 0) {
use_current_mode = true;
LOG(IAQT_LOG, LOG_INFO, "Light Programming #: %d, button: %s, color light type: %d, using current mode\n", val, key->label, typ);
// NOT SURE WHAT TO DO HERE..... No color mode and iaatouch doesn;t support last color in PDA mode.
} else if (val == -1) {
turn_off = true;
LOG(IAQT_LOG, LOG_INFO, "Light Programming #: %d, button: %s, color light type: %d, Turning off\n", val, key->label, typ);
} else {
mode_name = light_mode_name(typ, val-1, IAQTOUCH);
use_current_mode = false;
if (mode_name == NULL) {
LOG(IAQT_LOG, LOG_ERR, "Light Programming #: %d, button: %s, color light type: %d, couldn't find mode name '%s'\n", val, key->label, typ, mode_name);
cleanAndTerminateThread(threadCtrl);
return ptr;
} else {
LOG(IAQT_LOG, LOG_INFO, "Light Programming #: %d, button: %s, color light type: %d, name '%s'\n", val, key->label, typ, mode_name);
}
}
// See if it's on the current page
button = iaqtFindButtonByLabel(key->label);
if (button == NULL) {
// No luck, go to the device page
if ( goto_iaqt_page(IAQ_PAGE_DEVICES, aq_data) == false )
goto f_end;
button = iaqtFindButtonByLabel(key->label);
// If not found see if page has next
if (button == NULL && iaqtFindButtonByIndex(16)->type == 0x03 ) {
iaqt_queue_cmd(KEY_IAQTCH_NEXT_PAGE);
waitfor_iaqt_nextPage(aq_data);
// This will fail, since not looking at device page 2 buttons
button = iaqtFindButtonByLabel(key->label);
}
}
if (button == NULL) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find '%s' button on device list\n", key->label);
goto f_end;
}
// WE have a iaqualink button, press it.
send_aqt_cmd(button->keycode);
// See if we want to use the last color, or turn it off
if (use_current_mode || turn_off) {
// After pressing the button, Just need to wait for 5 seconds and it will :-
// a) if off turn on and default to last color.
// b) if on, turn off. (pain that we need to wait 5 seconds.)
waitfor_iaqt_queue2empty();
waitfor_iaqt_nextPage(aq_data);
if (use_current_mode) {
// Their is no message for this, so give one.
sprintf(aq_data->last_display_message, "Light will turn on in 5 seconds");
aq_data->is_display_message_programming = true;
aq_data->updated = true;
}
// Wait for next page maybe?
// Below needs a timeout.
while (waitfor_iaqt_nextPage(aq_data) == IAQ_PAGE_COLOR_LIGHT);
goto f_end;
}
// BELOW WE NEED TO CATER FOR OK POPUP IF LIGHT IS ALREADY ON
if (button->state == 0x01) { // Light is on, need to select the BUTTON
waitfor_iaqt_queue2empty();
// We Should wait for popup message, before sending code
send_aqt_cmd(KEY_IAQTCH_OK);
}
if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_COLOR_LIGHT) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not color light page\n");
goto f_end;
}
button = iaqtFindButtonByLabel(mode_name);
if (button == NULL) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did find color '%s' in color light page\n",mode_name);
goto f_end;
}
//LOG(IAQT_LOG, LOG_ERR, "IAQ Touch WAIYING FOR 1 MESSAGES\n");
//waitfor_iaqt_messages(aq_data, 1);
// Finally found the color. select it
send_aqt_cmd(button->keycode);
waitfor_iaqt_nextPage(aq_data);
f_end:
goto_iaqt_page(IAQ_PAGE_HOME, aq_data);
cleanAndTerminateThread(threadCtrl);
// just stop compiler error, ptr is not valid as it's just been freed
return ptr;
}
void *set_aqualink_iaqtouch_pump_rpm( void *ptr )
{
struct programmingThreadCtrl *threadCtrl;
@ -415,6 +739,7 @@ void *set_aqualink_iaqtouch_pump_rpm( void *ptr )
char VSPstr[20];
int structIndex;
struct iaqt_page_button *button;
char *pumpName = NULL;
//printf("**** program string '%s'\n",buf);
@ -423,6 +748,7 @@ void *set_aqualink_iaqtouch_pump_rpm( void *ptr )
//int pumpRPM = atoi(&buf[2]);
for (structIndex=0; structIndex < aq_data->num_pumps; structIndex++) {
if (aq_data->pumps[structIndex].pumpIndex == pumpIndex) {
pumpName = aq_data->pumps[structIndex].pumpName;
if (aq_data->pumps[structIndex].pumpType == PT_UNKNOWN) {
LOG(IAQT_LOG,LOG_ERR, "Can't set Pump RPM/GPM until type is known\n");
cleanAndTerminateThread(threadCtrl);
@ -436,8 +762,8 @@ void *set_aqualink_iaqtouch_pump_rpm( void *ptr )
//int pumpRPM = atoi(&buf[2]);
//int pumpIndex = 1;
// Just force to pump 1 for testing
sprintf(VSPstr, "VSP%1d Spd ADJ",pumpIndex);
// NSF Should probably check pumpRPM is not -1 here
waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_PUMP_RPM);
@ -447,10 +773,17 @@ void *set_aqualink_iaqtouch_pump_rpm( void *ptr )
if ( goto_iaqt_page(IAQ_PAGE_DEVICES, aq_data) == false )
goto f_end;
sprintf(VSPstr, "VSP%1d Spd ADJ",pumpIndex);
button = iaqtFindButtonByLabel(VSPstr);
if (button == NULL && pumpName[0] != '\0') {
// Try by name
sprintf(VSPstr, "%s ADJ",pumpName);
button = iaqtFindButtonByLabel(VSPstr);
}
if (button == NULL) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find '%s' button on page setup\n", VSPstr);
goto f_end;
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not Pump by index 'VSP%1d Spd ADJ' or by name '%s' button on page setup\n", pumpIndex, VSPstr);
goto f_end;
}
send_aqt_cmd(button->keycode);
@ -486,7 +819,9 @@ void *set_aqualink_iaqtouch_pump_rpm( void *ptr )
//send_aqt_cmd(0x80);
// Go to status page on startup to read devices
goto_iaqt_page(IAQ_PAGE_STATUS, aq_data);
// This is too soon
//goto_iaqt_page(IAQ_PAGE_STATUS, aq_data);
//waitfor_iaqt_nextPage(aq_data);
f_end:
goto_iaqt_page(IAQ_PAGE_HOME, aq_data);
@ -563,6 +898,7 @@ void *get_aqualink_iaqtouch_freezeprotect( void *ptr )
int frz = rsm_atoi(iaqtGetMessageLine(0));
if (frz >= 0) {
aq_data->frz_protect_set_point = frz;
aq_data->frz_protect_state = ON;
LOG(IAQT_LOG,LOG_NOTICE, "IAQ Touch Freeze Protection setpoint %d\n",frz);
}
@ -587,6 +923,10 @@ void *get_aqualink_iaqtouch_setpoints( void *ptr )
waitForSingleThreadOrTerminate(threadCtrl, AQ_GET_IAQTOUCH_SETPOINTS);
// Get product info.
send_aqt_cmd(KEY_IAQTCH_HELP);
waitfor_iaqt_nextPage(aq_data);
if ( goto_iaqt_page(IAQ_PAGE_SET_TEMP, aq_data) == false )
goto f_end;
@ -604,7 +944,7 @@ void *get_aqualink_iaqtouch_setpoints( void *ptr )
if (isSINGLE_DEV_PANEL != true)
{
changePanelToMode_Only();
LOG(AQRS_LOG,LOG_ERR, "AqualinkD set to 'Combo Pool & Spa' but detected 'Only Pool OR Spa' panel, please change config\n");
LOG(IAQT_LOG,LOG_ERR, "AqualinkD set to 'Combo Pool & Spa' but detected 'Only Pool OR Spa' panel, please change config\n");
}
}
}
@ -621,11 +961,17 @@ void *get_aqualink_iaqtouch_setpoints( void *ptr )
if (isSINGLE_DEV_PANEL != true)
{
changePanelToMode_Only();
LOG(AQRS_LOG,LOG_ERR, "AqualinkD set to 'Combo Pool & Spa' but detected 'Only Pool OR Spa' panel, please change config\n");
LOG(IAQT_LOG,LOG_ERR, "AqualinkD set to 'Combo Pool & Spa' but detected 'Only Pool OR Spa' panel, please change config\n");
}
}
}
button = iaqtFindButtonByLabel("Chiller");
if (button != NULL) {
aq_data->chiller_set_point = rsm_atoi((char *)&button->name + 8);
LOG(IAQT_LOG,LOG_DEBUG, "IAQ Touch got to Chiller setpoint %d\n",aq_data->chiller_set_point);
}
if ( goto_iaqt_page(IAQ_PAGE_FREEZE_PROTECT, aq_data) == false )
goto f_end;
@ -636,6 +982,52 @@ void *get_aqualink_iaqtouch_setpoints( void *ptr )
LOG(IAQT_LOG,LOG_NOTICE, "IAQ Touch Freeze Protection setpoint %d\n",frz);
}
// Get the temperature units if we are in iaq touch PDA mode
if (isPDA_PANEL) {
// If we are here, hit back then next button to get button with degrees on it.
// Only if in PDA mode
send_aqt_cmd(KEY_IAQTCH_BACK); // Clear the feeze protect menu and go back to system setup
if ( waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_SYSTEM_SETUP )
{
LOG(IAQT_LOG,LOG_ERR, "Couldn't get back to setup page, Temperature units unknown, default to DegF\n");
aq_data->temp_units = FAHRENHEIT;
goto f_end;
}
send_aqt_cmd(KEY_IAQTCH_NEXT_PAGE_ALTERNATE); // Go to page 2
if ( waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_SYSTEM_SETUP2 )
{
LOG(IAQT_LOG,LOG_ERR, "Couldn't get back to setup page, Temperature units unknown, default to DegF\n");
aq_data->temp_units = FAHRENHEIT;
goto f_end;
}
button = iaqtFindButtonByLabel("Degrees");
if (button != NULL) {
LOG(IAQT_LOG,LOG_NOTICE, "Temperature units are '%s'\n",button->name);
if (button->name[8] == 'C') {
LOG(IAQT_LOG,LOG_NOTICE, "Temperature unit message is '%s' set to degC\n",button->name);
aq_data->temp_units = CELSIUS;
} else {
LOG(IAQT_LOG,LOG_NOTICE, "Temperature unit message is '%s' set to degF\n",button->name);
aq_data->temp_units = FAHRENHEIT;
}
/*
printf("************** DEG IS '%c'\n", (uintptr_t)button->name[8]);
if ( (uintptr_t)button->name[8] == 'C') { // NSF THIS DOES NOT WORK, LEAVE COMPILER WARNING TO COME BACK AND FIX
aq_data->temp_units = CELSIUS;
} else {
aq_data->temp_units = FAHRENHEIT;
}
*/
}
}
// Need to run over table messages and check ens with X for on off.
// Go to status page on startup to read devices
@ -811,8 +1203,7 @@ void *set_aqualink_iaqtouch_swg_boost( void *ptr )
}
bool set_aqualink_iaqtouch_heater_setpoint( struct aqualinkdata *aq_data, bool pool, int val)
bool set_aqualink_iaqtouch_heater_setpoint( struct aqualinkdata *aq_data, SP_TYPE type, int val)
{
struct iaqt_page_button *button;
char *name;
@ -821,17 +1212,21 @@ bool set_aqualink_iaqtouch_heater_setpoint( struct aqualinkdata *aq_data, bool p
return false;
if (isCOMBO_PANEL) {
if (pool)
if (type == SP_POOL)
name = "Pool Heat";
else
name = "Spa Heat";
} else {
if (pool)
if (type == SP_POOL)
name = "Temp1";
else
name = "Temp2";
}
if (type == SP_CHILLER) {
name = "Chiller";
}
button = iaqtFindButtonByLabel(name);
if (button == NULL) {
@ -851,12 +1246,18 @@ bool set_aqualink_iaqtouch_heater_setpoint( struct aqualinkdata *aq_data, bool p
button = iaqtFindButtonByLabel(name);
if (button != NULL) {
if (pool)
int value = 0;
if (type == SP_POOL) {
aq_data->pool_htr_set_point = rsm_atoi((char *)&button->name + strlen(name));
else
value = aq_data->pool_htr_set_point;
} else if (type == SP_SPA) {
aq_data->spa_htr_set_point = rsm_atoi((char *)&button->name + strlen(name));
LOG(IAQT_LOG,LOG_DEBUG, "IAQ Touch set %s heater setpoint to %d\n",name,pool?aq_data->pool_htr_set_point:aq_data->spa_htr_set_point);
value = aq_data->spa_htr_set_point;
} else if (type == SP_CHILLER) {
aq_data->chiller_set_point = rsm_atoi((char *)&button->name + strlen(name));
value = aq_data->chiller_set_point;
}
LOG(IAQT_LOG,LOG_DEBUG, "IAQ Touch set %s heater setpoint to %d\n",name,value);
}
return true;
@ -871,9 +1272,9 @@ void *set_aqualink_iaqtouch_spa_heater_temp( void *ptr )
waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_SPA_HEATER_TEMP);
int val = atoi((char*)threadCtrl->thread_args);
val = setpoint_check(SPA_HTR_SETOINT, val, aq_data);
val = setpoint_check(SPA_HTR_SETPOINT, val, aq_data);
set_aqualink_iaqtouch_heater_setpoint(aq_data, false, val);
set_aqualink_iaqtouch_heater_setpoint(aq_data, SP_SPA, val);
goto_iaqt_page(IAQ_PAGE_HOME, aq_data);
cleanAndTerminateThread(threadCtrl);
@ -890,9 +1291,28 @@ void *set_aqualink_iaqtouch_pool_heater_temp( void *ptr )
waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_POOL_HEATER_TEMP);
int val = atoi((char*)threadCtrl->thread_args);
val = setpoint_check(POOL_HTR_SETOINT, val, aq_data);
val = setpoint_check(POOL_HTR_SETPOINT, val, aq_data);
set_aqualink_iaqtouch_heater_setpoint(aq_data, true, val);
set_aqualink_iaqtouch_heater_setpoint(aq_data, SP_POOL, val);
goto_iaqt_page(IAQ_PAGE_HOME, aq_data);
cleanAndTerminateThread(threadCtrl);
return ptr;
}
void *set_aqualink_iaqtouch_chiller_temp( void *ptr )
{
struct programmingThreadCtrl *threadCtrl;
threadCtrl = (struct programmingThreadCtrl *) ptr;
struct aqualinkdata *aq_data = threadCtrl->aq_data;
waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_POOL_HEATER_TEMP);
int val = atoi((char*)threadCtrl->thread_args);
//val = setpoint_check(POOL_HTR_SETPOINT, val, aq_data);
set_aqualink_iaqtouch_heater_setpoint(aq_data, SP_CHILLER, val);
goto_iaqt_page(IAQ_PAGE_HOME, aq_data);
cleanAndTerminateThread(threadCtrl);
@ -1108,4 +1528,4 @@ void *set_aqualink_iaqtouch_time( void *ptr )
// Debug: iAQ Touch: To 0x33 of type iAq pMes | HEX: 0x10|0x02|0x33|0x25|0x02|0x41|0x4d|0x00|0xfa|0x10|0x03|
//LOG(IAQT_LOG,LOG_DEBUG "Setting time to %d/%d/%d %d:%d\n", result->tm_mon + 1, result->tm_mday, result->tm_year + 1900, result->tm_hour + 1, result->tm_min);
*/
*/

View File

@ -3,6 +3,8 @@
#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);
@ -17,8 +19,12 @@ 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);

718
source/iaqualink.c Normal file
View File

@ -0,0 +1,718 @@
/*
* 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.
for (int bi=aq_data->virtual_button_start ; bi < aq_data->total_buttons ; bi++) {
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|
*/

View File

@ -32,6 +32,9 @@
#include "version.h"
#include "aq_timer.h"
#include "aq_programmer.h"
#include "rs_msg_utils.h"
#include "color_lights.h"
#include "iaqualink.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\"}"
@ -43,27 +46,43 @@
//SPA WILL TURN OFF AFTER COOL DOWN CYCLE
bool printableChar(char ch)
{
if ( (ch < 32 || ch > 126) ||
ch == 123 || // {
ch == 125 || // }
ch == 34 || // "
ch == 92 // backslash
) {
return false;
}
return true;
}
int json_chars(char *dest, const char *src, int dest_len, int src_len)
{
int i;
int end = dest_len < src_len ? dest_len:src_len;
for(i=0; i < end; i++) {
/*
if ( (src[i] < 32 || src[i] > 126) ||
src[i] == 123 || // {
src[i] == 125 || // }
src[i] == 34 || // "
src[i] == 92 // backslash
) // only printable chars
src[i] == 92 // backslash
) // only printable chars*/
if (! printableChar(src[i]))
dest[i] = ' ';
else
dest[i] = src[i];
}
i--;
/* // Don't delete trailing whitespace
while (dest[i] == ' ')
i--;
*/
if (dest[i] != '\0') {
if (i < (dest_len-1))
@ -71,13 +90,13 @@ int json_chars(char *dest, const char *src, int dest_len, int src_len)
dest[i] = '\0';
}
return i;
}
int build_logmsg_JSON(char *dest, const char *src, int dest_len, int src_len)
int build_logmsg_JSON(char *dest, int loglevel, const char *src, int dest_len, int src_len)
{
int length = sprintf(dest, "{\"logmsg\":\"");
int length = sprintf(dest, "{\"logmsg\":\"%-7s",elevel2text(loglevel));
length += json_chars(dest+length, src, (dest_len-20), src_len);
length += sprintf(dest+length, "\"}");
dest[length] = '\0';
@ -89,36 +108,58 @@ int build_logmsg_JSON(char *dest, const char *src, int dest_len, int src_len)
const char* _getStatus(struct aqualinkdata *aqdata, const char *blankmsg)
{
/*
if (aqdata->active_thread.thread_id != 0 && !aqdata->simulate_panel) {
//return JSON_PROGRAMMING;
return programtypeDisplayName(aqdata->active_thread.ptype);
}
*/
/*
printf("**** Programming=%s, length=%ld, empty=%s, Message=%s \n",
aqdata->active_thread.thread_id==0?"no":"yes",
strlen(aqdata->last_display_message),
rsm_isempy(aqdata->last_display_message,strlen(aqdata->last_display_message))==true?"yes":"no",
aqdata->last_display_message);
*/
// If only one bit set (conected) then ignore all these if's
if (aqdata->status_mask != CONNECTED) {
if ((aqdata->status_mask & ERROR_SERIAL) == ERROR_SERIAL)
return "ERROR No Serial connection";
else if ((aqdata->status_mask & ERROR_NO_DEVICE_ID) == ERROR_NO_DEVICE_ID)
return "ERROR No device ID";
else if ((aqdata->status_mask & CHECKING_CONFIG) == CHECKING_CONFIG)
return "Checking Config";
else if ((aqdata->status_mask & AUTOCONFIGURE_ID) == AUTOCONFIGURE_ID)
return "Searching for free device ID's, Please wait!";
else if ((aqdata->status_mask & AUTOCONFIGURE_PANEL) == AUTOCONFIGURE_PANEL)
return "Getting Panel Information";
else if ((aqdata->status_mask & CONNECTING) == CONNECTING)
return "Connecting (waiting for control panel)";
else if ((aqdata->status_mask & NOT_CONNECTED) == NOT_CONNECTED)
return "Not Connected to panel";
}
if (aqdata->active_thread.thread_id != 0) {
if (!aqdata->is_display_message_programming || rsm_isempy(aqdata->last_display_message,strlen(aqdata->last_display_message))){
return programtypeDisplayName(aqdata->active_thread.ptype);
}
}
//if (aqdata->last_message != NULL && stristr(aqdata->last_message, "SERVICE") != NULL ) {
//if (aqdata->last_message != NULL && stristr(aqdata->last_message, "SERVICE") != NULL ) {
if (aqdata->service_mode_state == ON) {
return JSON_SERVICE;
} else if (aqdata->service_mode_state == FLASH) {
return JSON_TIMEOUT;
}
// NSF should probably use json_chars here.
if (aqdata->last_display_message[0] != '\0') {
int i;
for(i=0; i < strlen(aqdata->last_display_message); i++ ) {
if (aqdata->last_display_message[i] <= 31 || aqdata->last_display_message[i] >= 127) {
if (! printableChar(aqdata->last_display_message[i])) {
aqdata->last_display_message[i] = ' ';
} else {
switch (aqdata->last_display_message[i]) {
case '"':
case '/':
case '\n':
case '\t':
case '\f':
case '\r':
case '\b':
aqdata->last_display_message[i] = ' ';
break;
}
}
}
//printf("JSON Sending '%s'\n",aqdata->last_display_message);
@ -168,7 +209,7 @@ int build_mqtt_status_message_JSON(char* buffer, int size, int idx, int nvalue,
return strlen(buffer);
}
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)
{
//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);
@ -227,32 +268,54 @@ char *get_aux_information(aqkey *button, struct aqualinkdata *aqdata, char *buff
int length = 0;
buffer[0] = '\0';
if ((button->special_mask & VS_PUMP) == VS_PUMP)
//if ((button->special_mask & VS_PUMP) == VS_PUMP)
if (isVS_PUMP(button->special_mask))
{
//printf("Button %s is VSP\n", button->name);
for (i=0; i < aqdata->num_pumps; i++) {
if (button == aqdata->pumps[i].button) {
length += sprintf(buffer, ",\"type_ext\":\"switch_vsp\",\"Pump_RPM\":\"%d\",\"Pump_GPM\":\"%d\",\"Pump_Watts\":\"%d\",\"Pump_Type\":\"%s\"",
aqdata->pumps[i].rpm,aqdata->pumps[i].gpm,aqdata->pumps[i].watts,
(aqdata->pumps[i].pumpType==VFPUMP?"vfPump":(aqdata->pumps[i].pumpType==VSPUMP?"vsPump":"ePump")));
length += sprintf(buffer, ",\"type_ext\":\"switch_vsp\",\"Pump_RPM\":\"%d\",\"Pump_GPM\":\"%d\",\"Pump_Watts\":\"%d\",\"Pump_Type\":\"%s\",\"Pump_Status\":\"%d\",\"Pump_Speed\":\"%d\"",
aqdata->pumps[i].rpm,
aqdata->pumps[i].gpm,
aqdata->pumps[i].watts,
(aqdata->pumps[i].pumpType==VFPUMP?"vfPump":(aqdata->pumps[i].pumpType==VSPUMP?"vsPump":"ePump")),
getPumpStatus(i, aqdata),
getPumpSpeedAsPercent(&aqdata->pumps[i]));
return buffer;
}
}
}
else if ((button->special_mask & PROGRAM_LIGHT) == PROGRAM_LIGHT)
//else if ((button->special_mask & PROGRAM_LIGHT) == PROGRAM_LIGHT)
else if (isPLIGHT(button->special_mask))
{
//printf("Button %s is ProgramableLight\n", button->name);
for (i=0; i < aqdata->num_lights; i++) {
if (button == aqdata->lights[i].button) {
length += sprintf(buffer, ",\"type_ext\": \"switch_program\", \"Light_Type\":\"%d\"", aqdata->lights[i].lightType);
if (aqdata->lights[i].lightType == LC_DIMMER2) {
length += sprintf(buffer, ",\"type_ext\": \"light_dimmer\", \"Light_Type\":\"%d\", \"Light_Program\":\"%d\", \"Program_Name\":\"%d%%\" ",
aqdata->lights[i].lightType,
aqdata->lights[i].currentValue,
aqdata->lights[i].currentValue);
} else {
length += sprintf(buffer, ",\"type_ext\": \"switch_program\", \"Light_Type\":\"%d\", \"Light_Program\":\"%d\", \"Program_Name\":\"%s\" ",
aqdata->lights[i].lightType,
aqdata->lights[i].currentValue,
get_currentlight_mode_name(aqdata->lights[i], ALLBUTTON));
//light_mode_name(aqdata->lights[i].lightType, aqdata->lights[i].currentValue, ALLBUTTON));
}
return buffer;
}
}
}
if (isVBUTTON_ALTLABEL(button->special_mask))
{
length += sprintf(buffer, ",\"alt_label\":\"%s\", \"in_alt_mode\": \"%s\" ",((vbutton_detail *)button->special_mask_ptr)->altlabel, ((vbutton_detail *)button->special_mask_ptr)->in_alt_mode?JSON_ON:JSON_OFF );
//return buffer;
}
//printf("Button %s is Switch\n", button->name);
length += sprintf(buffer, ",\"type_ext\": \"switch_timer\", \"timer_active\":\"%s\"", (((button->special_mask & TIMER_ACTIVE) == TIMER_ACTIVE)?JSON_ON:JSON_OFF) );
length += sprintf(buffer+length, ",\"type_ext\": \"switch_timer\", \"timer_active\":\"%s\"", (((button->special_mask & TIMER_ACTIVE) == TIMER_ACTIVE)?JSON_ON:JSON_OFF) );
if ((button->special_mask & TIMER_ACTIVE) == TIMER_ACTIVE) {
length += sprintf(buffer+length,",\"timer_duration\":\"%d\"", get_timer_left(button));
}
@ -268,9 +331,10 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool
int i;
// IF temp units are F assume homekit is using F
bool homekit_f = (homekit && aqdata->temp_units==FAHRENHEIT);
bool homekit_f = (homekit && ( aqdata->temp_units==FAHRENHEIT || aqdata->temp_units == UNKNOWN) );
length += sprintf(buffer+length, "{\"type\": \"devices\"");
length += sprintf(buffer+length, ",\"aqualinkd_version\":\"%s\"",AQUALINKD_VERSION);//"09/01/16 THU",
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 )
@ -284,7 +348,7 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool
for (i=0; i < aqdata->total_buttons; i++)
{
if ( strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0 && (_aqconfig_.force_ps_setpoints || aqdata->pool_htr_set_point != TEMP_UNKNOWN)) {
if ( strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0 && (ENABLE_HEATERS || 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\", \"int_status\": \"%d\", \"timer_active\":\"%s\" },",
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,
@ -297,7 +361,7 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool
LED2int(aqdata->aqbuttons[i].led->state),
((aqdata->aqbuttons[i].special_mask & TIMER_ACTIVE) == TIMER_ACTIVE?JSON_ON:JSON_OFF) );
} else if ( strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name)==0 && (_aqconfig_.force_ps_setpoints || aqdata->spa_htr_set_point != TEMP_UNKNOWN)) {
} else if ( strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name)==0 && (ENABLE_HEATERS || 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\", \"int_status\": \"%d\", \"timer_active\":\"%s\" },",
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,
@ -311,6 +375,10 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool
((aqdata->aqbuttons[i].special_mask & TIMER_ACTIVE) == TIMER_ACTIVE?JSON_ON:JSON_OFF));
} else {
if (!homekit && ENABLE_CHILLER && isVBUTTON_CHILLER(aqdata->aqbuttons[i].special_mask) ) {
// We will add this VButton as a thermostat
continue;
}
get_aux_information(&aqdata->aqbuttons[i], aqdata, aux_info);
//length += sprintf(buffer+length, "{\"type\": \"switch\", \"type_ext\": \"switch_vsp\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"int_status\": \"%d\" %s},",
length += sprintf(buffer+length, "{\"type\": \"switch\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"int_status\": \"%d\" %s},",
@ -351,13 +419,11 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool
}*/
}
if ( aqdata->frz_protect_set_point != TEMP_UNKNOWN && aqdata->air_temp != TEMP_UNKNOWN) {
if ( ENABLE_FREEZEPROTECT || (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\", \"int_status\": \"%d\" },",
FREEZE_PROTECT,
"Freeze Protection",
//JSON_OFF,
aqdata->frz_protect_state==ON?JSON_ON:JSON_OFF,
//JSON_ENABLED,
aqdata->frz_protect_state==ON?LED2text(ON):LED2text(ENABLE),
((homekit)?2:0),
((homekit_f)?degFtoC(aqdata->frz_protect_set_point):aqdata->frz_protect_set_point),
@ -366,6 +432,21 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool
aqdata->frz_protect_state==ON?1:0);
}
if ( (ENABLE_CHILLER || (aqdata->chiller_set_point != TEMP_UNKNOWN && getWaterTemp(aqdata) != TEMP_UNKNOWN)) && (aqdata->chiller_button != NULL) ) {
length += sprintf(buffer+length, "{\"type\": \"setpoint_chiller\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"spvalue\": \"%.*f\", \"value\": \"%.*f\", \"int_status\": \"%d\" },",
CHILLER,
"Heat Pump Chiller",
aqdata->chiller_button->led->state==ON?JSON_ON:JSON_OFF,
//((vbutton_detail *)aqdata->chiller_button->special_mask_ptr)->in_alt_mode?JSON_ON:JSON_OFF,
aqdata->chiller_button->led->state==ON?LED2text(ON):LED2text(ENABLE),
//((vbutton_detail *)aqdata->chiller_button->special_mask_ptr)->in_alt_mode?(aqdata->chiller_button->led->state==ON?LED2text(ON):LED2text(ENABLE)):JSON_OFF,
((homekit)?2:0),
((homekit_f)?degFtoC(aqdata->chiller_set_point):aqdata->chiller_set_point),
((homekit)?2:0),
((homekit_f)?degFtoC(getWaterTemp(aqdata)):getWaterTemp(aqdata)),
aqdata->chiller_button->led->state==ON?1:0);
}
if (aqdata->swg_led_state != LED_S_UNKNOWN) {
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\", \"int_status\": \"%d\" },",
@ -390,6 +471,7 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool
((homekit_f)?2:0),
((homekit_f)?degFtoC(aqdata->swg_percent):aqdata->swg_percent));
//if (!homekit) { // For the moment keep boost off homekit
length += sprintf(buffer+length, "{\"type\": \"switch\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"int_status\": \"%d\"},",
SWG_BOOST_TOPIC,
"SWG Boost",
@ -450,7 +532,7 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool
"on",
((homekit)?2:0),
((homekit_f)?degFtoC(aqdata->pool_temp):aqdata->pool_temp));
length += sprintf(buffer+length, "{\"type\": \"temperature\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"value\": \"%.*f\" }",
length += sprintf(buffer+length, "{\"type\": \"temperature\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"value\": \"%.*f\" },",
SPA_TEMP_TOPIC,
/*SPA_TEMPERATURE,*/
"Spa Water Temperature",
@ -458,11 +540,26 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool
((homekit)?2:0),
((homekit_f)?degFtoC(aqdata->spa_temp):aqdata->spa_temp));
for (i=0; i < aqdata->num_sensors; i++)
{
if (aqdata->sensors[i].value != TEMP_UNKNOWN) {
//length += sprintf(buffer+length, "\"%s\": \"%.2f\",", aqdata->sensors[i].label, aqdata->sensors[i].value );
length += sprintf(buffer+length, "{\"type\": \"temperature\", \"id\": \"%s/%s\", \"name\": \"%s\", \"state\": \"%s\", \"value\": \"%.*f\" },",
SENSOR_TOPIC,aqdata->sensors[i].label,
aqdata->sensors[i].label,
"on",
((homekit)?2:0),
((homekit_f)?aqdata->sensors[i].value:aqdata->sensors[i].value));
}
}
/*
length += sprintf(buffer+length, "], \"aux_device_detail\": [");
for (i=0; i < MAX_PUMPS; i++) {
}
*/
if (buffer[length-1] == ',')
length--;
length += sprintf(buffer+length, "]}");
LOG(NET_LOG,LOG_DEBUG, "JSON: %s used %d of %d\n", homekit?"homebridge":"web", length, size);
@ -474,9 +571,20 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool
//return length;
}
int logmaskjsonobject(int16_t flag, char* buffer)
int logmaskjsonobject(logmask_t flag, char* buffer)
{
int length = sprintf(buffer, "{\"name\":\"%s\",\"id\":\"%d\",\"set\":\"%s\"},", logmask2name(flag), flag,(isDebugLogMaskSet(flag)?JSON_ON:JSON_OFF));
if (flag == RSSD_LOG) {
//length = sprintf(buffer, "{\"name\":\"%s\",\"id\":\"%d\",\"set\":\"%s\",\"filter\":\"0x%02hhx\"},", logmask2name(flag), flag,(isDebugLogMaskSet(flag)?JSON_ON:JSON_OFF), _aqconfig_.RSSD_LOG_filter[0]);
length = sprintf(buffer, "{\"name\":\"%s\",\"id\":\"%d\",\"set\":\"%s\",\"filters\":[", logmask2name(flag), flag,(isDebugLogMaskSet(flag)?JSON_ON:JSON_OFF));
for (int i=0; i < MAX_RSSD_LOG_FILTERS; i++) {
length += sprintf(buffer+length, "\"0x%02hhx\",", _aqconfig_.RSSD_LOG_filter[i]);
}
//"]},"
length += sprintf(buffer+length-1, "]},");
length--;
}
return length;
}
int logleveljsonobject(int level, char* buffer)
@ -484,6 +592,7 @@ int logleveljsonobject(int level, char* buffer)
int length = sprintf(buffer, "{\"name\":\"%s\",\"id\":\"%d\",\"set\":\"%s\"},", loglevel2name(level), level,(getSystemLogLevel()==level?JSON_ON:JSON_OFF));
return length;
}
int build_aqualink_aqmanager_JSON(struct aqualinkdata *aqdata, char* buffer, int size)
{
memset(&buffer[0], 0, size);
@ -491,28 +600,44 @@ int build_aqualink_aqmanager_JSON(struct aqualinkdata *aqdata, char* buffer, int
length += sprintf(buffer+length, "{\"type\": \"aqmanager\"");
length += sprintf(buffer+length, ",\"deamonized\": \"%s\"", (_aqconfig_.deamonize?JSON_ON:JSON_OFF) );
if ( isMASK_SET(aqdata->status_mask,AUTOCONFIGURE_ID ) ||
isMASK_SET(aqdata->status_mask,AUTOCONFIGURE_PANEL ) /*||
isMASK_SET(aqdata->status_mask,CONNECTING )*/ )
{
length += sprintf(buffer+length, ",\"config_editor\": \"no\"");
} else {
length += sprintf(buffer+length, ",\"config_editor\": \"yes\"");
}
length += sprintf(buffer+length, ",\"aqualinkd_version\":\"%s\"",AQUALINKD_VERSION);
/*
length += sprintf(buffer+length, ",\"panel_type\":\"%s\"",getPanelString());
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, ",\"logging2file\": \"%s\"",islogFileReady()?JSON_ON:JSON_OFF);
length += sprintf(buffer+length, ",\"logfileready\": \"%s\"",islogFileReady()?JSON_ON:JSON_OFF);
length += sprintf(buffer+length, ",\"logfilename\": \"%s\"",_aqconfig_.log_file);
//length += sprintf(buffer+length, ",\"logfileready\": \"%s\"",islogFileReady()?JSON_ON:JSON_OFF);
//length += sprintf(buffer+length, ",\"logfilename\": \"%s\"",_aqconfig_.log_file);
length += sprintf(buffer+length, ",\"debugmasks\":[");
length += logmaskjsonobject(AQUA_LOG, buffer+length);
length += logmaskjsonobject(NET_LOG, buffer+length);
length += logmaskjsonobject(AQRS_LOG, buffer+length);
length += logmaskjsonobject(ALLB_LOG, buffer+length);
length += logmaskjsonobject(ONET_LOG, buffer+length);
length += logmaskjsonobject(IAQT_LOG, buffer+length);
length += logmaskjsonobject(PDA_LOG, buffer+length);
length += logmaskjsonobject(RSSA_LOG, buffer+length);
length += logmaskjsonobject(DJAN_LOG, buffer+length);
length += logmaskjsonobject(DPEN_LOG, buffer+length);
length += logmaskjsonobject(RSSD_LOG, buffer+length);
length += logmaskjsonobject(PROG_LOG, buffer+length);
length += logmaskjsonobject(DBGT_LOG, buffer+length);
length += logmaskjsonobject(TIMR_LOG, buffer+length);
length += logmaskjsonobject(SCHD_LOG, buffer+length);
length += logmaskjsonobject(RSTM_LOG, buffer+length);
length += logmaskjsonobject(SIM_LOG, buffer+length);
length += logmaskjsonobject(RSSD_LOG, buffer+length); // Make sure the last one.
// DBGT_LOG is a compile time only, so don;t include
if (buffer[length-1] == ',')
length--;
length += sprintf(buffer+length, "]");
@ -532,6 +657,7 @@ int build_aqualink_aqmanager_JSON(struct aqualinkdata *aqdata, char* buffer, int
return length;
}
int build_aqualink_status_JSON(struct aqualinkdata *aqdata, char* buffer, int size)
{
//strncpy(buffer, test_message, strlen(test_message)+1);
@ -545,7 +671,8 @@ int build_aqualink_status_JSON(struct aqualinkdata *aqdata, char* buffer, int si
length += sprintf(buffer+length, "{\"type\": \"status\"");
length += sprintf(buffer+length, ",\"status\":\"%s\"",getStatus(aqdata) );
length += sprintf(buffer+length, ",\"panel_message\":\"%s\"",aqdata->last_message );
length += sprintf(buffer+length, ",\"panel_type\":\"%s\"",getPanelString());
length += sprintf(buffer+length, ",\"panel_type_full\":\"%s\"",getPanelString());
length += sprintf(buffer+length, ",\"panel_type\":\"%s\"",getShortPanelString());
//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,
@ -559,6 +686,11 @@ int build_aqualink_status_JSON(struct aqualinkdata *aqdata, char* buffer, int si
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 ( (ENABLE_CHILLER || aqdata->chiller_set_point != TEMP_UNKNOWN) && aqdata->chiller_button != NULL) {
length += sprintf(buffer+length, ",\"chiller_set_pnt\":\"%d\"",aqdata->chiller_set_point );//"0",
if (isVBUTTON_CHILLER(aqdata->chiller_button->special_mask))
length += sprintf(buffer+length, ",\"chiller_mode\":\"%s\"",((vbutton_detail *)aqdata->chiller_button->special_mask_ptr)->in_alt_mode?"cool":"heat");
}
if ( aqdata->air_temp == TEMP_UNKNOWN )
length += sprintf(buffer+length, ",\"air_temp\":\" \"");
@ -604,7 +736,7 @@ int build_aqualink_status_JSON(struct aqualinkdata *aqdata, char* buffer, int si
if ( aqdata->orp != TEMP_UNKNOWN )
length += sprintf(buffer+length, ",\"chem_orp\":\"%d\"",aqdata->orp );
if ( READ_RSDEV_SWG )
//if ( READ_RSDEV_SWG )
length += sprintf(buffer+length, ",\"swg_fullstatus\": \"%d\"", aqdata->ar_swg_device_status);
length += sprintf(buffer+length, ",\"leds\":{" );
@ -624,10 +756,14 @@ int build_aqualink_status_JSON(struct aqualinkdata *aqdata, char* buffer, int si
length += sprintf(buffer+length, ", \"%s\": \"%s\"", SWG_BOOST_TOPIC, aqdata->boost?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, aqdata->frz_protect_state==ON?JSON_ON:JSON_ENABLED);
if ( aqdata->frz_protect_set_point != TEMP_UNKNOWN || ENABLE_FREEZEPROTECT ) {
//length += sprintf(buffer+length, ", \"%s\": \"%s\"", FREEZE_PROTECT, aqdata->frz_protect_state==ON?JSON_ON:JSON_ENABLED);
length += sprintf(buffer+length, ", \"%s\": \"%s\"", FREEZE_PROTECT, LED2text(aqdata->frz_protect_state) );
}
// Add Chiller if exists
if (aqdata->chiller_button != NULL) {
length += sprintf(buffer+length, ", \"%s\": \"%s\"", CHILLER, LED2text(aqdata->chiller_button->led->state) );
}
//length += sprintf(buffer+length, "}, \"extra\":{" );
length += sprintf(buffer+length, "},");
@ -642,10 +778,12 @@ printf("Pump GPM %d\n",aqdata->pumps[i].gpm);
printf("Pump GPM %d\n",aqdata->pumps[i].watts);
printf("Pump Type %d\n",aqdata->pumps[i].pumpType);
*/
if (aqdata->pumps[i].pumpType != PT_UNKNOWN && (aqdata->pumps[i].rpm != TEMP_UNKNOWN || aqdata->pumps[i].gpm != TEMP_UNKNOWN || aqdata->pumps[i].watts != TEMP_UNKNOWN)) {
length += sprintf(buffer+length, "\"Pump_%d\":{\"name\":\"%s\",\"id\":\"%s\",\"RPM\":\"%d\",\"GPM\":\"%d\",\"Watts\":\"%d\",\"Pump_Type\":\"%s\"},",
//if (aqdata->pumps[i].pumpType != PT_UNKNOWN && (aqdata->pumps[i].rpm != TEMP_UNKNOWN || aqdata->pumps[i].gpm != TEMP_UNKNOWN || aqdata->pumps[i].watts != TEMP_UNKNOWN)) {
if (aqdata->pumps[i].pumpType != PT_UNKNOWN ) {
length += sprintf(buffer+length, "\"Pump_%d\":{\"name\":\"%s\",\"id\":\"%s\",\"RPM\":\"%d\",\"GPM\":\"%d\",\"Watts\":\"%d\",\"Pump_Type\":\"%s\",\"Status\":\"%d\"},",
i+1,aqdata->pumps[i].button->label,aqdata->pumps[i].button->name,aqdata->pumps[i].rpm,aqdata->pumps[i].gpm,aqdata->pumps[i].watts,
(aqdata->pumps[i].pumpType==VFPUMP?"vfPump":(aqdata->pumps[i].pumpType==VSPUMP?"vsPump":"ePump")));
(aqdata->pumps[i].pumpType==VFPUMP?"vfPump":(aqdata->pumps[i].pumpType==VSPUMP?"vsPump":"ePump")),
getPumpStatus(i, aqdata));
}
}
if (buffer[length-1] == ',')
@ -674,8 +812,45 @@ printf("Pump Type %d\n",aqdata->pumps[i].pumpType);
length--;
length += sprintf(buffer+length, "}");
length += sprintf(buffer+length, ",\"light_program_names\":{" );
for (i=0; i < aqdata->num_lights; i++)
{
if (aqdata->lights[i].lightType == LC_DIMMER2) {
length += sprintf(buffer+length, "\"%s\": \"%d%%\",", aqdata->lights[i].button->name, aqdata->lights[i].currentValue );
} else {
//length += sprintf(buffer+length, "\"%s\": \"%s\",", aqdata->lights[i].button->name, light_mode_name(aqdata->lights[i].lightType, aqdata->lights[i].currentValue, RSSADAPTER) );
length += sprintf(buffer+length, "\"%s\": \"%s\",", aqdata->lights[i].button->name, get_currentlight_mode_name(aqdata->lights[i], RSSADAPTER) );
}
}
if (buffer[length-1] == ',')
length--;
length += sprintf(buffer+length, "}");
length += sprintf(buffer+length, ",\"alternate_modes\":{" );
for (i=aqdata->virtual_button_start; i < aqdata->total_buttons; i++)
{
if (isVBUTTON_ALTLABEL(aqdata->aqbuttons[i].special_mask)) {
length += sprintf(buffer+length, "\"%s\": \"%s\",",aqdata->aqbuttons[i].name, ((vbutton_detail *)aqdata->aqbuttons[i].special_mask_ptr)->in_alt_mode?JSON_ON:JSON_OFF );
}
}
if (buffer[length-1] == ',')
length--;
length += sprintf(buffer+length, "}");
length += sprintf(buffer+length, ",\"sensors\":{" );
for (i=0; i < aqdata->num_sensors; i++)
{
//printf("Sensor value %f %.2f\n",aqdata->sensors[i].value,aqdata->sensors[i].value);
if (aqdata->sensors[i].value != TEMP_UNKNOWN) {
length += sprintf(buffer+length, "\"%s\": \"%.2f\",", aqdata->sensors[i].label, aqdata->sensors[i].value );
}
}
if (buffer[length-1] == ',')
length--;
length += sprintf(buffer+length, "}");
length += sprintf(buffer+length, "}" );
@ -705,6 +880,83 @@ int build_aux_labels_JSON(struct aqualinkdata *aqdata, char* buffer, int size)
//return strlen(buffer);
}
/*
const char* emulationtype2name(emulation_type type) {
switch (type) {
case ALLBUTTON:
return "allbutton";
break;
case RSSADAPTER:
return "allbutton";
break;
case ONETOUCH:
return "onetouch";
break;
case IAQTOUCH:
return "iaqualinktouch";
break;
case AQUAPDA:
return "aquapda";
break;
case JANDY_DEVICE:
return "jandydevice";
break;
case SIMULATOR:
return "allbutton";
break;
default:
return "none";
break;
}
}
*/
int build_aqualink_simulator_packet_JSON(struct aqualinkdata *aqdata, char* buffer, int size)
{
memset(&buffer[0], 0, size);
int length = 0;
int i;
length += sprintf(buffer+length, "{\"type\": \"simpacket\"");
if (aqdata->simulator_packet[PKT_DEST] >= 0x40 && aqdata->simulator_packet[PKT_DEST] <= 0x43) {
length += sprintf(buffer+length, ",\"simtype\": \"onetouch\"");
} else if (aqdata->simulator_packet[PKT_DEST] >= 0x08 && aqdata->simulator_packet[PKT_DEST] <= 0x0a) {
length += sprintf(buffer+length, ",\"simtype\": \"allbutton\"");
} else if (aqdata->simulator_packet[PKT_DEST] >= 0x30 && aqdata->simulator_packet[PKT_DEST] <= 0x33) {
length += sprintf(buffer+length, ",\"simtype\": \"iaqtouch\"");
} else if (aqdata->simulator_packet[PKT_DEST] >= 0x60 && aqdata->simulator_packet[PKT_DEST] <= 0x63) {
length += sprintf(buffer+length, ",\"simtype\": \"aquapda\"");
} else {
length += sprintf(buffer+length, ",\"simtype\": \"unknown\"");
}
//if (aqdata->simulator_packet[i][])
//length += sprintf(buffer+length, ",\"simtype\": \"onetouch\"");
length += sprintf(buffer+length, ",\"raw\": [");
for (i=0; i < aqdata->simulator_packet_length; i++)
{
length += sprintf(buffer+length, "\"0x%02hhx\",", aqdata->simulator_packet[i]);
}
if (buffer[length-1] == ',')
length--;
length += sprintf(buffer+length, "]");
length += sprintf(buffer+length, ",\"dec\": [");
for (i=0; i < aqdata->simulator_packet_length; i++)
{
length += sprintf(buffer+length, "%d,", aqdata->simulator_packet[i]);
}
if (buffer[length-1] == ',')
length--;
length += sprintf(buffer+length, "]");
length += sprintf(buffer+length, "}");
//printf("Buffer=%d, used=%d, OUT='%s'\n",size,length,buffer);
return length;
}
// WS Received '{"parameter":"SPA_HTR","value":99}'
// WS Received '{"command":"KEY_HTR_POOL"}'
@ -890,4 +1142,372 @@ bool parseJSONmqttrequest(const char *str, size_t len, int *idx, int *nvalue, ch
return false;
}
/*
int json_cfg_element_OLD(char* buffer, int size, const char *name, const void *value, cfg_value_type type, char *valid_val) {
int result = 0;
char valid_values[256];
if (valid_val != NULL) {
sprintf(valid_values,",\"valid values\":%s",valid_val);
}
switch(type){
case CFG_INT:
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%d\", \"type\":\"int\" %s}", name, *(int *)value, (valid_val==NULL?"":valid_values) );
break;
case CFG_STRING:
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"string\" %s}", name, (char *)value, (valid_val==NULL?"":valid_values) );
break;
case CFG_BOOL:
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"bool\" %s}", name, bool2text(*(bool *)value), (valid_val==NULL?"":valid_values));
break;
case CFG_HEX:
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"0x%02hhx\", \"type\":\"hex\" %s}", name, *(unsigned char *)value, (valid_val==NULL?"":valid_values) );
break;
}
if (result <= 0 || result >= size) {
LOG(NET_LOG,LOG_ERR, "Buffer full in build_aqualink_config_JSON(), result truncated!");
return 0;
}
return result;
}
*/
//#ifdef CONFIG_EDITOR
int json_cfg_element(char* buffer, int size, const char *name, const void *value, cfg_value_type type, uint8_t mask, char *valid_val, uint8_t config_mask) {
int result = 0;
char valid_values[256];
char adv[128];
int adv_size=0;
// We shouldn't get CFG_HIDE here. Since we can't exit with 0, simply add a space
if (isMASKSET(config_mask, CFG_HIDE)) {
return snprintf(buffer, size, " ");
}
if (valid_val != NULL) {
sprintf(valid_values,",\"valid values\":%s",valid_val);
}
adv_size = sprintf(adv,",\"advanced\": \"%s\"", isMASKSET(config_mask, CFG_GRP_ADVANCED)?"yes":"no");
if (isMASKSET(config_mask, CFG_READONLY))
adv_size += sprintf(adv+adv_size,",\"readonly\": \"yes\"");
if (isMASKSET(config_mask, CFG_FORCE_RESTART))
adv_size += sprintf(adv+adv_size,",\"force_restart\": \"yes\"");
switch(type){
case CFG_INT:
if (*(int *)value == AQ_UNKNOWN) {
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"\", \"type\":\"int\" %s %s}", name, (valid_val==NULL?"":valid_values),adv );
} else {
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%d\", \"type\":\"int\" %s %s}", name, *(int *)value, (valid_val==NULL?"":valid_values),adv );
}
break;
case CFG_STRING:
if (*(char **)value == NULL) {
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"\", \"type\":\"string\" %s %s}", name, (valid_val==NULL?"":valid_values),adv );
} else {
if (isMASK_SET(config_mask, CFG_PASSWD_MASK)) {
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"string\", \"passwd_mask\":\"yes\" %s}", name, PASSWD_MASK_TEXT,adv);
} else {
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"string\" %s %s}", name, *(char **)value, (valid_val==NULL?"":valid_values),adv );
}
}
break;
case CFG_BOOL:
//result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"bool\" %s}", name, bool2text(*(bool *)value), (valid_val==NULL?"":valid_values));
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"bool\", \"valid values\": %s %s}", name, bool2text(*(bool *)value), CFG_V_BOOL, adv);
break;
case CFG_HEX:
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"0x%02hhx\", \"type\":\"hex\" %s %s}", name, *(unsigned char *)value, (valid_val==NULL?"":valid_values), adv );
break;
case CFG_FLOAT:
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%f\", \"type\":\"float\" %s %s}", name, *(float *)value, (valid_val==NULL?"":valid_values), adv );
break;
case CFG_BITMASK:
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"bool\", \"valid values\": %s %s}", name, (*(uint8_t *)value & mask) == mask? bool2text(true):bool2text(false) ,CFG_V_BOOL, adv );
break;
case CFG_SPECIAL:
if (strncasecmp(name, CFG_N_log_level, strlen(CFG_N_log_level)) == 0) {
//fprintf(fp, "%s=%s\n", _cfgParams[i].name, loglevel2cgn_name(_aqconfig_.log_level));
//result = json_cfg_element(buffer+length, size-length, CFG_N_log_level, &stringptr, CFG_STRING, "[\"DEBUG\", \"INFO\", \"NOTICE\", \"WARNING\", \"ERROR\"]"));
//stringptr = loglevel2cgn_name(_aqconfig_.log_level);
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"string\" %s %s}",name,loglevel2cgn_name(*(int *)value), ",\"valid values\":[\"DEBUG\", \"INFO\", \"NOTICE\", \"WARNING\", \"ERROR\"]", adv);
} else if (strncasecmp(name, CFG_N_panel_type, strlen(CFG_N_panel_type)) == 0) {
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"string\" %s}",name,getShortPanelString(), adv);
} else {
result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"Something went wrong\", \"type\":\"string\"}",name);
}
break;
}
if (result <= 0 || result >= size) {
LOG(NET_LOG,LOG_ERR, "Buffer full, result truncated! size left=%d needed=%d @ element %s\n",size,result,name);
return 0;
}
return result;
}
/*
const char *pumpType2String(pump_type ptype) {
switch (ptype) {
case EPUMP:
return "JANDY ePUMP";
break;
case VSPUMP:
return "Pentair VF";
break;
case VFPUMP:
return "Pentair VS";
break;
case PT_UNKNOWN:
default:
return "unknown";
break;
}
}
*/
int build_aqualink_config_JSON(char* buffer, int size, struct aqualinkdata *aq_data)
{
memset(&buffer[0], 0, size);
int length = 0;
int result;
int i;
char buf[256];
char buf1[256];
const char *stringptr;
int delectCharAt = 0;
if ((result = snprintf(buffer+length, size-length, "{\"type\": \"config\"")) < 0 || result >= size-length) {
length += snprintf(buffer+length, size-length, "}");
return length;
} else {
length += result;
}
/*
if ((result = snprintf(buffer+length, size-length, ",\"status\": \"!!!! NOT FULLY IMPLIMENTED YET !!!!\"")) < 0 || result >= size-length) {
length += snprintf(buffer+length, size-length, "}");
return length;
} else {
length += result;
}
*/
if ((result = snprintf(buffer+length, size-length, ",\"max_pumps\": \"%d\",\"max_lights\": \"%d\",\"max_sensors\": \"%d\",\"max_light_programs\": \"%d\",\"max_vbuttons\": \"%d\"",
MAX_PUMPS,MAX_LIGHTS,MAX_SENSORS,LIGHT_COLOR_OPTIONS-1, (TOTAL_BUTTONS - aq_data->virtual_button_start) )) < 0 || result >= size-length) {
length += snprintf(buffer+length, size-length, "}");
return length;
} else {
length += result;
}
//#ifdef CONFIG_DEV_TEST
for (int i=0; i <= _numCfgParams; i++) {
if (isMASK_SET(_cfgParams[i].config_mask, CFG_HIDE) ) {
continue;
}
// We can't change web_directory or port while running, so don;t even chow those options.
// mongoose holds a pointer to the string web_directoy, so can;t change that easily while running
// web port = well derr we are using that currently
/*
if ( strncasecmp(_cfgParams[i].name, CFG_N_socket_port, strlen(CFG_N_socket_port)) == 0 ||
strncasecmp(_cfgParams[i].name, CFG_N_web_directory, strlen(CFG_N_web_directory)) == 0 ) {
continue;
}
*/
if (isMASK_SET(_cfgParams[i].config_mask, CFG_READONLY) ) {
// NSF in the future we should allow these to pass, but set the UI as readonly.
continue;
}
if ((result = json_cfg_element(buffer+length, size-length, _cfgParams[i].name, _cfgParams[i].value_ptr, _cfgParams[i].value_type, _cfgParams[i].mask, _cfgParams[i].valid_values, _cfgParams[i].config_mask)) <= 0) {
LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length);
return length;
} else {
length += result;
}
}
for (i = 1; i <= aq_data->num_sensors; i++)
{
length += sprintf(buffer+length, ",\"sensor_%.2d\":{ \"advanced\":\"yes\",",i );
// The next json_cfg_element() call will add a , at the beginning, so save the next char index so we can delete it later.
delectCharAt = length;
//fprintf(fp,"\nsensor_%.2d_path=%s\n",i+1,aqdata->sensors->path);
//fprintf(fp,"sensor_%.2d_label=%s\n",i+1,aqdata->sensors->label);
//fprintf(fp,"sensor_%.2d_factor=%f\n",i+1,aqdata->sensors->factor);
sprintf(buf,"sensor_%.2d_path", i);
if ((result = json_cfg_element(buffer+length, size-length, buf, &aq_data->sensors[i-1].path, CFG_STRING, 0, NULL, CFG_GRP_ADVANCED)) <= 0)
return length;
else
length += result;
sprintf(buf,"sensor_%.2d_label", i);
if ((result = json_cfg_element(buffer+length, size-length, buf, &aq_data->sensors[i-1].label, CFG_STRING, 0, NULL, CFG_GRP_ADVANCED)) <= 0)
return length;
else
length += result;
sprintf(buf,"sensor_%.2d_factor", i);
if ((result = json_cfg_element(buffer+length, size-length, buf, &aq_data->sensors[i-1].factor, CFG_FLOAT, 0, NULL, CFG_GRP_ADVANCED)) <= 0)
return length;
else
length += result;
length += sprintf(buffer+length, "}" );
if (delectCharAt != 0) {
buffer[delectCharAt] = ' ';
delectCharAt = 0;
}
}
// add custom light modes/colors
bool isShow;
const char *lname;
const char *bufptr = buf1;
for (i=1; i < LIGHT_COLOR_OPTIONS; i++) {
if ((lname = get_aqualinkd_light_mode_name(i, &isShow)) != NULL) {
//fprintf(fp,"light_program_%.2d=%s%s\n",i,lname,isShow?" - show":"");
sprintf(buf,"light_program_%.2d", i);
sprintf(buf1,"%s%s",lname,isShow?" - show":"");
//printf("%s %s\n",buf,buf1);
if ((result = json_cfg_element(buffer+length, size-length, buf, &bufptr, CFG_STRING, 0, NULL, CFG_GRP_ADVANCED)) <= 0)
return length;
else
length += result;
} else {
break;
}
}
// All buttons
for (i = 0; i < aq_data->total_buttons; i++)
{
char prefix[30];
if (isVBUTTON(aq_data->aqbuttons[i].special_mask)) {
sprintf(prefix,"virtual_button_%.2d",(i+1)-aq_data->virtual_button_start);
} else {
sprintf(prefix,"button_%.2d",i+1);
}
//length += sprintf(buffer+length, ",\"%s\":{",prefix );
length += sprintf(buffer+length, ",\"%s\":{ \"default\":\"%s\", ",prefix, aq_data->aqbuttons[i].name );
// The next json_cfg_element() call will add a , at the beginning, so save the next char index so we can delete it later.
delectCharAt = length;
sprintf(buf,"%s_label", prefix);
if ((result = json_cfg_element(buffer+length, size-length, buf, &aq_data->aqbuttons[i].label, CFG_STRING, 0, NULL, 0)) <= 0) {
LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length);
return length;
} else
length += result;
if (isVS_PUMP(aq_data->aqbuttons[i].special_mask))
{
if (((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpIndex > 0) {
sprintf(buf,"%s_pumpIndex", prefix);
if ((result = json_cfg_element(buffer+length, size-length, buf, &((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpIndex, CFG_INT, 0, NULL, 0)) <= 0) {
LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length);
return length;
} else
length += result;
}
if (((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpID != NUL) {
sprintf(buf,"%s_pumpID", prefix);
if ((result = json_cfg_element(buffer+length, size-length, buf, &((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpID, CFG_HEX, 0, NULL, 0)) <= 0) {
LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length);
return length;
} else
length += result;
}
if (((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpName[0] != '\0') {
sprintf(buf,"%s_pumpName", prefix);
stringptr = ((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpName;
if ((result = json_cfg_element(buffer+length, size-length, buf, &stringptr, CFG_STRING, 0, NULL, 0)) <= 0) {
LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length);
return length;
} else
length += result;
}
if (((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpType != PT_UNKNOWN) {
sprintf(buf,"%s_pumpType", prefix);
stringptr = pumpType2String(((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpType);
if ((result = json_cfg_element(buffer+length, size-length, buf, &stringptr, CFG_STRING, 0, "[\"\", \"JANDY ePUMP\",\"Pentair VS\",\"Pentair VF\"]", 0) ) <= 0) {
LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length);
return length;
} else
length += result;
}
} else if (isPLIGHT(aq_data->aqbuttons[i].special_mask)) {
if (((clight_detail *)aq_data->aqbuttons[i].special_mask_ptr)->lightType > 0) {
sprintf(buf,"%s_lightMode", prefix);
if ((result = json_cfg_element(buffer+length, size-length, buf, &((clight_detail *)aq_data->aqbuttons[i].special_mask_ptr)->lightType, CFG_INT, 0, NULL, 0)) <= 0) {
LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length);
return length;
} else
length += result;
}
} else if ( (isVBUTTON(aq_data->aqbuttons[i].special_mask) && aq_data->aqbuttons[i].rssd_code >= IAQ_ONETOUCH_1 && aq_data->aqbuttons[i].rssd_code <= IAQ_ONETOUCH_6 ) ) {
sprintf(buf,"%s_onetouchID", prefix);
int oID = (aq_data->aqbuttons[i].rssd_code - 15);
if ((result = json_cfg_element(buffer+length, size-length, buf, &oID, CFG_INT, 0, "[\"\", \"1\",\"2\",\"3\",\"4\",\"5\",\"6\"]", 0)) <= 0) {
LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length);
return length;
} else
length += result;
} else if ( isVBUTTON_ALTLABEL(aq_data->aqbuttons[i].special_mask)) {
sprintf(buf,"%s_altlabel", prefix);
if ((result = json_cfg_element(buffer+length, size-length, buf, &((vbutton_detail *)aq_data->aqbuttons[i].special_mask_ptr)->altlabel, CFG_STRING, 0, NULL, 0)) <= 0) {
LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length);
return length;
} else
length += result;
}
length += sprintf(buffer+length, "}" );
if (delectCharAt != 0) {
buffer[delectCharAt] = ' ';
delectCharAt = 0;
}
}
// Need to add one last element, can be crap. Makes the HTML/JS easier in the loop
if ((result = snprintf(buffer+length, size-length, ",\"version\": \"1.0\"")) < 0 || result >= size-length) {
length += snprintf(buffer+length, size-length, "}");
return length;
} else {
length += result;
}
if ((result = snprintf(buffer+length, size-length, "}")) < 0) {
return 0;
} else {
length += result;
}
return length;
}
//#endif

View File

@ -7,8 +7,11 @@
//#define JSON_BUFFER_SIZE 4000
//#define JSON_STATUS_SIZE 1024
#define JSON_LABEL_SIZE 600
#define JSON_BUFFER_SIZE 5120
//#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
@ -48,15 +51,19 @@ int build_aqualink_status_JSON(struct aqualinkdata *aqdata, char* buffer, int si
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, const char *src, int dest_len, int src_len);
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_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_ */

File diff suppressed because it is too large Load Diff

View File

@ -28,8 +28,16 @@ bool start_net_services(struct aqualinkdata *aqdata);
void stop_net_services();
time_t poll_net_services(int timeout_ms);
void broadcast_aqualinkstate();
void broadcast_aqualinkstate_error(char *msg);
void broadcast_log(char *msg);
void broadcast_aqualinkstate_error(const char *msg);
void broadcast_simulator_message();
// NSF Need to find a better way, this is not thread safe, so don;t like exposting it.
//void send_mqtt(struct mg_connection *nc, const char *toppic, const char *message);
// superseded with systemd/sd-journal
//void broadcast_log(char *msg);
//#endif

View File

@ -40,30 +40,32 @@ static int _ot_hlightcharindexstart = -1;
static int _ot_hlightcharindexstop = -1;
static char _menu[ONETOUCH_LINES][AQ_MSGLEN+1];
static struct ot_macro _macros[3];
bool _panel_version_P2 = false; // Older panels REV 0.1 and 0.2
void set_macro_status();
void pump_update(struct aqualinkdata *aq_data, int updated);
bool log_heater_setpoints(struct aqualinkdata *aq_data);
#ifdef AQ_RS16
void rs16led_update(struct aqualinkdata *aq_data, int updated);
#endif
void print_onetouch_menu()
{
int i;
for (i=0; i < ONETOUCH_LINES; i++) {
//printf("PDA Line %d = %s\n",i,_menu[i]);
LOG(ONET_LOG,LOG_INFO, "OneTouch Menu Line %d = %s\n",i,_menu[i]);
LOG(ONET_LOG,LOG_INFO, "Menu Line %d = %s\n",i,_menu[i]);
}
if (_ot_hlightcharindexstart > -1) {
LOG(ONET_LOG,LOG_INFO, "OneTouch Menu highlighted line = %d, '%s' hligh-char(s) '%.*s'\n",
LOG(ONET_LOG,LOG_INFO, "Menu highlighted line = %d, '%s' hligh-char(s) '%.*s'\n",
_ot_hlightindex,_menu[_ot_hlightindex],
(_ot_hlightcharindexstop - _ot_hlightcharindexstart + 1),
&_menu[_ot_hlightindex][_ot_hlightcharindexstart]);
} else if (_ot_hlightindex > -1) {
LOG(ONET_LOG,LOG_INFO, "OneTouch Menu highlighted line = %d = %s\n",_ot_hlightindex,_menu[_ot_hlightindex]);
LOG(ONET_LOG,LOG_INFO, "Menu highlighted line = %d = %s\n",_ot_hlightindex,_menu[_ot_hlightindex]);
}
}
@ -178,7 +180,7 @@ bool process_onetouch_menu_packet(struct aqualinkdata *aq_data, unsigned char* p
_ot_hlightcharindexstart = -1;
_ot_hlightcharindexstart = -1;
}
LOG(ONET_LOG,LOG_DEBUG, "OneTouch Menu highlighted line = %d = %s\n",_ot_hlightindex,_menu[_ot_hlightindex]);
LOG(ONET_LOG,LOG_DEBUG, "Menu highlighted line = %d = %s\n",_ot_hlightindex,_menu[_ot_hlightindex]);
//if (getLogLevel() >= LOG_DEBUG){print_onetouch_menu();}
break;
case CMD_PDA_HIGHLIGHTCHARS:
@ -193,7 +195,7 @@ bool process_onetouch_menu_packet(struct aqualinkdata *aq_data, unsigned char* p
_ot_hlightcharindexstart = -1;
_ot_hlightcharindexstart = -1;
}
LOG(ONET_LOG,LOG_DEBUG, "OneTouch Menu highlighted line = %d, '%s' chars '%.*s'\n",
LOG(ONET_LOG,LOG_DEBUG, "Menu highlighted line = %d, '%s' chars '%.*s'\n",
_ot_hlightindex,
_menu[_ot_hlightindex],
(_ot_hlightcharindexstop - _ot_hlightcharindexstart) + 1,
@ -255,7 +257,7 @@ bool log_heater_setpoints(struct aqualinkdata *aq_data)
if (isSINGLE_DEV_PANEL != true)
{
changePanelToMode_Only();
LOG(AQRS_LOG,LOG_ERR, "AqualinkD set to 'Combo Pool & Spa' but detected 'Only Pool OR Spa' panel, please change config\n");
LOG(ONET_LOG,LOG_ERR, "AqualinkD set to 'Combo Pool & Spa' but detected 'Only Pool OR Spa' panel, please change config\n");
}
}
if (rsm_strcmp(_menu[3], "Temp2") == 0 )
@ -271,12 +273,41 @@ bool log_heater_setpoints(struct aqualinkdata *aq_data)
return rtn;
}
/*
One Touch: OneTouch Menu Line 0 =
One Touch: OneTouch Menu Line 1 =
One Touch: OneTouch Menu Line 2 =
One Touch: OneTouch Menu Line 3 =
One Touch: OneTouch Menu Line 4 = MODEL E0260801
One Touch: OneTouch Menu Line 5 = RS-8 Combo
One Touch: OneTouch Menu Line 6 =
One Touch: OneTouch Menu Line 7 = REV. O.2
One Touch: OneTouch Menu Line 8 =
One Touch: OneTouch Menu Line 9 =
One Touch: OneTouch Menu Line 10 =
One Touch: OneTouch Menu Line 11 =
*/
bool log_panelversion(struct aqualinkdata *aq_data)
{
char *end;
static bool revTest=false;
setPanelInformationFromPanelMsg(aq_data, _menu[4], PANEL_CPU, ONETOUCH);
setPanelInformationFromPanelMsg(aq_data, _menu[5], PANEL_STRING, ONETOUCH);
setPanelInformationFromPanelMsg(aq_data, _menu[7], PANEL_REV, ONETOUCH);
// It's already been set
if (strlen(aq_data->version) > 0) {
// If another protocol set the version, we need to check the rev.
if (!revTest){
LOG(ONET_LOG,LOG_NOTICE, "Control Panel revision %s\n", aq_data->revision);
if ( strcmp(aq_data->revision, "O.1") == 0 || strcmp(aq_data->revision, "O.2") == 0 ) {
LOG(ONET_LOG,LOG_NOTICE, "Setting early version for OneTouch\n");
_panel_version_P2 = true;
revTest = true;
}
LOG(ONET_LOG,LOG_NOTICE, "Control Panel revision %s\n", aq_data->revision);
}
return false;
}
@ -290,6 +321,17 @@ bool log_panelversion(struct aqualinkdata *aq_data)
end = aq_data->version + strlen(aq_data->version) - 1;
while(end > aq_data->version && isspace(*end)) end--;
rsm_get_revision(aq_data->revision, _menu[7], AQ_MSGLEN);
LOG(ONET_LOG,LOG_NOTICE, "Control Panel version %s\n", aq_data->version);
LOG(ONET_LOG,LOG_NOTICE, "Control Panel revision %s\n", aq_data->revision);
if ( strcmp(aq_data->revision, "O.1") == 0 || strcmp(aq_data->revision, "O.2") == 0 ) {
LOG(ONET_LOG,LOG_NOTICE, "Setting early version for OneTouch\n");
_panel_version_P2 = true;
}
// Probably should check the panel size here as well.
// One Touch: OneTouch Menu Line 5 = RS-16 Combo
// Write new null terminator
*(end+1) = 0;
@ -313,119 +355,308 @@ bool log_freeze_setpoints(struct aqualinkdata *aq_data)
return rtn;
}
bool log_qeuiptment_status(struct aqualinkdata *aq_data)
/*
bool get_pumpinfo_from_menu_OLD(struct aqualinkdata *aq_data, int menuLineIdx)
{
int rpm = 0;
int watts = 0;
int gpm = 0;
panel_vsp_status panelStatus = PS_OK;
int pump_index = rsm_atoi(&_menu[menuLineIdx][14]);
if (pump_index <= 0)
pump_index = rsm_atoi(&_menu[menuLineIdx][12]); // Pump inxed is in different position on line ` ePump AC 4`
// RPM displays differently depending on 3 or 4 digit rpm.
if (rsm_strcmp(_menu[menuLineIdx + 1], "RPM:") == 0)
{
rpm = rsm_atoi(&_menu[menuLineIdx + 1][10]);
if (rsm_strcmp(_menu[menuLineIdx + 2], "Watts:") == 0){
watts = rsm_atoi(&_menu[menuLineIdx + 2][10]);
}
if (rsm_strcmp(_menu[menuLineIdx + 3], "GPM:") == 0){
gpm = rsm_atoi(&_menu[menuLineIdx + 3][10]);
}
}
else if (rsm_strcmp(_menu[menuLineIdx+1], "*** Priming ***") == 0){
//rpm = PUMP_PRIMING; // NSF need to remove future
panelStatus = PS_PRIMING;
}
else if (rsm_strcmp(_menu[menuLineIdx+1], "(Offline)") == 0){
//rpm = PUMP_OFFLINE; // NSF need to remove future
panelStatus = PS_OFFLINE;
}
else if (rsm_strcmp(_menu[menuLineIdx+1], "(Priming Error)") == 0){
//rpm = PUMP_ERROR; // NSF need to remove future
panelStatus = PS_ERROR;
}
LOG(ONET_LOG, LOG_DEBUG, "Found Pump '%s', Index %d, RPM %d, Watts %d, GPM %d\n", _menu[menuLineIdx], pump_index, rpm, watts, gpm);
for (int i = 0; i < aq_data->num_pumps; i++)
{
if (aq_data->pumps[i].pumpIndex == pump_index)
{
// printf("**** FOUND PUMP %d at index %d *****\n",pump_index,i);
// aq_data->pumps[i].updated = true;
pump_update(aq_data, i);
aq_data->pumps[i].rpm = rpm;
aq_data->pumps[i].watts = watts;
aq_data->pumps[i].gpm = gpm;
aq_data->pumps[i].pStatus = panelStatus;
// LOG(ONET_LOG,LOG_INFO, "Matched OneTouch Pump to Index %d, RPM %d, Watts %d, GPM %d\n",i,rpm,watts,gpm);
LOG(ONET_LOG, LOG_INFO, "Matched Pump to '%s', Index %d, RPM %d, Watts %d, GPM %d\n", aq_data->pumps[i].button->name, i, rpm, watts, gpm);
if (aq_data->pumps[i].pumpType == PT_UNKNOWN)
{
if (rsm_strcmp(_menu[2], "Intelliflo VS") == 0)
aq_data->pumps[i].pumpType = VSPUMP;
else if (rsm_strcmp(_menu[2], "Intelliflo VF") == 0)
aq_data->pumps[i].pumpType = VFPUMP;
else if (rsm_strcmp(_menu[2], "Jandy ePUMP") == 0 ||
rsm_strcmp(_menu[2], "ePump AC") == 0)
aq_data->pumps[i].pumpType = EPUMP;
LOG(ONET_LOG, LOG_INFO, "Pump index %d set PumpType to %d\n", i, aq_data->pumps[i].pumpType);
}
return true;
}
}
LOG(ONET_LOG, LOG_WARNING, "Did not find AqualinkD config for Pump '%s'\n",_menu[menuLineIdx]);
return false;
}
*/
bool get_pumpinfo_from_menu(struct aqualinkdata *aq_data, int menuLineIdx, int pump_number)
{
int rpm = 0;
int watts = 0;
int gpm = 0;
panel_vsp_status pStatus = PS_OK;
char *cidx = NULL;
// valid controlpanel pump numbers are 1,2,3,4
if (pump_number < 1 || pump_number > MAX_PUMPS) {
LOG(ONET_LOG, LOG_WARNING, "Pump number %d for pump '%s' is invalid, ignoring!\n",pump_number,_menu[menuLineIdx]);
return false;
}
if ( (cidx = rsm_charafterstr(_menu[menuLineIdx + 1], "RPM", AQ_MSGLEN)) != NULL ){
rpm = rsm_atoi(cidx);
// Assuming Watts is always next line and GPM (if available) line after
if ( (cidx = rsm_charafterstr(_menu[menuLineIdx + 2], "Watts", AQ_MSGLEN)) != NULL ){
watts = rsm_atoi(cidx);
}
if ( (cidx = rsm_charafterstr(_menu[menuLineIdx + 3], "GPM", AQ_MSGLEN)) != NULL ){
gpm = rsm_atoi(cidx);
}
}
else if (rsm_strcmp(_menu[menuLineIdx + 1], "*** Priming ***") == 0){
pStatus = PS_PRIMING;
}
else if (rsm_strcmp(_menu[menuLineIdx + 1], "(Offline)") == 0){
pStatus = PS_OFFLINE;
}
else if (rsm_strcmp(_menu[menuLineIdx + 1], "(Priming Error)") == 0){
pStatus = PS_ERROR;
}
if (rpm==0 && watts==0 && rpm==0) {
// Didn't get any info, so return.
return false;
}
LOG(ONET_LOG, LOG_DEBUG, "Found Pump information '%s', RPM %d, Watts %d, GPM %d\n", _menu[menuLineIdx], rpm, watts, gpm);
for (int i=0; i < aq_data->num_pumps; i++) {
if (aq_data->pumps[i].pumpIndex == pump_number) {
LOG(ONET_LOG,LOG_INFO, "Pump label: %s Index: %d, Number: %d, RPM: %d, Watts: %d, GPM: %d\n",aq_data->pumps[i].button->name, i ,pump_number,rpm,watts,gpm);
pump_update(aq_data, i);
aq_data->pumps[i].rpm = rpm;
aq_data->pumps[i].watts = watts;
aq_data->pumps[i].gpm = gpm;
aq_data->pumps[i].pStatus = pStatus;
if (aq_data->pumps[i].pumpType == PT_UNKNOWN){
if (rsm_strcmp(_menu[menuLineIdx],"Intelliflo VS") == 0)
aq_data->pumps[i].pumpType = VSPUMP;
else if (rsm_strcmp(_menu[menuLineIdx],"Intelliflo VF") == 0)
aq_data->pumps[i].pumpType = VFPUMP;
else if (rsm_strcmp(_menu[menuLineIdx],"Jandy ePUMP") == 0 ||
rsm_strcmp(_menu[menuLineIdx],"ePump AC") == 0)
aq_data->pumps[i].pumpType = EPUMP;
LOG(ONET_LOG, LOG_INFO, "Pump index %d set PumpType to %d\n", i, aq_data->pumps[i].pumpType);
}
return true;
}
}
LOG(ONET_LOG,LOG_WARNING, "Could not find config for Pump %s, Number %d, RPM %d, Watts %d, GPM %d\n",_menu[menuLineIdx],pump_number,rpm,watts,gpm);
return false;
}
/*
Info: OneTouch Menu Line 1 =
Info: OneTouch Menu Line 2 = Chemlink 1
Info: OneTouch Menu Line 3 = ORP 750/PH 7.0
*/
bool get_chemlinkinfo_from_menu(struct aqualinkdata *aq_data, int menuLineIdx)
{
if (rsm_strcmp(_menu[menuLineIdx + 1], "ORP") == 0)
{
int orp = atoi(&_menu[menuLineIdx + 1][4]);
char *indx = strchr(_menu[menuLineIdx + 1], '/');
float ph = atof(indx + 3);
LOG(ONET_LOG, LOG_INFO, "Cemlink ORP = %d PH = %f\n", orp, ph);
if (aq_data->ph != ph || aq_data->orp != orp)
{
aq_data->ph = ph;
aq_data->orp = orp;
return true;
}
return false;
}
LOG(ONET_LOG, LOG_WARNING, "Did not understand Chemlink message '%s'\n",_menu[menuLineIdx + 1]);
return false;
}
/*
Info: OneTouch Menu Line 2 = AQUAPURE 60%
Info: OneTouch Menu Line 3 = Salt 7600 PPM */
bool get_aquapureinfo_from_menu(struct aqualinkdata *aq_data, int menuLineIdx)
{
int i;
bool rtn = false;
#ifdef READ_SWG_FROM_EXTENDED_ID
int swgp = atoi(&_menu[menuLineIdx][10]);
if (aq_data->swg_percent != swgp)
{
changeSWGpercent(aq_data, swgp);
rtn = true;
}
if (rsm_strcmp(_menu[menuLineIdx+1], "Salt") == 0)
{
int ppm = atoi(&_menu[menuLineIdx+1][6]);
if (aq_data->swg_ppm != ppm)
{
aq_data->swg_ppm = ppm;
rtn = true;
}
LOG(ONET_LOG, LOG_INFO, "Aquapure SWG %d%, %d PPM\n", swgp, ppm);
}
#endif
return rtn;
}
bool get_RS16buttoninfo_from_menu(struct aqualinkdata *aq_data, int menuLineIdx)
{
for (int i = aq_data->rs16_vbutton_start; i <= aq_data->rs16_vbutton_end; i++)
{
if (rsm_strcmp(_menu[menuLineIdx], aq_data->aqbuttons[i].label) == 0)
{
// Matched must be on.
LOG(ONET_LOG, LOG_INFO, "RS16 equiptment status '%s' matched '%s'\n", _menu[menuLineIdx], aq_data->aqbuttons[i].label);
rs16led_update(aq_data, i);
aq_data->aqbuttons[i].led->state = ON;
return true;
}
}
return false;
}
/*
For older Panel versions 0.1 and 0.2
These display information all on one page, kind'a limped together like PDA
OneTouch Menu Line 0 = EQUIPMENT STATUS
OneTouch Menu Line 1 =
OneTouch Menu Line 2 = AquaPure 35%
OneTouch Menu Line 3 = SALT 3200 PPM
OneTouch Menu Line 4 = FILTER PUMP
OneTouch Menu Line 5 = Intelliflo VS 1
OneTouch Menu Line 6 = RPM: 1750
OneTouch Menu Line 7 = WATTS: 330
OneTouch Menu Line 8 =
OneTouch Menu Line 9 =
OneTouch Menu Line 10 =
OneTouch Menu Line 11 =
*/
bool log_qeuiptment_status_VP2(struct aqualinkdata *aq_data)
{
bool rtn = false;
char *cidx = NULL;
int i;
for (i = 0; i < ONETOUCH_LINES; i++)
{
/*
if (rsm_strcmp(_menu[i], "Intelliflo VS") == 0 ||
rsm_strcmp(_menu[i], "Intelliflo VF") == 0 ||
rsm_strcmp(_menu[i], "Jandy ePUMP") == 0 ||
rsm_strcmp(_menu[i], "ePump AC") == 0)
{
rtn = get_pumpinfo_from_menu(aq_data, i);
}*/
if ( (cidx = rsm_charafterstr(_menu[i], "Intelliflo VS", AQ_MSGLEN)) != NULL ||
(cidx = rsm_charafterstr(_menu[i], "Intelliflo VF", AQ_MSGLEN)) != NULL ||
(cidx = rsm_charafterstr(_menu[i], "Jandy ePUMP", AQ_MSGLEN)) != NULL ||
(cidx = rsm_charafterstr(_menu[i], "ePump AC", AQ_MSGLEN)) != NULL)
{
rtn = get_pumpinfo_from_menu(aq_data, i, rsm_atoi(cidx));
} else if (rsm_strcmp(_menu[2],"AQUAPURE") == 0) {
rtn = get_aquapureinfo_from_menu(aq_data, i);
} else if (rsm_strcmp(_menu[i],"Chemlink") == 0) {
rtn = get_chemlinkinfo_from_menu(aq_data, i);
} else if (PANEL_SIZE() >= 16 ) {
// Loop over RS 16 buttons.
get_RS16buttoninfo_from_menu(aq_data, i);
}
}
return rtn;
}
/*
Newer panels have a page per device and specific lines with information
*/
bool log_qeuiptment_status(struct aqualinkdata *aq_data)
{
if (_panel_version_P2)
return log_qeuiptment_status_VP2(aq_data);
bool rtn = false;
char *cidx = NULL;
/*
if (rsm_strcmp(_menu[2],"Intelliflo VS") == 0 ||
rsm_strcmp(_menu[2],"Intelliflo VF") == 0 ||
rsm_strcmp(_menu[2],"Jandy ePUMP") == 0 ||
rsm_strcmp(_menu[2],"ePump AC") == 0) {
rtn = true;
int rpm = 0;
int watts = 0;
int gpm = 0;
int pump_index = rsm_atoi(&_menu[2][14]);
if (pump_index <= 0)
pump_index = rsm_atoi(&_menu[2][12]); // Pump inxed is in different position on line ` ePump AC 4`
// RPM displays differently depending on 3 or 4 digit rpm.
if (rsm_strcmp(_menu[3],"RPM:") == 0){
rpm = rsm_atoi(&_menu[3][10]);
if (rsm_strcmp(_menu[4],"Watts:") == 0) {
watts = rsm_atoi(&_menu[4][10]);
}
if (rsm_strcmp(_menu[5],"GPM:") == 0) {
gpm = rsm_atoi(&_menu[5][10]);
}
} else if (rsm_strcmp(_menu[3],"*** Priming ***") == 0){
rpm = PUMP_PRIMING;
} else if (rsm_strcmp(_menu[3],"(Offline)") == 0){
rpm = PUMP_OFFLINE;
} else if (rsm_strcmp(_menu[3],"(Priming Error)") == 0){
rpm = PUMP_ERROR;
}
LOG(ONET_LOG,LOG_INFO, "OneTouch Pump %s, Index %d, RPM %d, Watts %d, GPM %d\n",_menu[2],pump_index,rpm,watts,gpm);
for (i=0; i < aq_data->num_pumps; i++) {
if (aq_data->pumps[i].pumpIndex == pump_index) {
//printf("**** FOUND PUMP %d at index %d *****\n",pump_index,i);
//aq_data->pumps[i].updated = true;
pump_update(aq_data, i);
aq_data->pumps[i].rpm = rpm;
aq_data->pumps[i].watts = watts;
aq_data->pumps[i].gpm = gpm;
if (aq_data->pumps[i].pumpType == PT_UNKNOWN){
if (rsm_strcmp(_menu[2],"Intelliflo VS") == 0)
aq_data->pumps[i].pumpType = VSPUMP;
else if (rsm_strcmp(_menu[2],"Intelliflo VF") == 0)
aq_data->pumps[i].pumpType = VFPUMP;
else if (rsm_strcmp(_menu[2],"Jandy ePUMP") == 0 ||
rsm_strcmp(_menu[2],"ePump AC") == 0)
aq_data->pumps[i].pumpType = EPUMP;
}
//printf ("Set Pump Type to %d\n",aq_data->pumps[i].pumpType);
}
}
#ifdef READ_SWG_FROM_EXTENDED_ID
rtn = get_pumpinfo_from_menu(aq_data, 2);
} */
if ( (cidx = rsm_charafterstr(_menu[2], "Intelliflo VS", AQ_MSGLEN)) != NULL ||
(cidx = rsm_charafterstr(_menu[2], "Intelliflo VF", AQ_MSGLEN)) != NULL ||
(cidx = rsm_charafterstr(_menu[2], "Jandy ePUMP", AQ_MSGLEN)) != NULL ||
(cidx = rsm_charafterstr(_menu[2], "ePump AC", AQ_MSGLEN)) != NULL)
{
rtn = get_pumpinfo_from_menu(aq_data, 2, rsm_atoi(cidx));
} else if (rsm_strcmp(_menu[2],"AQUAPURE") == 0) {
/* Info: OneTouch Menu Line 0 = Equipment Status
Info: OneTouch Menu Line 1 =
Info: OneTouch Menu Line 2 = AQUAPURE 60%
Info: OneTouch Menu Line 3 = Salt 7600 PPM */
int swgp = atoi(&_menu[2][10]);
if ( aq_data->swg_percent != swgp ) {
//aq_data->swg_percent = swgp;
if (changeSWGpercent(aq_data, swgp))
LOG(ONET_LOG,LOG_INFO, "OneTouch SWG = %d\n",swgp);
rtn = true;
}
if (rsm_strcmp(_menu[3],"Salt") == 0) {
int ppm = atoi(&_menu[3][6]);
if ( aq_data->swg_ppm != ppm ) {
aq_data->swg_ppm = ppm;
rtn = true;
}
LOG(ONET_LOG,LOG_INFO, "OneTouch PPM = %d\n",ppm);
}
#endif
rtn = get_aquapureinfo_from_menu(aq_data, 2);
} else if (rsm_strcmp(_menu[2],"Chemlink") == 0) {
/* Info: OneTouch Menu Line 0 = Equipment Status
Info: OneTouch Menu Line 1 =
Info: OneTouch Menu Line 2 = Chemlink 1
Info: OneTouch Menu Line 3 = ORP 750/PH 7.0 */
if (rsm_strcmp(_menu[3],"ORP") == 0) {
int orp = atoi(&_menu[3][4]);
char *indx = strchr(_menu[3], '/');
float ph = atof(indx+3);
if (aq_data->ph != ph || aq_data->orp != orp) {
aq_data->ph = ph;
aq_data->orp = orp;
return true;
}
LOG(ONET_LOG,LOG_INFO, "OneTouch Cemlink ORP = %d PH = %f\n",orp,ph);
}
rtn = get_chemlinkinfo_from_menu(aq_data, 2);
}
#ifdef AQ_RS16
else if (PANEL_SIZE() >= 16 ) { // This fails on RS4, comeback and find out why. // Run over devices that have no status LED's on RS12&16 panels.
//else if ( 16 <= (int)PANEL_SIZE ) {
int j;
int i;
for (i=2; i <= ONETOUCH_LINES; i++) {
for (j = aq_data->rs16_vbutton_start; j <= aq_data->rs16_vbutton_end; j++) {
if ( rsm_strcmp(_menu[i], aq_data->aqbuttons[j].label) == 0 ) {
//Matched must be on.
LOG(ONET_LOG,LOG_DEBUG, "OneTouch equiptment status '%s' matched '%s'\n",_menu[i],aq_data->aqbuttons[j].label);
rs16led_update(aq_data, j);
aq_data->aqbuttons[j].led->state = ON;
}
}
get_RS16buttoninfo_from_menu(aq_data, i);
}
}
#endif
@ -456,7 +687,7 @@ ot_menu_type get_onetouch_menu_type()
return OTM_BOOST;
else if (rsm_strcmp(_menu[0],"Set AQUAPURE") == 0)
return OTM_SET_AQUAPURE;
else if (rsm_strcmp(_menu[7],"REV ") == 0) // NSF Need a better check.
else if (rsm_strcmp(_menu[7],"REV") == 0) // NSF Need a better check.
return OTM_VERSION;
return OTM_UNKNOWN;
@ -473,6 +704,7 @@ void pump_update(struct aqualinkdata *aq_data, int updated) {
aq_data->pumps[i].rpm = PUMP_OFF_RPM;
aq_data->pumps[i].gpm = PUMP_OFF_GPM;
aq_data->pumps[i].watts = PUMP_OFF_WAT;
aq_data->pumps[i].pStatus = PS_OFF;
}
}
updates = '\0';
@ -481,7 +713,7 @@ void pump_update(struct aqualinkdata *aq_data, int updated) {
}
}
#ifdef AQ_RS16
void rs16led_update(struct aqualinkdata *aq_data, int updated) {
//LOG(ONET_LOG,LOG_INFO, "******* VLED check %d ******\n",updated);
const int bitmask[4] = {1,2,4,8};
@ -504,7 +736,7 @@ void rs16led_update(struct aqualinkdata *aq_data, int updated) {
//LOG(ONET_LOG,LOG_INFO, "******* Updated VLED status %d ******\n",updated);
}
}
#endif
bool new_menu(struct aqualinkdata *aq_data)
@ -552,10 +784,10 @@ bool new_menu(struct aqualinkdata *aq_data)
if (last_menu_type == OTM_EQUIPTMENT_STATUS && menu_type != OTM_EQUIPTMENT_STATUS && !in_ot_programming_mode(aq_data) ) {
// End of equiptment status chain of menus, reset any pump that wasn't listed in menus as long as we are not in programming mode
pump_update(aq_data, -1);
#ifdef AQ_RS16
if (PANEL_SIZE() >= 16)
rs16led_update(aq_data, -1);
#endif
}
last_menu_type = menu_type;

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