diff --git a/Makefile b/Makefile
index b6b5a140..72f6808d 100755
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,7 @@ AQ_PDA = true
AQ_ONETOUCH = true
AQ_IAQTOUCH = true
AQ_MANAGER =true
+
#AQ_RS_EXTRA_OPTS = false
#AQ_CONTAINER = false // this is for compiling for containers
#AQ_MEMCMP = true // Not implimented correctly yet.
@@ -76,7 +77,7 @@ 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\
devices_jandy.c packetLogger.c devices_pentair.c color_lights.c serialadapter.c aq_timer.c aq_scheduler.c web_config.c\
- serial_logger.c mongoose.c simulator.c timespec_subtract.c
+ serial_logger.c mongoose.c hassio.c simulator.c timespec_subtract.c
AQ_FLAGS =
diff --git a/README.md b/README.md
index d982578a..c10d88be 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ Designed to mimic AqualinkRS devices, used to fully configure the master control
* 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
-
+
## All Web interfaces.
* http://aqualink.ip/ <- (Standard WEB UI
@@ -83,7 +83,35 @@ Designed to mimic AqualinkRS devices, used to fully configure the master control
* Add set time to OneTouch protocol.
* Update AqualinkD Management console to manage configuration
* Create iAqualink Touch Simulator
+* Probably decoded enough protocols for AuqlinkD to self configure.
+
+
+# Update in Release 2.3.5
+* Added Home Assistant integration through MQTT discover
+ * Please read the Wiki section on this [Wiki - HASSIO](https://github.com/sfeakes/AqualinkD/wiki#HASSIO)
+ * There are still some enhacments to come on this.
+* Added support for reading extended information for Jandy JXi heaters.
+* Added Color Light to iAqualinkTouch protocol.
+* Added iAqualinkTouch support for PDA only panels that can use that protocol.
+ * PDA panel needs to be Rev 6.0 or newer.
+ * This makes the PDA only panels quicker and less error prone.
+ * Introduces color light support and VSP
+ * Consider this PDA support Beta.
+ * Read PDA Wiki
+* 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.
@@ -95,11 +123,11 @@ Designed to mimic AqualinkRS devices, used to fully configure the master control
# Update in Release 2.3.3
* Introduced Aqualink Manager UI http://aqualink.ip/aqmanager.html
- * [AqualinkD Manager](#AQManager)
+ * [AqualinkD Manager](https://github.com/sfeakes/AqualinkD/wiki#AQManager)
* Moved logging into systemd/journal (journalctl) from syslog
- * [AqualinkD Log](#Log)
+ * [AqualinkD Log](https://github.com/sfeakes/AqualinkD/wiki#Log)
* Updated to scheduler
- * [AqualinkD Scheduler](#Scheduler)
+ * [AqualinkD Scheduler](https://github.com/sfeakes/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.
diff --git a/a.out b/a.out
new file mode 100755
index 00000000..80722d4a
Binary files /dev/null and b/a.out differ
diff --git a/aq_mqtt.h b/aq_mqtt.h
index e701d9dd..4ea8a150 100644
--- a/aq_mqtt.h
+++ b/aq_mqtt.h
@@ -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,13 +33,18 @@
#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 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"
diff --git a/aq_panel.c b/aq_panel.c
index 520dad3b..633911f5 100644
--- a/aq_panel.c
+++ b/aq_panel.c
@@ -525,9 +525,14 @@ bool setDeviceState(struct aqualinkdata *aqdata, int deviceIndex, bool isON)
LOG(AQUA_LOG, LOG_INFO, "received '%s' for '%s', turning '%s'\n", (isON == false ? "OFF" : "ON"), button->name, (isON == false ? "OFF" : "ON"));
#ifdef AQ_PDA
if (isPDA_PANEL) {
- char msg[PTHREAD_ARG];
- sprintf(msg, "%-5d%-5d", deviceIndex, (isON == false ? OFF : ON));
- aq_programmer(AQ_PDA_DEVICE_ON_OFF, msg, aqdata);
+ if (button->special_mask & PROGRAM_LIGHT && isPDA_IAQT) {
+ // AqualinkTouch in PDA mode, we can program light. (if turing off, use standard AQ_PDA_DEVICE_ON_OFF below)
+ programDeviceLightMode(aqdata, (isON?0:-1), deviceIndex); // -1 means off 0 means use current light mode
+ } else {
+ char msg[PTHREAD_ARG];
+ sprintf(msg, "%-5d%-5d", deviceIndex, (isON == false ? OFF : ON));
+ aq_programmer(AQ_PDA_DEVICE_ON_OFF, msg, aqdata);
+ }
} else
#endif
{
@@ -598,7 +603,7 @@ void programDeviceLightMode(struct aqualinkdata *aqdata, int value, int button)
int i;
clight_detail *light = NULL;
#ifdef AQ_PDA
- if (isPDA_PANEL) {
+ if (isPDA_PANEL && !isPDA_IAQT) {
LOG(AQUA_LOG,LOG_ERR, "Light mode control not supported in PDA mode\n");
return;
}
@@ -642,7 +647,7 @@ void programDeviceLightMode(struct aqualinkdata *aqdata, int value, int button)
//bool panel_device_request(struct aqualinkdata *aqdata, action_type type, int deviceIndex, int value, int subIndex, bool fromMQTT)
bool panel_device_request(struct aqualinkdata *aqdata, action_type type, int deviceIndex, int value, request_source source)
{
- //LOG(AQUA_LOG,LOG_NOTICE, "Device request type %d for deviceindex %d of value %d from %d\n",type,deviceIndex, value, source);
+ LOG(AQUA_LOG,LOG_INFO, "Device request type %d for deviceindex %d of value %d from %d\n",type,deviceIndex, value, source);
switch (type) {
case ON_OFF:
//setDeviceState(&aqdata->aqbuttons[deviceIndex], value<=0?false:true, deviceIndex );
diff --git a/aq_programmer.c b/aq_programmer.c
index d9c78b48..63cc1287 100644
--- a/aq_programmer.c
+++ b/aq_programmer.c
@@ -397,8 +397,15 @@ void queueGetProgramData(emulation_type source_type, struct aqualinkdata *aq_dat
}
}
#ifdef AQ_PDA
- } else if ( source_type == AQUAPDA) {
+ } else if ( isPDA_PANEL && source_type == AQUAPDA) {
aq_programmer(AQ_PDA_INIT, NULL, aq_data);
+ } else if ( isPDA_PANEL && source_type == IAQTOUCH) {
+ //aq_programmer(AQ_PDA_INIT, NULL, aq_data);
+ if (_aqconfig_.use_panel_aux_labels) {
+ aq_programmer(AQ_GET_AUX_LABELS, NULL, aq_data);
+ }
+ aq_programmer(AQ_GET_IAQTOUCH_SETPOINTS, NULL, aq_data);
+
#endif
} else { // Must be all button only
aq_programmer(AQ_GET_POOL_SPA_HEATER_TEMPS, NULL, aq_data);
@@ -468,7 +475,8 @@ bool in_light_programming_mode(struct aqualinkdata *aq_data)
{
if ( ( aq_data->active_thread.thread_id != 0 ) &&
( aq_data->active_thread.ptype == AQ_SET_LIGHTPROGRAM_MODE ||
- aq_data->active_thread.ptype == AQ_SET_LIGHTCOLOR_MODE)
+ aq_data->active_thread.ptype == AQ_SET_LIGHTCOLOR_MODE ||
+ aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE)
) {
return true;
}
@@ -527,7 +535,9 @@ bool in_iaqt_programming_mode(struct aqualinkdata *aq_data)
aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_POOL_HEATER_TEMP ||
aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_SPA_HEATER_TEMP ||
aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_SET_TIME ||
- aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_PUMP_VS_PROGRAM)
+ aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_PUMP_VS_PROGRAM ||
+ aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_DEVICE_ON_OFF ||
+ aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE)
) {
return true;
}
@@ -580,14 +590,14 @@ void kick_aq_program_thread(struct aqualinkdata *aq_data, emulation_type source_
LOG(ONET_LOG, LOG_DEBUG, "Kicking OneTouch thread %d,%p\n",aq_data->active_thread.ptype, aq_data->active_thread.thread_id);
pthread_cond_broadcast(&aq_data->active_thread.thread_cond);
}
- else if (source_type == ALLBUTTON && !in_ot_programming_mode(aq_data)) {
- LOG(PROG_LOG, LOG_DEBUG, "Kicking RS thread %d,%p message '%s'\n",aq_data->active_thread.ptype, aq_data->active_thread.thread_id,aq_data->last_message);
- pthread_cond_broadcast(&aq_data->active_thread.thread_cond);
- }
else if (source_type == IAQTOUCH && in_iaqt_programming_mode(aq_data)) {
LOG(IAQT_LOG, LOG_DEBUG, "Kicking IAQ Touch thread %d,%p\n",aq_data->active_thread.ptype, aq_data->active_thread.thread_id);
pthread_cond_broadcast(&aq_data->active_thread.thread_cond);
}
+ else if (source_type == ALLBUTTON && !in_ot_programming_mode(aq_data) && !in_iaqt_programming_mode(aq_data)) {
+ LOG(PROG_LOG, LOG_DEBUG, "Kicking RS Allbutton thread %d,%p message '%s'\n",aq_data->active_thread.ptype, aq_data->active_thread.thread_id,aq_data->last_message);
+ pthread_cond_broadcast(&aq_data->active_thread.thread_cond);
+ }
#ifdef AQ_PDA
else if (source_type == AQUAPDA && !in_ot_programming_mode(aq_data)) {
LOG(PDA_LOG, LOG_DEBUG, "Kicking PDA thread %d,%p\n",aq_data->active_thread.ptype, aq_data->active_thread.thread_id);
@@ -671,7 +681,7 @@ void _aq_programmer(program_type r_type, char *args, struct aqualinkdata *aq_dat
else if (r_type == AQ_SET_SPA_HEATER_TEMP)
type = AQ_SET_RSSADAPTER_SPA_HEATER_TEMP;
} else if (r_type == AQ_SET_PUMP_RPM) {
- if (isONET_ENABLED)
+ if (isONET_ENABLED || isPDA_IAQT)
type = AQ_SET_ONETOUCH_PUMP_RPM;
else if (isIAQT_ENABLED)
type = AQ_SET_IAQTOUCH_PUMP_RPM;
@@ -719,7 +729,7 @@ void _aq_programmer(program_type r_type, char *args, struct aqualinkdata *aq_dat
}
#endif
#ifdef AQ_IAQTOUCH
- else if (isIAQT_ENABLED && isEXTP_ENABLED) {
+ else if ((isIAQT_ENABLED && isEXTP_ENABLED) || isPDA_IAQT) {
// IAQ Touch programming modes that should overite standard ones.
switch (r_type){
case AQ_GET_POOL_SPA_HEATER_TEMPS:
@@ -741,15 +751,30 @@ void _aq_programmer(program_type r_type, char *args, struct aqualinkdata *aq_dat
case AQ_SET_TIME:
type = AQ_SET_IAQTOUCH_SET_TIME;
break;
+ case AQ_PDA_DEVICE_ON_OFF:
+ if (isPDA_IAQT) {
+ type = AQ_SET_IAQTOUCH_DEVICE_ON_OFF;
+ }
+ break;
+ // This isn;t going to work outside of PDA mode, if the labels are incorrect.
+ case AQ_SET_LIGHTCOLOR_MODE:
+ if (isPDA_IAQT) {
+ type = AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE;
+ }
+ break;
default:
type = r_type;
break;
}
}
#endif
+
+
+
#ifdef AQ_PDA
// Check we are doing something valid request
- if (isPDA_PANEL) {
+ if (isPDA_PANEL && !isPDA_IAQT)
+ {
pda_reset_sleep();
if (type != AQ_PDA_INIT &&
type != AQ_PDA_WAKE_INIT &&
@@ -1028,6 +1053,12 @@ void _aq_programmer(program_type r_type, char *args, struct aqualinkdata *aq_dat
return;
}
break;
+ case AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE:
+ if( pthread_create( &programmingthread->thread_id , NULL , set_aqualink_iaqtouch_light_colormode, (void*)programmingthread) < 0) {
+ LOG(PROG_LOG, LOG_ERR, "could not create thread\n");
+ return;
+ }
+ break;
#endif
#ifdef AQ_PDA
case AQ_PDA_INIT:
@@ -1054,6 +1085,12 @@ void _aq_programmer(program_type r_type, char *args, struct aqualinkdata *aq_dat
return;
}
break;
+ case AQ_SET_IAQTOUCH_DEVICE_ON_OFF:
+ if( pthread_create( &programmingthread->thread_id , NULL , set_aqualink_iaqtouch_device_on_off, (void*)programmingthread) < 0) {
+ LOG(PROG_LOG, LOG_ERR, "could not create thread\n");
+ return;
+ }
+ break;
#endif
default:
@@ -1522,7 +1559,7 @@ void *set_aqualink_light_colormode( void *ptr )
use_current_mode = true;
LOG(PROG_LOG, LOG_INFO, "Light Programming #: %d, on button: %s, color light type: %d, using current mode\n", val, button->label, typ);
} else {
- mode_name = light_mode_name(typ, val-1);
+ mode_name = light_mode_name(typ, val-1, ALLBUTTON);
use_current_mode = false;
if (mode_name == NULL) {
LOG(PROG_LOG, LOG_ERR, "Light Programming #: %d, on button: %s, color light type: %d, couldn't find mode name '%s'\n", val, button->label, typ, mode_name);
@@ -2615,6 +2652,12 @@ const char *ptypeName(program_type type)
case AQ_SET_IAQTOUCH_SET_TIME:
return "Set iAqualink Set Time";
break;
+ case AQ_SET_IAQTOUCH_DEVICE_ON_OFF:
+ return "Set iAqualink Device On/Off";
+ break;
+ case AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE:
+ return "Set iAqualink Light Color (using panel)";
+ break;
#endif
#ifdef AQ_PDA
case AQ_PDA_INIT:
@@ -2680,6 +2723,7 @@ const char *programtypeDisplayName(program_type type)
break;
case AQ_SET_LIGHTPROGRAM_MODE:
case AQ_SET_LIGHTCOLOR_MODE:
+ case AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE:
return "Programming: setting light color";
break;
case AQ_SET_SWG_PERCENT:
@@ -2709,6 +2753,9 @@ const char *programtypeDisplayName(program_type type)
case AQ_GET_IAQTOUCH_VSP_ASSIGNMENT:
return "Get Pump Assignment";
break;
+ case AQ_SET_IAQTOUCH_DEVICE_ON_OFF:
+ return "Programming: setting device on/off";
+ break;
#ifdef AQ_PDA
case AQ_PDA_DEVICE_STATUS:
return "Programming: retrieving PDA Device status";
diff --git a/aq_programmer.h b/aq_programmer.h
index b3e8a006..65ebb543 100644
--- a/aq_programmer.h
+++ b/aq_programmer.h
@@ -83,6 +83,8 @@ typedef enum {
AQ_SET_IAQTOUCH_POOL_HEATER_TEMP,
AQ_SET_IAQTOUCH_SPA_HEATER_TEMP,
AQ_SET_IAQTOUCH_SET_TIME,
+ AQ_SET_IAQTOUCH_DEVICE_ON_OFF,
+ AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE,
AQ_GET_RSSADAPTER_SETPOINTS,
AQ_SET_RSSADAPTER_POOL_HEATER_TEMP,
AQ_SET_RSSADAPTER_SPA_HEATER_TEMP,
diff --git a/aq_serial.c b/aq_serial.c
index 652a6e03..503757bf 100644
--- a/aq_serial.c
+++ b/aq_serial.c
@@ -140,6 +140,12 @@ 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 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.
@@ -165,6 +171,19 @@ const char* get_packet_type(unsigned char* packet , int length)
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;
+
default:
sprintf(buf, "Unknown '0x%02hhx'", packet[PKT_CMD]);
return buf;
diff --git a/aq_serial.h b/aq_serial.h
index 2b9d5c7e..8b639d43 100644
--- a/aq_serial.h
+++ b/aq_serial.h
@@ -38,12 +38,16 @@
*/
#define PENTAIR_DEC_PUMP_MIN 96 // 0x60
#define PENTAIR_DEC_PUMP_MAX 111 // 0x6F
-#define JANDY_DEC_PUMP_MIN 120 // 0x80
-#define JANDY_DEC_PUMP_MAX 123 // 0x83
-#define JANDY_DEC_LX_MIN 56 // 0x40
-#define JANDY_DEC_LX_MAX 59 // 0x43
-#define JANDY_DEC_LXI_MIN 104 // 0x60
-#define JANDY_DEC_LXI_MAX 107 // 0x6B
+#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
+#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
// PACKET DEFINES Jandy
@@ -125,6 +129,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
@@ -294,8 +302,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
@@ -337,7 +346,7 @@ 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
/*
@@ -355,6 +364,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
@@ -390,6 +403,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
@@ -401,10 +415,14 @@ 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_START_BOOST 0x3f
+//#define IAQ_PAGE_DEGREES 0xFF // Added this as never want to actually select the page, just go to it.
@@ -426,7 +444,9 @@ typedef enum {
DRS_NONE,
DRS_SWG,
DRS_EPUMP,
- DRS_LXI
+ DRS_JXI,
+ DRS_LX,
+ DRS_CHEM
} rsDeviceType;
typedef enum {
diff --git a/aqualink.h b/aqualink.h
index d3dd3833..b0508f7d 100644
--- a/aqualink.h
+++ b/aqualink.h
@@ -205,6 +205,7 @@ struct aqualinkdata
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;
@@ -220,6 +221,7 @@ struct aqualinkdata
int swg_percent;
int swg_ppm;
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;
@@ -229,6 +231,7 @@ struct aqualinkdata
clight_detail lights[MAX_LIGHTS];
bool boost;
char boost_msg[10];
+ int boost_duration; // need to remove boost message and use this
float ph;
int orp;
diff --git a/aqualinkd.c b/aqualinkd.c
index 65665e18..0d57192f 100644
--- a/aqualinkd.c
+++ b/aqualinkd.c
@@ -180,6 +180,12 @@ bool checkAqualinkTime()
LOG(AQUA_LOG,LOG_DEBUG, "time not checked, will check in %d seconds\n", TIME_CHECK_INTERVAL - time_difference);
return true;
}
+ else if (strlen(_aqualink_data.date) <=0 ||
+ strlen(_aqualink_data.time) <=0)
+ {
+ LOG(AQUA_LOG,LOG_DEBUG, "time not checked, no time from panel\n");
+ return true;
+ }
else
{
last_checked = now;
@@ -188,7 +194,7 @@ bool checkAqualinkTime()
char datestr[DATE_STRING_LEN];
#ifdef AQ_PDA
- if (isPDA_PANEL) {
+ if (isPDA_PANEL && !isPDA_IAQT) {
LOG(AQUA_LOG,LOG_DEBUG, "PDA Time Check\n");
// date is simply a day or week for PDA.
localtime_r(&now, &aq_tm);
@@ -358,6 +364,7 @@ int16_t RS16_endswithLEDstate(char *msg)
#endif
+
void _processMessage(char *message, bool reset);
void processMessage(char *message)
@@ -454,6 +461,7 @@ void _processMessage(char *message, bool reset)
if ((msg_loop & MSG_BOOST) != MSG_BOOST) {
_aqualink_data.boost = false;
_aqualink_data.boost_msg[0] = '\0';
+ _aqualink_data.boost_duration = 0;
//if (_aqualink_data.swg_percent >= 101)
// _aqualink_data.swg_percent = 0;
}
@@ -619,7 +627,8 @@ void _processMessage(char *message, bool reset)
else if (strcasestr(msg, MSG_SWG_HIGH_SALT) != NULL)
setSWGdeviceStatus(&_aqualink_data, ALLBUTTON, SWG_STATUS_HI_SALT);
else if (strcasestr(msg, MSG_SWG_FAULT) != NULL)
- setSWGdeviceStatus(&_aqualink_data, ALLBUTTON, SWG_STATUS_CHECK_PCB);
+ setSWGdeviceStatus(&_aqualink_data, ALLBUTTON, SWG_STATUS_GENFAULT);
+ //setSWGdeviceStatus(&_aqualink_data, ALLBUTTON, SWG_STATUS_CHECK_PCB);
// Any of these messages want to display.
strcpy(_aqualink_data.last_display_message, msg);
@@ -730,9 +739,11 @@ void _processMessage(char *message, bool reset)
// Ignore messages if in programming mode. We get one of these turning off for some strange reason.
if (in_programming_mode(&_aqualink_data) == false) {
snprintf(_aqualink_data.boost_msg, 6, "%s", &msg[11]);
+ _aqualink_data.boost_duration = rsm_HHMM2min(_aqualink_data.boost_msg);
_aqualink_data.boost = true;
msg_loop |= MSG_BOOST;
msg_loop |= MSG_SWG;
+ //convert_boost_to_duration(_aqualink_data.boost_msg)
//if (_aqualink_data.ar_swg_status != SWG_STATUS_ON) {_aqualink_data.ar_swg_status = SWG_STATUS_ON;}
if (_aqualink_data.swg_percent != 101) {changeSWGpercent(&_aqualink_data, 101);}
//boost_msg_count = 0;
@@ -807,6 +818,9 @@ bool process_packet(unsigned char *packet, int length)
#ifdef AQ_PDA
if (isPDA_PANEL)
{
+ if (isPDA_IAQT) {
+ return false;
+ }
return process_pda_packet(packet, length);
}
#endif
@@ -987,7 +1001,7 @@ void action_delayed_request()
}
else
{
- LOG(AQUA_LOG,LOG_NOTICE, "SWG % is already %d, not changing\n", _aqualink_data.unactioned.value);
+ LOG(AQUA_LOG,LOG_NOTICE, "SWG %% is already %d, not changing\n", _aqualink_data.unactioned.value);
}
}
// Let's just tell everyone we set it, before we actually did. Makes homekit happy, and it will re-correct on error.
@@ -1168,7 +1182,7 @@ int startup(char *self, char *cfgFile)
return EXIT_FAILURE;
}
} else if (isPDA_PANEL) {
- if (_aqconfig_.device_id >= 0x60 && _aqconfig_.device_id <= 0x63) {
+ if ( (_aqconfig_.device_id >= 0x60 && _aqconfig_.device_id <= 0x63) || _aqconfig_.device_id == 0x33 ) {
// We are good
} else {
LOG(AQUA_LOG,LOG_ERR, "Device ID 0x%02hhx does not match PDA panel, please check config!\n", _aqconfig_.device_id);
@@ -1313,6 +1327,9 @@ int startup(char *self, char *cfgFile)
LOG(AQUA_LOG,LOG_NOTICE, "Read SWG direct = %s\n", bool2text(READ_RSDEV_SWG));
LOG(AQUA_LOG,LOG_NOTICE, "Read ePump direct = %s\n", bool2text(READ_RSDEV_ePUMP));
LOG(AQUA_LOG,LOG_NOTICE, "Read vsfPump direct = %s\n", bool2text(READ_RSDEV_vsfPUMP));
+ LOG(AQUA_LOG,LOG_NOTICE, "Read JXi heater direct = %s\n", bool2text(READ_RSDEV_JXI));
+ LOG(AQUA_LOG,LOG_NOTICE, "Read LX heater direct = %s\n", bool2text(READ_RSDEV_LX));
+ LOG(AQUA_LOG,LOG_NOTICE, "Read Chem Feeder direct = %s\n", bool2text(READ_RSDEV_CHEM));
if (READ_RSDEV_SWG && _aqconfig_.swg_zero_ignore != DEFAULT_SWG_ZERO_IGNORE_COUNT)
LOG(AQUA_LOG,LOG_NOTICE, "Ignore SWG 0 msg count = %d\n", _aqconfig_.swg_zero_ignore);
@@ -1507,6 +1524,7 @@ void main_loop()
int blank_read_reconnect = MAX_ZERO_READ_BEFORE_RECONNECT_BLOCKING; // Will get reset if non blocking
sprintf(_aqualink_data.last_display_message, "%s", "Connecting to Control Panel");
+ _aqualink_data.is_display_message_programming = false;
//_aqualink_data.simulate_panel = false;
_aqualink_data.active_thread.thread_id = 0;
_aqualink_data.air_temp = TEMP_UNKNOWN;
@@ -1519,6 +1537,7 @@ void main_loop()
_aqualink_data.swg_percent = TEMP_UNKNOWN;
_aqualink_data.swg_ppm = TEMP_UNKNOWN;
_aqualink_data.ar_swg_device_status = SWG_STATUS_UNKNOWN;
+ _aqualink_data.heater_err_status = NUL; // 0x00 is no error
_aqualink_data.swg_led_state = LED_S_UNKNOWN;
_aqualink_data.swg_delayed_percent = TEMP_UNKNOWN;
_aqualink_data.temp_units = UNKNOWN;
@@ -1530,6 +1549,8 @@ void main_loop()
_aqualink_data.orp = TEMP_UNKNOWN;
_aqualink_data.simulator_id = NUL;
_aqualink_data.simulator_active = SIM_NONE;
+ _aqualink_data.boost_duration = 0;
+ _aqualink_data.boost = false;
pthread_mutex_init(&_aqualink_data.active_thread.thread_mutex, NULL);
pthread_cond_init(&_aqualink_data.active_thread.thread_cond, NULL);
@@ -1547,6 +1568,11 @@ void main_loop()
_aqualink_data.swg_ppm = 0;
}
+ if (_aqconfig_.force_chem_feeder == true) {
+ _aqualink_data.ph = 0;
+ _aqualink_data.orp = 0;
+ }
+
signal(SIGINT, intHandler);
signal(SIGTERM, intHandler);
signal(SIGQUIT, intHandler);
@@ -1881,7 +1907,15 @@ void main_loop()
#ifdef AQ_PDA
if (isPDA_PANEL) {
// If we are in simulator mode, the sim has already send the ack
- if (_aqualink_data.simulator_active == SIM_NONE) {
+ if (isPDA_IAQT) {
+ //printf("****PDA IAQT Code\n");
+ _aqualink_data.updated = process_iaqtouch_packet(packet_buffer, packet_length, &_aqualink_data);
+ _aqualink_data.updated = true; // FORCE UPDATE SINCE THIS IS NOT WORKING YET
+ caculate_ack_packet(rs_fd, packet_buffer, IAQTOUCH);
+ if (checkAqualinkTime() == false) // Need to do this better.
+ {aq_programmer(AQ_SET_TIME, NULL, &_aqualink_data);}
+ }
+ else /*if (_aqualink_data.simulator_active == SIM_NONE)*/ {
caculate_ack_packet(rs_fd, packet_buffer, AQUAPDA);
}
}
diff --git a/color_lights.c b/color_lights.c
index 262d7885..4f8ef5f0 100644
--- a/color_lights.c
+++ b/color_lights.c
@@ -6,6 +6,7 @@
//#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] =
{
@@ -50,9 +51,9 @@ const char *_color_light_options[NUMBER_LIGHT_COLOR_TYPES][LIGHT_COLOR_OPTIONS]
},
{ // Color Logic
"Voodoo Lounge",
- "Blue Sea",
- "Royal Blue",
- "Afternoon Skies",
+ "Deep Blue Sea",
+ //"Royal Blue",
+ "Afternoon Skies", // 'Afternoon Sky' on allbutton, Skies on iaqtouch
//"Aqua Green",
"Emerald",
"Sangria",
@@ -91,8 +92,15 @@ const char *_color_light_options[NUMBER_LIGHT_COLOR_TYPES][LIGHT_COLOR_OPTIONS]
};
-const char *light_mode_name(clight_type type, int index)
+const char *light_mode_name(clight_type type, int index, emulation_type protocol)
{
+ // 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];
}
diff --git a/color_lights.h b/color_lights.h
index f96981bb..11bc4413 100644
--- a/color_lights.h
+++ b/color_lights.h
@@ -3,6 +3,7 @@
#define COLOR_LIGHTS_H_
#include "aqualink.h"
+#include "aq_programmer.h"
#define LIGHT_COLOR_NAME 16
#define LIGHT_COLOR_OPTIONS 17
@@ -19,7 +20,8 @@ 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 *light_mode_name(clight_type type, int index, emulation_type protocol);
int build_color_lights_js(struct aqualinkdata *aqdata, char* buffer, int size);
diff --git a/config.c b/config.c
index 54c6c9b7..8c1cfe2f 100644
--- a/config.c
+++ b/config.c
@@ -92,6 +92,7 @@ void init_parameters (struct aqconfig * parms)
parms->mqtt_dz_sub_topic = DEFAULT_MQTT_DZ_OUT;
parms->mqtt_dz_pub_topic = DEFAULT_MQTT_DZ_IN;
+ parms->mqtt_hass_discover_topic = DEFAULT_HASS_DISCOVER;
parms->mqtt_aq_topic = DEFAULT_MQTT_AQ_TP;
parms->mqtt_server = DEFAULT_MQTT_SERVER;
parms->mqtt_user = DEFAULT_MQTT_USER;
@@ -129,6 +130,7 @@ void init_parameters (struct aqconfig * parms)
parms->force_swg = false;
parms->force_ps_setpoints = false;
parms->force_frzprotect_setpoints = false;
+ parms->force_chem_feeder = false;
//parms->swg_pool_and_spa = false;
parms->swg_zero_ignore = DEFAULT_SWG_ZERO_IGNORE_COUNT;
parms->display_warnings_web = false;
@@ -442,6 +444,15 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) {
} else if (strncasecmp(param, "mqtt_dz_pub_topic", 17) == 0) {
_aqconfig_.mqtt_dz_pub_topic = cleanalloc(value);
rtn=true;
+ } else if (strncasecmp(param, "mqtt_hassio_discover_topic", 26) == 0) {
+ _aqconfig_.mqtt_hass_discover_topic = cleanalloc(value);
+ /* It might also make sence to also set these to true. Since aqualinkd does not know the state at the time discover topics are published.
+ _aqconfig_.force_swg;
+ _aqconfig_.force_ps_setpoints;
+ _aqconfig_.force_frzprotect_setpoints;
+ force_chem_feeder
+ */
+ rtn=true;
} else if (strncasecmp(param, "mqtt_aq_topic", 13) == 0) {
_aqconfig_.mqtt_aq_topic = cleanalloc(value);
rtn=true;
@@ -552,11 +563,23 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) {
else
_aqconfig_.read_RS485_devmask &= ~READ_RS485_PEN_PUMP;
rtn=true;
- } else if (strncasecmp (param, "read_RS485_LXi", 14) == 0) {
+ } else if (strncasecmp (param, "read_RS485_JXi", 14) == 0) {
+ if (text2bool(value))
+ _aqconfig_.read_RS485_devmask |= READ_RS485_JAN_JXI;
+ else
+ _aqconfig_.read_RS485_devmask &= ~READ_RS485_JAN_JXI;
+ rtn=true;
+ } else if (strncasecmp (param, "read_RS485_LX", 14) == 0) {
if (text2bool(value))
- _aqconfig_.read_RS485_devmask |= READ_RS485_JAN_LXI;
+ _aqconfig_.read_RS485_devmask |= READ_RS485_JAN_LX;
else
- _aqconfig_.read_RS485_devmask &= ~READ_RS485_JAN_LXI;
+ _aqconfig_.read_RS485_devmask &= ~READ_RS485_JAN_LX;
+ rtn=true;
+ } else if (strncasecmp (param, "read_RS485_Chem", 14) == 0) {
+ if (text2bool(value))
+ _aqconfig_.read_RS485_devmask |= READ_RS485_JAN_CHEM;
+ else
+ _aqconfig_.read_RS485_devmask &= ~READ_RS485_JAN_CHEM;
rtn=true;
} else if (strncasecmp (param, "use_panel_aux_labels", 20) == 0) {
_aqconfig_.use_panel_aux_labels = text2bool(value);
@@ -570,6 +593,9 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) {
} else if (strncasecmp (param, "force_frzprotect_setpoints", 26) == 0) {
_aqconfig_.force_frzprotect_setpoints = text2bool(value);
rtn=true;
+ } else if (strncasecmp (param, "force_chem_feeder", 17) == 0) {
+ _aqconfig_.force_chem_feeder = text2bool(value);
+ rtn=true;
} else if (strncasecmp (param, "debug_RSProtocol_bytes", 22) == 0) {
_aqconfig_.log_raw_bytes = text2bool(value);
rtn=true;
diff --git a/config.h b/config.h
index c28cb6ec..65db5988 100644
--- a/config.h
+++ b/config.h
@@ -15,6 +15,7 @@
#define DEFAULT_DEVICE_ID "0x0a"
#define DEFAULT_MQTT_DZ_IN NULL
#define DEFAULT_MQTT_DZ_OUT NULL
+#define DEFAULT_HASS_DISCOVER NULL
#define DEFAULT_MQTT_AQ_TP NULL
#define DEFAULT_MQTT_SERVER NULL
#define DEFAULT_MQTT_USER NULL
@@ -27,8 +28,9 @@
#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_LXI (1 << 3) // Jandy LX & LXi heater
-//#define READ_RS485_JAN_LXI (1 << 4) // Jandy LXi heater
+#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
struct aqconfig
{
@@ -51,6 +53,7 @@ struct aqconfig
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;
@@ -79,6 +82,7 @@ struct aqconfig
bool force_swg;
bool force_ps_setpoints;
bool force_frzprotect_setpoints;
+ bool force_chem_feeder;
int swg_zero_ignore;
bool display_warnings_web;
bool log_protocol_packets; // Read & Write as packets
@@ -112,8 +116,11 @@ struct aqconfig _aqconfig_;
#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_LXI ((_aqconfig_.read_RS485_devmask & READ_RS485_JAN_LXI) == READ_RS485_JAN_LXI)
+#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 isPDA_IAQT (_aqconfig_.device_id == 0x33)
//#define isPDA ((_aqconfig_.paneltype_mask & RSP_PDA) == RSP_PDA)
diff --git a/devices_jandy.c b/devices_jandy.c
index 7bc52851..27d967f2 100644
--- a/devices_jandy.c
+++ b/devices_jandy.c
@@ -39,6 +39,7 @@ bool processJandyPacket(unsigned char *packet_buffer, int packet_length, struct
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)
{
@@ -50,9 +51,17 @@ bool processJandyPacket(unsigned char *packet_buffer, int packet_length, struct
{
rtn = processPacketFromJandyPump(packet_buffer, packet_length, aqdata, previous_packet_to);
}
- else if (interestedInNextAck == DRS_LXI)
+ else if (interestedInNextAck == DRS_JXI)
+ {
+ rtn = processPacketFromJandyJXiHeater(packet_buffer, packet_length, aqdata, previous_packet_to);
+ }
+ else if (interestedInNextAck == DRS_LX)
{
- rtn = processPacketFromJandyHeater(packet_buffer, packet_length, aqdata, previous_packet_to);
+ rtn = processPacketFromJandyLXHeater(packet_buffer, packet_length, aqdata, previous_packet_to);
+ }
+ else if (interestedInNextAck == DRS_CHEM)
+ {
+ rtn = processPacketFromJandyChemFeeder(packet_buffer, packet_length, aqdata, previous_packet_to);
}
interestedInNextAck = DRS_NONE;
previous_packet_to = NUL;
@@ -71,7 +80,7 @@ bool processJandyPacket(unsigned char *packet_buffer, int packet_length, struct
interestedInNextAck = DRS_NONE;
previous_packet_to = NUL;
}
- else if (READ_RSDEV_SWG && packet_buffer[PKT_DEST] == SWG_DEV_ID)
+ else if (READ_RSDEV_SWG && packet_buffer[PKT_DEST] >= JANDY_DEC_SWG_MIN && packet_buffer[PKT_DEST] <= JANDY_DEC_SWG_MAX)
{
interestedInNextAck = DRS_SWG;
rtn = processPacketToSWG(packet_buffer, packet_length, aqdata, _aqconfig_.swg_zero_ignore);
@@ -83,11 +92,22 @@ bool processJandyPacket(unsigned char *packet_buffer, int packet_length, struct
rtn = processPacketToJandyPump(packet_buffer, packet_length, aqdata);
previous_packet_to = packet_buffer[PKT_DEST];
}
- else if (READ_RSDEV_LXI && ( (packet_buffer[PKT_DEST] >= JANDY_DEC_LX_MIN && packet_buffer[PKT_DEST] <= JANDY_DEC_LX_MAX)
- || (packet_buffer[PKT_DEST] >= JANDY_DEC_LXI_MIN && packet_buffer[PKT_DEST] <= JANDY_DEC_LXI_MAX)))
+ else if (READ_RSDEV_JXI && packet_buffer[PKT_DEST] >= JANDY_DEC_JXI_MIN && packet_buffer[PKT_DEST] <= JANDY_DEC_JXI_MAX)
+ {
+ interestedInNextAck = DRS_JXI;
+ rtn = processPacketToJandyJXiHeater(packet_buffer, packet_length, aqdata);
+ previous_packet_to = packet_buffer[PKT_DEST];
+ }
+ else if (READ_RSDEV_LX && packet_buffer[PKT_DEST] >= JANDY_DEC_LX_MIN && packet_buffer[PKT_DEST] <= JANDY_DEC_LX_MAX)
+ {
+ interestedInNextAck = DRS_LX;
+ rtn = processPacketToJandyLXHeater(packet_buffer, packet_length, aqdata);
+ previous_packet_to = packet_buffer[PKT_DEST];
+ }
+ else if (READ_RSDEV_CHEM && packet_buffer[PKT_DEST] >= JANDY_DEC_CHEM_MIN && packet_buffer[PKT_DEST] <= JANDY_DEC_CHEM_MAX)
{
- interestedInNextAck = DRS_LXI;
- rtn = processPacketToJandyHeater(packet_buffer, packet_length, aqdata);
+ interestedInNextAck = DRS_CHEM;
+ rtn = processPacketToJandyChemFeeder(packet_buffer, packet_length, aqdata);
previous_packet_to = packet_buffer[PKT_DEST];
}
else
@@ -109,13 +129,13 @@ bool processPacketToSWG(unsigned char *packet, int packet_length, struct aqualin
static int swg_zero_cnt = 0;
bool changedAnything = false;
- if (getLogLevel(DJAN_LOG) == LOG_DEBUG) {
+ // Only log if we are jandy debug move and not serial (otherwise it'll print twice)
+ if (getLogLevel(DJAN_LOG) == LOG_DEBUG && getLogLevel(RSSD_LOG) < LOG_DEBUG ) {
char buff[1024];
- beautifyPacket(buff, packet, packet_length, false);
- LOG(DJAN_LOG,LOG_DEBUG, "%s", buff);
+ beautifyPacket(buff, packet, packet_length, true);
+ LOG(DJAN_LOG,LOG_DEBUG, "To SWG: %s", buff);
}
-
// 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|
@@ -161,10 +181,11 @@ bool processPacketFromSWG(unsigned char *packet, int packet_length, struct aqual
bool changedAnything = false;
_swg_noreply_cnt = 0;
- if (getLogLevel(DJAN_LOG) == LOG_DEBUG) {
+ // Only log if we are jandy debug move and not serial (otherwise it'll print twice)
+ if (getLogLevel(DJAN_LOG) == LOG_DEBUG && getLogLevel(RSSD_LOG) < LOG_DEBUG ) {
char buff[1024];
beautifyPacket(buff, packet, packet_length, true);
- LOG(DJAN_LOG,LOG_DEBUG, "%s", buff);
+ LOG(DJAN_LOG,LOG_DEBUG, "From SWG: %s", buff);
}
if (packet[PKT_CMD] == CMD_PPM) {
@@ -213,6 +234,7 @@ bool isSWGDeviceErrorState(unsigned char status)
status == SWG_STATUS_LOW_TEMP ||
status == SWG_STATUS_HIGH_CURRENT ||
status == SWG_STATUS_NO_FLOW)
+ // Maybe add CLEAN_CELL and GENFAULT here
return true;
else
return false;
@@ -220,6 +242,17 @@ bool isSWGDeviceErrorState(unsigned char status)
void setSWGdeviceStatus(struct aqualinkdata *aqdata, emulation_type requester, unsigned char status) {
static unsigned char last_status = SWG_STATUS_UNKNOWN;
+ /* This is only needed for DEBUG
+ static bool haveSeenRSSWG = false;
+
+ if (requester == JANDY_DEVICE) {
+ haveSeenRSSWG = true;
+ }
+ */
+ // If we are reading state directly from RS458, then ignore everything else.
+ if ( READ_RSDEV_SWG && requester != JANDY_DEVICE ) {
+ return;
+ }
if ((aqdata->ar_swg_device_status == status) || (last_status == status)) {
//LOG(DJAN_LOG, LOG_DEBUG, "Set SWG device state to '0x%02hhx', request from %d\n", aqdata->ar_swg_device_status, requester);
@@ -227,10 +260,15 @@ void setSWGdeviceStatus(struct aqualinkdata *aqdata, emulation_type requester, u
}
last_status = status;
- // If we get (ALLBUTTON, SWG_STATUS_CHECK_PCB), it sends this for many status, like clean cell.
+ // If we get (ALLBUTTON, SWG_STATUS_CHECK_PCB // GENFAULT), 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 ) {
+ // Need to rethink this. Use general fault only if we are not reading SWG status direct from device
+ //if ( READ_RSDEV_SWG && requester == ALLBUTTON && status == SWG_STATUS_GENFAULT ) {
+
+ // SWG_STATUS_GENFAULT is shown on panels for many reasons, if we are NOT reading the status directly from the SWG
+ // then use it, otherwise disguard it as we will have a better status
+ if (requester == ALLBUTTON && status == SWG_STATUS_GENFAULT ) {
if (aqdata->ar_swg_device_status > SWG_STATUS_ON &&
aqdata->ar_swg_device_status < SWG_STATUS_TURNING_OFF) {
LOG(DJAN_LOG, LOG_DEBUG, "Ignoring set SWG device state to '0x%02hhx', request from %d\n", aqdata->ar_swg_device_status, requester);
@@ -250,6 +288,7 @@ void setSWGdeviceStatus(struct aqualinkdata *aqdata, emulation_type requester, u
case SWG_STATUS_LOW_VOLTS:
case SWG_STATUS_LOW_TEMP:
case SWG_STATUS_CHECK_PCB:
+ case SWG_STATUS_GENFAULT:
aqdata->ar_swg_device_status = status;
aqdata->swg_led_state = isSWGDeviceErrorState(status)?ENABLE:ON;
break;
@@ -393,6 +432,9 @@ aqledstate get_swg_led_state(struct aqualinkdata *aqdata)
case SWG_STATUS_OFF: // THIS IS OUR OFF STATUS, NOT AQUAPURE
return OFF;
break;
+ case SWG_STATUS_GENFAULT:
+ return ENABLE;
+ break;
default:
return (aqdata->swg_percent > 0?ON:ENABLE);
break;
@@ -466,6 +508,11 @@ void get_swg_status_mqtt(struct aqualinkdata *aqdata, char *message, int *status
sprintf(message, "AQUAPURE OFF");
*dzalert = 0;
break;
+ case SWG_STATUS_GENFAULT:
+ *status = (aqdata->swg_percent > 0?SWG_ON:SWG_OFF);
+ sprintf(message, "AQUAPURE GENERAL FAULT");
+ *dzalert = 2;
+ break;
default:
*status = (aqdata->swg_percent > 0?SWG_ON:SWG_OFF);
sprintf(message, "AQUAPURE UNKNOWN STATUS");
@@ -498,6 +545,12 @@ bool processPacketToJandyPump(unsigned char *packet_buffer, int packet_length, s
Type 0x1F and cmd 0x45 is RPM = 39 * (256) + 96 / 4 = 2520 or Byte 8 * 265 + Byte 7 / 4
*/
+ // Only log if we are jandy debug move and not serial (otherwise it'll print twice)
+ if (getLogLevel(DJAN_LOG) == LOG_DEBUG && getLogLevel(RSSD_LOG) < LOG_DEBUG ) {
+ char msg[1000];
+ beautifyPacket(msg, packet_buffer, packet_length, true);
+ LOG(DJAN_LOG, LOG_DEBUG, "To ePump: %s\n", msg);
+ }
// If type 0x45 and 0x44 set to interested in next command.
if (packet_buffer[3] == CMD_EPUMP_RPM) {
@@ -506,11 +559,8 @@ bool processPacketToJandyPump(unsigned char *packet_buffer, int packet_length, s
} else if (packet_buffer[3] == CMD_EPUMP_WATTS) {
LOG(DJAN_LOG, LOG_DEBUG, "ControlPanel request Pump ID 0x%02hhx get watts\n",packet_buffer[PKT_DEST]);
}
-
- if (getLogLevel(DJAN_LOG) >= LOG_DEBUG) {
- char msg[1000];
- beautifyPacket(msg, packet_buffer, packet_length, true);
- LOG(DJAN_LOG, LOG_DEBUG, "To ePump: %s\n", msg);
+
+ if (getLogLevel(DJAN_LOG) == LOG_DEBUG) {
//find pump for message
for (int i=0; i < aqdata->num_pumps; i++) {
if (aqdata->pumps[i].pumpID == packet_buffer[PKT_DEST]) {
@@ -527,6 +577,14 @@ bool processPacketFromJandyPump(unsigned char *packet_buffer, int packet_length,
{
bool found=false;
+ // Only log if we are jandy debug move and not serial (otherwise it'll print twice)
+ if (getLogLevel(DJAN_LOG) == LOG_DEBUG && getLogLevel(RSSD_LOG) < LOG_DEBUG ) {
+ 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);
+ }
+
if (packet_buffer[3] == CMD_EPUMP_STATUS && packet_buffer[4] == CMD_EPUMP_RPM) {
for (int i = 0; i < MAX_PUMPS; i++) {
if ( aqdata->pumps[i].prclType == JANDY && aqdata->pumps[i].pumpID == previous_packet_to ) {
@@ -552,33 +610,219 @@ bool processPacketFromJandyPump(unsigned char *packet_buffer, int packet_length,
LOG(DJAN_LOG, LOG_NOTICE, "Jandy Pump found at ID 0x%02hhx with WATTS %d, but not configured, information ignored!\n",previous_packet_to, (packet_buffer[EP_HI_B_WAT] * 256) + packet_buffer[EP_LO_B_WAT]);
}
- if (getLogLevel(DJAN_LOG) >= LOG_DEBUG) {
+
+ return false;
+}
+
+void processMissingAckPacketFromJandyPump(unsigned char destination, struct aqualinkdata *aqdata)
+{
+ // Do nothing for the moment.
+ return;
+}
+
+
+
+
+
+
+bool processPacketToJandyJXiHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata)
+{
+
+ if (getLogLevel(DJAN_LOG) == LOG_DEBUG && getLogLevel(RSSD_LOG) < LOG_DEBUG ) {
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);
+ LOG(DJAN_LOG, LOG_DEBUG, "To JXi: %s\n", msg);
+ }
+
+ if (packet_buffer[3] != CMD_JXI_PING) {
+ // Not sure what this message is, so ignore
+ // Maybe print a messsage.
+ return false;
}
+ /*
+ Below counfing first as bit 0
+ 4th bit 0x00 no pump on (nothing)
+ 0x10 seems to be JXi came online. nothing more
+ 0x11 (pool mode)
+ 0x12 (spa mode)
+ 0x19 heat pool
+ 0x1a heat spa
+ 5th bit 0x55 = 85 deg. (current pool setpoint)
+ 6th bit 0x66 = 102 deg. (current spa setpoint)
+ 7th bit 0x4f = current water temp 79 (0xFF is off / 255)
+ */
+
+ if (packet_buffer[5] != aqdata->pool_htr_set_point) {
+ LOG(DJAN_LOG, LOG_DEBUG, "JXi pool setpoint %d, Pool heater sp %d (changing to LXi)\n", packet_buffer[5], aqdata->pool_htr_set_point);
+ aqdata->pool_htr_set_point = packet_buffer[5];
+ }
+
+ if (packet_buffer[6] != aqdata->spa_htr_set_point) {
+ LOG(DJAN_LOG, LOG_DEBUG, "JXi spa setpoint %d, Spa heater sp %d (changing to LXi)\n", packet_buffer[6], aqdata->spa_htr_set_point);
+ aqdata->spa_htr_set_point = packet_buffer[6];
+ }
+
+ if (packet_buffer[7] != 0xff && packet_buffer[4] != 0x00) {
+ if (packet_buffer[4] == 0x11 || packet_buffer[4] == 0x19) {
+ if (aqdata->pool_temp != packet_buffer[7]) {
+ LOG(DJAN_LOG, LOG_DEBUG, "JXi pool water temp %d, pool water temp %d (changing to LXi)\n", packet_buffer[7], aqdata->pool_temp);
+ aqdata->pool_temp = packet_buffer[7];
+ }
+ } else if (packet_buffer[4] == 0x12 || packet_buffer[4] == 0x1a) {
+ if (aqdata->spa_temp != packet_buffer[7]) {
+ LOG(DJAN_LOG, LOG_DEBUG, "JXi spa water temp %d, spa water temp %d (changing to LXi)\n", packet_buffer[7], aqdata->spa_temp);
+ aqdata->spa_temp = packet_buffer[7];
+ }
+ }
+ }
+
+ switch (packet_buffer[4]) {
+ case 0x11: // Pool heat off or enabled
+ break;
+ case 0x12: // Pool Heat enabled or heating
+ break;
+ case 0x19: // Spa heat off or enabled
+ break;
+ case 0x1a: // Spa Hear Heat enabled or heating
+ break;
+ }
+
+ /*
+ char msg[1000];
+ int length = 0;
+
+ beautifyPacket(msg, packet_buffer, packet_length, true);
+ LOG(DJAN_LOG, LOG_INFO, "To JXi Heater: %s\n", msg);
+
+ length += sprintf(msg+length, "Last panel info ");
+
+ for (int i=0; i < aqdata->total_buttons; i++)
+ {
+ if ( strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0) {
+ length += sprintf(msg+length, ", Pool Heat LED=%d ",aqdata->aqbuttons[i].led->state);
+ }
+ if ( strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name) == 0) {
+ length += sprintf(msg+length, ", Spa Heat LED=%d ",aqdata->aqbuttons[i].led->state);
+ }
+ }
+
+ length += sprintf(msg+length, ", Pool SP=%d, Spa SP=%d",aqdata->pool_htr_set_point, aqdata->spa_htr_set_point);
+ length += sprintf(msg+length, ", Pool temp=%d, Spa temp=%d",aqdata->pool_temp, aqdata->spa_temp);
+
+ LOG(DJAN_LOG, LOG_INFO, "%s\n", msg);
+
return false;
+ */
+
+ return true;
}
-void processMissingAckPacketFromJandyPump(unsigned char destination, struct aqualinkdata *aqdata)
+void getJandyHeaterError(struct aqualinkdata *aqdata, char *message)
{
- // Do nothing for the moment.
- return;
+ if (aqdata->heater_err_status == NUL) {
+ return;
+ }
+
+ int size = sprintf(message, "JXi Heater ");
+ getJandyHeaterErrorMQTT(aqdata, message+size);
}
+void getJandyHeaterErrorMQTT(struct aqualinkdata *aqdata, char *message)
+{
+ switch (aqdata->heater_err_status) {
+ case 0x00:
+ //sprintf(message, "");
+ break;
+ case 0x10:
+ sprintf(message, "FAULT HIGH LIMIT");
+ break;
+ case 0x02:
+ sprintf(message, "FAULT H20 SENSOR");
+ break;
+ case 0x08:
+ sprintf(message, "FAULT AUX MONITOR");
+ break;
+ default:
+ //
+ /* Error we haven't decoded yet
+ ?x?? check flow
+ 0x10 Fault high limit
+ ?x?? Fault High Flu temp
+ ?x?? Fault Check Igntion Control
+ 0x02 Fault Short H20 sensor (or Fault open water sensor)
+ ?x?? Pump fault
+ 0x08 AUX Monitor
+ */
+ sprintf(message, "FAULT 0x%02hhx",aqdata->heater_err_status);
+ break;
+ }
+}
+bool processPacketFromJandyJXiHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to)
+{
+ if (getLogLevel(DJAN_LOG) == LOG_DEBUG && getLogLevel(RSSD_LOG) < LOG_DEBUG ) {
+ 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 JXi: %s\n", msg);
+ }
+
+ if (packet_buffer[3] != CMD_JXI_STATUS) {
+ // Not sure what this message is, so ignore
+ // Maybe print a messsage.
+ return false;
+ }
+
+ // No error is 0x00, so blindly set it.
+ aqdata->heater_err_status = packet_buffer[6];
+ // Check if error first
+ if (packet_buffer[6] != 0x00) {
+
+ } else if (packet_buffer[4] == 0x00) {
+ // Not heating.
+ // Heater off or enabeled
+ } else if (packet_buffer[4] == 0x08) {
+ // Heating
+ // Heater on of enabled
+ }
+ /*
+ char msg[1000];
+ int length = 0;
+
+ beautifyPacket(msg, packet_buffer, packet_length, true);
+ LOG(DJAN_LOG, LOG_INFO, "From JXi Heater: %s\n", msg);
+
+ length += sprintf(msg+length, "Last panel info ");
+
+ for (int i=0; i < aqdata->total_buttons; i++)
+ {
+ if ( strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0) {
+ length += sprintf(msg+length, ", Pool Heat LED=%d ",aqdata->aqbuttons[i].led->state);
+ }
+ if ( strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name) == 0) {
+ length += sprintf(msg+length, ", Spa Heat LED=%d ",aqdata->aqbuttons[i].led->state);
+ }
+ }
+ length += sprintf(msg+length, ", Pool SP=%d, Spa SP=%d",aqdata->pool_htr_set_point, aqdata->spa_htr_set_point);
+ length += sprintf(msg+length, ", Pool temp=%d, Spa temp=%d",aqdata->pool_temp, aqdata->spa_temp);
+ LOG(DJAN_LOG, LOG_INFO, "%s\n", msg);
+ return false;
+ */
+ return true;
+}
-bool processPacketToJandyHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata)
+bool processPacketToJandyLXHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata)
{
+
char msg[1000];
int length = 0;
beautifyPacket(msg, packet_buffer, packet_length, true);
- LOG(DJAN_LOG, LOG_INFO, "To Heater: %s\n", msg);
+ LOG(DJAN_LOG, LOG_INFO, "To LX: %s\n", msg);
length += sprintf(msg+length, "Last panel info ");
@@ -598,15 +842,16 @@ bool processPacketToJandyHeater(unsigned char *packet_buffer, int packet_length,
LOG(DJAN_LOG, LOG_INFO, "%s\n", msg);
return false;
+
}
-bool processPacketFromJandyHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to)
+bool processPacketFromJandyLXHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to)
{
char msg[1000];
int length = 0;
beautifyPacket(msg, packet_buffer, packet_length, true);
- LOG(DJAN_LOG, LOG_INFO, "From Heater: %s\n", msg);
+ LOG(DJAN_LOG, LOG_INFO, "From LX: %s\n", msg);
length += sprintf(msg+length, "Last panel info ");
@@ -629,3 +874,91 @@ bool processPacketFromJandyHeater(unsigned char *packet_buffer, int packet_lengt
}
+bool processPacketToJandyChemFeeder(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata)
+{
+ char msg[1000];
+ int length = 0;
+
+ beautifyPacket(msg, packet_buffer, packet_length, true);
+ LOG(DJAN_LOG, LOG_INFO, "To Chem: %s\n", msg);
+
+ length += sprintf(msg+length, "Last panel info ");
+
+ length += sprintf(msg+length, ", pH=%f, ORP=%d",aqdata->ph, aqdata->orp);
+
+ LOG(DJAN_LOG, LOG_INFO, "%s\n", msg);
+
+ return false;
+}
+
+bool processPacketFromJandyChemFeeder(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to){
+ char msg[1000];
+ int length = 0;
+
+ beautifyPacket(msg, packet_buffer, packet_length, true);
+ LOG(DJAN_LOG, LOG_INFO, "From Chem: %s\n", msg);
+
+ length += sprintf(msg+length, "Last panel info ");
+
+ length += sprintf(msg+length, ", pH=%f, ORP=%d",aqdata->ph, aqdata->orp);
+
+ LOG(DJAN_LOG, LOG_INFO, "%s\n", msg);
+
+ return false;
+}
+
+
+
+
+/*
+
+// JXi Heater
+
+// Normal ping and return
+5th bit 0x00 no pump on (nothing)
+ 0x10 seems to be JXi came online. nothing more
+ 0x11 (pool mode)
+ 0x12 (spa mode)
+ 0x19 heat pool
+ 0x1a heat spa
+6th bit 0x55 = 85 deg. (current pool setpoint)
+7th bit 0x66 = 102 deg. (current spa setpoint)
+8th bit 0x4f = current water temp 79 (0xFF is off / 255)
+
+Jandy To 0x68 of type Unknown '0x0c' | HEX: 0x10|0x02|0x68|0x0c|0x11|0x55|0x66|0x4f|0xa1|0x10|0x03|
+Jandy From 0x68 of type Unknown '0x0d' | HEX: 0x10|0x02|0x00|0x0d|0x00|0x00|0x00|0x1f|0x10|0x03|
+
+Request to turn on 85
+5th bit 0x19 looks like turn on
+6th bit 0x55 = 85 deg.
+7th bit 0x4f = current temp 79
+Jandy To 0x68 of type Unknown '0x0c' | HEX: 0x10|0x02|0x68|0x0c|0x19|0x55|0x66|0x4f|0xa9|0x10|0x03|
+Jandy From 0x68 of type Unknown '0x0d' | HEX: 0x10|0x02|0x00|0x0d|0x08|0x00|0x00|0x27|0x10|0x03|
+
+Request to turn on 90
+5th bit 0x19 looks like turn on
+6th bit 0x5a = 90 deg.
+Jandy To 0x68 of type Unknown '0x0c' | HEX: 0x10|0x02|0x68|0x0c|0x19|0x5a|0x66|0x4f|0xae|0x10|0x03|
+Jandy From 0x68 of type Unknown '0x0d' | HEX: 0x10|0x02|0x00|0x0d|0x08|0x00|0x00|0x27|0x10|0x03|
+
+Request to turn off (standard ping) // return had hi limit error in it
+Jandy To 0x68 of type Unknown '0x0c' | HEX: 0x10|0x02|0x68|0x0c|0x11|0x55|0x66|0x4f|0xa1|0x10|0x03|
+Jandy From 0x68 of type Unknown '0x0d' | HEX: 0x10|0x02|0x00|0x0d|0x00|0x00|0x10|0x2f|0x10|0x03|
+
+Returns
+
+5th bit is type 0x00 nothing (or enabeled) - 0x08 looks like heat
+Hi limit error return
+7th bit 0x10 looks like the error
+Jandy To 0x68 of type Unknown '0x0c' | HEX: 0x10|0x02|0x68|0x0c|0x19|0x5a|0x66|0x4f|0xae|0x10|0x03|
+Jandy From 0x68 of type Unknown '0x0d' | HEX: 0x10|0x02|0x00|0x0d|0x08|0x00|0x10|0x37|0x10|0x03|
+
+Errors are ->
+check flow
+Fault high limit -> 0x10
+Fault High Flu temp
+Fault Check Igntion Control
+Fault Short H20 sensor (or Fault open water sensor) -> 0x02
+Pump fault
+AUX Monitor -> 0x08
+*/
\ No newline at end of file
diff --git a/devices_jandy.h b/devices_jandy.h
index 161cad0c..aaf427ed 100644
--- a/devices_jandy.h
+++ b/devices_jandy.h
@@ -14,8 +14,13 @@ bool processPacketFromJandyPump(unsigned char *packet_buffer, int packet_length,
void processMissingAckPacketFromSWG(unsigned char destination, struct aqualinkdata *aqdata);
void processMissingAckPacketFromJandyPump(unsigned char destination, struct aqualinkdata *aqdata);
-bool processPacketFromJandyHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to );
-bool processPacketToJandyHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
+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);
void get_swg_status_mqtt(struct aqualinkdata *aqdata, char *message, int *status, int *dzalert);
aqledstate get_swg_led_state(struct aqualinkdata *aqdata);
@@ -27,4 +32,7 @@ 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);
+
#endif // AQUAPURE_H_
\ No newline at end of file
diff --git a/devices_pentair.c b/devices_pentair.c
index f7abac00..4ab5e21a 100644
--- a/devices_pentair.c
+++ b/devices_pentair.c
@@ -24,11 +24,20 @@
#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, 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
diff --git a/extras/HASSIO.png b/extras/HASSIO.png
new file mode 100644
index 00000000..49d1bdc6
Binary files /dev/null and b/extras/HASSIO.png differ
diff --git a/extras/aqualinkd-docker.cmd b/extras/aqualinkd-docker.cmd
new file mode 100755
index 00000000..0946a25c
--- /dev/null
+++ b/extras/aqualinkd-docker.cmd
@@ -0,0 +1,47 @@
+#!/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
+
+
+
+# Start cron
+service cron start
+
+# Start AqualinkD not in daemon mode
+/usr/local/bin/aqualinkd -d -c $AQUA_CONF
+
diff --git a/hassio.c b/hassio.c
new file mode 100644
index 00000000..93a5186f
--- /dev/null
+++ b/hassio.c
@@ -0,0 +1,444 @@
+
+#include
+#include
+#include
+
+#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 "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 "\"," \
+ " \"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\","
+ "\"min_temp\": 36,"
+ "\"max_temp\": 104,"
+ "\"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\","
+ "\"temperature_state_template\": \"{{ value_json }}\","
+ "\"qos\": 1,"
+ "\"retain\": false"
+"}";
+
+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\","
+ "\"min_temp\": 34,"
+ "\"max_temp\": 42,"
+ "\"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\","
+ "\"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"
+"}";
+
+// 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\","
+ "\"qos\": 1,"
+ "\"retain\": false"
+"}";
+
+const char *HASSIO_TEMP_SENSOR_DISCOVER = "{"
+ "\"device\": {" HASS_DEVICE "},"
+ "\"availability\": {" HASS_AVAILABILITY "},"
+ "\"type\": \"sensor\","
+ "\"unique_id\": \"aqualinkd_%s\","
+ "\"name\": \"%s Temp\","
+ "\"state_topic\": \"%s/%s\","
+ "\"value_template\": \"{{ value_json }}\","
+ "\"unit_of_measurement\": \"°F\","
+ "\"device_class\": \"temperature\","
+ "\"icon\": \"%s\""
+"}";
+
+const char *HASSIO_SENSOR_DISCOVER = "{"
+ "\"device\": {" HASS_DEVICE "},"
+ "\"availability\": {" HASS_AVAILABILITY "},"
+ "\"type\": \"sensor\","
+ "\"unique_id\": \"aqualinkd_%s\","
+ "\"name\": \"%s\","
+ "\"state_topic\": \"%s/%s\","
+ "\"value_template\": \"{{ value_json }}\","
+ "\"unit_of_measurement\": \"%s\","
+ "\"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\","
+ "\"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_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\""
+"}";
+
+/*
+char *HASSIO_TEXT_DISCOVER = "{"
+ "\"device\": {" HASS_DEVICE "},"
+ "\"type\": \"text\","
+ "\"unique_id\": \"aqualinkd_%s\","
+ "\"name\": \"%s\","
+ "\"command_topic\": \"junk/null\","
+ "\"state_topic\": \"%s/%s\""
+"}";
+*/
+/*
+char *HASSIO_SERVICE_MODE_DISCOVER = "{"
+ "\"type\": \"sensor\","
+ "\"unique_id\": \"aqualinkd_Service_Mode\","
+ "\"name\": \"Service Mode\","
+ "\"state_topic\": \"aqualinkd/Service_Mode\","
+ "\"value_template\": \"{% set values = { '0':'off', '1':'on'} %}{{ values[value] if value in values.keys() else 'off' }}\","
+ "\"icon\": \"mdi:account-wrench\""
+"}";
+*/
+
+/*
+Others to add
+
+{
+ "type": "text",
+ "unique_id": "display",
+ "name": "AqualinkD Display Message",
+ "command_topic": "junk/null",
+ "state_topic": "aqualinkd/Display_Message"
+}
+
+{
+ "type": "sensor",
+ "unique_id": "Service_Mode",
+ "name": "Service Mode",
+ "state_topic": "aqualinkd/Service_Mode",
+ "value_template": "{% set values = { '0':'off', '1':'on'} %}{{ values[value] if value in values.keys() else 'off' }}",
+ "icon": "mdi:account-wrench"
+}
+
+mdi:pump
+
+mdi:water-outline // orph, ph, ppm, swg
+
+mdi:water-thermometer // water
+mdi:thermometer // air
+
+mdi:account-wrench // server
+*/
+
+
+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];
+
+ 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++)
+ { // Heaters
+ if ( (strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0 && (_aqconfig_.force_ps_setpoints || aqdata->pool_htr_set_point != TEMP_UNKNOWN)) ||
+ (strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name)==0 && (_aqconfig_.force_ps_setpoints || aqdata->spa_htr_set_point != TEMP_UNKNOWN)) ) {
+ sprintf(msg,HASSIO_CLIMATE_DISCOVER,
+ _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);
+ sprintf(topic, "%s/climate/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, aqdata->aqbuttons[i].name);
+ send_mqtt(nc, topic, msg);
+ } else if (strcmp("NONE",aqdata->aqbuttons[i].label) != 0 ) {
+ // 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,
+ _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);
+ sprintf(topic, "%s/switch/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, aqdata->aqbuttons[i].name);
+ send_mqtt(nc, topic, msg);
+ }
+ }
+
+ // Freezeprotect
+ if ( _aqconfig_.force_frzprotect_setpoints || (aqdata->frz_protect_set_point != TEMP_UNKNOWN && aqdata->air_temp != TEMP_UNKNOWN) ) {
+ sprintf(msg, HASSIO_FREEZE_PROTECT_DISCOVER,
+ _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
+ );
+ sprintf(topic, "%s/climate/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, FREEZE_PROTECT);
+ send_mqtt(nc, topic, msg);
+ }
+
+
+ // SWG
+ if ( aqdata->swg_percent != TEMP_UNKNOWN ) {
+
+ sprintf(msg, HASSIO_SWG_DISCOVER,
+ _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,
+ _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);
+ 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,_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,_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,_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);
+ /*
+ // SWG Display message (move to SWG area)
+ rsm_char_replace(idbuf, SWG_STATUS_MSG_TOPIC, "/", "_");
+ sprintf(msg, HASSIO_TEXT_SENSOR_DISCOVER,idbuf,"SWG Msg",_aqconfig_.mqtt_aq_topic,SWG_STATUS_MSG_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,_aqconfig_.mqtt_aq_topic,"Pool","Pool",_aqconfig_.mqtt_aq_topic,POOL_TEMP_TOPIC,"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,_aqconfig_.mqtt_aq_topic,"Spa","Spa",_aqconfig_.mqtt_aq_topic,SPA_TEMP_TOPIC,"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,_aqconfig_.mqtt_aq_topic,"Air","Air",_aqconfig_.mqtt_aq_topic,AIR_TEMP_TOPIC,"mdi:thermometer");
+ sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Air");
+ send_mqtt(nc, topic, msg);
+
+ // Pumps
+ for (i=0; i < aqdata->num_pumps; i++) {
+ int pn=i+1;
+ if (aqdata->pumps[i].pumpType==VFPUMP) {
+ // We have GPM info
+ sprintf(msg, HASSIO_PUMP_SENSOR_DISCOVER,
+ _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);
+ }
+ sprintf(msg, HASSIO_PUMP_SENSOR_DISCOVER,
+ _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,
+ _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 (_aqconfig_.force_chem_feeder || aqdata->ph != TEMP_UNKNOWN) {
+ rsm_char_replace(idbuf, CHEM_PH_TOPIC, "/", "_");
+ sprintf(msg, HASSIO_SENSOR_DISCOVER,_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 (_aqconfig_.force_chem_feeder || aqdata->orp != TEMP_UNKNOWN) {
+ rsm_char_replace(idbuf, CHEM_ORP_TOPIC, "/", "_");
+ sprintf(msg, HASSIO_SENSOR_DISCOVER,_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_ONOFF_SENSOR_DISCOVER,_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,_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);
+
+
+
+}
\ No newline at end of file
diff --git a/hassio.h b/hassio.h
new file mode 100644
index 00000000..ad9fe2f0
--- /dev/null
+++ b/hassio.h
@@ -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_
\ No newline at end of file
diff --git a/iaqtouch.c b/iaqtouch.c
index 7b17c31b..bb1a0354 100644
--- a/iaqtouch.c
+++ b/iaqtouch.c
@@ -16,6 +16,7 @@
#include
#include
+#include
#include "aq_serial.h"
#include "aqualink.h"
@@ -69,14 +70,15 @@ unsigned char _lastMsgType = 0x00;
//unsigned char _last_kick_type = -1;
int _deviceStatusLines = 0;
+char _homeStatus[IAQ_STATUS_PAGE_LINES][AQ_MSGLEN+1];
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];
+struct iaqt_page_button _homeButtons[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];
+// Need to cache these pages, as only get updates after initial load.
+struct iaqt_page_button _devicePageButtons[3][IAQ_PAGE_BUTTONS];
+struct iaqt_page_button _deviceSystemSetupButtons[3][IAQ_PAGE_BUTTONS];
unsigned char iaqtLastMsg()
{
@@ -110,6 +112,11 @@ unsigned char iaqtCurrentPage()
{
return _currentPage;
}
+unsigned char iaqtCurrentPageLoading()
+{
+ return _currentPageLoading;
+}
+
const char *iaqtGetMessageLine(int index) {
if (index < IAQ_STATUS_PAGE_LINES)
@@ -129,12 +136,20 @@ struct iaqt_page_button *iaqtFindButtonByIndex(int index) {
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;
+ if (_currentPage == IAQ_PAGE_DEVICES)
+ buttons = _devicePageButtons[0];
+ else if (_currentPage == IAQ_PAGE_DEVICES2)
+ buttons = _devicePageButtons[1];
+ else if (_currentPage == IAQ_PAGE_DEVICES3)
+ buttons = _devicePageButtons[2];
else if (_currentPage == IAQ_PAGE_SYSTEM_SETUP )
- buttons = _deviceSystemSetupButtons;
+ buttons = _deviceSystemSetupButtons[0];
+ else if (_currentPage == IAQ_PAGE_SYSTEM_SETUP2 )
+ buttons = _deviceSystemSetupButtons[1];
+ else if (_currentPage == IAQ_PAGE_SYSTEM_SETUP3 )
+ buttons = _deviceSystemSetupButtons[2];
+ else if (_currentPage == IAQ_PAGE_HOME )
+ buttons = _homeButtons;
else
buttons = _pageButtons;
@@ -145,16 +160,24 @@ struct iaqt_page_button *iaqtFindButtonByIndex(int index) {
return NULL;
}
-struct iaqt_page_button *iaqtFindButtonByLabel(char *label) {
+struct iaqt_page_button *iaqtFindButtonByLabel(const char *label) {
int i;
struct iaqt_page_button *buttons;
- if (_currentPage == IAQ_PAGE_DEVICES )
- buttons = _devicePageButtons;
- else if (_currentPage == IAQ_PAGE_DEVICES2 )
- buttons = _devicePage2Buttons;
+ if (_currentPage == IAQ_PAGE_DEVICES)
+ buttons = _devicePageButtons[0];
+ else if (_currentPage == IAQ_PAGE_DEVICES2)
+ buttons = _devicePageButtons[1];
+ else if (_currentPage == IAQ_PAGE_DEVICES3)
+ buttons = _devicePageButtons[2];
else if (_currentPage == IAQ_PAGE_SYSTEM_SETUP )
- buttons = _deviceSystemSetupButtons;
+ buttons = _deviceSystemSetupButtons[0];
+ else if (_currentPage == IAQ_PAGE_SYSTEM_SETUP2 )
+ buttons = _deviceSystemSetupButtons[1];
+ else if (_currentPage == IAQ_PAGE_SYSTEM_SETUP3 )
+ buttons = _deviceSystemSetupButtons[2];
+ else if (_currentPage == IAQ_PAGE_HOME )
+ buttons = _homeButtons;
else
buttons = _pageButtons;
@@ -237,8 +260,10 @@ void processPageMessage(unsigned char *message, int length)
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) {
+
+ if (_currentPageLoading == IAQ_PAGE_HOME || _currentPage == IAQ_PAGE_HOME) {
+ rsm_strncpy(_homeStatus[(int)message[PKT_IAQT_MSGINDX]], &message[PKT_IAQT_MSGDATA], AQ_MSGLEN, length-PKT_IAQT_MSGDATA-3);
+ } else if (_currentPageLoading == IAQ_PAGE_STATUS || _currentPage == IAQ_PAGE_STATUS) { // 2nd page of device status doesn;t gine us new page message
//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);
@@ -259,19 +284,30 @@ void processTableMessage(unsigned char *message, int length)
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)
+void processPageButton(unsigned char *message, int length, struct aqualinkdata *aq_data)
{
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
+ if (_currentPageLoading == IAQ_PAGE_DEVICES)
+ button = &_devicePageButtons[0][index];
+ else if (_currentPageLoading == IAQ_PAGE_DEVICES2)
+ button = &_devicePageButtons[1][index];
+ else if (_currentPageLoading == IAQ_PAGE_DEVICES3)
+ button = &_devicePageButtons[2][index];
+ else if (_currentPageLoading == IAQ_PAGE_SYSTEM_SETUP)
+ button = &_deviceSystemSetupButtons[0][index];
+ else if (_currentPageLoading == IAQ_PAGE_SYSTEM_SETUP2)
+ button = &_deviceSystemSetupButtons[1][index];
+ else if (_currentPageLoading == IAQ_PAGE_SYSTEM_SETUP3)
+ button = &_deviceSystemSetupButtons[2][index];
+ else if (_currentPageLoading == IAQ_PAGE_HOME )
+ button = &_homeButtons[index];
+ else {
button = &_pageButtons[index];
+ // if _currentPageLoading = 0x00 then we should use current page
+ LOG(IAQT_LOG,LOG_INFO, "Not sure where to add Button %d %s - LoadingPage = %s\n",index,button->name,iaqt_page_name(_currentPageLoading));
+ }
button->state = message[PKT_IAQT_BUTSTATE];
button->type = message[PKT_IAQT_BUTTYPE];
@@ -285,19 +321,74 @@ void processPageButton(unsigned char *message, int length)
// This doesn't work with return which is 0x00
//strncpy(&button->name, (char *)message + PKT_IAQT_BUTDATA, AQ_MSGLEN);
+ memset(button->name, 0, sizeof(button->name));
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);
+ LOG(IAQT_LOG,LOG_DEBUG, "Added Button %d %s - LoadingPage = %s\n",index,button->name,iaqt_page_name(_currentPageLoading));
-/*
- _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);
+ // This get's called or every device state change in PDA mode, since we page over all the devices.
+ // So capture and update the device state
- LOG(IAQT_LOG,LOG_NOTICE, "Added Button %d %s\n",index,_pageButtons[index].name);
-*/
+ if (isPDA_PANEL || PANEL_SIZE() >= 16 ) {
+ int start = 0;
+ int end = aq_data->total_buttons;
+
+#ifdef AQ_RS16
+ if (PANEL_SIZE() >= 16) {
+ start = aq_data->rs16_vbutton_start;
+ end = aq_data->rs16_vbutton_end + 1; // Using < in comparison and not <=, so +1
+//printf("************ CHECK RS16 BUTTONS ************\n");
+ }
+#endif
+
+ for (int i = start; i < end; i++)
+ {
+
+ int rtn=-1;
+ //LOG(IAQT_LOG,LOG_DEBUG, "Button compare '%s' to '%s'\n",button->name, aq_data->aqbuttons[i].label);
+ // If we are loading HOME page then simply button name is the label ie "Aux3"
+ // If loading DEVICES? page then button name + statusis "Aux3 OFF "
+
+ if (_currentPageLoading == IAQ_PAGE_HOME)
+ rtn = rsm_strmatch((const char *)button->name, aq_data->aqbuttons[i].label);
+ else
+ rtn = rsm_strmatch_ignore((const char *)button->name, aq_data->aqbuttons[i].label,5); // 5 = 3 chars and 2 spaces ' OFF '
+
+ if (rtn == 0)
+ {
+ LOG(IAQT_LOG,LOG_DEBUG, "*** Found Status for %s state 0x%02hhx\n", aq_data->aqbuttons[i].label, button->state);
+ switch(button->state) {
+ case 0x00:
+ if (aq_data->aqbuttons[i].led->state != OFF) {
+ aq_data->aqbuttons[i].led->state = OFF;
+ aq_data->updated = true;
+ }
+ break;
+ case 0x01:
+ if (aq_data->aqbuttons[i].led->state != ON) {
+ aq_data->aqbuttons[i].led->state = ON;
+ aq_data->updated = true;
+ }
+ break;
+ case 0x02:
+ if (aq_data->aqbuttons[i].led->state != FLASH) {
+ aq_data->aqbuttons[i].led->state = FLASH;
+ aq_data->updated = true;
+ }
+ break;
+ case 0x03:
+ if (aq_data->aqbuttons[i].led->state != ENABLE) {
+ aq_data->aqbuttons[i].led->state = ENABLE;
+ aq_data->updated = true;
+ }
+ break;
+ default:
+ LOG(IAQT_LOG,LOG_NOTICE, "Unknown state 0x%02hhx for button %s\n",button->state,button->name);
+ break;
+ }
+ }
+ }
+ }
}
@@ -367,39 +458,45 @@ void passDeviceStatusPage(struct aqualinkdata *aq_data)
continue;
} else if (rsm_strcmp(_deviceStatus[i],"RPM:") == 0) {
- if (pump != NULL)
+ if (pump != NULL) {
pump->rpm = rsm_atoi(&_deviceStatus[i][9]);
- else
+ aq_data->updated = true;
+ } 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)
+ if (pump != NULL) {
pump->gpm = rsm_atoi(&_deviceStatus[i][9]);
- else
+ aq_data->updated = true;
+ } 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)
+ if (pump != NULL) {
pump->watts = rsm_atoi(&_deviceStatus[i][9]);
- else
+ aq_data->updated = true;
+ } 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)
+ if (pump != NULL) {
pump->rpm = PUMP_PRIMING;
- else
+ aq_data->updated = true;
+ } 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)
+ if (pump != NULL) {
pump->rpm = PUMP_OFFLINE;
- else
+ aq_data->updated = true;
+ } 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)
+ if (pump != NULL) {
pump->rpm = PUMP_ERROR;
- else
+ aq_data->updated = true;
+ } else
LOG(IAQT_LOG,LOG_WARNING, "Got pump message '%s' but can't find pump\n",_deviceStatus[i]);
continue;
// Need to catch messages like
@@ -422,22 +519,27 @@ void passDeviceStatusPage(struct aqualinkdata *aq_data)
aq_data->ph = ph;
aq_data->orp = orp;
}
+ aq_data->updated = true;
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) {
+//#ifdef READ_SWG_FROM_EXTENDED_ID
+ else if (isPDA_PANEL) {
+ 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) {
+ } else if (rsm_strcmp(_deviceStatus[i],"salt") == 0) {
aq_data->swg_ppm = rsm_atoi(&_deviceStatus[i][5]);
+ aq_data->updated = true;
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) {
+ } else if (rsm_strcmp(_deviceStatus[i],"Boost Pool") == 0) {
aq_data->boost = true;
+ aq_data->updated = true;
// Let RS pickup time remaing message.
- }
-#endif
+ }
+ }
+//#endif
} // for
}
@@ -451,9 +553,13 @@ void debugPrintButtons(struct iaqt_page_button buttons[])
}
}
+//#define member_size(type, member) (sizeof( ((type*)0)->member ))
+
void processPage(struct aqualinkdata *aq_data)
{
+ //static int _home_cnt = 0;
int i;
+ int dp = 0;
LOG(IAQT_LOG,LOG_INFO, "Page: %s | 0x%02hhx\n",iaqt_page_name(_currentPage),_currentPage);
@@ -468,18 +574,36 @@ void processPage(struct aqualinkdata *aq_data)
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)
+ if (_pageButtons[1].type == 0x02) {
iaqt_queue_cmd(KEY_IAQTCH_KEY02);
- else
+ } else {
iaqt_pump_update(aq_data, -1); // Reset pumps.
+ if ( (isPDA_PANEL || PANEL_SIZE() >= 16) && !in_iaqt_programming_mode(aq_data) ) {
+ iaqt_queue_cmd(KEY_IAQTCH_HOME);
+ }
+ }
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);
+ case IAQ_PAGE_DEVICES3:
+ if (_currentPage == IAQ_PAGE_DEVICES)
+ dp = 0;
+ else if (_currentPage == IAQ_PAGE_DEVICES2)
+ dp = 1;
+ else if (_currentPage == IAQ_PAGE_DEVICES3)
+ dp = 2;
+ //LOG(IAQT_LOG,LOG_INFO, "Devices Page #1:-\n");
+ debugPrintButtons(_devicePageButtons[dp]);
+
+ // If Button 15 has type 0x02 then we have previous, if 0x00 nothing (previous send code KEY_IAQTCH_PREV_PAGE)
+ // If Button 16 has type 0x03 then we have next, if 0x00 nothing (next send code KEY_IAQTCH_NEXT_PAGE)
+ if ( (isPDA_PANEL || PANEL_SIZE() >= 16) && !in_iaqt_programming_mode(aq_data) ) {
+ if (_devicePageButtons[dp][16].type == 0x03) {
+ iaqt_queue_cmd(KEY_IAQTCH_NEXT_PAGE);
+ } else {
+ iaqt_queue_cmd(KEY_IAQTCH_STATUS);
+ }
+ }
break;
case IAQ_PAGE_COLOR_LIGHT:
//LOG(IAQT_LOG,LOG_INFO, "Color Light Page :-\n");
@@ -491,15 +615,68 @@ void processPage(struct aqualinkdata *aq_data)
if (_deviceStatus[i][0] != 0)
LOG(IAQT_LOG,LOG_INFO, "Status page %.2d| %s\n",i,_deviceStatus[i]);
- debugPrintButtons(_pageButtons);
+ for (i=0; i air_temp = atoi(_homeStatus[1]);
+ LOG(IAQT_LOG,LOG_DEBUG, "Air Temp set to %d\n",aq_data->air_temp);
+ aq_data->updated = true;
+ }
+ if (rsm_strcmp(_homeStatus[4],"Pool Temp") == 0) {
+ aq_data->pool_temp = atoi(_homeStatus[0]);
+ LOG(IAQT_LOG,LOG_DEBUG, "Pool Temp set to %d\n",aq_data->air_temp);
+ aq_data->updated = true;
+ } else if (rsm_strcmp(_homeStatus[4],"Spa Temp") == 0) {
+ aq_data->spa_temp = atoi(_homeStatus[0]);
+ LOG(IAQT_LOG,LOG_DEBUG, "Spa Temp set to %d\n",aq_data->spa_temp);
+ aq_data->updated = true;
+ }
+ }
+
+ //passHomePage(aq_data);
+ debugPrintButtons(_homeButtons);
break;
case IAQ_PAGE_SYSTEM_SETUP:
//LOG(IAQT_LOG,LOG_INFO, "System Setup :-\n");
- debugPrintButtons(_deviceSystemSetupButtons);
+ debugPrintButtons(_deviceSystemSetupButtons[0]);
+ break;
+ case IAQ_PAGE_SYSTEM_SETUP2:
+ //LOG(IAQT_LOG,LOG_INFO, "System Setup :-\n");
+ debugPrintButtons(_deviceSystemSetupButtons[1]);
+ break;
+ case IAQ_PAGE_SYSTEM_SETUP3:
+ //LOG(IAQT_LOG,LOG_INFO, "System Setup :-\n");
+ debugPrintButtons(_deviceSystemSetupButtons[2]);
break;
case IAQ_PAGE_SET_VSP:
debugPrintButtons(_pageButtons);
break;
+ case IAQ_PAGE_HELP:
+ /*
+ Info: iAQ Touch: Table Messages 01| Interface: AquaLink Touch
+ Info: iAQ Touch: Table Messages 02| Model: RS-8 Combo
+ Info: iAQ Touch: Table Messages 03| AquaLink: REV T.0.1
+ Info: iAQ Touch: Table Messages 04| CPU p/n: B0029221
+ Info: iAQ Touch: Table Messages 05| TL Rev:
+ */
+ if (isPDA_PANEL && ((char *)_tableInformation[03]) > 0) {
+ if ( rsm_get_revision(aq_data->revision,(char *)_tableInformation[3], sizeof(aq_data->revision) ) == TRUE) {
+ int len = rsm_get_boardcpu(aq_data->version, sizeof(aq_data->version), (char *)_tableInformation[4], IAQT_TABLE_MSGLEN );
+ sprintf(aq_data->version+len, " REV %s",aq_data->revision);
+ LOG(IAQT_LOG,LOG_NOTICE, "Control Panel revision %s\n", aq_data->revision);
+ LOG(IAQT_LOG,LOG_NOTICE, "Control Panel version %s\n", aq_data->version);
+ aq_data->updated = true;
+ }
+ }
+
+ break;
default:
//LOG(IAQT_LOG,LOG_INFO, "** UNKNOWN PAGE 0x%02hhx **\n",_currentPage);
@@ -521,11 +698,27 @@ void processPage(struct aqualinkdata *aq_data)
bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkdata *aq_data)
{
+ static bool gotInit = false;
static int cnt = 0;
static bool gotStatus = true;
+ static char message[AQ_MSGLONGLEN + 1];
+ bool fake_pageend = false;
//char buff[1024];
+ // NSF Take this out
+
+ if ( packet[3] != CMD_IAQ_POLL && getLogLevel(IAQT_LOG) >= LOG_DEBUG ) {
+ //if ( getLogLevel(IAQT_LOG) >= LOG_DEBUG ) {
+ char buff[1000];
+ beautifyPacket(buff, packet, length, false);
+ LOG(IAQT_LOG,LOG_DEBUG, "Received message : %s", buff);
+ }
+
if (packet[PKT_CMD] == CMD_IAQ_PAGE_START) {
+ // Reset and messages on new page
+ aq_data->last_display_message[0] = ' ';
+ aq_data->last_display_message[1] = '\0';
+ aq_data->is_display_message_programming = false;
LOG(IAQT_LOG,LOG_DEBUG, "Turning IAQ SEND off\n");
set_iaq_cansend(false);
_currentPageLoading = packet[PKT_IAQT_PAGTYPE];
@@ -533,6 +726,7 @@ bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkd
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 );
+ //memset(_devicePageButtons, 0, IAQ_PAGE_BUTTONS * sizeof(struct iaqt_page_button));
// Fix bug with control panel where after a few hours status page disapears and you need to hit menu.
if (gotStatus == false)
gotStatus = true;
@@ -542,17 +736,32 @@ bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkd
LOG(IAQT_LOG,LOG_DEBUG, "Turning IAQ SEND on\n");
if (_currentPageLoading != NUL) {
_currentPage = _currentPageLoading;
- _currentPageLoading = NUL;
+ //_currentPageLoading = NUL;
} else {
LOG(IAQT_LOG,LOG_DEBUG, "Page end message without proceding page start, ignoring!\n");
}
+
+ if (isPDA_PANEL) {
+ // Time is in the page end command
+ // 1/18/2011 13:42
+ //Hex |0x10|0x02|0x33|0x28|0x01|0x12|0x0b|0x0d|0x2a|0xc2|0x10|0x03|
+ //Dec | 16| 2| 51| 40| 1| 18| 11| 13| 42| 194| 16| 3
+ //Ascii | | | 3| (| | | | | *| | |
+ snprintf(aq_data->date, sizeof(aq_data->date), "%02d/%02d/%02d", packet[4],packet[5],packet[6]);
+ if (packet[7] <= 12)
+ snprintf(aq_data->time, sizeof(aq_data->date), "%d:%02d am", packet[7],packet[8]);
+ else
+ snprintf(aq_data->time, sizeof(aq_data->date), "%d:%02d pm", (packet[7] - 12),packet[8]);
+ }
+
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);
+ processPageButton(packet, length, aq_data);
// 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
@@ -567,32 +776,71 @@ bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkd
// 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) {
+ if (packet[7] == 0x00 && packet[4] == 0x0e) { // packet[4] is button number 0x0e is 14 (last button)
//printf("** MANUAL PAGE END\n");
LOG(IAQT_LOG,LOG_DEBUG, "MANUAL PAGE END\n");
_currentPage = _currentPageLoading;
- _currentPageLoading = NUL;
+ //_currentPageLoading = NUL;
processPage(aq_data);
set_iaq_cansend(true);
+ // For programming mode
+ fake_pageend = 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
}
}
- }
+ } else if (isPDA_PANEL && packet[PKT_CMD] == CMD_IAQ_MSG_LONG) {
+ char *sp;
+ // Set disply message if PDA panel
+ memset(message, 0, AQ_MSGLONGLEN + 1);
+ rsm_strncpy(message, packet + 6, AQ_MSGLONGLEN, length-9);
+ LOG(IAQT_LOG,LOG_NOTICE, "Popup message '%s'\n",message);
+
+ // Change this message, since you can't press OK. 'Light will turn off in 5 seconds. To change colors press Ok now.'
+ if ((sp = rsm_strncasestr(message, "To change colors press Ok now", strlen(message))) != NULL)
+ {
+ *sp = '\0';
+ }
+
+ strcpy(aq_data->last_display_message, message); // Also display the message on web UI
+
+ if (in_programming_mode(aq_data)) {
+ aq_data->is_display_message_programming = true;
+ } else {
+ aq_data->is_display_message_programming = false;
+ }
+ /*
+ for(int i=0; i REQUEST_STATUS_POLL_COUNT && in_programming_mode(aq_data) == false ) {
- iaqt_queue_cmd(KEY_IAQTCH_STATUS);
+ if (isPDA_PANEL || PANEL_SIZE() >= 16) {
+ iaqt_queue_cmd(KEY_IAQTCH_HOMEP_KEY08);
+ } else {
+ 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;
@@ -600,21 +848,32 @@ bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkd
// 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);
+ cnt = REQUEST_STATUS_POLL_COUNT - 5;
+ /*
+ if (isPDA_PANEL) {
+ iaqt_queue_cmd(KEY_IAQTCH_HOMEP_KEY08);
+ } else {
+ 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;
}
-
+ // On poll no need to kick programming threads
return false;
}
//debuglogPacket(IAQT_LOG ,packet, length);
//_lastMsgType = packet[PKT_CMD];
- set_iaqtouch_lastmsg(packet[PKT_CMD]);
+ if (fake_pageend){
+ set_iaqtouch_lastmsg(CMD_IAQ_PAGE_END);
+ } else {
+ set_iaqtouch_lastmsg(packet[PKT_CMD]);
+ }
//debuglogPacket(IAQT_LOG ,packet, length);
//beautifyPacket(buff, packet, length);
//LOG(IAQT_LOG,LOG_DEBUG, "%s", buff);
@@ -623,12 +882,15 @@ bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkd
kick_aq_program_thread(aq_data, IAQTOUCH);
- return false;
+ return true;
}
+//char _namebuf[40];
const char *iaqt_page_name(const unsigned char page)
{
+ static char _namebuf[40];
+
switch (page){
case IAQ_PAGE_HOME:
return "HOME";
@@ -645,6 +907,9 @@ const char *iaqt_page_name(const unsigned char page)
case IAQ_PAGE_DEVICES2:
return "Devices #2";
break;
+ case IAQ_PAGE_DEVICES3:
+ return "Devices #2";
+ break;
case IAQ_PAGE_SET_TEMP:
return "Set Temp";
break;
@@ -678,6 +943,12 @@ const char *iaqt_page_name(const unsigned char page)
case IAQ_PAGE_SYSTEM_SETUP:
return "System Setup";
break;
+ case IAQ_PAGE_SYSTEM_SETUP2:
+ return "System Setup #2";
+ break;
+ case IAQ_PAGE_SYSTEM_SETUP3:
+ return "System Setup #3";
+ break;
case IAQ_PAGE_VSP_SETUP:
return "VSP Setup";
break;
@@ -687,8 +958,13 @@ const char *iaqt_page_name(const unsigned char page)
case IAQ_PAGE_LABEL_AUX:
return "Label Aux";
break;
+ case IAQ_PAGE_HELP:
+ return "Help Page";
+ break;
default:
- return "** Unknown **";
+ sprintf (_namebuf,"** Unknown 0x%02hhx **",page);
+ return _namebuf;
+ //return "** Unknown **";
break;
}
return "";
@@ -921,4 +1197,4 @@ Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x24|0x01|0x00|0x00|0x02|0x00|0x00
Button | 1 | 0x02 | |-| |-| -off-
-*/
\ No newline at end of file
+*/
diff --git a/iaqtouch.h b/iaqtouch.h
index 74b3a4b4..61deb7e1 100644
--- a/iaqtouch.h
+++ b/iaqtouch.h
@@ -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);
diff --git a/iaqtouch_aq_programmer.c b/iaqtouch_aq_programmer.c
index 7fd3b106..86bff4a6 100644
--- a/iaqtouch_aq_programmer.c
+++ b/iaqtouch_aq_programmer.c
@@ -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.
@@ -98,6 +99,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 ) ) {
@@ -167,9 +174,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 +255,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();
@@ -317,7 +380,8 @@ bool goto_iaqt_page(const unsigned char pageID, struct aqualinkdata *aq_data) {
LOG(IAQT_LOG, LOG_DEBUG, "IAQ Touch got to Device 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 +452,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 +476,207 @@ 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) {
+ // 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 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;
+}
+
+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;
@@ -486,7 +757,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);
@@ -587,6 +860,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;
@@ -636,6 +913,40 @@ 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') {
+ 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
diff --git a/iaqtouch_aq_programmer.h b/iaqtouch_aq_programmer.h
index db51aa87..94d943f5 100644
--- a/iaqtouch_aq_programmer.h
+++ b/iaqtouch_aq_programmer.h
@@ -19,6 +19,8 @@ void *set_aqualink_iaqtouch_spa_heater_temp( void *ptr );
void *set_aqualink_iaqtouch_pool_heater_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
int ref_iaqt_control_cmd(unsigned char **cmd);
void rem_iaqt_control_cmd(unsigned char *cmd);
diff --git a/json_messages.c b/json_messages.c
index 5d0940a9..dd77ae08 100644
--- a/json_messages.c
+++ b/json_messages.c
@@ -98,6 +98,11 @@ const char* _getStatus(struct aqualinkdata *aqdata, const char *blankmsg)
return programtypeDisplayName(aqdata->active_thread.ptype);
}
*/
+ 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->service_mode_state == ON) {
@@ -611,7 +616,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\":{" );
diff --git a/net_services.c b/net_services.c
index 6e428907..f770dfa8 100644
--- a/net_services.c
+++ b/net_services.c
@@ -44,6 +44,7 @@
#include "aq_scheduler.h"
#include "rs_msg_utils.h"
#include "simulator.h"
+#include "hassio.h"
#include "version.h"
#ifdef AQ_PDA
@@ -685,14 +686,17 @@ void mqtt_broadcast_aqualinkstate(struct mg_connection *nc)
const char *status;
if (_aqconfig_.mqtt_timed_update) {
+#ifdef AQ_NO_THREAD_NETSERVICE
if (cnt > 300) { // 100 = about every 2 minutes.
+#else
+ if (cnt > 30) { // 30 = about every 2 minutes.
+#endif
reset_last_mqtt_status();
cnt = 0;
} else {
cnt++;
}
}
-
//LOG(NET_LOG,LOG_INFO, "mqtt_broadcast_aqualinkstate: START\n");
@@ -819,6 +823,12 @@ void mqtt_broadcast_aqualinkstate(struct mg_connection *nc)
send_mqtt_int_msg(nc, SWG_BOOST_TOPIC, _aqualink_data->boost);
_last_mqtt_aqualinkdata.boost = _aqualink_data->boost;
}
+
+ if ( _aqualink_data->boost_duration != _last_mqtt_aqualinkdata.boost_duration ) {
+ send_mqtt_int_msg(nc, SWG_BOOST_DURATION_TOPIC, _aqualink_data->boost_duration);
+ _last_mqtt_aqualinkdata.boost_duration = _aqualink_data->boost_duration;
+ }
+
} else {
//LOG(NET_LOG,LOG_DEBUG, "SWG status unknown\n");
}
@@ -831,9 +841,10 @@ void mqtt_broadcast_aqualinkstate(struct mg_connection *nc)
int dzalert;
get_swg_status_mqtt(_aqualink_data, message, &status, &dzalert);
- send_domoticz_mqtt_status_message(nc, _aqconfig_.dzidx_swg_status, dzalert, &message[9]);
+ send_domoticz_mqtt_status_message(nc, _aqconfig_.dzidx_swg_status, dzalert, &message[9]);
send_mqtt_int_msg(nc, SWG_EXTENDED_TOPIC, (int)_aqualink_data->ar_swg_device_status);
+ send_mqtt_string_msg(nc, SWG_STATUS_MSG_TOPIC, message);
_last_mqtt_aqualinkdata.ar_swg_device_status = _aqualink_data->ar_swg_device_status;
//LOG(NET_LOG,LOG_DEBUG, "SWG Extended sending cur=%d sent=%d\n",_aqualink_data->ar_swg_device_status,_last_mqtt_aqualinkdata.ar_swg_device_status);
@@ -844,6 +855,22 @@ void mqtt_broadcast_aqualinkstate(struct mg_connection *nc)
//LOG(NET_LOG,LOG_DEBUG, "SWG Extended unknown\n");
}
+ if (READ_RSDEV_JXI && _aqualink_data->heater_err_status != _last_mqtt_aqualinkdata.heater_err_status) {
+ char message[30];
+
+ if (_aqualink_data->heater_err_status == NUL) {
+ send_mqtt_int_msg(nc, LXI_ERROR_CODE, (int)_aqualink_data->heater_err_status);
+ send_mqtt_string_msg(nc, LXI_ERROR_MESSAGE, "");
+ } else {
+ //send_mqtt_int_msg(nc, LXI_STATUS, (int)_aqualink_data->heater_err_status);
+ send_mqtt_int_msg(nc, LXI_ERROR_CODE, (int)_aqualink_data->heater_err_status);
+ getJandyHeaterErrorMQTT(_aqualink_data, message);
+ send_mqtt_string_msg(nc, LXI_ERROR_MESSAGE, status);
+ }
+
+ _last_mqtt_aqualinkdata.heater_err_status = _aqualink_data->heater_err_status;
+ }
+
// LOG(NET_LOG,LOG_INFO, "mqtt_broadcast_aqualinkstate: START LEDs\n");
// if (time(NULL) % 2) {} <-- use to determin odd/even second in time to make state flash on enabled.
@@ -952,6 +979,7 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float
char *ri1 = (char *)URI;
char *ri2 = NULL;
char *ri3 = NULL;
+ //bool charvalue=false;
//char *ri4 = NULL;
LOG(NET_LOG,LOG_DEBUG, "%s: URI Request '%.*s': value %.2f\n", actionName[from], uri_length, URI, value);
@@ -1325,7 +1353,21 @@ void action_mqtt_message(struct mg_connection *nc, struct mg_mqtt_message *msg)
strncpy(tmp, msg->payload.p, msg->payload.len);
tmp[msg->payload.len] = '\0';
- float value = atof(tmp);
+ //float value = atof(tmp);
+
+ // Check value like on/off/heat/cool and convery to int.
+ // HASSIO doesn't support `mode_command_template` so easier to code around their limotation here.
+ char *end;
+ float value = strtof(tmp, &end);
+ if (tmp == end) { // Not a number
+ // See if any test resembeling 1, of not leave at zero.
+ if (rsm_strcmp(tmp, "on")==0 || rsm_strcmp(tmp, "heat")==0 || rsm_strcmp(tmp, "cool")==0)
+ value = 1;
+
+ LOG(NET_LOG,LOG_NOTICE, "MQTT: converted value from '%s' to '%.0f', from message '%.*s'\n",tmp,value,msg->topic.len, msg->topic.p);
+ }
+
+
//int val = _aqualink_data->unactioned.value = (_aqualink_data->temp_units != CELSIUS && _aqconfig_.convert_mqtt_temp) ? round(degCtoF(value)) : round(value);
bool convert = (_aqualink_data->temp_units != CELSIUS && _aqconfig_.convert_mqtt_temp)?true:false;
int offset = strlen(_aqconfig_.mqtt_aq_topic)+1;
@@ -1821,6 +1863,8 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
mg_mqtt_subscribe(nc, topics, 1, 42);
LOG(NET_LOG,LOG_INFO, "MQTT: Subscribing to '%s'\n", _aqconfig_.mqtt_dz_sub_topic);
}
+
+ publish_mqtt_hassio_discover( _aqualink_data, nc);
}
break;
case MG_EV_MQTT_PUBACK:
@@ -1876,6 +1920,13 @@ void reset_last_mqtt_status()
_last_mqtt_aqualinkdata.boost = -1;
_last_mqtt_aqualinkdata.swg_percent = -1;
_last_mqtt_aqualinkdata.swg_ppm = -1;
+ _last_mqtt_aqualinkdata.heater_err_status = NUL; // 0x00
+
+ for (i=0; i < _aqualink_data->num_pumps; i++) {
+ _last_mqtt_aqualinkdata.pumps[i].gpm = -1;
+ _last_mqtt_aqualinkdata.pumps[i].rpm = -1;
+ _last_mqtt_aqualinkdata.pumps[i].watts = -1;
+ }
}
diff --git a/net_services.h b/net_services.h
index b2449e4a..a53be002 100644
--- a/net_services.h
+++ b/net_services.h
@@ -31,6 +31,11 @@ void broadcast_aqualinkstate();
void broadcast_aqualinkstate_error(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
diff --git a/pda.c b/pda.c
index 267a9db5..e72539e9 100644
--- a/pda.c
+++ b/pda.c
@@ -687,6 +687,7 @@ void process_pda_packet_msg_long_equiptment_status(const char *msg_line, int lin
//snprintf(_aqualink_data->boost_msg, sizeof(_aqualink_data->boost_msg), "%s", msg+2);
//Message is ' 23:21 Remain', we only want time part
snprintf(_aqualink_data->boost_msg, 6, "%s", msg);
+ _aqualink_data->boost_duration = rsm_HHMM2min(_aqualink_data->boost_msg);
}
else if ((index = rsm_strncasestr(msg, MSG_SWG_PCT, AQ_MSGLEN)) != NULL)
{
diff --git a/release/aqualinkd b/release/aqualinkd
deleted file mode 100755
index 827d7c63..00000000
Binary files a/release/aqualinkd and /dev/null differ
diff --git a/release/aqualinkd.conf b/release/aqualinkd.conf
index 872ffb52..9548f370 100755
--- a/release/aqualinkd.conf
+++ b/release/aqualinkd.conf
@@ -77,16 +77,21 @@ device_id=0x0a
# 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.
+# 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.
# 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
#read_RS485_swg = yes
#read_RS485_ePump = yes
#read_RS485_vsfPump = yes
+#read_RS485_JXi = yes
+#read_RS485_LX = yes
+#read_RS485_Chem = yes
# Keep the panel time synced with systemtime. Make sure to set systemtime / NTP correctly.
keep_paneltime_synced = yes
@@ -164,6 +169,10 @@ force_PS_setpoints = no
# to be listed as thermostat on startup.
force_Frzprotect_setpoints = no
+# AqualinkD can take sime time to find chemical feeder (if panel supports it), This will force the chemical feeder
+# to be listed on startup.
+force_chem_feeder = no
+
# 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.
diff --git a/release/aqualinkd.test.pda.conf b/release/aqualinkd.test.pda.conf
index cfc4ec1a..5ac3c9f9 100644
--- a/release/aqualinkd.test.pda.conf
+++ b/release/aqualinkd.test.pda.conf
@@ -18,8 +18,8 @@ web_directory=/nas/data/Development/Raspberry/AqualinkD/web
#log_level=DEBUG_SERIAL
#log_level=DEBUG
-log_level=INFO
-#log_level=NOTICE
+#log_level=INFO
+log_level=NOTICE
# AQUA_LOG 1
# NET_LOG 2
@@ -40,15 +40,19 @@ log_level=INFO
#debug_log_mask = 2
#debug_log_mask = 4
#debug_log_mask = 8
-#debug_log_mask = 16
+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 = 1024
#debug_log_mask = 2048
#debug_log_mask = 4096
+# Log any packets from this device. (less outpit that DEBUG_SERIAL)
+#debug_log_mask = 512 - MUST be set for this to work.
+RSSD_LOG_filter = 0x33
+
display_warnings_in_web = yes
rs485_frame_delay = 4
@@ -80,7 +84,8 @@ mqtt_aq_topic = aqualinkd-test
#device_id=0x0a
#device_id=0xFF # For testing one touch, don't use kaypad
#device_id=0x00
-device_id=0x60
+#device_id=0x60
+device_id=0x33
#rssa_device_id=0x48
@@ -89,7 +94,7 @@ device_id=0x60
# 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
+#extended_device_id=0x33
# 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.
@@ -227,7 +232,7 @@ button_01_pumpID=0x78
#button_01_PDA_label=FILTER PUMP
button_01_pumpIndex=1
-button_02_label=Spa Mode
+button_02_label=Spa
#button_02_dzidx=38
#button_02_pumpID= 0x78
#button_02_pumpIndex=3
diff --git a/release/serial_logger b/release/serial_logger
index 046d3e57..049b2b09 100755
Binary files a/release/serial_logger and b/release/serial_logger differ
diff --git a/rs_msg_utils.c b/rs_msg_utils.c
index 76ceed7d..a89349a2 100644
--- a/rs_msg_utils.c
+++ b/rs_msg_utils.c
@@ -20,6 +20,8 @@
#include
#include
#include
+#include
+#include
#include "utils.h"
#include "rs_msg_utils.h"
@@ -83,6 +85,42 @@ bool rsm_get_revision(char *dest, const char *src, int src_len)
return true;
}
+/*
+pull board CPU from strings line
+ ' CPU p/n: B0029221'
+ 'B0029221 REV T.0.1'
+ 'E0260801 REV. O.2'
+*/
+int rsm_get_boardcpu(char *dest, int dest_len, const char *src, int src_len)
+{
+ //char *regexString="/\\w\\d{4,10}/gi";
+ //char *regexString="/[[:alpha:]][[:digit:]]{4,10}/gi";
+ char *regexString="[[:alpha:]][[:digit:]]{4,10}";
+ regex_t regexCompiled;
+ regmatch_t match;
+ int rc, begin, end, len;
+
+ if (0 != (rc = regcomp(®exCompiled, regexString, REG_EXTENDED))) {
+ LOG(AQUA_LOG,LOG_ERR, "regcomp() failed, returning nonzero (%d)\n", rc);
+ return 0;
+ }
+
+ if ((regexec(®exCompiled,src,1,&match,0)) != 0) {
+ regfree(®exCompiled);
+ printf("********** ERROR didn;t line match \n");
+ return 0;
+ }
+
+ begin = (int)match.rm_so;
+ end = (int)match.rm_eo;
+ len = AQ_MIN((end-begin), dest_len);
+
+ strncpy(dest, src+match.rm_so, len );
+
+ regfree(®exCompiled);
+
+ return len;
+}
/*
Find first char after a space in haystack after searching needle.
@@ -110,6 +148,18 @@ char *rsm_charafterstr(const char *haystack, const char *needle, int length)
return ++sp;
}
+/*
+ Check if string has printable chars and is not empty
+*/
+bool rsm_isempy(const char *src, int length)
+{
+ int i;
+ for(i=0; i < length; i++) {
+ if (src[i] > 32 && src[i] < 127) // 32 is space
+ return true;
+ }
+ return false;
+}
/*
Can probably replace this with rsm_strncasestr in all code.
*/
@@ -193,9 +243,82 @@ int rsm_strcmp(const char *haystack, const char *needle)
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);
+ //printf("***** rsm_strcmp Compare (reset)%d chars of '%s' to '%s'\n",strlen(sp2),sp1,sp2);
return strncasecmp(sp1, sp2, strlen(sp2));
}
+// Match two strings, used for button labels
+// exact character length once white space removed is used for match
+// use case insensative for match.
+// so 'spa' !- 'spa mode'
+int rsm_strmatch(const char *haystack, const char *needle)
+{
+ return rsm_strmatch_ignore(haystack, needle, 0);
+ /*
+ char *sp1 = (char *)haystack;
+ char *sp2 = (char *)needle;
+
+ char *ep1 = (char *)sp1 + strlen(sp1) - 1;
+ char *ep2 = (char *)sp2 + strlen(sp2) - 1;
+ //int i=0;
+ // Get rid of all padding
+ while(isspace(*sp1)) sp1++;
+ while(isspace(*sp2)) sp2++;
+ while(isspace(*ep1) && (ep1 >= sp1)) ep1--;
+ while(isspace(*ep2) && (ep2 >= sp2)) ep2--;
+
+ int l1 = ep1 - sp1 +1;
+ int l2 = ep2 - sp2 +1;
+
+ //printf("***** rsm_strmatch Compare %d chars of '%s' to %d chars in '%s'\n",l2,sp2,l1,sp1);
+
+ if ( l1 != l2 || (ep1 - sp1) <= 0 || (ep2 - 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, l2);
+ */
+}
+
+// Match two strings, used for button labels
+// exact character length once white space removed is used for match
+// ignore_chars will delete the last X chars from haystack.
+// use case insensative for match.
+// so 'spa' !- 'spa mode'
+int rsm_strmatch_ignore(const char *haystack, const char *needle, int ignore_chars)
+{
+ char *sp1 = (char *)haystack;
+ char *sp2 = (char *)needle;
+
+ char *ep1 = (char *)sp1 + strlen(sp1) - 1;
+ char *ep2 = (char *)sp2 + strlen(sp2) - 1;
+ //int i=0;
+ // Get rid of all padding
+ while(isspace(*sp1)) sp1++;
+ while(isspace(*sp2)) sp2++;
+ while(isspace(*ep2) && (ep2 >= sp2)) ep2--;
+ if (ignore_chars > 0)
+ ep1 = ep1 - ignore_chars;
+ else
+ while(isspace(*ep1) && (ep1 >= sp1)) ep1--;
+
+
+ int l1 = ep1 - sp1 +1;
+ int l2 = ep2 - sp2 +1;
+
+ //printf("***** %s() Compare %d chars of '%s' to %d chars in '%s'\n",(ignore_chars==0?"rsm_strmatch":"rsm_strmatch_ignore"),l2,sp2,l1,sp1);
+
+ if ( l1 != l2 || (ep1 - sp1) <= 0 || (ep2 - 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, l2);
+}
+
/*
* Find last index of char in string.
@@ -218,8 +341,7 @@ char *rsm_lastindexof(const char *haystack, const char *needle, size_t length)
return NULL;
}
-#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)
{
@@ -241,7 +363,27 @@ int rsm_strncmp(const char *haystack, const char *needle, int length)
//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));
+ return strncasecmp(sp1, sp2, AQ_MIN((ep1-sp1)+1,length));
+}
+
+
+char *rsm_char_replace(char *replaced , char *search, char *find, char *replace)
+{
+ int len;
+ int i;
+ char *fp = find;
+ char *rp = replace;
+
+ len = strlen(search);
+ for(i = 0; i < len; i++){
+ if (search[i] == *fp)
+ replaced[i] = *rp;
+ else
+ replaced[i] = search[i];
+ }
+ replaced[i] = '\0';
+
+ return replaced;
}
// NSF Check is this works correctly.
@@ -253,7 +395,7 @@ char *rsm_strncpycut(char *dest, const char *src, int dest_len, int src_len)
while(isspace(*sp)) sp++;
while(isspace(*ep)) ep--;
- int length=MIN((ep-sp)+1,dest_len);
+ int length=AQ_MIN((ep-sp)+1,dest_len);
memset(dest, '\0',dest_len);
return strncpy(dest, sp, length);
@@ -304,8 +446,6 @@ int rsm_strncpy_nul2sp(char *dest, const unsigned char *src, int dest_len, int s
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)
@@ -343,4 +483,14 @@ float rsm_atof(const char* str)
}
return atof(&str[i]);
+}
+
+// MEssages as HH:MM ie 01:23
+int rsm_HHMM2min(char *message) {
+ char *ptr;
+
+ int hour = strtoul(message, &ptr, 10);
+ int min = strtoul(message+3, &ptr, 10);
+
+ return (hour*60)+min;
}
\ No newline at end of file
diff --git a/rs_msg_utils.h b/rs_msg_utils.h
index befff8fa..b9d2fdf7 100644
--- a/rs_msg_utils.h
+++ b/rs_msg_utils.h
@@ -1,16 +1,23 @@
#ifndef RS_MSG_UTILS_H_
#define RS_MSG_UTILS_H_
+//#define MIN(x, y) (((x) < (y)) ? (x) : (y))
bool rsm_get_revision(char *dest, const char *src, int src_len);
+int rsm_get_boardcpu(char *dest, int dest_len, const char *src, int src_len);
+
char *rsm_charafterstr(const char *haystack, const char *needle, int length);
+bool rsm_isempy(const char *src, int length);
char *rsm_strstr(const char *haystack, const char *needle);
//char *rsm_strnstr(const char *haystack, const char *needle, int length);
char *rsm_strnstr(const char *haystack, const char *needle, size_t slen);
char *rsm_strncasestr(const char *haystack, const char *needle, size_t length);
char *rsm_lastindexof(const char *haystack, const char *needle, size_t length);
+int rsm_strmatch(const char *haystack, const char *needle);
+int rsm_strmatch_ignore(const char *haystack, const char *needle, int ignore_chars);
+
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);
@@ -18,6 +25,8 @@ int rsm_strncpy_nul2sp(char *dest, const unsigned char *src, int dest_len, int s
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);
-
+//char *rsm_char_replace(char *replaced , char *search, const char find, const char replace);
+char *rsm_char_replace(char *replaced , char *search, char *find, char *replace);
+int rsm_HHMM2min(char *message);
#endif //RS_MSG_UTILS_H_
diff --git a/serial_logger.c b/serial_logger.c
index ad08f669..af75431d 100644
--- a/serial_logger.c
+++ b/serial_logger.c
@@ -117,7 +117,7 @@ int serial_logger (int rs_fd, char *port_name, int logLevel) {
#define PDA " <-- PDA Remote"
#define EPUMP " <-- Jandy VSP ePump"
#define CHEM " <-- Chemlink"
-#define LXI_LRZ_HEATER " <-- LXi / LRZ Heater"
+#define JXI_HEATER " <-- LXi / LRZ Heater"
#define UNKNOWN " <-- Unknown Device"
@@ -151,7 +151,7 @@ const char *getDevice(unsigned char ID) {
if (ID >= 0x60 && ID <= 0x63)
return PDA;
if (ID >= 0x68 && ID <= 0x6B)
- return LXI_LRZ_HEATER;
+ return JXI_HEATER;
//if (ID >= 0x70 && ID <= 0x73)
if (ID >= 0x78 && ID <= 0x7B)
return EPUMP;
diff --git a/simulator.c b/simulator.c
index 709574e4..080e0adb 100644
--- a/simulator.c
+++ b/simulator.c
@@ -9,7 +9,7 @@
#define MAX_STACK 20
int _sim_stack_place = 0;
-unsigned char _commands[MAX_STACK];
+unsigned char _sim_commands[MAX_STACK];
bool push_simulator_cmd(unsigned char cmd);
@@ -27,7 +27,7 @@ void simulator_send_cmd(unsigned char cmd)
bool push_simulator_cmd(unsigned char cmd)
{
if (_sim_stack_place < MAX_STACK) {
- _commands[_sim_stack_place] = cmd;
+ _sim_commands[_sim_stack_place] = cmd;
_sim_stack_place++;
} else {
LOG(SIM_LOG, LOG_ERR, "Command queue overflow, too many unsent commands to RS control panel\n");
@@ -42,9 +42,9 @@ unsigned char pop_simulator_cmd(unsigned char receive_type)
unsigned char cmd = NUL;
if (_sim_stack_place > 0 && receive_type == CMD_STATUS ) {
- cmd = _commands[0];
+ cmd = _sim_commands[0];
_sim_stack_place--;
- memmove(&_commands[0], &_commands[1], sizeof(unsigned char) * _sim_stack_place ) ;
+ memmove(&_sim_commands[0], &_sim_commands[1], sizeof(unsigned char) * _sim_stack_place ) ;
}
LOG(SIM_LOG,LOG_DEBUG, "Sending '0x%02hhx' to controller\n", cmd);
diff --git a/util.c b/util.c
deleted file mode 100644
index e69de29b..00000000
diff --git a/utils.h b/utils.h
index 0d6234bc..f16d9639 100644
--- a/utils.h
+++ b/utils.h
@@ -47,6 +47,16 @@
// Set scheduler log to timer log
#define SCHD_LOG TIMR_LOG
+
+/*
+#define INT_MAX +2147483647
+#define INT_MIN -2147483647
+*/
+
+#define AQ_MAX(x, y) (((x) > (y)) ? (x) : (y))
+#define AQ_MIN(x, y) (((x) < (y)) ? (x) : (y))
+
+
/*
typedef enum
{
diff --git a/version.h b/version.h
index 45826be6..84546360 100644
--- a/version.h
+++ b/version.h
@@ -1,4 +1,5 @@
#define AQUALINKD_NAME "Aqualink Daemon"
-#define AQUALINKD_VERSION "2.3.4"
+#define AQUALINKD_SHORT_NAME "AqualinkD"
+#define AQUALINKD_VERSION "2.3.5"
diff --git a/web/config.js b/web/config.js
index 6d3417a9..3c6bc822 100644
--- a/web/config.js
+++ b/web/config.js
@@ -35,8 +35,8 @@
"Spa_Water",
"Freeze_Protect",
"CHEM/pH",
- "CHEM/ORP"
- //"Solar_Heater",
+ "CHEM/ORP",
+ "Solar_Heater",
];
// This get's picked up by dynamic_config.js and used as mode 0
@@ -72,6 +72,8 @@
32: "Low volts",
64: "Low temp",
128: "Check PCB",
+ 253: "General Fault",
+ 254: "Unknown",
255: "Off"
}
diff --git a/web/controller.html b/web/controller.html
index da42990f..8a57d4d2 100644
--- a/web/controller.html
+++ b/web/controller.html
@@ -590,6 +590,21 @@
var _scheduler_devices_json= [];
+ var _swgALLStatus = {
+ 0: "On",
+ 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"
+ }
if (typeof show_vsp_gpm !== 'undefined' && show_vsp_gpm == false)
var _show_vsp_gpm=false;
@@ -605,19 +620,7 @@
if (typeof swgStatus !== 'undefined') {
var _swgStatus = swgStatus;
} else {
- var _swgStatus = {
- 0: "On",
- 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",
- 255: "Off"
- }
+ var _swgStatus = _swgALLStatus
}
function init() {
@@ -1100,6 +1103,9 @@
if (exstatus > 0 && exstatus < 255) {// Not off or on
//text = swgFullstatus2String(exstatus);
text = _swgStatus[exstatus];
+ if (typeof text == 'undefined') {
+ text = _swgALLStatus[exstatus];
+ }
} else if (tile.getAttribute('Boost') == 'on')
text = "Boost";
else if (status == 'enabled')
diff --git a/web/onetouch_sim.html b/web/onetouch_sim.html
index ce17872e..3dda6e46 100644
--- a/web/onetouch_sim.html
+++ b/web/onetouch_sim.html
@@ -421,13 +421,14 @@
status.classList.remove("error");
}
}
+
function update_status(data) {
// Some form of error if PDA only panel.
if (data.panel_type.startsWith("PDA")) {
//document.getElementById("status").innerHTML = ' !!! PDA only panels are not Supported !!! '
//document.getElementById("status").classList.add("error");
- update_status_message("PDA only panels are not Supported", true);
+ update_status_message("Some PDA panels do not support OneTouch", true);
}
const versionlabel = document.getElementById("version");