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 @@