diff --git a/README.md b/README.md index 0c06d0b..4d08af7 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,6 @@ AqualinkD will be moving over to github hosted runners for compiling, currently
# Updates in 3.0.0 (dev) -* WARNING V3.0 has undergone a significant amount code changes and refactoring, there may be issues. * Serial optimization for AqualinkD HAT. * Can now edit webconfig in aqmanager, added many UI customization options. * web/config.js is now web/config.json any custom settings will need to be migrated. @@ -164,7 +163,7 @@ AqualinkD will be moving over to github hosted runners for compiling, currently * HTTPS is for two way auth only, ie You create your own cert and load on both AqualinkD server and all client devices. * Example script to generate HTTPS certificates is in (./extras/generate-certs.sh) * Optimized updates to MQTT, web sockets & WebUI (only update when absolutely necessary) -* Added options to force upgrades in aqmanager. (add ?upgrade or ?devupgrade to url to enable upgrade button) +* Added option to select version to install, including dev releases. * MQTT Discovery for all supporting hubs (HomeAssistant Domoticz Hubitat OpenHAB etc) * Moved Domoticz support over to MQTT autodiscovery. * Change tile color & label for ph / orp & ppm tiles when values are out of optimal range. @@ -175,12 +174,13 @@ AqualinkD will be moving over to github hosted runners for compiling, currently * Changed caching of HTTP server. (Better for UI config updates) * Autoconfigure will now get panel size/type for panels that support PC-Dock interface. * Autoconfigure will *try* to work for PDA panels. +* PDA Color light fixes. +* PDA Dimmer lights now supported. * Cleaned up exit & errors when running as daemon and docker. * Fixed issues with external sensors and homekit. -* Added preliminary support for Jandy Infinite water color lights +* Added preliminary support for Jandy Infinite water color lights. - Need to finish off :- * HAT serial optimizations broke some USB serial adapters - * SWG not auto finding # Updates in 2.6.11 (Sept 14 2025) diff --git a/release/aqualinkd-arm64 b/release/aqualinkd-arm64 index f2b2acf..ac5444d 100755 Binary files a/release/aqualinkd-arm64 and b/release/aqualinkd-arm64 differ diff --git a/release/aqualinkd-armhf b/release/aqualinkd-armhf index a037168..dca7d0e 100755 Binary files a/release/aqualinkd-armhf and b/release/aqualinkd-armhf differ diff --git a/release/serial_logger-arm64 b/release/serial_logger-arm64 index ea486bb..f580784 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 787dc98..bf94007 100755 Binary files a/release/serial_logger-armhf and b/release/serial_logger-armhf differ diff --git a/source/allbutton_aq_programmer.c b/source/allbutton_aq_programmer.c index 4732c4a..87c0186 100644 --- a/source/allbutton_aq_programmer.c +++ b/source/allbutton_aq_programmer.c @@ -583,7 +583,7 @@ void *set_allbutton_light_programmode( void *ptr ) struct programmingThreadCtrl *threadCtrl; threadCtrl = (struct programmingThreadCtrl *) ptr; struct aqualinkdata *aqdata = threadCtrl->aqdata; - + const int seconds = 1000; waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_LIGHTPROGRAM_MODE); @@ -643,12 +643,14 @@ void *set_allbutton_light_programmode( void *ptr ) // Light is on, we know the mode, we can advance to next mode without re-setting program to 1 int cmode = ((clight_detail *)button->special_mask_ptr)->currentValue; if (cmode > 0) { - int numPrograms = get_num_light_modes(0); - int adv_steps = (val - cmode + numPrograms) % numPrograms; + int numPrograms = get_num_light_modes(0) - 1; // Need to ignore program 0=off, so reduce by 1 + //int adv_steps = (val - cmode + numPrograms) % numPrograms; + int adv_steps = ((val - cmode) % numPrograms + numPrograms) % numPrograms; LOG(ALLB_LOG, LOG_INFO, "Advancing Light program by %d (current=%d, new=%d, total=%d)\n",adv_steps, cmode, val, numPrograms); if (adv_steps > 0) { send_cmd(button->code); waitfor_queue2empty(); + delay(pmode * seconds); // 0.3 works, but using 0.4 to be safe } final_mode = val; val = adv_steps; @@ -662,7 +664,7 @@ void *set_allbutton_light_programmode( void *ptr ) //LOG(ALLB_LOG, LOG_INFO, "Not using advance mode (state=%d, usemode=%d)\n",button->led->state,useProgAdvance); } - const int seconds = 1000; + // Needs to start programming sequence with light on, if off we need to turn on for 15 seconds // before we can send the next off. if ( button->led->state != ON && iOn > 0) { diff --git a/source/aq_panel.c b/source/aq_panel.c index 1ee7b0e..5e618c1 100644 --- a/source/aq_panel.c +++ b/source/aq_panel.c @@ -1453,9 +1453,13 @@ bool setDeviceState(struct aqualinkdata *aqdata, int deviceIndex, bool isON, req LOG(PANL_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) { - if (button->special_mask & PROGRAM_LIGHT && isPDA_IAQT) { + if (button->special_mask & PROGRAM_LIGHT/* && isPDA_IAQT*/) { + //if ( isPDA_IAQT && isIAQL_ACTIVE) { // AqualinkTouch in PDA mode, we can program light. (if turing off, use standard AQ_PDA_DEVICE_ON_OFF below) - programDeviceLightMode(aqdata, (isON?USE_LAST_VALUE:0), deviceIndex, (source==NET_MQTT?true:false), source); + // programDeviceLightMode(aqdata, (isON?USE_LAST_VALUE:0), deviceIndex, (source==NET_MQTT?true:false), source); + //} else { + aq_programmer(AQ_SET_LIGHTCOLOR_MODE, button, (isON == false ? 0 : 4), true, aqdata); + //} } else { // If we are using AqualinkTouch with iAqualink enabled, we can send button on/off much faster using that. if ( isPDA_IAQT && isIAQL_ACTIVE) { @@ -1571,10 +1575,18 @@ void programDeviceLightBrightness(struct aqualinkdata *aqdata, int value, int de { //int extra_value = false; + clight_detail *light = getProgramableLight(aqdata, deviceIndex); + if (value < 0 || value > 100) { LOG(PANL_LOG,LOG_ERR, "Dimmer value %d is not valid, using %d\n",value,AQ_CLAMP(value,0,100)); value = AQ_CLAMP(value,0,100); } + + if (light->lightType == LC_DIMMER && value !=0 && value != 25&& value != 50 && value != 75 && value != 100) { + // Make sure we have 0/25/50/75 for LC_DIMMER + LOG(PANL_LOG,LOG_DEBUG, "Dimmer value %d is not valid, rounding to nearest 25!\n",value); + value = dimmer_mode_to_percent(dimmer_percent_to_mode_index(value)); + } if (expectMultiple) { // Queue up a request, this will call us back through with expectMultiple=false @@ -1585,8 +1597,6 @@ void programDeviceLightBrightness(struct aqualinkdata *aqdata, int value, int de return; } - clight_detail *light = getProgramableLight(aqdata, deviceIndex); - if (light == NULL || (light->lightType != LC_DIMMER2 && light->lightType != LC_DIMMER)) { LOG(PANL_LOG,LOG_ERR, "Can not set light brightness on device '%s'\n",aqdata->aqbuttons[deviceIndex].label); return; @@ -1597,6 +1607,11 @@ void programDeviceLightBrightness(struct aqualinkdata *aqdata, int value, int de return; } + if (isPDA_PANEL) { + aq_programmer(AQ_SET_LIGHTCOLOR_MODE, light->button, dimmer_percent_to_mode_index(value), false, aqdata); + return; + } + if (value == 0) { // We simply need to turn the light off at this point, so use allbutton key as it's the quickest. // but can't turn off a virtual light. @@ -1692,6 +1707,16 @@ void programDeviceLightMode(struct aqualinkdata *aqdata, int value, int deviceIn return; } + // If it's a PDA panel, + if (isPDA_PANEL) { + if (value == USE_LAST_VALUE) { + extra_value = true; + value = 1; + } + aq_programmer(AQ_SET_LIGHTCOLOR_MODE, light->button, value, extra_value, aqdata); + return; + } + // Turn on a light with no mode set. if (value == USE_LAST_VALUE) { extra_value = true; @@ -1883,7 +1908,7 @@ void updateLightProgram(struct aqualinkdata *aqdata, int value, clight_detail *l if (value > 0 && light->lastValue != value) { light->lastValue = value; if (_aqconfig_.save_light_programming_value && light->lightType == LC_PROGRAMABLE ) { - LOG(PANL_LOG,LOG_NOTICE, "Writing light programming value to config for %s\n",light->button->label); + LOG(PANL_LOG,LOG_INFO, "Writing light programming value to config for %s\n",light->button->label); writeCfg(aqdata); } } diff --git a/source/aq_programmer.c b/source/aq_programmer.c index fc6360a..0e38e10 100644 --- a/source/aq_programmer.c +++ b/source/aq_programmer.c @@ -502,6 +502,7 @@ void _aq_programmer_(program_type r_type, char *args, aqkey *button, int value, program_type type = r_type; + //DPRINTF("**** aq_programmer() with %d - %s\n",r_type, ptypeName(type)); // RS SerialAdapter is quickest for changing thermostat temps, so use that if enabeled. // VSP RPM can only be changed with oneTouch or iAquatouch so check / use those // VSP Program is only available with iAquatouch, so check / use that. diff --git a/source/color_lights.c b/source/color_lights.c index 2d34e06..398b8af 100644 --- a/source/color_lights.c +++ b/source/color_lights.c @@ -5,6 +5,7 @@ //#define COLOR_LIGHTS_C_ #include "color_lights.h" +#include "rs_msg_utils.h" /* @@ -185,8 +186,6 @@ void setColorLightsPanelVersion(uint8_t supported) bool is_valid_light_mode(clight_type type, int index) { - printf("Checking _color_light_options[%d][%d]\n",type,index); - if (type == LC_DIMMER2) { if (index >= 0 && index <=100) { return true; @@ -199,8 +198,6 @@ bool is_valid_light_mode(clight_type type, int index) return false; } - printf("result = %s\n", _color_light_options[type][index]); - return true; } @@ -317,6 +314,23 @@ const char *light_mode_name(clight_type type, int index, emulation_type protocol return _color_light_options[type][index]; } +int light_mode_index(clight_type type, const char *name) +{ + for (int i=0; i < LIGHT_COLOR_OPTIONS; i++) { + if (_color_light_options[type][i] == NULL) { + return AQ_UNKNOWN; + } + + if ( rsm_strmatch(_color_light_options[type][i], name) == 0) { + return i; + } else if (rsm_strmatch_ignore(name, _color_light_options[type][i], -1) == 0) { // Remove 1 char from check for USA! to USA. + return i; + } + } + + return AQ_UNKNOWN; +} + bool isShowMode(const char *mode) { diff --git a/source/color_lights.h b/source/color_lights.h index d6f7f2d..0d056c4 100644 --- a/source/color_lights.h +++ b/source/color_lights.h @@ -42,6 +42,7 @@ void clear_aqualinkd_light_modes(); bool set_currentlight_value(clight_detail *light, int index); bool is_valid_light_mode(clight_type type, int index); const char* lightTypeName(clight_type type); +int light_mode_index(clight_type type, const char *name); bool set_aqualinkd_light_mode_name(char *name, int index, bool isShow); const char *get_aqualinkd_light_mode_name(int index, bool *isShow); diff --git a/source/config.c b/source/config.c index bb20b98..3b49cfa 100644 --- a/source/config.c +++ b/source/config.c @@ -1753,7 +1753,10 @@ void check_print_config (struct aqualinkdata *aqdata) } if ( ! is_aqualink_touch_id(_aqconfig_.extended_device_id) && ! is_onetouch_id(_aqconfig_.extended_device_id)) { - setMASK(errors, ERR_VSP_EXTENDEDID); + // Ignore this error for PDA panels. PDA needs it to read VSP. (but can;t set VSP) + if (_aqconfig_.device_id != 0x60) { + setMASK(errors, ERR_VSP_EXTENDEDID); + } //LOG(AQUA_LOG,LOG_WARNING, "Config error, '%s' must be set for VSP's\n", CFG_N_extended_device_id_programming); } } @@ -1775,6 +1778,15 @@ void check_print_config (struct aqualinkdata *aqdata) LOG(AQUA_LOG,LOG_WARNING, "Config error, Light mode %d is only valid for a virtual button\n",LC_JANDYINFINATE); } } + if (aqdata->lights[i].lightType == LC_PROGRAMABLE && _aqconfig_.device_id == 0x60) { + LOG(AQUA_LOG,LOG_WARNING, "Config error, Light mode %d is not supported in PDA mode\n",LC_PROGRAMABLE); + } + if (aqdata->lights[i].lightType == LC_DIMMER && _aqconfig_.device_id == 0x60) { + LOG(AQUA_LOG,LOG_WARNING, "Config error, Light mode %d is not supported in PDA mode\n",LC_PROGRAMABLE); + } + if (aqdata->lights[i].lightType == LC_DIMMER2 && _aqconfig_.device_id == 0x60) { + LOG(AQUA_LOG,LOG_WARNING, "Config error, Light mode %d is not supported in PDA mode\n",LC_PROGRAMABLE); + } } // Check valid setting diff --git a/source/iaqtouch.c b/source/iaqtouch.c index c7e8ad0..7122126 100644 --- a/source/iaqtouch.c +++ b/source/iaqtouch.c @@ -823,10 +823,6 @@ void processPage(struct aqualinkdata *aqdata) } } -// if enable_iaqualink this poll count can be increased if we sit on the device status page -// all device status are quicker to update in enable_iaqualink, so leaves just pump/swg info to get. -#define FULL_STATUS_POLL_COUNT 200 // We did have this at 20, but put too much load on panel, (couldn't program light) -#define DEVICE_STATUS_POLL_COUNT 20 // This must be less than FULL_STATUS_POLL_COUNT //#define REQUEST_DEVICES_POLL_COUNT 30 // if _aqconfig_.enable_iaqualink=true then REQUEST_STATUS_POLL_COUNT will be used. @@ -1083,7 +1079,7 @@ if not programming && poll packet { LOG(IAQT_LOG,LOG_ERR,"Poll count=%d, too high, looks like page is stuck\n",_pollCnt); _pollCnt=0; } else { - LOG(IAQT_LOG,LOG_DEBUG,"Poll count=%d, Curent Page=0x%02hhx\n",_pollCnt, _currentPage); + //LOG(IAQT_LOG,LOG_DEBUG,"Poll count=%d, Curent Page=0x%02hhx\n",_pollCnt, _currentPage); } if (_pollCnt++ > FULL_STATUS_POLL_COUNT) { diff --git a/source/iaqtouch.h b/source/iaqtouch.h index c9282ff..8d1de61 100644 --- a/source/iaqtouch.h +++ b/source/iaqtouch.h @@ -4,6 +4,17 @@ #define IAQT_MSGLEN 21 + + +// if enable_iaqualink this poll count can be increased if we sit on the device status page +// all device status are quicker to update in enable_iaqualink, so leaves just pump/swg/chiller info to get. +// Below are numbers that are coprime or have few common factors, so less lightly to be executed at the same time. +#define FULL_STATUS_POLL_COUNT 199 // We did have this at 20, but put too much load on panel, (couldn't program light) +#define DEVICE_STATUS_POLL_COUNT 43 // This must be less than FULL_STATUS_POLL_COUNT +#define IAQALINK_STATUS_POLL_COUNT 101 + + + struct iaqt_page_button { char name[IAQT_MSGLEN]; unsigned char type; // 0x01 box, 0x08 icon wirlpool, 0x0b icon heater, 0x01 icon (main page), 0x07 light diff --git a/source/iaqualink.c b/source/iaqualink.c index 616231b..cb94e44 100644 --- a/source/iaqualink.c +++ b/source/iaqualink.c @@ -21,6 +21,7 @@ #include "aq_serial.h" #include "aqualink.h" #include "iaqualink.h" +#include "iaqtouch.h" #include "packetLogger.h" #include "aq_serial.h" #include "serialadapter.h" @@ -681,7 +682,7 @@ bool process_iaqualink_packet(unsigned char *packet, int length, struct aqualink if (packet[PKT_CMD] == 0x53) { cnt++; - if (cnt == 20) { // 20 is probably too low, should increase. (only RS16 and below) + if (cnt >= IAQALINK_STATUS_POLL_COUNT) { // 20 is probably too low, should increase. (only RS16 and below) cnt=0; /* sendid=sendid==0x18?0x60:0x18; diff --git a/source/json_messages.c b/source/json_messages.c index fd3953d..4ebf964 100644 --- a/source/json_messages.c +++ b/source/json_messages.c @@ -261,9 +261,9 @@ int LED2int(aqledstate state) } } -#define AUX_BUFFER_SIZE 200 +#define AUX_BUFFER_SIZE 500 -char *get_aux_information(aqkey *button, struct aqualinkdata *aqdata, char *buffer) +char *get_aux_information(aqkey *button, struct aqualinkdata *aqdata, char *buffer, bool homekit) { int i; int length = 0; @@ -293,7 +293,7 @@ char *get_aux_information(aqkey *button, struct aqualinkdata *aqdata, char *buff //printf("Button %s is ProgramableLight\n", button->name); for (i=0; i < aqdata->num_lights; i++) { if (button == aqdata->lights[i].button) { - if (aqdata->lights[i].lightType == LC_DIMMER2) { + if (aqdata->lights[i].lightType == LC_DIMMER2 ) { length += sprintf(buffer, ",\"type_ext\": \"light_dimmer\", \"Light_Type\":\"%d\", \"Light_Program\":\"%d\", \"Program_Name\":\"%d%%\" ", aqdata->lights[i].lightType, aqdata->lights[i].currentValue, @@ -304,7 +304,12 @@ char *get_aux_information(aqkey *button, struct aqualinkdata *aqdata, char *buff aqdata->lights[i].currentValue, get_currentlight_mode_name(aqdata->lights[i], ALLBUTTON)); //light_mode_name(aqdata->lights[i].lightType, aqdata->lights[i].currentValue, ALLBUTTON)); + + length += sprintf(buffer+length, ",\"light_programs\": ["); + length += build_color_light_jsonarray(aqdata->lights[i].lightType, &buffer[length], AUX_BUFFER_SIZE-length); + length += sprintf(buffer+length, "]"); } + return buffer; } } @@ -380,7 +385,7 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool // We will add this VButton as a thermostat continue; } - get_aux_information(&aqdata->aqbuttons[i], aqdata, aux_info); + get_aux_information(&aqdata->aqbuttons[i], aqdata, aux_info, homekit); //length += sprintf(buffer+length, "{\"type\": \"switch\", \"type_ext\": \"switch_vsp\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"int_status\": \"%d\" %s},", length += sprintf(buffer+length, "{\"type\": \"switch\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"int_status\": \"%d\" %s},", aqdata->aqbuttons[i].name, @@ -659,6 +664,7 @@ int build_aqualink_aqmanager_JSON(struct aqualinkdata *aqdata, char* buffer, int length += sprintf(buffer+length, ",\"panel_type_full\":\"%s\"",getPanelString()); length += sprintf(buffer+length, ",\"panel_type\":\"%s\"",getShortPanelString()); length += sprintf(buffer+length, ",\"panel_revision\":\"%s %s\"",aqdata->panel_cpu, aqdata->panel_rev );//8157 REV MMM", + length += sprintf(buffer+length, ",\"panel_reported_string\":\"%s\"",aqdata->panel_string); diff --git a/source/onetouch.c b/source/onetouch.c index 978a694..7e16236 100644 --- a/source/onetouch.c +++ b/source/onetouch.c @@ -778,8 +778,6 @@ void rs16led_update(struct aqualinkdata *aqdata, int updated) { } } - - bool new_menu(struct aqualinkdata *aqdata) { static bool initRS = false; @@ -800,8 +798,9 @@ bool new_menu(struct aqualinkdata *aqdata) } rtn = log_qeuiptment_status(aqdata); // Hit select to get to next menu ASAP. - if ( in_ot_programming_mode(aqdata) == false ) - ot_queue_cmd(KEY_ONET_SELECT); + if ( in_ot_programming_mode(aqdata) == false ) { + ot_queue_cmd(KEY_ONET_SELECT); // This makes it run through the menu's quicker. But no need to do it. + } break; case OTM_SET_TEMP: rtn = log_heater_setpoints(aqdata); diff --git a/source/pda_aq_programmer.c b/source/pda_aq_programmer.c index 5e6354b..c2d1653 100644 --- a/source/pda_aq_programmer.c +++ b/source/pda_aq_programmer.c @@ -45,6 +45,7 @@ bool waitForPDAMessageHighlight(struct aqualinkdata *aqdata, int highlighIndex, bool waitForPDAMessageType(struct aqualinkdata *aqdata, unsigned char mtype, int numMessageReceived); bool waitForPDAMessageTypes(struct aqualinkdata *aqdata, unsigned char mtype1, unsigned char mtype2, int numMessageReceived); bool waitForPDAMessageTypesOrMenu(struct aqualinkdata *aqdata, unsigned char mtype1, unsigned char mtype2, int numMessageReceived, char *text, int line); +bool waitForPDAMessages(struct aqualinkdata *aqdata, int numberMessages); bool goto_pda_menu(struct aqualinkdata *aqdata, pda_menu_type menu); bool wait_pda_selected_item(struct aqualinkdata *aqdata); bool waitForPDAnextMenu(struct aqualinkdata *aqdata); @@ -804,6 +805,39 @@ void *set_aqualink_PDA_device_on_off( void *ptr ) } #endif +bool waitForLightCycleMessage(struct aqualinkdata *aqdata) +{ + + waitfor_queue2empty(); + + // Wait for the message to appear. + waitForPDAMessages(aqdata, 15); + + // check the message did appear ..... PDA Menu Line 4 = Please + if (rsm_strmatch(pda_m_line(4), "Please") == 0) + { + // Wait for it to disapear + waitForPDAMessageTypes(aqdata, CMD_PDA_HIGHLIGHT, CMD_PDA_HIGHLIGHTCHARS, 60); // Long wait + } + else + { + LOG(PDA_LOG, LOG_WARNING, "PDA light Programming :- Didn't see Cycling message\n"); + return false; + } + + return true; +} + +bool waitForLightOffMessage(struct aqualinkdata *aqdata) +{ + if (rsm_strmatch(pda_m_line(3),"Light will turn") == 0) { + waitForPDAnextMenu(aqdata); + } else { + LOG(PDA_LOG,LOG_WARNING, "PDA light Programming :- Didn't see off message\n"); + return false; + } + return true; +} void *set_aqualink_PDA_light_mode( void *ptr ) { @@ -822,8 +856,11 @@ void *set_aqualink_PDA_light_mode( void *ptr ) struct programmerArgs *pargs = &threadCtrl->pArgs; aqkey *button = threadCtrl->pArgs.button; //unsigned char code = pargs->button->code; - int val = pargs->value; - int typ = ((clight_detail *)button->special_mask_ptr)->lightType; + int mode = pargs->value; + use_current_mode = pargs->alt_value; + //clight_type typ = ((clight_detail *)button->special_mask_ptr)->lightType; + clight_detail *light = (clight_detail *)button->special_mask_ptr; + clight_type typ = light->lightType; #else char *buf = (char*)threadCtrl->thread_args; int val = atoi(&buf[0]); @@ -845,26 +882,14 @@ void *set_aqualink_PDA_light_mode( void *ptr ) return ptr; } - clight_type lighttype = ((clight_detail *)button->special_mask_ptr)->lightType; + mode_name = light_mode_name(typ, mode, AQUAPDA); - - if (val <= 0) { - use_current_mode = true; - LOG(PDA_LOG, LOG_INFO, "PDA Light Programming #: %d, on button: %s, color light type: %d, using current mode\n", val, button->label, typ); - } else { - if (lighttype == LC_DIMMER2 || LC_DIMMER2 == LC_DIMMER) { - mode_name = light_mode_name(typ, val-1, AQUAPDA); - } else { - mode_name = light_mode_name(typ, val, AQUAPDA); - } - use_current_mode = false; - if (mode_name == NULL) { - LOG(PDA_LOG, LOG_ERR, "PDA Light Programming #: %d, on button: %s, color light type: %d, couldn't find mode name '%s'\n", val, button->label, typ, mode_name); + if (mode_name == NULL) { + LOG(PDA_LOG, LOG_ERR, "PDA Light Programming #: Received %d, on button: %s, color light type: %d, couldn't find mode name '%s'\n", mode, button->label, typ, mode_name); cleanAndTerminateThread(threadCtrl); return ptr; - } else { - LOG(PDA_LOG, LOG_INFO, "PDA Light Programming #: %d, on button: %s, color light type: %d, name '%s'\n", val, button->label, typ, mode_name); - } + } else { + LOG(PDA_LOG, LOG_INFO, "PDA Light Programming #: Received %d, on button: %s, color light type: %d, name '%s'\n", mode, button->label, typ, mode_name); } if (! goto_pda_menu(aqdata, PM_EQUIPTMENT_CONTROL)) { @@ -876,26 +901,80 @@ void *set_aqualink_PDA_light_mode( void *ptr ) if ( find_pda_menu_item(aqdata, button->label, 0) ) { // Remove 1 char to account for '100%' (4 chars not the usual 3) LOG(PDA_LOG,LOG_INFO, "PDA Light Programming, found device '%s', changing to '%s'\n",button->label,mode_name); force_queue_delete(); // NSF This is a bad thing to do. Need to fix this + // get the status as it would have been updated by pda.c seeing the state so we know it's accurate. + // BUT, it could change after next key press + aqledstate current_state = button->led->state; send_pda_cmd(KEY_PDA_SELECT); - waitForPDAMessageTypes(aqdata,CMD_PDA_HIGHLIGHT,CMD_PDA_HIGHLIGHTCHARS,15); - // if we get `PDA Menu Line 3 = Light will turn ` light is on and we need to press enter again. - // if we get `PDA Menu Line 2 = Dimmer Power ` we need to cycle over 25%/50%/75%/100% - // if we get `Menu Line 0 = Set Color` we can set color. - if (lighttype == LC_DIMMER2 || LC_DIMMER2 == LC_DIMMER) { + waitfor_queue2empty(); + waitForPDAMessages(aqdata, 15); // We get a number of different things here depending on light state, so simply wait 15 messages - } else { - if (strncasecmp(pda_m_line(3),"Light will turn", 15) == 0) { - send_pda_cmd(KEY_PDA_SELECT); - waitForPDAMessageTypes(aqdata,CMD_PDA_HIGHLIGHT,CMD_PDA_HIGHLIGHTCHARS,5); + if (typ == LC_DIMMER2 || typ == LC_DIMMER) { + if (mode == 0) { + // We are simply turning it off, and that would have happened above, so do nothing but wait for the light turn off message + //waitForLightOffMessage(aqdata); + } else { + if (current_state == ON && mode > 0) { // Need to use the state BEFORE the last key press + // Button was on, and we are changing mode so turn it on as the previous send_pda_cmd(KEY_PDA_SELECT) + // would have tured it off, so turn it on + send_pda_cmd(KEY_PDA_SELECT); + waitfor_queue2empty(); + waitForPDAMessages(aqdata, 5); + } + if (use_current_mode) { + char *current_mode = pda_m_hlight(); + send_pda_cmd(KEY_PDA_SELECT); + mode = light_mode_index(typ, current_mode); + LOG(PDA_LOG,LOG_INFO, "PDA light Programming :- Current Mode = %d '%s'\n",mode,current_mode); + // No light cycling message at this point. + } else { + //int current = rsm_atoi(pda_m_line(4)); + //while(current != mode_name) + int i = 0; + while(rsm_strmatch(pda_m_line(4), mode_name) != 0) { + send_pda_cmd(KEY_PDA_DOWN); + waitfor_queue2empty(); + waitForPDAMessages(aqdata, 2); + if (++i > 6) { + LOG(PDA_LOG,LOG_INFO, "PDA light Programming :- Couldn't find %s\n",mode_name); + } + } + send_pda_cmd(KEY_PDA_SELECT); + waitfor_queue2empty(); + waitForPDAMessages(aqdata, 5); + } } - if (use_current_mode && mode_name != NULL) { + } else { + // Turn off look for "Light will turn" and simply wait. + // Turn on to default, get light color name from menu and press select. + // Turn to mode, loop over mode options. + if (mode == 0) { // off + //waitForPDAMessageTypes(aqdata,CMD_PDA_HIGHLIGHT,CMD_STATUS,5); // Wait for the actual text to show. + if (waitForLightOffMessage(aqdata)) { + light->button->led->state = OFF; + } + } else if (use_current_mode) { // use current + //waitForPDAMessageTypes(aqdata,CMD_PDA_HIGHLIGHT,CMD_PDA_HIGHLIGHTCHARS,15); + char *current_color = pda_m_hlight(); + send_pda_cmd(KEY_PDA_SELECT); + // Reset the mode indet + mode = light_mode_index(typ, current_color); + LOG(PDA_LOG,LOG_INFO, "PDA light Programming :- Current Color = %d '%s'\n",mode,current_color); + waitForLightCycleMessage(aqdata); + } else { // set mode. + if (strncasecmp(pda_m_line(3),"Light will turn", 15) == 0) { + // If light is currently on, we will get this message, and need to clear it. + send_pda_cmd(KEY_PDA_SELECT); + waitForPDAMessageTypes(aqdata,CMD_PDA_HIGHLIGHT,CMD_PDA_HIGHLIGHTCHARS,15); + } if (find_pda_menu_item(aqdata,(char *)mode_name,strlen(mode_name))) { send_pda_cmd(KEY_PDA_SELECT); + waitForLightCycleMessage(aqdata); } else { LOG(PDA_LOG,LOG_ERR, "PDA Light Programming, could find mode '%s' for device '%s'\n",mode_name,button->label); } } } + if (mode > 0) {updateLightProgram(aqdata, mode, light);} } else { LOG(PDA_LOG,LOG_ERR, "PDA Light Programming, device '%s' not found\n",button->label); } @@ -1158,9 +1237,6 @@ bool waitForPDANextMessageType(struct aqualinkdata *aqdata, unsigned char mtype, } - - -// Wait for Message, hit return on particular menu. bool waitForPDAMessageTypesOrMenu(struct aqualinkdata *aqdata, unsigned char mtype1, unsigned char mtype2, int numMessageReceived, char *text, int line) { LOG(PDA_LOG,LOG_DEBUG, "waitForPDAMessageTypes 0x%02hhx or 0x%02hhx\n",mtype1,mtype2); @@ -1202,6 +1278,21 @@ bool waitForPDAMessageTypes(struct aqualinkdata *aqdata, unsigned char mtype1, u return waitForPDAMessageTypesOrMenu(aqdata, mtype1, mtype2, numMessageReceived, NULL, 0); } +bool waitForPDAMessages(struct aqualinkdata *aqdata, int numberMessages) +{ + int received=0; + + pthread_mutex_lock(&aqdata->active_thread.thread_mutex); + while( ++received <= numberMessages) + { + LOG(PDA_LOG,LOG_DEBUG, "waitForPDAMessages: %d of %d\n",received,numberMessages); + pthread_cond_wait(&aqdata->active_thread.thread_cond, &aqdata->active_thread.thread_mutex); + } + pthread_mutex_unlock(&aqdata->active_thread.thread_mutex); + + return true; +} + /* Use -1 for cur_val if you want this to find the current value and change it. Use number for cur_val to increase / decrease from known start point diff --git a/source/rs_msg_utils.c b/source/rs_msg_utils.c index ab8f22d..effa4a6 100644 --- a/source/rs_msg_utils.c +++ b/source/rs_msg_utils.c @@ -335,6 +335,8 @@ int rsm_strmatch(const char *haystack, const char *needle) // 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. +// ignore_chars negative will also strip spaces from end before removing char. +// ignore_chars positive will remove chars from end. // use case insensative for match. // so 'spa' !- 'spa mode' int rsm_strmatch_ignore(const char *haystack, const char *needle, int ignore_chars) @@ -349,12 +351,15 @@ int rsm_strmatch_ignore(const char *haystack, const char *needle, int ignore_cha while(isspace(*sp1)) sp1++; while(isspace(*sp2)) sp2++; while(isspace(*ep2) && (ep2 >= sp2)) ep2--; - if (ignore_chars > 0) + + if (ignore_chars < 0) { + while(isspace(*ep1) && (ep1 >= sp1)) ep1--; + ep1 = ep1 + ignore_chars; + }else 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; diff --git a/source/version.h b/source/version.h index e1562a2..9a43eb7 100644 --- a/source/version.h +++ b/source/version.h @@ -4,5 +4,5 @@ #define AQUALINKD_SHORT_NAME "AqualinkD" // Use Magor . Minor . Patch -#define AQUALINKD_VERSION "3.0.0 (beta 3)" +#define AQUALINKD_VERSION "3.0.0" \ No newline at end of file diff --git a/web/HA_tilePlugin.js b/web/HA_tilePlugin.js index 9f3683a..ebe3816 100644 --- a/web/HA_tilePlugin.js +++ b/web/HA_tilePlugin.js @@ -1,32 +1,42 @@ /* - For manual setup, follow the below. - For easy setup, read the online documentation +Below is the basic premise of the file if you need to create something for a different home automation - 1) put below in aqualinkd config.json - - "external_script": "/HA_tilePlugin.js" +exampleCreateTile() - 2) Configure any custom icons in aqualinkd config.json, example below 'light.back_floodlights' is HA ID:- +function exampleCreateTile() { + var tile = {}; + tile["id"] = "my.unique.id"; + tile["name"] = "Example Switch"; + tile["display"] = "true"; + tile["type"] = "switch"; // switch or value + tile["state"] = 'on'; + tile["status"] = tile["state"]; // status and state are different for AqualinkD, but for purposes of a switch or sensor they are the same. - "light.back_floodlights": { - "display": "true", - "on_icon": "extra/light-on.png", - "off_icon": "extra/light-off-grey.png" - }, + // Call AqualinkD function to create the tile and add to display. + createTile(tile); + // Make sure we use our own callback for button press. (only needed for a switch) + var subdiv = document.getElementById(tile["id"]); + subdiv.setAttribute('onclick', "exampleTilePressedCallback('" + tile["id"] + "')"); +} - 3) Add the HA ID's you want to the setTile function below :- - homeassistantAction("light.back_floodlights"); +// use this function to update the state or value of a tile +function exampleUpdateTileStatus() { + // For Switch + setTileOn("my.unique.id", 'on'); // valid values are 'on' and 'off' + // For value + setTileValue("my.unique.id", "0.00"); +} - 4) IN HOME ASSISTANT you will need to allow Cross-Origin Resource Sharing - edit configuration.yaml, and add below (change aqualinkd to the machine name running aqualinkd) - - http: - cors_allowed_origins - - http://aqualinkd - - 5) Add your HA API token & server below. +// This will be called when a tile is clicked in the UI. +function exampleTilePressedCallback() { + // Get the state of the tile + var state = (document.getElementById("my.unique.id").getAttribute('status') == 'off') ? 'on' : 'off'; + // Action what needs to happen. ie send request to home automation hub. + // Change / re-set the tile in teh display. + setTileOn("my.unique.id", state); +} */ // Add your HA API token @@ -38,6 +48,7 @@ var HA_SERVER = 'http://192.168.1.255:8123'; setupTiles(); +// Only use asunv to garantee order, also why homeassistantAction() function returns a promise. function setupTiles() { // If we have specific user agents setup, make sure one matches. if (_config?.HA_tilePlugin && _config?.HA_tilePlugin?.userAgents) { @@ -197,6 +208,7 @@ function getURL(service, action) { } } + function homeassistantAction(id, action="status") { var http = new XMLHttpRequest(); if (http) { @@ -229,6 +241,5 @@ function homeassistantAction(id, action="status") { http.setRequestHeader("Authorization", "Bearer "+HA_TOKEN); http.send('{"entity_id": "'+id+'"}'); } - } diff --git a/web/aqmanager.html b/web/aqmanager.html index 65ae90e..1251e3f 100644 --- a/web/aqmanager.html +++ b/web/aqmanager.html @@ -518,13 +518,12 @@