pull/296/head
sfeakes 2024-05-21 17:06:31 -05:00
parent 7c37a7cc23
commit 767745c97d
28 changed files with 682 additions and 44 deletions

View File

@ -77,7 +77,7 @@ endif
# Main source files
SRCS = aqualinkd.c utils.c config.c aq_serial.c aq_panel.c 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 simulator.c timespec_subtract.c
serial_logger.c mongoose.c hassio.c simulator.c timespec_subtract.c
AQ_FLAGS =

View File

@ -67,7 +67,7 @@ Designed to mimic AqualinkRS devices, used to fully configure the master control
* Full support for homekit scenes: ie: Create a "Spa scene" to: "turn spa on, set spa heater to X temperature and turn spa blower on", etc etc).
### In Home Assistant
<img src="extras/HomeAssistant2.png?raw=true" width="800"></img>
<img src="extras/HASSIO.png?raw=true" width="800"></img>
## All Web interfaces.
* http://aqualink.ip/ <- (Standard WEB UI
@ -87,7 +87,6 @@ Designed to mimic AqualinkRS devices, used to fully configure the master control
# Update in Release 2.3.5
* NEED TO FIX
* Not saying programming mode
* Not always doing on/off
* Heaters are slow to turn on, need to hit extra button
* Spa turns on Spa Heat (first button on home page ???)
@ -98,6 +97,9 @@ Designed to mimic AqualinkRS devices, used to fully configure the master control
* Panel version
* can't use iaquatouch panel / wireless
* Added Home Assistant integration through MQTT discover
* Please read the Wiki section on this [Wiki - HASSIO](https://github.com/sfeakes/AqualinkD/wiki#HASSIO)
* There are still some enhacments to come on this.
* Added support for reading extended information for Jandy JXi heaters.
* Added Color Light to iAqualinkTouch protocol.
* Added iAqualinkTouch support for PDA only panels that can use that protocol.
@ -119,11 +121,11 @@ Designed to mimic AqualinkRS devices, used to fully configure the master control
# Update in Release 2.3.3
* Introduced Aqualink Manager UI http://aqualink.ip/aqmanager.html
* [AqualinkD Manager](#AQManager)
* [AqualinkD Manager](https://github.com/sfeakes/AqualinkD/wiki#AQManager)
* Moved logging into systemd/journal (journalctl) from syslog
* [AqualinkD Log](#Log)
* [AqualinkD Log](https://github.com/sfeakes/AqualinkD/wiki#Log)
* Updated to scheduler
* [AqualinkD Scheduler](#Scheduler)
* [AqualinkD Scheduler](https://github.com/sfeakes/AqualinkD/wiki#Scheduler)
* Introduced RS485 frame delay / timer.
* Improve PDA panels reliability (PDA pannels are slower than RS panels)
* Potentially fixed Pentair VSP / SWG problems since Pentair VSP use a different protocol, this will allow a timed delay for the VSP to post a status messages. Seems to only effect RS485 bus when both a Pentair VSP and Jandy SWG are present.

View File

@ -23,6 +23,7 @@
#define SWG_SETPOINT_TOPIC SWG_TOPIC "/setpoint"
#define SWG_EXTENDED_TOPIC SWG_TOPIC "/fullstatus"
#define SWG_BOOST_TOPIC SWG_TOPIC "/Boost"
#define SWG_BOOST_DURATION_TOPIC SWG_BOOST_TOPIC "/duration"
#define SWG_STATUS_MSG_TOPIC SWG_TOPIC "/Display_Message"
@ -42,8 +43,8 @@
#define BATTERY_STATE "Battery"
#define POOL_THERMO_TEMP_TOPIC BTN_POOL_HTR "/Temperature"
#define SPA_THERMO_TEMP_TOPIC BTN_SPA_HTR "/Temperature"
//#define POOL_THERMO_TEMP_TOPIC BTN_POOL_HTR "/Temperature"
//#define SPA_THERMO_TEMP_TOPIC BTN_SPA_HTR "/Temperature"
//#define PUMP_TOPIC "Pump_"
#define PUMP_RPM_TOPIC "/RPM"

View File

@ -590,14 +590,14 @@ void kick_aq_program_thread(struct aqualinkdata *aq_data, emulation_type source_
LOG(ONET_LOG, LOG_DEBUG, "Kicking OneTouch thread %d,%p\n",aq_data->active_thread.ptype, aq_data->active_thread.thread_id);
pthread_cond_broadcast(&aq_data->active_thread.thread_cond);
}
else if (source_type == ALLBUTTON && !in_ot_programming_mode(aq_data)) {
LOG(PROG_LOG, LOG_DEBUG, "Kicking RS thread %d,%p message '%s'\n",aq_data->active_thread.ptype, aq_data->active_thread.thread_id,aq_data->last_message);
pthread_cond_broadcast(&aq_data->active_thread.thread_cond);
}
else if (source_type == IAQTOUCH && in_iaqt_programming_mode(aq_data)) {
LOG(IAQT_LOG, LOG_DEBUG, "Kicking IAQ Touch thread %d,%p\n",aq_data->active_thread.ptype, aq_data->active_thread.thread_id);
pthread_cond_broadcast(&aq_data->active_thread.thread_cond);
}
else if (source_type == ALLBUTTON && !in_ot_programming_mode(aq_data) && !in_iaqt_programming_mode(aq_data)) {
LOG(PROG_LOG, LOG_DEBUG, "Kicking RS Allbutton thread %d,%p message '%s'\n",aq_data->active_thread.ptype, aq_data->active_thread.thread_id,aq_data->last_message);
pthread_cond_broadcast(&aq_data->active_thread.thread_cond);
}
#ifdef AQ_PDA
else if (source_type == AQUAPDA && !in_ot_programming_mode(aq_data)) {
LOG(PDA_LOG, LOG_DEBUG, "Kicking PDA thread %d,%p\n",aq_data->active_thread.ptype, aq_data->active_thread.thread_id);

View File

@ -298,8 +298,9 @@ SPILLOVER IS DISABLED WHILE SPA IS ON
//#define SWG_STATUS_OFFLINE 0xFE
//#define SWG_STATUS_UNKNOWN -128 // Idiot. unsigned char....Derr.
#define SWG_STATUS_OFF 0xFF // Documented this as off in API, so don't change.
#define SWG_STATUS_UNKNOWN 0xFE
#define SWG_STATUS_OFF 0xFF // Documented this as off in API, so don't change.
#define SWG_STATUS_UNKNOWN 0xFE
#define SWG_STATUS_GENFAULT 0xFD //This is displayed in the panel, so adding it
// These are actual from RS485
#define SWG_STATUS_ON 0x00

View File

@ -231,6 +231,7 @@ struct aqualinkdata
clight_detail lights[MAX_LIGHTS];
bool boost;
char boost_msg[10];
int boost_duration; // need to remove boost message and use this
float ph;
int orp;

View File

@ -364,6 +364,7 @@ int16_t RS16_endswithLEDstate(char *msg)
#endif
void _processMessage(char *message, bool reset);
void processMessage(char *message)
@ -460,6 +461,7 @@ void _processMessage(char *message, bool reset)
if ((msg_loop & MSG_BOOST) != MSG_BOOST) {
_aqualink_data.boost = false;
_aqualink_data.boost_msg[0] = '\0';
_aqualink_data.boost_duration = 0;
//if (_aqualink_data.swg_percent >= 101)
// _aqualink_data.swg_percent = 0;
}
@ -625,7 +627,8 @@ void _processMessage(char *message, bool reset)
else if (strcasestr(msg, MSG_SWG_HIGH_SALT) != NULL)
setSWGdeviceStatus(&_aqualink_data, ALLBUTTON, SWG_STATUS_HI_SALT);
else if (strcasestr(msg, MSG_SWG_FAULT) != NULL)
setSWGdeviceStatus(&_aqualink_data, ALLBUTTON, SWG_STATUS_CHECK_PCB);
setSWGdeviceStatus(&_aqualink_data, ALLBUTTON, SWG_STATUS_GENFAULT);
//setSWGdeviceStatus(&_aqualink_data, ALLBUTTON, SWG_STATUS_CHECK_PCB);
// Any of these messages want to display.
strcpy(_aqualink_data.last_display_message, msg);
@ -736,9 +739,11 @@ void _processMessage(char *message, bool reset)
// Ignore messages if in programming mode. We get one of these turning off for some strange reason.
if (in_programming_mode(&_aqualink_data) == false) {
snprintf(_aqualink_data.boost_msg, 6, "%s", &msg[11]);
_aqualink_data.boost_duration = rsm_HHMM2min(_aqualink_data.boost_msg);
_aqualink_data.boost = true;
msg_loop |= MSG_BOOST;
msg_loop |= MSG_SWG;
//convert_boost_to_duration(_aqualink_data.boost_msg)
//if (_aqualink_data.ar_swg_status != SWG_STATUS_ON) {_aqualink_data.ar_swg_status = SWG_STATUS_ON;}
if (_aqualink_data.swg_percent != 101) {changeSWGpercent(&_aqualink_data, 101);}
//boost_msg_count = 0;
@ -996,7 +1001,7 @@ void action_delayed_request()
}
else
{
LOG(AQUA_LOG,LOG_NOTICE, "SWG % is already %d, not changing\n", _aqualink_data.unactioned.value);
LOG(AQUA_LOG,LOG_NOTICE, "SWG %% is already %d, not changing\n", _aqualink_data.unactioned.value);
}
}
// Let's just tell everyone we set it, before we actually did. Makes homekit happy, and it will re-correct on error.
@ -1322,6 +1327,8 @@ int startup(char *self, char *cfgFile)
LOG(AQUA_LOG,LOG_NOTICE, "Read SWG direct = %s\n", bool2text(READ_RSDEV_SWG));
LOG(AQUA_LOG,LOG_NOTICE, "Read ePump direct = %s\n", bool2text(READ_RSDEV_ePUMP));
LOG(AQUA_LOG,LOG_NOTICE, "Read vsfPump direct = %s\n", bool2text(READ_RSDEV_vsfPUMP));
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));
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);
@ -1541,6 +1548,8 @@ void main_loop()
_aqualink_data.orp = TEMP_UNKNOWN;
_aqualink_data.simulator_id = NUL;
_aqualink_data.simulator_active = SIM_NONE;
_aqualink_data.boost_duration = 0;
_aqualink_data.boost = false;
pthread_mutex_init(&_aqualink_data.active_thread.thread_mutex, NULL);
pthread_cond_init(&_aqualink_data.active_thread.thread_cond, NULL);
@ -1558,6 +1567,11 @@ void main_loop()
_aqualink_data.swg_ppm = 0;
}
if (_aqconfig_.force_chem_feeder == true) {
_aqualink_data.ph = 0;
_aqualink_data.orp = 0;
}
signal(SIGINT, intHandler);
signal(SIGTERM, intHandler);
signal(SIGQUIT, intHandler);

View File

@ -92,6 +92,7 @@ void init_parameters (struct aqconfig * parms)
parms->mqtt_dz_sub_topic = DEFAULT_MQTT_DZ_OUT;
parms->mqtt_dz_pub_topic = DEFAULT_MQTT_DZ_IN;
parms->mqtt_hass_discover_topic = DEFAULT_HASS_DISCOVER;
parms->mqtt_aq_topic = DEFAULT_MQTT_AQ_TP;
parms->mqtt_server = DEFAULT_MQTT_SERVER;
parms->mqtt_user = DEFAULT_MQTT_USER;
@ -129,6 +130,7 @@ void init_parameters (struct aqconfig * parms)
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;
@ -442,6 +444,15 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) {
} else if (strncasecmp(param, "mqtt_dz_pub_topic", 17) == 0) {
_aqconfig_.mqtt_dz_pub_topic = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param, "mqtt_hassio_discover_topic", 26) == 0) {
_aqconfig_.mqtt_hass_discover_topic = cleanalloc(value);
/* It might also make sence to also set these to true. Since aqualinkd does not know the state at the time discover topics are published.
_aqconfig_.force_swg;
_aqconfig_.force_ps_setpoints;
_aqconfig_.force_frzprotect_setpoints;
force_chem_feeder
*/
rtn=true;
} else if (strncasecmp(param, "mqtt_aq_topic", 13) == 0) {
_aqconfig_.mqtt_aq_topic = cleanalloc(value);
rtn=true;
@ -576,6 +587,9 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) {
} else if (strncasecmp (param, "force_frzprotect_setpoints", 26) == 0) {
_aqconfig_.force_frzprotect_setpoints = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "force_chem_feeder", 17) == 0) {
_aqconfig_.force_chem_feeder = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "debug_RSProtocol_bytes", 22) == 0) {
_aqconfig_.log_raw_bytes = text2bool(value);
rtn=true;

View File

@ -15,6 +15,7 @@
#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_SERVER NULL
#define DEFAULT_MQTT_USER NULL
@ -51,6 +52,7 @@ struct aqconfig
char *mqtt_dz_sub_topic;
char *mqtt_dz_pub_topic;
char *mqtt_aq_topic;
char *mqtt_hass_discover_topic;
char *mqtt_server;
char *mqtt_user;
char *mqtt_passwd;
@ -79,6 +81,7 @@ struct aqconfig
bool force_swg;
bool force_ps_setpoints;
bool force_frzprotect_setpoints;
bool force_chem_feeder;
int swg_zero_ignore;
bool display_warnings_web;
bool log_protocol_packets; // Read & Write as packets

View File

@ -218,6 +218,7 @@ bool isSWGDeviceErrorState(unsigned char status)
status == SWG_STATUS_LOW_TEMP ||
status == SWG_STATUS_HIGH_CURRENT ||
status == SWG_STATUS_NO_FLOW)
// Maybe add CLEAN_CELL and GENFAULT here
return true;
else
return false;
@ -225,6 +226,17 @@ bool isSWGDeviceErrorState(unsigned char status)
void setSWGdeviceStatus(struct aqualinkdata *aqdata, emulation_type requester, unsigned char status) {
static unsigned char last_status = SWG_STATUS_UNKNOWN;
/* This is only needed for DEBUG
static bool haveSeenRSSWG = false;
if (requester == JANDY_DEVICE) {
haveSeenRSSWG = true;
}
*/
// If we are reading state directly from RS458, then ignore everything else.
if ( READ_RSDEV_SWG && requester != JANDY_DEVICE ) {
return;
}
if ((aqdata->ar_swg_device_status == status) || (last_status == status)) {
//LOG(DJAN_LOG, LOG_DEBUG, "Set SWG device state to '0x%02hhx', request from %d\n", aqdata->ar_swg_device_status, requester);
@ -232,10 +244,15 @@ void setSWGdeviceStatus(struct aqualinkdata *aqdata, emulation_type requester, u
}
last_status = status;
// If we get (ALLBUTTON, SWG_STATUS_CHECK_PCB), it sends this for many status, like clean cell.
// If we get (ALLBUTTON, SWG_STATUS_CHECK_PCB // GENFAULT), it sends this for many status, like clean cell.
// So if we are in one of those states, don't use it.
if (requester == ALLBUTTON && status == SWG_STATUS_CHECK_PCB ) {
// Need to rethink this. Use general fault only if we are not reading SWG status direct from device
//if ( READ_RSDEV_SWG && requester == ALLBUTTON && status == SWG_STATUS_GENFAULT ) {
// SWG_STATUS_GENFAULT is shown on panels for many reasons, if we are NOT reading the status directly from the SWG
// then use it, otherwise disguard it as we will have a better status
if (requester == ALLBUTTON && status == SWG_STATUS_GENFAULT ) {
if (aqdata->ar_swg_device_status > SWG_STATUS_ON &&
aqdata->ar_swg_device_status < SWG_STATUS_TURNING_OFF) {
LOG(DJAN_LOG, LOG_DEBUG, "Ignoring set SWG device state to '0x%02hhx', request from %d\n", aqdata->ar_swg_device_status, requester);
@ -255,6 +272,7 @@ void setSWGdeviceStatus(struct aqualinkdata *aqdata, emulation_type requester, u
case SWG_STATUS_LOW_VOLTS:
case SWG_STATUS_LOW_TEMP:
case SWG_STATUS_CHECK_PCB:
case SWG_STATUS_GENFAULT:
aqdata->ar_swg_device_status = status;
aqdata->swg_led_state = isSWGDeviceErrorState(status)?ENABLE:ON;
break;
@ -398,6 +416,9 @@ aqledstate get_swg_led_state(struct aqualinkdata *aqdata)
case SWG_STATUS_OFF: // THIS IS OUR OFF STATUS, NOT AQUAPURE
return OFF;
break;
case SWG_STATUS_GENFAULT:
return ENABLE;
break;
default:
return (aqdata->swg_percent > 0?ON:ENABLE);
break;
@ -471,6 +492,11 @@ void get_swg_status_mqtt(struct aqualinkdata *aqdata, char *message, int *status
sprintf(message, "AQUAPURE OFF");
*dzalert = 0;
break;
case SWG_STATUS_GENFAULT:
*status = (aqdata->swg_percent > 0?SWG_ON:SWG_OFF);
sprintf(message, "AQUAPURE GENERAL FAULT");
*dzalert = 2;
break;
default:
*status = (aqdata->swg_percent > 0?SWG_ON:SWG_OFF);
sprintf(message, "AQUAPURE UNKNOWN STATUS");

BIN
extras/HASSIO.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

44
extras/aqualinkd-docker.cmd Executable file
View File

@ -0,0 +1,44 @@
#!/bin/bash
CONFDIR=/aquadconf
AQUA_CONF=$CONFDIR/aqualinkd.conf
# Check we have a config directory
if [ -d "$CONFDIR" ]; then
# Check we have config file, if not copy default
if [ ! -f "$AQUA_CONF" ]; then
echo "Warning no aqualinkd.conf in $CONFDIR", using default
cp /etc/aqualinkd.conf $CONFDIR
fi
# Replace local filesystem config with mounted config
ln -sf "$AQUA_CONF" /etc/aqualinkd.conf
# If we have a web config, replace the local filesystem with mounted
if [ -f "$CONFDIR/config.js" ]; then
ln -sf "$CONFDIR/config.js" /var/www/aqualinkd/config.js
fi
# If don't have a cron file, create one
if [ ! -f "$CONFDIR/aqualinkd.schedule" ]; then
echo "#***** AUTO GENERATED DO NOT EDIT *****" > "$CONFDIR/aqualinkd.schedule"
fi
# link mounted cron file to local filesystem.
ln -sf "$CONFDIR/aqualinkd.schedule" /etc/cron.d/aqualinkd
chmod 644 "$CONFDIR/aqualinkd.schedule"
else
# No conig dir, show warning
echo "WARNING no config directory, AqualinkD starting with default config, no changes will be saved"
AQUA_CONF="/etc/aqualinkd.conf"
fi
# Start cron
service cron start
# Start AqualinkD not in daemon mode
/usr/local/bin/aqualinkd -d -c $AQUA_CONF

436
hassio.c Normal file
View File

@ -0,0 +1,436 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mongoose.h"
#include "aqualink.h"
#include "net_services.h"
#include "json_messages.h"
#include "aq_mqtt.h"
#include "rs_msg_utils.h"
#include "config.h"
#include "version.h"
// NSF Need to find a better way, this is not thread safe, so don;t want to expost it from net_services.h.
void send_mqtt(struct mg_connection *nc, const char *toppic, const char *message);
#define HASS_DEVICE "\"identifiers\": [\"" AQUALINKD_SHORT_NAME "\"], \"sw_version\": \"" AQUALINKD_VERSION "\", \"model\": \"" AQUALINKD_NAME "\", \"name\": \"AqualinkD\", \"manufacturer\": \"" AQUALINKD_SHORT_NAME "\", \"suggested_area\": \"pool\""
#define HASS_AVAILABILITY "\"payload_available\" : \"1\",\"payload_not_available\" : \"0\",\"topic\": \"%s/" MQTT_LWM_TOPIC "\""
char *HASSIO_CLIMATE_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"climate\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"%s\","
"\"modes\": [\"off\", \"heat\"],"
"\"send_if_off\": true,"
"\"initial\": 36,"
"\"power_command_topic\": \"%s/%s/set\","
"\"payload_on\": \"1\","
"\"payload_off\": \"0\","
"\"current_temperature_topic\": \"%s/%s\","
"\"min_temp\": 36,"
"\"max_temp\": 104,"
"\"mode_command_topic\": \"%s/%s/set\","
"\"mode_state_topic\": \"%s/%s/enabled\","
"\"mode_state_template\": \"{%% set values = { '0':'off', '1':'heat'} %%}{{ values[value] if value in values.keys() else 'off' }}\","
"\"temperature_command_topic\": \"%s/%s/setpoint/set\","
"\"temperature_state_topic\": \"%s/%s/setpoint\","
"\"temperature_state_template\": \"{{ value_json }}\","
"\"qos\": 1,"
"\"retain\": false"
"}";
char *HASSIO_FREEZE_PROTECT_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"climate\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"Freeze Protect\","
"\"modes\": [\"off\", \"auto\"],"
"\"send_if_off\": true,"
"\"initial\": 34,"
"\"payload_on\": \"1\","
"\"payload_off\": \"0\","
"\"current_temperature_topic\": \"%s/%s\","
"\"min_temp\": 34,"
"\"max_temp\": 42,"
"\"mode_state_topic\": \"%s/%s\","
"\"mode_state_template\": \"{%% set values = { '0':'off', '1':'auto'} %%}{{ values[value] if value in values.keys() else 'off' }}\","
"\"temperature_command_topic\": \"%s/%s/setpoint/set\","
"\"temperature_state_topic\": \"%s/%s/setpoint\","
"\"temperature_state_template\": \"{{ value_json }}\""
"}";
char *HASSIO_SWG_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"humidifier\","
"\"device_class\": \"humidifier\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"Salt Water Generator\","
"\"state_topic\": \"%s/%s\","
"\"state_template\": \"{%% set values = { '0':'off', '2':'on'} %%}{{ values[value] if value in values.keys() else 'off' }}\","
"\"command_topic\": \"%s/%s/set\","
"\"current_humidity_topic\": \"%s/%s\","
"\"target_humidity_command_topic\": \"%s/%s/set\","
"\"target_humidity_state_topic\": \"%s/%s\","
"\"payload_on\": \"2\","
"\"payload_off\": \"0\","
"\"min_humidity\":0,"
"\"max_humidity\":100,"
"\"optimistic\": false"
"}";
// Need to add timer attributes to the switches, once figure out how to use in homeassistant
// ie aqualinkd/Filter_Pump/timer/duration
char *HASSIO_SWITCH_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"switch\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"%s\","
"\"state_topic\": \"%s/%s\","
"\"command_topic\": \"%s/%s/set\","
"\"json_attributes_topic\": \"%s/%s/delay\","
"\"json_attributes_topic\": \"%s/%s/delay\","
"\"json_attributes_template\": \"{{ {'delay': value|int} | tojson }}\","
"\"payload_on\": \"1\","
"\"payload_off\": \"0\","
"\"qos\": 1,"
"\"retain\": false"
"}";
char *HASSIO_TEMP_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"%s Temp\","
"\"state_topic\": \"%s/%s\","
"\"value_template\": \"{{ value_json }}\","
"\"unit_of_measurement\": \"°F\","
"\"device_class\": \"temperature\","
"\"icon\": \"%s\""
"}";
char *HASSIO_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"%s\","
"\"state_topic\": \"%s/%s\","
"\"value_template\": \"{{ value_json }}\","
"\"unit_of_measurement\": \"%s\","
"\"icon\": \"%s\""
"}";
char *HASSIO_ONOFF_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"%s\","
"\"state_topic\": \"%s/%s\","
"\"payload_on\": \"1\","
"\"payload_off\": \"0\","
"\"value_template\": \"{%% set values = { '0':'off', '1':'on'} %%}{{ values[value] if value in values.keys() else 'off' }}\","
"\"icon\": \"%s\""
"}";
char *HASSIO_PUMP_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"unique_id\": \"aqualinkd_%s%d_%s\","
"\"name\": \"%s %s %s\","
"\"state_topic\": \"%s/%s%s\","
"\"value_template\": \"{{ value_json }}\","
"\"unit_of_measurement\": \"%s\","
"\"icon\": \"mdi:pump\""
"}";
char *HASSIO_TEXT_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"%s\","
"\"state_topic\": \"%s/%s\","
"\"icon\": \"mdi:card-text\""
"}";
char *HASSIO_SWG_TEXT_SENSOR_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"availability\": {" HASS_AVAILABILITY "},"
"\"type\": \"sensor\","
"\"unique_id\": \"%s\","
"\"name\": \"%s\","
"\"state_topic\": \"%s/%s\","
"\"payload_on\": \"0\","
"\"payload_off\": \"255\","
"\"value_template\": \"{%% set values = { '0':'Generating',"
"'1':'No flow', "
"'2':'low salt', "
"'4':'high salt', "
"'8':'clean cell', "
"'9':'turning off', "
"'16':'high current', "
"'32':'low volts', "
"'64':'low temp', "
"'128':'Check PCB',"
"'253':'General Fault',"
"'254':'Unknown',"
"'255':'Off'} %%}"
"{{ values[value] if value in values.keys() else 'off' }}\","
"\"icon\": \"mdi:card-text\""
"}";
/*
char *HASSIO_TEXT_DISCOVER = "{"
"\"device\": {" HASS_DEVICE "},"
"\"type\": \"text\","
"\"unique_id\": \"aqualinkd_%s\","
"\"name\": \"%s\","
"\"command_topic\": \"junk/null\","
"\"state_topic\": \"%s/%s\""
"}";
*/
/*
char *HASSIO_SERVICE_MODE_DISCOVER = "{"
"\"type\": \"sensor\","
"\"unique_id\": \"aqualinkd_Service_Mode\","
"\"name\": \"Service Mode\","
"\"state_topic\": \"aqualinkd/Service_Mode\","
"\"value_template\": \"{% set values = { '0':'off', '1':'on'} %}{{ values[value] if value in values.keys() else 'off' }}\","
"\"icon\": \"mdi:account-wrench\""
"}";
*/
/*
Others to add
{
"type": "text",
"unique_id": "display",
"name": "AqualinkD Display Message",
"command_topic": "junk/null",
"state_topic": "aqualinkd/Display_Message"
}
{
"type": "sensor",
"unique_id": "Service_Mode",
"name": "Service Mode",
"state_topic": "aqualinkd/Service_Mode",
"value_template": "{% set values = { '0':'off', '1':'on'} %}{{ values[value] if value in values.keys() else 'off' }}",
"icon": "mdi:account-wrench"
}
mdi:pump
mdi:water-outline // orph, ph, ppm, swg
mdi:water-thermometer // water
mdi:thermometer // air
mdi:account-wrench // server
*/
void publish_mqtt_hassio_discover(struct aqualinkdata *aqdata, struct mg_connection *nc)
{
if (_aqconfig_.mqtt_hass_discover_topic == NULL)
return;
int i;
char msg[JSON_STATUS_SIZE];
char topic[250];
char idbuf[128];
LOG(NET_LOG,LOG_INFO, "MQTT: Publishing discover messages to '%s'\n", _aqconfig_.mqtt_hass_discover_topic);
for (i=0; i < aqdata->total_buttons; i++)
{ // 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)) ) {
sprintf(msg,HASSIO_CLIMATE_DISCOVER,
_aqconfig_.mqtt_aq_topic,
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,(strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0)?POOL_TEMP_TOPIC:SPA_TEMP_TOPIC,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name);
sprintf(topic, "%s/climate/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, aqdata->aqbuttons[i].name);
send_mqtt(nc, topic, msg);
} else if (strcmp("NONE",aqdata->aqbuttons[i].label) != 0 ) {
// Switches
//sprintf(msg,"{\"type\": \"switch\",\"unique_id\": \"%s\",\"name\": \"%s\",\"state_topic\": \"aqualinkd/%s\",\"command_topic\": \"aqualinkd/%s/set\",\"json_attributes_topic\": \"aqualinkd/%s/delay\",\"json_attributes_topic\": \"aqualinkd/%s/delay\",\"json_attributes_template\": \"{{ {'delay': value|int} | tojson }}\",\"payload_on\": \"1\",\"payload_off\": \"0\",\"qos\": 1,\"retain\": false}" ,
sprintf(msg, HASSIO_SWITCH_DISCOVER,
_aqconfig_.mqtt_aq_topic,
aqdata->aqbuttons[i].name,
aqdata->aqbuttons[i].label,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name,
_aqconfig_.mqtt_aq_topic,aqdata->aqbuttons[i].name);
sprintf(topic, "%s/switch/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, aqdata->aqbuttons[i].name);
send_mqtt(nc, topic, msg);
}
}
// Freezeprotect
if ( _aqconfig_.force_frzprotect_setpoints || (aqdata->frz_protect_set_point != TEMP_UNKNOWN && aqdata->air_temp != TEMP_UNKNOWN) ) {
sprintf(msg, HASSIO_FREEZE_PROTECT_DISCOVER,
_aqconfig_.mqtt_aq_topic,
FREEZE_PROTECT,
_aqconfig_.mqtt_aq_topic,AIR_TEMP_TOPIC,
_aqconfig_.mqtt_aq_topic,FREEZE_PROTECT_ENABELED,
_aqconfig_.mqtt_aq_topic,FREEZE_PROTECT,
_aqconfig_.mqtt_aq_topic,FREEZE_PROTECT,
_aqconfig_.mqtt_aq_topic,FREEZE_PROTECT
);
sprintf(topic, "%s/climate/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, FREEZE_PROTECT);
send_mqtt(nc, topic, msg);
}
// SWG
if ( aqdata->swg_percent != TEMP_UNKNOWN ) {
sprintf(msg, HASSIO_SWG_DISCOVER,
_aqconfig_.mqtt_aq_topic,
SWG_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_PERCENT_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_PERCENT_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_PERCENT_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_PERCENT_TOPIC
);
sprintf(topic, "%s/humidifier/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, SWG_TOPIC);
send_mqtt(nc, topic, msg);
rsm_char_replace(idbuf, SWG_BOOST_TOPIC, "/", "_");
sprintf(msg, HASSIO_SWITCH_DISCOVER,
_aqconfig_.mqtt_aq_topic,
idbuf,
"SWG Boost",
_aqconfig_.mqtt_aq_topic,SWG_BOOST_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_BOOST_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_BOOST_TOPIC,
_aqconfig_.mqtt_aq_topic,SWG_BOOST_TOPIC);
sprintf(topic, "%s/switch/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, idbuf);
send_mqtt(nc, topic, msg);
rsm_char_replace(idbuf, SWG_PERCENT_TOPIC, "/", "_");
sprintf(msg, HASSIO_SENSOR_DISCOVER,_aqconfig_.mqtt_aq_topic,idbuf,"SWG Percent",_aqconfig_.mqtt_aq_topic,SWG_PERCENT_TOPIC, "%", "mdi:water-outline");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, idbuf);
send_mqtt(nc, topic, msg);
rsm_char_replace(idbuf, SWG_PPM_TOPIC, "/", "_");
sprintf(msg, HASSIO_SENSOR_DISCOVER,_aqconfig_.mqtt_aq_topic,idbuf,"SWG PPM",_aqconfig_.mqtt_aq_topic,SWG_PPM_TOPIC, "ppm", "mdi:water-outline");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, idbuf);
send_mqtt(nc, topic, msg);
rsm_char_replace(idbuf, SWG_EXTENDED_TOPIC, "/", "_");
sprintf(msg, HASSIO_SWG_TEXT_SENSOR_DISCOVER,_aqconfig_.mqtt_aq_topic,idbuf,"SWG Msg",_aqconfig_.mqtt_aq_topic,SWG_EXTENDED_TOPIC);
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, idbuf);
send_mqtt(nc, topic, msg);
/*
// SWG Display message (move to SWG area)
rsm_char_replace(idbuf, SWG_STATUS_MSG_TOPIC, "/", "_");
sprintf(msg, HASSIO_TEXT_SENSOR_DISCOVER,idbuf,"SWG Msg",_aqconfig_.mqtt_aq_topic,SWG_STATUS_MSG_TOPIC);
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, idbuf);
send_mqtt(nc, topic, msg);
*/
}
// Temperatures
sprintf(msg, HASSIO_TEMP_SENSOR_DISCOVER,_aqconfig_.mqtt_aq_topic,"Pool","Pool",_aqconfig_.mqtt_aq_topic,POOL_TEMP_TOPIC,"mdi:water-thermometer");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Pool");
send_mqtt(nc, topic, msg);
sprintf(msg, HASSIO_TEMP_SENSOR_DISCOVER,_aqconfig_.mqtt_aq_topic,"Spa","Spa",_aqconfig_.mqtt_aq_topic,SPA_TEMP_TOPIC,"mdi:water-thermometer");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Spa");
send_mqtt(nc, topic, msg);
sprintf(msg, HASSIO_TEMP_SENSOR_DISCOVER,_aqconfig_.mqtt_aq_topic,"Air","Air",_aqconfig_.mqtt_aq_topic,AIR_TEMP_TOPIC,"mdi:thermometer");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Air");
send_mqtt(nc, topic, msg);
// Pumps
for (i=0; i < aqdata->num_pumps; i++) {
int pn=i+1;
if (aqdata->pumps[i].pumpType==VFPUMP) {
// We have GPM info
sprintf(msg, HASSIO_PUMP_SENSOR_DISCOVER,
_aqconfig_.mqtt_aq_topic,
"Pump",pn,"GPM",
aqdata->pumps[i].button->label,(rsm_strncasestr(aqdata->pumps[i].button->label,"pump",strlen(aqdata->pumps[i].button->label))!=NULL)?"":"Pump","GPM",
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name ,PUMP_GPM_TOPIC,
"GPM");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s%d_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Pump",pn,"GPM");
send_mqtt(nc, topic, msg);
}
sprintf(msg, HASSIO_PUMP_SENSOR_DISCOVER,
_aqconfig_.mqtt_aq_topic,
"Pump",pn,"RPM",
aqdata->pumps[i].button->label,(rsm_strncasestr(aqdata->pumps[i].button->label,"pump",strlen(aqdata->pumps[i].button->label))!=NULL)?"":"Pump","RPM",
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name ,PUMP_RPM_TOPIC,
"RPM");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s%d_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Pump",pn,"RPM");
send_mqtt(nc, topic, msg);
sprintf(msg, HASSIO_PUMP_SENSOR_DISCOVER,
_aqconfig_.mqtt_aq_topic,
"Pump",pn,"Watts",
aqdata->pumps[i].button->label,(rsm_strncasestr(aqdata->pumps[i].button->label,"pump",strlen(aqdata->pumps[i].button->label))!=NULL)?"":"Pump","Watts",
_aqconfig_.mqtt_aq_topic,aqdata->pumps[i].button->name ,PUMP_WATTS_TOPIC,
"Watts");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s%d_%s/config", _aqconfig_.mqtt_hass_discover_topic, "Pump",pn,"Watts");
send_mqtt(nc, topic, msg);
}
// Chem feeder (ph/orp)
if (_aqconfig_.force_chem_feeder || aqdata->ph != TEMP_UNKNOWN) {
rsm_char_replace(idbuf, CHEM_PH_TOPIC, "/", "_");
sprintf(msg, HASSIO_SENSOR_DISCOVER,_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) {
rsm_char_replace(idbuf, CHEM_ORP_TOPIC, "/", "_");
sprintf(msg, HASSIO_SENSOR_DISCOVER,_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);
send_mqtt(nc, topic, msg);
}
// Misc stuff
sprintf(msg, HASSIO_ONOFF_SENSOR_DISCOVER,_aqconfig_.mqtt_aq_topic,SERVICE_MODE_TOPIC,"Service Mode",_aqconfig_.mqtt_aq_topic,SERVICE_MODE_TOPIC, "mdi:account-wrench");
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, SERVICE_MODE_TOPIC);
send_mqtt(nc, topic, msg);
/* // Leave below if we decide to go back to a text box
sprintf(msg, HASSIO_TEXT_DISCOVER,DISPLAY_MSG_TOPIC,"Display Messages",_aqconfig_.mqtt_aq_topic,DISPLAY_MSG_TOPIC);
sprintf(topic, "%s/text/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, DISPLAY_MSG_TOPIC);
*/
// It actually works better posting this to sensor and not text.
sprintf(msg, HASSIO_TEXT_SENSOR_DISCOVER,_aqconfig_.mqtt_aq_topic,DISPLAY_MSG_TOPIC,"Display Msg",_aqconfig_.mqtt_aq_topic,DISPLAY_MSG_TOPIC);
sprintf(topic, "%s/sensor/aqualinkd/aqualinkd_%s/config", _aqconfig_.mqtt_hass_discover_topic, DISPLAY_MSG_TOPIC);
send_mqtt(nc, topic, msg);
}

8
hassio.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef HASSIO_H_
#define HASSIO_H_
void publish_mqtt_hassio_discover(struct aqualinkdata *aqdata, struct mg_connection *nc);
#endif // HASSIO_H_

View File

@ -306,7 +306,7 @@ void processPageButton(unsigned char *message, int length, struct aqualinkdata *
else {
button = &_pageButtons[index];
// if _currentPageLoading = 0x00 then we should use current page
LOG(IAQT_LOG,LOG_NOTICE, "Not sure where to add Button %d %s - LoadingPage = %s\n",index,button->name,iaqt_page_name(_currentPageLoading));
LOG(IAQT_LOG,LOG_INFO, "Not sure where to add Button %d %s - LoadingPage = %s\n",index,button->name,iaqt_page_name(_currentPageLoading));
}
button->state = message[PKT_IAQT_BUTSTATE];

View File

@ -616,7 +616,7 @@ int build_aqualink_status_JSON(struct aqualinkdata *aqdata, char* buffer, int si
if ( aqdata->orp != TEMP_UNKNOWN )
length += sprintf(buffer+length, ",\"chem_orp\":\"%d\"",aqdata->orp );
if ( READ_RSDEV_SWG )
//if ( READ_RSDEV_SWG )
length += sprintf(buffer+length, ",\"swg_fullstatus\": \"%d\"", aqdata->ar_swg_device_status);
length += sprintf(buffer+length, ",\"leds\":{" );

View File

@ -44,6 +44,7 @@
#include "aq_scheduler.h"
#include "rs_msg_utils.h"
#include "simulator.h"
#include "hassio.h"
#include "version.h"
#ifdef AQ_PDA
@ -696,7 +697,6 @@ void mqtt_broadcast_aqualinkstate(struct mg_connection *nc)
cnt++;
}
}
//LOG(NET_LOG,LOG_INFO, "mqtt_broadcast_aqualinkstate: START\n");
@ -823,6 +823,12 @@ void mqtt_broadcast_aqualinkstate(struct mg_connection *nc)
send_mqtt_int_msg(nc, SWG_BOOST_TOPIC, _aqualink_data->boost);
_last_mqtt_aqualinkdata.boost = _aqualink_data->boost;
}
if ( _aqualink_data->boost_duration != _last_mqtt_aqualinkdata.boost_duration ) {
send_mqtt_int_msg(nc, SWG_BOOST_DURATION_TOPIC, _aqualink_data->boost_duration);
_last_mqtt_aqualinkdata.boost_duration = _aqualink_data->boost_duration;
}
} else {
//LOG(NET_LOG,LOG_DEBUG, "SWG status unknown\n");
}
@ -973,6 +979,7 @@ uriAtype action_URI(request_source from, const char *URI, int uri_length, float
char *ri1 = (char *)URI;
char *ri2 = NULL;
char *ri3 = NULL;
//bool charvalue=false;
//char *ri4 = NULL;
LOG(NET_LOG,LOG_DEBUG, "%s: URI Request '%.*s': value %.2f\n", actionName[from], uri_length, URI, value);
@ -1346,7 +1353,21 @@ void action_mqtt_message(struct mg_connection *nc, struct mg_mqtt_message *msg)
strncpy(tmp, msg->payload.p, msg->payload.len);
tmp[msg->payload.len] = '\0';
float value = atof(tmp);
//float value = atof(tmp);
// Check value like on/off/heat/cool and convery to int.
// HASSIO doesn't support `mode_command_template` so easier to code around their limotation here.
char *end;
float value = strtof(tmp, &end);
if (tmp == end) { // Not a number
// See if any test resembeling 1, of not leave at zero.
if (rsm_strcmp(tmp, "on")==0 || rsm_strcmp(tmp, "heat")==0 || rsm_strcmp(tmp, "cool")==0)
value = 1;
LOG(NET_LOG,LOG_NOTICE, "MQTT: converted value from '%s' to '%.0f', from message '%.*s'\n",tmp,value,msg->topic.len, msg->topic.p);
}
//int val = _aqualink_data->unactioned.value = (_aqualink_data->temp_units != CELSIUS && _aqconfig_.convert_mqtt_temp) ? round(degCtoF(value)) : round(value);
bool convert = (_aqualink_data->temp_units != CELSIUS && _aqconfig_.convert_mqtt_temp)?true:false;
int offset = strlen(_aqconfig_.mqtt_aq_topic)+1;
@ -1842,6 +1863,8 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
mg_mqtt_subscribe(nc, topics, 1, 42);
LOG(NET_LOG,LOG_INFO, "MQTT: Subscribing to '%s'\n", _aqconfig_.mqtt_dz_sub_topic);
}
publish_mqtt_hassio_discover( _aqualink_data, nc);
}
break;
case MG_EV_MQTT_PUBACK:
@ -1899,6 +1922,12 @@ void reset_last_mqtt_status()
_last_mqtt_aqualinkdata.swg_ppm = -1;
_last_mqtt_aqualinkdata.heater_err_status = NUL; // 0x00
for (i=0; i < _aqualink_data->num_pumps; i++) {
_last_mqtt_aqualinkdata.pumps[i].gpm = -1;
_last_mqtt_aqualinkdata.pumps[i].rpm = -1;
_last_mqtt_aqualinkdata.pumps[i].watts = -1;
}
}
void start_mqtt(struct mg_mgr *mgr) {

View File

@ -31,6 +31,11 @@ void broadcast_aqualinkstate();
void broadcast_aqualinkstate_error(char *msg);
void broadcast_simulator_message();
// NSF Need to find a better way, this is not thread safe, so don;t like exposting it.
//void send_mqtt(struct mg_connection *nc, const char *toppic, const char *message);
// superseded with systemd/sd-journal
//void broadcast_log(char *msg);
//#endif

1
pda.c
View File

@ -687,6 +687,7 @@ void process_pda_packet_msg_long_equiptment_status(const char *msg_line, int lin
//snprintf(_aqualink_data->boost_msg, sizeof(_aqualink_data->boost_msg), "%s", msg+2);
//Message is ' 23:21 Remain', we only want time part
snprintf(_aqualink_data->boost_msg, 6, "%s", msg);
_aqualink_data->boost_duration = rsm_HHMM2min(_aqualink_data->boost_msg);
}
else if ((index = rsm_strncasestr(msg, MSG_SWG_PCT, AQ_MSGLEN)) != NULL)
{

BIN
release/aqualinkd Executable file

Binary file not shown.

View File

@ -168,6 +168,10 @@ force_PS_setpoints = no
# to be listed as thermostat on startup.
force_Frzprotect_setpoints = no
# AqualinkD can take sime time to find chemical feeder (if panel supports it), This will force the chemical feeder
# to be listed on startup.
force_chem_feeder = no
# Lights can be programmed by control panel or AqualinkD (if controlpanel doesn;t support specific light or light mode you want)
# IF YOU WANT AQUALINKD TO PROGRAM THE LIGHT, IT MUST NOT BE CONFIGURED AS A COLOR LIGHT IN THE JANDY CONTROL PANEL.
# Light probramming mode. 0=safe mode, but slow.

Binary file not shown.

View File

@ -21,6 +21,7 @@
#include <stdio.h>
#include <ctype.h>
#include <regex.h>
#include <limits.h>
#include "utils.h"
#include "rs_msg_utils.h"
@ -112,7 +113,7 @@ int rsm_get_boardcpu(char *dest, int dest_len, const char *src, int src_len)
begin = (int)match.rm_so;
end = (int)match.rm_eo;
len = MIN((end-begin), dest_len);
len = AQ_MIN((end-begin), dest_len);
strncpy(dest, src+match.rm_so, len );
@ -340,8 +341,7 @@ char *rsm_lastindexof(const char *haystack, const char *needle, size_t length)
return NULL;
}
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
int rsm_strncmp(const char *haystack, const char *needle, int length)
{
@ -363,7 +363,27 @@ int rsm_strncmp(const char *haystack, const char *needle, int length)
//LOG(AQUA_LOG,LOG_DEBUG, "CHECK haystack SP1='%c' EP1='%c' SP2='%c' '%.*s' for '%s' length=%d\n",*sp1,*ep1,*sp2,(ep1-sp1)+1,sp1,sp2,(ep1-sp1)+1);
// Need to write this myself for speed
// Need to check if full length string (no space on end), that the +1 is accurate. MIN should do it
return strncasecmp(sp1, sp2, MIN((ep1-sp1)+1,length));
return strncasecmp(sp1, sp2, AQ_MIN((ep1-sp1)+1,length));
}
char *rsm_char_replace(char *replaced , char *search, char *find, char *replace)
{
int len;
int i;
char *fp = find;
char *rp = replace;
len = strlen(search);
for(i = 0; i < len; i++){
if (search[i] == *fp)
replaced[i] = *rp;
else
replaced[i] = search[i];
}
replaced[i] = '\0';
return replaced;
}
// NSF Check is this works correctly.
@ -375,7 +395,7 @@ char *rsm_strncpycut(char *dest, const char *src, int dest_len, int src_len)
while(isspace(*sp)) sp++;
while(isspace(*ep)) ep--;
int length=MIN((ep-sp)+1,dest_len);
int length=AQ_MIN((ep-sp)+1,dest_len);
memset(dest, '\0',dest_len);
return strncpy(dest, sp, length);
@ -426,8 +446,6 @@ int rsm_strncpy_nul2sp(char *dest, const unsigned char *src, int dest_len, int s
return _rsm_strncpy(dest, src, dest_len, src_len, true);
}
#define INT_MAX +2147483647
#define INT_MIN -2147483647
// atoi that can have blank start
int rsm_atoi(const char* str)
@ -465,4 +483,14 @@ float rsm_atof(const char* str)
}
return atof(&str[i]);
}
// MEssages as HH:MM ie 01:23
int rsm_HHMM2min(char *message) {
char *ptr;
int hour = strtoul(message, &ptr, 10);
int min = strtoul(message+3, &ptr, 10);
return (hour*60)+min;
}

View File

@ -1,7 +1,7 @@
#ifndef RS_MSG_UTILS_H_
#define RS_MSG_UTILS_H_
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
//#define MIN(x, y) (((x) < (y)) ? (x) : (y))
bool rsm_get_revision(char *dest, const char *src, int src_len);
int rsm_get_boardcpu(char *dest, int dest_len, const char *src, int src_len);
@ -25,6 +25,8 @@ int rsm_strncpy_nul2sp(char *dest, const unsigned char *src, int dest_len, int s
int rsm_atoi(const char* str);
float rsm_atof(const char* str);
char *rsm_strncpycut(char *dest, const char *src, int dest_len, int src_len);
//char *rsm_char_replace(char *replaced , char *search, const char find, const char replace);
char *rsm_char_replace(char *replaced , char *search, char *find, char *replace);
int rsm_HHMM2min(char *message);
#endif //RS_MSG_UTILS_H_

10
utils.h
View File

@ -47,6 +47,16 @@
// Set scheduler log to timer log
#define SCHD_LOG TIMR_LOG
/*
#define INT_MAX +2147483647
#define INT_MIN -2147483647
*/
#define AQ_MAX(x, y) (((x) > (y)) ? (x) : (y))
#define AQ_MIN(x, y) (((x) < (y)) ? (x) : (y))
/*
typedef enum
{

View File

@ -1,4 +1,5 @@
#define AQUALINKD_NAME "Aqualink Daemon"
#define AQUALINKD_SHORT_NAME "AqualinkD"
#define AQUALINKD_VERSION "2.3.5"

View File

@ -72,6 +72,8 @@
32: "Low volts",
64: "Low temp",
128: "Check PCB",
253: "General Fault",
254: "Unknown",
255: "Off"
}

View File

@ -590,6 +590,21 @@
var _scheduler_devices_json= [];
var _swgALLStatus = {
0: "On",
1: "No flow",
2: "Low salt",
4: "High salt",
8: "Clean cell",
9: "Turning off",
16: "High current",
32: "Low volts",
64: "Low temp",
128: "Check PCB",
253: "General Fault",
254: "Unknown",
255: "Off"
}
if (typeof show_vsp_gpm !== 'undefined' && show_vsp_gpm == false)
var _show_vsp_gpm=false;
@ -605,19 +620,7 @@
if (typeof swgStatus !== 'undefined') {
var _swgStatus = swgStatus;
} else {
var _swgStatus = {
0: "On",
1: "No flow",
2: "Low salt",
4: "High salt",
8: "Clean cell",
9: "Turning off",
16: "High current",
32: "Low volts",
64: "Low temp",
128: "Check PCB",
255: "Off"
}
var _swgStatus = _swgALLStatus
}
function init() {
@ -1100,6 +1103,9 @@
if (exstatus > 0 && exstatus < 255) {// Not off or on
//text = swgFullstatus2String(exstatus);
text = _swgStatus[exstatus];
if (typeof text == 'undefined') {
text = _swgALLStatus[exstatus];
}
} else if (tile.getAttribute('Boost') == 'on')
text = "Boost";
else if (status == 'enabled')