Add PDA light selection support

In previous version, a virtual button can be used to cycle over light
color. With this version, it supports selecting a specific color from
Web UI and HomeKit.

For HomeKit, the device is implemented as a DIMMER instead a switch and
will require AqualinkD HomeKit to be updated.

Though, the virtual cycle buton can be use with HomeKit if you don't
want a dimmer style button.

Signed-off-by: locnho <locnhinho@gmail.com>
pull/426/head
locnho 2025-06-06 00:46:43 -07:00
parent 90a22b67c5
commit b065ebfab3
8 changed files with 227 additions and 24 deletions

View File

@ -1325,7 +1325,7 @@ void programDeviceLightBrightness(struct aqualinkdata *aqdata, int value, int de
{
clight_detail *light = getProgramableLight(aqdata, deviceIndex);
if (!isRSSA_ENABLED) {
if (!isRSSA_ENABLED && !isPDA_PANEL) {
LOG(PANL_LOG,LOG_ERR, "Light mode brightness is only supported with `rssa_device_id` set\n");
return;
}
@ -1339,9 +1339,18 @@ void programDeviceLightBrightness(struct aqualinkdata *aqdata, int value, int de
if (light->lightType == LC_DIMMER) {
value = round(value / 25);
}
//
// For PDA panel, light supports selection from WS as well as HomeKit.
// WS is an integer while HomeKit is percentage. For proper processing
// of format, the perentage value is +100 so that we can tell the difference.
if (isPDA_PANEL && light->lightType == LC_PROGRAMABLE && source == NET_MQTT) {
value += 100;
}
if (!expectMultiple) {
if (value <= 0) {
if (isPDA_PANEL) {
programDeviceLightMode(aqdata, value, deviceIndex);
} else if (value <= 0) {
// Consider this a bad/malformed request to turn the light off.
panel_device_request(aqdata, ON_OFF, deviceIndex, 0, source);
} else {
@ -1363,12 +1372,6 @@ void programDeviceLightBrightness(struct aqualinkdata *aqdata, int value, int de
//void programDeviceLightMode(struct aqualinkdata *aqdata, int value, int button)
void programDeviceLightMode(struct aqualinkdata *aqdata, int value, int deviceIndex)
{
#ifdef AQ_PDA
if (isPDA_PANEL && !isPDA_IAQT) {
LOG(PANL_LOG,LOG_ERR, "Light mode control not supported in PDA mode\n");
return;
}
#endif
/*
int i;
clight_detail *light = NULL;
@ -1389,13 +1392,22 @@ void programDeviceLightMode(struct aqualinkdata *aqdata, int value, int deviceIn
char buf[LIGHT_MODE_BUFER];
if (light->lightType == LC_PROGRAMABLE ) {
//sprintf(buf, "%-5s%-5d%-5d%-5d%.2f",value,
if (isPDA_PANEL && light->lightType == LC_PROGRAMABLE) {
#ifdef AQ_PDA
sprintf(buf, "%-5d%-5d%-5d%-5d%.2f",value,
deviceIndex,
_aqconfig_.light_programming_initial_on,
_aqconfig_.light_programming_initial_off,
_aqconfig_.light_programming_mode );
_aqconfig_.light_programming_mode);
aq_programmer(AQ_PDA_SET_LIGHTPROGRAM_MODE, buf, aqdata);
#endif
} else if (light->lightType == LC_PROGRAMABLE ) {
//sprintf(buf, "%-5s%-5d%-5d%-5d%.2f",value,
sprintf(buf, "%-5d%-5d%-5d%-5d%.2f",value,
deviceIndex,
_aqconfig_.light_programming_initial_on,
_aqconfig_.light_programming_initial_off,
_aqconfig_.light_programming_mode);
aq_programmer(AQ_SET_LIGHTPROGRAM_MODE, buf, aqdata);
} else if (isRSSA_ENABLED) {
// If we are using rs-serial then turn light on first.

View File

@ -108,13 +108,13 @@ const func_ptr _prog_functions[AQP_RSSADAPTER_MAX] = {
[AQ_PDA_SET_FREEZE_PROTECT_TEMP] = set_aqualink_PDA_freeze_protectsetpoint,
[AQ_PDA_SET_TIME] = set_PDA_aqualink_time,
//[AQ_PDA_GET_POOL_SPA_HEATER_TEMPS]= get_aqualink_PDA_pool_spa_heater_temps,
[AQ_PDA_GET_FREEZE_PROTECT_TEMP] = get_PDA_aqualink_pool_spa_heater_temps
[AQ_PDA_GET_FREEZE_PROTECT_TEMP] = get_PDA_aqualink_pool_spa_heater_temps,
/*
[AQ_PDA_SET_BOOST] = set_PDA_aqualink_boost
[AQ_PDA_SET_SWG_PERCENT] = set_PDA_aqualink_SWG_setpoint
[AQ_PDA_GET_AUX_LABELS] = get_PDA_aqualink_aux_labels
*/
[AQ_PDA_SET_LIGHTPROGRAM_MODE] = set_PDA_light_programmode,
};
@ -327,7 +327,8 @@ bool in_light_programming_mode(struct aqualinkdata *aq_data)
if ( ( aq_data->active_thread.thread_id != 0 ) &&
( aq_data->active_thread.ptype == AQ_SET_LIGHTPROGRAM_MODE ||
aq_data->active_thread.ptype == AQ_SET_LIGHTCOLOR_MODE ||
aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE)
aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE ||
aq_data->active_thread.ptype == AQ_PDA_SET_LIGHTPROGRAM_MODE)
) {
return true;
}
@ -806,6 +807,7 @@ const char *ptypeName(program_type type)
return "Get programs";
break;
case AQ_SET_LIGHTPROGRAM_MODE:
case AQ_PDA_SET_LIGHTPROGRAM_MODE:
return "Set light color (using AqualinkD)";
break;
case AQ_SET_LIGHTCOLOR_MODE:
@ -1014,6 +1016,7 @@ const char *programtypeDisplayName(program_type type)
return "Programming: retrieving programs";
break;
case AQ_SET_LIGHTPROGRAM_MODE:
case AQ_PDA_SET_LIGHTPROGRAM_MODE:
case AQ_SET_LIGHTCOLOR_MODE:
case AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE:
return "Programming: setting light color";

View File

@ -78,6 +78,7 @@ typedef enum {
AQ_PDA_SET_TIME,
AQ_PDA_GET_POOL_SPA_HEATER_TEMPS,
AQ_PDA_GET_FREEZE_PROTECT_TEMP,
AQ_PDA_SET_LIGHTPROGRAM_MODE,
// ******** OneTouch Delimiter make sure to change MAX/MIN below
AQ_SET_ONETOUCH_PUMP_RPM,
AQ_SET_ONETOUCH_MACRO,

View File

@ -229,6 +229,16 @@ const char *get_currentlight_mode_name(clight_detail light, emulation_type proto
return _color_light_options[light.lightType][light.currentValue];
}
int get_currentlight_mode_name_count(clight_detail light, emulation_type protocol)
{
int i = 0;
for (; i < LIGHT_COLOR_OPTIONS; i++) {
if (_color_light_options[light.lightType][i] == NULL)
break;
}
return i;
}
// This should not be uses for getting current lightmode name since it doesn;t have full logic
const char *light_mode_name(clight_type type, int index, emulation_type protocol)

View File

@ -37,6 +37,7 @@ void set_currentlight_value(clight_detail *light, int index);
bool set_aqualinkd_light_mode_name(char *name, int index, bool isShow);
const char *get_aqualinkd_light_mode_name(int index, bool *isShow);
int get_currentlight_mode_name_count(clight_detail light, emulation_type protocol);
//char *_color_light_options_[LIGHT_COLOR_TYPES][LIGHT_COLOR_OPTIONS][LIGHT_COLOR_NAME];
@ -107,4 +108,4 @@ Magenta
50% 2
75% 3
100% 4
*/
*/

View File

@ -299,10 +299,11 @@ char *get_aux_information(aqkey *button, struct aqualinkdata *aqdata, char *buff
aqdata->lights[i].currentValue,
aqdata->lights[i].currentValue);
} else {
length += sprintf(buffer, ",\"type_ext\": \"switch_program\", \"Light_Type\":\"%d\", \"Light_Program\":\"%d\", \"Program_Name\":\"%s\" ",
length += sprintf(buffer, ",\"type_ext\": \"switch_program\", \"Light_Type\":\"%d\", \"Light_Program\":\"%d\", \"Program_Name\":\"%s\", \"Light_Program_Total\":\"%d\" ",
aqdata->lights[i].lightType,
aqdata->lights[i].currentValue,
get_currentlight_mode_name(aqdata->lights[i], ALLBUTTON));
get_currentlight_mode_name(aqdata->lights[i], ALLBUTTON),
get_currentlight_mode_name_count(aqdata->lights[i], ALLBUTTON));
//light_mode_name(aqdata->lights[i].lightType, aqdata->lights[i].currentValue, ALLBUTTON));
}
return buffer;

View File

@ -34,7 +34,7 @@
#include "config.h"
#include "aq_panel.h"
#include "rs_msg_utils.h"
#include "color_lights.h"
#ifdef AQ_DEBUG
#include "timespec_subtract.h"
@ -302,6 +302,7 @@ bool find_pda_menu_item(struct aqualinkdata *aq_data, char *menuText, int charli
//delay(500);
//wait_for_empty_cmd_buffer();
//waitForPDAMessageType(aq_data,CMD_PDA_HIGHLIGHT,2);
aq_data->last_packet_type = CMD_STATUS; // Force new CMD_PDA_HIGHLIGHT/CMD_MSG_LONG
waitForPDAMessageTypes(aq_data,CMD_PDA_HIGHLIGHT,CMD_MSG_LONG,8);
//waitForMessage(aq_data, NULL, 1);
index = (charlimit == 0)?pda_find_m_index(menuText):pda_find_m_index_case(menuText, charlimit);
@ -666,7 +667,63 @@ bool program_PDA_lightmode_next_color(struct aqualinkdata *aq_data, unsigned int
return true;
}
void _program_PDA_lightmode(struct aqualinkdata *aq_data, unsigned int device, bool next_color)
bool program_PDA_lightmode_select(struct aqualinkdata *aq_data, unsigned int device, const char *light_mode)
{
int i;
bool found = false;
for (i = 0; i < LIGHT_COLOR_OPTIONS*2; i++) {
if (strcasestr(pda_m_hlight(), light_mode) != NULL) {
found = true;
break;
}
send_pda_cmd(KEY_PDA_DOWN);
while (get_pda_queue_length() > 0) { delay(500); }
aq_data->last_packet_type = CMD_STATUS; // Reset last packet type
if (!_waitForPDAMessageTypesOrMenu(aq_data, CMD_PDA_HIGHLIGHT, CMD_PDA_SHIFTLINES, 5, "SET COLOR", 0, 0)) {
LOG(PDA_LOG,LOG_WARNING, "PDA Light: %s - wait for SET COLOR\n", aq_data->aqbuttons[device].label);
continue;
}
// If shift menu up, wait for another menu line
if (aq_data->last_packet_type == CMD_PDA_SHIFTLINES &&
!_waitForPDAMessageTypesOrMenu(aq_data, CMD_PDA_0x04, CMD_PDA_0x04, 10, "SET COLOR", 0, 0)) {
LOG(PDA_LOG,LOG_WARNING, "PDA Light: %s - wait for SET COLOR\n", aq_data->aqbuttons[device].label);
continue;
}
}
if (!found) {
LOG(PDA_LOG,LOG_ERR, "PDA Light: %s - light color not found\n", aq_data->aqbuttons[device].label);
return false;
}
//
// Before send the select key, make sure that the menu is SET COLOR
if (!strcasestr(pda_m_line(0), "SET COLOR")) {
LOG(PDA_LOG,LOG_ERR, "PDA Light: %s - no SET COLOR\n", aq_data->aqbuttons[device].label);
return false;
}
send_pda_cmd(KEY_PDA_SELECT);
while (get_pda_queue_length() > 0) { delay(500); }
// When cycle light, need to process the 'PLEASE' wait message. Then wait for the equipment menu
if (!waitForPDAMessageType(aq_data,CMD_PDA_CLEAR,20)) {
LOG(PDA_LOG,LOG_ERR, "PDA Light: %s - wait for clear menu\n", aq_data->aqbuttons[device].label);
return false;
}
if (!waitForPDAMessageTypes(aq_data, CMD_PDA_HIGHLIGHT, CMD_PDA_0x04, 20)) {
LOG(PDA_LOG,LOG_ERR, "PDA Light: %s - wait for CMD_PDA_HIGHLIGHT\n", aq_data->aqbuttons[device].label);
return false;
}
if (aq_data->last_packet_type == CMD_PDA_0x04) {
if (!waitForPDAMessageTypes(aq_data,CMD_PDA_HIGHLIGHT,CMD_PDA_HIGHLIGHTCHARS,50)) {
LOG(PDA_LOG,LOG_ERR, "PDA Light: %s - wait for next menu\n", aq_data->aqbuttons[device].label);
return false;
}
}
return true;
}
void _program_PDA_lightmode(struct aqualinkdata *aq_data, unsigned int device, bool next_color, const char *light_mode)
{
if (!waitForPDAMessageType(aq_data,CMD_PDA_CLEAR,20)) {
LOG(PDA_LOG,LOG_ERR, "PDA light: %s - wait for clear menu\n", aq_data->aqbuttons[device].label);
@ -685,7 +742,11 @@ void _program_PDA_lightmode(struct aqualinkdata *aq_data, unsigned int device, b
LOG(PDA_LOG,LOG_ERR, "PDA light: %s - wait for SET COLOR\n", aq_data->aqbuttons[device].label);
return;
}
if (next_color) {
if (light_mode != NULL) {
aq_data->last_packet_type = CMD_STATUS; // Reset last packet type
if (!program_PDA_lightmode_select(aq_data, device, light_mode))
return;
} else if (next_color) {
aq_data->last_packet_type = CMD_STATUS; // Reset last packet type
if (!program_PDA_lightmode_next_color(aq_data, device))
return;
@ -728,7 +789,7 @@ void _program_PDA_lightmode(struct aqualinkdata *aq_data, unsigned int device, b
LOG(PDA_LOG,LOG_ERR, "PDA light: %s - no SELECT NOW.\n", aq_data->aqbuttons[device].label);
return;
}
if (next_color) {
if (next_color || light_mode != NULL) {
// Can't send SELECT immediately. Need to wait for around 12 message.
waitForPDANextMessageType(aq_data, CMD_PDA_0x04, 12);
send_pda_cmd(KEY_PDA_SELECT);
@ -738,8 +799,12 @@ void _program_PDA_lightmode(struct aqualinkdata *aq_data, unsigned int device, b
return;
}
aq_data->last_packet_type = CMD_STATUS; // Reset last packet type
if (!program_PDA_lightmode_next_color(aq_data, device))
if (light_mode != NULL) {
if (!program_PDA_lightmode_select(aq_data, device, light_mode))
return;
} else if (!program_PDA_lightmode_next_color(aq_data, device)) {
return;
}
} else if (!waitForPDAMessageTypes(aq_data,CMD_PDA_HIGHLIGHT,CMD_PDA_HIGHLIGHTCHARS,60)) {
LOG(PDA_LOG,LOG_ERR, "PDA light: %s - wait for next menu\n", aq_data->aqbuttons[device].label);
return;
@ -752,7 +817,7 @@ void _program_PDA_lightmode(struct aqualinkdata *aq_data, unsigned int device, b
void program_PDA_lightmode(struct aqualinkdata *aq_data, unsigned int device, bool next_color)
{
_program_PDA_lightmode(aq_data, device, next_color);
_program_PDA_lightmode(aq_data, device, next_color, NULL);
waitfor_pda_queue2empty();
goto_pda_menu(aq_data, PM_HOME);
}
@ -1569,6 +1634,114 @@ void *get_PDA_aqualink_aux_labels( void *ptr ) {
return ptr;
}
void *set_PDA_light_programmode( void *ptr )
{
struct programmingThreadCtrl *threadCtrl;
threadCtrl = (struct programmingThreadCtrl *) ptr;
struct aqualinkdata *aq_data = threadCtrl->aq_data;
char device_name[15];
waitForSingleThreadOrTerminate(threadCtrl, AQ_PDA_SET_FREEZE_PROTECT_TEMP);
char *buf = (char*)threadCtrl->thread_args;
int val = atoi(&buf[0]);
int btn = atoi(&buf[5]);
int iOn = atoi(&buf[10]);
int iOff = atoi(&buf[15]);
float pmode = atof(&buf[20]);
bool turn_off = false;
if (btn < 0 || btn >= aq_data->total_buttons) {
LOG(PDA_LOG,LOG_ERR, "Can't progra light mode on button %d\n", btn);
cleanAndTerminateThread(threadCtrl);
return ptr;
}
aqkey *button = &aq_data->aqbuttons[btn];
clight_detail *clight = (clight_detail *) button->special_mask_ptr;
//
// For WS, value is the selector color.
// For MQTT, value is the DIMMER percentage + 100.
// The +100 is to tell whether it is selector color or DIMM percentage.
if (val >= 100) {
val -= 100;
if (val > 0) {
// Light program mode by DIMMER
// value index = value / (100/x) or value * x / 100
int val1 = val;
int total_btn = get_currentlight_mode_name_count(*clight, ALLBUTTON);
val = val * total_btn / 100;
if (val >= total_btn)
val = total_btn - 1;
LOG(PDA_LOG,LOG_DEBUG, "Light Programming #: translate %d to %d\n", val1, val);
} else {
turn_off = true;
}
} else if (val == 0) {
turn_off = true;
}
LOG(PDA_LOG,LOG_INFO, "Light Programming #: %d %s, on button: %s, with pause mode: %f (initial on=%d, initial off=%d)\n",
val, light_mode_name(clight->lightType, val, AQUAPDA), button->label, pmode, iOn, iOff);
for (int i = 0; i < 2; i++) {
//
// Before start, put back to home menu to ensure. Otherwise may get timing issue.
// We can be sending SELECT at EQUIPMNET menu while PDA immediate fall back to HOME
// menu. And if we fail to find the EQUIPMENT menu, retry one more time.
send_pda_cmd(KEY_PDA_BACK);
if (!waitForPDAnextMenu(aq_data)) {
LOG(PDA_LOG,LOG_ERR, "PDA Light: can't find HOME menu\n");
cleanAndTerminateThread(threadCtrl);
return ptr;
}
//
// Now, navigate to equipment menu
if (!goto_pda_menu(aq_data, PM_EQUIPTMENT_CONTROL)) {
if (i <= 0)
continue;
LOG(PDA_LOG,LOG_ERR, "PDA Light: can't find EQUIPTMENT CONTROL menu\n");
cleanAndTerminateThread(threadCtrl);
return ptr;
}
//
// Make sure we actually got to the EQUIPMENT menu
if (strcasestr(pda_m_line(0), " EQUIPMENT ") == NULL) {
if (i <= 0)
continue;
// Did not see EQUIPMENT menu
LOG(PDA_LOG,LOG_ERR, "PDA Light: can't find EQUIPTMENT CONTROL menu\n");
cleanAndTerminateThread(threadCtrl);
return ptr;
}
break;
}
LOG(PDA_LOG,LOG_DEBUG, "PDA Light: look for %s\n", aq_data->aqbuttons[btn].label);
sprintf(device_name,"%-13s",aq_data->aqbuttons[btn].label);
if (find_pda_menu_item(aq_data, device_name, 13)) {
LOG(PDA_LOG,LOG_DEBUG, "PDA Light: found for %s\n", device_name);
send_pda_cmd(KEY_PDA_SELECT);
while (get_pda_queue_length() > 0) { delay(500); }
if (turn_off)
_program_PDA_lightmode(aq_data, btn, false, NULL);
else
_program_PDA_lightmode(aq_data, btn, false, light_mode_name(clight->lightType, val, AQUAPDA));
waitfor_pda_queue2empty();
goto_pda_menu(aq_data, PM_HOME);
}
cleanAndTerminateThread(threadCtrl);
return ptr;
}
/*
bool waitForPDAMessage(struct aqualinkdata *aq_data, int numMessageReceived, unsigned char packettype)
{

View File

@ -43,6 +43,8 @@ void *set_PDA_aqualink_boost(void *ptr);
//bool set_PDA_aqualink_time(struct aqualinkdata *aq_data);
void *set_PDA_aqualink_time( void *ptr );
void *set_PDA_light_programmode( void *ptr );
/*
// These are from aq_programmer.c , exposed here for PDA AQ PROGRAMMER
void send_cmd(unsigned char cmd);
@ -55,4 +57,4 @@ void longwaitfor_queue2empty();
//void pda_programming_thread_check(struct aqualinkdata *aq_data);
#endif // AQ_PDA_PROGRAMMER_H_
#endif // AQ_PDA_PROGRAMMER_H_