diff --git a/Makefile b/Makefile index 533b6c9..2d0d803 100755 --- a/Makefile +++ b/Makefile @@ -83,7 +83,7 @@ endif # Main source files SRCS = aqualinkd.c utils.c config.c aq_serial.c aq_panel.c aq_programmer.c allbutton.c allbutton_aq_programmer.c net_services.c json_messages.c rs_msg_utils.c\ devices_jandy.c packetLogger.c devices_pentair.c color_lights.c serialadapter.c aq_timer.c aq_scheduler.c web_config.c\ - serial_logger.c mongoose.c hassio.c simulator.c sensors.c timespec_subtract.c + serial_logger.c mongoose.c hassio.c simulator.c sensors.c aq_filesystem.c timespec_subtract.c AQ_FLAGS = diff --git a/README.md b/README.md index 9a78efe..9ca20a6 100644 --- a/README.md +++ b/README.md @@ -126,15 +126,21 @@ NEED TO FIX FOR THIS RELEASE. * with iaqualink2 no need to poll iaqtouch devices as frequently * update documentation on how vbutton / vpump / pump_max & min / enable_iauqalink2 * check rs serial adapter is active if light color mode 11 is used. + + +* Check SWG messages like "#1 TruClear", see log in this post https://github.com/sfeakes/AqualinkD/discussions/388 --> -# Updates in 2.5.1 +# Updates in 2.6.0 (dev) +* Added configuration editor in aqmanager. * Added scheduling of pump after events (Power On, Freeze Protect, Boost) * Fixed HA bug for thermostats not converting to °C when HA is set to display °C. * Added support for monitoring SBC system sensors, like CPU / GPU / Board (CPU temp being most useful). * Reduced load on panel over AqualinkTouch protocol. * Fixed higher than normal CPU load when leaving aqmanager open and sending no messages (leaving aqmanager open for over 14days). +* Reworked PDA sleep mode. +* Added preliminary Heat Pump / Chiller support (MQTT & HA support only, no Homekit or web ui yet) # Updates in 2.5.0 * PDA panel Rev 6.0 or newer that do not have a Jandy iAqualink device attached can use the AqualinkTouch protocol rather than PDA protocol. diff --git a/release/aqualinkd-arm64 b/release/aqualinkd-arm64 index 4ddfce1..4dc27d5 100755 Binary files a/release/aqualinkd-arm64 and b/release/aqualinkd-arm64 differ diff --git a/release/aqualinkd-armhf b/release/aqualinkd-armhf index 51a39f3..032a04d 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 7b96cd0..34b4e64 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 39a0b26..2a2ff71 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 21021fb..fa69ae8 100644 --- a/source/allbutton_aq_programmer.c +++ b/source/allbutton_aq_programmer.c @@ -652,7 +652,7 @@ void *set_allbutton_pool_heater_temps( void *ptr ) val = MEATER_MIN; } */ - val = setpoint_check(POOL_HTR_SETOINT, val, aq_data); + val = setpoint_check(POOL_HTR_SETPOINT, val, aq_data); // NSF IF in TEMP1 / TEMP2 mode, we need C range of 1 to 40 is 2 to 40 for TEMP1, 1 to 39 TEMP2 if (isSINGLE_DEV_PANEL){ @@ -721,7 +721,7 @@ void *set_allbutton_spa_heater_temps( void *ptr ) } else if ( val < MEATER_MIN) { val = MEATER_MIN; }*/ - val = setpoint_check(SPA_HTR_SETOINT, val, aq_data); + val = setpoint_check(SPA_HTR_SETPOINT, val, aq_data); // NSF IF in TEMP1 / TEMP2 mode, we need C range of 1 to 40 is 2 to 40 for TEMP1, 1 to 39 TEMP2 diff --git a/source/aq_filesystem.c b/source/aq_filesystem.c new file mode 100644 index 0000000..faf44b6 --- /dev/null +++ b/source/aq_filesystem.c @@ -0,0 +1,114 @@ + +#include +#include +#include +#include +#include +#include + +#include "aqualink.h" +#include "aq_scheduler.h" + +bool remount_root_ro(bool readonly) +{ + +#ifdef AQ_CONTAINER + // In container this is pointless + return false; +#endif + + if (readonly) + { + LOG(AQUA_LOG, LOG_INFO, "reMounting root RO\n"); + mount(NULL, "/", NULL, MS_REMOUNT | MS_RDONLY, NULL); + return true; + } + else + { + struct statvfs fsinfo; + statvfs("/", &fsinfo); + if ((fsinfo.f_flag & ST_RDONLY) == 0) // We are readwrite, ignore + return false; + + LOG(AQUA_LOG, LOG_INFO, "reMounting root RW\n"); + mount(NULL, "/", NULL, MS_REMOUNT, NULL); + return true; + } +} + +FILE *aq_open_file(char *filename, bool *ro_root, bool *created_file) +{ + FILE *fp; + + *ro_root = remount_root_ro(false); + + if (access(filename, F_OK) == 0) + { + *created_file = true; + } + + fp = fopen(filename, "w"); + if (fp == NULL) + { + remount_root_ro(*ro_root); + } + + return fp; +} + +bool aq_close_file(FILE *file, bool ro_root) +{ + if (file != NULL) + fclose(file); + + return remount_root_ro(ro_root); +} + +#define BUFFER_SIZE 4096 + +bool copy_file(const char *source_path, const char *destination_path) { + + + bool ro_root = remount_root_ro(false); + + FILE *source_file = fopen(source_path, "rb"); + if (source_file == NULL) { + LOG(AQUA_LOG,LOG_ERR, "Error opening source file: %s\n",source_path); + remount_root_ro(ro_root); + return false; + } + + FILE *destination_file = fopen(destination_path, "wb"); + if (destination_file == NULL) { + LOG(AQUA_LOG,LOG_ERR, "Error opening source file: %s\n",destination_path); + fclose(source_file); + remount_root_ro(ro_root); + return false; + } + + char buffer[BUFFER_SIZE]; + size_t bytes_read; + + while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, source_file)) > 0) { + if (fwrite(buffer, 1, bytes_read, destination_file) != bytes_read) { + LOG(AQUA_LOG,LOG_ERR, "Error writing to destination file: %s\n",destination_path); + fclose(source_file); + fclose(destination_file); + remount_root_ro(ro_root); + return false; + } + } + + if (ferror(source_file)) { + LOG(AQUA_LOG,LOG_ERR, "Error reading from source: %s\n",source_path); + fclose(source_file); + fclose(destination_file); + remount_root_ro(ro_root); + return false; + } + + fclose(source_file); + fclose(destination_file); + remount_root_ro(ro_root); + return true; +} \ No newline at end of file diff --git a/source/aq_filesystem.h b/source/aq_filesystem.h new file mode 100644 index 0000000..78392f9 --- /dev/null +++ b/source/aq_filesystem.h @@ -0,0 +1,12 @@ +#ifndef AQ_FILESYSTEM_H_ +#define AQ_FILESYSTEM_H_ + + +FILE *aq_open_file( char *filename, bool *ro_root, bool* created_file); +bool aq_close_file(FILE *file, bool ro_root); +bool copy_file(const char *source_path, const char *destination_path); + + + + +#endif //AQ_FILESYSTEM_H_ \ No newline at end of file diff --git a/source/aq_mqtt.h b/source/aq_mqtt.h index e91b4b6..94d6514 100644 --- a/source/aq_mqtt.h +++ b/source/aq_mqtt.h @@ -41,6 +41,9 @@ #define FREEZE_PROTECT "Freeze_Protect" #define FREEZE_PROTECT_ENABELED FREEZE_PROTECT ENABELED_SUBT +#define CHILLER "Chiller" +#define CHILLER_ENABELED CHILLER ENABELED_SUBT + #define BATTERY_STATE "Battery" //#define POOL_THERMO_TEMP_TOPIC BTN_POOL_HTR "/Temperature" @@ -76,6 +79,7 @@ #define MQTT_FLASH "2" #define MQTT_ON "1" #define MQTT_OFF "0" +#define MQTT_COOL MQTT_FLASH #define MQTT_LWM_TOPIC "Alive" diff --git a/source/aq_panel.c b/source/aq_panel.c index f55917b..ed926e0 100644 --- a/source/aq_panel.c +++ b/source/aq_panel.c @@ -183,6 +183,7 @@ setPanel("RS-8 Combo"); */ char _panelString[60]; +char _panelStringShort[60]; void setPanelString() { snprintf(_panelString, sizeof(_panelString), "%s%s-%s%d %s", @@ -191,21 +192,28 @@ void setPanelString() isDUAL_EQPT_PANEL?"2/":"", PANEL_SIZE(), isDUAL_EQPT_PANEL?"Dual Equipment":( - isCOMBO_PANEL?"Combo Pool/Spa":(isSINGLE_DEV_PANEL?"Only Pool/Spa":"") - ) - ); - -/* - isCOMBO_PANEL?"Combo Pool/Spa":"", - isSINGLE_DEV_PANEL?"Pool/Spa Only":"", - isDUAL_EQPT_PANEL?" Dual Equipment":""); -*/ + isCOMBO_PANEL?"Combo (Pool & Spa)":(isSINGLE_DEV_PANEL?"Only (Pool or Spa)":"") + )); + + snprintf(_panelStringShort, sizeof(_panelString), "%s%s-%s%d %s", + isRS_PANEL?"RS":"", + isPDA_PANEL?"PDA":"", // No need for both of these, but for error validation leave it in. + isDUAL_EQPT_PANEL?"2/":"", + PANEL_SIZE(), + isDUAL_EQPT_PANEL?"Dual":( + isCOMBO_PANEL?"Combo":(isSINGLE_DEV_PANEL?"Only":"") + )); } const char* getPanelString() { return _panelString; } +const char* getShortPanelString() +{ + return _panelStringShort; +} + //bool setPanelByName(const char *str) { @@ -360,6 +368,13 @@ aqkey *addVirtualButton(struct aqualinkdata *aqdata, char *label, int vindex) { snprintf(name, 9, "%s%d", BTN_VAUX, vindex); button->name = name; + if (label == NULL || strlen(label) <= 0) { + //button->label = name; + setVirtualButtonLabel(button, name); + } else { + setVirtualButtonLabel(button, label); + } + /* if (strlen(label) <= 0) { button->label = name; } else { @@ -376,7 +391,7 @@ aqkey *addVirtualButton(struct aqualinkdata *aqdata, char *label, int vindex) { button->rssd_code = IAQ_ONETOUCH_4; } else { button->rssd_code = NUL; - } + }*/ button->code = NUL; button->dz_idx = DZ_NULL_IDX; @@ -385,6 +400,26 @@ aqkey *addVirtualButton(struct aqualinkdata *aqdata, char *label, int vindex) { return button; } +bool setVirtualButtonLabel(aqkey *button, const char *label) { + + button->label = (char *)label; + + // These 3 vbuttons have a button code on iaqualink protocol, so use that for rssd_code. + if (strncasecmp (button->label, "ALL OFF", 7) == 0) { + button->rssd_code = IAQ_ALL_OFF; + } else if (strncasecmp (button->label, "Spa Mode", 8) == 0) { + button->rssd_code = IAQ_SPA_MODE; + } else if (strncasecmp (button->label, "Clean Mode", 10) == 0) { + button->rssd_code = IAQ_CLEAN_MODE; + } else if (strncasecmp (button->label, "Day Party", 9) == 0) { + button->rssd_code = IAQ_ONETOUCH_4; + } else { + button->rssd_code = NUL; + } + + return true; +} + // So the 0-100% should be 600-3450 RPM and 15-130 GPM (ie 1% would = 600 & 0%=off) // (value-600) / (3450-600) * 100 // (value) / 100 * (3450-600) + 600 @@ -724,12 +759,15 @@ const char* getActionName(action_type type) case NO_ACTION: return "No Action"; break; - case POOL_HTR_SETOINT: + case POOL_HTR_SETPOINT: return "Pool Heater Setpoint"; break; - case SPA_HTR_SETOINT: + case SPA_HTR_SETPOINT: return "Spa Heater Setpoint"; break; + case CHILLER_SETPOINT: + return "Chiller Setpoint"; + break; case FREEZE_SETPOINT: return "Freeze Protect Setpoint"; break; @@ -774,6 +812,20 @@ const char* getActionName(action_type type) //bool create_panel_request(struct aqualinkdata *aqdata, netRequest requester, int buttonIndex, int value, bool timer); //void create_program_request(struct aqualinkdata *aqdata, netRequest requester, action_type type, int value, int id); // id is only valid for PUMP RPM +// Get Pool or Spa temp depending on what's on +int getWaterTemp(struct aqualinkdata *aqdata) +{ + if (isSINGLE_DEV_PANEL) + return aqdata->pool_temp; + + // NSF Need to check if spa is on. + if (aqdata->aqbuttons[1].led->state == OFF) + return aqdata->pool_temp; + else + return aqdata->spa_temp; +} + + //bool setDeviceState(aqkey *button, bool isON) bool setDeviceState(struct aqualinkdata *aqdata, int deviceIndex, bool isON, request_source source) @@ -907,10 +959,19 @@ bool programDeviceValue(struct aqualinkdata *aqdata, action_type type, int value if (aqdata->unactioned.type != NO_ACTION && type != aqdata->unactioned.type) LOG(PANL_LOG,LOG_ERR, "about to overwrite unactioned panel program\n"); - if (type == POOL_HTR_SETOINT || type == SPA_HTR_SETOINT || type == FREEZE_SETPOINT || type == SWG_SETPOINT ) { + if (type == POOL_HTR_SETPOINT || type == SPA_HTR_SETPOINT || type == FREEZE_SETPOINT || type == SWG_SETPOINT ) { aqdata->unactioned.value = setpoint_check(type, value, aqdata); if (value != aqdata->unactioned.value) LOG(PANL_LOG,LOG_NOTICE, "requested setpoint value %d is invalid, change to %d\n", value, aqdata->unactioned.value); + } else if (type == CHILLER_SETPOINT) { + if (isIAQT_ENABLED) { + aqdata->unactioned.value = setpoint_check(type, value, aqdata); + if (value != aqdata->unactioned.value) + LOG(PANL_LOG,LOG_NOTICE, "requested setpoint value %d is invalid, change to %d\n", value, aqdata->unactioned.value); + } else { + LOG(PANL_LOG,LOG_ERR, "Chiller setpoint can only be set when `%s` is set to iAqualinkTouch procotol\n", CFG_N_extended_device_id); + return false; + } } else if (type == PUMP_RPM) { aqdata->unactioned.value = value; } else if (type == PUMP_VSPROGRAM) { @@ -1105,8 +1166,9 @@ bool panel_device_request(struct aqualinkdata *aqdata, action_type type, int dev programDeviceLightMode(aqdata, value, deviceIndex); } break; - case POOL_HTR_SETOINT: - case SPA_HTR_SETOINT: + case POOL_HTR_SETPOINT: + case SPA_HTR_SETPOINT: + case CHILLER_SETPOINT: case FREEZE_SETPOINT: case SWG_SETPOINT: case SWG_BOOST: diff --git a/source/aq_panel.h b/source/aq_panel.h index 59e42d6..046bf00 100644 --- a/source/aq_panel.h +++ b/source/aq_panel.h @@ -54,11 +54,14 @@ void setPanelByName(struct aqualinkdata *aqdata, const char *str); void setPanel(struct aqualinkdata *aqdata, bool rs, int size, bool combo, bool dual); const char* getPanelString(); +const char* getShortPanelString(); bool panel_device_request(struct aqualinkdata *aqdata, action_type type, int deviceIndex, int value, request_source source); void updateButtonLightProgram(struct aqualinkdata *aqdata, int value, int button); +int getWaterTemp(struct aqualinkdata *aqdata); + void changePanelToMode_Only(); void addPanelOneTouchInterface(); void addPanelIAQTouchInterface(); @@ -71,6 +74,7 @@ int convertPumpPercentToSpeed(pump_detail *pump, int value); // This is probable uint16_t getPanelSupport( char *rev_string, int rev_len); aqkey *addVirtualButton(struct aqualinkdata *aqdata, char *label, int vindex); +bool setVirtualButtonLabel(aqkey *button, const char *label); clight_detail *getProgramableLight(struct aqualinkdata *aqdata, int button); pump_detail *getPumpDetail(struct aqualinkdata *aqdata, int button); diff --git a/source/aq_programmer.c b/source/aq_programmer.c index f20272b..cc07486 100644 --- a/source/aq_programmer.c +++ b/source/aq_programmer.c @@ -90,6 +90,7 @@ const func_ptr _prog_functions[AQP_RSSADAPTER_MAX] = { [AQ_SET_IAQTOUCH_SWG_BOOST] = set_aqualink_iaqtouch_swg_boost, [AQ_SET_IAQTOUCH_POOL_HEATER_TEMP]= set_aqualink_iaqtouch_pool_heater_temp, [AQ_SET_IAQTOUCH_SPA_HEATER_TEMP] = set_aqualink_iaqtouch_spa_heater_temp, + [AQ_SET_IAQTOUCH_CHILLER_TEMP] = set_aqualink_iaqtouch_chiller_temp, [AQ_SET_IAQTOUCH_SET_TIME] = set_aqualink_iaqtouch_time, [AQ_SET_IAQTOUCH_PUMP_VS_PROGRAM] = set_aqualink_iaqtouch_pump_vs_program, [AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE] = set_aqualink_iaqtouch_light_colormode, @@ -156,7 +157,7 @@ int setpoint_check(int type, int value, struct aqualinkdata *aqdata) char *type_msg; switch(type) { - case POOL_HTR_SETOINT: + case POOL_HTR_SETPOINT: type_msg = (isSINGLE_DEV_PANEL?"Temp1":"Pool"); if ( aqdata->temp_units == CELSIUS ) { max = HEATER_MAX_C; @@ -173,7 +174,7 @@ int setpoint_check(int type, int value, struct aqualinkdata *aqdata) min = aqdata->spa_htr_set_point + 1; } break; - case SPA_HTR_SETOINT: + case SPA_HTR_SETPOINT: type_msg = (isSINGLE_DEV_PANEL?"Temp2":"Spa"); if ( aqdata->temp_units == CELSIUS ) { max = (isSINGLE_DEV_PANEL?HEATER_MAX_C-1:HEATER_MAX_C); @@ -200,6 +201,16 @@ int setpoint_check(int type, int value, struct aqualinkdata *aqdata) min = FREEZE_PT_MIN_F; } break; + case CHILLER_SETPOINT: + type_msg = "Freeze protect"; + if ( aqdata->temp_units == CELSIUS ) { + max = CHILLER_MAX_C; + min = CHILLER_MIN_C; + } else { + max = CHILLER_MAX_F; + min = CHILLER_MIN_F; + } + break; case SWG_SETPOINT: type_msg = "Salt Water Generator"; max = SWG_PERCENT_MAX; @@ -880,6 +891,9 @@ const char *ptypeName(program_type type) case AQ_SET_IAQTOUCH_SPA_HEATER_TEMP: return "Set AqualinkTouch Spa Heater"; break; + case AQ_SET_IAQTOUCH_CHILLER_TEMP: + return "Set AqualinkTouch Chiller Temp"; + break; // These to same as above, but on the iAqualink protocol, not AqualinkTouch protocol case AQ_SET_IAQLINK_POOL_HEATER_TEMP: return "Set iAqualink Pool Heater"; @@ -965,6 +979,7 @@ const char *programtypeDisplayName(program_type type) case AQ_SET_IAQTOUCH_POOL_HEATER_TEMP: case AQ_SET_RSSADAPTER_POOL_HEATER_TEMP: case AQ_SET_RSSADAPTER_SPA_HEATER_TEMP: + case AQ_SET_IAQTOUCH_CHILLER_TEMP: return "Programming: setting heater"; break; case AQ_SET_FRZ_PROTECTION_TEMP: diff --git a/source/aq_programmer.h b/source/aq_programmer.h index d718148..eacc324 100644 --- a/source/aq_programmer.h +++ b/source/aq_programmer.h @@ -16,11 +16,15 @@ #define HEATER_MIN_F 36 #define FREEZE_PT_MAX_F 42 #define FREEZE_PT_MIN_F 34 +#define CHILLER_MAX_F 104 +#define CHILLER_MIN_F 34 #define HEATER_MAX_C 40 #define HEATER_MIN_C 0 #define FREEZE_PT_MAX_C 5 #define FREEZE_PT_MIN_C 1 +#define CHILLER_MAX_C 40 +#define CHILLER_MIN_C 0 #define SWG_PERCENT_MAX 101 #define SWG_PERCENT_MIN 0 @@ -99,6 +103,7 @@ typedef enum { AQ_SET_IAQTOUCH_ONETOUCH_ON_OFF, AQ_SET_IAQTOUCH_POOL_HEATER_TEMP, AQ_SET_IAQTOUCH_SPA_HEATER_TEMP, + AQ_SET_IAQTOUCH_CHILLER_TEMP, AQ_SET_IAQLINK_POOL_HEATER_TEMP, // Same as above but using iAqualink not AqualinkTouch AQ_SET_IAQLINK_SPA_HEATER_TEMP, // Same as above but using iAqualink not AqualinkTouch AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE, @@ -111,6 +116,12 @@ typedef enum { // ******** Delimiter make sure to change MAX/MIN below } program_type; + + +#define AQ_SET_CHILLER_TEMP AQ_SET_IAQTOUCH_CHILLER_TEMP + + + #define AQP_GENERIC_MIN AQ_GET_POOL_SPA_HEATER_TEMPS #define AQP_GENERIC_MAX AQ_SET_PUMP_VS_PROGRAM diff --git a/source/aq_scheduler.c b/source/aq_scheduler.c index e705945..ced4ade 100644 --- a/source/aq_scheduler.c +++ b/source/aq_scheduler.c @@ -29,6 +29,7 @@ #include "config.h" #include "aq_panel.h" //#include "utils.h" +#include "aq_filesystem.h" @@ -38,28 +39,7 @@ Example /etc/cron.d/aqualinkd 01 10 1 * * curl localhost:80/api/Filter_Pump/set -d value=2 -X PUT */ -bool remount_root_ro(bool readonly) { -#ifdef AQ_CONTAINER - // In container this is pointless - return false; -#endif - - if (readonly) { - LOG(SCHD_LOG,LOG_INFO, "reMounting root RO\n"); - mount (NULL, "/", NULL, MS_REMOUNT | MS_RDONLY, NULL); - return true; - } else { - struct statvfs fsinfo; - statvfs("/", &fsinfo); - if ((fsinfo.f_flag & ST_RDONLY) == 0) // We are readwrite, ignore - return false; - - LOG(SCHD_LOG,LOG_INFO, "reMounting root RW\n"); - mount (NULL, "/", NULL, MS_REMOUNT, NULL); - return true; - } -} bool passJson_scObj(const char* line, int length, aqs_cron *values) { @@ -131,6 +111,9 @@ bool passJson_scObj(const char* line, int length, aqs_cron *values) return (captured >= 7)?true:false; } + + + int save_schedules_js(const char* inBuf, int inSize, char* outBuf, int outSize) { FILE *fp; @@ -138,6 +121,7 @@ int save_schedules_js(const char* inBuf, int inSize, char* outBuf, int outSize) bool inarray = false; aqs_cron cline; bool fileexists = false; + bool fs = false; if ( !_aqconfig_.enable_scheduler) { LOG(SCHD_LOG,LOG_WARNING, "Schedules are disabled\n"); @@ -146,13 +130,19 @@ int save_schedules_js(const char* inBuf, int inSize, char* outBuf, int outSize) LOG(SCHD_LOG,LOG_NOTICE, "Saving Schedule:\n"); + /* bool fs = remount_root_ro(false); if (access(CRON_FILE, F_OK) == 0) fileexists = true; fp = fopen(CRON_FILE, "w"); + */ + + fp = aq_open_file( CRON_FILE, &fs, &fileexists); + if (fp == NULL) { LOG(SCHD_LOG,LOG_ERR, "Open file failed '%s'\n", CRON_FILE); - remount_root_ro(true); + //remount_root_ro(true); + aq_close_file(fp, fs); return sprintf(outBuf, "{\"message\":\"Error Saving Schedules\"}"); } @@ -175,14 +165,16 @@ int save_schedules_js(const char* inBuf, int inSize, char* outBuf, int outSize) } fprintf(fp, "#***** AUTO GENERATED DO NOT EDIT *****\n"); - fclose(fp); + + //fclose(fp); // if we created file, change the permissions if (!fileexists) if ( chmod(CRON_FILE, S_IRUSR | S_IWUSR ) < 0 ) LOG(SCHD_LOG,LOG_ERR, "Could not change permissions on cron file %s, scheduling may not work\n",CRON_FILE); - remount_root_ro(fs); + //remount_root_ro(fs); + aq_close_file(fp, fs); return sprintf(outBuf, "{\"message\":\"Saved Schedules\"}"); } @@ -285,7 +277,7 @@ int build_schedules_js(char* buffer, int size) //LOG(SCHD_LOG,LOG_DEBUG, "Read from cron Day %d | Time %d:%d | Zone %d | Runtime %d\n",day,hour,minute,zone,runtime); // Test / get for pump start and end time - if (isAQS_USE_PUMP_TIME_FROM_CRON_ENABLED) { + if (isAQS_USE_CRON_PUMP_TIME_ENABLED) { // Could also check that dayw is * if ( cline.enabled && strstr(cline.url, AQS_PUMP_URL )) { @@ -335,11 +327,21 @@ bool event_happened_set_device_state(reset_event_type type, struct aqualinkdata // Check time is between hours. bool scheduledOn = false; - if (isAQS_USE_PUMP_TIME_FROM_CRON_ENABLED) { + if (isAQS_USE_CRON_PUMP_TIME_ENABLED) { get_cron_pump_times(); LOG(SCHD_LOG,LOG_DEBUG, "Pump on times from scheduler are between hours %.2d & %.2d\n",_aqconfig_.sched_chk_pumpon_hour, _aqconfig_.sched_chk_pumpoff_hour); } +/* + if (_aqconfig_.sched_chk_pumpon_hour == AQ_UNKNOWN || _aqconfig_.sched_chk_pumpoff_hour == AQ_UNKNOWN ) { + if ( CRON TURNED OFF ) { + LOG(SCHD_LOG,LOG_ERR, "No pump on / off times configures and cron scheduler not enabled, can't action event!"); + return false; + } + get_cron_pump_times(); + LOG(SCHD_LOG,LOG_DEBUG, "Pump on times from scheduler are between hours %.2d & %.2d\n",_aqconfig_.sched_chk_pumpon_hour, _aqconfig_.sched_chk_pumpoff_hour); + } +*/ time_t now = time(NULL); struct tm *tm_struct = localtime(&now); diff --git a/source/aq_scheduler.h b/source/aq_scheduler.h index 70271bb..70bf8e7 100644 --- a/source/aq_scheduler.h +++ b/source/aq_scheduler.h @@ -28,20 +28,25 @@ int build_schedules_js(char* buffer, int size); int save_schedules_js(const char* inBuf, int inSize, char* outBuf, int outSize); void get_cron_pump_times(); + + + #define AQS_PUMP_URL BTN_PUMP "/set" -#define AQS_DONT_USE_CRON_PUMP_TIME (1 << 0) +// All below AQS_ are the same mask, but don;t want CRON in the emum +#define AQS_USE_CRON_PUMP_TIME (1 << 0) typedef enum reset_event_type{ - AQS_POWER_ON = (1 << 1), - AQS_FRZ_PROTECT_OFF = (1 << 2), - AQS_BOOST_OFF = (1 << 3) + AQS_POWER_ON = (1 << 1), + AQS_FRZ_PROTECT_OFF = (1 << 2), + AQS_BOOST_OFF = (1 << 3) } reset_event_type; #define isAQS_START_PUMP_EVENT_ENABLED ( ((_aqconfig_.schedule_event_mask & AQS_POWER_ON) == AQS_POWER_ON) || \ ((_aqconfig_.schedule_event_mask & AQS_FRZ_PROTECT_OFF) == AQS_FRZ_PROTECT_OFF) || \ ((_aqconfig_.schedule_event_mask & AQS_BOOST_OFF) == AQS_BOOST_OFF) ) -#define isAQS_USE_PUMP_TIME_FROM_CRON_ENABLED !((_aqconfig_.schedule_event_mask & AQS_DONT_USE_CRON_PUMP_TIME) == AQS_DONT_USE_CRON_PUMP_TIME) +//#define isAQS_USE_PUMP_TIME_FROM_CRON_ENABLED !((_aqconfig_.schedule_event_mask & AQS_DONT_USE_CRON_PUMP_TIME) == AQS_DONT_USE_CRON_PUMP_TIME) +#define isAQS_USE_CRON_PUMP_TIME_ENABLED ((_aqconfig_.schedule_event_mask & AQS_USE_CRON_PUMP_TIME) == AQS_USE_CRON_PUMP_TIME) #define isAQS_POWER_ON_ENABED ((_aqconfig_.schedule_event_mask & AQS_POWER_ON) == AQS_POWER_ON) #define isAQS_FRZ_PROTECT_OFF_ENABED ((_aqconfig_.schedule_event_mask & AQS_FRZ_PROTECT_OFF) == AQS_FRZ_PROTECT_OFF) #define isAQS_BOOST_OFF_ENABED ((_aqconfig_.schedule_event_mask & AQS_BOOST_OFF) == AQS_BOOST_OFF) diff --git a/source/aq_serial.h b/source/aq_serial.h index dae8791..5a62dca 100644 --- a/source/aq_serial.h +++ b/source/aq_serial.h @@ -63,6 +63,8 @@ const char *getJandyDeviceName(emulation_type etype); #define JANDY_DEV_AQLNK_MIN 0x30 // #define JANDY_DEV_AQLNK_MAX 0x33 // 0 +#define JANDY_DEV_HPUMP_MIN 0x70 +#define JANDY_DEV_HPUMP_MAX 0x73 /* //===== Device ID's =====// //=========================================================================// @@ -522,7 +524,8 @@ typedef enum { DRS_EPUMP, DRS_JXI, DRS_LX, - DRS_CHEM + DRS_CHEM, + DRS_HEATPUMP } rsDeviceType; typedef enum { diff --git a/source/aqualink.h b/source/aqualink.h index 3f03c21..ba55a5e 100644 --- a/source/aqualink.h +++ b/source/aqualink.h @@ -61,6 +61,8 @@ bool checkAqualinkTime(); // Only need to externalise this for PDA */ #define TEMP_UNKNOWN -999 #define TEMP_REFRESH -998 + +#define AQ_UNKNOWN TEMP_UNKNOWN //#define UNKNOWN TEMP_UNKNOWN #define DATE_STRING_LEN 30 @@ -116,12 +118,21 @@ struct programmingthread { }; +typedef enum panel_status { + CONNECTED, + CONECTING, + LOOKING_IDS, + STARTING, + SERIAL_ERROR, // Errors that stop reading serial port should be below this line + NO_IDS_ERROR, +} panel_status; typedef enum action_type { NO_ACTION = -1, - POOL_HTR_SETOINT, - SPA_HTR_SETOINT, + POOL_HTR_SETPOINT, + SPA_HTR_SETPOINT, FREEZE_SETPOINT, + CHILLER_SETPOINT, SWG_SETPOINT, SWG_BOOST, PUMP_RPM, @@ -249,6 +260,7 @@ typedef struct clightd struct aqualinkdata { + panel_status panelstatus; char version[AQ_MSGLEN*2]; char revision[AQ_MSGLEN]; char date[AQ_MSGLEN]; @@ -271,11 +283,13 @@ struct aqualinkdata int spa_htr_set_point; int swg_percent; int swg_ppm; + int chiller_set_point; unsigned char ar_swg_device_status; // Actual state unsigned char heater_err_status; aqledstate swg_led_state; // Display state for UI's aqledstate service_mode_state; aqledstate frz_protect_state; + aqledstate chiller_state; int num_pumps; pump_detail pumps[MAX_PUMPS]; int num_lights; diff --git a/source/aqualinkd.c b/source/aqualinkd.c index e3b0abe..2753a08 100644 --- a/source/aqualinkd.c +++ b/source/aqualinkd.c @@ -134,6 +134,7 @@ bool isVirtualButtonEnabled() { return _aqualink_data.virtual_button_start>0?true:false; } + // Should move to panel. bool checkAqualinkTime() { @@ -250,12 +251,12 @@ void action_delayed_request() // If we don't know the units yet, we can't action setpoint, so wait until we do. if (_aqualink_data.temp_units == UNKNOWN && - (_aqualink_data.unactioned.type == POOL_HTR_SETOINT || _aqualink_data.unactioned.type == SPA_HTR_SETOINT || _aqualink_data.unactioned.type == FREEZE_SETPOINT)) + (_aqualink_data.unactioned.type == POOL_HTR_SETPOINT || _aqualink_data.unactioned.type == SPA_HTR_SETPOINT || _aqualink_data.unactioned.type == FREEZE_SETPOINT)) return; - if (_aqualink_data.unactioned.type == POOL_HTR_SETOINT) + if (_aqualink_data.unactioned.type == POOL_HTR_SETPOINT) { - _aqualink_data.unactioned.value = setpoint_check(POOL_HTR_SETOINT, _aqualink_data.unactioned.value, &_aqualink_data); + _aqualink_data.unactioned.value = setpoint_check(POOL_HTR_SETPOINT, _aqualink_data.unactioned.value, &_aqualink_data); if (_aqualink_data.pool_htr_set_point != _aqualink_data.unactioned.value) { aq_programmer(AQ_SET_POOL_HEATER_TEMP, sval, &_aqualink_data); @@ -266,9 +267,9 @@ void action_delayed_request() LOG(AQUA_LOG,LOG_NOTICE, "Pool heater setpoint is already %d, not changing\n", _aqualink_data.unactioned.value); } } - else if (_aqualink_data.unactioned.type == SPA_HTR_SETOINT) + else if (_aqualink_data.unactioned.type == SPA_HTR_SETPOINT) { - _aqualink_data.unactioned.value = setpoint_check(SPA_HTR_SETOINT, _aqualink_data.unactioned.value, &_aqualink_data); + _aqualink_data.unactioned.value = setpoint_check(SPA_HTR_SETPOINT, _aqualink_data.unactioned.value, &_aqualink_data); if (_aqualink_data.spa_htr_set_point != _aqualink_data.unactioned.value) { aq_programmer(AQ_SET_SPA_HEATER_TEMP, sval, &_aqualink_data); @@ -359,6 +360,19 @@ void action_delayed_request() else if (_aqualink_data.unactioned.type == LIGHT_MODE) { panel_device_request(&_aqualink_data, LIGHT_MODE, _aqualink_data.unactioned.id, _aqualink_data.unactioned.value, UNACTION_TIMER); } + else if (_aqualink_data.unactioned.type == CHILLER_SETPOINT) + { + _aqualink_data.unactioned.value = setpoint_check(CHILLER_SETPOINT, _aqualink_data.unactioned.value, &_aqualink_data); + if (_aqualink_data.chiller_set_point != _aqualink_data.unactioned.value) + { + aq_programmer(AQ_SET_CHILLER_TEMP, sval, &_aqualink_data); + LOG(AQUA_LOG,LOG_NOTICE, "Setting Chiller setpoint to %d\n", _aqualink_data.unactioned.value); + } + else + { + LOG(AQUA_LOG,LOG_NOTICE, "Chiller setpoint is already %d, not changing\n", _aqualink_data.unactioned.value); + } + } else { LOG(AQUA_LOG,LOG_ERR, "Unknown request of type %d\n", _aqualink_data.unactioned.type); @@ -479,7 +493,7 @@ int main(int argc, char *argv[]) return startup(argv[0], cfgFile); } -int startup(char *self, char *cfgFile) +int OLD_startup_OLD(char *self, char *cfgFile) { int i, j; @@ -575,6 +589,7 @@ int startup(char *self, char *cfgFile) isSINGLE_DEV_PANEL?"Pool/Spa Only":"", isDUAL_EQPT_PANEL?"Dual Equipment":""); */ + LOG(AQUA_LOG,LOG_NOTICE, "Panel set to %s\n", getPanelString()); LOG(AQUA_LOG,LOG_NOTICE, "Config log_level = %d\n", _aqconfig_.log_level); LOG(AQUA_LOG,LOG_NOTICE, "Config device_id = 0x%02hhx\n", _aqconfig_.device_id); @@ -591,7 +606,7 @@ int startup(char *self, char *cfgFile) LOG(AQUA_LOG,LOG_NOTICE, "Config socket_port = %s\n", _aqconfig_.socket_port); LOG(AQUA_LOG,LOG_NOTICE, "Config web_directory = %s\n", _aqconfig_.web_directory); //LOG(AQUA_LOG,LOG_NOTICE, "Config read_all_devices = %s\n", bool2text(_aqconfig_.read_all_devices)); - LOG(AQUA_LOG,LOG_NOTICE, "Config use_aux_labels = %s\n", bool2text(_aqconfig_.use_panel_aux_labels)); + //LOG(AQUA_LOG,LOG_NOTICE, "Config use_aux_labels = %s\n", bool2text(_aqconfig_.use_panel_aux_labels)); LOG(AQUA_LOG,LOG_NOTICE, "Config override frz prot = %s\n", bool2text(_aqconfig_.override_freeze_protect)); #ifndef MG_DISABLE_MQTT LOG(AQUA_LOG,LOG_NOTICE, "Config mqtt server = %s\n", _aqconfig_.mqtt_server); @@ -626,9 +641,9 @@ int startup(char *self, char *cfgFile) LOG(AQUA_LOG,LOG_NOTICE, "Config PDA Sleep Mode = %s\n", bool2text(_aqconfig_.pda_sleep_mode)); } #endif - LOG(AQUA_LOG,LOG_NOTICE, "Config force SWG = %s\n", bool2text(_aqconfig_.force_swg)); - LOG(AQUA_LOG,LOG_NOTICE, "Config force PS setpoint = %s\n", bool2text(_aqconfig_.force_ps_setpoints)); - LOG(AQUA_LOG,LOG_NOTICE, "Config force Freeze Prot = %s\n", bool2text(_aqconfig_.force_frzprotect_setpoints)); + LOG(AQUA_LOG,LOG_NOTICE, "Config force SWG = %s\n", bool2text(ENABLE_SWG)); + LOG(AQUA_LOG,LOG_NOTICE, "Config force PS setpoint = %s\n", bool2text(ENABLE_HEATERS)); + LOG(AQUA_LOG,LOG_NOTICE, "Config force Freeze Prot = %s\n", bool2text(ENABLE_FREEZEPROTECT)); /* removed until domoticz has a better virtual thermostat LOG(AQUA_LOG,LOG_NOTICE, "Config idx pool thermostat = %d\n", _aqconfig_.dzidx_pool_thermostat); LOG(AQUA_LOG,LOG_NOTICE, "Config idx spa thermostat = %d\n", _aqconfig_.dzidx_spa_thermostat); @@ -655,7 +670,7 @@ int startup(char *self, char *cfgFile) LOG(AQUA_LOG,LOG_NOTICE, "Read JXi heater direct = %s\n", bool2text(READ_RSDEV_JXI)); LOG(AQUA_LOG,LOG_NOTICE, "Read LX heater direct = %s\n", bool2text(READ_RSDEV_LX)); LOG(AQUA_LOG,LOG_NOTICE, "Read Chem Feeder direct = %s\n", bool2text(READ_RSDEV_CHEM)); - +/* if (isAQS_START_PUMP_EVENT_ENABLED) { if (isAQS_USE_PUMP_TIME_FROM_CRON_ENABLED) { get_cron_pump_times(); @@ -664,7 +679,7 @@ int startup(char *self, char *cfgFile) LOG(AQUA_LOG,LOG_NOTICE, "Start Pump between times = %d:00 and %d:00\n",_aqconfig_.sched_chk_pumpon_hour,_aqconfig_.sched_chk_pumpoff_hour); } else { LOG(AQUA_LOG,LOG_NOTICE, "Start Pump on events = %s\n", bool2text(false)); - } + }*/ /* if (READ_RSDEV_SWG && _aqconfig_.swg_zero_ignore != DEFAULT_SWG_ZERO_IGNORE_COUNT) LOG(AQUA_LOG,LOG_NOTICE, "Ignore SWG 0 msg count = %d\n", _aqconfig_.swg_zero_ignore); @@ -743,11 +758,105 @@ int startup(char *self, char *cfgFile) _aqualink_data.aqbuttons[i].rssd_code); } */ -#ifdef CONFIG_EDITOR - check_print_config(&_aqualink_data); - writeCfg(&_aqualink_data); + + if (_aqconfig_.deamonize == true) + { + char pidfile[256]; + // sprintf(pidfile, "%s/%s.pid",PIDLOCATION, basename(argv[0])); + //sprintf(pidfile, "%s/%s.pid", "/run", basename(argv[0])); + //sprintf(pidfile, "%s/%s.pid", "/run", basename(self)); + sprintf(pidfile, "%s/%s.pid", "/run", _aqualink_data.self); + daemonise(pidfile, main_loop); + } + else + { + main_loop(); + } + + exit(EXIT_SUCCESS); +} + +int startup(char *self, char *cfgFile) +{ + _self = self; + _cfgFile = cfgFile; + + sprintf(_aqualink_data.self, basename(self)); + clearDebugLogMask(); + read_config(&_aqualink_data, cfgFile); + + if (_cmdln_loglevel != -1) + _aqconfig_.log_level = _cmdln_loglevel; + + if (_cmdln_debugRS485) + _aqconfig_.log_protocol_packets = true; + + if (_cmdln_lograwRS485) + _aqconfig_.log_raw_bytes = true; + + +#ifdef AQ_MANAGER + setLoggingPrms(_aqconfig_.log_level, _aqconfig_.deamonize, (_aqconfig_.display_warnings_web?_aqualink_data.last_display_message:NULL)); +#else + if (_aqconfig_.display_warnings_web == true) + setLoggingPrms(_aqconfig_.log_level, _aqconfig_.deamonize, _aqconfig_.log_file, _aqualink_data.last_display_message); + else + setLoggingPrms(_aqconfig_.log_level, _aqconfig_.deamonize, _aqconfig_.log_file, NULL); #endif + LOG(AQUA_LOG,LOG_NOTICE, "%s v%s\n", AQUALINKD_NAME, AQUALINKD_VERSION); + + check_print_config(&_aqualink_data); + + + // Sanity check on Device ID's against panel type + if (isRS_PANEL) { + if ( (_aqconfig_.device_id >= 0x08 && _aqconfig_.device_id <= 0x0B) || _aqconfig_.device_id == 0x00 /*|| _aqconfig_.device_id == 0xFF*/) { + // We are good + } else { + LOG(AQUA_LOG,LOG_ERR, "Device ID 0x%02hhx does not match RS panel, Going to search for ID!\n", _aqconfig_.device_id); + _aqconfig_.device_id = 0x00; + //return EXIT_FAILURE; + } + } else if (isPDA_PANEL) { + if ( (_aqconfig_.device_id >= 0x60 && _aqconfig_.device_id <= 0x63) || _aqconfig_.device_id == 0x33 ) { + if ( _aqconfig_.device_id == 0x33 ) { + LOG(AQUA_LOG,LOG_NOTICE, "Enabeling iAqualink protocol.\n"); + _aqconfig_.enable_iaqualink = true; + } + // We are good + } else { + LOG(AQUA_LOG,LOG_ERR, "Device ID 0x%02hhx does not match PDA panel, please check config!\n", _aqconfig_.device_id); + return EXIT_FAILURE; + } + } else { + LOG(AQUA_LOG,LOG_ERR, "Error unknown panel type, please check config!\n"); + return EXIT_FAILURE; + } + + if (_aqconfig_.rssa_device_id != 0x00) { + if (_aqconfig_.rssa_device_id >= 0x48 && _aqconfig_.rssa_device_id <= 0x4B /*&& _aqconfig_.rssa_device_id != 0xFF*/) { + // We are good + } else { + LOG(AQUA_LOG,LOG_ERR, "RSSA Device ID 0x%02hhx does not match RS panel, please check config!\n", _aqconfig_.rssa_device_id); + return EXIT_FAILURE; + } + } + +#if defined AQ_ONETOUCH || defined AQ_IAQTOUCH + if (_aqconfig_.extended_device_id != 0x00) { + if ( (_aqconfig_.extended_device_id >= 0x30 && _aqconfig_.extended_device_id <= 0x33) || + (_aqconfig_.extended_device_id >= 0x40 && _aqconfig_.extended_device_id <= 0x43) /*|| + _aqconfig_.extended_device_id != 0xFF*/ ) { + // We are good + } else { + LOG(AQUA_LOG,LOG_ERR, "Extended Device ID 0x%02hhx does not match OneTouch or AqualinkTouch ID, please check config!\n", _aqconfig_.extended_device_id); + return EXIT_FAILURE; + } + } +#endif + + if (_aqconfig_.deamonize == true) { char pidfile[256]; @@ -898,6 +1007,7 @@ void main_loop() bool print_once = false; int blank_read_reconnect = MAX_ZERO_READ_BEFORE_RECONNECT_BLOCKING; // Will get reset if non blocking + _aqualink_data.panelstatus = STARTING; sprintf(_aqualink_data.last_display_message, "%s", "Connecting to Control Panel"); _aqualink_data.is_display_message_programming = false; //_aqualink_data.simulate_panel = false; @@ -908,6 +1018,8 @@ void main_loop() _aqualink_data.frz_protect_set_point = TEMP_UNKNOWN; _aqualink_data.pool_htr_set_point = TEMP_UNKNOWN; _aqualink_data.spa_htr_set_point = TEMP_UNKNOWN; + _aqualink_data.chiller_set_point = TEMP_UNKNOWN; + _aqualink_data.chiller_state = LED_S_UNKNOWN; _aqualink_data.unactioned.type = NO_ACTION; _aqualink_data.swg_percent = TEMP_UNKNOWN; _aqualink_data.swg_ppm = TEMP_UNKNOWN; @@ -961,14 +1073,14 @@ void main_loop() _aqualink_data.sensors[i].value = TEMP_UNKNOWN; } - if (_aqconfig_.force_swg == true) { + if (ENABLE_SWG) { //_aqualink_data.ar_swg_device_status = SWG_STATUS_OFF; _aqualink_data.swg_led_state = OFF; _aqualink_data.swg_percent = 0; _aqualink_data.swg_ppm = 0; } - if (_aqconfig_.force_chem_feeder == true) { + if (ENABLE_CHEM_FEEDER) { _aqualink_data.ph = 0; _aqualink_data.orp = 0; } @@ -992,9 +1104,12 @@ void main_loop() if (rs_fd == -1) { LOG(AQUA_LOG,LOG_ERR, "Error Aqualink setting serial port: %s\n", _aqconfig_.serial_port); + _aqualink_data.panelstatus = SERIAL_ERROR; #ifndef AQ_CONTAINER exit(EXIT_FAILURE); #endif + } else { + _aqualink_data.panelstatus = LOOKING_IDS; } LOG(AQUA_LOG,LOG_NOTICE, "Listening to Aqualink RS8 on serial port: %s\n", _aqconfig_.serial_port); @@ -1052,7 +1167,7 @@ void main_loop() #endif // Enable or disable rs serial adapter interface. - if (_aqconfig_.rssa_device_id != 0x00) + if (_aqconfig_.rssa_device_id != 0x00 /*&& _aqconfig_.rssa_device_id != 0xFF*/) addPanelRSserialAdapterInterface(); else got_probe_rssa = true; diff --git a/source/color_lights.c b/source/color_lights.c index ae940f2..3ecad7e 100644 --- a/source/color_lights.c +++ b/source/color_lights.c @@ -18,6 +18,7 @@ Haywood Universal Color */ bool isShowMode(const char *mode); + /****** This list MUST be in order of clight_type enum *******/ char *_color_light_options[NUMBER_LIGHT_COLOR_TYPES][LIGHT_COLOR_OPTIONS] = //char *_color_light_options[NUMBER_LIGHT_COLOR_TYPES][LIGHT_COLOR_OPTIONS] = @@ -158,6 +159,16 @@ void setColorLightsPanelVersion(uint8_t supported) set = true; } */ +void clear_aqualinkd_light_modes() +{ + //_color_light_options[0] = _aqualinkd_custom_colors; + + for (int i=0; i < LIGHT_COLOR_OPTIONS; i++) { + _color_light_options[0][i] = NULL; + //_color_light_options[0][i] = i; + //_color_light_options[0][i] = _aqualinkd_custom_colors[i]; + } +} bool set_aqualinkd_light_mode_name(char *name, int index, bool isShow) { @@ -180,7 +191,7 @@ bool set_aqualinkd_light_mode_name(char *name, int index, bool isShow) const char *get_aqualinkd_light_mode_name(int index, bool *isShow) { // if index 1 is "1" then none are set. - if ( strcmp(_color_light_options[0][1], "1") == 0) { + if ( _color_light_options[0][1] == NULL || strcmp(_color_light_options[0][1], "1") == 0) { return NULL; } @@ -205,6 +216,9 @@ const char *get_currentlight_mode_name(clight_detail light, emulation_type proto return ""; } + if (_color_light_options[light.lightType][light.currentValue] == NULL) { + return ""; + } // Rename any modes depending on emulation type if (protocol == ALLBUTTON) { if (strcmp(_color_light_options[light.lightType][light.currentValue],"Afternoon Skies") == 0) { @@ -223,6 +237,10 @@ const char *light_mode_name(clight_type type, int index, emulation_type protocol return ""; } + if (_color_light_options[type][index] == NULL) { + return ""; + } + // Rename any modes depending on emulation type if (protocol == ALLBUTTON) { if (strcmp(_color_light_options[type][index],"Afternoon Skies") == 0) { @@ -290,7 +308,7 @@ int build_color_lights_js(struct aqualinkdata *aqdata, char* buffer, int size) length += sprintf(buffer+length, "var _light_program = [];\n"); - if ( strcmp(_color_light_options[0][1], "1") == 0) { + if ( _color_light_options[0][1] == NULL || strcmp(_color_light_options[0][1], "1") == 0) { length += sprintf(buffer+length, "_light_program[0] = light_program;\n"); i=1; } else { diff --git a/source/color_lights.h b/source/color_lights.h index 82d6f48..f373c57 100644 --- a/source/color_lights.h +++ b/source/color_lights.h @@ -32,6 +32,7 @@ const char *light_mode_name(clight_type type, int index, emulation_type protocol int build_color_lights_js(struct aqualinkdata *aqdata, char* buffer, int size); int build_color_light_jsonarray(int index, char* buffer, int size); +void clear_aqualinkd_light_modes(); void set_currentlight_value(clight_detail *light, int index); bool set_aqualinkd_light_mode_name(char *name, int index, bool isShow); diff --git a/source/config.c b/source/config.c index c46a5b6..7c4c50e 100644 --- a/source/config.c +++ b/source/config.c @@ -46,6 +46,7 @@ #include "aq_scheduler.h" #include "aq_panel.h" #include "rs_msg_utils.h" +#include "aq_filesystem.h" #define MAXCFGLINE 256 @@ -66,8 +67,7 @@ struct tmpPanelInfo { bool combo; bool dual; }; - -struct tmpPanelInfo *_tmpPanel; +struct tmpPanelInfo _defaultPanel; /* * initialize data to default values @@ -84,37 +84,131 @@ struct tmpPanelInfo *_tmpPanel; overide_freeze_protect */ +void set_cfg_parm_to_default(cfgParam *parm) { + + //printf("Setting default %s\n",parm->name); + switch(parm->value_type) { + case CFG_STRING: + *(char **)parm->value_ptr = (char *)parm->default_value; + break; + case CFG_INT: + *(int *)parm->value_ptr = *(int *) parm->default_value; + break; + case CFG_BOOL: + *(bool *)parm->value_ptr = *(bool *) parm->default_value; + break; + case CFG_HEX: + *(unsigned char *)parm->value_ptr = *(unsigned char *)parm->default_value; + break; + case CFG_FLOAT: + *(float *)parm->value_ptr = *(float *)parm->default_value; + break; + case CFG_BITMASK: + if ( *(bool *)parm->default_value == true) { + *(uint8_t *)parm->value_ptr |= parm->mask; + } else { + *(uint8_t *)parm->value_ptr &= ~parm->mask; + } + break; + case CFG_SPECIAL: + if (strncasecmp(parm->name, CFG_N_log_level, strlen(CFG_N_log_level)) == 0) { + *(int *)parm->value_ptr = *(int *)parm->default_value; + } else if (strncasecmp(parm->name, CFG_N_panel_type, strlen(CFG_N_panel_type)) == 0) { + //setPanelByName(aqdata, cleanwhitespace(value)); + } else { + LOG(AQUA_LOG,LOG_ERR, "ADD CONFIG DEFAULT FOR %s\n",parm->name); + } + break; + } + +} + +void set_config_defaults() +{ + for (int i=0; i <= _numCfgParams; i++) { + + set_cfg_parm_to_default(&_cfgParams[i]); + } + +} + +/* +#define cfgint "const int" +#define cfgbool "const bool" +#define cfgchar "const char *" +#define cfghex "const unsigned char" +*/ + +const int _dcfg_unknownInt = TEMP_UNKNOWN; +const unsigned char _dcfg_unknownHex = NUL; // 0x00 don't use +const unsigned char _dcfg_findIDHex = 0xFF; + +const bool _dcfg_false = false; +const bool _dcfg_true = true; +const int _dcfg_loglevel = DEFAULT_LOG_LEVEL; + +const char *_dcfg_web_port = DEFAULT_WEBPORT; +//const char *_dcfg_web_port = "80"; +const char *_dcfg_web_root = DEFAULT_WEBROOT; +const char *_dcfg_serial_port = DEFAULT_SERIALPORT; + +const char *_dcfg_mqtt_ha_discover = DEFAULT_HASS_DISCOVER; +const char *_dcfg_mqtt_aq_tp = DEFAULT_MQTT_AQ_TP; + +const char *_dcfg_null = NULL; + +const int _dcfg_zero = 0; +const int _dcfg_light_programming_mode = 0; +const int _dcfg_light_programming_initial_on = 15; +const int _dcfg_light_programming_initial_off = 12; + + void init_parameters (struct aqconfig * parms) { #ifdef CONFIG_DEV_TEST _numCfgParams = 0; + + const int unknownInt = TEMP_UNKNOWN; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.socket_port; _cfgParams[_numCfgParams].value_type = CFG_STRING; _cfgParams[_numCfgParams].name = CFG_N_socket_port; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)_dcfg_web_port; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.serial_port; _cfgParams[_numCfgParams].value_type = CFG_STRING; _cfgParams[_numCfgParams].name = CFG_N_serial_port; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)_dcfg_serial_port; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.log_level; _cfgParams[_numCfgParams].value_type = CFG_SPECIAL; // Set with _aqconfig_.log_level = text2elevel(cleanalloc(value)); _cfgParams[_numCfgParams].name = CFG_N_log_level; _cfgParams[_numCfgParams].valid_values = CFG_V_log_level; + _cfgParams[_numCfgParams].default_value = (void *) &_dcfg_loglevel; + + _numCfgParams++; + _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.paneltype_mask; + _cfgParams[_numCfgParams].value_type = CFG_SPECIAL; + _cfgParams[_numCfgParams].name = CFG_N_panel_type; + _cfgParams[_numCfgParams].default_value = NULL; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.device_id; _cfgParams[_numCfgParams].value_type = CFG_HEX; _cfgParams[_numCfgParams].name = CFG_N_device_id; _cfgParams[_numCfgParams].valid_values = CFG_V_device_id; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_findIDHex; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.rssa_device_id; _cfgParams[_numCfgParams].value_type = CFG_HEX; _cfgParams[_numCfgParams].name = CFG_N_rssa_device_id; _cfgParams[_numCfgParams].valid_values = CFG_V_rssa_device_id; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_unknownHex; #if defined AQ_ONETOUCH || defined AQ_IAQTOUCH _numCfgParams++; @@ -122,321 +216,411 @@ void init_parameters (struct aqconfig * parms) _cfgParams[_numCfgParams].value_type = CFG_BOOL; _cfgParams[_numCfgParams].name = CFG_N_extended_device_id_programming; _cfgParams[_numCfgParams].valid_values = CFG_V_BOOL; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.extended_device_id; _cfgParams[_numCfgParams].value_type = CFG_HEX; _cfgParams[_numCfgParams].name = CFG_N_extended_device_id; _cfgParams[_numCfgParams].valid_values = CFG_V_extended_device_id; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_unknownHex; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.enable_iaqualink; _cfgParams[_numCfgParams].value_type = CFG_BOOL; _cfgParams[_numCfgParams].name = CFG_N_enable_iaqualink; _cfgParams[_numCfgParams].valid_values = CFG_V_BOOL; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; #endif _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.web_directory; _cfgParams[_numCfgParams].value_type = CFG_STRING; _cfgParams[_numCfgParams].name = CFG_N_web_directory; - + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = NULL; + #ifndef AQ_MANAGER _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.log_file; _cfgParams[_numCfgParams].value_type = CFG_STRING; _cfgParams[_numCfgParams].name = CFG_N_log_file; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = NULL; #endif - + _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_server; _cfgParams[_numCfgParams].value_type = CFG_STRING; _cfgParams[_numCfgParams].name = CFG_N_mqtt_server; + _cfgParams[_numCfgParams].default_value = (void *)_dcfg_null; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_user; _cfgParams[_numCfgParams].value_type = CFG_STRING; _cfgParams[_numCfgParams].name = CFG_N_mqtt_user; + _cfgParams[_numCfgParams].default_value = (void *)NULL; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_passwd; _cfgParams[_numCfgParams].value_type = CFG_STRING; _cfgParams[_numCfgParams].name = CFG_N_mqtt_passwd; + _cfgParams[_numCfgParams].default_value = (void *)NULL; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_aq_topic; _cfgParams[_numCfgParams].value_type = CFG_STRING; _cfgParams[_numCfgParams].name = CFG_N_mqtt_aq_topic; + _cfgParams[_numCfgParams].default_value = (void *)_dcfg_mqtt_aq_tp; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_hass_discover_topic; _cfgParams[_numCfgParams].value_type = CFG_STRING; _cfgParams[_numCfgParams].name = CFG_N_mqtt_hass_discover_topic; + _cfgParams[_numCfgParams].default_value = (void *)_dcfg_mqtt_ha_discover; + _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_hass_discover_use_mac; _cfgParams[_numCfgParams].value_type = CFG_BOOL; _cfgParams[_numCfgParams].name = CFG_N_mqtt_hass_discover_use_mac; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_timed_update; _cfgParams[_numCfgParams].value_type = CFG_BOOL; _cfgParams[_numCfgParams].name = CFG_N_mqtt_timed_update; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.convert_mqtt_temp; _cfgParams[_numCfgParams].value_type = CFG_BOOL; _cfgParams[_numCfgParams].name = CFG_N_convert_mqtt_temp; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_dz_sub_topic; _cfgParams[_numCfgParams].value_type = CFG_STRING; _cfgParams[_numCfgParams].name = CFG_N_mqtt_dz_sub_topic; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = NULL; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_dz_pub_topic; _cfgParams[_numCfgParams].value_type = CFG_STRING; _cfgParams[_numCfgParams].name = CFG_N_mqtt_dz_pub_topic; - + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = NULL; +/* _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_dz_pub_topic; _cfgParams[_numCfgParams].value_type = CFG_STRING; _cfgParams[_numCfgParams].name = CFG_N_mqtt_dz_pub_topic; - + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = NULL; +*/ _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.dzidx_air_temp; _cfgParams[_numCfgParams].value_type = CFG_INT; _cfgParams[_numCfgParams].name = CFG_N_dzidx_air_temp; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)&unknownInt; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.dzidx_pool_water_temp; _cfgParams[_numCfgParams].value_type = CFG_INT; _cfgParams[_numCfgParams].name = CFG_N_dzidx_pool_water_temp; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)&unknownInt; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.dzidx_spa_water_temp; _cfgParams[_numCfgParams].value_type = CFG_INT; _cfgParams[_numCfgParams].name = CFG_N_dzidx_spa_water_temp; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)&unknownInt; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.dzidx_swg_percent; _cfgParams[_numCfgParams].value_type = CFG_INT; _cfgParams[_numCfgParams].name = CFG_N_dzidx_swg_percent; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)&unknownInt; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.dzidx_swg_ppm; _cfgParams[_numCfgParams].value_type = CFG_INT; _cfgParams[_numCfgParams].name = CFG_N_dzidx_swg_ppm; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)&unknownInt; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.dzidx_swg_status; _cfgParams[_numCfgParams].value_type = CFG_INT; _cfgParams[_numCfgParams].name = CFG_N_dzidx_swg_status; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)&unknownInt; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.convert_dz_temp; _cfgParams[_numCfgParams].value_type = CFG_BOOL; _cfgParams[_numCfgParams].name = CFG_N_convert_dz_temp; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)&unknownInt; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.light_programming_mode; _cfgParams[_numCfgParams].value_type = CFG_FLOAT; _cfgParams[_numCfgParams].name = CFG_N_light_programming_mode; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_light_programming_mode; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.light_programming_initial_on; _cfgParams[_numCfgParams].value_type = CFG_INT; _cfgParams[_numCfgParams].name = CFG_N_light_programming_initial_on; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_light_programming_initial_on; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.light_programming_initial_off; _cfgParams[_numCfgParams].value_type = CFG_INT; _cfgParams[_numCfgParams].name = CFG_N_light_programming_initial_off; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_light_programming_initial_off; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask; _cfgParams[_numCfgParams].value_type = CFG_BITMASK; _cfgParams[_numCfgParams].name = CFG_N_read_RS485_swg; _cfgParams[_numCfgParams].mask = READ_RS485_SWG; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask; _cfgParams[_numCfgParams].value_type = CFG_BITMASK; _cfgParams[_numCfgParams].name = CFG_N_read_RS485_ePump; _cfgParams[_numCfgParams].mask = READ_RS485_JAN_PUMP; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask; _cfgParams[_numCfgParams].value_type = CFG_BITMASK; _cfgParams[_numCfgParams].name = CFG_N_read_RS485_vsfPump; _cfgParams[_numCfgParams].mask = READ_RS485_PEN_PUMP; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask; _cfgParams[_numCfgParams].value_type = CFG_BITMASK; _cfgParams[_numCfgParams].name = CFG_N_read_RS485_JXi; _cfgParams[_numCfgParams].mask = READ_RS485_JAN_JXI; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask; _cfgParams[_numCfgParams].value_type = CFG_BITMASK; _cfgParams[_numCfgParams].name = CFG_N_read_RS485_LX; _cfgParams[_numCfgParams].mask = READ_RS485_JAN_LX; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask; _cfgParams[_numCfgParams].value_type = CFG_BITMASK; _cfgParams[_numCfgParams].name = CFG_N_read_RS485_Chem; _cfgParams[_numCfgParams].mask = READ_RS485_JAN_CHEM; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask; _cfgParams[_numCfgParams].value_type = CFG_BITMASK; _cfgParams[_numCfgParams].name = CFG_N_read_RS485_iAqualink; _cfgParams[_numCfgParams].mask = READ_RS485_IAQUALNK; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; + + _numCfgParams++; + _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask; + _cfgParams[_numCfgParams].value_type = CFG_BITMASK; + _cfgParams[_numCfgParams].name = CFG_N_read_RS485_HeatPump; + _cfgParams[_numCfgParams].mask = READ_RS485_HEATPUMP; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; // ADD FORCE OPTIONS HERE - /* + _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.force_device_devmask; _cfgParams[_numCfgParams].value_type = CFG_BITMASK; _cfgParams[_numCfgParams].name = CFG_N_force_swg; _cfgParams[_numCfgParams].mask = FORCE_SWG_SP; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.force_device_devmask; _cfgParams[_numCfgParams].value_type = CFG_BITMASK; _cfgParams[_numCfgParams].name = CFG_N_force_ps_setpoints; _cfgParams[_numCfgParams].mask = FORCE_POOLSPA_SP; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.force_device_devmask; _cfgParams[_numCfgParams].value_type = CFG_BITMASK; _cfgParams[_numCfgParams].name = CFG_N_force_frzprotect_setpoints; _cfgParams[_numCfgParams].mask = FORCE_FREEZEPROTECT_SP; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.force_device_devmask; _cfgParams[_numCfgParams].value_type = CFG_BITMASK; _cfgParams[_numCfgParams].name = CFG_N_force_chem_feeder; _cfgParams[_numCfgParams].mask = FORCE_CHEM_FEEDER; -*/ - _numCfgParams++; - _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.force_swg; - _cfgParams[_numCfgParams].value_type = CFG_BOOL; - _cfgParams[_numCfgParams].name = CFG_N_force_swg; - _numCfgParams++; - _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.force_ps_setpoints; - _cfgParams[_numCfgParams].value_type = CFG_BOOL; - _cfgParams[_numCfgParams].name = CFG_N_force_ps_setpoints; - _numCfgParams++; - _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.force_frzprotect_setpoints; - _cfgParams[_numCfgParams].value_type = CFG_BOOL; - _cfgParams[_numCfgParams].name = CFG_N_force_frzprotect_setpoints; - _numCfgParams++; - _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.force_chem_feeder; - _cfgParams[_numCfgParams].value_type = CFG_BOOL; - _cfgParams[_numCfgParams].name = CFG_N_force_chem_feeder; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; - - /* End change force */ + //When we add chiller support + _numCfgParams++; + _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.force_device_devmask; + _cfgParams[_numCfgParams].value_type = CFG_BITMASK; + _cfgParams[_numCfgParams].name = CFG_N_force_chiller; + _cfgParams[_numCfgParams].mask = FORCE_CHILLER; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.enable_scheduler; _cfgParams[_numCfgParams].value_type = CFG_BOOL; _cfgParams[_numCfgParams].name = CFG_N_enable_scheduler; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.schedule_event_mask; _cfgParams[_numCfgParams].value_type = CFG_BITMASK; - _cfgParams[_numCfgParams].name = CFG_N_scheduler_check_poweron; + _cfgParams[_numCfgParams].name = CFG_N_event_check_usecron; + _cfgParams[_numCfgParams].mask = AQS_USE_CRON_PUMP_TIME; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; + + _numCfgParams++; + _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.schedule_event_mask; + _cfgParams[_numCfgParams].value_type = CFG_BITMASK; + _cfgParams[_numCfgParams].name = CFG_N_event_check_poweron; _cfgParams[_numCfgParams].mask = AQS_POWER_ON; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.schedule_event_mask; _cfgParams[_numCfgParams].value_type = CFG_BITMASK; - _cfgParams[_numCfgParams].name = CFG_N_scheduler_check_freezeprotectoff; + _cfgParams[_numCfgParams].name = CFG_N_event_check_freezeprotectoff; _cfgParams[_numCfgParams].mask = AQS_FRZ_PROTECT_OFF; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.schedule_event_mask; _cfgParams[_numCfgParams].value_type = CFG_BITMASK; - _cfgParams[_numCfgParams].name = CFG_N_scheduler_check_boostoff; + _cfgParams[_numCfgParams].name = CFG_N_event_check_boostoff; _cfgParams[_numCfgParams].mask = AQS_BOOST_OFF; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; - - // ADD ched on/off OPTIONS HERE - /* - **** CFG_N_scheduler_check_pumpon_hour - **** CFG_N_scheduler_check_pumpoff_hour - _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.sched_chk_pumpon_hour; _cfgParams[_numCfgParams].value_type = CFG_INT; - _cfgParams[_numCfgParams].name = CFG_N_scheduler_check_pumpon_hour; - // Valid values 0 to 11 (or 12) + _cfgParams[_numCfgParams].name = CFG_N_event_check_pumpon_hour; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_zero; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.sched_chk_pumpoff_hour; _cfgParams[_numCfgParams].value_type = CFG_INT; - _cfgParams[_numCfgParams].name = CFG_N_scheduler_check_pumpoff_hour; - // Valid values 0 to 11 (or 12) - */ - + _cfgParams[_numCfgParams].name = CFG_N_event_check_pumpoff_hour; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_zero; + _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.ftdi_low_latency; _cfgParams[_numCfgParams].value_type = CFG_BOOL; _cfgParams[_numCfgParams].name = CFG_N_ftdi_low_latency; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.frame_delay; _cfgParams[_numCfgParams].value_type = CFG_INT; _cfgParams[_numCfgParams].name = CFG_N_rs485_frame_delay; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_zero; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.sync_panel_time; _cfgParams[_numCfgParams].value_type = CFG_BOOL; _cfgParams[_numCfgParams].name = CFG_N_sync_panel_time; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.display_warnings_web; _cfgParams[_numCfgParams].value_type = CFG_BOOL; _cfgParams[_numCfgParams].name = "display_warnings_in_web"; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.override_freeze_protect; _cfgParams[_numCfgParams].value_type = CFG_BOOL; _cfgParams[_numCfgParams].name = CFG_N_override_freeze_protect; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.report_zero_spa_temp; _cfgParams[_numCfgParams].value_type = CFG_BOOL; _cfgParams[_numCfgParams].name = CFG_N_report_zero_spa_temp; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true; _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.report_zero_pool_temp; _cfgParams[_numCfgParams].value_type = CFG_BOOL; _cfgParams[_numCfgParams].name = CFG_N_report_zero_pool_temp; + _cfgParams[_numCfgParams].advanced = true; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true; #ifdef AQ_PDA _numCfgParams++; _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.pda_sleep_mode; _cfgParams[_numCfgParams].value_type = CFG_BOOL; _cfgParams[_numCfgParams].name = CFG_N_pda_sleep_mode; + _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true; #endif #endif - // Set default panel if it get's missed from config - _tmpPanel = malloc(sizeof(struct tmpPanelInfo)); - _tmpPanel->size = 8; - _tmpPanel->rs = true; - _tmpPanel->combo = true; - _tmpPanel->dual = false; + // Default to daemonize + parms->deamonize = true; + + // clear the panel type + parms->paneltype_mask = 0; + + // Create default panel if it get's missed from config + _defaultPanel.size = 6; + _defaultPanel.rs = true; + _defaultPanel.combo = true; + _defaultPanel.dual = false; + + // Few other defaults we don;t set in general config + parms->log_protocol_packets = false; // Read & Write as packets write to file + parms->log_raw_bytes = false; // bytes read and write to file + +#ifdef AQ_NO_THREAD_NETSERVICE + parms->rs_poll_speed = DEFAULT_POLL_SPEED; + parms->thread_netservices = true; +#endif + + parms->device_pre_state = true; clearDebugLogMask(); + + for (int i=0; i < MAX_RSSD_LOG_FILTERS; i++) { + parms->RSSD_LOG_filter[i] = NUL; + } + + generate_mqtt_id(parms->mqtt_ID, MQTT_ID_LEN); + + set_config_defaults(); + + //set_config_defaults(); //int i; //char *p; //parms->rs_panel_size = 8; + /* parms->serial_port = DEFAULT_SERIALPORT; parms->log_level = DEFAULT_LOG_LEVEL; parms->socket_port = DEFAULT_WEBPORT; @@ -444,15 +628,15 @@ void init_parameters (struct aqconfig * parms) //parms->device_id = strtoul(DEFAULT_DEVICE_ID, &p, 16); parms->device_id = strtoul(DEFAULT_DEVICE_ID, NULL, 16); parms->rssa_device_id = NUL; + */ - for (int i=0; i < MAX_RSSD_LOG_FILTERS; i++) { - parms->RSSD_LOG_filter[i] = NUL; - } - parms->paneltype_mask = 0; + /* #if defined AQ_ONETOUCH || defined AQ_IAQTOUCH parms->extended_device_id = NUL; parms->extended_device_id_programming = false; #endif +*/ +/* //sscanf(DEFAULT_DEVICE_ID, "0x%x", &parms->device_id); parms->override_freeze_protect = FALSE; @@ -495,10 +679,11 @@ void init_parameters (struct aqconfig * parms) parms->read_RS485_devmask = 0; parms->use_panel_aux_labels = false; - parms->force_swg = false; - parms->force_ps_setpoints = false; - parms->force_frzprotect_setpoints = false; - parms->force_chem_feeder = false; + //parms->force_swg = false; + //parms->force_ps_setpoints = false; + //parms->force_frzprotect_setpoints = false; + //parms->force_chem_feeder = false; + //parms->swg_pool_and_spa = false; //parms->swg_zero_ignore = DEFAULT_SWG_ZERO_IGNORE_COUNT; parms->display_warnings_web = false; @@ -525,8 +710,7 @@ void init_parameters (struct aqconfig * parms) parms->ftdi_low_latency = true; parms->frame_delay = 0; parms->device_pre_state = true; - - generate_mqtt_id(parms->mqtt_ID, MQTT_ID_LEN); + */ } @@ -598,6 +782,7 @@ char *ncleanalloc(char*str, int length) strcpy ( result, str ); } //printf("Result=%s\n",result); + return result; } @@ -710,6 +895,7 @@ char *generate_mqtt_id(char *buf, int len) { bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { bool rtn = false; + char *tmpval; #ifdef CONFIG_DEV_TEST @@ -731,31 +917,34 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { } } + if (strlen(cleanwhitespace(value)) <= 0) { + LOG(AQUA_LOG,LOG_NOTICE,"Set configuration option `%s` to default since value is blank\n",_cfgParams[i].name ); + set_cfg_parm_to_default(&_cfgParams[i]); + return true; + } + switch (_cfgParams[i].value_type) { - case CFG_STRING: - *(char **)_cfgParams[i].value_ptr = cleanalloc(value); - //printf("Set Value = %s\n",_aqconfig_.testChar ); + case CFG_STRING: + if (_cfgParams[i].value_ptr != NULL && *(char **)_cfgParams[i].value_ptr != _cfgParams[i].default_value) { + LOG(AQUA_LOG,LOG_DEBUG,"FREE Memory for config %s %s\n",_cfgParams[i].name, *(char **)_cfgParams[i].value_ptr); + free(*(char **)_cfgParams[i].value_ptr); + *(char **)_cfgParams[i].value_ptr = NULL; + } + *(char **)_cfgParams[i].value_ptr = cleanalloc(value); break; case CFG_INT: - // convert int to string and check in valid values *(int *)_cfgParams[i].value_ptr = strtoul(cleanwhitespace(value), NULL, 10); - //*(int *)_cfgParams[i].value_ptr = strtoul(value, NULL, 10); // Should we use cleanalloc ????? - //printf("Set Value = %d\n",_aqconfig_.testInt ); break; case CFG_BOOL: *(bool *)_cfgParams[i].value_ptr = text2bool(value); - //printf("Set Value = %d\n",_aqconfig_.testBool ); break; case CFG_HEX: - // Convert to 0x%02hhx and check it exists *(unsigned char *)_cfgParams[i].value_ptr = strtoul(cleanwhitespace(value), NULL, 16); - //*(unsigned char *)_cfgParams[i].value_ptr = strtoul(cleanalloc(value), NULL, 16); // Need to free this - //printf("Set Value = 0x%02hhx\n",_aqconfig_.testHex ); break; case CFG_FLOAT: - // NSF look to use strtof below - *(float *)_cfgParams[i].value_ptr = atof(cleanalloc(value)); // should free this - //printf("Set Value = %d\n",_aqconfig_.testInt ); + tmpval = cleanalloc(value); + *(float *)_cfgParams[i].value_ptr = atof(tmpval); + free(tmpval); break; case CFG_BITMASK: if (text2bool(value)) @@ -766,7 +955,8 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { case CFG_SPECIAL: if (strncasecmp(param, CFG_N_log_level, strlen(CFG_N_log_level)) == 0) { *(int *)_cfgParams[i].value_ptr = text2elevel(cleanwhitespace(value)); - //*(int *)_cfgParams[i].value_ptr = text2elevel(cleanalloc(value)); + } else if (strncasecmp(param, CFG_N_panel_type, strlen(CFG_N_panel_type)) == 0) { + setPanelByName(aqdata, cleanwhitespace(value)); } else { LOG(AQUA_LOG,LOG_ERR, "ADD SPECIAL CONFIG FOR '%s'\n",param); } @@ -780,10 +970,15 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { //_cfgParams[_numCfgParams].value_type = CFG_STRING; //_cfgParams[_numCfgParams].name = "testChar"; - LOG(AQUA_LOG,LOG_ERR, "Missing cfg for %s\n",param); + //LOG(AQUA_LOG,LOG_ERR, "Missing cfg for %s\n",param); #endif +if (strlen(cleanwhitespace(value)) <= 0) { + LOG(AQUA_LOG,LOG_WARNING,"Configuration value is blank for option `%s`, Ignoring\n",param ); + return true; +} + // Values we don't want in general config. if (strncasecmp(param, "debug_log_mask", 14) == 0) { addDebugLogMask(strtoul(value, NULL, 10)); @@ -805,30 +1000,38 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { // Build panel without string } else if (strncasecmp(param, "panel_type_size", 15) == 0) { - _tmpPanel->size = strtoul(value, NULL, 10); + _defaultPanel.size = strtoul(value, NULL, 10); rtn=true; } else if (strncasecmp(param, "panel_type_combo", 16) == 0) { - _tmpPanel->combo = text2bool(value); + _defaultPanel.combo = text2bool(value); rtn=true; } else if (strncasecmp(param, "panel_type_dual", 15) == 0) { - _tmpPanel->dual = text2bool(value); + _defaultPanel.dual = text2bool(value); rtn=true; } else if (strncasecmp(param, "panel_type_pda", 14) == 0) { - _tmpPanel->rs = !text2bool(value); + _defaultPanel.rs = !text2bool(value); rtn=true; } else if (strncasecmp(param, "panel_type_rs", 13) == 0) { - _tmpPanel->rs = text2bool(value); + _defaultPanel.rs = text2bool(value); rtn=true; + +/* } else if (strncasecmp(param, CFG_N_panel_type, CFG_C_panel_type) == 0) { // This must be last so it doesn't get picked up by other settings setPanelByName(aqdata, cleanwhitespace(value)); rtn=true; - } else if ((strncasecmp(param, CFG_N_mqtt_hass_discover_topic, CFG_C_mqtt_hass_discover_topic) == 0) || +*/ + + // Old names that we will map. + } else if ((strncasecmp(param, CFG_N_mqtt_hass_discover_topic, strlen(CFG_N_mqtt_hass_discover_topic)) == 0) || (strncasecmp(param, "mqtt_hassio_discover_topic", 26) == 0) || (strncasecmp(param, "mqtt_hass_discover_topic", 24) == 0)) { _aqconfig_.mqtt_hass_discover_topic = cleanalloc(value); rtn=true; - - // Old names that we will map. + } else if ((strncasecmp(param, CFG_N_mqtt_hass_discover_use_mac, strlen(CFG_N_mqtt_hass_discover_use_mac)) == 0) || + (strncasecmp(param, "mqtt_hassio_discover_use_mac", 28) == 0) || + (strncasecmp(param, "mqtt_hass_discover_use_mac", 26) == 0)) { + _aqconfig_.mqtt_hass_discover_use_mac = text2bool(value); + rtn=true; } else if (strncasecmp (param, "keep_paneltime_synced", 21) == 0) { _aqconfig_.sync_panel_time = text2bool(value); rtn=true; @@ -856,9 +1059,30 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { } else if (strncasecmp(param, "convert_dz_temp_to_c", 20) == 0) { _aqconfig_.convert_dz_temp = text2bool(value); rtn=true; + } else if (strncasecmp (param, "scheduler_check_poweron", 23) == 0) { + if (text2bool(value)) { + _aqconfig_.schedule_event_mask |= AQS_POWER_ON; + } + rtn=true; + } else if (strncasecmp (param, "scheduler_check_freezeprotectoff", 32) == 0) { + if (text2bool(value)) { + _aqconfig_.schedule_event_mask |= AQS_FRZ_PROTECT_OFF; + } + rtn=true; + } else if (strncasecmp (param, "scheduler_check_boostoff", 24) == 0) { + if (text2bool(value)) { + _aqconfig_.schedule_event_mask |= AQS_BOOST_OFF; + } + rtn=true; + } else if (strncasecmp (param, "scheduler_check_pumpon_hour", 27) == 0) { + _aqconfig_.sched_chk_pumpon_hour = strtoul(value, NULL, 10); + rtn=true; + } else if (strncasecmp (param, "scheduler_check_pumpoff_hour", 28) == 0) { + _aqconfig_.sched_chk_pumpoff_hour = strtoul(value, NULL, 10); + rtn=true; - +#ifndef CONFIG_DEV_TEST // Below should be deleted once complete } else if (strncasecmp(param, CFG_N_socket_port, CFG_C_socket_port) == 0) { _aqconfig_.socket_port = cleanalloc(value); @@ -896,7 +1120,7 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { } else if (strncasecmp(param, "rs_panel_size", 13) == 0) { LOG(AQUA_LOG,LOG_WARNING, "Config error, 'rs_panel_size' no longer supported, please use 'panel_type'\n"); - _tmpPanel->size = strtoul(value, NULL, 10); + _defaultPanel.size = strtoul(value, NULL, 10); rtn=true; } else if (strncasecmp(param, CFG_N_web_directory, CFG_C_web_directory) == 0) { _aqconfig_.web_directory = cleanalloc(value); @@ -1148,6 +1372,10 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { } else if (strncasecmp (param, "device_pre_state", 16) == 0) { _aqconfig_.device_pre_state = text2bool(value); rtn=true; + +#endif // CONFIG_DEV_TEST + + } else if (strncasecmp(param, "light_program_", 14) == 0) { int num = strtoul(param + 14, NULL, 10); if ( num >= LIGHT_COLOR_OPTIONS ) { @@ -1155,18 +1383,23 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { } char *name = cleanalloc(value); int len = strlen(name); - if ( strncmp(name+len-7, " - Show", 7) == 0 ) { - name[len-7] = '\0'; + if (len > 0) { + if ( strncmp(name+len-7, " - Show", 7) == 0 ) { + name[len-7] = '\0'; //printf("Value '%s' index %d is show\n",name,num); - set_aqualinkd_light_mode_name(name,num,true); + set_aqualinkd_light_mode_name(name,num,true); + } else { + set_aqualinkd_light_mode_name(name,num,false); + } + rtn=true; } else { - set_aqualinkd_light_mode_name(name,num,false); + LOG(AQUA_LOG,LOG_WARNING, "Config error, light_program_%d is blank\n",num); + rtn=false; } - rtn=true; } else if (strncasecmp(param, "button_", 7) == 0) { // Check we have inichalized panel information, if not use any settings we may have if (_aqconfig_.paneltype_mask == 0) - setPanel(aqdata, _tmpPanel->rs, _tmpPanel->size, _tmpPanel->combo, _tmpPanel->dual); + setPanel(aqdata, _defaultPanel.rs, _defaultPanel.size, _defaultPanel.combo, _defaultPanel.dual); int num = strtoul(param + 7, NULL, 10) - 1; if (num > TOTAL_BUTTONS) { @@ -1231,8 +1464,10 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { // LOG(AQUA_LOG,LOG_WARNING, "Config error, extended_device_id must on of the folowing (0x30,0x31,0x32,0x33), ignored setting : %s",param); } else if (strncasecmp(param + 17, "_label", 6) == 0) { char *label = cleanalloc(value); - aqkey *button = addVirtualButton(aqdata, label, num); + //aqkey *button = addVirtualButton(aqdata, label, num); + aqkey *button = getVirtualButton(aqdata, num); if (button != NULL) { + setVirtualButtonLabel(button, label); button->special_mask |= VIRTUAL_BUTTON; button->led->state = OFF; } else { @@ -1289,9 +1524,9 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { } } else if (strncasecmp(param, "sensor_", 7) == 0) { int num = strtoul(param + 7, NULL, 10) - 1; - if (num > MAX_SENSORS || num < 0) { + if (num + 1 > MAX_SENSORS || num < 0) { LOG(AQUA_LOG,LOG_ERR, "Config error, Maximum of %d sensors allowd `%s` ignored!",MAX_SENSORS,param); - } else { + } else if (strlen(cleanwhitespace(value)) > 0) { if ( num + 1 > aqdata->num_sensors ) { aqdata->num_sensors = num + 1; } @@ -1305,11 +1540,14 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { aqdata->sensors[num].factor = atof(value); //printf("Factor = %f - %s\n",aqdata->sensors[num].factor, value); if (aqdata->sensors[num].factor == 0) { - LOG(AQUA_LOG,LOG_ERR, "Config error, couldn't understand `%s` from `%s`, using 1!",value,param); + LOG(AQUA_LOG,LOG_ERR, "Config error, couldn't understand `%s` from `%s`, using `1.0`!",value,param); aqdata->sensors[num].factor = 1; } } rtn=true; + } else { + LOG(AQUA_LOG,LOG_ERR, "Config error, blank value for `%s`\n",param); + rtn = false; } } @@ -1321,9 +1559,19 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) { aqkey *getVirtualButton(struct aqualinkdata *aqdata, int num) { aqkey *vbutton = NULL; + //char vbname[10]; + //snprintf(vbname, 9, "%s%d", BTN_VAUX, num); + + + if (aqdata->virtual_button_start <= 0) { + return addVirtualButton(aqdata, NULL, num); + } + + // num should be the index of vbutton (which starts at max buttons). + // num should also match the button NAME as in "Aux_V4" + char vbname[10]; snprintf(vbname, 9, "%s%d", BTN_VAUX, num); - for (int i = aqdata->virtual_button_start; i < aqdata->total_buttons; i++) { // printf("Checking %s agasinsdt %s\n",aqdata->aqbuttons[i].name, vbname); @@ -1333,6 +1581,11 @@ aqkey *getVirtualButton(struct aqualinkdata *aqdata, int num) break; } } + + if (vbutton == NULL) { + return addVirtualButton(aqdata, NULL, num); + } + return vbutton; } @@ -1459,8 +1712,6 @@ void read_config (struct aqualinkdata *aqdata, char *cfgFile) //int tokenindex = 0; char *b_ptr; - - _aqconfig_.config_file = cleanalloc(cfgFile); if( (fp = fopen(cfgFile, "r")) != NULL){ @@ -1495,7 +1746,7 @@ void read_config (struct aqualinkdata *aqdata, char *cfgFile) exit (EXIT_FAILURE); } - free(_tmpPanel); + //free(_defaultPanel); } @@ -1533,7 +1784,7 @@ char *errorlevel2text(int level) #ifdef CONFIG_EDITOR -#define MAX_PRINTLEN 30 +#define MAX_PRINTLEN 35 void check_print_config (struct aqualinkdata *aqdata) { int i, j; @@ -1541,6 +1792,32 @@ void check_print_config (struct aqualinkdata *aqdata) // Sanity checks + + // If no panel has been set, use default one + if (_aqconfig_.paneltype_mask == 0) { + //printf("Set temp panel info Size=%d, RS=%d, combo=%d, dual=%d\n",_defaultPanel.size,_defaultPanel.rs,_defaultPanel.combo, _defaultPanel.dual); + LOG(AQUA_LOG,LOG_ERR, "No panel set in config, using default values\n"); + setPanel(aqdata, _defaultPanel.rs, _defaultPanel.size, _defaultPanel.combo, _defaultPanel.dual); + } + + // Make sure all sensors are fully populated + for (i=0; i < aqdata->num_sensors; i++ ) { + if ( aqdata->sensors[i].label == NULL || aqdata->sensors[i].path == NULL ) { + LOG(AQUA_LOG,LOG_ERR, "Invalid sensor %d, removing!\n",i+1); + if (i == (aqdata->num_sensors-1) ) { // last sensor + // don't need to do anything, just reduce total number sensors + } else if (aqdata->num_sensors > 1) { // there are more sensors adter this bad one + for (int j=i; j < aqdata->num_sensors; j++) { + aqdata->sensors[j].label = aqdata->sensors[j+1].label; + aqdata->sensors[j].path = aqdata->sensors[j+1].path; + aqdata->sensors[j].factor = aqdata->sensors[j+1].factor; + } + } + aqdata->num_sensors --; + } + } + + /* _cfgParams[_numCfgParams].mask = READ_RS485_IAQUALNK; if ( bitmask READ_RS485_IAQUALNK && _aqconfig_.enable_iaqualink ) error and use (_aqconfig_.enable_iaqualink, disable bitmask @@ -1582,47 +1859,53 @@ void check_print_config (struct aqualinkdata *aqdata) switch (_cfgParams[i].value_type) { case CFG_STRING: if (*(char **)_cfgParams[i].value_ptr == NULL) - LOG(AQUA_LOG,LOG_NOTICE, "%-30s =\n", name); + LOG(AQUA_LOG,LOG_NOTICE, "%-35s =\n", name); else - LOG(AQUA_LOG,LOG_NOTICE, "%-30s = %s\n",name, *(char **)_cfgParams[i].value_ptr); + LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %s\n",name, *(char **)_cfgParams[i].value_ptr); break; case CFG_INT: if (*(int *)_cfgParams[i].value_ptr == TEMP_UNKNOWN) - LOG(AQUA_LOG,LOG_NOTICE, "%-30s =\n", name); + LOG(AQUA_LOG,LOG_NOTICE, "%-35s =\n", name); else - LOG(AQUA_LOG,LOG_NOTICE, "%-30s = %d\n", name, *(int *)_cfgParams[i].value_ptr); + LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %d\n", name, *(int *)_cfgParams[i].value_ptr); break; case CFG_BOOL: - LOG(AQUA_LOG,LOG_NOTICE, "%-30s = %s\n", name, bool2text(*(bool *)_cfgParams[i].value_ptr)); + LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %s\n", name, bool2text(*(bool *)_cfgParams[i].value_ptr)); break; case CFG_HEX: - LOG(AQUA_LOG,LOG_NOTICE, "%-30s = 0x%02hhx\n", name, *(unsigned char *)_cfgParams[i].value_ptr); + LOG(AQUA_LOG,LOG_NOTICE, "%-35s = 0x%02hhx\n", name, *(unsigned char *)_cfgParams[i].value_ptr); break; case CFG_FLOAT: - LOG(AQUA_LOG,LOG_NOTICE, "%-30s = %f\n", name, *(float *)_cfgParams[i].value_ptr); + LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %f\n", name, *(float *)_cfgParams[i].value_ptr); break; case CFG_BITMASK: - LOG(AQUA_LOG,LOG_NOTICE, "%-30s = %s\n", _cfgParams[i].name, (*(uint8_t *)_cfgParams[i].value_ptr & _cfgParams[i].mask) == _cfgParams[i].mask?bool2text(true):bool2text(false)); + LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %s\n", name, (*(uint8_t *)_cfgParams[i].value_ptr & _cfgParams[i].mask) == _cfgParams[i].mask?bool2text(true):bool2text(false)); break; case CFG_SPECIAL: if (strncasecmp(_cfgParams[i].name, CFG_N_log_level, strlen(CFG_N_log_level)) == 0) { - LOG(AQUA_LOG,LOG_NOTICE, "%-30s = %s\n", _cfgParams[i].name, loglevel2cgn_name(_aqconfig_.log_level)); + LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %s\n", name, loglevel2cgn_name(_aqconfig_.log_level)); + } else if (strncasecmp(_cfgParams[i].name, CFG_N_panel_type, strlen(CFG_N_panel_type)) == 0) { + LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %s\n", name, getPanelString()); } else { - LOG(AQUA_LOG,LOG_NOTICE, "%-30s = NEED TO ADD CODE TO HANDLE THIS\n",name); + LOG(AQUA_LOG,LOG_NOTICE, "%-35s = NEED TO ADD CODE TO HANDLE THIS\n",name); } break; } } if (isAQS_START_PUMP_EVENT_ENABLED) { - if (isAQS_USE_PUMP_TIME_FROM_CRON_ENABLED) { - get_cron_pump_times(); + if (isAQS_USE_CRON_PUMP_TIME_ENABLED) { + if (_aqconfig_.enable_scheduler) { + get_cron_pump_times(); + } else { + LOG(AQUA_LOG,LOG_ERR,"Scheduler is disabled, but use pump times from scheduler is enabled\n"); + } } - LOG(AQUA_LOG,LOG_NOTICE, "Start Pump on events = %s %s %s\n",isAQS_POWER_ON_ENABED?"PowerON":"",AQS_FRZ_PROTECT_OFF?"FreezeProtect":"",AQS_BOOST_OFF?"Boost":""); - LOG(AQUA_LOG,LOG_NOTICE, "Start Pump between times = %d:00 and %d:00\n",_aqconfig_.sched_chk_pumpon_hour,_aqconfig_.sched_chk_pumpoff_hour); - } else { - LOG(AQUA_LOG,LOG_NOTICE, "Start Pump on events = %s\n", bool2text(false)); - } + //LOG(AQUA_LOG,LOG_NOTICE, "Start Pump on events = %s %s %s\n",isAQS_POWER_ON_ENABED?"PowerON":"",AQS_FRZ_PROTECT_OFF?"FreezeProtect":"",AQS_BOOST_OFF?"Boost":""); + LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %d:00 and %d:00\n","Start Pump between times",_aqconfig_.sched_chk_pumpon_hour,_aqconfig_.sched_chk_pumpoff_hour); + } /*else { + LOG(AQUA_LOG,LOG_NOTICE, "Start Pump on events = %s\n", bool2text(false)); + }*/ //for (i = 0; i < TOTAL_BUTONS; i++) @@ -1666,7 +1949,7 @@ void check_print_config (struct aqualinkdata *aqdata) for (i = 0; i < aqdata->num_sensors; i++) { - LOG(AQUA_LOG,LOG_NOTICE, "Config Sensor %02d = label %-15s | %s\n", i+1, aqdata->sensors[i].label,aqdata->sensors[i].path); + LOG(AQUA_LOG,LOG_NOTICE, "Config Sensor %02d = label %-15s | %s\n", i+1, aqdata->sensors[i].label,aqdata->sensors[i].path); } } @@ -1710,92 +1993,119 @@ void writeIntValue (FILE *fp, char *msg, int value) fprintf(fp, "%s = %d\n", msg, value); } -int save_config_js(const char* inBuf, int inSize, char* outBuf, int outSize) +int save_config_js(const char* inBuf, int inSize, char* outBuf, int outSize, struct aqualinkdata *aqdata) { - int i; - //int start=0; - //int end=0; - //bool inarray = false; - printf("\n%.*s\n",inSize,inBuf); + //printf("\n%.*s\n",inSize,inBuf); - const char *regexString=" *\"([^\",:]+) *\" *: *\"([^\",:]+)\" *,*"; + //const char *regexString=" *\"([^\",:]+) *\" *: *\"([^\",:]+)\" *,*"; + const char *regexString=" *\"([^\",:]+) *\" *: *\"([^\",]*)\" *,*"; size_t maxGroups = 3; - size_t maxMatches = 100; + size_t maxMatches = 200; //size_t maxGroups = 3; regmatch_t groupArray[maxGroups]; regex_t regexCompiled; int rc; - char * cursor = NULL; + char * cursor = (char *)inBuf;; unsigned int m; - char key[64]; char value[64]; + int psize = PANEL_SIZE(); // Get current panel size + int ignodeBtnLabelsGrater = TOTAL_BUTTONS+1; + bool ignorePair = false; + + // First clear out all special current config items. + // Light Programs + clear_aqualinkd_light_modes(); + // Lights, Buttons, Pumps, VirtButtons + for (int i = 0; i < aqdata->total_buttons; i++) + { + if (isVS_PUMP(aqdata->aqbuttons[i].special_mask)) { + ((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->button = NULL; + } else if (isPLIGHT(aqdata->aqbuttons[i].special_mask)) { + ((clight_detail *)aqdata->aqbuttons[i].special_mask_ptr)->button = NULL; + } + free ( aqdata->aqbuttons[i].label); + aqdata->aqbuttons[i].special_mask = 0; + aqdata->aqbuttons[i].special_mask_ptr = NULL; + aqdata->aqbuttons[i].dz_idx = AQ_UNKNOWN; + aqdata->aqbuttons[i].code = NUL; + aqdata->aqbuttons[i].rssd_code = NUL; + } + aqdata->num_pumps = 0; + aqdata->num_lights = 0; + + // Sensors + for (int i=0; i < aqdata->num_sensors; i++ ) { + free(aqdata->sensors[i].label); + free(aqdata->sensors[i].path); + aqdata->sensors[i].label = NULL; + aqdata->sensors[i].path = NULL; + } + aqdata->num_sensors=0; + + // These should get set once we get panel info. + aqdata->total_buttons = 0; + aqdata->virtual_button_start = 0; + + // Now we can loop over the JS and set values. const char *sptr = strstr( inBuf, "values" ); if (_aqdata == NULL) { LOG(AQUA_LOG,LOG_ERR, "Saving config, not initialized:\n'%.*s'\n", inSize, inBuf); - return sprintf(outBuf, "{\"message\":\"ERROR saving config\"}"); + return snprintf(outBuf, outSize, "{\"message\":\"ERROR saving config\"}"); } if (sptr == NULL) { LOG(AQUA_LOG,LOG_ERR, "Saving config, invalid JSON:\n'%.*s'\n", inSize, inBuf); - return sprintf(outBuf, "{\"message\":\"ERROR in Config\"}"); + return snprintf(outBuf, outSize, "{\"message\":\"ERROR in Config\"}"); } - for (i=(sptr - inBuf); i < inSize; i++) { - if ( inBuf[i] == '{' ) { - //inarray=true; - //start=i; - } else if ( inBuf[i] == '}' ) { - //inarray=false; - //end=i; - } - } - - //printf("\nVALUES='%.*s'\n",(end - start)-2 ,&inBuf[start+1]); - if (0 != (rc = regcomp(®exCompiled, regexString, REG_EXTENDED))) { LOG(AQUA_LOG,LOG_ERR, "Saving config regcomp() failed, returning nonzero (%d)\n", rc); - return sprintf(outBuf, "{\"message\":\"ERROR in Config\"}"); + return snprintf(outBuf, outSize, "{\"message\":\"ERROR in Config\"}"); } //cursor = inBuf+start+1; for (m = 0; m < maxMatches; m ++) { - if (regexec(®exCompiled, cursor, maxGroups, groupArray, 0)) - break; // No more matches + if (0 != (rc = regexec(®exCompiled, cursor, maxGroups, groupArray, 0))) { + //printf("Failed to match '%s' with '%s',returning %d.\n", cursor, pattern, rc); + break; + } snprintf(key, 64, "%.*s", (groupArray[1].rm_eo - groupArray[1].rm_so), (cursor + groupArray[1].rm_so)); snprintf(value, 64, "%.*s", (groupArray[2].rm_eo - groupArray[2].rm_so), (cursor + groupArray[2].rm_so)); - printf("**** Pair = %s : %s \n",key,value); + //printf("**** Pair = %s : %s \n",key,value); - setConfigValue(_aqdata ,key,value); + // If panel size changed, see if we should ignore the label + if (strncasecmp(key, "button_", 7 ) == 0) { + if ( strtoul(key + 7, NULL, 10) >= ignodeBtnLabelsGrater) { + ignorePair = true; + LOG(AQUA_LOG,LOG_NOTICE, "Panel size changed, ignoring %s\n",key); + } + } + + if (!ignorePair) { + setConfigValue(_aqdata ,key,value); + } + + // Check if panel size has changed + if (strncasecmp(key, CFG_N_panel_type, (int)strlen(CFG_N_panel_type) ) == 0) { + if (psize != PANEL_SIZE()) { + // Panel size changed + ignodeBtnLabelsGrater = PANEL_SIZE(); + } + } cursor += groupArray[0].rm_eo; -/* - unsigned int offset = 0; - unsigned int g = 0; - for (g = 0; g < maxGroups; g++) - { - if (groupArray[g].rm_so == (size_t)-1) - break; // No more groups - - if (g == 0) - offset = groupArray[g].rm_eo; - - char cursorCopy[strlen(cursor) + 1]; - strcpy(cursorCopy, cursor); - cursorCopy[groupArray[g].rm_eo] = 0; - printf("**** Match %u, Group %u: [%2u-%2u]: %s\n", - m, g, groupArray[g].rm_so, groupArray[g].rm_eo, - cursorCopy + groupArray[g].rm_so); - } - cursor += offset; - */ } regfree(®exCompiled); + + check_print_config(aqdata); + writeCfg(aqdata); + return sprintf(outBuf, "{\"message\":\"Saved Config\"}"); } @@ -1825,11 +2135,24 @@ bool writeCfg (struct aqualinkdata *aqdata) int i; FILE *fp; char *lastName = NULL; + bool ro_root; + bool created_file; - LOG(AQUA_LOG,LOG_ERR, "writeCfg() not implimented\n"); + //LOG(AQUA_LOG,LOG_ERR, "writeCfg() not implimented\n"); //fp = fopen(_aqconfig_.config_file, "w"); - fp = fopen("/tmp/aqualinkd.conf", "w"); + //fp = fopen("/tmp/aqualinkd.conf", "w"); + + char backup_file[256]; + sprintf(backup_file,"%s.backup",_aqconfig_.config_file); + + if (copy_file(_aqconfig_.config_file, backup_file) != true) { + LOG(AQUA_LOG,LOG_WARNING,"Couldn't make a backup `%s` of config file `%s`\n",backup_file, _aqconfig_.config_file); + } else { + LOG(AQUA_LOG,LOG_NOTICE,"Made backup of config %s\n",backup_file); + } + + fp = aq_open_file(_aqconfig_.config_file, &ro_root, &created_file); if (fp == NULL) { LOG(AQUA_LOG,LOG_ERR, "Open config file failed '%s'\n", _aqconfig_.config_file); @@ -1879,6 +2202,8 @@ bool writeCfg (struct aqualinkdata *aqdata) case CFG_SPECIAL: if (strncasecmp(_cfgParams[i].name, CFG_N_log_level, strlen(CFG_N_log_level)) == 0) { fprintf(fp, "%s=%s\n", _cfgParams[i].name, loglevel2cgn_name(_aqconfig_.log_level)); + } else if (strncasecmp(_cfgParams[i].name, CFG_N_panel_type, strlen(CFG_N_panel_type)) == 0) { + fprintf(fp, "%s=%s\n", _cfgParams[i].name, getPanelString()); } else { fprintf(fp, "%s=NEED TO ADD CODE TO HANDLE THIS\n",_cfgParams[i].name); } @@ -1890,11 +2215,11 @@ bool writeCfg (struct aqualinkdata *aqdata) fprintf(fp,"\n"); // Add custom censors - for (i = 0; i < aqdata->num_sensors; i++) + for (i = 1; i <= aqdata->num_sensors; i++) { - fprintf(fp,"\nsensor_%.2d_path=%s\n",i+1,aqdata->sensors->path); - fprintf(fp,"sensor_%.2d_label=%s\n",i+1,aqdata->sensors->label); - fprintf(fp,"sensor_%.2d_factor=%f\n",i+1,aqdata->sensors->factor); + fprintf(fp,"\nsensor_%.2d_path=%s\n",i,aqdata->sensors[i-1].path); + fprintf(fp,"sensor_%.2d_label=%s\n",i,aqdata->sensors[i-1].label); + fprintf(fp,"sensor_%.2d_factor=%f\n",i,aqdata->sensors[i-1].factor); } fprintf(fp,"\n"); @@ -1956,8 +2281,8 @@ bool writeCfg (struct aqualinkdata *aqdata) //remount_root_ro(fs); fprintf(fp,"\n"); - fclose(fp); - + //fclose(fp); + aq_close_file(fp, ro_root); /* FILE *fp; diff --git a/source/config.h b/source/config.h index 4db98e2..16bdd07 100644 --- a/source/config.h +++ b/source/config.h @@ -6,22 +6,25 @@ #include "aq_serial.h" #include "aqualink.h" -//#define CONFIG_DEV_TEST -//#define CONFIG_EDITOR +#define CONFIG_DEV_TEST +#define CONFIG_EDITOR //#define DEFAULT_LOG_LEVEL 10 #define DEFAULT_LOG_LEVEL LOG_NOTICE -#define DEFAULT_WEBPORT "6580" -#define DEFAULT_WEBROOT "./" +//#define DEFAULT_WEBPORT "6580" +//#define DEFAULT_WEBROOT "./" +#define DEFAULT_WEBPORT "80" +#define DEFAULT_WEBROOT "/var/www/aqualinkd/" #define DEFAULT_SERIALPORT "/dev/ttyUSB0" #define DEFAULT_DEVICE_ID "0x0a" -#define DEFAULT_MQTT_DZ_IN NULL -#define DEFAULT_MQTT_DZ_OUT NULL -#define DEFAULT_HASS_DISCOVER NULL -#define DEFAULT_MQTT_AQ_TP NULL +#define DEFAULT_MQTT_DZ_IN NULL // "domoticz/in" +#define DEFAULT_MQTT_DZ_OUT NULL // "domoticz/out" +#define DEFAULT_HASS_DISCOVER "homeassistant" +#define DEFAULT_MQTT_AQ_TP "aqualinkd" #define DEFAULT_MQTT_SERVER NULL #define DEFAULT_MQTT_USER NULL #define DEFAULT_MQTT_PASSWD NULL + //#define DEFAULT_SWG_ZERO_IGNORE_COUNT 0 #define MQTT_ID_LEN 18 // 20 seems to kill mosquitto 1.6 @@ -34,6 +37,7 @@ #define READ_RS485_JAN_LX (1 << 4) // Jandy LX heater #define READ_RS485_JAN_CHEM (1 << 5) // Jandy Chemical Feeder #define READ_RS485_IAQUALNK (1 << 6) // Read iAqualink messages +#define READ_RS485_HEATPUMP (1 << 7) // Read HeatPump messages #define MAX_RSSD_LOG_FILTERS 4 @@ -87,15 +91,9 @@ struct aqconfig //bool read_all_devices; //bool read_pentair_packets; uint8_t read_RS485_devmask; - bool use_panel_aux_labels; + bool use_panel_aux_labels; // Took this option out of config - //uint8_t force_device_devmask; // should change the below to devmask - - bool force_swg; - bool force_ps_setpoints; - bool force_frzprotect_setpoints; - bool force_chem_feeder; - + uint8_t force_device_devmask; //int swg_zero_ignore; // This can be removed since this was due to VSP that's been fixed. bool display_warnings_web; @@ -103,20 +101,11 @@ struct aqconfig bool log_raw_bytes; // Read as bytes unsigned char RSSD_LOG_filter[MAX_RSSD_LOG_FILTERS]; //bool log_raw_RS_bytes; - /* -#ifdef AQ_RS_EXTRA_OPTS - bool readahead_b4_write; - bool prioritize_ack; -#endif -*/ + bool mqtt_timed_update; bool sync_panel_time; bool enable_scheduler; int8_t schedule_event_mask; // Was int16_t, but no need - //int16_t schedule_event_mask; - //bool sched_chk_poweron; - //bool sched_chk_freezeprotectoff; - //bool sched_chk_boostoff; int sched_chk_pumpon_hour; int sched_chk_pumpoff_hour; bool ftdi_low_latency; @@ -142,21 +131,23 @@ struct aqconfig _aqconfig_; #define READ_RSDEV_LX ((_aqconfig_.read_RS485_devmask & READ_RS485_JAN_LX) == READ_RS485_JAN_LX) #define READ_RSDEV_CHEM ((_aqconfig_.read_RS485_devmask & READ_RS485_JAN_CHEM) == READ_RS485_JAN_CHEM) #define READ_RSDEV_iAQLNK ((_aqconfig_.read_RS485_devmask & READ_RS485_IAQUALNK) == READ_RS485_IAQUALNK) +#define READ_RSDEV_HPUMP ((_aqconfig_.read_RS485_devmask & READ_RS485_HEATPUMP) == READ_RS485_HEATPUMP) #define isPDA_IAQT (_aqconfig_.device_id == 0x33) //#define isPDA ((_aqconfig_.paneltype_mask & RSP_PDA) == RSP_PDA) -/* + #define FORCE_SWG_SP (1 << 0) #define FORCE_POOLSPA_SP (1 << 1) #define FORCE_FREEZEPROTECT_SP (1 << 2) #define FORCE_CHEM_FEEDER (1 << 3) +#define FORCE_CHILLER (1 << 4) #define ENABLE_SWG ((_aqconfig_.force_device_devmask & FORCE_SWG_SP) == FORCE_SWG_SP) -#define ENABLE_HEATERs ((_aqconfig_.force_device_devmask & FORCE_POOLSPA_SP) == FORCE_POOLSPA_SP) +#define ENABLE_HEATERS ((_aqconfig_.force_device_devmask & FORCE_POOLSPA_SP) == FORCE_POOLSPA_SP) #define ENABLE_FREEZEPROTECT ((_aqconfig_.force_device_devmask & FORCE_FREEZEPROTECT_SP) == FORCE_FREEZEPROTECT_SP) #define ENABLE_CHEM_FEEDER ((_aqconfig_.force_device_devmask & FORCE_CHEM_FEEDER) == FORCE_CHEM_FEEDER) -*/ +#define ENABLE_CHILLER ((_aqconfig_.force_device_devmask & FORCE_CHILLER) == FORCE_CHILLER) /* #ifndef CONFIG_C @@ -184,7 +175,7 @@ char *ncleanalloc(char *str, int length); const char *pumpType2String(pump_type ptype); #ifdef CONFIG_EDITOR -int save_config_js(const char* inBuf, int inSize, char* outBuf, int outSize); +int save_config_js(const char* inBuf, int inSize, char* outBuf, int outSize, struct aqualinkdata *aqdata); void check_print_config (struct aqualinkdata *aqdata); #endif @@ -203,11 +194,13 @@ typedef enum cfg_value_type{ #ifdef CONFIG_DEV_TEST typedef struct cfgParam { void *value_ptr; + void *default_value; //int max_value; // Max length of string (maybe mad int as well) cfg_value_type value_type; char *name; char *valid_values; uint8_t mask; + bool advanced; } cfgParam; #ifndef CONFIG_C @@ -229,7 +222,7 @@ int _numCfgParams; #define CFG_N_log_level "log_level" #define CFG_V_log_level "[\"DEBUG\", \"INFO\", \"NOTICE\", \"WARNING\", \"ERROR\"]" #define CFG_C_log_level 9 -#define CFG_N_socket_port "socket_port" +#define CFG_N_socket_port "socket_port" // Change to Web_socket #define CFG_C_socket_port 11 #define CFG_N_web_directory "web_directory" #define CFG_C_web_directory 13 @@ -322,6 +315,7 @@ int _numCfgParams; #define CFG_C_force_frzprotect_setpoints 26 #define CFG_N_force_chem_feeder "force_chem_feeder" #define CFG_C_force_chem_feeder 17 +#define CFG_N_force_chiller "force_chiller" #define CFG_N_display_warnings_web "display_warnings_web" #define CFG_C_display_warnings_web 20 #define CFG_N_log_protocol_packets "log_protocol_packets" @@ -343,20 +337,24 @@ int _numCfgParams; #define CFG_C_read_RS485_Chem 15 #define CFG_N_read_RS485_iAqualink "read_RS485_iAqualink" #define CFG_C_read_RS485_iAqualink 20 +#define CFG_N_read_RS485_HeatPump "read_RS485_HeatPump" #define CFG_N_enable_scheduler "enable_scheduler" #define CFG_C_enable_scheduler 16 -#define CFG_N_scheduler_check_poweron "scheduler_check_poweron" -#define CFG_C_scheduler_check_poweron 23 -#define CFG_N_scheduler_check_freezeprotectoff "scheduler_check_freezeprotectoff" -#define CFG_C_scheduler_check_freezeprotectoff 32 -#define CFG_N_scheduler_check_boostoff "scheduler_check_boostoff" -#define CFG_C_scheduler_check_boostoff 24 -#define CFG_N_scheduler_check_pumpon_hour "scheduler_check_pumpon_hour" -#define CFG_C_scheduler_check_pumpon_hour 27 -#define CFG_N_scheduler_check_pumpoff_hour "scheduler_check_pumpoff_hour" -#define CFG_C_scheduler_check_pumpoff_hour 28 + +#define CFG_N_event_check_poweron "event_poweron_check_pump" +#define CFG_C_event_check_poweron 24 +#define CFG_N_event_check_freezeprotectoff "event_freezeprotectoff_check_pump" +#define CFG_C_event_check_freezeprotectoff 33 +#define CFG_N_event_check_boostoff "event_boostoff_check_pump" +#define CFG_C_event_check_boostoff 25 +#define CFG_N_event_check_pumpon_hour "event_check_pumpon_hour" +#define CFG_C_event_check_pumpon_hour 23 +#define CFG_N_event_check_pumpoff_hour "event_check_pumpoff_hour" +#define CFG_C_event_check_pumpoff_hour 24 +#define CFG_N_event_check_usecron "event_check_use_scheduler_times" +#define CFG_C_event_check_usecron 32 #define CFG_N_ftdi_low_latency "ftdi_low_latency" #define CFG_C_ftdi_low_latency 16 diff --git a/source/devices_jandy.c b/source/devices_jandy.c index e6d3692..594d4f9 100644 --- a/source/devices_jandy.c +++ b/source/devices_jandy.c @@ -64,6 +64,10 @@ bool processJandyPacket(unsigned char *packet_buffer, int packet_length, struct { rtn = processPacketFromJandyChemFeeder(packet_buffer, packet_length, aqdata, previous_packet_to); } + else if (interestedInNextAck == DRS_HEATPUMP) + { + rtn = processPacketFromHeatPump(packet_buffer, packet_length, aqdata, previous_packet_to); + } interestedInNextAck = DRS_NONE; previous_packet_to = NUL; } @@ -117,6 +121,12 @@ bool processJandyPacket(unsigned char *packet_buffer, int packet_length, struct { process_iAqualinkStatusPacket(packet_buffer, packet_length, aqdata); } + else if (READ_RSDEV_HPUMP && packet_buffer[PKT_DEST] >= JANDY_DEV_HPUMP_MIN && packet_buffer[PKT_DEST] <= JANDY_DEV_HPUMP_MAX) + { + interestedInNextAck = DRS_HEATPUMP; + rtn = processPacketToHeatPump(packet_buffer, packet_length, aqdata); + previous_packet_to = packet_buffer[PKT_DEST]; + } else { interestedInNextAck = DRS_NONE; @@ -953,8 +963,35 @@ bool processPacketFromJandyChemFeeder(unsigned char *packet_buffer, int packet_l return false; } +bool processPacketToHeatPump(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata) +{ + char msg[1024]; + + beautifyPacket(msg, 1024, packet_buffer, packet_length, true); + LOG(DJAN_LOG, LOG_INFO, "To HPump: %s\n", msg); + + if (packet_buffer[3] == 0x0c ) { + if (packet_buffer[4] == 0x00) { + // Heat Pump is off + LOG(DJAN_LOG, LOG_DEBUG, "Heat Pump 0x%02hhx is off\n",packet_buffer[2] ); + } else { + // Heat Pump is on or enabled + LOG(DJAN_LOG, LOG_DEBUG, "Heat Pump 0x%02hhx is on or enabled\n",packet_buffer[2]); + } + } + return false; +} +bool processPacketFromHeatPump(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to) +{ + char msg[1024]; + + beautifyPacket(msg, 1024, packet_buffer, packet_length, true); + LOG(DJAN_LOG, LOG_INFO, "From HPump: %s\n", msg); + + return false; +} /* // JXi Heater @@ -1006,4 +1043,23 @@ Fault Check Igntion Control Fault Short H20 sensor (or Fault open water sensor) -> 0x02 Pump fault AUX Monitor -> 0x08 +*/ + + +/* + +Heat Pump Chiller. Messages are in this thread. +https://github.com/sfeakes/AqualinkD/discussions/391#discussioncomment-12431509 + +LXi heater ping | HEX: 0x10|0x02|0x70|0x0c|0x09|0x00|0x00|0x00|0x97|0x10|0x03| +LXi status | HEX: 0x10|0x02|0x00|0x0d|0x48|0x00|0x00|0x67|0x10|0x03| + +LXi heater ping | HEX: 0x10|0x02|0x70|0x0c|0x00|0x00|0x00|0x00|0x8e|0x10|0x03| byte 4 0x00 is OFF. +LXi status | HEX: 0x10|0x02|0x00|0x0d|0x40|0x00|0x00|0x5f|0x10|0x03| 0x40 (maybe off, but odd message for off) + +LXi heater ping | HEX: 0x10|0x02|0x70|0x0c|0x29|0x00|0x00|0x00|0xb7|0x10|0x03|. byte 4 0x29 This is some on / enable +LXi status | HEX: 0x10|0x02|0x00|0x0d|0x68|0x00|0x00|0x87|0x10|0x03| 0x68 probably is chiller ON + + + */ \ No newline at end of file diff --git a/source/devices_jandy.h b/source/devices_jandy.h index 8df01e7..e4db083 100644 --- a/source/devices_jandy.h +++ b/source/devices_jandy.h @@ -22,6 +22,9 @@ bool processPacketToJandyLXHeater(unsigned char *packet_buffer, int packet_lengt bool processPacketFromJandyChemFeeder(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to ); bool processPacketToJandyChemFeeder(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata); +bool processPacketToHeatPump(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata); +bool processPacketFromHeatPump(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to); + void get_swg_status_mqtt(struct aqualinkdata *aqdata, char *message, int *status, int *dzalert); aqledstate get_swg_led_state(struct aqualinkdata *aqdata); diff --git a/source/hassio.c b/source/hassio.c index 9e4507d..af5b09d 100644 --- a/source/hassio.c +++ b/source/hassio.c @@ -111,6 +111,33 @@ const char *HASSIO_SWG_DISCOVER = "{" "\"optimistic\": false" "}"; +const char *HASSIO_CHILLER_DISCOVER = "{" + "\"device\": {" HASS_DEVICE "}," + "\"availability\": {" HASS_AVAILABILITY "}," + "\"type\": \"climate\"," + "\"unique_id\": \"aqualinkd_%s\"," + "\"name\": \"Chiller\"," + "\"modes\": [\"off\", \"cool\"]," + "\"send_if_off\": true," + "\"initial\": 34," + "\"power_command_topic\": \"%s/%s/set\"," // add + "\"payload_on\": \"2\"," + "\"payload_off\": \"0\"," + "\"current_temperature_topic\": \"%s/%s\"," + "\"mode_command_topic\": \"%s/%s/set\"," // add + "\"mode_state_topic\": \"%s/%s\"," + "\"mode_state_template\": \"{%% set values = { '0':'off', '2':'cool'} %%}{{ values[value] if value in values.keys() else 'off' }}\"," + "\"temperature_command_topic\": \"%s/%s/setpoint/set\"," + "\"temperature_state_topic\": \"%s/%s/setpoint\"," + "\"action_template\": \"{%% set values = { '0':'off', '2':'cooling'} %%}{{ values[value] if value in values.keys() else 'off' }}\"," + "\"action_topic\": \"%s/%s\"," + /*"\"temperature_state_template\": \"{{ value_json }}\""*/ + "\"min_temp\": %0.2f," + "\"max_temp\": %0.2f," + "\"temperature_unit\": \"%s\"" + //"%s" +"}"; + // Use Fan for VSP // Need to change the max / min. These do NOT lomit the slider in hassio, only the MQTT limits. // So the 0-100% should be 600-3450 RPM and 15-130 GPM (ie 1% would = 600 & 0%=off) @@ -375,8 +402,8 @@ void publish_mqtt_hassio_discover(struct aqualinkdata *aqdata, struct mg_connect { if (strcmp("NONE",aqdata->aqbuttons[i].label) != 0 ) { // Heaters - if ( (strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0 && (_aqconfig_.force_ps_setpoints || aqdata->pool_htr_set_point != TEMP_UNKNOWN)) || - (strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name)==0 && (_aqconfig_.force_ps_setpoints || aqdata->spa_htr_set_point != TEMP_UNKNOWN)) ) { + if ( (strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0 && (ENABLE_HEATERS || aqdata->pool_htr_set_point != TEMP_UNKNOWN)) || + (strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name)==0 && (ENABLE_HEATERS || aqdata->spa_htr_set_point != TEMP_UNKNOWN)) ) { sprintf(msg,HASSIO_CLIMATE_DISCOVER, connections, _aqconfig_.mqtt_aq_topic, @@ -390,8 +417,10 @@ void publish_mqtt_hassio_discover(struct aqualinkdata *aqdata, struct mg_connect _aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name, _aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name, //(_aqconfig_.convert_mqtt_temp?HASSIO_CONVERT_CLIMATE_TOF:HASSIO_NO_CONVERT_CLIMATE)); - (_aqconfig_.convert_mqtt_temp?degFtoC(36):36.00), - (_aqconfig_.convert_mqtt_temp?degFtoC(104):104.00), + //(_aqconfig_.convert_mqtt_temp?degFtoC(36):36.00), + //(_aqconfig_.convert_mqtt_temp?degFtoC(104):104.00), + (_aqconfig_.convert_mqtt_temp?(float)HEATER_MIN_C:(float)HEATER_MIN_F), + (_aqconfig_.convert_mqtt_temp?(float)HEATER_MAX_C:(float)HEATER_MAX_F), (_aqconfig_.convert_mqtt_temp?"C":"F")); sprintf(topic, "%s/climate/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, aqdata->aqbuttons[i].name); send_mqtt(nc, topic, msg); @@ -461,7 +490,7 @@ void publish_mqtt_hassio_discover(struct aqualinkdata *aqdata, struct mg_connect } // Freezeprotect - if ( _aqconfig_.force_frzprotect_setpoints || (aqdata->frz_protect_set_point != TEMP_UNKNOWN && aqdata->air_temp != TEMP_UNKNOWN) ) { + if ( ENABLE_FREEZEPROTECT || (aqdata->frz_protect_set_point != TEMP_UNKNOWN && aqdata->air_temp != TEMP_UNKNOWN) ) { sprintf(msg, HASSIO_FREEZE_PROTECT_DISCOVER, connections, _aqconfig_.mqtt_aq_topic, @@ -472,15 +501,40 @@ void publish_mqtt_hassio_discover(struct aqualinkdata *aqdata, struct mg_connect _aqconfig_.mqtt_aq_topic,FREEZE_PROTECT, _aqconfig_.mqtt_aq_topic,FREEZE_PROTECT, //(_aqconfig_.convert_mqtt_temp?HASSIO_CONVERT_CLIMATE_TOF:HASSIO_NO_CONVERT_CLIMATE)); - (_aqconfig_.convert_mqtt_temp?degFtoC(34):34.00), - (_aqconfig_.convert_mqtt_temp?degFtoC(42):42.00), + //(_aqconfig_.convert_mqtt_temp?degFtoC(34):34.00), + //(_aqconfig_.convert_mqtt_temp?degFtoC(42):42.00), + (_aqconfig_.convert_mqtt_temp?(float)FREEZE_PT_MIN_C:(float)FREEZE_PT_MIN_F), + (_aqconfig_.convert_mqtt_temp?(float)FREEZE_PT_MAX_C:(float)FREEZE_PT_MAX_F), (_aqconfig_.convert_mqtt_temp?"C":"F")); sprintf(topic, "%s/climate/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, FREEZE_PROTECT); send_mqtt(nc, topic, msg); } + if (ENABLE_CHILLER || (aqdata->chiller_set_point != TEMP_UNKNOWN && aqdata->chiller_state != LED_S_UNKNOWN) ) { + // USe freeze protect for the moment. + sprintf(msg, HASSIO_CHILLER_DISCOVER, + connections, + _aqconfig_.mqtt_aq_topic, + CHILLER, + _aqconfig_.mqtt_aq_topic,CHILLER, + _aqconfig_.mqtt_aq_topic,POOL_TEMP_TOPIC, + _aqconfig_.mqtt_aq_topic,CHILLER, + _aqconfig_.mqtt_aq_topic,CHILLER_ENABELED, + _aqconfig_.mqtt_aq_topic,CHILLER, + _aqconfig_.mqtt_aq_topic,CHILLER, + _aqconfig_.mqtt_aq_topic,CHILLER, + //(_aqconfig_.convert_mqtt_temp?HASSIO_CONVERT_CLIMATE_TOF:HASSIO_NO_CONVERT_CLIMATE)); + //(_aqconfig_.convert_mqtt_temp?degFtoC(34):34.00), + //(_aqconfig_.convert_mqtt_temp?degFtoC(104):104.00), + (_aqconfig_.convert_mqtt_temp?(float)CHILLER_MIN_C:(float)CHILLER_MIN_F), + (_aqconfig_.convert_mqtt_temp?(float)CHILLER_MAX_C:(float)CHILLER_MAX_F), + (_aqconfig_.convert_mqtt_temp?"C":"F")); + sprintf(topic, "%s/climate/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, CHILLER); + send_mqtt(nc, topic, msg); + } + // SWG - if ( aqdata->swg_percent != TEMP_UNKNOWN ) { + if ( ENABLE_SWG || aqdata->swg_percent != TEMP_UNKNOWN ) { sprintf(msg, HASSIO_SWG_DISCOVER, connections, @@ -636,14 +690,14 @@ void publish_mqtt_hassio_discover(struct aqualinkdata *aqdata, struct mg_connect } // Chem feeder (ph/orp) - if (_aqconfig_.force_chem_feeder || aqdata->ph != TEMP_UNKNOWN) { + if (ENABLE_CHEM_FEEDER || aqdata->ph != TEMP_UNKNOWN) { rsm_char_replace(idbuf, CHEM_PH_TOPIC, "/", "_"); sprintf(msg, HASSIO_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,idbuf,"Water Chemistry pH",_aqconfig_.mqtt_aq_topic,CHEM_PH_TOPIC, "pH", "mdi:water-outline"); sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, idbuf); send_mqtt(nc, topic, msg); } - if (_aqconfig_.force_chem_feeder || aqdata->orp != TEMP_UNKNOWN) { + if (ENABLE_CHEM_FEEDER || aqdata->orp != TEMP_UNKNOWN) { rsm_char_replace(idbuf, CHEM_ORP_TOPIC, "/", "_"); sprintf(msg, HASSIO_SENSOR_DISCOVER,connections,_aqconfig_.mqtt_aq_topic,idbuf,"Water Chemistry ORP",_aqconfig_.mqtt_aq_topic,CHEM_ORP_TOPIC, "orp", "mdi:water-outline"); sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, idbuf); diff --git a/source/iaqtouch.c b/source/iaqtouch.c index 60057c5..2959335 100644 --- a/source/iaqtouch.c +++ b/source/iaqtouch.c @@ -348,6 +348,39 @@ void updateAQButtonFromPageButton(struct aqualinkdata *aq_data, struct iaqt_page } } } + + // Quick and dirty check for heat pump/chiller + // if we see Heat Pump then chiller is not enabled. + // if we see chiller then it is enabled. + // Not sure why button name changes from "Heat Pump" to "Chiller", need to figure this out. (might be pump) + // THIS NEEDS TO BE DELETED AND MAKE A HEAT_PUMP / CHILLER BUTTON + int ignore = 5; + if (_currentPageLoading == IAQ_PAGE_HOME) + ignore = 0; + + if ( rsm_strmatch_ignore((const char *)pageButton->name, "Heat Pump", ignore) == 0 ) { + aq_data->chiller_state = OFF; + aq_data->updated = true; + //printf("********* Disable Chiller \n"); + } else if (rsm_strmatch_ignore((const char *)pageButton->name, "Chiller", ignore) == 0) { + switch(pageButton->state) { + case 0x00: + aq_data->chiller_state = OFF; + break; + case 0x01: + aq_data->chiller_state = ON; + break; + case 0x02: + aq_data->chiller_state = FLASH; + break; + case 0x03: + aq_data->chiller_state = ENABLE; + break; + } + aq_data->updated = true; + LOG(IAQT_LOG,LOG_DEBUG, "*** Found Status for %s state 0x%02hhx\n", "Chiller", pageButton->state); + } + } void processPageButton(unsigned char *message, int length, struct aqualinkdata *aq_data) @@ -471,7 +504,31 @@ void processPageButton(unsigned char *message, int length, struct aqualinkdata * } */ } +/* No need as we can see this in buttons +typedef enum iaqt_devices{ + DEVICE_RESET = (1 << 0), + DEVICE_CHILLER = (1 << 1) +}iaqt_devices; +void iaqt_device_update(struct aqualinkdata *aq_data, iaqt_devices mask) { + + static uint8_t device_bitmask; + + if (mask == DEVICE_RESET) { + if ( (device_bitmask & DEVICE_CHILLER) == DEVICE_CHILLER ) { + aq_data->chiller_state = ON; + LOG(IAQT_LOG,LOG_INFO, "Saw Chiller in device status\n"); + } else { + aq_data->chiller_state = OFF; + } + + device_bitmask = 0; + return; + } + + device_bitmask |= mask; +} +*/ // Log if we saw a pump in a device page cycle. void iaqt_pump_update(struct aqualinkdata *aq_data, int updated) { @@ -699,6 +756,12 @@ void passDeviceStatusPage(struct aqualinkdata *aq_data) LOG(IAQT_LOG,LOG_INFO, "Set Cemlink ORP = %d PH = %f from message '%s'\n",orp,ph,_deviceStatus[i]); } } +/* + if (rsm_strcmp(_deviceStatus[i],"Chiller") == 0) { + iaqt_device_update(aq_data, DEVICE_CHILLER); + aq_data->chiller_state = ON; + } +*/ //#ifdef READ_SWG_FROM_EXTENDED_ID else if (isPDA_PANEL) { if (rsm_strcmp(_deviceStatus[i],"AQUAPURE") == 0) { @@ -754,6 +817,7 @@ void processPage(struct aqualinkdata *aq_data) iaqt_queue_cmd(KEY_IAQTCH_KEY02); } else { iaqt_pump_update(aq_data, -1); // Reset pumps. + //iaqt_device_update(aq_data,DEVICE_RESET); // Reset any devices we monitor. if ( (isPDA_PANEL || PANEL_SIZE() >= 16) && !in_iaqt_programming_mode(aq_data) ) { iaqt_queue_cmd(KEY_IAQTCH_HOME); } @@ -1160,7 +1224,7 @@ if not programming && poll packet { iaqt_queue_cmd(nextPageRequestKey); } else if ( (_pollCnt % DEVICE_STATUS_POLL_COUNT == 0) && - _currentPage == IAQ_PAGE_DEVICES || _currentPage == IAQ_PAGE_DEVICES_REV_Yg) { + (_currentPage == IAQ_PAGE_DEVICES || _currentPage == IAQ_PAGE_DEVICES_REV_Yg) ) { iaqt_queue_cmd(KEY_IAQTCH_STATUS); // This will force us to go to status, then it'll jump back to devices, then force status again } } else if (in_programming_mode(aq_data) == true) { diff --git a/source/iaqtouch_aq_programmer.c b/source/iaqtouch_aq_programmer.c index 19078a6..75b7d12 100644 --- a/source/iaqtouch_aq_programmer.c +++ b/source/iaqtouch_aq_programmer.c @@ -46,7 +46,12 @@ #define KEY_IAQTCH_LOCKOUT_PASSWD KEY_IAQTCH_KEY08 #define KEY_IAQTCH_SET_ACQUAPURE KEY_IAQTCH_KEY09 - +// Set Point types +typedef enum SP_TYPE{ + SP_POOL, + SP_SPA, + SP_CHILLER +} SP_TYPE; bool _cansend = false; @@ -960,6 +965,12 @@ void *get_aqualink_iaqtouch_setpoints( void *ptr ) } } + button = iaqtFindButtonByLabel("Chiller"); + if (button != NULL) { + aq_data->chiller_set_point = rsm_atoi((char *)&button->name + 8); + LOG(IAQT_LOG,LOG_DEBUG, "IAQ Touch got to Chiller setpoint %d\n",aq_data->chiller_set_point); + } + if ( goto_iaqt_page(IAQ_PAGE_FREEZE_PROTECT, aq_data) == false ) goto f_end; @@ -1191,8 +1202,7 @@ void *set_aqualink_iaqtouch_swg_boost( void *ptr ) } - -bool set_aqualink_iaqtouch_heater_setpoint( struct aqualinkdata *aq_data, bool pool, int val) +bool set_aqualink_iaqtouch_heater_setpoint( struct aqualinkdata *aq_data, SP_TYPE type, int val) { struct iaqt_page_button *button; char *name; @@ -1201,17 +1211,21 @@ bool set_aqualink_iaqtouch_heater_setpoint( struct aqualinkdata *aq_data, bool p return false; if (isCOMBO_PANEL) { - if (pool) + if (type == SP_POOL) name = "Pool Heat"; else name = "Spa Heat"; } else { - if (pool) + if (type == SP_POOL) name = "Temp1"; else name = "Temp2"; } + if (type == SP_CHILLER) { + name = "Chiller"; + } + button = iaqtFindButtonByLabel(name); if (button == NULL) { @@ -1231,12 +1245,18 @@ bool set_aqualink_iaqtouch_heater_setpoint( struct aqualinkdata *aq_data, bool p button = iaqtFindButtonByLabel(name); if (button != NULL) { - if (pool) + int value = 0; + if (type == SP_POOL) { aq_data->pool_htr_set_point = rsm_atoi((char *)&button->name + strlen(name)); - else + value = aq_data->pool_htr_set_point; + } else if (type == SP_SPA) { aq_data->spa_htr_set_point = rsm_atoi((char *)&button->name + strlen(name)); - - LOG(IAQT_LOG,LOG_DEBUG, "IAQ Touch set %s heater setpoint to %d\n",name,pool?aq_data->pool_htr_set_point:aq_data->spa_htr_set_point); + value = aq_data->spa_htr_set_point; + } else if (type == SP_CHILLER) { + aq_data->chiller_set_point = rsm_atoi((char *)&button->name + strlen(name)); + value = aq_data->chiller_set_point; + } + LOG(IAQT_LOG,LOG_DEBUG, "IAQ Touch set %s heater setpoint to %d\n",name,value); } return true; @@ -1251,9 +1271,9 @@ void *set_aqualink_iaqtouch_spa_heater_temp( void *ptr ) waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_SPA_HEATER_TEMP); int val = atoi((char*)threadCtrl->thread_args); - val = setpoint_check(SPA_HTR_SETOINT, val, aq_data); + val = setpoint_check(SPA_HTR_SETPOINT, val, aq_data); - set_aqualink_iaqtouch_heater_setpoint(aq_data, false, val); + set_aqualink_iaqtouch_heater_setpoint(aq_data, SP_SPA, val); goto_iaqt_page(IAQ_PAGE_HOME, aq_data); cleanAndTerminateThread(threadCtrl); @@ -1270,9 +1290,28 @@ void *set_aqualink_iaqtouch_pool_heater_temp( void *ptr ) waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_POOL_HEATER_TEMP); int val = atoi((char*)threadCtrl->thread_args); - val = setpoint_check(POOL_HTR_SETOINT, val, aq_data); + val = setpoint_check(POOL_HTR_SETPOINT, val, aq_data); - set_aqualink_iaqtouch_heater_setpoint(aq_data, true, val); + set_aqualink_iaqtouch_heater_setpoint(aq_data, SP_POOL, val); + + goto_iaqt_page(IAQ_PAGE_HOME, aq_data); + cleanAndTerminateThread(threadCtrl); + + return ptr; +} + +void *set_aqualink_iaqtouch_chiller_temp( void *ptr ) +{ + struct programmingThreadCtrl *threadCtrl; + threadCtrl = (struct programmingThreadCtrl *) ptr; + struct aqualinkdata *aq_data = threadCtrl->aq_data; + + waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_POOL_HEATER_TEMP); + + int val = atoi((char*)threadCtrl->thread_args); + //val = setpoint_check(POOL_HTR_SETPOINT, val, aq_data); + + set_aqualink_iaqtouch_heater_setpoint(aq_data, SP_CHILLER, val); goto_iaqt_page(IAQ_PAGE_HOME, aq_data); cleanAndTerminateThread(threadCtrl); diff --git a/source/iaqtouch_aq_programmer.h b/source/iaqtouch_aq_programmer.h index a7dfe01..0a2b4df 100644 --- a/source/iaqtouch_aq_programmer.h +++ b/source/iaqtouch_aq_programmer.h @@ -3,6 +3,8 @@ #ifndef IAQ_TOUCH_PROGRAMMER_H_ #define IAQ_TOUCH_PROGRAMMER_H_ + + unsigned char pop_iaqt_cmd(unsigned char receive_type); void set_iaq_cansend(bool cansend); @@ -17,6 +19,7 @@ void *set_aqualink_iaqtouch_swg_percent( void *ptr ); void *set_aqualink_iaqtouch_swg_boost( void *ptr ); void *set_aqualink_iaqtouch_spa_heater_temp( void *ptr ); void *set_aqualink_iaqtouch_pool_heater_temp( void *ptr ); +void *set_aqualink_iaqtouch_chiller_temp( void *ptr ); void *set_aqualink_iaqtouch_time( void *ptr ); void *set_aqualink_iaqtouch_pump_vs_program( void *ptr ); void *set_aqualink_iaqtouch_light_colormode( void *ptr ); diff --git a/source/json_messages.c b/source/json_messages.c index 49367f5..a36030e 100644 --- a/source/json_messages.c +++ b/source/json_messages.c @@ -321,7 +321,7 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool for (i=0; i < aqdata->total_buttons; i++) { - if ( strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0 && (_aqconfig_.force_ps_setpoints || aqdata->pool_htr_set_point != TEMP_UNKNOWN)) { + if ( strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0 && (ENABLE_HEATERS || aqdata->pool_htr_set_point != TEMP_UNKNOWN)) { length += sprintf(buffer+length, "{\"type\": \"setpoint_thermo\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"spvalue\": \"%.*f\", \"value\": \"%.*f\", \"int_status\": \"%d\", \"timer_active\":\"%s\" },", aqdata->aqbuttons[i].name, aqdata->aqbuttons[i].label, @@ -334,7 +334,7 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool LED2int(aqdata->aqbuttons[i].led->state), ((aqdata->aqbuttons[i].special_mask & TIMER_ACTIVE) == TIMER_ACTIVE?JSON_ON:JSON_OFF) ); - } else if ( strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name)==0 && (_aqconfig_.force_ps_setpoints || aqdata->spa_htr_set_point != TEMP_UNKNOWN)) { + } else if ( strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name)==0 && (ENABLE_HEATERS || aqdata->spa_htr_set_point != TEMP_UNKNOWN)) { length += sprintf(buffer+length, "{\"type\": \"setpoint_thermo\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"spvalue\": \"%.*f\", \"value\": \"%.*f\", \"int_status\": \"%d\", \"timer_active\":\"%s\" },", aqdata->aqbuttons[i].name, aqdata->aqbuttons[i].label, @@ -388,7 +388,7 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool }*/ } - if ( _aqconfig_.force_frzprotect_setpoints || (aqdata->frz_protect_set_point != TEMP_UNKNOWN && aqdata->air_temp != TEMP_UNKNOWN) ) { + if ( ENABLE_FREEZEPROTECT || (aqdata->frz_protect_set_point != TEMP_UNKNOWN && aqdata->air_temp != TEMP_UNKNOWN) ) { length += sprintf(buffer+length, "{\"type\": \"setpoint_freeze\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"spvalue\": \"%.*f\", \"value\": \"%.*f\", \"int_status\": \"%d\" },", FREEZE_PROTECT, "Freeze Protection", @@ -403,6 +403,21 @@ int build_device_JSON(struct aqualinkdata *aqdata, char* buffer, int size, bool aqdata->frz_protect_state==ON?1:0); } + if ( ENABLE_CHILLER || (aqdata->chiller_set_point != TEMP_UNKNOWN && getWaterTemp(aqdata) != TEMP_UNKNOWN) ) { + length += sprintf(buffer+length, "{\"type\": \"setpoint_chiller\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"spvalue\": \"%.*f\", \"value\": \"%.*f\", \"int_status\": \"%d\" },", + CHILLER, + "Chiller", + //JSON_OFF, + aqdata->chiller_state==ON?JSON_ON:JSON_OFF, + //JSON_ENABLED, + aqdata->chiller_state==ON?LED2text(ON):LED2text(ENABLE), + ((homekit)?2:0), + ((homekit_f)?degFtoC(aqdata->chiller_set_point):aqdata->chiller_set_point), + ((homekit)?2:0), + ((homekit_f)?degFtoC(getWaterTemp(aqdata)):getWaterTemp(aqdata)), + aqdata->chiller_state==ON?1:0); + } + if (aqdata->swg_led_state != LED_S_UNKNOWN) { if ( aqdata->swg_percent != TEMP_UNKNOWN ) { length += sprintf(buffer+length, "{\"type\": \"setpoint_swg\", \"id\": \"%s\", \"name\": \"%s\", \"state\": \"%s\", \"status\": \"%s\", \"spvalue\": \"%.*f\", \"value\": \"%.*f\", \"int_status\": \"%d\" },", @@ -603,7 +618,8 @@ int build_aqualink_status_JSON(struct aqualinkdata *aqdata, char* buffer, int si length += sprintf(buffer+length, "{\"type\": \"status\""); length += sprintf(buffer+length, ",\"status\":\"%s\"",getStatus(aqdata) ); length += sprintf(buffer+length, ",\"panel_message\":\"%s\"",aqdata->last_message ); - length += sprintf(buffer+length, ",\"panel_type\":\"%s\"",getPanelString()); + length += sprintf(buffer+length, ",\"panel_type_full\":\"%s\"",getPanelString()); + length += sprintf(buffer+length, ",\"panel_type\":\"%s\"",getShortPanelString()); //length += sprintf(buffer+length, ",\"message\":\"%s\"",aqdata->message ); length += sprintf(buffer+length, ",\"version\":\"%s\"",aqdata->version );//8157 REV MMM", length += sprintf(buffer+length, ",\"aqualinkd_version\":\"%s\"", AQUALINKD_VERSION ); //1.0b, @@ -617,6 +633,9 @@ int build_aqualink_status_JSON(struct aqualinkdata *aqdata, char* buffer, int si length += sprintf(buffer+length, ",\"spa_htr_set_pnt\":\"%d\"",aqdata->spa_htr_set_point );//"99", //length += sprintf(buffer+length, ",\"freeze_protection":\"%s\"",aqdata->frz_protect_set_point );//"off", length += sprintf(buffer+length, ",\"frz_protect_set_pnt\":\"%d\"",aqdata->frz_protect_set_point );//"0", + if (ENABLE_CHILLER || aqdata->chiller_set_point != TEMP_UNKNOWN) { + length += sprintf(buffer+length, ",\"chiller_set_pnt\":\"%d\"",aqdata->chiller_set_point );//"0", + } if ( aqdata->air_temp == TEMP_UNKNOWN ) length += sprintf(buffer+length, ",\"air_temp\":\" \""); @@ -682,7 +701,7 @@ int build_aqualink_status_JSON(struct aqualinkdata *aqdata, char* buffer, int si length += sprintf(buffer+length, ", \"%s\": \"%s\"", SWG_BOOST_TOPIC, aqdata->boost?JSON_ON:JSON_OFF); } //NSF Need to come back and read what the display states when Freeze protection is on - if ( aqdata->frz_protect_set_point != TEMP_UNKNOWN || _aqconfig_.force_frzprotect_setpoints ) { + if ( aqdata->frz_protect_set_point != TEMP_UNKNOWN || ENABLE_FREEZEPROTECT ) { //length += sprintf(buffer+length, ", \"%s\": \"%s\"", FREEZE_PROTECT, aqdata->frz_protect_state==ON?JSON_ON:JSON_ENABLED); length += sprintf(buffer+length, ", \"%s\": \"%s\"", FREEZE_PROTECT, LED2text(aqdata->frz_protect_state) ); } @@ -1089,40 +1108,55 @@ int json_cfg_element_OLD(char* buffer, int size, const char *name, const void *v #ifdef CONFIG_EDITOR -int json_cfg_element(char* buffer, int size, const char *name, const void *value, cfg_value_type type, uint8_t mask, char *valid_val) { +int json_cfg_element(char* buffer, int size, const char *name, const void *value, cfg_value_type type, uint8_t mask, char *valid_val, bool advanced) { int result = 0; char valid_values[256]; + char adv[20]; if (valid_val != NULL) { sprintf(valid_values,",\"valid values\":%s",valid_val); } + sprintf(adv,",\"advanced\": \"%s\"", advanced?"yes":"no"); + + switch(type){ case CFG_INT: - result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%d\", \"type\":\"int\" %s}", name, *(int *)value, (valid_val==NULL?"":valid_values) ); + if (*(int *)value == AQ_UNKNOWN) { + result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"\", \"type\":\"int\" %s %s}", name, (valid_val==NULL?"":valid_values),adv ); + } else { + result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%d\", \"type\":\"int\" %s %s}", name, *(int *)value, (valid_val==NULL?"":valid_values),adv ); + } break; case CFG_STRING: - result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"string\" %s}", name, *(char **)value, (valid_val==NULL?"":valid_values) ); + if (*(char **)value == NULL) { + result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"\", \"type\":\"string\" %s %s}", name, (valid_val==NULL?"":valid_values),adv ); + } else { + result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"string\" %s %s}", name, *(char **)value, (valid_val==NULL?"":valid_values),adv ); + } break; case CFG_BOOL: - result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"bool\" %s}", name, bool2text(*(bool *)value), (valid_val==NULL?"":valid_values)); + //result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"bool\" %s}", name, bool2text(*(bool *)value), (valid_val==NULL?"":valid_values)); + result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"bool\", \"valid values\": %s %s}", name, bool2text(*(bool *)value), CFG_V_BOOL, adv); break; case CFG_HEX: - result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"0x%02hhx\", \"type\":\"hex\" %s}", name, *(unsigned char *)value, (valid_val==NULL?"":valid_values) ); + result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"0x%02hhx\", \"type\":\"hex\" %s %s}", name, *(unsigned char *)value, (valid_val==NULL?"":valid_values), adv ); break; case CFG_FLOAT: - result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%f\", \"type\":\"float\" %s}", name, *(float *)value, (valid_val==NULL?"":valid_values) ); + result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%f\", \"type\":\"float\" %s %s}", name, *(float *)value, (valid_val==NULL?"":valid_values), adv ); break; case CFG_BITMASK: - result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"bool\" %s}", name, (*(uint8_t *)value & mask) == mask? bool2text(true):bool2text(false) ,CFG_V_BOOL ); + result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"bool\", \"valid values\": %s %s}", name, (*(uint8_t *)value & mask) == mask? bool2text(true):bool2text(false) ,CFG_V_BOOL, adv ); break; case CFG_SPECIAL: if (strncasecmp(name, CFG_N_log_level, strlen(CFG_N_log_level)) == 0) { //fprintf(fp, "%s=%s\n", _cfgParams[i].name, loglevel2cgn_name(_aqconfig_.log_level)); //result = json_cfg_element(buffer+length, size-length, CFG_N_log_level, &stringptr, CFG_STRING, "[\"DEBUG\", \"INFO\", \"NOTICE\", \"WARNING\", \"ERROR\"]")); //stringptr = loglevel2cgn_name(_aqconfig_.log_level); - result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"string\" %s}",name,loglevel2cgn_name(*(int *)value), "[\"DEBUG\", \"INFO\", \"NOTICE\", \"WARNING\", \"ERROR\"]"); + result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"string\" %s %s}",name,loglevel2cgn_name(*(int *)value), ",\"valid values\":[\"DEBUG\", \"INFO\", \"NOTICE\", \"WARNING\", \"ERROR\"]", adv); + } else if (strncasecmp(name, CFG_N_panel_type, strlen(CFG_N_panel_type)) == 0) { + result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"%s\", \"type\":\"string\" %s}",name,getShortPanelString(), adv); } else { result = snprintf(buffer, size, ",\"%s\" : {\"value\":\"Something went wrong\", \"type\":\"string\"}",name); } @@ -1130,7 +1164,7 @@ int json_cfg_element(char* buffer, int size, const char *name, const void *value } if (result <= 0 || result >= size) { - LOG(NET_LOG,LOG_ERR, "Buffer full in build_aqualink_config_JSON(), result truncated!"); + LOG(NET_LOG,LOG_ERR, "Buffer full, result truncated! size left=%d needed=%d @ element %s\n",size,result,name); return 0; } @@ -1166,7 +1200,9 @@ int build_aqualink_config_JSON(char* buffer, int size, struct aqualinkdata *aq_d int result; int i; char buf[256]; + char buf1[256]; const char *stringptr; + int delectCharAt = 0; if ((result = snprintf(buffer+length, size-length, "{\"type\": \"config\"")) < 0 || result >= size-length) { length += snprintf(buffer+length, size-length, "}"); @@ -1174,29 +1210,103 @@ int build_aqualink_config_JSON(char* buffer, int size, struct aqualinkdata *aq_d } else { length += result; } - +/* if ((result = snprintf(buffer+length, size-length, ",\"status\": \"!!!! NOT FULLY IMPLIMENTED YET !!!!\"")) < 0 || result >= size-length) { length += snprintf(buffer+length, size-length, "}"); return length; } else { length += result; } +*/ + if ((result = snprintf(buffer+length, size-length, ",\"max_pumps\": \"%d\",\"max_lights\": \"%d\",\"max_sensors\": \"%d\",\"max_light_programs\": \"%d\",\"max_vbuttons\": \"%d\"", + MAX_PUMPS,MAX_LIGHTS,MAX_SENSORS,LIGHT_COLOR_OPTIONS-1, (TOTAL_BUTTONS - aq_data->virtual_button_start) )) < 0 || result >= size-length) { + length += snprintf(buffer+length, size-length, "}"); + return length; + } else { + length += result; + } + #ifdef CONFIG_DEV_TEST for (int i=0; i <= _numCfgParams; i++) { - if ((result = json_cfg_element(buffer+length, size-length, _cfgParams[i].name, _cfgParams[i].value_ptr, _cfgParams[i].value_type, _cfgParams[i].mask, _cfgParams[i].valid_values)) <= 0) + // We can't change web_directory or port while running, so don;t even chow those options. + // mongoose holds a pointer to the string web_directoy, so can;t change that easily while running + // web port = well derr we are using that currently + if ( strncasecmp(_cfgParams[i].name, CFG_N_socket_port, strlen(CFG_N_socket_port)) == 0 || + strncasecmp(_cfgParams[i].name, CFG_N_web_directory, strlen(CFG_N_web_directory)) == 0 ) { + continue; + } + if ((result = json_cfg_element(buffer+length, size-length, _cfgParams[i].name, _cfgParams[i].value_ptr, _cfgParams[i].value_type, _cfgParams[i].mask, _cfgParams[i].valid_values, _cfgParams[i].advanced)) <= 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; + } + } + + // TODO add custom light modes/colors + // TODO add RSSD LOG FILTERS + // Add custom censors + + for (i = 1; i <= aq_data->num_sensors; i++) + { + length += sprintf(buffer+length, ",\"sensor_%.2d\":{ \"advanced\":\"yes\",",i ); + // The next json_cfg_element() call will add a , at the beginning, so save the next char index so we can delete it later. + delectCharAt = length; + + //fprintf(fp,"\nsensor_%.2d_path=%s\n",i+1,aqdata->sensors->path); + //fprintf(fp,"sensor_%.2d_label=%s\n",i+1,aqdata->sensors->label); + //fprintf(fp,"sensor_%.2d_factor=%f\n",i+1,aqdata->sensors->factor); + sprintf(buf,"sensor_%.2d_path", i); + if ((result = json_cfg_element(buffer+length, size-length, buf, &aq_data->sensors[i-1].path, CFG_STRING, 0, NULL, true)) <= 0) return length; else length += result; + + sprintf(buf,"sensor_%.2d_label", i); + if ((result = json_cfg_element(buffer+length, size-length, buf, &aq_data->sensors[i-1].label, CFG_STRING, 0, NULL, true)) <= 0) + return length; + else + length += result; + + sprintf(buf,"sensor_%.2d_factor", i); + if ((result = json_cfg_element(buffer+length, size-length, buf, &aq_data->sensors[i-1].factor, CFG_FLOAT, 0, NULL, true)) <= 0) + return length; + else + length += result; + + length += sprintf(buffer+length, "}" ); + if (delectCharAt != 0) { + buffer[delectCharAt] = ' '; + delectCharAt = 0; + } } - #endif + + // add custom light modes/colors + bool isShow; + const char *lname; + const char *bufptr = buf1; + for (i=1; i < LIGHT_COLOR_OPTIONS; i++) { + if ((lname = get_aqualinkd_light_mode_name(i, &isShow)) != NULL) { + //fprintf(fp,"light_program_%.2d=%s%s\n",i,lname,isShow?" - show":""); + sprintf(buf,"light_program_%.2d", i); + sprintf(buf1,"%s%s",lname,isShow?" - show":""); + //printf("%s %s\n",buf,buf1); + if ((result = json_cfg_element(buffer+length, size-length, buf, &bufptr, CFG_STRING, 0, NULL, true)) <= 0) + return length; + else + length += result; + + } else { + break; + } + } + - // TODO add custom light modes/colors - // TODO add RSSD LOG FILTERS - - // TODO add devmask for reading RS485 external + #else + @@ -1206,6 +1316,7 @@ int build_aqualink_config_JSON(char* buffer, int size, struct aqualinkdata *aq_d else length += result; */ +/* stringptr = getPanelString(); if ((result = json_cfg_element(buffer+length, size-length, CFG_N_panel_type, &stringptr, CFG_STRING, 0, NULL)) <= 0) return length; @@ -1289,9 +1400,11 @@ int build_aqualink_config_JSON(char* buffer, int size, struct aqualinkdata *aq_d else length += result; - +*/ +#endif // CONGIG_DEV_TEST // All buttons + for (i = 0; i < aq_data->total_buttons; i++) { char prefix[30]; @@ -1300,64 +1413,91 @@ int build_aqualink_config_JSON(char* buffer, int size, struct aqualinkdata *aq_d } else { sprintf(prefix,"button_%.2d",i+1); } + + //length += sprintf(buffer+length, ",\"%s\":{",prefix ); + length += sprintf(buffer+length, ",\"%s\":{ \"default\":\"%s\", ",prefix, aq_data->aqbuttons[i].name ); + // The next json_cfg_element() call will add a , at the beginning, so save the next char index so we can delete it later. + delectCharAt = length; sprintf(buf,"%s_label", prefix); - if ((result = json_cfg_element(buffer+length, size-length, buf, &aq_data->aqbuttons[i].label, CFG_STRING, 0, NULL)) <= 0) + if ((result = json_cfg_element(buffer+length, size-length, buf, &aq_data->aqbuttons[i].label, CFG_STRING, 0, NULL, false)) <= 0) { + LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length); return length; - else + } else length += result; if (isVS_PUMP(aq_data->aqbuttons[i].special_mask)) { if (((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpIndex > 0) { sprintf(buf,"%s_pumpIndex", prefix); - if ((result = json_cfg_element(buffer+length, size-length, buf, &((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpIndex, CFG_INT, 0, NULL)) <= 0) + if ((result = json_cfg_element(buffer+length, size-length, buf, &((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpIndex, CFG_INT, 0, NULL, false)) <= 0) { + LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length); return length; - else + } else length += result; } if (((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpID != NUL) { sprintf(buf,"%s_pumpID", prefix); - if ((result = json_cfg_element(buffer+length, size-length, buf, &((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpID, CFG_HEX, 0, NULL)) <= 0) + if ((result = json_cfg_element(buffer+length, size-length, buf, &((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpID, CFG_HEX, 0, NULL, false)) <= 0) { + LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length); return length; - else + } else length += result; } if (((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpName[0] != '\0') { sprintf(buf,"%s_pumpName", prefix); stringptr = ((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpName; - if ((result = json_cfg_element(buffer+length, size-length, buf, &stringptr, CFG_STRING, 0, NULL)) <= 0) + if ((result = json_cfg_element(buffer+length, size-length, buf, &stringptr, CFG_STRING, 0, NULL, false)) <= 0) { + LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length); return length; - else + } else length += result; } if (((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpType != PT_UNKNOWN) { sprintf(buf,"%s_pumpType", prefix); stringptr = pumpType2String(((pump_detail *)aq_data->aqbuttons[i].special_mask_ptr)->pumpType); - if ((result = json_cfg_element(buffer+length, size-length, buf, &stringptr, CFG_STRING, 0, "[\"JANDY ePUMP\",\"Pentair VS\",\"Pentair VF\"]")) <= 0) + if ((result = json_cfg_element(buffer+length, size-length, buf, &stringptr, CFG_STRING, 0, "[\"JANDY ePUMP\",\"Pentair VS\",\"Pentair VF\"]", false) ) <= 0) { + LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length); return length; - else + } else length += result; } } else if (isPLIGHT(aq_data->aqbuttons[i].special_mask)) { if (((clight_detail *)aq_data->aqbuttons[i].special_mask_ptr)->lightType > 0) { sprintf(buf,"%s_lightMode", prefix); - if ((result = json_cfg_element(buffer+length, size-length, buf, &((clight_detail *)aq_data->aqbuttons[i].special_mask_ptr)->lightType, CFG_INT, 0, NULL)) <= 0) + if ((result = json_cfg_element(buffer+length, size-length, buf, &((clight_detail *)aq_data->aqbuttons[i].special_mask_ptr)->lightType, CFG_INT, 0, NULL, false)) <= 0) { + LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length); return length; - else + } else length += result; } } else if ( (isVBUTTON(aq_data->aqbuttons[i].special_mask) && aq_data->aqbuttons[i].rssd_code >= IAQ_ONETOUCH_1 && aq_data->aqbuttons[i].rssd_code <= IAQ_ONETOUCH_6 ) ) { sprintf(buf,"%s_onetouchID", prefix); int oID = (aq_data->aqbuttons[i].rssd_code - 15); - if ((result = json_cfg_element(buffer+length, size-length, buf, &oID, CFG_INT, 0, "[\"1\",\"2\",\"3\",\"4\",\"5\",\"6\"]")) <= 0) + if ((result = json_cfg_element(buffer+length, size-length, buf, &oID, CFG_INT, 0, "[\"1\",\"2\",\"3\",\"4\",\"5\",\"6\"]", false)) <= 0) { + LOG(NET_LOG,LOG_ERR, "Config json buffer full in, result truncated! size=%d curently used=%d\n",size,length); return length; - else + } else length += result; - } + } + + length += sprintf(buffer+length, "}" ); + if (delectCharAt != 0) { + buffer[delectCharAt] = ' '; + delectCharAt = 0; + } + + } + + // Need to add one last element, can be crap. Makes the HTML/JS easier in the loop + if ((result = snprintf(buffer+length, size-length, ",\"version\": \"1.0\"")) < 0 || result >= size-length) { + length += snprintf(buffer+length, size-length, "}"); + return length; + } else { + length += result; } diff --git a/source/json_messages.h b/source/json_messages.h index 7ffae87..18bef28 100644 --- a/source/json_messages.h +++ b/source/json_messages.h @@ -7,7 +7,9 @@ //#define JSON_BUFFER_SIZE 4000 //#define JSON_STATUS_SIZE 1024 #define JSON_LABEL_SIZE 600 -#define JSON_BUFFER_SIZE 5120 +//#define JSON_BUFFER_SIZE 8192 +//#define JSON_BUFFER_SIZE 10240 +#define JSON_BUFFER_SIZE 12288 #define JSON_STATUS_SIZE 2048 #define JSON_SIMULATOR_SIZE 2048 diff --git a/source/net_services.c b/source/net_services.c index a98d906..a868ac4 100644 --- a/source/net_services.c +++ b/source/net_services.c @@ -588,7 +588,7 @@ void send_mqtt_led_state_msg(struct mg_connection *nc, char *dev_name, aqledstat void send_mqtt_swg_state_msg(struct mg_connection *nc, char *dev_name, aqledstate state) { //send_mqtt_led_state_msg(nc, dev_name, state, SWG_ON, SWG_OFF); - send_mqtt_led_state_msg(nc, dev_name, state, "2", "0"); + send_mqtt_led_state_msg(nc, dev_name, state, MQTT_COOL, MQTT_OFF); } void send_mqtt_heater_state_msg(struct mg_connection *nc, char *dev_name, aqledstate state) @@ -787,6 +787,17 @@ void mqtt_broadcast_aqualinkstate(struct mg_connection *nc) //send_mqtt_string_msg(nc, FREEZE_PROTECT_ENABELED, MQTT_ON); } + if (_aqualink_data->chiller_set_point != TEMP_UNKNOWN && _aqualink_data->chiller_set_point != _last_mqtt_aqualinkdata.chiller_set_point) { + _last_mqtt_aqualinkdata.chiller_set_point = _aqualink_data->chiller_set_point; + send_mqtt_setpoint_msg(nc, CHILLER, _aqualink_data->chiller_set_point); + //send_mqtt_string_msg(nc, CHILLER_ENABELED, MQTT_ON); + } + if (_aqualink_data->chiller_state != _last_mqtt_aqualinkdata.chiller_state) { + _last_mqtt_aqualinkdata.chiller_state = _aqualink_data->chiller_state; + //send_mqtt_string_msg(nc, CHILLER, _aqualink_data->chiller_state==ON?MQTT_ON:MQTT_OFF); + send_mqtt_led_state_msg(nc, CHILLER, _aqualink_data->chiller_state, MQTT_COOL, MQTT_OFF); + } + if (_aqualink_data->battery != _last_mqtt_aqualinkdata.battery) { _last_mqtt_aqualinkdata.battery = _aqualink_data->battery; send_mqtt_string_msg(nc, BATTERY_STATE, _aqualink_data->battery==OK?MQTT_ON:MQTT_OFF); @@ -1239,14 +1250,17 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float } else if (ri3 != NULL && (strncasecmp(ri2, "setpoint", 8) == 0) && (strncasecmp(ri3, "set", 3) == 0)) { int val = convertTemp? round(degCtoF(value)) : round(value); if (strncmp(ri1, BTN_POOL_HTR, strlen(BTN_POOL_HTR)) == 0) { - //create_program_request(from, POOL_HTR_SETOINT, val, 0); - panel_device_request(_aqualink_data, POOL_HTR_SETOINT, 0, val, from); + //create_program_request(from, POOL_HTR_SETPOINT, val, 0); + panel_device_request(_aqualink_data, POOL_HTR_SETPOINT, 0, val, from); } else if (strncmp(ri1, BTN_SPA_HTR, strlen(BTN_SPA_HTR)) == 0) { - //create_program_request(from, SPA_HTR_SETOINT, val, 0); - panel_device_request(_aqualink_data, SPA_HTR_SETOINT, 0, val, from); + //create_program_request(from, SPA_HTR_SETPOINT, val, 0); + panel_device_request(_aqualink_data, SPA_HTR_SETPOINT, 0, val, from); } else if (strncmp(ri1, FREEZE_PROTECT, strlen(FREEZE_PROTECT)) == 0) { //create_program_request(from, FREEZE_SETPOINT, val, 0); panel_device_request(_aqualink_data, FREEZE_SETPOINT, 0, val, from); + } else if (strncmp(ri1, CHILLER, strlen(CHILLER)) == 0) { + //create_program_request(from, FREEZE_SETPOINT, val, 0); + panel_device_request(_aqualink_data, CHILLER_SETPOINT, 0, val, from); } else if (strncmp(ri1, "SWG", 3) == 0) { // If we get SWG percent as setpoint message it's from homebridge so use the convert //int val = round(degCtoF(value)); //int val = convertTemp? round(degCtoF(value)) : round(value); @@ -1892,7 +1906,7 @@ void action_websocket_request(struct mg_connection *nc, struct websocket_message { DEBUG_TIMER_START(&tid); char message[JSON_BUFFER_SIZE]; - save_config_js((char *)wm->data, wm->size, message, JSON_BUFFER_SIZE); + save_config_js((char *)wm->data, wm->size, message, JSON_BUFFER_SIZE, _aqualink_data); DEBUG_TIMER_STOP(tid, NET_LOG, "action_websocket_request() save_config_js took"); ws_send(nc, message); } @@ -2108,6 +2122,8 @@ void reset_last_mqtt_status() _last_mqtt_aqualinkdata.service_mode_state = -1; _last_mqtt_aqualinkdata.pool_htr_set_point = TEMP_REFRESH; _last_mqtt_aqualinkdata.spa_htr_set_point = TEMP_REFRESH; + _last_mqtt_aqualinkdata.chiller_set_point = TEMP_REFRESH; + _last_mqtt_aqualinkdata.chiller_state = LED_S_UNKNOWN; _last_mqtt_aqualinkdata.ph = -1; _last_mqtt_aqualinkdata.orp = -1; _last_mqtt_aqualinkdata.boost = -1; @@ -2136,11 +2152,13 @@ void reset_last_mqtt_status() } void start_mqtt(struct mg_mgr *mgr) { - LOG(NET_LOG,LOG_NOTICE, "Starting MQTT client to %s\n", _aqconfig_.mqtt_server); + if ( _aqconfig_.mqtt_server == NULL || ( _aqconfig_.mqtt_aq_topic == NULL && _aqconfig_.mqtt_dz_pub_topic == NULL && _aqconfig_.mqtt_dz_sub_topic == NULL) ) return; + LOG(NET_LOG,LOG_NOTICE, "Starting MQTT client to %s\n", _aqconfig_.mqtt_server); + if (mg_connect(mgr, _aqconfig_.mqtt_server, ev_handler) == NULL) { LOG(NET_LOG,LOG_ERR, "Failed to create MQTT listener to %s\n", _aqconfig_.mqtt_server); } else { diff --git a/source/onetouch_aq_programmer.c b/source/onetouch_aq_programmer.c index 54d2ad5..4e9ef2a 100644 --- a/source/onetouch_aq_programmer.c +++ b/source/onetouch_aq_programmer.c @@ -726,7 +726,7 @@ void *set_aqualink_onetouch_pool_heater_temp( void *ptr ) waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_ONETOUCH_POOL_HEATER_TEMP); int val = atoi((char*)threadCtrl->thread_args); - val = setpoint_check(POOL_HTR_SETOINT, val, aq_data); + val = setpoint_check(POOL_HTR_SETPOINT, val, aq_data); LOG(ONET_LOG,LOG_DEBUG, "OneTouch set pool heater temp to %d\n", val); set_aqualink_onetouch_heater_setpoint(aq_data, true, val); @@ -746,7 +746,7 @@ void *set_aqualink_onetouch_spa_heater_temp( void *ptr ) waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_ONETOUCH_SPA_HEATER_TEMP); int val = atoi((char*)threadCtrl->thread_args); - val = setpoint_check(SPA_HTR_SETOINT, val, aq_data); + val = setpoint_check(SPA_HTR_SETPOINT, val, aq_data); LOG(ONET_LOG,LOG_DEBUG, "OneTouch set spa heater temp to %d\n", val); set_aqualink_onetouch_heater_setpoint(aq_data, false, val); diff --git a/source/pda.c b/source/pda.c index 1a27549..0f1bd67 100644 --- a/source/pda.c +++ b/source/pda.c @@ -41,13 +41,14 @@ // static struct aqualinkdata _aqualink_data; static struct aqualinkdata *_aqualink_data; static unsigned char _last_packet_type; -static unsigned long _pda_loop_cnt = 0; +static unsigned long _pda_loop_cnt = -0; static bool _initWithRS = false; // Each RS message is around 0.25 seconds apart - -#define PDA_SLEEP_FOR 120 // 30 seconds -#define PDA_WAKE_FOR 6 // ~1 seconds +//#define PDA_SLEEP_FOR 120 // +#define PDA_SLEEP_FOR 40 // +#define PDA_WAKE_FOR 10 // +#define PDA_IGNORE_NO_STATUS_PAGE 40 // 36 seems to be the status loop @@ -59,14 +60,16 @@ void init_pda(struct aqualinkdata *aqdata) bool pda_shouldSleep() { - //LOG(PDA_LOG,LOG_DEBUG, "PDA loop count %d, will sleep at %d\n",_pda_loop_cnt,PDA_LOOP_COUNT); - if (_pda_loop_cnt++ < PDA_WAKE_FOR) { - return false; - } else if (_pda_loop_cnt > PDA_WAKE_FOR + PDA_SLEEP_FOR) { - _pda_loop_cnt = 0; - return false; - } + static bool sawHome = false; + static bool sawStatus = false; + static bool inSleep = false; + if (pda_m_type() == PM_HOME) + sawHome = true; + + if (pda_m_type() == PM_EQUIPTMENT_STATUS) + sawStatus = true; + // NSF NEED TO CHECK ACTIVE THREADS. if (_aqualink_data->active_thread.thread_id != 0) { LOG(PDA_LOG,LOG_DEBUG, "PDA can't sleep as thread %d,%p is active\n", @@ -79,10 +82,47 @@ bool pda_shouldSleep() { // Last see if there are any open websockets. (don't sleep if the web UI is open) if ( _aqualink_data->open_websockets > 0 ) { + _pda_loop_cnt = 0; LOG(PDA_LOG,LOG_DEBUG, "PDA can't sleep as websocket is active\n"); return false; } - + + // Only sleep if we are on home page + if (pda_m_type() != PM_HOME /*&& pda_m_type() != PM_BUILDING_HOME*/) { + _pda_loop_cnt = 0; + LOG(PDA_LOG,LOG_DEBUG, "PDA can't sleep not on home page\n"); + return false; + } + + _pda_loop_cnt++; + + if (inSleep == false && + (sawHome == false || ( sawStatus == false && _pda_loop_cnt <= PDA_IGNORE_NO_STATUS_PAGE))){ + LOG(PDA_LOG,LOG_DEBUG, "PDA can't sleep haven't seen both Home and Status pages\n"); + return false; + } else if (inSleep == false && + (sawHome == false || ( sawStatus == false && _pda_loop_cnt > PDA_IGNORE_NO_STATUS_PAGE))){ + // We hit the timeout waiting for status page (ie no devices are on), so reset counter + LOG(PDA_LOG,LOG_DEBUG, "PDA Allowing sleep - timeout waiting for Status page (all devices must be off)\n"); + _pda_loop_cnt = PDA_WAKE_FOR; + } + + //_pda_loop_cnt++; + + LOG(PDA_LOG,LOG_DEBUG, "PDA loop count %d, wake between 0->%d, sleep %d->%d\n",_pda_loop_cnt, PDA_WAKE_FOR, PDA_WAKE_FOR, (PDA_WAKE_FOR + PDA_SLEEP_FOR)); + if (_pda_loop_cnt < PDA_WAKE_FOR) { + //inSleep == false; + return false; + } else if (_pda_loop_cnt > PDA_WAKE_FOR + PDA_SLEEP_FOR) { + _pda_loop_cnt = 0; + inSleep = false; + return false; + } + + sawHome = false; + sawStatus = false; + inSleep = true; + return true; } diff --git a/source/pda_aq_programmer.c b/source/pda_aq_programmer.c index d323288..2894366 100644 --- a/source/pda_aq_programmer.c +++ b/source/pda_aq_programmer.c @@ -1168,7 +1168,7 @@ void *set_aqualink_PDA_pool_heater_temps( void *ptr ) waitForSingleThreadOrTerminate(threadCtrl, AQ_PDA_SET_POOL_HEATER_TEMPS); int val = atoi((char*)threadCtrl->thread_args); - val = setpoint_check(POOL_HTR_SETOINT, val, aq_data); + val = setpoint_check(POOL_HTR_SETPOINT, val, aq_data); set_PDA_aqualink_heater_setpoint(aq_data, val, true); @@ -1188,7 +1188,7 @@ void *set_aqualink_PDA_spa_heater_temps( void *ptr ) waitForSingleThreadOrTerminate(threadCtrl, AQ_PDA_SET_SPA_HEATER_TEMPS); int val = atoi((char*)threadCtrl->thread_args); - val = setpoint_check(SPA_HTR_SETOINT, val, aq_data); + val = setpoint_check(SPA_HTR_SETPOINT, val, aq_data); set_PDA_aqualink_heater_setpoint(aq_data, val, false); diff --git a/source/serial_logger.c b/source/serial_logger.c index bb63968..922fe71 100644 --- a/source/serial_logger.c +++ b/source/serial_logger.c @@ -41,7 +41,7 @@ #define SLOG_MAX 80 #define PACKET_MAX 800 -#define VERSION "serial_logger V2.7" +#define VERSION "serial_logger V2.8" /* typedef enum used { @@ -152,6 +152,7 @@ int serial_logger (int rs_fd, char *port_name, int logLevel, int slogger_packets #define PC_DOCK " <-- PC Interface (RS485 to RS232)" #define PDA " <-- PDA Remote" #define EPUMP " <-- Jandy VSP ePump" +#define EPUMP2 " <-- Jandy VSP (rev W or newer)" #define CHEM " <-- Chemlink" #define JXI_HEATER " <-- LXi / LRZ Heater" @@ -169,6 +170,8 @@ int serial_logger (int rs_fd, char *port_name, int logLevel, int slogger_packets #define P_RWCTL " <-- Remote wireless controller (Screen Logic)" #define P_CTL " <-- Pool controller (EasyTouch)" +// 0xE0 -> this is pump ID on rev W panel (16 pumps available total) - so maybe last is 0xF0 or 0xEF + const char *getDevice(unsigned char ID) { if (ID >= 0x00 && ID <= 0x03) return MASTER; @@ -219,6 +222,9 @@ const char *getDevice(unsigned char ID) { if (ID >= 0x28 && ID <= 0x2B) return REM_PWR_CENT; + if (ID >= 0xE0 && ID <= 0xEF) // this could end at 0xF0 + return EPUMP2; + //if (ID == 0x08) // return KEYPAD; diff --git a/source/serialadapter.c b/source/serialadapter.c index 006cfd1..5002c94 100644 --- a/source/serialadapter.c +++ b/source/serialadapter.c @@ -162,7 +162,7 @@ void set_aqualink_rssadapter_aux_state(int buttonIndex, bool turnOn) */ void increase_aqualink_rssadapter_pool_setpoint(char *args, struct aqualinkdata *aqdata) { int val = atoi(args); - val = setpoint_check(POOL_HTR_SETOINT, aqdata->pool_htr_set_point + val, aqdata); + val = setpoint_check(POOL_HTR_SETPOINT, aqdata->pool_htr_set_point + val, aqdata); LOG(RSSA_LOG,LOG_DEBUG, "Increasing pool heater from %d to %d\n",aqdata->pool_htr_set_point,val); @@ -172,7 +172,7 @@ void increase_aqualink_rssadapter_pool_setpoint(char *args, struct aqualinkdata void increase_aqualink_rssadapter_spa_setpoint(char *args, struct aqualinkdata *aqdata) { int val = atoi(args); - val = setpoint_check(SPA_HTR_SETOINT, aqdata->spa_htr_set_point + val, aqdata); + val = setpoint_check(SPA_HTR_SETPOINT, aqdata->spa_htr_set_point + val, aqdata); LOG(RSSA_LOG,LOG_DEBUG, "Increasing spa heater from %d to %d\n",aqdata->spa_htr_set_point,val); @@ -181,7 +181,7 @@ void increase_aqualink_rssadapter_spa_setpoint(char *args, struct aqualinkdata * void set_aqualink_rssadapter_pool_setpoint(char *args, struct aqualinkdata *aqdata) { int val = atoi(args); - val = setpoint_check(POOL_HTR_SETOINT, val, aqdata); + val = setpoint_check(POOL_HTR_SETPOINT, val, aqdata); LOG(RSSA_LOG,LOG_DEBUG, "Setting pool heater to %d\n",val); @@ -198,7 +198,7 @@ void set_aqualink_rssadapter_pool_setpoint(char *args, struct aqualinkdata *aqda void set_aqualink_rssadapter_spa_setpoint(char *args, struct aqualinkdata *aqdata) { int val = atoi(args); - val = setpoint_check(SPA_HTR_SETOINT, val, aqdata); + val = setpoint_check(SPA_HTR_SETPOINT, val, aqdata); queue_aqualink_rssadapter_setpoint( (isSINGLE_DEV_PANEL?RS_SA_POOLSP2:RS_SA_SPASP), val); /* diff --git a/source/version.h b/source/version.h index 98dae5f..7334f77 100644 --- a/source/version.h +++ b/source/version.h @@ -4,4 +4,4 @@ #define AQUALINKD_SHORT_NAME "AqualinkD" // Use Magor . Minor . Patch -#define AQUALINKD_VERSION "2.5.2 (dev)" +#define AQUALINKD_VERSION "2.6.0 (dev)" diff --git a/web/aqmanager.html b/web/aqmanager.html index 15d0185..65be4cf 100644 --- a/web/aqmanager.html +++ b/web/aqmanager.html @@ -302,13 +302,23 @@