diff --git a/Makefile b/Makefile index 311824b..a7515cf 100755 --- a/Makefile +++ b/Makefile @@ -98,7 +98,7 @@ ifeq ($(AQ_ONETOUCH), true) endif ifeq ($(AQ_IAQTOUCH), true) - SRCS := $(SRCS) iaqtouch.c iaqtouch_aq_programmer.c + SRCS := $(SRCS) iaqtouch.c iaqtouch_aq_programmer.c iaqualink.c AQ_FLAGS := $(AQ_FLAGS) -D AQ_IAQTOUCH endif diff --git a/README.md b/README.md index 11f4405..6012c2c 100644 --- a/README.md +++ b/README.md @@ -114,15 +114,22 @@ Designed to mimic AqualinkRS devices, used to fully configure the master control # Updates in 2.4.1 (under development) @@ -130,6 +137,10 @@ NEED TO FIX FOR THIS RELEASE. * This is faster, more reliable and does not intefear with the physical PDA device (like existing implimentation) * Please consider this very much BETA at the moment. * use `device_id=0x33` in aqualinkd.conf +* Added MQTT vsp_pump/speed/set for setting speed (RPM/GPM) by %, for automation hubs. +* cleaned up code for spa_mode and spa for newer pannels. +* Allow VSP to be asigned to virtual button. +* Fixed bug with timer not starting. # Updates in Release 2.4.0 * WARNING Breaking change if you use dimmer (please change button_??_lightMode from 6 to 10) diff --git a/release/aqualinkd-amd64 b/release/aqualinkd-amd64 index 3dd339d..3a514e4 100755 Binary files a/release/aqualinkd-amd64 and b/release/aqualinkd-amd64 differ diff --git a/release/aqualinkd-arm64 b/release/aqualinkd-arm64 index 9795867..671dff1 100755 Binary files a/release/aqualinkd-arm64 and b/release/aqualinkd-arm64 differ diff --git a/release/aqualinkd-armhf b/release/aqualinkd-armhf index 640f65a..62a1e3a 100755 Binary files a/release/aqualinkd-armhf and b/release/aqualinkd-armhf differ diff --git a/release/aqualinkd.conf b/release/aqualinkd.conf index 8fbbecd..da44eb5 100755 --- a/release/aqualinkd.conf +++ b/release/aqualinkd.conf @@ -269,7 +269,7 @@ use_panel_aux_labels=no # Below are settings for standard buttons on RS-8 Combo panel used as example. button_01_label=Filter Pump -button_02_label=Spa Mode +button_02_label=Spa button_03_label=Cleaner @@ -305,4 +305,4 @@ button_12_label=Solar Heater #virtual_button_02_label=Clean Mode #virtual_button_03_label = OneTouch 4 #virtual_button_04_label = OneTouch 5 -#virtual_button_05_label = OneTouch 6 \ No newline at end of file +#virtual_button_05_label = OneTouch 6 diff --git a/release/install.sh b/release/install.sh index 1856d66..fd5a771 100755 --- a/release/install.sh +++ b/release/install.sh @@ -132,8 +132,8 @@ fi # V2.3.9 has kind-a breaking change for config.js, so check existing and rename if needed # we added Aux_V? to the button list if [ -f "$WEBLocation/config.js" ]; then - # Test is if has AUX_V1 in file - if ! grep -q Aux_V1 $WEBLocation/config.js; then + # Test is if has AUX_V1 in file AND "Spa" is in file (Spa_mode changed to Spa) + if ! grep -q 'Aux_V1' $WEBLocation/$file || ! grep -q '"Spa"' $WEBLocation/$file; then dateext=`date +%Y%m%d_%H_%M_%S` echo "AqualinkD web config is old, making copy to $WEBLocation/config.js.$dateext" echo "Please make changes to new version $WEBLocation/config.js" diff --git a/release/serial_logger-amd64 b/release/serial_logger-amd64 index 8712448..738a1e3 100755 Binary files a/release/serial_logger-amd64 and b/release/serial_logger-amd64 differ diff --git a/release/serial_logger-arm64 b/release/serial_logger-arm64 index bf70e29..fcc041b 100755 Binary files a/release/serial_logger-arm64 and b/release/serial_logger-arm64 differ diff --git a/release/serial_logger-armhf b/release/serial_logger-armhf index f9170f2..a0724c0 100755 Binary files a/release/serial_logger-armhf and b/release/serial_logger-armhf differ diff --git a/source/aq_mqtt.h b/source/aq_mqtt.h index f1e115d..b693634 100644 --- a/source/aq_mqtt.h +++ b/source/aq_mqtt.h @@ -53,6 +53,7 @@ #define PUMP_MODE_TOPIC "/Mode" #define PUMP_STATUS_TOPIC "/Status" #define PUMP_PPC_TOPIC "/PPC" +#define PUMP_SPEED_TOPIC "/Speed" #define LIGHT_PROGRAM_TOPIC "/program" /* diff --git a/source/aq_panel.c b/source/aq_panel.c index f410f11..b1d858b 100644 --- a/source/aq_panel.c +++ b/source/aq_panel.c @@ -360,6 +360,32 @@ aqkey *addVirtualButton(struct aqualinkdata *aqdata, char *label, int vindex) { return button; } +// So the 0-100% should be 600-3450 RPM and 15-130 GPM (ie 1% would = 600 & 0%=off) +// (value-600) / (3450-600) * 100 +// (value) / 100 * (3450-600) + 600 + +//{{ (((value | float(0) - %d) / %d) * 100) | int }} - minspeed, (maxspeed - minspeed), +//{{ ((value | float(0) / 100) * %d) + %d | int }} - (maxspeed - minspeed), minspeed) + +int getPumpSpeedAsPercent(pump_detail *pump) { + int pValue = pump->pumpType==VFPUMP?pump->gpm:pump->rpm; + + if (pValue < pump->minSpeed) { + return 0; + } + + // Below will return 0% if pump is at min, so return max (caculation, 1) + return AQ_MAX( (int)((float)(pValue - pump->minSpeed) / (float)(pump->maxSpeed - pump->minSpeed) * 100) + 0.5, 1); +} + +int convertPumpPercentToSpeed(pump_detail *pump, int pValue) { + if (pValue >= 100) + return pump->maxSpeed; + else if (pValue <= 0) + return pump->minSpeed; + + return ( ((float)(pValue / (float)100) * (pump->maxSpeed - pump->minSpeed) + pump->minSpeed)) + 0.5; +} // 4,6,8,10,12,14 void initPanelButtons(struct aqualinkdata *aqdata, bool rs, int size, bool combo, bool dual) { @@ -717,6 +743,13 @@ bool setDeviceState(struct aqualinkdata *aqdata, int deviceIndex, bool isON, req { aqkey *button = &aqdata->aqbuttons[deviceIndex]; + if (button->special_mask & VIRTUAL_BUTTON && button->special_mask & VS_PUMP) { + // Virtual Button with VSP is always on. + LOG(PANL_LOG, LOG_INFO, "received '%s' for '%s', virtual pump is always on, ignoring", (isON == false ? "OFF" : "ON"), button->name); + button->led->state = ON; + return false; + } + if ((button->led->state == OFF && isON == false) || (isON > 0 && (button->led->state == ON || button->led->state == FLASH || button->led->state == ENABLE))) { diff --git a/source/aq_panel.h b/source/aq_panel.h index f3a6e57..5bad8eb 100644 --- a/source/aq_panel.h +++ b/source/aq_panel.h @@ -62,6 +62,9 @@ void addPanelIAQTouchInterface(); void addPanelRSserialAdapterInterface(); void changePanelToExtendedIDProgramming(); +int getPumpSpeedAsPercent(pump_detail *pump); +int convertPumpPercentToSpeed(pump_detail *pump, int value); // This is probable only needed internally + uint16_t getPanelSupport( char *rev_string, int rev_len); aqkey *addVirtualButton(struct aqualinkdata *aqdata, char *label, int vindex); diff --git a/source/aq_programmer.h b/source/aq_programmer.h index 87d9d60..38f5877 100644 --- a/source/aq_programmer.h +++ b/source/aq_programmer.h @@ -35,6 +35,7 @@ typedef enum emulation_type{ ONETOUCH, IAQTOUCH, AQUAPDA, // AQUAPALM and PDA are taken as specific type. + IAQUALNK, // iAqualink (wifi extra ID) JANDY_DEVICE, // Very rarley used. SIMULATOR } emulation_type; diff --git a/source/aq_serial.c b/source/aq_serial.c index 315a0d3..a480ea9 100644 --- a/source/aq_serial.c +++ b/source/aq_serial.c @@ -76,6 +76,8 @@ emulation_type getJandyDeviceType(unsigned char ID) { return AQUAPDA; if (ID >= 0x30 && ID <= 0x33) return IAQTOUCH; + if (ID >= 0xa0 && ID <= 0xa3) + return IAQUALNK; /* if (ID >= 0x00 && ID <= 0x03) @@ -286,6 +288,10 @@ const char* get_jandy_packet_type(unsigned char* packet , int length) return "LXi status"; break; + case 0x53: + return "iAqalnk Poll"; + break; + default: sprintf(buf, "Unknown '0x%02hhx'", packet[PKT_CMD]); return buf; diff --git a/source/aq_serial.h b/source/aq_serial.h index 487dfd3..b8f5b52 100644 --- a/source/aq_serial.h +++ b/source/aq_serial.h @@ -232,7 +232,7 @@ DEV_UNKNOWN_MASK = 0xF8; // Unknown mask, used to reset values #endif #define BTN_PUMP "Filter_Pump" -#define BTN_SPA "Spa_Mode" +#define BTN_SPA "Spa" #define BTN_AUX1 "Aux_1" #define BTN_AUX2 "Aux_2" #define BTN_AUX3 "Aux_3" diff --git a/source/aq_timer.c b/source/aq_timer.c index 69161cd..5a315ae 100644 --- a/source/aq_timer.c +++ b/source/aq_timer.c @@ -88,6 +88,7 @@ void start_timer(struct aqualinkdata *aq_data, /*aqkey *button,*/ int deviceInde tmthread->thread_id = 0; tmthread->duration_min = duration; tmthread->next = NULL; + tmthread->started_at = time(0); // This will get reset once we actually start. But need it here incase someone calls get_timer_left() before we start if( pthread_create( &tmthread->thread_id , NULL , timer_worker, (void*)tmthread) < 0) { LOG(TIMR_LOG, LOG_ERR, "could not create timer thread for button '%s'\n",button->name); diff --git a/source/aqualink.h b/source/aqualink.h index 1fddd1e..3c9810f 100644 --- a/source/aqualink.h +++ b/source/aqualink.h @@ -68,6 +68,12 @@ bool checkAqualinkTime(); // Only need to externalise this for PDA bool isVirtualButtonEnabled(); +#define PUMP_RPM_MAX 3450 +#define PUMP_RPM_MIN 600 +#define PUMP_GPM_MAX 130 +#define PUMP_GPM_MIN 15 + + enum { FAHRENHEIT, CELSIUS, @@ -179,6 +185,8 @@ typedef struct pumpd int rpm; int gpm; int watts; + int maxSpeed; // Max rpm or gpm depending on pump + int minSpeed; unsigned char pumpID; int pumpIndex; char pumpName[PUMP_NAME_LENGTH]; diff --git a/source/aqualinkd.c b/source/aqualinkd.c index 6ddf64b..2eb9a91 100644 --- a/source/aqualinkd.c +++ b/source/aqualinkd.c @@ -49,6 +49,7 @@ #include "onetouch_aq_programmer.h" #include "iaqtouch.h" #include "iaqtouch_aq_programmer.h" +#include "iaqualink.h" #include "version.h" #include "rs_msg_utils.h" #include "serialadapter.h" @@ -697,6 +698,12 @@ int startup(char *self, char *cfgFile) LOG(AQUA_LOG,LOG_NOTICE, "Config BTN %-13s = label %-15s | %s\n", _aqualink_data.aqbuttons[i].name, _aqualink_data.aqbuttons[i].label, ext); } + + if ( ((_aqualink_data.aqbuttons[i].special_mask & VIRTUAL_BUTTON) == VIRTUAL_BUTTON) && + ((_aqualink_data.aqbuttons[i].special_mask & VS_PUMP ) != VS_PUMP) && + (_aqconfig_.extended_device_id < 0x30 || _aqconfig_.extended_device_id > 0x33 ) ){ + LOG(AQUA_LOG,LOG_WARNING, "Config error, extended_device_id must be on of the folowing (0x30,0x31,0x32,0x33) to use virtual button : '%s'",_aqualink_data.aqbuttons[i].label); + } } /* for (i=0; i < _aqualink_data.total_buttons; i++) @@ -766,6 +773,41 @@ void caculate_ack_packet(int rs_fd, unsigned char *packet_buffer, emulation_type } //DEBUG_TIMER_STOP(_rs_packet_timer,AQUA_LOG,"AquaTouch Emulation type Processed packet in"); break; + case IAQUALNK: + /* + Probe | HEX: 0x10|0x02|0xa3|0x00|0xb5|0x10|0x03| + Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x00|0x13|0x10|0x03| + Unknown '0x61' | HEX: 0x10|0x02|0xa3|0x61|0x00|0x00|0x00|0x04|0x00|0x27|0x41|0x10|0x03| + Ack | HEX: 0x10|0x02|0x00|0x01|0x61|0x00|0x74|0x10|0x03| + Unknown '0x50' | HEX: 0x10|0x02|0xa3|0x50|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x20|0x00|0x25|0x10|0x03| + Ack | HEX: 0x10|0x02|0x00|0x01|0x50|0x00|0x63|0x10|0x03| + Unknown '0x51' | HEX: 0x10|0x02|0xa3|0x51|0x00|0x06|0x10|0x03| + Ack | HEX: 0x10|0x02|0x00|0x01|0x51|0x00|0x64|0x10|0x03| + Unknown '0x59' | HEX: 0x10|0x02|0xa3|0x59|0x00|0x0e|0x10|0x03| + Ack | HEX: 0x10|0x02|0x00|0x01|0x59|0x00|0x6c|0x10|0x03| + Unknown '0x52' | HEX: 0x10|0x02|0xa3|0x52|0x00|0x07|0x10|0x03| + Ack | HEX: 0x10|0x02|0x00|0x01|0x52|0x00|0x65|0x10|0x03| + Unknown '0x53' | HEX: 0x10|0x02|0xa3|0x53|0x08|0x10|0x03| + Ack | HEX: 0x10|0x02|0x00|0x01|0x3f|0x00|0x52|0x10|0x03| + Use byte 3 as return ack, except for 0x53=0x3f + */ + if (packet_buffer[PKT_CMD] == 0x53) { + /* + static int cnt=0; + if (cnt++ > 10) { + cnt=0; + LOG(IAQL_LOG,LOG_NOTICE, "Sending get bigass packet\n"); + send_extended_ack(rs_fd, 0x3f, 0x18); + } else*/ { + // Use 0x3f + send_extended_ack(rs_fd, 0x3f, 0x00); + } + send_jandy_command(rs_fd, get_rssa_cmd(packet_buffer[PKT_CMD]), 4); + } else { + // Use packet_buffer[PKT_CMD] + send_extended_ack(rs_fd, packet_buffer[PKT_CMD], 0x00); + } + break; #endif #ifdef AQ_PDA case AQUAPDA: @@ -879,7 +921,8 @@ void main_loop() pthread_mutex_init(&_aqualink_data.active_thread.thread_mutex, NULL); pthread_cond_init(&_aqualink_data.active_thread.thread_cond, NULL); - for (i=0; i < MAX_PUMPS; i++) { + //for (i=0; i < MAX_PUMPS; i++) { + for (i=0; i < _aqualink_data.num_pumps; i++) { _aqualink_data.pumps[i].rpm = TEMP_UNKNOWN; _aqualink_data.pumps[i].gpm = TEMP_UNKNOWN; _aqualink_data.pumps[i].watts = TEMP_UNKNOWN; @@ -888,6 +931,15 @@ void main_loop() _aqualink_data.pumps[i].status = TEMP_UNKNOWN; _aqualink_data.pumps[i].pStatus = PS_OFF; _aqualink_data.pumps[i].pressureCurve = TEMP_UNKNOWN; + + if (_aqualink_data.pumps[i].maxSpeed <= 0) { + _aqualink_data.pumps[i].maxSpeed = (_aqualink_data.pumps[i].pumpType==VFPUMP?PUMP_GPM_MAX:PUMP_RPM_MAX); + } + if (_aqualink_data.pumps[i].minSpeed <= 0) { + _aqualink_data.pumps[i].minSpeed = (_aqualink_data.pumps[i].pumpType==VFPUMP?PUMP_GPM_MIN:PUMP_RPM_MIN); + } + + //printf("arrayindex=%d, pump=%d, min=%d, max=%d\n",i,_aqualink_data.pumps[i].pumpIndex, _aqualink_data.pumps[i].minSpeed ,_aqualink_data.pumps[i].maxSpeed); } for (i=0; i < MAX_LIGHTS; i++) { @@ -1241,10 +1293,14 @@ void main_loop() } // Process and packets of devices we are acting as - if (packet_length > 0 && getProtocolType(packet_buffer) == JANDY && + if (packet_length > 0 && getProtocolType(packet_buffer) == JANDY && packet_buffer[PKT_DEST] != 0x00 && (packet_buffer[PKT_DEST] == _aqconfig_.device_id || packet_buffer[PKT_DEST] == _aqconfig_.rssa_device_id || - packet_buffer[PKT_DEST] == _aqconfig_.extended_device_id )) +#if defined AQ_ONETOUCH || defined AQ_IAQTOUCH + packet_buffer[PKT_DEST] == _aqconfig_.extended_device_id || + packet_buffer[PKT_DEST] == _aqconfig_.extended_device_id2 +#endif + )) { switch(getJandyDeviceType(packet_buffer[PKT_DEST])){ case ALLBUTTON: @@ -1267,6 +1323,10 @@ void main_loop() _aqualink_data.updated = process_pda_packet(packet_buffer, packet_length); caculate_ack_packet(rs_fd, packet_buffer, AQUAPDA); break; + case IAQUALNK: + _aqualink_data.updated = process_iaqualink_packet(packet_buffer, packet_length, &_aqualink_data); + caculate_ack_packet(rs_fd, packet_buffer, IAQUALNK); + break; default: break; } diff --git a/source/config.c b/source/config.c index 88e8ea2..dcd10ac 100644 --- a/source/config.c +++ b/source/config.c @@ -47,6 +47,8 @@ char *generate_mqtt_id(char *buf, int len); pump_detail *getpump(struct aqualinkdata *aqdata, int button); +bool populatePumpData(struct aqualinkdata *aqdata, char *pumpcfg ,aqkey *button, char *value); +pump_detail *getPumpFromButtonID(struct aqualinkdata *aqdata, aqkey *button); struct tmpPanelInfo { int size; @@ -403,9 +405,11 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { // Has to be before the below. _aqconfig_.extended_device_id_programming = text2bool(value); rtn=true; - } else if (strncasecmp(param, "extended_device_id", 9) == 0) { + } else if (strncasecmp(param, "extended_device_id", 18) == 0) { _aqconfig_.extended_device_id = strtoul(cleanalloc(value), NULL, 16); - //_config_parameters.onetouch_device_id != 0x00 + rtn=true; + } else if (strncasecmp(param, "enable_iaqualink", 16) == 0) { + _aqconfig_.enable_iaqualink = text2bool(value); rtn=true; #endif } else if (strncasecmp(param, "panel_type_size", 15) == 0) { @@ -682,63 +686,25 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { LOG(AQUA_LOG,LOG_ERR, "Config error, (colored|programmable) Lights limited to %d, ignoring %s'\n",MAX_LIGHTS,param); } rtn=true; - } else if (strncasecmp(param + 9, "_pumpID", 7) == 0) { - pump_detail *pump = getpump(aqdata, num); - if (pump != NULL) { - pump->pumpID = strtoul(cleanalloc(value), NULL, 16); - //if ( (int)pump->pumpID <= PENTAIR_DEC_PUMP_MAX) { - if ( (int)pump->pumpID >= PENTAIR_DEC_PUMP_MIN && (int)pump->pumpID <= PENTAIR_DEC_PUMP_MAX) { - pump->prclType = PENTAIR; - } else { - pump->prclType = JANDY; - //pump->pumpType = EPUMP; // For testing let the interface set this - } - } else { - LOG(AQUA_LOG,LOG_ERR, "Config error, VSP Pumps limited to %d, ignoring : %s",MAX_PUMPS,param); - } - rtn=true; - } else if (strncasecmp(param + 9, "_pumpIndex", 10) == 0) { //button_01_pumpIndex=1 - pump_detail *pump = getpump(aqdata, num); - if (pump != NULL) { - pump->pumpIndex = strtoul(value, NULL, 10); - } else { - LOG(AQUA_LOG,LOG_ERR, "Config error, VSP Pumps limited to %d, ignoring : %s",MAX_PUMPS,param); - } - rtn=true; - } else if (strncasecmp(param + 9, "_pumpType", 9) == 0) { - // This is not documented, as it's prefered for AqualinkD to find the pump type. - pump_detail *pump = getpump(aqdata, num); - if (pump != NULL) { - if ( stristr(value, "Pentair VS") != 0) - pump->pumpType = VSPUMP; - else if ( stristr(value, "Pentair VF") != 0) - pump->pumpType = VFPUMP; - else if ( stristr(value, "Jandy ePump") != 0) - pump->pumpType = EPUMP; - } else { - LOG(AQUA_LOG,LOG_ERR, "Config error, VSP Pumps limited to %d, ignoring : %s",MAX_PUMPS,param); - } - rtn=true; - } else if (strncasecmp(param + 9, "_pumpName", 9) == 0) { //button_01_pumpIndex=1 - pump_detail *pump = getpump(aqdata, num); - if (pump != NULL) { - //pump->pumpName = cleanalloc(value); - strncpy(pump->pumpName ,cleanwhitespace(value), PUMP_NAME_LENGTH-1); - } else { + } else if (strncasecmp(param + 9, "_pump", 5) == 0) { + + if ( ! populatePumpData(aqdata, param + 10, &aqdata->aqbuttons[num], value) ) + { LOG(AQUA_LOG,LOG_ERR, "Config error, VSP Pumps limited to %d, ignoring : %s",MAX_PUMPS,param); } + rtn=true; - } -#if defined AQ_IAQTOUCH + } +//#if defined AQ_IAQTOUCH } else if (strncasecmp(param, "virtual_button_", 15) == 0) { - rtn=true; + rtn=true; + int num = strtoul(param + 15, NULL, 10); if (_aqconfig_.paneltype_mask == 0) { // ERROR the vbutton will be irnored. LOG(AQUA_LOG,LOG_WARNING, "Config error, Panel type mush be definied before adding a virtual_button, ignored setting : %s",param); - } else if (_aqconfig_.extended_device_id < 0x30 || _aqconfig_.extended_device_id > 0x33 ) { - LOG(AQUA_LOG,LOG_WARNING, "Config error, extended_device_id must on of the folowing (0x30,0x31,0x32,0x33), ignored setting : %s",param); + //} else if (_aqconfig_.extended_device_id < 0x30 || _aqconfig_.extended_device_id > 0x33 ) { + // LOG(AQUA_LOG,LOG_WARNING, "Config error, extended_device_id must on of the folowing (0x30,0x31,0x32,0x33), ignored setting : %s",param); } else if (strncasecmp(param + 17, "_label", 6) == 0) { - int num = strtoul(param + 15, NULL, 10); char *label = cleanalloc(value); aqkey *button = addVirtualButton(aqdata, label, num); if (button != NULL) { @@ -746,15 +712,102 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { } else { LOG(AQUA_LOG,LOG_WARNING, "Error with '%s', total buttons=%d, config has %d already, ignoring!\n",param, TOTAL_BUTTONS, aqdata->total_buttons+1); } + } else if (strncasecmp(param + 17, "_pump", 5) == 0) { + char *vbname = malloc(sizeof(char*) * 10); + snprintf(vbname, 9, "%s%d", BTN_VAUX, num); + aqkey *vbutton = NULL; + for (int i = aqdata->virtual_button_start; i < aqdata->total_buttons; i++) { + //printf("Checking %s agasinsdt %s\n",aqdata->aqbuttons[i].name, vbname); + if ( strcmp( aqdata->aqbuttons[i].name, vbname) == 0 ) { + vbutton = &aqdata->aqbuttons[i]; + vbutton->led->state = ON; //Virtual pump is always on + if ( ! populatePumpData(aqdata, param + 18, vbutton, value) ) + { + LOG(AQUA_LOG,LOG_ERR, "Config error, VSP Pumps limited to %d, ignoring : %s",MAX_PUMPS,param); + } + break; + } + } + if (vbutton == NULL) { + LOG(AQUA_LOG,LOG_ERR, "Config error, could not find vitrual button for `%s`",param); + } } } -#endif +//#endif return rtn; } +// pumpcfg is pointer to pumpIndex, pumpName, pumpType pumpID, (ie pull off button_??_ or vurtual_button_??_) +bool populatePumpData(struct aqualinkdata *aqdata, char *pumpcfg ,aqkey *button, char *value) +{ + + pump_detail *pump = getPumpFromButtonID(aqdata, button); + if (pump == NULL) { + return false; + } + + if (strncasecmp(pumpcfg, "pumpIndex", 9) == 0) { + pump->pumpIndex = strtoul(value, NULL, 10); + } else if (strncasecmp(pumpcfg, "pumpType", 8) == 0) { + if ( stristr(value, "Pentair VS") != 0) + pump->pumpType = VSPUMP; + else if ( stristr(value, "Pentair VF") != 0) + pump->pumpType = VFPUMP; + else if ( stristr(value, "Jandy ePump") != 0) + pump->pumpType = EPUMP; + } else if (strncasecmp(pumpcfg, "pumpName", 8) == 0) { + strncpy(pump->pumpName ,cleanwhitespace(value), PUMP_NAME_LENGTH-1); + } else if (strncasecmp(pumpcfg, "pumpID", 6) == 0) { + pump->pumpID = strtoul(cleanalloc(value), NULL, 16); + if ( (int)pump->pumpID >= PENTAIR_DEC_PUMP_MIN && (int)pump->pumpID <= PENTAIR_DEC_PUMP_MAX) { + pump->prclType = PENTAIR; + } else { + pump->prclType = JANDY; + } + } else if (strncasecmp(pumpcfg, "pumpMaxSpeed", 12) == 0) { + pump->maxSpeed = strtoul(value, NULL, 10); + } else if (strncasecmp(pumpcfg, "pumpMinSpeed", 12) == 0) { + pump->minSpeed = strtoul(value, NULL, 10); + } + + return true; +} + +pump_detail *getPumpFromButtonID(struct aqualinkdata *aqdata, aqkey *button) +{ + int pi; + // Does it exist + for (pi=0; pi < aqdata->num_pumps; pi++) { + if (aqdata->pumps[pi].button == button) { + return &aqdata->pumps[pi]; + } + } + // Create new entry + if (aqdata->num_pumps < MAX_PUMPS) { + //printf ("Creating pump %d\n",button); + button->special_mask |= VS_PUMP; + aqdata->pumps[aqdata->num_pumps].button = button; + aqdata->pumps[aqdata->num_pumps].pumpType = PT_UNKNOWN; + aqdata->pumps[aqdata->num_pumps].rpm = TEMP_UNKNOWN; + aqdata->pumps[aqdata->num_pumps].watts = TEMP_UNKNOWN; + aqdata->pumps[aqdata->num_pumps].gpm = TEMP_UNKNOWN; + aqdata->pumps[aqdata->num_pumps].pStatus = PS_OFF; + aqdata->pumps[aqdata->num_pumps].pumpIndex = 0; + aqdata->pumps[aqdata->num_pumps].maxSpeed = TEMP_UNKNOWN; + aqdata->pumps[aqdata->num_pumps].minSpeed = TEMP_UNKNOWN; + //pumpType + aqdata->pumps[aqdata->num_pumps].pumpName[0] = '\0'; + aqdata->num_pumps++; + return &aqdata->pumps[aqdata->num_pumps-1]; + } + + return NULL; +} + +/* pump_detail *getpump(struct aqualinkdata *aqdata, int button) { //static int _pumpindex = 0; @@ -788,7 +841,7 @@ pump_detail *getpump(struct aqualinkdata *aqdata, int button) return NULL; } - +*/ void init_config() { diff --git a/source/config.h b/source/config.h index 460e9d2..2560756 100644 --- a/source/config.h +++ b/source/config.h @@ -44,7 +44,9 @@ struct aqconfig int16_t paneltype_mask; #if defined AQ_ONETOUCH || defined AQ_IAQTOUCH unsigned char extended_device_id; + unsigned char extended_device_id2; bool extended_device_id_programming; + bool enable_iaqualink; #endif bool deamonize; #ifndef AQ_MANAGER // Need to uncomment and clean up referances in future. diff --git a/source/hassio.c b/source/hassio.c index c99a0ae..3932596 100644 --- a/source/hassio.c +++ b/source/hassio.c @@ -127,9 +127,8 @@ const char *HASSIO_VSP_DISCOVER = "{" "\"payload_off\": \"0\"," "\"percentage_command_topic\": \"%s/%s/%s/set\"," // aqualinkd,filter_pump , RPM|GPM "\"percentage_state_topic\": \"%s/%s/%s\"," // aqualinkd,filter_pump , RPM|GPM - // "\"percentage_value_template\": \"{{ (((value | float(0) - %d) / %d) * 100) | int }}\"," // 600, (3450-600) - "\"percentage_value_template\": \"{%% if value | float(0) > %d %%} {{ (((value | float(0) - %d) / %d) * 100) | int }}{%% else %%} 1{%% endif %%}\"," // min,min,(max-min) - "\"percentage_command_template\": \"{{ ((value | float(0) / 100) * %d) + %d | int }}\"," // (3450-130), 600 + //"\"percentage_value_template\": \"{%% if value | float(0) > %d %%} {{ (((value | float(0) - %d) / %d) * 100) | int }}{%% else %%} 1{%% endif %%}\"," // min,min,(max-min) + //"\"percentage_command_template\": \"{{ ((value | float(0) / 100) * %d) + %d | int }}\"," // (3450-130), 600 "\"speed_range_max\": 100," "\"speed_range_min\": 1," // 18|12 600rpm|15gpm "\"qos\": 1," @@ -469,20 +468,9 @@ void publish_mqtt_hassio_discover(struct aqualinkdata *aqdata, struct mg_connect // VSP Pumps for (i=0; i < aqdata->num_pumps; i++) { - int maxspeed=3450; // Min is 600 - int minspeed=600; // 600 as % of max - char units[4]; - sprintf(units, "RPM"); - - if ( aqdata->pumps[i].pumpType == VFPUMP ) { - maxspeed=130; // Min is 15 - minspeed=15; // 15 as % of max - sprintf(units, "GPM"); - } + char units[] = "Speed"; // Create a FAN for pump against the button it' assigned to // In the future maybe change this to the pump# or change the sensors to button??? - // Need to change the max / min. These do NOT lomit the slider in hassio, only the MQTT limits. - // So the 0-100% should be 600-3450 RPM and 15-130 GPM (ie 1% would = 600 & 0%=off) sprintf(msg, HASSIO_VSP_DISCOVER, _aqconfig_.mqtt_aq_topic, aqdata->pumps[i].button->name,units, @@ -491,9 +479,7 @@ void publish_mqtt_hassio_discover(struct aqualinkdata *aqdata, struct mg_connect _aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name, _aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name, _aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name,units, - _aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name,units, - minspeed, minspeed, (maxspeed - minspeed), - (maxspeed - minspeed), minspeed); + _aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name,units); sprintf(topic, "%s/fan/aqualinkd/aqualinkd_%s_%s/config", _aqconfig_.mqtt_hass_discover_topic, aqdata->pumps[i].button->name, units); send_mqtt(nc, topic, msg); diff --git a/source/iaqtouch.c b/source/iaqtouch.c index 1245786..057266e 100644 --- a/source/iaqtouch.c +++ b/source/iaqtouch.c @@ -1000,6 +1000,13 @@ bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkd //printf("***** iAqualink Touch STARTUP Message ******* \n"); if (gotInit == false) { LOG(IAQT_LOG,LOG_DEBUG, "STARTUP Message\n"); + + if (_aqconfig_.enable_iaqualink) { + LOG(IAQT_LOG,LOG_DEBUG, "Enabling iAqualink Protocol\n"); + // Below will not send 0x29 since queueGetProgramData takes presidance, + //iaqt_queue_cmd(0x29); + _aqconfig_.extended_device_id2 = _aqconfig_.extended_device_id + 112; // 0x70 in dec + } //LOG(IAQT_LOG,LOG_ERR, "STARTUP REMOVED GET PANEL DATA FOR TESTING\n"); queueGetProgramData(IAQTOUCH, aq_data); gotInit = true; diff --git a/source/iaqualink.c b/source/iaqualink.c new file mode 100644 index 0000000..e62e982 --- /dev/null +++ b/source/iaqualink.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2017 Shaun Feakes - All rights reserved + * + * You may use redistribute and/or modify this code under the terms of + * the GNU General Public License version 2 as published by the + * Free Software Foundation. For the terms of this license, + * see . + * + * You are free to use this software under the terms of the GNU General + * Public License, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * https://github.com/sfeakes/aqualinkd + */ + +#include +#include +#include + +#include "aq_serial.h" +#include "aqualink.h" +#include "packetLogger.h" + +bool process_iaqualink_packet(unsigned char *packet, int length, struct aqualinkdata *aq_data) +{ + + //debuglogPacket(IAQL_LOG, packet, length, true, true); + + return true; +} \ No newline at end of file diff --git a/source/iaqualink.h b/source/iaqualink.h index 8f0b4eb..e255549 100644 --- a/source/iaqualink.h +++ b/source/iaqualink.h @@ -1,7 +1,16 @@ + +bool process_iaqualink_packet(unsigned char *packet, int length, struct aqualinkdata *aq_data); + + /* Read Jandy packet To 0xa3 of type Unknown '0x53' | HEX: 0x10|0x02|0xa3|0x53|0x08|0x10|0x03| Read Jandy packet To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x3f|0x00|0x52|0x10|0x03| + +Below get's sent to AqualinkTouch with iAqualink is enabled. +End of message is board cpu and panel type. +Read Jandy packet To 0x33 of type Unknown '0x70' | HEX: 0x10|0x02|0x33|0x70|0x0d|0x00|0x01|0x02|0x03|0x05|0x06|0x07|0x0e|0x0f|0x1a|0x1d|0x20|0x21|0x00|0x00|0x00|0x00|0x00|0x48|0x00|0x66|0x00|0x50|0x00|0x00|0x00|0xff|0x42|0x30|0x33|0x31|0x36|0x38|0x32|0x33|0x20|0x52|0x53|0x2d|0x34|0x20|0x43|0x6f|0x6d|0x62|0x6f|0x00|0x00|0x4b|0x10|0x03| + */ \ No newline at end of file diff --git a/source/net_services.c b/source/net_services.c index 7329b34..4130f25 100644 --- a/source/net_services.c +++ b/source/net_services.c @@ -75,6 +75,7 @@ void mqtt_broadcast_aqualinkstate(struct mg_connection *nc); #endif void reset_last_mqtt_status(); +bool uri_strcmp(const char *uri, const char *string); //static const char *s_http_port = "8080"; static struct mg_serve_http_opts _http_server_opts; @@ -914,11 +915,17 @@ void mqtt_broadcast_aqualinkstate(struct mg_connection *nc) _last_mqtt_aqualinkdata.pumps[i].rpm = _aqualink_data->pumps[i].rpm; //send_mqtt_aux_msg(nc, PUMP_TOPIC, i+1, PUMP_RPM_TOPIC, _aqualink_data->pumps[i].rpm); send_mqtt_aux_msg(nc, _aqualink_data->pumps[i].button->name, PUMP_RPM_TOPIC, _aqualink_data->pumps[i].rpm); + if (_aqualink_data->pumps[i].pumpType == EPUMP || _aqualink_data->pumps[i].pumpType == VSPUMP) { + send_mqtt_aux_msg(nc, _aqualink_data->pumps[i].button->name, PUMP_SPEED_TOPIC, getPumpSpeedAsPercent(&_aqualink_data->pumps[i])); + } } if (_aqualink_data->pumps[i].gpm != TEMP_UNKNOWN && _aqualink_data->pumps[i].gpm != _last_mqtt_aqualinkdata.pumps[i].gpm) { _last_mqtt_aqualinkdata.pumps[i].gpm = _aqualink_data->pumps[i].gpm; //send_mqtt_aux_msg(nc, PUMP_TOPIC, i+1, PUMP_GPH_TOPIC, _aqualink_data->pumps[i].gph); send_mqtt_aux_msg(nc, _aqualink_data->pumps[i].button->name, PUMP_GPM_TOPIC, _aqualink_data->pumps[i].gpm); + if (_aqualink_data->pumps[i].pumpType == VFPUMP) { + send_mqtt_aux_msg(nc, _aqualink_data->pumps[i].button->name, PUMP_SPEED_TOPIC, getPumpSpeedAsPercent(&_aqualink_data->pumps[i])); + } } if (_aqualink_data->pumps[i].watts != TEMP_UNKNOWN && _aqualink_data->pumps[i].watts != _last_mqtt_aqualinkdata.pumps[i].watts) { _last_mqtt_aqualinkdata.pumps[i].watts = _aqualink_data->pumps[i].watts; @@ -1254,7 +1261,7 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float rtn = uBad; } // Action a pump RPM/GPM message - } else if ((ri3 != NULL && ((strncasecmp(ri2, "RPM", 3) == 0) || (strncasecmp(ri2, "GPM", 3) == 0) || (strncasecmp(ri2, "VSP", 3) == 0)) && (strncasecmp(ri3, "set", 3) == 0))) { + } else if ((ri3 != NULL && ((strncasecmp(ri2, "RPM", 3) == 0) || (strncasecmp(ri2, "GPM", 3) == 0) || (strncasecmp(ri2, "Speed", 5) == 0) || (strncasecmp(ri2, "VSP", 3) == 0)) && (strncasecmp(ri3, "set", 3) == 0))) { found = false; // Is it a pump index or pump name if (strncmp(ri1, "Pump_", 5) == 0) { // Pump by number @@ -1274,9 +1281,15 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float return uBad; } } else { - LOG(NET_LOG,LOG_NOTICE, "%s: request to change pump %d %s to %d\n",actionName[from],pumpIndex+1, (strncasecmp(ri2, "GPM", 3) == 0)?"GPM":"RPM", round(value)); + if (strncasecmp(ri2, "Speed", 5) == 0) { + int val = convertPumpPercentToSpeed(&_aqualink_data->pumps[i], round(value)); + LOG(NET_LOG,LOG_NOTICE, "%s: request to change pump %d Speed to %d%%, using %s of %d\n",actionName[from],pumpIndex+1, round(value), (_aqualink_data->pumps[i].pumpType==VFPUMP?"GPM":"RPM" ) ,val); + panel_device_request(_aqualink_data, PUMP_RPM, pumpIndex, val, from); + } else { + LOG(NET_LOG,LOG_NOTICE, "%s: request to change pump %d %s to %d\n",actionName[from],pumpIndex+1, (strncasecmp(ri2, "GPM", 3) == 0)?"GPM":"RPM", round(value)); //create_program_request(from, PUMP_RPM, round(value), pumpIndex); - panel_device_request(_aqualink_data, PUMP_RPM, pumpIndex, round(value), from); + panel_device_request(_aqualink_data, PUMP_RPM, pumpIndex, round(value), from); + } } //_aqualink_data->unactioned.type = PUMP_RPM; //_aqualink_data->unactioned.value = round(value); @@ -1287,7 +1300,8 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float } } else { // Pump by button name for (i=0; i < _aqualink_data->total_buttons ; i++) { - if (strncmp(ri1, _aqualink_data->aqbuttons[i].name, strlen(_aqualink_data->aqbuttons[i].name)) == 0 ){ + //if (strncmp(ri1, _aqualink_data->aqbuttons[i].name, strlen(_aqualink_data->aqbuttons[i].name)) == 0 ){ + if ( uri_strcmp(ri1, _aqualink_data->aqbuttons[i].name)) { int pi; for (pi=0; pi < _aqualink_data->num_pumps; pi++) { if (_aqualink_data->pumps[pi].button == &_aqualink_data->aqbuttons[i]) { @@ -1304,9 +1318,15 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float return uBad; } } else { - LOG(NET_LOG,LOG_NOTICE, "%s: request to change pump %d %s to %d\n",actionName[from], pi+1, (strncasecmp(ri2, "GPM", 3) == 0)?"GPM":"RPM", round(value)); + if (strncasecmp(ri2, "Speed", 5) == 0) { + int val = convertPumpPercentToSpeed(&_aqualink_data->pumps[pi], round(value)); + LOG(NET_LOG,LOG_NOTICE, "%s: request to change pump %d Speed to %d%%, using %s of %d\n",actionName[from],_aqualink_data->pumps[pi].pumpIndex, round(value), (_aqualink_data->pumps[i].pumpType==VFPUMP?"GPM":"RPM" ) ,val); + panel_device_request(_aqualink_data, PUMP_RPM, _aqualink_data->pumps[pi].pumpIndex, val, from); + } else { + LOG(NET_LOG,LOG_NOTICE, "%s: request to change pump %d %s to %d\n",actionName[from], pi+1, (strncasecmp(ri2, "GPM", 3) == 0)?"GPM":"RPM", round(value)); //create_program_request(from, PUMP_RPM, round(value), _aqualink_data->pumps[pi].pumpIndex); - panel_device_request(_aqualink_data, PUMP_RPM, _aqualink_data->pumps[pi].pumpIndex, round(value), from); + panel_device_request(_aqualink_data, PUMP_RPM, _aqualink_data->pumps[pi].pumpIndex, round(value), from); + } } //_aqualink_data->unactioned.type = PUMP_RPM; //_aqualink_data->unactioned.value = round(value); @@ -1363,13 +1383,15 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float for (i=0; i < _aqualink_data->total_buttons && found==false; i++) { // If Label = "Spa", "Spa_Heater" will turn on "Spa", so need to check '/' on label as next character - if (strncmp(ri1, _aqualink_data->aqbuttons[i].name, strlen(_aqualink_data->aqbuttons[i].name)) == 0 || - (strncmp(ri1, _aqualink_data->aqbuttons[i].label, strlen(_aqualink_data->aqbuttons[i].label)) == 0 && ri1[strlen(_aqualink_data->aqbuttons[i].label)] == '/')) + //if (strncmp(ri1, _aqualink_data->aqbuttons[i].name, strlen(_aqualink_data->aqbuttons[i].name)) == 0 || + // (strncmp(ri1, _aqualink_data->aqbuttons[i].label, strlen(_aqualink_data->aqbuttons[i].label)) == 0 && ri1[strlen(_aqualink_data->aqbuttons[i].label)] == '/')) + if ( uri_strcmp(ri1, _aqualink_data->aqbuttons[i].name) || uri_strcmp(ri1, _aqualink_data->aqbuttons[i].label) ) { found = true; //create_panel_request(from, i, value, istimer); + LOG(NET_LOG,LOG_INFO, "%d: MATCH %s to topic %.*s\n",from,_aqualink_data->aqbuttons[i].name,uri_length, URI); + LOG(NET_LOG,LOG_INFO, "ri1=%s, length=%d char@=%c\n",ri1,strlen(_aqualink_data->aqbuttons[i].label),ri1[strlen(_aqualink_data->aqbuttons[i].label)] ); panel_device_request(_aqualink_data, atype, i, value, from); - //LOG(NET_LOG,LOG_INFO, "%s: MATCH %s to topic %.*s\n",from,_aqualink_data->aqbuttons[i].name,uri_length, URI); } } if(!found) { @@ -1387,6 +1409,28 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float return rtn; } +/* + Quicker and more accurate for us than normal strncmp, since we check for the trailing / at right position + check Spa against uri /Spa/set /Spa_mode/set / Spa_heater/set +*/ +bool uri_strcmp(const char *uri, const char *string) { + int i; + int len = strlen(string); + + // Check the trailing / on length first. + if (uri[len] != '/') { + return false; + } + + // Now check all characters + for (i=0; i < len; i++) { + if ( uri[i] != string[i] ){ + return false; + } + } + + return true; +} void action_mqtt_message(struct mg_connection *nc, struct mg_mqtt_message *msg) { char *rtnmsg; diff --git a/source/utils.c b/source/utils.c index 149e7cc..f7faf71 100644 --- a/source/utils.c +++ b/source/utils.c @@ -334,6 +334,9 @@ const char* logmask2name(logmask_t from) case SLOG_LOG: return "Serial Log:"; break; + case IAQL_LOG: + return "iAqualink :"; + break; case AQUA_LOG: default: return "AqualinkD: "; diff --git a/source/utils.h b/source/utils.h index a472b62..2f1821e 100644 --- a/source/utils.h +++ b/source/utils.h @@ -52,6 +52,7 @@ typedef int32_t logmask_t; #define DBGT_LOG (1 << 14) // Debug Timer #define SLOG_LOG (1 << 15) // Serial_Logger +#define IAQL_LOG (1 << 16) // iAqualink #define TIMR_LOG SCHD_LOG #define PANL_LOG PROG_LOG diff --git a/web/config.js b/web/config.js index a2bf0a1..b23dc95 100644 --- a/web/config.js +++ b/web/config.js @@ -6,7 +6,7 @@ // http://aqualink.ip.address/api/devices var devices = [ "Filter_Pump", - "Spa_Mode", + "Spa", "Aux_1", "Aux_2", "Aux_3", diff --git a/web/hk/Spa-off.png b/web/hk/Spa-off.png new file mode 120000 index 0000000..5f60636 --- /dev/null +++ b/web/hk/Spa-off.png @@ -0,0 +1 @@ +./switch-off.png \ No newline at end of file diff --git a/web/hk/Spa-on.png b/web/hk/Spa-on.png new file mode 120000 index 0000000..6afbc0b --- /dev/null +++ b/web/hk/Spa-on.png @@ -0,0 +1 @@ +./switch-on.png \ No newline at end of file