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");