mirror of https://github.com/sfeakes/AqualinkD.git
parent
57da0a7803
commit
7416dc6ced
|
|
@ -160,8 +160,11 @@ Need to look at sub panel (combined panels)
|
|||
when serial port is wrong, can't edit config.
|
||||
-->
|
||||
|
||||
## Release 3.0.5 (dev)
|
||||
## Release 3.1.0
|
||||
* added device power to watts in MQTT discovery for power monitoring
|
||||
* Updated timers to be finer grained, (seconds vs minutes). - ie support for dosing pumps.
|
||||
* Added config options for button (circuit) runtimes to set default runtimes for a device.
|
||||
* Added support for onetouch macro's to OneTouch protocol.
|
||||
|
||||
## Release 3.0.4 (March 2026)
|
||||
* Fixed AqualinkD not starting when IP not assigned and MQTT enabled.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,14 @@ All notable changes to AqualinkD are documented here. Releases are listed in rev
|
|||
|
||||
---
|
||||
|
||||
## Release 3.1.0 (April 2026)
|
||||
* added device power to watts in MQTT discovery for power monitoring
|
||||
* Updated timers to be finer grained, (seconds vs minutes). - ie support for dosing pumps.
|
||||
* Added config options for button (circuit) runtimes to set default runtimes for a device.
|
||||
* Added support for onetouch macro's to OneTouch protocol.
|
||||
|
||||
---
|
||||
|
||||
## Release 3.0.4 (March 2026)
|
||||
* Fixed AqualinkD not starting when IP not assigned and MQTT enabled.
|
||||
* Fixed aqmanager config editor issue when adding pumpType.
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1337,6 +1337,8 @@ const char* getActionName(action_type type)
|
|||
break;
|
||||
case TIMER:
|
||||
return "Timer";
|
||||
case TIMER_SEC:
|
||||
return "Timer (Seconds)";
|
||||
break;
|
||||
case LIGHT_MODE:
|
||||
return "Light Mode";
|
||||
|
|
@ -1451,7 +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 ( button->runtime_sec > 0) {
|
||||
LOG(PANL_LOG, LOG_INFO, "'%s' is has runtime %u seconds, setting timer\n", button->name, button->runtime_sec);
|
||||
start_timer(aqdata, deviceIndex, 0, button->runtime_sec);
|
||||
}
|
||||
|
||||
#ifdef AQ_PDA
|
||||
if (isPDA_PANEL) {
|
||||
if (button->special_mask & PROGRAM_LIGHT/* && isPDA_IAQT*/) {
|
||||
//if ( isPDA_IAQT && isIAQL_ACTIVE) {
|
||||
|
|
@ -1525,9 +1533,12 @@ bool setDeviceState(struct aqualinkdata *aqdata, int deviceIndex, bool isON, req
|
|||
} else {
|
||||
aq_programmer(AQ_SET_IAQTOUCH_DEVICE_ON_OFF, button, (isON == false ? OFF : ON), deviceIndex, aqdata);
|
||||
set_pre_state = false;
|
||||
}
|
||||
}
|
||||
} else if (isONET_ENABLED){
|
||||
aq_programmer(AQ_SET_ONETOUCH_MACRO, button, (isON == false ? OFF : ON), deviceIndex, aqdata);
|
||||
set_pre_state = false;
|
||||
} else {
|
||||
LOG(PANL_LOG, LOG_ERR, "Can only use Aqualink Touch protocol for Virtual Buttons");
|
||||
LOG(PANL_LOG, LOG_ERR, "Can only use Aqualink Touch for Virtual Buttons");
|
||||
}
|
||||
} else {
|
||||
// Everything else, simply send the button code.
|
||||
|
|
@ -1831,7 +1842,7 @@ bool panel_device_request(struct aqualinkdata *aqdata, action_type type, int dev
|
|||
deviceIndex,
|
||||
value,
|
||||
getRequestName(source));
|
||||
} else if (type == ON_OFF || type == TIMER || type == LIGHT_BRIGHTNESS || type == LIGHT_MODE){
|
||||
} else if (type == ON_OFF || type == TIMER ||type == TIMER_SEC || type == LIGHT_BRIGHTNESS || type == LIGHT_MODE){
|
||||
LOG(PANL_LOG,LOG_INFO, "Device request type '%s' for deviceindex %d '%s' of value %d from '%s'\n",
|
||||
getActionName(type),
|
||||
deviceIndex,
|
||||
|
|
@ -1856,10 +1867,12 @@ bool panel_device_request(struct aqualinkdata *aqdata, action_type type, int dev
|
|||
}
|
||||
break;
|
||||
case TIMER:
|
||||
//setDeviceState(&aqdata->aqbuttons[deviceIndex], true);
|
||||
setDeviceState(aqdata, deviceIndex, true, source);
|
||||
//start_timer(aqdata, &aqdata->aqbuttons[deviceIndex], deviceIndex, value);
|
||||
start_timer(aqdata, deviceIndex, value);
|
||||
start_timer(aqdata, deviceIndex, value, 0);
|
||||
break;
|
||||
case TIMER_SEC:
|
||||
setDeviceState(aqdata, deviceIndex, true, source);
|
||||
start_timer(aqdata, deviceIndex, 0, value);
|
||||
break;
|
||||
case LIGHT_BRIGHTNESS:
|
||||
// Allow value=0 here (unlike LIGHT_MODE) since we could get multiple requests from a slider. (aka HomeKit)
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ const func_ptr _prog_functions[AQP_RSSADAPTER_MAX] = {
|
|||
[AQ_SET_IAQTOUCH_PUMP_VS_PROGRAM] = set_aqualink_iaqtouch_pump_vs_program,
|
||||
[AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE] = set_aqualink_iaqtouch_light_colormode,
|
||||
[AQ_SET_IAQTOUCH_DEVICE_ON_OFF] = set_aqualink_iaqtouch_device_on_off,
|
||||
[AQ_SET_ONETOUCH_MACRO] = set_aqualink_onetouch_macro,
|
||||
//[AQ_SET_IAQTOUCH_ONETOUCH_ON_OFF] = set_aqualink_iaqtouch_onetouch_on_off, // Not finished and not needed
|
||||
[AQ_PDA_INIT] = set_aqualink_PDA_init,
|
||||
[AQ_PDA_WAKE_INIT] = set_aqualink_PDA_wakeinit,
|
||||
|
|
@ -734,7 +735,7 @@ void _aq_programmer_(program_type r_type, char *args, aqkey *button, int value,
|
|||
return; // No need to create this as thread.
|
||||
break;
|
||||
default:
|
||||
// Should check that _prog_functions[type] is valid.
|
||||
// NSF REALLY Should check that _prog_functions[type] is valid.
|
||||
if( pthread_create( &programmingthread->thread_id , NULL , _prog_functions[type], (void*)programmingthread) < 0) {
|
||||
LOG(PROG_LOG, LOG_ERR, "could not create thread\n");
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ typedef enum pump_type {
|
|||
PT_UNKNOWN = -1,
|
||||
EPUMP, // = ePump AC & Jandy ePUMP
|
||||
VSPUMP, // = Intelliflo VS
|
||||
VFPUMP // = Intelliflo VF (GPM)
|
||||
VFPUMP, // = Intelliflo VF (GPM)
|
||||
} pump_type;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ struct timerthread {
|
|||
int deviceIndex;
|
||||
struct aqualinkdata *aqdata;
|
||||
int duration_min;
|
||||
u_int32_t duration_sec;
|
||||
struct timespec timeout;
|
||||
time_t started_at;
|
||||
struct timerthread *next;
|
||||
|
|
@ -57,7 +58,25 @@ int get_timer_left(aqkey *button)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void clear_timer(struct aqualinkdata *aqdata, /*aqkey *button,*/ int deviceIndex)
|
||||
uint32_t get_timer_left_sec(aqkey *button)
|
||||
{
|
||||
struct timerthread *t_ptr = find_timerthread(button);
|
||||
|
||||
if (t_ptr != NULL) {
|
||||
time_t now = time(0);
|
||||
long total_duration_sec = (t_ptr->duration_min * 60) + t_ptr->duration_sec;
|
||||
long elapsed_sec = (long)difftime(now, t_ptr->started_at);
|
||||
if (elapsed_sec >= total_duration_sec) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (uint32_t)(total_duration_sec - elapsed_sec);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void clear_timer(struct aqualinkdata *aqdata, int deviceIndex)
|
||||
{
|
||||
//struct timerthread *t_ptr = find_timerthread(button);
|
||||
struct timerthread *t_ptr = find_timerthread(&aqdata->aqbuttons[deviceIndex]);
|
||||
|
|
@ -65,18 +84,21 @@ void clear_timer(struct aqualinkdata *aqdata, /*aqkey *button,*/ int deviceIndex
|
|||
if (t_ptr != NULL) {
|
||||
LOG(TIMR_LOG, LOG_INFO, "Clearing timer for '%s'\n",t_ptr->button->name);
|
||||
t_ptr->duration_min = 0;
|
||||
t_ptr->duration_sec = 0;
|
||||
pthread_cond_broadcast(&t_ptr->thread_cond);
|
||||
}
|
||||
}
|
||||
|
||||
void start_timer(struct aqualinkdata *aqdata, /*aqkey *button,*/ int deviceIndex, int duration)
|
||||
|
||||
void start_timer(struct aqualinkdata *aqdata, int deviceIndex, int duration_min, u_int32_t duration_sec)
|
||||
{
|
||||
aqkey *button = &aqdata->aqbuttons[deviceIndex];
|
||||
struct timerthread *t_ptr = find_timerthread(button);
|
||||
|
||||
if (t_ptr != NULL) {
|
||||
LOG(TIMR_LOG, LOG_INFO, "Timer already active for '%s', resetting\n",t_ptr->button->name);
|
||||
t_ptr->duration_min = duration;
|
||||
t_ptr->duration_min = duration_min;
|
||||
t_ptr->duration_sec = duration_sec;
|
||||
pthread_cond_broadcast(&t_ptr->thread_cond);
|
||||
return;
|
||||
}
|
||||
|
|
@ -86,7 +108,8 @@ void start_timer(struct aqualinkdata *aqdata, /*aqkey *button,*/ int deviceIndex
|
|||
tmthread->button = button;
|
||||
tmthread->deviceIndex = deviceIndex;
|
||||
tmthread->thread_id = 0;
|
||||
tmthread->duration_min = duration;
|
||||
tmthread->duration_min = duration_min;
|
||||
tmthread->duration_sec = duration_sec;
|
||||
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
|
||||
|
||||
|
|
@ -158,8 +181,9 @@ void *timer_worker( void *ptr )
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// Wait the entire duration.
|
||||
pthread_mutex_lock(&tmthread->thread_mutex);
|
||||
|
||||
do {
|
||||
if (retval != 0) {
|
||||
LOG(TIMR_LOG, LOG_ERR, "pthread_cond_timedwait failed for '%s', error %d %s\n",tmthread->button->name,retval,strerror(retval));
|
||||
|
|
@ -169,15 +193,72 @@ void *timer_worker( void *ptr )
|
|||
break;
|
||||
}
|
||||
clock_gettime(CLOCK_REALTIME, &tmthread->timeout);
|
||||
tmthread->timeout.tv_sec += (tmthread->duration_min * 60);
|
||||
tmthread->timeout.tv_sec += (tmthread->duration_min * 60) + tmthread->duration_sec;
|
||||
tmthread->started_at = time(0);
|
||||
LOG(TIMR_LOG, LOG_INFO, "Will turn off '%s' in %d minutes\n",tmthread->button->name, tmthread->duration_min);
|
||||
LOG(TIMR_LOG, LOG_INFO, "Will turn off '%s' in %d:%d\n",tmthread->button->name, tmthread->duration_min, tmthread->duration_sec);
|
||||
} while ((retval = pthread_cond_timedwait(&tmthread->thread_cond, &tmthread->thread_mutex, &tmthread->timeout)) != ETIMEDOUT);
|
||||
pthread_mutex_unlock(&tmthread->thread_mutex);
|
||||
*/
|
||||
|
||||
// Waake every minute (or second) and set the dirty flag.
|
||||
pthread_mutex_lock(&tmthread->thread_mutex);
|
||||
|
||||
// 1. Calculate the absolute end time once
|
||||
struct timespec end_time;
|
||||
clock_gettime(CLOCK_REALTIME, &end_time);
|
||||
end_time.tv_sec += (tmthread->duration_min * 60) + tmthread->duration_sec;
|
||||
|
||||
tmthread->started_at = time(0);
|
||||
LOG(TIMR_LOG, LOG_INFO, "Timer started for '%s': %d:%02d total duration\n", tmthread->button->name, tmthread->duration_min, tmthread->duration_sec);
|
||||
|
||||
while (1) {
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
|
||||
// 2. Calculate remaining time
|
||||
long remaining_sec = end_time.tv_sec - now.tv_sec;
|
||||
|
||||
if (remaining_sec <= 0) {
|
||||
break; // Timer finished
|
||||
}
|
||||
|
||||
SET_DIRTY(tmthread->aqdata->is_dirty);
|
||||
// 3. Print time left
|
||||
if (remaining_sec >= 60) {
|
||||
LOG(TIMR_LOG, LOG_INFO, "Time left for '%s': %ldm %lds\n", tmthread->button->name, remaining_sec / 60, remaining_sec % 60);
|
||||
} else {
|
||||
LOG(TIMR_LOG, LOG_INFO, "Time left for '%s': %ld seconds\n", tmthread->button->name, remaining_sec);
|
||||
}
|
||||
|
||||
// Set next wake time
|
||||
tmthread->timeout = now;
|
||||
tmthread->timeout.tv_sec += (remaining_sec > 60) ? 60 : 1;
|
||||
|
||||
// Wait for timeout or signal
|
||||
retval = pthread_cond_timedwait(&tmthread->thread_cond, &tmthread->thread_mutex, &tmthread->timeout);
|
||||
|
||||
if (retval == 0) {
|
||||
// We were signaled! Someone changed tmthread->duration_min or duration_sec
|
||||
if (tmthread->duration_min <= 0 && tmthread->duration_sec <= 0) {
|
||||
break; // Cancelled
|
||||
}
|
||||
|
||||
LOG(TIMR_LOG, LOG_INFO, "Timer update received for '%s'. Recalculating...\n", tmthread->button->name);
|
||||
|
||||
// Update the end_time based on the NEW duration
|
||||
clock_gettime(CLOCK_REALTIME, &end_time);
|
||||
end_time.tv_sec += (tmthread->duration_min * 60) + tmthread->duration_sec;
|
||||
// Also update started_at so get_timer_left_sec() remains accurate
|
||||
tmthread->started_at = time(0);
|
||||
}
|
||||
else if (retval != ETIMEDOUT) {
|
||||
LOG(TIMR_LOG, LOG_ERR, "pthread_cond_timedwait error: %d\n", retval);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&tmthread->thread_mutex);
|
||||
|
||||
LOG(TIMR_LOG, LOG_NOTICE, "End timer for '%s'\n",tmthread->button->name);
|
||||
LOG(TIMR_LOG, LOG_NOTICE, "End timer for '%s'\n", tmthread->button->name);
|
||||
|
||||
// We need to detect if we ended on time or were killed.
|
||||
// If killed the device is probable off (or being set to off), so we should probably poll a few times before turning off.
|
||||
|
|
@ -187,7 +268,7 @@ void *timer_worker( void *ptr )
|
|||
|
||||
// if duration_min is 0 we were killed, if not we got here on timeout, so turn off device.
|
||||
|
||||
if (tmthread->duration_min != 0 && tmthread->button->led->state != OFF) {
|
||||
if ( (tmthread->duration_min != 0 || tmthread->duration_sec != 0) && tmthread->button->led->state != OFF) {
|
||||
LOG(TIMR_LOG, LOG_INFO, "Timer waking turning '%s' off\n",tmthread->button->name);
|
||||
panel_device_request(tmthread->aqdata, ON_OFF, tmthread->deviceIndex, false, NET_TIMER);
|
||||
} else if (tmthread->button->led->state == OFF) {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@
|
|||
|
||||
#include "aqualink.h"
|
||||
|
||||
void start_timer(struct aqualinkdata *aq_data, /*aqkey *button,*/ int deviceIndex, int duration);
|
||||
void start_timer(struct aqualinkdata *aqdata, int deviceIndex, int duration_min, uint32_t duration_sec);
|
||||
int get_timer_left(aqkey *button);
|
||||
void clear_timer(struct aqualinkdata *aq_data, /*aqkey *button,*/ int deviceIndex);
|
||||
u_int32_t get_timer_left_sec(aqkey *button);
|
||||
void clear_timer(struct aqualinkdata *aq_data, int deviceIndex);
|
||||
// Not best place for this, but leave it here so all requests are in net services, this is forward decleration of function in net_services.c
|
||||
#ifdef AQ_PDA
|
||||
void create_PDA_on_off_request(aqkey *button, bool isON);
|
||||
|
|
|
|||
|
|
@ -100,18 +100,14 @@ typedef struct aqualinkled
|
|||
|
||||
typedef struct aqualinkkey
|
||||
{
|
||||
//int number;
|
||||
//aqledstate ledstate; // In the future there is no need to aqled struct so move code over to this.
|
||||
aqled *led;
|
||||
char *label;
|
||||
char *name;
|
||||
//#ifdef AQ_PDA
|
||||
// char *pda_label;
|
||||
//#endif
|
||||
unsigned char code;
|
||||
unsigned char rssd_code;
|
||||
uint8_t special_mask;
|
||||
void *special_mask_ptr;
|
||||
uint32_t runtime_sec;
|
||||
} aqkey;
|
||||
|
||||
|
||||
|
|
@ -176,6 +172,7 @@ typedef enum action_type {
|
|||
SPA_HTR_INCREMENT, // Setpoint add value
|
||||
ON_OFF,
|
||||
TIMER,
|
||||
TIMER_SEC,
|
||||
LIGHT_MODE,
|
||||
LIGHT_BRIGHTNESS,
|
||||
DATE_TIME
|
||||
|
|
|
|||
|
|
@ -1154,12 +1154,10 @@ if (strlen(cleanwhitespace(value)) <= 0) {
|
|||
rtn=true;
|
||||
*/
|
||||
} else if (strncasecmp(param + 9, "_light", 6) == 0) {
|
||||
|
||||
if ( ! populateLightData(aqdata, param + 10, &aqdata->aqbuttons[num], value) )
|
||||
{
|
||||
LOG(AQUA_LOG,LOG_ERR, "Config error, %s Ignored!",param,value);
|
||||
}
|
||||
|
||||
rtn=true;
|
||||
|
||||
/*
|
||||
|
|
@ -1175,14 +1173,15 @@ if (strlen(cleanwhitespace(value)) <= 0) {
|
|||
LOG(AQUA_LOG,LOG_ERR, "Config error, Couldn't find light for '%s'\n",value);
|
||||
}*/
|
||||
} 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;
|
||||
}
|
||||
} else if (strncasecmp(param + 9, "_runtime", 8) == 0) {
|
||||
aqdata->aqbuttons[num].runtime_sec = time_string_to_seconds(value);
|
||||
rtn=true;
|
||||
}
|
||||
//#if defined AQ_IAQTOUCH
|
||||
} else if (strncasecmp(param, "virtual_button_", 15) == 0) {
|
||||
|
||||
|
|
@ -1270,12 +1269,18 @@ if (strlen(cleanwhitespace(value)) <= 0) {
|
|||
vbutton->rssd_code = NUL;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
LOG(AQUA_LOG,LOG_ERR, "Config error, could not find vitrual button for `%s`",param);
|
||||
}
|
||||
rtn=true;
|
||||
}
|
||||
} else if (strncasecmp(param + 17, "_runtime", 8) == 0) {
|
||||
aqkey *vbutton = getVirtualButton(aqdata, num);
|
||||
if (vbutton != NULL) {
|
||||
vbutton->runtime_sec = time_string_to_seconds(value);
|
||||
rtn=true;
|
||||
}
|
||||
}
|
||||
} else if (strncasecmp(param, "sensor_", 7) == 0) {
|
||||
int num = strtoul(param + 7, NULL, 10) - 1;
|
||||
if (num + 1 > MAX_SENSORS || num < 0) {
|
||||
|
|
@ -1644,7 +1649,7 @@ char *errorlevel2text(int level)
|
|||
#define ERR_VBUTTON_NO_EXTENDEDID (1<<8)
|
||||
#define ERR_VBUTTON_4_BOOSTON (1<<9)
|
||||
#define ERR_HEATPUMP_CHILLER (1<<10)
|
||||
|
||||
#define ERR_VBUTTON_ONET_NOID (1<<11)
|
||||
|
||||
|
||||
void check_print_config (struct aqualinkdata *aqdata)
|
||||
|
|
@ -1988,6 +1993,14 @@ void check_print_config (struct aqualinkdata *aqdata)
|
|||
//char ext[] = " VSP ID None | AL ID 0 ";
|
||||
char ext[120];
|
||||
ext[0] = '\0';
|
||||
|
||||
char runtime[11];
|
||||
runtime[0] = '\0';
|
||||
if (aqdata->aqbuttons[i].runtime_sec > 0) {
|
||||
seconds_to_time_string(aqdata->aqbuttons[i].runtime_sec, runtime, sizeof(runtime));
|
||||
sprintf(runtime+8, " |");
|
||||
}
|
||||
|
||||
for (j = 0; j < aqdata->num_pumps; j++) {
|
||||
if (aqdata->pumps[j].button == &aqdata->aqbuttons[i]) {
|
||||
sprintf(ext, "VSP ID 0x%02hhx | PumpID %-1d | %s",
|
||||
|
|
@ -2008,15 +2021,23 @@ void check_print_config (struct aqualinkdata *aqdata)
|
|||
sprintf(ext,"%-12s|", ((altlabel_detail *)aqdata->aqbuttons[i].special_mask_ptr)->altlabel);
|
||||
}
|
||||
|
||||
LOG(AQUA_LOG,LOG_NOTICE, "Button %-13s = label %-15.15s | %s\n",
|
||||
aqdata->aqbuttons[i].name, aqdata->aqbuttons[i].label, ext);
|
||||
LOG(AQUA_LOG,LOG_NOTICE, "Button %-13s = label %-15.15s | %s%s\n",
|
||||
aqdata->aqbuttons[i].name, aqdata->aqbuttons[i].label, runtime,ext);
|
||||
|
||||
|
||||
if ( ((aqdata->aqbuttons[i].special_mask & VIRTUAL_BUTTON) == VIRTUAL_BUTTON) &&
|
||||
((aqdata->aqbuttons[i].special_mask & VS_PUMP ) != VS_PUMP) &&
|
||||
( ! is_aqualink_touch_id(_aqconfig_.extended_device_id))) {
|
||||
//(_aqconfig_.extended_device_id < 0x30 || _aqconfig_.extended_device_id > 0x33 ) ){
|
||||
setMASK(errors, ERR_VBUTTON_NO_EXTENDEDID);
|
||||
if ( isVBUTTON(aqdata->aqbuttons[i].special_mask) && ( ! is_aqualink_touch_id(_aqconfig_.extended_device_id)))
|
||||
{
|
||||
// Onetouch protocol is good for if vbutton is pump or has onetouchID's
|
||||
if (is_onetouch_id(_aqconfig_.extended_device_id)) {
|
||||
//removeMASK(errors, ERR_VBUTTON_NO_EXTENDEDID);
|
||||
if ( !isVS_PUMP(aqdata->aqbuttons[i].special_mask) && (aqdata->aqbuttons[i].rssd_code == NUL)) {
|
||||
setMASK(errors, ERR_VBUTTON_ONET_NOID);
|
||||
} //else if (!isVS_PUMP(aqdata->aqbuttons[i].special_mask)) {
|
||||
//removeMASK(errors, ERR_VBUTTON_NO_EXTENDEDID);
|
||||
//}
|
||||
} else {
|
||||
setMASK(errors, ERR_VBUTTON_NO_EXTENDEDID);
|
||||
}
|
||||
//LOG(AQUA_LOG,LOG_WARNING, "Config error, extended_device_id must be one of the folowing (0x30,0x31,0x32,0x33) to use virtual button : '%s'",aqdata->aqbuttons[i].label);
|
||||
}
|
||||
|
||||
|
|
@ -2068,6 +2089,9 @@ void check_print_config (struct aqualinkdata *aqdata)
|
|||
if (isMASK_SET(errors, ERR_HEATPUMP_CHILLER)) {
|
||||
LOG(AQUA_LOG,LOG_ERR, "Config error, Heat pump / Chiller was not configured correctly\n");
|
||||
}
|
||||
if (isMASK_SET(errors, ERR_VBUTTON_ONET_NOID)) {
|
||||
LOG(AQUA_LOG,LOG_ERR, "Config error, for Virtual Buttons using OneTouch protocol, 'onetouchID' must be set\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -2149,6 +2173,7 @@ int save_config_js(const char* inBuf, int inSize, char* outBuf, int outSize, str
|
|||
aqdata->aqbuttons[i].special_mask_ptr = NULL;
|
||||
aqdata->aqbuttons[i].code = NUL;
|
||||
aqdata->aqbuttons[i].rssd_code = NUL;
|
||||
aqdata->aqbuttons[i].runtime_sec = 0;
|
||||
}
|
||||
aqdata->num_pumps = 0;
|
||||
aqdata->num_lights = 0;
|
||||
|
|
@ -2459,6 +2484,12 @@ bool writeCfg (struct aqualinkdata *aqdata)
|
|||
if (isVBUTTON_ALTLABEL(aqdata->aqbuttons[i].special_mask)) {
|
||||
fprintf(fp,"%s_altLabel=%s\n", prefix, ((altlabel_detail *)aqdata->aqbuttons[i].special_mask_ptr)->altlabel);
|
||||
}
|
||||
|
||||
if (aqdata->aqbuttons[i].runtime_sec > 0) {
|
||||
char tmstr[30];
|
||||
seconds_to_time_string(aqdata->aqbuttons[i].runtime_sec, tmstr, sizeof(tmstr));
|
||||
fprintf(fp,"%s_runtime=%s\n", prefix, tmstr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -323,7 +323,8 @@ char *get_aux_information(aqkey *button, struct aqualinkdata *aqdata, char *buff
|
|||
//printf("Button %s is Switch\n", button->name);
|
||||
length += sprintf(buffer+length, ",\"type_ext\": \"switch_timer\", \"timer_active\":\"%s\"", (((button->special_mask & TIMER_ACTIVE) == TIMER_ACTIVE)?JSON_ON:JSON_OFF) );
|
||||
if ((button->special_mask & TIMER_ACTIVE) == TIMER_ACTIVE) {
|
||||
length += sprintf(buffer+length,",\"timer_duration\":\"%d\"", get_timer_left(button));
|
||||
//length += sprintf(buffer+length,",\"timer_duration\":\"%d\"", get_timer_left(button));
|
||||
length += sprintf(buffer+length,",\"timer_duration\":\"%u\"", get_timer_left_sec(button));
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
|
@ -835,7 +836,7 @@ printf("Pump GPM %d\n",aqdata->pumps[i].watts);
|
|||
printf("Pump Type %d\n",aqdata->pumps[i].pumpType);
|
||||
*/
|
||||
//if (aqdata->pumps[i].pumpType != PT_UNKNOWN && (aqdata->pumps[i].rpm != TEMP_UNKNOWN || aqdata->pumps[i].gpm != TEMP_UNKNOWN || aqdata->pumps[i].watts != TEMP_UNKNOWN)) {
|
||||
if (aqdata->pumps[i].pumpType != PT_UNKNOWN ) {
|
||||
if (aqdata->pumps[i].pumpType != PT_UNKNOWN) {
|
||||
length += sprintf(buffer+length, "\"Pump_%d\":{\"name\":\"%s\",\"id\":\"%s\",\"RPM\":\"%d\",\"GPM\":\"%d\",\"Watts\":\"%d\",\"Pump_Type\":\"%s\",\"Status\":\"%d\"},",
|
||||
i+1,aqdata->pumps[i].button->label,aqdata->pumps[i].button->name,aqdata->pumps[i].rpm,aqdata->pumps[i].gpm,aqdata->pumps[i].watts,
|
||||
(aqdata->pumps[i].pumpType==VFPUMP?"vfPump":(aqdata->pumps[i].pumpType==VSPUMP?"vsPump":"ePump")),
|
||||
|
|
@ -861,7 +862,8 @@ printf("Pump Type %d\n",aqdata->pumps[i].pumpType);
|
|||
for (i=0; i < aqdata->total_buttons; i++)
|
||||
{
|
||||
if ((aqdata->aqbuttons[i].special_mask & TIMER_ACTIVE) == TIMER_ACTIVE) {
|
||||
length += sprintf(buffer+length, "\"%s\": \"%d\",", aqdata->aqbuttons[i].name, get_timer_left(&aqdata->aqbuttons[i]) );
|
||||
//length += sprintf(buffer+length, "\"%s\": \"%d\",", aqdata->aqbuttons[i].name, get_timer_left(&aqdata->aqbuttons[i]) );
|
||||
length += sprintf(buffer+length, "\"%s\": \"%u\",", aqdata->aqbuttons[i].name, get_timer_left_sec(&aqdata->aqbuttons[i]) );
|
||||
}
|
||||
}
|
||||
if (buffer[length-1] == ',')
|
||||
|
|
@ -1454,6 +1456,19 @@ int build_aqualink_config_JSON(char* buffer, int size, struct aqualinkdata *aqda
|
|||
} else
|
||||
length += result;
|
||||
|
||||
if (aqdata->aqbuttons[i].runtime_sec > 0) {
|
||||
sprintf(buf,"%s_runtime", prefix);
|
||||
char buf1[10];
|
||||
buf1[0] = '\0';
|
||||
seconds_to_time_string(aqdata->aqbuttons[i].runtime_sec, buf1, sizeof(buf1));
|
||||
stringptr = buf1; // Create a pointer to the buffer
|
||||
if ((result = json_cfg_element(buffer+length, size-length, buf, &stringptr, CFG_STRING, 0, NULL, 0)) <= 0) {
|
||||
LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length);
|
||||
return length;
|
||||
} else
|
||||
length += result;
|
||||
}
|
||||
|
||||
if (isVS_PUMP(aqdata->aqbuttons[i].special_mask))
|
||||
{
|
||||
if (((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->pumpIndex > 0) {
|
||||
|
|
|
|||
|
|
@ -1406,11 +1406,11 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float
|
|||
if ( strncasecmp(ri2, "ORP", 3) == 0 ) {
|
||||
SET_IF_CHANGED(_aqualink_data->orp, round(value), _aqualink_data->is_dirty);
|
||||
rtn = uActioned;
|
||||
LOG(NET_LOG,LOG_NOTICE, "%s: request to set ORP to %d\n",actionName[from],_aqualink_data->orp);
|
||||
LOG(NET_LOG,LOG_INFO, "%s: request to set ORP to %d\n",actionName[from],_aqualink_data->orp);
|
||||
} else if ( strncasecmp(ri2, "Ph", 2) == 0 ) {
|
||||
SET_IF_CHANGED(_aqualink_data->ph, value, _aqualink_data->is_dirty);
|
||||
rtn = uActioned;
|
||||
LOG(NET_LOG,LOG_NOTICE, "%s: request to set Ph to %.2f\n",actionName[from],_aqualink_data->ph);
|
||||
LOG(NET_LOG,LOG_INFO, "%s: request to set Ph to %.2f\n",actionName[from],_aqualink_data->ph);
|
||||
} else {
|
||||
LOG(NET_LOG,LOG_WARNING,"%s: ignoring, unknown URI %.*s\n",actionName[from],uri_length,URI);
|
||||
rtn = uBad;
|
||||
|
|
@ -1424,12 +1424,11 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float
|
|||
//bool istimer = false;
|
||||
action_type atype = ON_OFF;
|
||||
//int timer=0;
|
||||
if (strncasecmp(ri2, "timer", 5) == 0) {
|
||||
//istimer = true;
|
||||
atype = TIMER;
|
||||
//timer = value; // Save off timer
|
||||
//value = 1; // Make sure we turn device on if timer.
|
||||
} else if ( value > 1 || value < 0) {
|
||||
if (strncasecmp(ri2, "timer_sec", 9) == 0) {
|
||||
atype = TIMER_SEC;
|
||||
} else if (strncasecmp(ri2, "timer", 5) == 0) {
|
||||
atype = TIMER;
|
||||
}else if ( value > 1 || value < 0) {
|
||||
LOG(NET_LOG,LOG_WARNING, "%s: URI %s has invalid value %.2f\n",actionName[from], URI, value);
|
||||
*rtnmsg = INVALID_VALUE;
|
||||
rtn = uBad;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ static struct ot_macro _macros[3];
|
|||
bool _panel_version_P2 = false; // Older panels REV 0.1 and 0.2
|
||||
|
||||
|
||||
void set_macro_status();
|
||||
void set_macro_status(struct aqualinkdata *aqdata);
|
||||
void pump_update(struct aqualinkdata *aqdata, int updated);
|
||||
bool log_heater_setpoints(struct aqualinkdata *aqdata);
|
||||
|
||||
|
|
@ -789,7 +789,7 @@ bool new_menu(struct aqualinkdata *aqdata)
|
|||
|
||||
switch (menu_type) {
|
||||
case OTM_ONETOUCH:
|
||||
set_macro_status();
|
||||
set_macro_status(aqdata);
|
||||
break;
|
||||
case OTM_EQUIPTMENT_STATUS:
|
||||
if (initRS == false) {
|
||||
|
|
@ -835,7 +835,8 @@ bool new_menu(struct aqualinkdata *aqdata)
|
|||
return rtn;
|
||||
}
|
||||
|
||||
void set_macro_status()
|
||||
|
||||
void set_macro_status(struct aqualinkdata *aqdata)
|
||||
{
|
||||
// OneTouch Menu Line 2 = SPA MODE OFF
|
||||
// OneTouch Menu Line 5 = CLEAN MODE ON
|
||||
|
|
@ -860,6 +861,18 @@ void set_macro_status()
|
|||
LOG(ONET_LOG,LOG_DEBUG, "Macro #2 '%s' is %s\n",_macros[1].name,_macros[1].ison?"On":"Off");
|
||||
LOG(ONET_LOG,LOG_DEBUG, "Macro #3 '%s' is %s\n",_macros[2].name,_macros[2].ison?"On":"Off");
|
||||
|
||||
for (int i = aqdata->virtual_button_start; i < aqdata->total_buttons; i++) {
|
||||
if (aqdata->aqbuttons[i].rssd_code - 15 == 1) {
|
||||
SET_IF_CHANGED(aqdata->aqbuttons[i].led->state, _macros[0].ison?ON:OFF, aqdata->is_dirty);
|
||||
LOG(ONET_LOG,LOG_DEBUG, "Setting Macro #1 '%s' to %s for button '%s'\n",_macros[0].name,_macros[0].ison?"On":"Off",aqdata->aqbuttons[i].label);
|
||||
} else if (aqdata->aqbuttons[i].rssd_code - 15 == 2) {
|
||||
SET_IF_CHANGED(aqdata->aqbuttons[i].led->state, _macros[1].ison?ON:OFF, aqdata->is_dirty);
|
||||
LOG(ONET_LOG,LOG_DEBUG, "Setting Macro #1 '%s' to %s for button '%s'\n",_macros[1].name,_macros[1].ison?"On":"Off",aqdata->aqbuttons[i].label);
|
||||
} else if (aqdata->aqbuttons[i].rssd_code - 15 == 3) {
|
||||
SET_IF_CHANGED(aqdata->aqbuttons[i].led->state, _macros[2].ison?ON:OFF, aqdata->is_dirty);
|
||||
LOG(ONET_LOG,LOG_DEBUG, "Setting Macro #1 '%s' to %s for button '%s'\n",_macros[2].name,_macros[2].ison?"On":"Off",aqdata->aqbuttons[i].label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -262,6 +262,41 @@ bool goto_onetouch_system_menu(struct aqualinkdata *aqdata)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool goto_onetouch_macros_menu(struct aqualinkdata *aqdata)
|
||||
{
|
||||
|
||||
for(int i=0; i < 4; i++) {
|
||||
|
||||
if (get_onetouch_menu_type() == OTM_ONETOUCH) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (get_onetouch_menu_type() == OTM_SYSTEM) {
|
||||
select_onetouch_menu_item(aqdata, "ONETOUCH ON/OFF");
|
||||
waitfor_ot_queue2empty();
|
||||
//LOG(ONET_LOG,LOG_DEBUG, "goto_onetouch_macros_menu queue empty\n");
|
||||
waitForNextOT_Menu(aqdata);
|
||||
//LOG(ONET_LOG,LOG_DEBUG, "goto_onetouch_macros_menu next menu\n");
|
||||
//waitForOT_MessageTypes(aqdata,CMD_PDA_HIGHLIGHT,CMD_PDA_HIGHLIGHT,15);
|
||||
//LOG(ONET_LOG,LOG_DEBUG, "goto_onetouch_macros_menu CMD_PDA_HIGHLIGHT\n");
|
||||
//delay(500);
|
||||
//LOG(ONET_LOG,LOG_DEBUG, "goto_onetouch_macros_menu delay\n");
|
||||
//waitForOT_MessageTypes(aqdata,CMD_PDA_CLEAR,CMD_PDA_HIGHLIGHTCHARS,15);
|
||||
//waitForOT_MessageTypes(aqdata,CMD_PDA_HIGHLIGHT,CMD_PDA_HIGHLIGHT,15);
|
||||
//waitForOT_MessageType(aqdata,CMD_PDA_HIGHLIGHT,CMD_PDA_0x04,10);
|
||||
} else if (get_onetouch_menu_type() != OTM_ONETOUCH){
|
||||
send_ot_cmd(KEY_ONET_BACK);
|
||||
waitfor_ot_queue2empty();
|
||||
waitForNextOT_Menu(aqdata);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LOG(ONET_LOG,LOG_ERR, "OneTouch device programmer couldn't get to ONETOUCH menu\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool goto_onetouch_menu(struct aqualinkdata *aqdata, ot_menu_type menu)
|
||||
{
|
||||
bool equErr = false;
|
||||
|
|
@ -270,6 +305,10 @@ bool goto_onetouch_menu(struct aqualinkdata *aqdata, ot_menu_type menu)
|
|||
|
||||
LOG(ONET_LOG,LOG_DEBUG, "OneTouch device programmer request for menu %d\n",menu);
|
||||
|
||||
if (menu == OTM_ONETOUCH){
|
||||
return goto_onetouch_macros_menu(aqdata);
|
||||
}
|
||||
|
||||
if ( ! goto_onetouch_system_menu(aqdata) ) {
|
||||
LOG(ONET_LOG,LOG_ERR, "OneTouch device programmer failed to get system menu\n");
|
||||
return false;
|
||||
|
|
@ -569,9 +608,10 @@ void *set_aqualink_onetouch_freezeprotect( void *ptr )
|
|||
|
||||
void *set_aqualink_onetouch_macro( void *ptr )
|
||||
{
|
||||
/*
|
||||
struct programmingThreadCtrl *threadCtrl;
|
||||
threadCtrl = (struct programmingThreadCtrl *) ptr;
|
||||
//struct aqualinkdata *aqdata = threadCtrl->aqdata;
|
||||
struct aqualinkdata *aqdata = threadCtrl->aqdata;
|
||||
|
||||
//sprintf(msg, "%-5d%-5d",index, (strcmp(value, "on") == 0)?ON:OFF);
|
||||
// Use above to set
|
||||
|
|
@ -585,13 +625,58 @@ void *set_aqualink_onetouch_macro( void *ptr )
|
|||
unsigned int device = atoi(&buf[0]);
|
||||
unsigned int state = atoi(&buf[5]);
|
||||
#endif
|
||||
*/
|
||||
struct programmingThreadCtrl *threadCtrl;
|
||||
threadCtrl = (struct programmingThreadCtrl *) ptr;
|
||||
struct aqualinkdata *aqdata = threadCtrl->aqdata;
|
||||
|
||||
#ifdef NEW_AQ_PROGRAMMER
|
||||
struct programmerArgs *pargs = &threadCtrl->pArgs;
|
||||
aqkey *button = threadCtrl->pArgs.button;
|
||||
int value = pargs->value;
|
||||
#else
|
||||
//int val = atoi((char*)threadCtrl->thread_args);
|
||||
#endif
|
||||
|
||||
if (! isVBUTTON(button->special_mask)){
|
||||
LOG(ONET_LOG,LOG_ERR, "OneTouch macro programmer only supports VBUTTON macros\n");
|
||||
return ptr;
|
||||
}
|
||||
|
||||
waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_ONETOUCH_MACRO);
|
||||
|
||||
LOG(ONET_LOG,LOG_DEBUG, "OneTouch Marco\n");
|
||||
|
||||
LOG(ONET_LOG,LOG_ERR, "OneTouch Macro not implimented (device=%d|state=%d)\n",button->label,state);
|
||||
//LOG(ONET_LOG,LOG_ERR, "OneTouch Macro not implimented (device=%d|state=%d)\n",button->label,state);
|
||||
|
||||
if ( !goto_onetouch_menu(aqdata, OTM_ONETOUCH) ){
|
||||
LOG(ONET_LOG,LOG_ERR, "OneTouch device programmer failed to get heater temp menu\n");
|
||||
}
|
||||
|
||||
// Check button is not= value. (don't do this before as the menu command above may change the state of the button)
|
||||
if (button->led->state == value) {
|
||||
LOG(ONET_LOG,LOG_DEBUG, "OneTouch Macro '%s' already in desired state, no change needed\n", button->label);
|
||||
cleanAndTerminateThread(threadCtrl);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
switch(button->rssd_code - 15) {
|
||||
case 1:
|
||||
send_ot_cmd(KEY_ONET_SELECT_1);
|
||||
SET_IF_CHANGED(button->led->state, !button->led->state, aqdata->is_dirty);
|
||||
break;
|
||||
case 2:
|
||||
send_ot_cmd(KEY_ONET_SELECT_2);
|
||||
SET_IF_CHANGED(button->led->state, !button->led->state, aqdata->is_dirty);
|
||||
break;
|
||||
case 3:
|
||||
send_ot_cmd(KEY_ONET_SELECT_3);
|
||||
SET_IF_CHANGED(button->led->state, !button->led->state, aqdata->is_dirty) ;
|
||||
break;
|
||||
default:
|
||||
LOG(ONET_LOG,LOG_ERR, "OneTouch Macro programmer only has 3 buttons OneTouchID %d is invalid\n",button->rssd_code - 15);
|
||||
}
|
||||
|
||||
cleanAndTerminateThread(threadCtrl);
|
||||
|
||||
// just stop compiler error, ptr is not valid as it's just been freed
|
||||
|
|
|
|||
|
|
@ -968,6 +968,69 @@ char *prittyString(char *str)
|
|||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
*/
|
||||
/**
|
||||
* Converts a time string to total seconds.
|
||||
* Supported formats: "HH:MM:SS", "MM:SS", or "SS"
|
||||
*/
|
||||
uint32_t time_string_to_seconds(const char *time_str) {
|
||||
if (time_str == NULL) return 0;
|
||||
|
||||
int parts[3] = {0, 0, 0};
|
||||
int count = 0;
|
||||
|
||||
// Count the number of colons to determine the format
|
||||
const char *p = time_str;
|
||||
int colons = 0;
|
||||
while (*p) {
|
||||
if (*p == ':') colons++;
|
||||
p++;
|
||||
}
|
||||
|
||||
if (colons == 2) {
|
||||
// Format: HH:MM:SS
|
||||
count = sscanf(time_str, "%d:%d:%d", &parts[0], &parts[1], &parts[2]);
|
||||
if (count == 3) {
|
||||
return (uint32_t)((parts[0] * 3600) + (parts[1] * 60) + parts[2]);
|
||||
}
|
||||
} else if (colons == 1) {
|
||||
// Format: MM:SS
|
||||
count = sscanf(time_str, "%d:%d", &parts[0], &parts[1]);
|
||||
if (count == 2) {
|
||||
return (uint32_t)((parts[0] * 60) + parts[1]);
|
||||
}
|
||||
} else {
|
||||
// Format: SS
|
||||
count = sscanf(time_str, "%d", &parts[0]);
|
||||
if (count == 1) {
|
||||
return (uint32_t)parts[0];
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // Return 0 if parsing failed
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts total seconds into a string with format "HH:MM:SS".
|
||||
* Ensure the output buffer is at least 9 bytes (8 chars + null terminator).
|
||||
*/
|
||||
void seconds_to_time_string(uint32_t total_seconds, char *output_buffer, size_t buffer_size) {
|
||||
if (output_buffer == NULL || buffer_size < 9) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t h = total_seconds / 3600;
|
||||
uint32_t m = (total_seconds % 3600) / 60;
|
||||
uint32_t s = total_seconds % 60;
|
||||
|
||||
// %02u ensures two digits with leading zeros
|
||||
snprintf(output_buffer, buffer_size, "%02u:%02u:%02u", h, m, s);
|
||||
}
|
||||
|
||||
|
||||
temperatureUOM getTemperatureUOM(const char *uom) {
|
||||
|
|
|
|||
|
|
@ -140,7 +140,8 @@ char *prittyString(char *str);
|
|||
//void closePacketLog();
|
||||
float timespec2float(const struct timespec *elapsed);
|
||||
bool isUomTemperature( const char *uom);
|
||||
|
||||
uint32_t time_string_to_seconds(const char *time_str);
|
||||
void seconds_to_time_string(uint32_t total_seconds, char *output_buffer, size_t buffer_size);
|
||||
|
||||
temperatureUOM getTemperatureUOM(const char *uom);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@
|
|||
#define AQUALINKD_SHORT_NAME "AqualinkD"
|
||||
|
||||
// Use Magor . Minor . Patch
|
||||
#define AQUALINKD_VERSION "3.0.5 (dev)"
|
||||
#define AQUALINKD_VERSION "3.1.0"
|
||||
|
||||
|
|
@ -955,8 +955,8 @@
|
|||
// }
|
||||
// key = 1
|
||||
//}
|
||||
const buttonTypelist = ["pumpIndex", "pumpID", "pumpType", "pumpName", "pumpMaxSpeed", "pumpMinSpeed", "lightMode"];
|
||||
const virtButtonTypelist = ["pumpIndex", "pumpID", "pumpType", "pumpName", "pumpMaxSpeed", "pumpMinSpeed", "lightMode", "onetouchID", "altLabel"];
|
||||
const buttonTypelist = ["pumpIndex", "pumpID", "pumpType", "pumpName", "pumpMaxSpeed", "pumpMinSpeed", "lightMode", "runtime"];
|
||||
const virtButtonTypelist = ["pumpIndex", "pumpID", "pumpType", "pumpName", "pumpMaxSpeed", "pumpMinSpeed", "lightMode", "runtime", "onetouchID", "altLabel"];
|
||||
const configtable = document.getElementById("config_table");
|
||||
const newRow = configtable.insertRow();
|
||||
var cell1 = newRow.insertCell();
|
||||
|
|
@ -1036,7 +1036,7 @@
|
|||
_addedBlankVirtualButton = true;
|
||||
return;
|
||||
}
|
||||
const virtButtonTypelist = ["pumpIndex", "pumpID", "pumpType", "pumpName", "pumpMaxSpeed", "pumpMinSpeed", "lightMode", "onetouchID", "altLabel"];
|
||||
const virtButtonTypelist = ["pumpIndex", "pumpID", "pumpType", "pumpName", "pumpMaxSpeed", "pumpMinSpeed", "lightMode", "runtime", "onetouchID", "altLabel"];
|
||||
const configtable = document.getElementById("config_table");
|
||||
const vbname = "virtual_button_" + String(num).padStart(2, '0');
|
||||
const vblabel = vbname + "_label";
|
||||
|
|
@ -1156,6 +1156,8 @@
|
|||
} else if(deleted && key.startsWith("light_program_")) {
|
||||
//console.log(key.slice(0, 9));
|
||||
delete _config[key];
|
||||
} else {
|
||||
//console.log("delete - Unknown delete option " + key);
|
||||
}
|
||||
if(rebuild) {
|
||||
resetConfig(null);
|
||||
|
|
@ -1288,6 +1290,7 @@
|
|||
break;
|
||||
case "pumpName":
|
||||
case "altLabel":
|
||||
case "runtime":
|
||||
js.type = "string";
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2017,6 +2017,31 @@
|
|||
const minutes = totalMinutes % 60;
|
||||
return `${padToTwoDigits(hours)}:${padToTwoDigits(minutes)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats seconds into a human-readable string.
|
||||
* Returns HH:MM if duration is 1 minute or more.
|
||||
* Returns MM:SS if duration is less than 1 minute.
|
||||
*/
|
||||
function formatDuration(totalSeconds) {
|
||||
if (totalSeconds < 0) totalSeconds = 0;
|
||||
|
||||
// If we are at 1 minute or more, round to the nearest minute
|
||||
// (e.g., 1:30 becomes 2:00, 1:29 stays 1:00)
|
||||
if (totalSeconds >= 60) {
|
||||
const roundedSeconds = Math.round(totalSeconds / 60) * 60;
|
||||
const h = Math.floor(roundedSeconds / 3600);
|
||||
const m = Math.floor((roundedSeconds % 3600) / 60);
|
||||
|
||||
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
// If under 60 seconds, show actual seconds
|
||||
const s = Math.floor(totalSeconds % 60);
|
||||
return `00:${s.toString().padStart(2, '0')}s`;
|
||||
}
|
||||
|
||||
|
||||
function padToTwoDigits(num) {
|
||||
return num.toString().padStart(2, "0");
|
||||
}
|
||||
|
|
@ -3186,7 +3211,9 @@
|
|||
//console.log("TIMER "+obj.toString());
|
||||
}
|
||||
for (var obj in data.timer_durations) {
|
||||
setTileOnText(obj.toString(),"Timer "+toHoursAndMinutes(data.timer_durations[obj]));
|
||||
//setTileOnText(obj.toString(),"Timer "+toHoursAndMinutes(data.timer_durations[obj]));
|
||||
setTileOnText(obj.toString(),"Timer "+formatDuration(data.timer_durations[obj]));
|
||||
|
||||
//setTileOnTextLine2(obj.toString(),"Timer "+toHoursAndMinutes(data.timer_durations[obj]));
|
||||
//console.log("TIMER "+obj.toString()+" duration "+data.timer_durations[obj]);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue