Update v3.2.5

pull/298/head^2
sfeakes 2024-05-22 18:57:33 -05:00
parent 675b0f52d1
commit dbbd0dda0c
88 changed files with 2157 additions and 269 deletions

View File

@ -14,6 +14,7 @@ AQ_PDA = true
AQ_ONETOUCH = true
AQ_IAQTOUCH = true
AQ_MANAGER =true
#AQ_RS_EXTRA_OPTS = false
#AQ_CONTAINER = false // this is for compiling for containers
#AQ_MEMCMP = true // Not implimented correctly yet.
@ -76,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
@ -83,7 +83,39 @@ Designed to mimic AqualinkRS devices, used to fully configure the master control
* Add set time to OneTouch protocol.
* Update AqualinkD Management console to manage configuration
* Create iAqualink Touch Simulator
* Probably decoded enough protocols for AuqlinkD to self configure.
<!--
* NEED TO FIX for PDA and iAQT protocol.
* 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 ???)
* SWG Stays on
* serial_logger
* Add wiki documentation
* about Heat vs Heater
* Panel version
* can't use iaquatouch panel / wireless
* Added iAqualinkTouch support for PDA only panels that can use that protocol.
* PDA panel needs to be Rev 6.0 or newer.
* This makes the PDA only panels quicker and less error prone.
* Introduces color light support and VSP
* Consider this PDA support Beta.
* Read PDA Wiki
-->
# Call for Help.
* The only Jandy devices I have not decoded yet are LX heater & Chemical Feeder. If you have either of these devices and are willing to post some logs, please let me know, or post in the [Discussions area](https://github.com/sfeakes/AqualinkD/discussions)
# Update in Release 2.3.5
* 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.
* Fixed issue mqtt_timed_update (1~2 min rather than between 2 & 20 min)
# Update in Release 2.3.4
* Changes for Docker
* Updated simulator code base and added new simulators for AllButton, OneTouch & PDA.
@ -95,11 +127,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,9 @@
#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"
#define CHEM_TOPIC "CHEM"
#define CHEM_PH_TOPIC CHEM_TOPIC "/pH"
@ -30,13 +33,18 @@
#define CHEM_ORP_TOPIC CHEM_TOPIC "/ORP"
#define CHRM_ORP_F_TOPIC CHEM_TOPIC "/ORP_f"
#define LXI_TOPIC "LXi"
#define LXI_STATUS LXI_TOPIC "/Status"
#define LXI_ERROR_CODE LXI_TOPIC "/Error"
#define LXI_ERROR_MESSAGE LXI_TOPIC "/Error_Message"
#define FREEZE_PROTECT "Freeze_Protect"
#define FREEZE_PROTECT_ENABELED FREEZE_PROTECT ENABELED_SUBT
#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

@ -525,9 +525,14 @@ bool setDeviceState(struct aqualinkdata *aqdata, int deviceIndex, bool isON)
LOG(AQUA_LOG, LOG_INFO, "received '%s' for '%s', turning '%s'\n", (isON == false ? "OFF" : "ON"), button->name, (isON == false ? "OFF" : "ON"));
#ifdef AQ_PDA
if (isPDA_PANEL) {
char msg[PTHREAD_ARG];
sprintf(msg, "%-5d%-5d", deviceIndex, (isON == false ? OFF : ON));
aq_programmer(AQ_PDA_DEVICE_ON_OFF, msg, aqdata);
if (button->special_mask & PROGRAM_LIGHT && isPDA_IAQT) {
// AqualinkTouch in PDA mode, we can program light. (if turing off, use standard AQ_PDA_DEVICE_ON_OFF below)
programDeviceLightMode(aqdata, (isON?0:-1), deviceIndex); // -1 means off 0 means use current light mode
} else {
char msg[PTHREAD_ARG];
sprintf(msg, "%-5d%-5d", deviceIndex, (isON == false ? OFF : ON));
aq_programmer(AQ_PDA_DEVICE_ON_OFF, msg, aqdata);
}
} else
#endif
{
@ -598,7 +603,7 @@ void programDeviceLightMode(struct aqualinkdata *aqdata, int value, int button)
int i;
clight_detail *light = NULL;
#ifdef AQ_PDA
if (isPDA_PANEL) {
if (isPDA_PANEL && !isPDA_IAQT) {
LOG(AQUA_LOG,LOG_ERR, "Light mode control not supported in PDA mode\n");
return;
}
@ -642,7 +647,7 @@ void programDeviceLightMode(struct aqualinkdata *aqdata, int value, int button)
//bool panel_device_request(struct aqualinkdata *aqdata, action_type type, int deviceIndex, int value, int subIndex, bool fromMQTT)
bool panel_device_request(struct aqualinkdata *aqdata, action_type type, int deviceIndex, int value, request_source source)
{
//LOG(AQUA_LOG,LOG_NOTICE, "Device request type %d for deviceindex %d of value %d from %d\n",type,deviceIndex, value, source);
LOG(AQUA_LOG,LOG_INFO, "Device request type %d for deviceindex %d of value %d from %d\n",type,deviceIndex, value, source);
switch (type) {
case ON_OFF:
//setDeviceState(&aqdata->aqbuttons[deviceIndex], value<=0?false:true, deviceIndex );

View File

@ -397,8 +397,15 @@ void queueGetProgramData(emulation_type source_type, struct aqualinkdata *aq_dat
}
}
#ifdef AQ_PDA
} else if ( source_type == AQUAPDA) {
} else if ( isPDA_PANEL && source_type == AQUAPDA) {
aq_programmer(AQ_PDA_INIT, NULL, aq_data);
} else if ( isPDA_PANEL && source_type == IAQTOUCH) {
//aq_programmer(AQ_PDA_INIT, NULL, aq_data);
if (_aqconfig_.use_panel_aux_labels) {
aq_programmer(AQ_GET_AUX_LABELS, NULL, aq_data);
}
aq_programmer(AQ_GET_IAQTOUCH_SETPOINTS, NULL, aq_data);
#endif
} else { // Must be all button only
aq_programmer(AQ_GET_POOL_SPA_HEATER_TEMPS, NULL, aq_data);
@ -468,7 +475,8 @@ bool in_light_programming_mode(struct aqualinkdata *aq_data)
{
if ( ( aq_data->active_thread.thread_id != 0 ) &&
( aq_data->active_thread.ptype == AQ_SET_LIGHTPROGRAM_MODE ||
aq_data->active_thread.ptype == AQ_SET_LIGHTCOLOR_MODE)
aq_data->active_thread.ptype == AQ_SET_LIGHTCOLOR_MODE ||
aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE)
) {
return true;
}
@ -527,7 +535,9 @@ bool in_iaqt_programming_mode(struct aqualinkdata *aq_data)
aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_POOL_HEATER_TEMP ||
aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_SPA_HEATER_TEMP ||
aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_SET_TIME ||
aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_PUMP_VS_PROGRAM)
aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_PUMP_VS_PROGRAM ||
aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_DEVICE_ON_OFF ||
aq_data->active_thread.ptype == AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE)
) {
return true;
}
@ -580,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);
@ -671,7 +681,7 @@ void _aq_programmer(program_type r_type, char *args, struct aqualinkdata *aq_dat
else if (r_type == AQ_SET_SPA_HEATER_TEMP)
type = AQ_SET_RSSADAPTER_SPA_HEATER_TEMP;
} else if (r_type == AQ_SET_PUMP_RPM) {
if (isONET_ENABLED)
if (isONET_ENABLED || isPDA_IAQT)
type = AQ_SET_ONETOUCH_PUMP_RPM;
else if (isIAQT_ENABLED)
type = AQ_SET_IAQTOUCH_PUMP_RPM;
@ -719,7 +729,7 @@ void _aq_programmer(program_type r_type, char *args, struct aqualinkdata *aq_dat
}
#endif
#ifdef AQ_IAQTOUCH
else if (isIAQT_ENABLED && isEXTP_ENABLED) {
else if ((isIAQT_ENABLED && isEXTP_ENABLED) || isPDA_IAQT) {
// IAQ Touch programming modes that should overite standard ones.
switch (r_type){
case AQ_GET_POOL_SPA_HEATER_TEMPS:
@ -741,15 +751,30 @@ void _aq_programmer(program_type r_type, char *args, struct aqualinkdata *aq_dat
case AQ_SET_TIME:
type = AQ_SET_IAQTOUCH_SET_TIME;
break;
case AQ_PDA_DEVICE_ON_OFF:
if (isPDA_IAQT) {
type = AQ_SET_IAQTOUCH_DEVICE_ON_OFF;
}
break;
// This isn;t going to work outside of PDA mode, if the labels are incorrect.
case AQ_SET_LIGHTCOLOR_MODE:
if (isPDA_IAQT) {
type = AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE;
}
break;
default:
type = r_type;
break;
}
}
#endif
#ifdef AQ_PDA
// Check we are doing something valid request
if (isPDA_PANEL) {
if (isPDA_PANEL && !isPDA_IAQT)
{
pda_reset_sleep();
if (type != AQ_PDA_INIT &&
type != AQ_PDA_WAKE_INIT &&
@ -1028,6 +1053,12 @@ void _aq_programmer(program_type r_type, char *args, struct aqualinkdata *aq_dat
return;
}
break;
case AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE:
if( pthread_create( &programmingthread->thread_id , NULL , set_aqualink_iaqtouch_light_colormode, (void*)programmingthread) < 0) {
LOG(PROG_LOG, LOG_ERR, "could not create thread\n");
return;
}
break;
#endif
#ifdef AQ_PDA
case AQ_PDA_INIT:
@ -1054,6 +1085,12 @@ void _aq_programmer(program_type r_type, char *args, struct aqualinkdata *aq_dat
return;
}
break;
case AQ_SET_IAQTOUCH_DEVICE_ON_OFF:
if( pthread_create( &programmingthread->thread_id , NULL , set_aqualink_iaqtouch_device_on_off, (void*)programmingthread) < 0) {
LOG(PROG_LOG, LOG_ERR, "could not create thread\n");
return;
}
break;
#endif
default:
@ -1522,7 +1559,7 @@ void *set_aqualink_light_colormode( void *ptr )
use_current_mode = true;
LOG(PROG_LOG, LOG_INFO, "Light Programming #: %d, on button: %s, color light type: %d, using current mode\n", val, button->label, typ);
} else {
mode_name = light_mode_name(typ, val-1);
mode_name = light_mode_name(typ, val-1, ALLBUTTON);
use_current_mode = false;
if (mode_name == NULL) {
LOG(PROG_LOG, LOG_ERR, "Light Programming #: %d, on button: %s, color light type: %d, couldn't find mode name '%s'\n", val, button->label, typ, mode_name);
@ -2615,6 +2652,12 @@ const char *ptypeName(program_type type)
case AQ_SET_IAQTOUCH_SET_TIME:
return "Set iAqualink Set Time";
break;
case AQ_SET_IAQTOUCH_DEVICE_ON_OFF:
return "Set iAqualink Device On/Off";
break;
case AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE:
return "Set iAqualink Light Color (using panel)";
break;
#endif
#ifdef AQ_PDA
case AQ_PDA_INIT:
@ -2680,6 +2723,7 @@ const char *programtypeDisplayName(program_type type)
break;
case AQ_SET_LIGHTPROGRAM_MODE:
case AQ_SET_LIGHTCOLOR_MODE:
case AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE:
return "Programming: setting light color";
break;
case AQ_SET_SWG_PERCENT:
@ -2709,6 +2753,9 @@ const char *programtypeDisplayName(program_type type)
case AQ_GET_IAQTOUCH_VSP_ASSIGNMENT:
return "Get Pump Assignment";
break;
case AQ_SET_IAQTOUCH_DEVICE_ON_OFF:
return "Programming: setting device on/off";
break;
#ifdef AQ_PDA
case AQ_PDA_DEVICE_STATUS:
return "Programming: retrieving PDA Device status";

View File

@ -83,6 +83,8 @@ typedef enum {
AQ_SET_IAQTOUCH_POOL_HEATER_TEMP,
AQ_SET_IAQTOUCH_SPA_HEATER_TEMP,
AQ_SET_IAQTOUCH_SET_TIME,
AQ_SET_IAQTOUCH_DEVICE_ON_OFF,
AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE,
AQ_GET_RSSADAPTER_SETPOINTS,
AQ_SET_RSSADAPTER_POOL_HEATER_TEMP,
AQ_SET_RSSADAPTER_SPA_HEATER_TEMP,

View File

@ -140,6 +140,12 @@ const char* get_packet_type(unsigned char* packet , int length)
case CMD_IAQ_STARTUP:
return "iAq init";
break;
case CMD_IAQ_TITLE_MESSAGE:
return "iAq ProductName";
break;
case CMD_IAQ_MSG_LONG:
return "iAq Popup message";
break;
case RSSA_DEV_STATUS:
// This is a fail reply 0x10|0x02|0x48|0x13|0x02|0x00|0x10|0x00|0x7f|0x10|0x03|
// Rather than check all, just check 0x02 and checksum sin't I'm not sure 0x10 means faiure without 0x00 around it.
@ -165,6 +171,19 @@ const char* get_packet_type(unsigned char* packet , int length)
case CMD_EPUMP_WATTS:
return "ePump get Watts";
break;
case CMD_JXI_PING:
if (packet[4] == 0x19)
return "LXi heater on";
else // 0x11 is normal off
return "LXi heater ping";
break;
case CMD_JXI_STATUS:
if (packet[6] == 0x10)
return "LXi error";
else
return "LXi status";
break;
default:
sprintf(buf, "Unknown '0x%02hhx'", packet[PKT_CMD]);
return buf;

View File

@ -38,12 +38,16 @@
*/
#define PENTAIR_DEC_PUMP_MIN 96 // 0x60
#define PENTAIR_DEC_PUMP_MAX 111 // 0x6F
#define JANDY_DEC_PUMP_MIN 120 // 0x80
#define JANDY_DEC_PUMP_MAX 123 // 0x83
#define JANDY_DEC_LX_MIN 56 // 0x40
#define JANDY_DEC_LX_MAX 59 // 0x43
#define JANDY_DEC_LXI_MIN 104 // 0x60
#define JANDY_DEC_LXI_MAX 107 // 0x6B
#define JANDY_DEC_SWG_MIN 80 // 0x50
#define JANDY_DEC_SWG_MAX 83 // 0x53
#define JANDY_DEC_PUMP_MIN 120 // 0x78
#define JANDY_DEC_PUMP_MAX 123 // 0x7b
#define JANDY_DEC_JXI_MIN 104 // 0x68
#define JANDY_DEC_JXI_MAX 107 // 0x6B
#define JANDY_DEC_LX_MIN 56 // 0x38
#define JANDY_DEC_LX_MAX 59 // 0x3B
#define JANDY_DEC_CHEM_MIN 128 // 0x80
#define JANDY_DEC_CHEM_MAX 131 // 0x83
// PACKET DEFINES Jandy
@ -125,6 +129,10 @@
#define CMD_PERCENT 0x11 // Set Percent
#define CMD_PPM 0x16 // Received PPM
/* LXi Heater commands */
#define CMD_JXI_PING 0x0c
#define CMD_JXI_STATUS 0x0d
/* PDA KEY CODES */ // Just plating at the moment
#define KEY_PDA_UP 0x06
#define KEY_PDA_DOWN 0x05
@ -294,8 +302,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
@ -337,7 +346,7 @@ SPILLOVER IS DISABLED WHILE SPA IS ON
#define CMD_IAQ_POLL 0x30 // Poll message or ready to receive command
#define CMD_IAQ_CTRL_READY 0x31 // Get this when we can send big control command
#define CMD_IAQ_PAGE_CONTINUE 0x40 // Seems we get this on AUX device page when there is another page, keeps circuling through pages.
#define CMD_IAQ_TITLE_MESSAGE 0x2d // This is what the product name is set to (Jandy RS) usually
//#define CMD_IAQ_VSP_ERROR 0x2c // Error when setting speed too high
#define CMD_IAQ_MSG_LONG 0x2c // This this is display popup message. Next 2 bytes 0x00|0x01 = wait and then 0x00|0x00 clear
/*
@ -355,6 +364,10 @@ SPILLOVER IS DISABLED WHILE SPA IS ON
#define KEY_IAQTCH_STATUS 0x06
#define KEY_IAQTCH_PREV_PAGE 0x20
#define KEY_IAQTCH_NEXT_PAGE 0x21
#define KEY_IAQTCH_OK 0x01 //HEX: 0x10|0x02|0x00|0x01|0x00|0x01|0x14|0x10|0x03|. OK BUTTON
#define KEY_IAQTCH_PREV_PAGE_ALTERNATE 0x1d // System setup prev
#define KEY_IAQTCH_NEXT_PAGE_ALTERNATE 0x1e // System setup next
// PAGE1 (Horosontal keys) (These are duplicate so probable delete)
#define KEY_IAQTCH_HOMEP_KEY01 0x11
@ -390,6 +403,7 @@ SPILLOVER IS DISABLED WHILE SPA IS ON
#define IAQ_PAGE_STATUS2 0x2a // Something get this for Status rather than 0x5b
#define IAQ_PAGE_DEVICES 0x36
#define IAQ_PAGE_DEVICES2 0x35
#define IAQ_PAGE_DEVICES3 0x51
#define IAQ_PAGE_SET_TEMP 0x39
#define IAQ_PAGE_MENU 0x0f
#define IAQ_PAGE_SET_VSP 0x1e
@ -401,10 +415,14 @@ SPILLOVER IS DISABLED WHILE SPA IS ON
#define IAQ_PAGE_ONETOUCH 0x4d
#define IAQ_PAGE_COLOR_LIGHT 0x48
#define IAQ_PAGE_SYSTEM_SETUP 0x14
#define IAQ_PAGE_SYSTEM_SETUP2 0x49
#define IAQ_PAGE_SYSTEM_SETUP3 0x4a
#define IAQ_PAGE_VSP_SETUP 0x2d
#define IAQ_PAGE_FREEZE_PROTECT 0x11
#define IAQ_PAGE_LABEL_AUX 0x32
#define IAQ_PAGE_HELP 0x0c
//#define IAQ_PAGE_START_BOOST 0x3f
//#define IAQ_PAGE_DEGREES 0xFF // Added this as never want to actually select the page, just go to it.
@ -426,7 +444,9 @@ typedef enum {
DRS_NONE,
DRS_SWG,
DRS_EPUMP,
DRS_LXI
DRS_JXI,
DRS_LX,
DRS_CHEM
} rsDeviceType;
typedef enum {

View File

@ -205,6 +205,7 @@ struct aqualinkdata
char time[AQ_MSGLEN];
char last_message[AQ_MSGLONGLEN+1]; // Last ascii message from panel - allbutton (or PDA) protocol
char last_display_message[AQ_MSGLONGLEN+1]; // Last message to display in web UI
bool is_display_message_programming;
aqled aqualinkleds[TOTAL_LEDS];
aqkey aqbuttons[TOTAL_BUTTONS];
unsigned short total_buttons;
@ -220,6 +221,7 @@ struct aqualinkdata
int swg_percent;
int swg_ppm;
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;
@ -229,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

@ -180,6 +180,12 @@ bool checkAqualinkTime()
LOG(AQUA_LOG,LOG_DEBUG, "time not checked, will check in %d seconds\n", TIME_CHECK_INTERVAL - time_difference);
return true;
}
else if (strlen(_aqualink_data.date) <=0 ||
strlen(_aqualink_data.time) <=0)
{
LOG(AQUA_LOG,LOG_DEBUG, "time not checked, no time from panel\n");
return true;
}
else
{
last_checked = now;
@ -188,7 +194,7 @@ bool checkAqualinkTime()
char datestr[DATE_STRING_LEN];
#ifdef AQ_PDA
if (isPDA_PANEL) {
if (isPDA_PANEL && !isPDA_IAQT) {
LOG(AQUA_LOG,LOG_DEBUG, "PDA Time Check\n");
// date is simply a day or week for PDA.
localtime_r(&now, &aq_tm);
@ -358,6 +364,7 @@ int16_t RS16_endswithLEDstate(char *msg)
#endif
void _processMessage(char *message, bool reset);
void processMessage(char *message)
@ -454,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;
}
@ -619,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);
@ -730,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;
@ -807,6 +818,9 @@ bool process_packet(unsigned char *packet, int length)
#ifdef AQ_PDA
if (isPDA_PANEL)
{
if (isPDA_IAQT) {
return false;
}
return process_pda_packet(packet, length);
}
#endif
@ -987,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.
@ -1168,7 +1182,7 @@ int startup(char *self, char *cfgFile)
return EXIT_FAILURE;
}
} else if (isPDA_PANEL) {
if (_aqconfig_.device_id >= 0x60 && _aqconfig_.device_id <= 0x63) {
if ( (_aqconfig_.device_id >= 0x60 && _aqconfig_.device_id <= 0x63) || _aqconfig_.device_id == 0x33 ) {
// 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);
@ -1313,6 +1327,9 @@ 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));
LOG(AQUA_LOG,LOG_NOTICE, "Read Chem Feeder direct = %s\n", bool2text(READ_RSDEV_CHEM));
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);
@ -1507,6 +1524,7 @@ void main_loop()
int blank_read_reconnect = MAX_ZERO_READ_BEFORE_RECONNECT_BLOCKING; // Will get reset if non blocking
sprintf(_aqualink_data.last_display_message, "%s", "Connecting to Control Panel");
_aqualink_data.is_display_message_programming = false;
//_aqualink_data.simulate_panel = false;
_aqualink_data.active_thread.thread_id = 0;
_aqualink_data.air_temp = TEMP_UNKNOWN;
@ -1519,6 +1537,7 @@ void main_loop()
_aqualink_data.swg_percent = TEMP_UNKNOWN;
_aqualink_data.swg_ppm = TEMP_UNKNOWN;
_aqualink_data.ar_swg_device_status = SWG_STATUS_UNKNOWN;
_aqualink_data.heater_err_status = NUL; // 0x00 is no error
_aqualink_data.swg_led_state = LED_S_UNKNOWN;
_aqualink_data.swg_delayed_percent = TEMP_UNKNOWN;
_aqualink_data.temp_units = UNKNOWN;
@ -1530,6 +1549,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);
@ -1547,6 +1568,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);
@ -1881,7 +1907,15 @@ void main_loop()
#ifdef AQ_PDA
if (isPDA_PANEL) {
// If we are in simulator mode, the sim has already send the ack
if (_aqualink_data.simulator_active == SIM_NONE) {
if (isPDA_IAQT) {
//printf("****PDA IAQT Code\n");
_aqualink_data.updated = process_iaqtouch_packet(packet_buffer, packet_length, &_aqualink_data);
_aqualink_data.updated = true; // FORCE UPDATE SINCE THIS IS NOT WORKING YET
caculate_ack_packet(rs_fd, packet_buffer, IAQTOUCH);
if (checkAqualinkTime() == false) // Need to do this better.
{aq_programmer(AQ_SET_TIME, NULL, &_aqualink_data);}
}
else /*if (_aqualink_data.simulator_active == SIM_NONE)*/ {
caculate_ack_packet(rs_fd, packet_buffer, AQUAPDA);
}
}

View File

@ -6,6 +6,7 @@
//#define COLOR_LIGHTS_C_
#include "color_lights.h"
/****** This list MUST be in order of clight_type enum *******/
const char *_color_light_options[NUMBER_LIGHT_COLOR_TYPES][LIGHT_COLOR_OPTIONS] =
{
@ -50,9 +51,9 @@ const char *_color_light_options[NUMBER_LIGHT_COLOR_TYPES][LIGHT_COLOR_OPTIONS]
},
{ // Color Logic
"Voodoo Lounge",
"Blue Sea",
"Royal Blue",
"Afternoon Skies",
"Deep Blue Sea",
//"Royal Blue",
"Afternoon Skies", // 'Afternoon Sky' on allbutton, Skies on iaqtouch
//"Aqua Green",
"Emerald",
"Sangria",
@ -91,8 +92,15 @@ const char *_color_light_options[NUMBER_LIGHT_COLOR_TYPES][LIGHT_COLOR_OPTIONS]
};
const char *light_mode_name(clight_type type, int index)
const char *light_mode_name(clight_type type, int index, emulation_type protocol)
{
// Rename any modes depending on emulation type
if (protocol == ALLBUTTON) {
if (strcmp(_color_light_options[type][index],"Afternoon Skies") == 0) {
return "Afternoon Sky";
}
}
return _color_light_options[type][index];
}

View File

@ -3,6 +3,7 @@
#define COLOR_LIGHTS_H_
#include "aqualink.h"
#include "aq_programmer.h"
#define LIGHT_COLOR_NAME 16
#define LIGHT_COLOR_OPTIONS 17
@ -19,7 +20,8 @@ typedef enum clight_type {
LC_INTELLIB
} clight_type;
*/
const char *light_mode_name(clight_type type, int index);
//const char *light_mode_name(clight_type type, int index);
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);

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;
@ -552,11 +563,23 @@ bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) {
else
_aqconfig_.read_RS485_devmask &= ~READ_RS485_PEN_PUMP;
rtn=true;
} else if (strncasecmp (param, "read_RS485_LXi", 14) == 0) {
} else if (strncasecmp (param, "read_RS485_JXi", 14) == 0) {
if (text2bool(value))
_aqconfig_.read_RS485_devmask |= READ_RS485_JAN_LXI;
_aqconfig_.read_RS485_devmask |= READ_RS485_JAN_JXI;
else
_aqconfig_.read_RS485_devmask &= ~READ_RS485_JAN_LXI;
_aqconfig_.read_RS485_devmask &= ~READ_RS485_JAN_JXI;
rtn=true;
} else if (strncasecmp (param, "read_RS485_LX", 14) == 0) {
if (text2bool(value))
_aqconfig_.read_RS485_devmask |= READ_RS485_JAN_LX;
else
_aqconfig_.read_RS485_devmask &= ~READ_RS485_JAN_LX;
rtn=true;
} else if (strncasecmp (param, "read_RS485_Chem", 14) == 0) {
if (text2bool(value))
_aqconfig_.read_RS485_devmask |= READ_RS485_JAN_CHEM;
else
_aqconfig_.read_RS485_devmask &= ~READ_RS485_JAN_CHEM;
rtn=true;
} else if (strncasecmp (param, "use_panel_aux_labels", 20) == 0) {
_aqconfig_.use_panel_aux_labels = text2bool(value);
@ -570,6 +593,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
@ -27,8 +28,9 @@
#define READ_RS485_SWG (1 << 0) // 1 SWG
#define READ_RS485_JAN_PUMP (1 << 1) // 2 Jandy Pump
#define READ_RS485_PEN_PUMP (1 << 2) // 4 Pentair Pump
#define READ_RS485_JAN_LXI (1 << 3) // Jandy LX & LXi heater
//#define READ_RS485_JAN_LXI (1 << 4) // Jandy LXi heater
#define READ_RS485_JAN_JXI (1 << 3) // Jandy JX & LXi heater
#define READ_RS485_JAN_LX (1 << 4) // Jandy LX heater
#define READ_RS485_JAN_CHEM (1 << 5) // Jandy Chemical Feeder
struct aqconfig
{
@ -51,6 +53,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 +82,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
@ -112,8 +116,11 @@ struct aqconfig _aqconfig_;
#define READ_RSDEV_SWG ((_aqconfig_.read_RS485_devmask & READ_RS485_SWG) == READ_RS485_SWG)
#define READ_RSDEV_ePUMP ((_aqconfig_.read_RS485_devmask & READ_RS485_JAN_PUMP) == READ_RS485_JAN_PUMP)
#define READ_RSDEV_vsfPUMP ((_aqconfig_.read_RS485_devmask & READ_RS485_PEN_PUMP) == READ_RS485_PEN_PUMP)
#define READ_RSDEV_LXI ((_aqconfig_.read_RS485_devmask & READ_RS485_JAN_LXI) == READ_RS485_JAN_LXI)
#define READ_RSDEV_JXI ((_aqconfig_.read_RS485_devmask & READ_RS485_JAN_JXI) == READ_RS485_JAN_JXI)
#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 isPDA_IAQT (_aqconfig_.device_id == 0x33)
//#define isPDA ((_aqconfig_.paneltype_mask & RSP_PDA) == RSP_PDA)

View File

@ -39,6 +39,7 @@ bool processJandyPacket(unsigned char *packet_buffer, int packet_length, struct
static rsDeviceType interestedInNextAck = DRS_NONE;
static unsigned char previous_packet_to = NUL; // bad name, it's not previous, it's previous that we were interested in.
int rtn = false;
// We received the ack from a Jandy device we are interested in
if (packet_buffer[PKT_DEST] == DEV_MASTER && interestedInNextAck != DRS_NONE)
{
@ -50,9 +51,17 @@ bool processJandyPacket(unsigned char *packet_buffer, int packet_length, struct
{
rtn = processPacketFromJandyPump(packet_buffer, packet_length, aqdata, previous_packet_to);
}
else if (interestedInNextAck == DRS_LXI)
else if (interestedInNextAck == DRS_JXI)
{
rtn = processPacketFromJandyHeater(packet_buffer, packet_length, aqdata, previous_packet_to);
rtn = processPacketFromJandyJXiHeater(packet_buffer, packet_length, aqdata, previous_packet_to);
}
else if (interestedInNextAck == DRS_LX)
{
rtn = processPacketFromJandyLXHeater(packet_buffer, packet_length, aqdata, previous_packet_to);
}
else if (interestedInNextAck == DRS_CHEM)
{
rtn = processPacketFromJandyChemFeeder(packet_buffer, packet_length, aqdata, previous_packet_to);
}
interestedInNextAck = DRS_NONE;
previous_packet_to = NUL;
@ -71,7 +80,7 @@ bool processJandyPacket(unsigned char *packet_buffer, int packet_length, struct
interestedInNextAck = DRS_NONE;
previous_packet_to = NUL;
}
else if (READ_RSDEV_SWG && packet_buffer[PKT_DEST] == SWG_DEV_ID)
else if (READ_RSDEV_SWG && packet_buffer[PKT_DEST] >= JANDY_DEC_SWG_MIN && packet_buffer[PKT_DEST] <= JANDY_DEC_SWG_MAX)
{
interestedInNextAck = DRS_SWG;
rtn = processPacketToSWG(packet_buffer, packet_length, aqdata, _aqconfig_.swg_zero_ignore);
@ -83,11 +92,22 @@ bool processJandyPacket(unsigned char *packet_buffer, int packet_length, struct
rtn = processPacketToJandyPump(packet_buffer, packet_length, aqdata);
previous_packet_to = packet_buffer[PKT_DEST];
}
else if (READ_RSDEV_LXI && ( (packet_buffer[PKT_DEST] >= JANDY_DEC_LX_MIN && packet_buffer[PKT_DEST] <= JANDY_DEC_LX_MAX)
|| (packet_buffer[PKT_DEST] >= JANDY_DEC_LXI_MIN && packet_buffer[PKT_DEST] <= JANDY_DEC_LXI_MAX)))
else if (READ_RSDEV_JXI && packet_buffer[PKT_DEST] >= JANDY_DEC_JXI_MIN && packet_buffer[PKT_DEST] <= JANDY_DEC_JXI_MAX)
{
interestedInNextAck = DRS_LXI;
rtn = processPacketToJandyHeater(packet_buffer, packet_length, aqdata);
interestedInNextAck = DRS_JXI;
rtn = processPacketToJandyJXiHeater(packet_buffer, packet_length, aqdata);
previous_packet_to = packet_buffer[PKT_DEST];
}
else if (READ_RSDEV_LX && packet_buffer[PKT_DEST] >= JANDY_DEC_LX_MIN && packet_buffer[PKT_DEST] <= JANDY_DEC_LX_MAX)
{
interestedInNextAck = DRS_LX;
rtn = processPacketToJandyLXHeater(packet_buffer, packet_length, aqdata);
previous_packet_to = packet_buffer[PKT_DEST];
}
else if (READ_RSDEV_CHEM && packet_buffer[PKT_DEST] >= JANDY_DEC_CHEM_MIN && packet_buffer[PKT_DEST] <= JANDY_DEC_CHEM_MAX)
{
interestedInNextAck = DRS_CHEM;
rtn = processPacketToJandyChemFeeder(packet_buffer, packet_length, aqdata);
previous_packet_to = packet_buffer[PKT_DEST];
}
else
@ -109,13 +129,13 @@ bool processPacketToSWG(unsigned char *packet, int packet_length, struct aqualin
static int swg_zero_cnt = 0;
bool changedAnything = false;
if (getLogLevel(DJAN_LOG) == LOG_DEBUG) {
// Only log if we are jandy debug move and not serial (otherwise it'll print twice)
if (getLogLevel(DJAN_LOG) == LOG_DEBUG && getLogLevel(RSSD_LOG) < LOG_DEBUG ) {
char buff[1024];
beautifyPacket(buff, packet, packet_length, false);
LOG(DJAN_LOG,LOG_DEBUG, "%s", buff);
beautifyPacket(buff, packet, packet_length, true);
LOG(DJAN_LOG,LOG_DEBUG, "To SWG: %s", buff);
}
// Only read message from controller to SWG to set SWG Percent if we are not programming, as we might be changing this
if (packet[3] == CMD_PERCENT && aqdata->active_thread.thread_id == 0 && packet[4] != 0xFF) {
// In service or timeout mode SWG set % message is very strange. AR %% | HEX: 0x10|0x02|0x50|0x11|0xff|0x72|0x10|0x03|
@ -161,10 +181,11 @@ bool processPacketFromSWG(unsigned char *packet, int packet_length, struct aqual
bool changedAnything = false;
_swg_noreply_cnt = 0;
if (getLogLevel(DJAN_LOG) == LOG_DEBUG) {
// Only log if we are jandy debug move and not serial (otherwise it'll print twice)
if (getLogLevel(DJAN_LOG) == LOG_DEBUG && getLogLevel(RSSD_LOG) < LOG_DEBUG ) {
char buff[1024];
beautifyPacket(buff, packet, packet_length, true);
LOG(DJAN_LOG,LOG_DEBUG, "%s", buff);
LOG(DJAN_LOG,LOG_DEBUG, "From SWG: %s", buff);
}
if (packet[PKT_CMD] == CMD_PPM) {
@ -213,6 +234,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;
@ -220,6 +242,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);
@ -227,10 +260,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);
@ -250,6 +288,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;
@ -393,6 +432,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;
@ -466,6 +508,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");
@ -498,6 +545,12 @@ bool processPacketToJandyPump(unsigned char *packet_buffer, int packet_length, s
Type 0x1F and cmd 0x45 is RPM = 39 * (256) + 96 / 4 = 2520 or Byte 8 * 265 + Byte 7 / 4
*/
// Only log if we are jandy debug move and not serial (otherwise it'll print twice)
if (getLogLevel(DJAN_LOG) == LOG_DEBUG && getLogLevel(RSSD_LOG) < LOG_DEBUG ) {
char msg[1000];
beautifyPacket(msg, packet_buffer, packet_length, true);
LOG(DJAN_LOG, LOG_DEBUG, "To ePump: %s\n", msg);
}
// If type 0x45 and 0x44 set to interested in next command.
if (packet_buffer[3] == CMD_EPUMP_RPM) {
@ -506,11 +559,8 @@ bool processPacketToJandyPump(unsigned char *packet_buffer, int packet_length, s
} else if (packet_buffer[3] == CMD_EPUMP_WATTS) {
LOG(DJAN_LOG, LOG_DEBUG, "ControlPanel request Pump ID 0x%02hhx get watts\n",packet_buffer[PKT_DEST]);
}
if (getLogLevel(DJAN_LOG) >= LOG_DEBUG) {
char msg[1000];
beautifyPacket(msg, packet_buffer, packet_length, true);
LOG(DJAN_LOG, LOG_DEBUG, "To ePump: %s\n", msg);
if (getLogLevel(DJAN_LOG) == LOG_DEBUG) {
//find pump for message
for (int i=0; i < aqdata->num_pumps; i++) {
if (aqdata->pumps[i].pumpID == packet_buffer[PKT_DEST]) {
@ -527,6 +577,14 @@ bool processPacketFromJandyPump(unsigned char *packet_buffer, int packet_length,
{
bool found=false;
// Only log if we are jandy debug move and not serial (otherwise it'll print twice)
if (getLogLevel(DJAN_LOG) == LOG_DEBUG && getLogLevel(RSSD_LOG) < LOG_DEBUG ) {
char msg[1000];
//logMessage(LOG_DEBUG, "Need to log ePump message here for future\n");
beautifyPacket(msg, packet_buffer, packet_length, true);
LOG(DJAN_LOG, LOG_DEBUG, "From ePump: %s\n", msg);
}
if (packet_buffer[3] == CMD_EPUMP_STATUS && packet_buffer[4] == CMD_EPUMP_RPM) {
for (int i = 0; i < MAX_PUMPS; i++) {
if ( aqdata->pumps[i].prclType == JANDY && aqdata->pumps[i].pumpID == previous_packet_to ) {
@ -552,12 +610,7 @@ bool processPacketFromJandyPump(unsigned char *packet_buffer, int packet_length,
LOG(DJAN_LOG, LOG_NOTICE, "Jandy Pump found at ID 0x%02hhx with WATTS %d, but not configured, information ignored!\n",previous_packet_to, (packet_buffer[EP_HI_B_WAT] * 256) + packet_buffer[EP_LO_B_WAT]);
}
if (getLogLevel(DJAN_LOG) >= LOG_DEBUG) {
char msg[1000];
//logMessage(LOG_DEBUG, "Need to log ePump message here for future\n");
beautifyPacket(msg, packet_buffer, packet_length, true);
LOG(DJAN_LOG, LOG_DEBUG, "From ePump: %s\n", msg);
}
return false;
}
@ -572,13 +625,173 @@ void processMissingAckPacketFromJandyPump(unsigned char destination, struct aqua
bool processPacketToJandyHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata)
bool processPacketToJandyJXiHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata)
{
if (getLogLevel(DJAN_LOG) == LOG_DEBUG && getLogLevel(RSSD_LOG) < LOG_DEBUG ) {
char msg[1000];
//logMessage(LOG_DEBUG, "Need to log ePump message here for future\n");
beautifyPacket(msg, packet_buffer, packet_length, true);
LOG(DJAN_LOG, LOG_DEBUG, "To JXi: %s\n", msg);
}
if (packet_buffer[3] != CMD_JXI_PING) {
// Not sure what this message is, so ignore
// Maybe print a messsage.
return false;
}
/*
Below counfing first as bit 0
4th bit 0x00 no pump on (nothing)
0x10 seems to be JXi came online. nothing more
0x11 (pool mode)
0x12 (spa mode)
0x19 heat pool
0x1a heat spa
5th bit 0x55 = 85 deg. (current pool setpoint)
6th bit 0x66 = 102 deg. (current spa setpoint)
7th bit 0x4f = current water temp 79 (0xFF is off / 255)
*/
if (packet_buffer[5] != aqdata->pool_htr_set_point) {
LOG(DJAN_LOG, LOG_DEBUG, "JXi pool setpoint %d, Pool heater sp %d (changing to LXi)\n", packet_buffer[5], aqdata->pool_htr_set_point);
aqdata->pool_htr_set_point = packet_buffer[5];
}
if (packet_buffer[6] != aqdata->spa_htr_set_point) {
LOG(DJAN_LOG, LOG_DEBUG, "JXi spa setpoint %d, Spa heater sp %d (changing to LXi)\n", packet_buffer[6], aqdata->spa_htr_set_point);
aqdata->spa_htr_set_point = packet_buffer[6];
}
if (packet_buffer[7] != 0xff && packet_buffer[4] != 0x00) {
if (packet_buffer[4] == 0x11 || packet_buffer[4] == 0x19) {
if (aqdata->pool_temp != packet_buffer[7]) {
LOG(DJAN_LOG, LOG_DEBUG, "JXi pool water temp %d, pool water temp %d (changing to LXi)\n", packet_buffer[7], aqdata->pool_temp);
aqdata->pool_temp = packet_buffer[7];
}
} else if (packet_buffer[4] == 0x12 || packet_buffer[4] == 0x1a) {
if (aqdata->spa_temp != packet_buffer[7]) {
LOG(DJAN_LOG, LOG_DEBUG, "JXi spa water temp %d, spa water temp %d (changing to LXi)\n", packet_buffer[7], aqdata->spa_temp);
aqdata->spa_temp = packet_buffer[7];
}
}
}
switch (packet_buffer[4]) {
case 0x11: // Pool heat off or enabled
break;
case 0x12: // Pool Heat enabled or heating
break;
case 0x19: // Spa heat off or enabled
break;
case 0x1a: // Spa Hear Heat enabled or heating
break;
}
/*
char msg[1000];
int length = 0;
beautifyPacket(msg, packet_buffer, packet_length, true);
LOG(DJAN_LOG, LOG_INFO, "To Heater: %s\n", msg);
LOG(DJAN_LOG, LOG_INFO, "To JXi Heater: %s\n", msg);
length += sprintf(msg+length, "Last panel info ");
for (int i=0; i < aqdata->total_buttons; i++)
{
if ( strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0) {
length += sprintf(msg+length, ", Pool Heat LED=%d ",aqdata->aqbuttons[i].led->state);
}
if ( strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name) == 0) {
length += sprintf(msg+length, ", Spa Heat LED=%d ",aqdata->aqbuttons[i].led->state);
}
}
length += sprintf(msg+length, ", Pool SP=%d, Spa SP=%d",aqdata->pool_htr_set_point, aqdata->spa_htr_set_point);
length += sprintf(msg+length, ", Pool temp=%d, Spa temp=%d",aqdata->pool_temp, aqdata->spa_temp);
LOG(DJAN_LOG, LOG_INFO, "%s\n", msg);
return false;
*/
return true;
}
void getJandyHeaterError(struct aqualinkdata *aqdata, char *message)
{
if (aqdata->heater_err_status == NUL) {
return;
}
int size = sprintf(message, "JXi Heater ");
getJandyHeaterErrorMQTT(aqdata, message+size);
}
void getJandyHeaterErrorMQTT(struct aqualinkdata *aqdata, char *message)
{
switch (aqdata->heater_err_status) {
case 0x00:
//sprintf(message, "");
break;
case 0x10:
sprintf(message, "FAULT HIGH LIMIT");
break;
case 0x02:
sprintf(message, "FAULT H20 SENSOR");
break;
case 0x08:
sprintf(message, "FAULT AUX MONITOR");
break;
default:
//
/* Error we haven't decoded yet
?x?? check flow
0x10 Fault high limit
?x?? Fault High Flu temp
?x?? Fault Check Igntion Control
0x02 Fault Short H20 sensor (or Fault open water sensor)
?x?? Pump fault
0x08 AUX Monitor
*/
sprintf(message, "FAULT 0x%02hhx",aqdata->heater_err_status);
break;
}
}
bool processPacketFromJandyJXiHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to)
{
if (getLogLevel(DJAN_LOG) == LOG_DEBUG && getLogLevel(RSSD_LOG) < LOG_DEBUG ) {
char msg[1000];
//logMessage(LOG_DEBUG, "Need to log ePump message here for future\n");
beautifyPacket(msg, packet_buffer, packet_length, true);
LOG(DJAN_LOG, LOG_DEBUG, "From JXi: %s\n", msg);
}
if (packet_buffer[3] != CMD_JXI_STATUS) {
// Not sure what this message is, so ignore
// Maybe print a messsage.
return false;
}
// No error is 0x00, so blindly set it.
aqdata->heater_err_status = packet_buffer[6];
// Check if error first
if (packet_buffer[6] != 0x00) {
} else if (packet_buffer[4] == 0x00) {
// Not heating.
// Heater off or enabeled
} else if (packet_buffer[4] == 0x08) {
// Heating
// Heater on of enabled
}
/*
char msg[1000];
int length = 0;
beautifyPacket(msg, packet_buffer, packet_length, true);
LOG(DJAN_LOG, LOG_INFO, "From JXi Heater: %s\n", msg);
length += sprintf(msg+length, "Last panel info ");
@ -598,15 +811,47 @@ bool processPacketToJandyHeater(unsigned char *packet_buffer, int packet_length,
LOG(DJAN_LOG, LOG_INFO, "%s\n", msg);
return false;
*/
return true;
}
bool processPacketFromJandyHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to)
bool processPacketToJandyLXHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata)
{
char msg[1000];
int length = 0;
beautifyPacket(msg, packet_buffer, packet_length, true);
LOG(DJAN_LOG, LOG_INFO, "To LX: %s\n", msg);
length += sprintf(msg+length, "Last panel info ");
for (int i=0; i < aqdata->total_buttons; i++)
{
if ( strcmp(BTN_POOL_HTR,aqdata->aqbuttons[i].name) == 0) {
length += sprintf(msg+length, ", Pool Heat LED=%d ",aqdata->aqbuttons[i].led->state);
}
if ( strcmp(BTN_SPA_HTR,aqdata->aqbuttons[i].name) == 0) {
length += sprintf(msg+length, ", Spa Heat LED=%d ",aqdata->aqbuttons[i].led->state);
}
}
length += sprintf(msg+length, ", Pool SP=%d, Spa SP=%d",aqdata->pool_htr_set_point, aqdata->spa_htr_set_point);
length += sprintf(msg+length, ", Pool temp=%d, Spa temp=%d",aqdata->pool_temp, aqdata->spa_temp);
LOG(DJAN_LOG, LOG_INFO, "%s\n", msg);
return false;
}
bool processPacketFromJandyLXHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to)
{
char msg[1000];
int length = 0;
beautifyPacket(msg, packet_buffer, packet_length, true);
LOG(DJAN_LOG, LOG_INFO, "From Heater: %s\n", msg);
LOG(DJAN_LOG, LOG_INFO, "From LX: %s\n", msg);
length += sprintf(msg+length, "Last panel info ");
@ -629,3 +874,91 @@ bool processPacketFromJandyHeater(unsigned char *packet_buffer, int packet_lengt
}
bool processPacketToJandyChemFeeder(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata)
{
char msg[1000];
int length = 0;
beautifyPacket(msg, packet_buffer, packet_length, true);
LOG(DJAN_LOG, LOG_INFO, "To Chem: %s\n", msg);
length += sprintf(msg+length, "Last panel info ");
length += sprintf(msg+length, ", pH=%f, ORP=%d",aqdata->ph, aqdata->orp);
LOG(DJAN_LOG, LOG_INFO, "%s\n", msg);
return false;
}
bool processPacketFromJandyChemFeeder(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to){
char msg[1000];
int length = 0;
beautifyPacket(msg, packet_buffer, packet_length, true);
LOG(DJAN_LOG, LOG_INFO, "From Chem: %s\n", msg);
length += sprintf(msg+length, "Last panel info ");
length += sprintf(msg+length, ", pH=%f, ORP=%d",aqdata->ph, aqdata->orp);
LOG(DJAN_LOG, LOG_INFO, "%s\n", msg);
return false;
}
/*
// JXi Heater
// Normal ping and return
5th bit 0x00 no pump on (nothing)
0x10 seems to be JXi came online. nothing more
0x11 (pool mode)
0x12 (spa mode)
0x19 heat pool
0x1a heat spa
6th bit 0x55 = 85 deg. (current pool setpoint)
7th bit 0x66 = 102 deg. (current spa setpoint)
8th bit 0x4f = current water temp 79 (0xFF is off / 255)
Jandy To 0x68 of type Unknown '0x0c' | HEX: 0x10|0x02|0x68|0x0c|0x11|0x55|0x66|0x4f|0xa1|0x10|0x03|
Jandy From 0x68 of type Unknown '0x0d' | HEX: 0x10|0x02|0x00|0x0d|0x00|0x00|0x00|0x1f|0x10|0x03|
Request to turn on 85
5th bit 0x19 looks like turn on
6th bit 0x55 = 85 deg.
7th bit 0x4f = current temp 79
Jandy To 0x68 of type Unknown '0x0c' | HEX: 0x10|0x02|0x68|0x0c|0x19|0x55|0x66|0x4f|0xa9|0x10|0x03|
Jandy From 0x68 of type Unknown '0x0d' | HEX: 0x10|0x02|0x00|0x0d|0x08|0x00|0x00|0x27|0x10|0x03|
Request to turn on 90
5th bit 0x19 looks like turn on
6th bit 0x5a = 90 deg.
Jandy To 0x68 of type Unknown '0x0c' | HEX: 0x10|0x02|0x68|0x0c|0x19|0x5a|0x66|0x4f|0xae|0x10|0x03|
Jandy From 0x68 of type Unknown '0x0d' | HEX: 0x10|0x02|0x00|0x0d|0x08|0x00|0x00|0x27|0x10|0x03|
Request to turn off (standard ping) // return had hi limit error in it
Jandy To 0x68 of type Unknown '0x0c' | HEX: 0x10|0x02|0x68|0x0c|0x11|0x55|0x66|0x4f|0xa1|0x10|0x03|
Jandy From 0x68 of type Unknown '0x0d' | HEX: 0x10|0x02|0x00|0x0d|0x00|0x00|0x10|0x2f|0x10|0x03|
Returns
5th bit is type 0x00 nothing (or enabeled) - 0x08 looks like heat
Hi limit error return
7th bit 0x10 looks like the error
Jandy To 0x68 of type Unknown '0x0c' | HEX: 0x10|0x02|0x68|0x0c|0x19|0x5a|0x66|0x4f|0xae|0x10|0x03|
Jandy From 0x68 of type Unknown '0x0d' | HEX: 0x10|0x02|0x00|0x0d|0x08|0x00|0x10|0x37|0x10|0x03|
Errors are ->
check flow
Fault high limit -> 0x10
Fault High Flu temp
Fault Check Igntion Control
Fault Short H20 sensor (or Fault open water sensor) -> 0x02
Pump fault
AUX Monitor -> 0x08
*/

View File

@ -14,8 +14,13 @@ bool processPacketFromJandyPump(unsigned char *packet_buffer, int packet_length,
void processMissingAckPacketFromSWG(unsigned char destination, struct aqualinkdata *aqdata);
void processMissingAckPacketFromJandyPump(unsigned char destination, struct aqualinkdata *aqdata);
bool processPacketFromJandyHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to );
bool processPacketToJandyHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
bool processPacketFromJandyJXiHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to );
bool processPacketToJandyJXiHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
bool processPacketFromJandyLXHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata, const unsigned char previous_packet_to );
bool processPacketToJandyLXHeater(unsigned char *packet_buffer, int packet_length, struct aqualinkdata *aqdata);
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);
void get_swg_status_mqtt(struct aqualinkdata *aqdata, char *message, int *status, int *dzalert);
aqledstate get_swg_led_state(struct aqualinkdata *aqdata);
@ -27,4 +32,7 @@ void setSWGenabled(struct aqualinkdata *aqdata);
bool setSWGboost(struct aqualinkdata *aqdata, bool on);
void setSWGdeviceStatus(struct aqualinkdata *aqdata, emulation_type requester, unsigned char status);
void getJandyHeaterError(struct aqualinkdata *aqdata, char *message);
void getJandyHeaterErrorMQTT(struct aqualinkdata *aqdata, char *message);
#endif // AQUAPURE_H_

View File

@ -24,11 +24,20 @@
#include "aq_serial.h"
#include "devices_pentair.h"
#include "utils.h"
#include "packetLogger.h"
bool processPentairPacket(unsigned char *packet, int packet_length, struct aqualinkdata *aqdata)
{
bool changedAnything = false;
int i;
// Only log if we are pentair debug move and not serial (otherwise it'll print twice)
if (getLogLevel(DPEN_LOG) == LOG_DEBUG && getLogLevel(RSSD_LOG) < LOG_DEBUG ) {
char buff[1024];
beautifyPacket(buff, packet, packet_length, true);
LOG(DPEN_LOG,LOG_DEBUG, "%s", buff);
}
//ID's 96 to 111 = Pentair (or 0x60 to 0x6F)
// Need to find a better way to support pump index

BIN
extras/HASSIO.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

View File

@ -182,33 +182,12 @@ function check_git_installed {
function check_curl_installed {
command -v curl >/dev/null 2>&1 || { echoerr "curl is not installed. Installing"; exit 1; }
}
#
# Main
#
# Check we are on a arm 32 bit machine.
# below is compatable on all linux.
#if file -Lb /usr/bin/ld | grep -s -i armhf > /dev/null; then
# dpkg is good for debain distros.
if dpkg --print-architecture | grep -s -i armhf > /dev/null; then
echo "Architecture is armhf";
else
echo "Architecture is '`dpkg --print-architecture`'"
echo "This does not look like linux running on a arm 32 platform"
echo "Please use another install method"
echo "https://github.com/sfeakes/AqualinkD/wiki#Install"
exit
fi
# Check something was passed
if [[ $# -eq 0 ]]; then
print_usage
exit
fi
# Pass command line
if [ "$1" == "release" ]; then
check_curl_installed

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

@ -0,0 +1,47 @@
#!/bin/bash
# Script to start AqualinkD inside container.
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

444
hassio.c Normal file
View File

@ -0,0 +1,444 @@
#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 "\""
const 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"
"}";
const 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 }}\""
"}";
const 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
const 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"
"}";
const 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\""
"}";
const 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\""
"}";
const 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\""
"}";
const 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\""
"}";
const 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\""
"}";
const 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

@ -16,6 +16,7 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "aq_serial.h"
#include "aqualink.h"
@ -69,14 +70,15 @@ unsigned char _lastMsgType = 0x00;
//unsigned char _last_kick_type = -1;
int _deviceStatusLines = 0;
char _homeStatus[IAQ_STATUS_PAGE_LINES][AQ_MSGLEN+1];
char _deviceStatus[IAQ_STATUS_PAGE_LINES][AQ_MSGLEN+1];
char _tableInformation[IAQ_MSG_TABLE_LINES][IAQT_TABLE_MSGLEN+1];
struct iaqt_page_button _pageButtons[IAQ_PAGE_BUTTONS];
struct iaqt_page_button _homeButtons[IAQ_PAGE_BUTTONS];
// Need to cache these two pages, as only get updates after initial load.
struct iaqt_page_button _devicePageButtons[IAQ_PAGE_BUTTONS];
struct iaqt_page_button _devicePage2Buttons[IAQ_PAGE_BUTTONS];
struct iaqt_page_button _deviceSystemSetupButtons[IAQ_PAGE_BUTTONS];
// Need to cache these pages, as only get updates after initial load.
struct iaqt_page_button _devicePageButtons[3][IAQ_PAGE_BUTTONS];
struct iaqt_page_button _deviceSystemSetupButtons[3][IAQ_PAGE_BUTTONS];
unsigned char iaqtLastMsg()
{
@ -110,6 +112,11 @@ unsigned char iaqtCurrentPage()
{
return _currentPage;
}
unsigned char iaqtCurrentPageLoading()
{
return _currentPageLoading;
}
const char *iaqtGetMessageLine(int index) {
if (index < IAQ_STATUS_PAGE_LINES)
@ -129,12 +136,20 @@ struct iaqt_page_button *iaqtFindButtonByIndex(int index) {
struct iaqt_page_button *buttons;
// NSF Need to merge this from iaqtFindButtonByLabel function
if (_currentPage == IAQ_PAGE_DEVICES )
buttons = _devicePageButtons;
else if (_currentPage == IAQ_PAGE_DEVICES2 )
buttons = _devicePage2Buttons;
if (_currentPage == IAQ_PAGE_DEVICES)
buttons = _devicePageButtons[0];
else if (_currentPage == IAQ_PAGE_DEVICES2)
buttons = _devicePageButtons[1];
else if (_currentPage == IAQ_PAGE_DEVICES3)
buttons = _devicePageButtons[2];
else if (_currentPage == IAQ_PAGE_SYSTEM_SETUP )
buttons = _deviceSystemSetupButtons;
buttons = _deviceSystemSetupButtons[0];
else if (_currentPage == IAQ_PAGE_SYSTEM_SETUP2 )
buttons = _deviceSystemSetupButtons[1];
else if (_currentPage == IAQ_PAGE_SYSTEM_SETUP3 )
buttons = _deviceSystemSetupButtons[2];
else if (_currentPage == IAQ_PAGE_HOME )
buttons = _homeButtons;
else
buttons = _pageButtons;
@ -145,16 +160,24 @@ struct iaqt_page_button *iaqtFindButtonByIndex(int index) {
return NULL;
}
struct iaqt_page_button *iaqtFindButtonByLabel(char *label) {
struct iaqt_page_button *iaqtFindButtonByLabel(const char *label) {
int i;
struct iaqt_page_button *buttons;
if (_currentPage == IAQ_PAGE_DEVICES )
buttons = _devicePageButtons;
else if (_currentPage == IAQ_PAGE_DEVICES2 )
buttons = _devicePage2Buttons;
if (_currentPage == IAQ_PAGE_DEVICES)
buttons = _devicePageButtons[0];
else if (_currentPage == IAQ_PAGE_DEVICES2)
buttons = _devicePageButtons[1];
else if (_currentPage == IAQ_PAGE_DEVICES3)
buttons = _devicePageButtons[2];
else if (_currentPage == IAQ_PAGE_SYSTEM_SETUP )
buttons = _deviceSystemSetupButtons;
buttons = _deviceSystemSetupButtons[0];
else if (_currentPage == IAQ_PAGE_SYSTEM_SETUP2 )
buttons = _deviceSystemSetupButtons[1];
else if (_currentPage == IAQ_PAGE_SYSTEM_SETUP3 )
buttons = _deviceSystemSetupButtons[2];
else if (_currentPage == IAQ_PAGE_HOME )
buttons = _homeButtons;
else
buttons = _pageButtons;
@ -237,8 +260,10 @@ void processPageMessage(unsigned char *message, int length)
LOG(IAQT_LOG,LOG_ERR, "Run out of IAQT message buffer, need %d have %d\n",(int)message[PKT_IAQT_MSGINDX],IAQ_STATUS_PAGE_LINES);
return;
}
// 2nd page of device status doesn;t gine us new page message
if (_currentPageLoading == IAQ_PAGE_STATUS || _currentPage == IAQ_PAGE_STATUS) {
if (_currentPageLoading == IAQ_PAGE_HOME || _currentPage == IAQ_PAGE_HOME) {
rsm_strncpy(_homeStatus[(int)message[PKT_IAQT_MSGINDX]], &message[PKT_IAQT_MSGDATA], AQ_MSGLEN, length-PKT_IAQT_MSGDATA-3);
} else if (_currentPageLoading == IAQ_PAGE_STATUS || _currentPage == IAQ_PAGE_STATUS) { // 2nd page of device status doesn;t gine us new page message
//sprintf(_deviceStatus[(int)message[4]], message[5], AQ_MSGLEN);
//strncpy(_deviceStatus[(int)message[PKT_IAQT_MSGINDX]], (char *)message + PKT_IAQT_MSGDATA, AQ_MSGLEN);
rsm_strncpy(_deviceStatus[(int)message[PKT_IAQT_MSGINDX]], &message[PKT_IAQT_MSGDATA], AQ_MSGLEN, length-PKT_IAQT_MSGDATA-3);
@ -259,19 +284,30 @@ void processTableMessage(unsigned char *message, int length)
LOG(IAQT_LOG,LOG_ERR, "Run out of IAQT table buffer, need %d have %d\n",(int)message[5],IAQ_MSG_TABLE_LINES);
}
void processPageButton(unsigned char *message, int length)
void processPageButton(unsigned char *message, int length, struct aqualinkdata *aq_data)
{
struct iaqt_page_button *button;
int index = (int)message[PKT_IAQT_BUTINDX];
if (_currentPageLoading == IAQ_PAGE_DEVICES )
button = &_devicePageButtons[index];
else if (_currentPageLoading == IAQ_PAGE_DEVICES2 )
button = &_devicePage2Buttons[index];
else if (_currentPageLoading == IAQ_PAGE_SYSTEM_SETUP )
button = &_deviceSystemSetupButtons[index];
else
if (_currentPageLoading == IAQ_PAGE_DEVICES)
button = &_devicePageButtons[0][index];
else if (_currentPageLoading == IAQ_PAGE_DEVICES2)
button = &_devicePageButtons[1][index];
else if (_currentPageLoading == IAQ_PAGE_DEVICES3)
button = &_devicePageButtons[2][index];
else if (_currentPageLoading == IAQ_PAGE_SYSTEM_SETUP)
button = &_deviceSystemSetupButtons[0][index];
else if (_currentPageLoading == IAQ_PAGE_SYSTEM_SETUP2)
button = &_deviceSystemSetupButtons[1][index];
else if (_currentPageLoading == IAQ_PAGE_SYSTEM_SETUP3)
button = &_deviceSystemSetupButtons[2][index];
else if (_currentPageLoading == IAQ_PAGE_HOME )
button = &_homeButtons[index];
else {
button = &_pageButtons[index];
// if _currentPageLoading = 0x00 then we should use current page
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];
button->type = message[PKT_IAQT_BUTTYPE];
@ -285,19 +321,74 @@ void processPageButton(unsigned char *message, int length)
// This doesn't work with return which is 0x00
//strncpy(&button->name, (char *)message + PKT_IAQT_BUTDATA, AQ_MSGLEN);
memset(button->name, 0, sizeof(button->name));
rsm_strncpy_nul2sp((char *)button->name, &message[PKT_IAQT_BUTDATA], IAQT_MSGLEN, length-PKT_IAQT_BUTDATA-3);
LOG(IAQT_LOG,LOG_DEBUG, "Added Button %d %s\n",index,button->name);
LOG(IAQT_LOG,LOG_DEBUG, "Added Button %d %s - LoadingPage = %s\n",index,button->name,iaqt_page_name(_currentPageLoading));
/*
_pageButtons[index].state = (int)message[5];
_pageButtons[index].type = (int)message[7];
_pageButtons[index].unknownByte = (int)message[6];
// This doesn't work with return which is 0x00
strncpy(&_pageButtons[index].name, (char *)message + 8, AQ_MSGLEN);
// This get's called or every device state change in PDA mode, since we page over all the devices.
// So capture and update the device state
LOG(IAQT_LOG,LOG_NOTICE, "Added Button %d %s\n",index,_pageButtons[index].name);
*/
if (isPDA_PANEL || PANEL_SIZE() >= 16 ) {
int start = 0;
int end = aq_data->total_buttons;
#ifdef AQ_RS16
if (PANEL_SIZE() >= 16) {
start = aq_data->rs16_vbutton_start;
end = aq_data->rs16_vbutton_end + 1; // Using < in comparison and not <=, so +1
//printf("************ CHECK RS16 BUTTONS ************\n");
}
#endif
for (int i = start; i < end; i++)
{
int rtn=-1;
//LOG(IAQT_LOG,LOG_DEBUG, "Button compare '%s' to '%s'\n",button->name, aq_data->aqbuttons[i].label);
// If we are loading HOME page then simply button name is the label ie "Aux3"
// If loading DEVICES? page then button name + statusis "Aux3 OFF "
if (_currentPageLoading == IAQ_PAGE_HOME)
rtn = rsm_strmatch((const char *)button->name, aq_data->aqbuttons[i].label);
else
rtn = rsm_strmatch_ignore((const char *)button->name, aq_data->aqbuttons[i].label,5); // 5 = 3 chars and 2 spaces ' OFF '
if (rtn == 0)
{
LOG(IAQT_LOG,LOG_DEBUG, "*** Found Status for %s state 0x%02hhx\n", aq_data->aqbuttons[i].label, button->state);
switch(button->state) {
case 0x00:
if (aq_data->aqbuttons[i].led->state != OFF) {
aq_data->aqbuttons[i].led->state = OFF;
aq_data->updated = true;
}
break;
case 0x01:
if (aq_data->aqbuttons[i].led->state != ON) {
aq_data->aqbuttons[i].led->state = ON;
aq_data->updated = true;
}
break;
case 0x02:
if (aq_data->aqbuttons[i].led->state != FLASH) {
aq_data->aqbuttons[i].led->state = FLASH;
aq_data->updated = true;
}
break;
case 0x03:
if (aq_data->aqbuttons[i].led->state != ENABLE) {
aq_data->aqbuttons[i].led->state = ENABLE;
aq_data->updated = true;
}
break;
default:
LOG(IAQT_LOG,LOG_NOTICE, "Unknown state 0x%02hhx for button %s\n",button->state,button->name);
break;
}
}
}
}
}
@ -367,39 +458,45 @@ void passDeviceStatusPage(struct aqualinkdata *aq_data)
continue;
} else if (rsm_strcmp(_deviceStatus[i],"RPM:") == 0) {
if (pump != NULL)
if (pump != NULL) {
pump->rpm = rsm_atoi(&_deviceStatus[i][9]);
else
aq_data->updated = true;
} else
LOG(IAQT_LOG,LOG_WARNING, "Got pump message '%s' but can't find pump\n",_deviceStatus[i]);
continue;
} else if (rsm_strcmp(_deviceStatus[i],"GPM:") == 0) {
if (pump != NULL)
if (pump != NULL) {
pump->gpm = rsm_atoi(&_deviceStatus[i][9]);
else
aq_data->updated = true;
} else
LOG(IAQT_LOG,LOG_WARNING, "Got pump message '%s' but can't find pump\n",_deviceStatus[i]);
continue;
} else if (rsm_strcmp(_deviceStatus[i],"Watts:") == 0) {
if (pump != NULL)
if (pump != NULL) {
pump->watts = rsm_atoi(&_deviceStatus[i][9]);
else
aq_data->updated = true;
} else
LOG(IAQT_LOG,LOG_WARNING, "Got pump message '%s' but can't find pump\n",_deviceStatus[i]);
continue;
} else if (rsm_strcmp(_deviceStatus[i],"*** Priming ***") == 0) {
if (pump != NULL)
if (pump != NULL) {
pump->rpm = PUMP_PRIMING;
else
aq_data->updated = true;
} else
LOG(IAQT_LOG,LOG_WARNING, "Got pump message '%s' but can't find pump\n",_deviceStatus[i]);
continue;
} else if (rsm_strcmp(_deviceStatus[i],"(Offline)") == 0) {
if (pump != NULL)
if (pump != NULL) {
pump->rpm = PUMP_OFFLINE;
else
aq_data->updated = true;
} else
LOG(IAQT_LOG,LOG_WARNING, "Got pump message '%s' but can't find pump\n",_deviceStatus[i]);
continue;
} else if (rsm_strcmp(_deviceStatus[i],"(Priming Error)") == 0) {
if (pump != NULL)
if (pump != NULL) {
pump->rpm = PUMP_ERROR;
else
aq_data->updated = true;
} else
LOG(IAQT_LOG,LOG_WARNING, "Got pump message '%s' but can't find pump\n",_deviceStatus[i]);
continue;
// Need to catch messages like
@ -422,22 +519,27 @@ void passDeviceStatusPage(struct aqualinkdata *aq_data)
aq_data->ph = ph;
aq_data->orp = orp;
}
aq_data->updated = true;
LOG(IAQT_LOG,LOG_INFO, "Set Cemlink ORP = %d PH = %f from message '%s'\n",orp,ph,_deviceStatus[i]);
}
}
#ifdef READ_SWG_FROM_EXTENDED_ID
else if (rsm_strcmp(_deviceStatus[i],"AQUAPURE") == 0) {
//#ifdef READ_SWG_FROM_EXTENDED_ID
else if (isPDA_PANEL) {
if (rsm_strcmp(_deviceStatus[i],"AQUAPURE") == 0) {
//aq_data->swg_percent = rsm_atoi(&_deviceStatus[i][9]);
if (changeSWGpercent(aq_data, rsm_atoi(&_deviceStatus[i][9])))
LOG(IAQT_LOG,LOG_DEBUG, "Set swg %% to %d from message'%s'\n",aq_data->swg_percent,_deviceStatus[i]);
} else if (rsm_strcmp(_deviceStatus[i],"salt") == 0) {
} else if (rsm_strcmp(_deviceStatus[i],"salt") == 0) {
aq_data->swg_ppm = rsm_atoi(&_deviceStatus[i][5]);
aq_data->updated = true;
LOG(IAQT_LOG,LOG_DEBUG, "Set swg PPM to %d from message'%s'\n",aq_data->swg_ppm,_deviceStatus[i]);
} else if (rsm_strcmp(_deviceStatus[i],"Boost Pool") == 0) {
} else if (rsm_strcmp(_deviceStatus[i],"Boost Pool") == 0) {
aq_data->boost = true;
aq_data->updated = true;
// Let RS pickup time remaing message.
}
#endif
}
}
//#endif
} // for
}
@ -451,9 +553,13 @@ void debugPrintButtons(struct iaqt_page_button buttons[])
}
}
//#define member_size(type, member) (sizeof( ((type*)0)->member ))
void processPage(struct aqualinkdata *aq_data)
{
//static int _home_cnt = 0;
int i;
int dp = 0;
LOG(IAQT_LOG,LOG_INFO, "Page: %s | 0x%02hhx\n",iaqt_page_name(_currentPage),_currentPage);
@ -468,18 +574,36 @@ void processPage(struct aqualinkdata *aq_data)
debugPrintButtons(_pageButtons);
passDeviceStatusPage(aq_data);
// If button 1 is type 0x02 then there is a next page. Since status page isn't used for programming, hit the next page button.
if (_pageButtons[1].type == 0x02)
if (_pageButtons[1].type == 0x02) {
iaqt_queue_cmd(KEY_IAQTCH_KEY02);
else
} else {
iaqt_pump_update(aq_data, -1); // Reset pumps.
if ( (isPDA_PANEL || PANEL_SIZE() >= 16) && !in_iaqt_programming_mode(aq_data) ) {
iaqt_queue_cmd(KEY_IAQTCH_HOME);
}
}
break;
case IAQ_PAGE_DEVICES:
//LOG(IAQT_LOG,LOG_INFO, "Devices Page #1:-\n");
debugPrintButtons(_devicePageButtons);
break;
case IAQ_PAGE_DEVICES2:
//LOG(IAQT_LOG,LOG_INFO, "Devices Page #2:-\n");
debugPrintButtons(_devicePage2Buttons);
case IAQ_PAGE_DEVICES3:
if (_currentPage == IAQ_PAGE_DEVICES)
dp = 0;
else if (_currentPage == IAQ_PAGE_DEVICES2)
dp = 1;
else if (_currentPage == IAQ_PAGE_DEVICES3)
dp = 2;
//LOG(IAQT_LOG,LOG_INFO, "Devices Page #1:-\n");
debugPrintButtons(_devicePageButtons[dp]);
// If Button 15 has type 0x02 then we have previous, if 0x00 nothing (previous send code KEY_IAQTCH_PREV_PAGE)
// If Button 16 has type 0x03 then we have next, if 0x00 nothing (next send code KEY_IAQTCH_NEXT_PAGE)
if ( (isPDA_PANEL || PANEL_SIZE() >= 16) && !in_iaqt_programming_mode(aq_data) ) {
if (_devicePageButtons[dp][16].type == 0x03) {
iaqt_queue_cmd(KEY_IAQTCH_NEXT_PAGE);
} else {
iaqt_queue_cmd(KEY_IAQTCH_STATUS);
}
}
break;
case IAQ_PAGE_COLOR_LIGHT:
//LOG(IAQT_LOG,LOG_INFO, "Color Light Page :-\n");
@ -491,15 +615,68 @@ void processPage(struct aqualinkdata *aq_data)
if (_deviceStatus[i][0] != 0)
LOG(IAQT_LOG,LOG_INFO, "Status page %.2d| %s\n",i,_deviceStatus[i]);
debugPrintButtons(_pageButtons);
for (i=0; i <IAQ_STATUS_PAGE_LINES; i++ )
if (_homeStatus[i][0] != 0)
LOG(IAQT_LOG,LOG_INFO, "Home Status page %.2d| %s\n",i,_homeStatus[i]);
//Info: iAQ Touch: Home Status page 00| 65
//Info: iAQ Touch: Home Status page 01| 72
//Info: iAQ Touch: Home Status page 04| Spa Temp
//Info: iAQ Touch: Home Status page 05| Air Temp
if (isPDA_PANEL) { // Set temp if PDA panel
if (rsm_strcmp(_homeStatus[5],"Air Temp") == 0) {
aq_data->air_temp = atoi(_homeStatus[1]);
LOG(IAQT_LOG,LOG_DEBUG, "Air Temp set to %d\n",aq_data->air_temp);
aq_data->updated = true;
}
if (rsm_strcmp(_homeStatus[4],"Pool Temp") == 0) {
aq_data->pool_temp = atoi(_homeStatus[0]);
LOG(IAQT_LOG,LOG_DEBUG, "Pool Temp set to %d\n",aq_data->air_temp);
aq_data->updated = true;
} else if (rsm_strcmp(_homeStatus[4],"Spa Temp") == 0) {
aq_data->spa_temp = atoi(_homeStatus[0]);
LOG(IAQT_LOG,LOG_DEBUG, "Spa Temp set to %d\n",aq_data->spa_temp);
aq_data->updated = true;
}
}
//passHomePage(aq_data);
debugPrintButtons(_homeButtons);
break;
case IAQ_PAGE_SYSTEM_SETUP:
//LOG(IAQT_LOG,LOG_INFO, "System Setup :-\n");
debugPrintButtons(_deviceSystemSetupButtons);
debugPrintButtons(_deviceSystemSetupButtons[0]);
break;
case IAQ_PAGE_SYSTEM_SETUP2:
//LOG(IAQT_LOG,LOG_INFO, "System Setup :-\n");
debugPrintButtons(_deviceSystemSetupButtons[1]);
break;
case IAQ_PAGE_SYSTEM_SETUP3:
//LOG(IAQT_LOG,LOG_INFO, "System Setup :-\n");
debugPrintButtons(_deviceSystemSetupButtons[2]);
break;
case IAQ_PAGE_SET_VSP:
debugPrintButtons(_pageButtons);
break;
case IAQ_PAGE_HELP:
/*
Info: iAQ Touch: Table Messages 01| Interface: AquaLink Touch
Info: iAQ Touch: Table Messages 02| Model: RS-8 Combo
Info: iAQ Touch: Table Messages 03| AquaLink: REV T.0.1
Info: iAQ Touch: Table Messages 04| CPU p/n: B0029221
Info: iAQ Touch: Table Messages 05| TL Rev:
*/
if (isPDA_PANEL && ((char *)_tableInformation[03]) > 0) {
if ( rsm_get_revision(aq_data->revision,(char *)_tableInformation[3], sizeof(aq_data->revision) ) == TRUE) {
int len = rsm_get_boardcpu(aq_data->version, sizeof(aq_data->version), (char *)_tableInformation[4], IAQT_TABLE_MSGLEN );
sprintf(aq_data->version+len, " REV %s",aq_data->revision);
LOG(IAQT_LOG,LOG_NOTICE, "Control Panel revision %s\n", aq_data->revision);
LOG(IAQT_LOG,LOG_NOTICE, "Control Panel version %s\n", aq_data->version);
aq_data->updated = true;
}
}
break;
default:
//LOG(IAQT_LOG,LOG_INFO, "** UNKNOWN PAGE 0x%02hhx **\n",_currentPage);
@ -521,11 +698,27 @@ void processPage(struct aqualinkdata *aq_data)
bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkdata *aq_data)
{
static bool gotInit = false;
static int cnt = 0;
static bool gotStatus = true;
static char message[AQ_MSGLONGLEN + 1];
bool fake_pageend = false;
//char buff[1024];
// NSF Take this out
if ( packet[3] != CMD_IAQ_POLL && getLogLevel(IAQT_LOG) >= LOG_DEBUG ) {
//if ( getLogLevel(IAQT_LOG) >= LOG_DEBUG ) {
char buff[1000];
beautifyPacket(buff, packet, length, false);
LOG(IAQT_LOG,LOG_DEBUG, "Received message : %s", buff);
}
if (packet[PKT_CMD] == CMD_IAQ_PAGE_START) {
// Reset and messages on new page
aq_data->last_display_message[0] = ' ';
aq_data->last_display_message[1] = '\0';
aq_data->is_display_message_programming = false;
LOG(IAQT_LOG,LOG_DEBUG, "Turning IAQ SEND off\n");
set_iaq_cansend(false);
_currentPageLoading = packet[PKT_IAQT_PAGTYPE];
@ -533,6 +726,7 @@ bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkd
memset(_pageButtons, 0, IAQ_PAGE_BUTTONS * sizeof(struct iaqt_page_button));
memset(_deviceStatus, 0, sizeof(char) * IAQ_STATUS_PAGE_LINES * AQ_MSGLEN+1 );
memset(_tableInformation, 0, sizeof(char) * IAQ_MSG_TABLE_LINES * AQ_MSGLEN+1 );
//memset(_devicePageButtons, 0, IAQ_PAGE_BUTTONS * sizeof(struct iaqt_page_button));
// Fix bug with control panel where after a few hours status page disapears and you need to hit menu.
if (gotStatus == false)
gotStatus = true;
@ -542,17 +736,32 @@ bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkd
LOG(IAQT_LOG,LOG_DEBUG, "Turning IAQ SEND on\n");
if (_currentPageLoading != NUL) {
_currentPage = _currentPageLoading;
_currentPageLoading = NUL;
//_currentPageLoading = NUL;
} else {
LOG(IAQT_LOG,LOG_DEBUG, "Page end message without proceding page start, ignoring!\n");
}
if (isPDA_PANEL) {
// Time is in the page end command
// 1/18/2011 13:42
//Hex |0x10|0x02|0x33|0x28|0x01|0x12|0x0b|0x0d|0x2a|0xc2|0x10|0x03|
//Dec | 16| 2| 51| 40| 1| 18| 11| 13| 42| 194| 16| 3
//Ascii | | | 3| (| | | | | *| | |
snprintf(aq_data->date, sizeof(aq_data->date), "%02d/%02d/%02d", packet[4],packet[5],packet[6]);
if (packet[7] <= 12)
snprintf(aq_data->time, sizeof(aq_data->date), "%d:%02d am", packet[7],packet[8]);
else
snprintf(aq_data->time, sizeof(aq_data->date), "%d:%02d pm", (packet[7] - 12),packet[8]);
}
processPage(aq_data);
} else if (packet[PKT_CMD] == CMD_IAQ_TABLE_MSG) {
processTableMessage(packet, length);
} else if (packet[PKT_CMD] == CMD_IAQ_PAGE_MSG) {
processPageMessage(packet, length);
} else if (packet[PKT_CMD] == CMD_IAQ_PAGE_BUTTON) {
processPageButton(packet, length);
processPageButton(packet, length, aq_data);
// Second page on status doesn't send start & end, but button is message, so use that to kick off next page.
if (_currentPage == IAQ_PAGE_STATUS) {
/* Notice: Added Button 1
@ -567,32 +776,71 @@ bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkd
// if we get a button with 0x00 state on Light Page, that's the end of page.
if (_currentPageLoading == IAQ_PAGE_COLOR_LIGHT) {
if (packet[7] == 0x00) {
if (packet[7] == 0x00 && packet[4] == 0x0e) { // packet[4] is button number 0x0e is 14 (last button)
//printf("** MANUAL PAGE END\n");
LOG(IAQT_LOG,LOG_DEBUG, "MANUAL PAGE END\n");
_currentPage = _currentPageLoading;
_currentPageLoading = NUL;
//_currentPageLoading = NUL;
processPage(aq_data);
set_iaq_cansend(true);
// For programming mode
fake_pageend = true;
// Also END page here, as you can send commands.
// NEED to rethink this approach. ie, selecting light needs to hold open while showing page, no page end, then select light color, then message "please wait", the finally done
}
}
}
} else if (isPDA_PANEL && packet[PKT_CMD] == CMD_IAQ_MSG_LONG) {
char *sp;
// Set disply message if PDA panel
memset(message, 0, AQ_MSGLONGLEN + 1);
rsm_strncpy(message, packet + 6, AQ_MSGLONGLEN, length-9);
LOG(IAQT_LOG,LOG_NOTICE, "Popup message '%s'\n",message);
// Change this message, since you can't press OK. 'Light will turn off in 5 seconds. To change colors press Ok now.'
if ((sp = rsm_strncasestr(message, "To change colors press Ok now", strlen(message))) != NULL)
{
*sp = '\0';
}
strcpy(aq_data->last_display_message, message); // Also display the message on web UI
if (in_programming_mode(aq_data)) {
aq_data->is_display_message_programming = true;
} else {
aq_data->is_display_message_programming = false;
}
/*
for(int i=0; i<length; i++) {
printf("0x%02hhx|",packet[i]);
}
printf("\n");
*/
}
if (packet[3] == 0x29) {
//printf("***** iAqualink Touch STARTUP Message ******* \n");
LOG(IAQT_LOG,LOG_DEBUG, "STARTUP Message\n");
queueGetProgramData(IAQTOUCH, aq_data);
if (gotInit == false) {
LOG(IAQT_LOG,LOG_DEBUG, "STARTUP Message\n");
//LOG(IAQT_LOG,LOG_ERR, "STARTUP REMOVED GET PANEL DATA FOR TESTING\n");
queueGetProgramData(IAQTOUCH, aq_data);
gotInit = true;
}
//aq_programmer(AQ_SET_IAQTOUCH_SET_TIME, NULL, aq_data);
}
// Standard ack/poll not interested in printing or kicking threads
if (packet[3] == 0x30) {
/*
NEED TO ALTERNATE SEND KEY_IAQTCH_HOMEP_KEY08 KEY and KEY_IAQTCH_STATUS BELOW FOR PDA
*/
// Standard ack/poll
if (packet[3] == CMD_IAQ_POLL) {
//LOG(IAQT_LOG,LOG_DEBUG, "poll count %d\n",cnt);
// Load status page every 50 messages
if (cnt++ > REQUEST_STATUS_POLL_COUNT && in_programming_mode(aq_data) == false ) {
iaqt_queue_cmd(KEY_IAQTCH_STATUS);
if (isPDA_PANEL || PANEL_SIZE() >= 16) {
iaqt_queue_cmd(KEY_IAQTCH_HOMEP_KEY08);
} else {
iaqt_queue_cmd(KEY_IAQTCH_STATUS);
}
gotStatus = false; // Reset if we got status page, for fix panel bug.
//aq_programmer(AQ_GET_IAQTOUCH_VSP_ASSIGNMENT, NULL, aq_data);
cnt = 0;
@ -600,21 +848,32 @@ bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkd
// Fix bug with control panel where after a few hours status page disapears and you need to hit menu.
LOG(IAQT_LOG,LOG_INFO, "Overcomming Jandy control panel bug, (missing status, goto menu)\n",cnt);
iaqt_queue_cmd(KEY_IAQTCH_HOME);
iaqt_queue_cmd(KEY_IAQTCH_STATUS);
cnt = REQUEST_STATUS_POLL_COUNT - 5;
/*
if (isPDA_PANEL) {
iaqt_queue_cmd(KEY_IAQTCH_HOMEP_KEY08);
} else {
iaqt_queue_cmd(KEY_IAQTCH_STATUS);
}
*/
} else if (in_programming_mode(aq_data) == true) {
// Set count to something close to above, so we will pull latest info once programming has finished.
// This is goot for VSP GPM programming as it takes number of seconds to register once finished programming.
// -5 seems to be too quick for VSP/GPM so using 10
cnt = REQUEST_STATUS_POLL_COUNT - 10;
}
// On poll no need to kick programming threads
return false;
}
//debuglogPacket(IAQT_LOG ,packet, length);
//_lastMsgType = packet[PKT_CMD];
set_iaqtouch_lastmsg(packet[PKT_CMD]);
if (fake_pageend){
set_iaqtouch_lastmsg(CMD_IAQ_PAGE_END);
} else {
set_iaqtouch_lastmsg(packet[PKT_CMD]);
}
//debuglogPacket(IAQT_LOG ,packet, length);
//beautifyPacket(buff, packet, length);
//LOG(IAQT_LOG,LOG_DEBUG, "%s", buff);
@ -623,12 +882,15 @@ bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkd
kick_aq_program_thread(aq_data, IAQTOUCH);
return false;
return true;
}
//char _namebuf[40];
const char *iaqt_page_name(const unsigned char page)
{
static char _namebuf[40];
switch (page){
case IAQ_PAGE_HOME:
return "HOME";
@ -645,6 +907,9 @@ const char *iaqt_page_name(const unsigned char page)
case IAQ_PAGE_DEVICES2:
return "Devices #2";
break;
case IAQ_PAGE_DEVICES3:
return "Devices #2";
break;
case IAQ_PAGE_SET_TEMP:
return "Set Temp";
break;
@ -678,6 +943,12 @@ const char *iaqt_page_name(const unsigned char page)
case IAQ_PAGE_SYSTEM_SETUP:
return "System Setup";
break;
case IAQ_PAGE_SYSTEM_SETUP2:
return "System Setup #2";
break;
case IAQ_PAGE_SYSTEM_SETUP3:
return "System Setup #3";
break;
case IAQ_PAGE_VSP_SETUP:
return "VSP Setup";
break;
@ -687,8 +958,13 @@ const char *iaqt_page_name(const unsigned char page)
case IAQ_PAGE_LABEL_AUX:
return "Label Aux";
break;
case IAQ_PAGE_HELP:
return "Help Page";
break;
default:
return "** Unknown **";
sprintf (_namebuf,"** Unknown 0x%02hhx **",page);
return _namebuf;
//return "** Unknown **";
break;
}
return "";
@ -921,4 +1197,4 @@ Notice: Jandy Packet | HEX: 0x10|0x02|0x31|0x24|0x01|0x00|0x00|0x02|0x00|0x00
Button | 1 | 0x02 | |-| |-| -off-
*/
*/

View File

@ -15,8 +15,9 @@ struct iaqt_page_button {
bool process_iaqtouch_packet(unsigned char *packet, int length, struct aqualinkdata *aq_data);
unsigned char iaqtThreadKickType();
unsigned char iaqtCurrentPage();
unsigned char iaqtCurrentPageLoading();
bool wasiaqtThreadKickTypePage();
struct iaqt_page_button *iaqtFindButtonByLabel(char *label);
struct iaqt_page_button *iaqtFindButtonByLabel(const char *label);
struct iaqt_page_button *iaqtFindButtonByIndex(int index);
const char *iaqtGetMessageLine(int index);
const char *iaqtGetTableInfoLine(int index);

View File

@ -32,6 +32,7 @@
#include "config.h"
#include "devices_jandy.h"
#include "packetLogger.h"
#include "color_lights.h"
// System Page is obfiously fixed and not dynamic loaded, so set buttons to stop confustion.
@ -98,6 +99,12 @@ void waitfor_iaqt_queue2empty()
delay(PROGRAMMING_POLL_DELAY_TIME);
}
// Initial startup can take some time, _cansend should be false during this time.
// If we start programming before we receive the first status page, nothing works, this forces that wait
while(_cansend == false) {
delay(PROGRAMMING_POLL_DELAY_TIME * 2);
}
if (_iaqt_pgm_command != NUL) {
// Wait for longer interval
while ( (_iaqt_pgm_command != NUL) && ( i++ < PROGRAMMING_POLL_COUNTER * 2 ) ) {
@ -167,9 +174,64 @@ bool waitfor_iaqt_ctrl_queue2empty()
}
return true;
}
/*
unsigned const char _waitfor_iaqt_nextPage(struct aqualinkdata *aq_data, int numMessageReceived) {
waitfor_iaqt_queue2empty();
int i=0;
pthread_mutex_lock(&aq_data->active_thread.thread_mutex);
while( ++i <= numMessageReceived)
{
//LOG(IAQT_LOG,LOG_DEBUG, "waitfor_iaqt_nextPage (%d of %d)\n",i,numMessageReceived);
pthread_cond_wait(&aq_data->active_thread.thread_cond, &aq_data->active_thread.thread_mutex);
if(wasiaqtThreadKickTypePage()) break;
}
LOG(IAQT_LOG,LOG_DEBUG, "waitfor_iaqt_nextPage finished in (%d of %d)\n",i,numMessageReceived);
pthread_mutex_unlock(&aq_data->active_thread.thread_mutex);
if(wasiaqtThreadKickTypePage())
return iaqtCurrentPage();
else
return NUL;
}
unsigned const char shortwaitfor_iaqt_nextPage(struct aqualinkdata *aq_data) {
return _waitfor_iaqt_nextPage(aq_data, 3);
}
*/
unsigned const char waitfor_iaqt_messages(struct aqualinkdata *aq_data, int numMessageReceived) {
//return _waitfor_iaqt_nextPage(aq_data, 30);
waitfor_iaqt_queue2empty();
int i=0;
LOG(IAQT_LOG,LOG_DEBUG, "waitfor_iaqt_messages (%d of %d)\n",i,numMessageReceived);
pthread_mutex_lock(&aq_data->active_thread.thread_mutex);
while( ++i <= numMessageReceived)
{
//LOG(IAQT_LOG,LOG_DEBUG, "waitfor_iaqt_nextPage (%d of %d)\n",i,numMessageReceived);
pthread_cond_wait(&aq_data->active_thread.thread_cond, &aq_data->active_thread.thread_mutex);
}
LOG(IAQT_LOG,LOG_DEBUG, "waitfor_iaqt_messages finished in (%d of %d)\n",i,numMessageReceived);
pthread_mutex_unlock(&aq_data->active_thread.thread_mutex);
return iaqtLastMsg();
}
unsigned const char waitfor_iaqt_nextPage(struct aqualinkdata *aq_data) {
//return _waitfor_iaqt_nextPage(aq_data, 30);
waitfor_iaqt_queue2empty();
@ -193,9 +255,10 @@ unsigned const char waitfor_iaqt_nextPage(struct aqualinkdata *aq_data) {
return iaqtCurrentPage();
else
return NUL;
}
unsigned const char waitfor_iaqt_nextMessage(struct aqualinkdata *aq_data, const unsigned char msg_type) {
waitfor_iaqt_queue2empty();
@ -317,7 +380,8 @@ bool goto_iaqt_page(const unsigned char pageID, struct aqualinkdata *aq_data) {
LOG(IAQT_LOG, LOG_DEBUG, "IAQ Touch got to Device page\n");
return true;
} else if (pageID == IAQ_PAGE_MENU || pageID == IAQ_PAGE_SET_TEMP || pageID == IAQ_PAGE_SET_TIME || pageID == IAQ_PAGE_SET_SWG ||
pageID == IAQ_PAGE_SYSTEM_SETUP || pageID == IAQ_PAGE_FREEZE_PROTECT || pageID == IAQ_PAGE_LABEL_AUX || pageID == IAQ_PAGE_VSP_SETUP) {
pageID == IAQ_PAGE_SYSTEM_SETUP || pageID == IAQ_PAGE_FREEZE_PROTECT || pageID == IAQ_PAGE_LABEL_AUX ||
pageID == IAQ_PAGE_VSP_SETUP) {
// All other pages require us to go to Menu page
send_aqt_cmd(KEY_IAQTCH_MENU);
if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_MENU) {
@ -388,8 +452,14 @@ bool goto_iaqt_page(const unsigned char pageID, struct aqualinkdata *aq_data) {
button = iaqtFindButtonByLabel(menuText);
if (button == NULL) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find '%s' button on page setup\n", menuText);
return false;
//send_aqt_cmd(KEY_IAQTCH_NEXT_PAGE);
// Try Next Page
//unsigned char page = waitfor_iaqt_nextPage(aq_data);
//LOG(IAQT_LOG, LOG_ERR, "PAGE RETURN IS 0x%02hhx\n",page);
//if (waitfor_iaqt_nextPage(aq_data) != pageID) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find '%s' button on page setup\n", menuText);
return false;
//}
}
// send_aqt_cmd(KEY_IAQTCH_KEY01);
send_aqt_cmd(button->keycode);
@ -406,6 +476,207 @@ bool goto_iaqt_page(const unsigned char pageID, struct aqualinkdata *aq_data) {
return false;
}
void *set_aqualink_iaqtouch_device_on_off( void *ptr )
{
struct programmingThreadCtrl *threadCtrl;
threadCtrl = (struct programmingThreadCtrl *) ptr;
struct aqualinkdata *aq_data = threadCtrl->aq_data;
char *buf = (char*)threadCtrl->thread_args;
//char device_name[15];
struct iaqt_page_button *button;
unsigned int device = atoi(&buf[0]);
unsigned int state = atoi(&buf[5]);
waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_DEVICE_ON_OFF);
if (device > aq_data->total_buttons) {
LOG(IAQT_LOG,LOG_ERR, "(PDA mode) Device On/Off :- bad device number '%d'\n",device);
cleanAndTerminateThread(threadCtrl);
return ptr;
}
LOG(IAQT_LOG,LOG_INFO, "PDA Device On/Off, device '%s', state %d\n",aq_data->aqbuttons[device].label,state);
// See if it's on the current page
button = iaqtFindButtonByLabel(aq_data->aqbuttons[device].label);
if (button == NULL) {
// No luck, go to the device page
if ( goto_iaqt_page(IAQ_PAGE_DEVICES, aq_data) == false )
goto f_end;
button = iaqtFindButtonByLabel(aq_data->aqbuttons[device].label);
// If not found see if page has next
if (button == NULL && iaqtFindButtonByIndex(16)->type == 0x03 ) {
iaqt_queue_cmd(KEY_IAQTCH_NEXT_PAGE);
waitfor_iaqt_nextPage(aq_data);
// This will fail, since not looking at device page 2 buttons
button = iaqtFindButtonByLabel(aq_data->aqbuttons[device].label);
}
}
if (button == NULL) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find '%s' button on device list\n", aq_data->aqbuttons[device].label);
goto f_end;
}
send_aqt_cmd(button->keycode);
//LOG(IAQT_LOG, LOG_ERR, "******* CURRENT MENU '0x%02hhx' *****\n",iaqtCurrentPage());
// NSF NEED TO CHECK FOR POPUP MESSAGE, AND KILL IT.
//page IAQ_PAGE_SET_TEMP hit button 0
//page IAQ_PAGE_COLOR_LIGHT hit button ???????
// Heater popup can be cleared with a home button and still turn on.
// Color light can be cleared with a home button, but won;t turn on.
waitfor_iaqt_queue2empty();
//waitfor_iaqt_messages(aq_data,1);
// OR maybe use waitfor_iaqt_messages(aq_data,1)
// Turn spa on when pump off, ned to remove message "spa will turn on after safty delay", home doesn't clear.
send_aqt_cmd(KEY_IAQTCH_HOME);
// Turn spa off need to read message if heater is on AND hit ok......
f_end:
goto_iaqt_page(IAQ_PAGE_HOME, aq_data);
cleanAndTerminateThread(threadCtrl);
// just stop compiler error, ptr is not valid as it's just been freed
return ptr;
}
void *set_aqualink_iaqtouch_light_colormode( void *ptr )
{
struct programmingThreadCtrl *threadCtrl;
threadCtrl = (struct programmingThreadCtrl *) ptr;
struct aqualinkdata *aq_data = threadCtrl->aq_data;
char *buf = (char*)threadCtrl->thread_args;
//char device_name[15];
struct iaqt_page_button *button;
const char *mode_name;
int val = atoi(&buf[0]);
int btn = atoi(&buf[5]);
int typ = atoi(&buf[10]);
bool use_current_mode = false;
bool turn_off = false;
waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_LIGHTCOLOR_MODE);
//char *buf = (char*)threadCtrl->thread_args;
if (btn < 0 || btn >= aq_data->total_buttons ) {
LOG(IAQT_LOG, LOG_ERR, "Can't program light mode on button %d\n", btn);
cleanAndTerminateThread(threadCtrl);
return ptr;
}
aqkey *key = &aq_data->aqbuttons[btn];
//unsigned char code = key->code;
// We also need to cater for light being ON AND changing the color mode. we have extra OK to hit.
if (val == 0) {
use_current_mode = true;
LOG(IAQT_LOG, LOG_INFO, "Light Programming #: %d, button: %s, color light type: %d, using current mode\n", val, key->label, typ);
// NOT SURE WHAT TO DO HERE..... No color mode and iaatouch doesn;t support last color in PDA mode.
} else if (val == -1) {
turn_off = true;
LOG(IAQT_LOG, LOG_INFO, "Light Programming #: %d, button: %s, color light type: %d, Turning off\n", val, key->label, typ);
} else {
mode_name = light_mode_name(typ, val-1, IAQTOUCH);
use_current_mode = false;
if (mode_name == NULL) {
LOG(IAQT_LOG, LOG_ERR, "Light Programming #: %d, button: %s, color light type: %d, couldn't find mode name '%s'\n", val, key->label, typ, mode_name);
cleanAndTerminateThread(threadCtrl);
return ptr;
} else {
LOG(IAQT_LOG, LOG_INFO, "Light Programming #: %d, button: %s, color light type: %d, name '%s'\n", val, key->label, typ, mode_name);
}
}
// See if it's on the current page
button = iaqtFindButtonByLabel(key->label);
if (button == NULL) {
// No luck, go to the device page
if ( goto_iaqt_page(IAQ_PAGE_DEVICES, aq_data) == false )
goto f_end;
button = iaqtFindButtonByLabel(key->label);
// If not found see if page has next
if (button == NULL && iaqtFindButtonByIndex(16)->type == 0x03 ) {
iaqt_queue_cmd(KEY_IAQTCH_NEXT_PAGE);
waitfor_iaqt_nextPage(aq_data);
// This will fail, since not looking at device page 2 buttons
button = iaqtFindButtonByLabel(key->label);
}
}
if (button == NULL) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find '%s' button on device list\n", key->label);
goto f_end;
}
// WE have a iaqualink button, press it.
send_aqt_cmd(button->keycode);
// See if we want to use the last color, or turn it off
if (use_current_mode || turn_off) {
// After pressing the button, Just need to wait for 5 seconds and it will :-
// a) if off turn on and default to last color.
// b) if on, turn off. (pain that we need to wait 5 seconds.)
waitfor_iaqt_queue2empty();
waitfor_iaqt_nextPage(aq_data);
if (use_current_mode) {
// Their is no message for this, so give one.
sprintf(aq_data->last_display_message, "Light will turn on in 5 seconds");
aq_data->is_display_message_programming = true;
aq_data->updated = true;
}
// Wait for next page maybe?
// Below needs a timeout.
while (waitfor_iaqt_nextPage(aq_data) == IAQ_PAGE_COLOR_LIGHT);
goto f_end;
}
// BELOW WE NEED TO CATER FOR OK POPUP IF LIGHT IS ALREADY ON
if (button->state == 0x01) { // Light is on, need to select the BUTTON
waitfor_iaqt_queue2empty();
// We Should wait for popup message, before sending code
send_aqt_cmd(KEY_IAQTCH_OK);
}
if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_COLOR_LIGHT) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not color light page\n");
goto f_end;
}
button = iaqtFindButtonByLabel(mode_name);
if (button == NULL) {
LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did find color '%s' in color light page\n",mode_name);
goto f_end;
}
//LOG(IAQT_LOG, LOG_ERR, "IAQ Touch WAIYING FOR 1 MESSAGES\n");
//waitfor_iaqt_messages(aq_data, 1);
// Finally found the color. select it
send_aqt_cmd(button->keycode);
waitfor_iaqt_nextPage(aq_data);
f_end:
goto_iaqt_page(IAQ_PAGE_HOME, aq_data);
cleanAndTerminateThread(threadCtrl);
// just stop compiler error, ptr is not valid as it's just been freed
return ptr;
}
void *set_aqualink_iaqtouch_pump_rpm( void *ptr )
{
struct programmingThreadCtrl *threadCtrl;
@ -486,7 +757,9 @@ void *set_aqualink_iaqtouch_pump_rpm( void *ptr )
//send_aqt_cmd(0x80);
// Go to status page on startup to read devices
goto_iaqt_page(IAQ_PAGE_STATUS, aq_data);
// This is too soon
//goto_iaqt_page(IAQ_PAGE_STATUS, aq_data);
//waitfor_iaqt_nextPage(aq_data);
f_end:
goto_iaqt_page(IAQ_PAGE_HOME, aq_data);
@ -587,6 +860,10 @@ void *get_aqualink_iaqtouch_setpoints( void *ptr )
waitForSingleThreadOrTerminate(threadCtrl, AQ_GET_IAQTOUCH_SETPOINTS);
// Get product info.
send_aqt_cmd(KEY_IAQTCH_HELP);
waitfor_iaqt_nextPage(aq_data);
if ( goto_iaqt_page(IAQ_PAGE_SET_TEMP, aq_data) == false )
goto f_end;
@ -636,6 +913,40 @@ void *get_aqualink_iaqtouch_setpoints( void *ptr )
LOG(IAQT_LOG,LOG_NOTICE, "IAQ Touch Freeze Protection setpoint %d\n",frz);
}
// Get the temperature units if we are in iaq touch PDA mode
if (isPDA_PANEL) {
// If we are here, hit back then next button to get button with degrees on it.
// Only if in PDA mode
send_aqt_cmd(KEY_IAQTCH_BACK); // Clear the feeze protect menu and go back to system setup
if ( waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_SYSTEM_SETUP )
{
LOG(IAQT_LOG,LOG_ERR, "Couldn't get back to setup page, Temperature units unknown, default to DegF\n");
aq_data->temp_units = FAHRENHEIT;
goto f_end;
}
send_aqt_cmd(KEY_IAQTCH_NEXT_PAGE_ALTERNATE); // Go to page 2
if ( waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_SYSTEM_SETUP2 )
{
LOG(IAQT_LOG,LOG_ERR, "Couldn't get back to setup page, Temperature units unknown, default to DegF\n");
aq_data->temp_units = FAHRENHEIT;
goto f_end;
}
button = iaqtFindButtonByLabel("Degrees");
if (button != NULL) {
LOG(IAQT_LOG,LOG_NOTICE, "Temperature units are '%s'\n",button->name);
if (*button->name[8] == 'C') {
aq_data->temp_units = CELSIUS;
} else {
aq_data->temp_units = FAHRENHEIT;
}
}
}
// Need to run over table messages and check ens with X for on off.
// Go to status page on startup to read devices

View File

@ -19,6 +19,8 @@ void *set_aqualink_iaqtouch_spa_heater_temp( void *ptr );
void *set_aqualink_iaqtouch_pool_heater_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 );
void *set_aqualink_iaqtouch_device_on_off( void *ptr ); // For PDA only
int ref_iaqt_control_cmd(unsigned char **cmd);
void rem_iaqt_control_cmd(unsigned char *cmd);

View File

@ -98,6 +98,11 @@ const char* _getStatus(struct aqualinkdata *aqdata, const char *blankmsg)
return programtypeDisplayName(aqdata->active_thread.ptype);
}
*/
if (aqdata->active_thread.thread_id != 0) {
if (!aqdata->is_display_message_programming || rsm_isempy(aqdata->last_display_message,strlen(aqdata->last_display_message))){
return programtypeDisplayName(aqdata->active_thread.ptype);
}
}
//if (aqdata->last_message != NULL && stristr(aqdata->last_message, "SERVICE") != NULL ) {
if (aqdata->service_mode_state == ON) {
@ -611,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
@ -685,14 +686,17 @@ void mqtt_broadcast_aqualinkstate(struct mg_connection *nc)
const char *status;
if (_aqconfig_.mqtt_timed_update) {
#ifdef AQ_NO_THREAD_NETSERVICE
if (cnt > 300) { // 100 = about every 2 minutes.
#else
if (cnt > 30) { // 30 = about every 2 minutes.
#endif
reset_last_mqtt_status();
cnt = 0;
} else {
cnt++;
}
}
//LOG(NET_LOG,LOG_INFO, "mqtt_broadcast_aqualinkstate: START\n");
@ -819,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");
}
@ -831,9 +841,10 @@ void mqtt_broadcast_aqualinkstate(struct mg_connection *nc)
int dzalert;
get_swg_status_mqtt(_aqualink_data, message, &status, &dzalert);
send_domoticz_mqtt_status_message(nc, _aqconfig_.dzidx_swg_status, dzalert, &message[9]);
send_domoticz_mqtt_status_message(nc, _aqconfig_.dzidx_swg_status, dzalert, &message[9]);
send_mqtt_int_msg(nc, SWG_EXTENDED_TOPIC, (int)_aqualink_data->ar_swg_device_status);
send_mqtt_string_msg(nc, SWG_STATUS_MSG_TOPIC, message);
_last_mqtt_aqualinkdata.ar_swg_device_status = _aqualink_data->ar_swg_device_status;
//LOG(NET_LOG,LOG_DEBUG, "SWG Extended sending cur=%d sent=%d\n",_aqualink_data->ar_swg_device_status,_last_mqtt_aqualinkdata.ar_swg_device_status);
@ -844,6 +855,22 @@ void mqtt_broadcast_aqualinkstate(struct mg_connection *nc)
//LOG(NET_LOG,LOG_DEBUG, "SWG Extended unknown\n");
}
if (READ_RSDEV_JXI && _aqualink_data->heater_err_status != _last_mqtt_aqualinkdata.heater_err_status) {
char message[30];
if (_aqualink_data->heater_err_status == NUL) {
send_mqtt_int_msg(nc, LXI_ERROR_CODE, (int)_aqualink_data->heater_err_status);
send_mqtt_string_msg(nc, LXI_ERROR_MESSAGE, "");
} else {
//send_mqtt_int_msg(nc, LXI_STATUS, (int)_aqualink_data->heater_err_status);
send_mqtt_int_msg(nc, LXI_ERROR_CODE, (int)_aqualink_data->heater_err_status);
getJandyHeaterErrorMQTT(_aqualink_data, message);
send_mqtt_string_msg(nc, LXI_ERROR_MESSAGE, status);
}
_last_mqtt_aqualinkdata.heater_err_status = _aqualink_data->heater_err_status;
}
// LOG(NET_LOG,LOG_INFO, "mqtt_broadcast_aqualinkstate: START LEDs\n");
// if (time(NULL) % 2) {} <-- use to determin odd/even second in time to make state flash on enabled.
@ -952,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);
@ -1325,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;
@ -1821,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:
@ -1876,6 +1920,13 @@ void reset_last_mqtt_status()
_last_mqtt_aqualinkdata.boost = -1;
_last_mqtt_aqualinkdata.swg_percent = -1;
_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;
}
}

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

View File

@ -589,8 +589,8 @@ bool log_qeuiptment_status_VP2(struct aqualinkdata *aq_data)
} else if (PANEL_SIZE() >= 16 ) {
// Loop over RS 16 buttons.
get_RS16buttoninfo_from_menu(aq_data, i);
#endif
}
#endif
}
return rtn;

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)
{

Binary file not shown.

View File

@ -77,16 +77,22 @@ device_id=0x0a
# If you have extended_device_id set, then you can also use that ID for programming some features.
# This means that you can turn things on/off while AqualinkD is programming certian features.
# If you are using Aqualink Touch protocol for extended_device_id then this is highly recomended
# as it will speed up programming substantially. if One Touch it's 50/50.
# as it will speed up programming substantially. if One Touch it's 50/50.
#extended_device_id_programming = yes
# Read information from these devices directly from the RS485 bus as well as control panel.
# swg = Salt Water Generator
# ePump = Jandy ePump or ePump AC
# vsfPump = Pentair VS,VF,VSF pump
# JXi = Jandy JXi heater (might also be LXi heaters)
# LX = Jandy LX & LT heaters
# Chem = Jandy Chemical Feeder
#read_RS485_swg = yes
#read_RS485_ePump = yes
#read_RS485_vsfPump = yes
#read_RS485_JXi = yes
#read_RS485_LX = yes
#read_RS485_Chem = yes
# Keep the panel time synced with systemtime. Make sure to set systemtime / NTP correctly.
keep_paneltime_synced = yes
@ -164,6 +170,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.

View File

@ -18,8 +18,8 @@ web_directory=/nas/data/Development/Raspberry/AqualinkD/web
#log_level=DEBUG_SERIAL
#log_level=DEBUG
log_level=INFO
#log_level=NOTICE
#log_level=INFO
log_level=NOTICE
# AQUA_LOG 1
# NET_LOG 2
@ -40,15 +40,19 @@ log_level=INFO
#debug_log_mask = 2
#debug_log_mask = 4
#debug_log_mask = 8
#debug_log_mask = 16
debug_log_mask = 16
#debug_log_mask = 32
#debug_log_mask = 64
#debug_log_mask = 256
#debug_log_mask = 512
#debug_log_mask = 1024
debug_log_mask = 1024
#debug_log_mask = 2048
#debug_log_mask = 4096
# Log any packets from this device. (less outpit that DEBUG_SERIAL)
#debug_log_mask = 512 - MUST be set for this to work.
RSSD_LOG_filter = 0x33
display_warnings_in_web = yes
rs485_frame_delay = 4
@ -80,7 +84,8 @@ mqtt_aq_topic = aqualinkd-test
#device_id=0x0a
#device_id=0xFF # For testing one touch, don't use kaypad
#device_id=0x00
device_id=0x60
#device_id=0x60
device_id=0x33
#rssa_device_id=0x48
@ -89,7 +94,7 @@ device_id=0x60
# Valid ID's are 0x40, 0x41, 0x42 & 0x43.
# If you have a one touch remote do not use Ox40
#extended_device_id=0x43
#extended_device_id=0x31
#extended_device_id=0x33
# If you have extended_device_id set, then you can also use that ID for programming some features.
# This means that you can turn things on/off while AqualinkD is programming certian features.
@ -227,7 +232,7 @@ button_01_pumpID=0x78
#button_01_PDA_label=FILTER PUMP
button_01_pumpIndex=1
button_02_label=Spa Mode
button_02_label=Spa
#button_02_dzidx=38
#button_02_pumpID= 0x78
#button_02_pumpIndex=3

Binary file not shown.

View File

@ -20,6 +20,8 @@
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <regex.h>
#include <limits.h>
#include "utils.h"
#include "rs_msg_utils.h"
@ -83,6 +85,42 @@ bool rsm_get_revision(char *dest, const char *src, int src_len)
return true;
}
/*
pull board CPU from strings line
' CPU p/n: B0029221'
'B0029221 REV T.0.1'
'E0260801 REV. O.2'
*/
int rsm_get_boardcpu(char *dest, int dest_len, const char *src, int src_len)
{
//char *regexString="/\\w\\d{4,10}/gi";
//char *regexString="/[[:alpha:]][[:digit:]]{4,10}/gi";
char *regexString="[[:alpha:]][[:digit:]]{4,10}";
regex_t regexCompiled;
regmatch_t match;
int rc, begin, end, len;
if (0 != (rc = regcomp(&regexCompiled, regexString, REG_EXTENDED))) {
LOG(AQUA_LOG,LOG_ERR, "regcomp() failed, returning nonzero (%d)\n", rc);
return 0;
}
if ((regexec(&regexCompiled,src,1,&match,0)) != 0) {
regfree(&regexCompiled);
printf("********** ERROR didn;t line match \n");
return 0;
}
begin = (int)match.rm_so;
end = (int)match.rm_eo;
len = AQ_MIN((end-begin), dest_len);
strncpy(dest, src+match.rm_so, len );
regfree(&regexCompiled);
return len;
}
/*
Find first char after a space in haystack after searching needle.
@ -110,6 +148,18 @@ char *rsm_charafterstr(const char *haystack, const char *needle, int length)
return ++sp;
}
/*
Check if string has printable chars and is not empty
*/
bool rsm_isempy(const char *src, int length)
{
int i;
for(i=0; i < length; i++) {
if (src[i] > 32 && src[i] < 127) // 32 is space
return true;
}
return false;
}
/*
Can probably replace this with rsm_strncasestr in all code.
*/
@ -193,9 +243,82 @@ int rsm_strcmp(const char *haystack, const char *needle)
return -1;
// Need to write this myself for speed
//LOG(AQUA_LOG,LOG_DEBUG, "Compare (reset)%d chars of '%s' to '%s'\n",strlen(sp2),sp1,sp2);
//printf("***** rsm_strcmp Compare (reset)%d chars of '%s' to '%s'\n",strlen(sp2),sp1,sp2);
return strncasecmp(sp1, sp2, strlen(sp2));
}
// Match two strings, used for button labels
// exact character length once white space removed is used for match
// use case insensative for match.
// so 'spa' !- 'spa mode'
int rsm_strmatch(const char *haystack, const char *needle)
{
return rsm_strmatch_ignore(haystack, needle, 0);
/*
char *sp1 = (char *)haystack;
char *sp2 = (char *)needle;
char *ep1 = (char *)sp1 + strlen(sp1) - 1;
char *ep2 = (char *)sp2 + strlen(sp2) - 1;
//int i=0;
// Get rid of all padding
while(isspace(*sp1)) sp1++;
while(isspace(*sp2)) sp2++;
while(isspace(*ep1) && (ep1 >= sp1)) ep1--;
while(isspace(*ep2) && (ep2 >= sp2)) ep2--;
int l1 = ep1 - sp1 +1;
int l2 = ep2 - sp2 +1;
//printf("***** rsm_strmatch Compare %d chars of '%s' to %d chars in '%s'\n",l2,sp2,l1,sp1);
if ( l1 != l2 || (ep1 - sp1) <= 0 || (ep2 - sp2) <= 0 ) {
return -1;
}
// Need to write this myself for speed
//LOG(AQUA_LOG,LOG_DEBUG, "Compare (reset)%d chars of '%s' to '%s'\n",strlen(sp2),sp1,sp2);
return strncasecmp(sp1, sp2, l2);
*/
}
// Match two strings, used for button labels
// exact character length once white space removed is used for match
// ignore_chars will delete the last X chars from haystack.
// use case insensative for match.
// so 'spa' !- 'spa mode'
int rsm_strmatch_ignore(const char *haystack, const char *needle, int ignore_chars)
{
char *sp1 = (char *)haystack;
char *sp2 = (char *)needle;
char *ep1 = (char *)sp1 + strlen(sp1) - 1;
char *ep2 = (char *)sp2 + strlen(sp2) - 1;
//int i=0;
// Get rid of all padding
while(isspace(*sp1)) sp1++;
while(isspace(*sp2)) sp2++;
while(isspace(*ep2) && (ep2 >= sp2)) ep2--;
if (ignore_chars > 0)
ep1 = ep1 - ignore_chars;
else
while(isspace(*ep1) && (ep1 >= sp1)) ep1--;
int l1 = ep1 - sp1 +1;
int l2 = ep2 - sp2 +1;
//printf("***** %s() Compare %d chars of '%s' to %d chars in '%s'\n",(ignore_chars==0?"rsm_strmatch":"rsm_strmatch_ignore"),l2,sp2,l1,sp1);
if ( l1 != l2 || (ep1 - sp1) <= 0 || (ep2 - sp2) <= 0 ) {
return -1;
}
// Need to write this myself for speed
//LOG(AQUA_LOG,LOG_DEBUG, "Compare (reset)%d chars of '%s' to '%s'\n",strlen(sp2),sp1,sp2);
return strncasecmp(sp1, sp2, l2);
}
/*
* Find last index of char in string.
@ -218,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)
{
@ -241,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.
@ -253,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);
@ -304,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)
@ -343,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,16 +1,23 @@
#ifndef RS_MSG_UTILS_H_
#define RS_MSG_UTILS_H_
//#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);
char *rsm_charafterstr(const char *haystack, const char *needle, int length);
bool rsm_isempy(const char *src, int length);
char *rsm_strstr(const char *haystack, const char *needle);
//char *rsm_strnstr(const char *haystack, const char *needle, int length);
char *rsm_strnstr(const char *haystack, const char *needle, size_t slen);
char *rsm_strncasestr(const char *haystack, const char *needle, size_t length);
char *rsm_lastindexof(const char *haystack, const char *needle, size_t length);
int rsm_strmatch(const char *haystack, const char *needle);
int rsm_strmatch_ignore(const char *haystack, const char *needle, int ignore_chars);
int rsm_strncpy(char *dest, const unsigned char *src, int dest_len, int src_len);
int rsm_strcmp(const char *s1, const char *s2);
int rsm_strncmp(const char *haystack, const char *needle, int length);
@ -18,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_

View File

@ -117,7 +117,7 @@ int serial_logger (int rs_fd, char *port_name, int logLevel) {
#define PDA " <-- PDA Remote"
#define EPUMP " <-- Jandy VSP ePump"
#define CHEM " <-- Chemlink"
#define LXI_LRZ_HEATER " <-- LXi / LRZ Heater"
#define JXI_HEATER " <-- LXi / LRZ Heater"
#define UNKNOWN " <-- Unknown Device"
@ -151,7 +151,7 @@ const char *getDevice(unsigned char ID) {
if (ID >= 0x60 && ID <= 0x63)
return PDA;
if (ID >= 0x68 && ID <= 0x6B)
return LXI_LRZ_HEATER;
return JXI_HEATER;
//if (ID >= 0x70 && ID <= 0x73)
if (ID >= 0x78 && ID <= 0x7B)
return EPUMP;

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_VERSION "2.3.4"
#define AQUALINKD_SHORT_NAME "AqualinkD"
#define AQUALINKD_VERSION "2.3.5"

View File

@ -35,8 +35,8 @@
"Spa_Water",
"Freeze_Protect",
"CHEM/pH",
"CHEM/ORP"
//"Solar_Heater",
"CHEM/ORP",
"Solar_Heater",
];
// This get's picked up by dynamic_config.js and used as mode 0
@ -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')

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -1 +1 @@
switch-off.png
./switch-off.png

View File

@ -1 +1 @@
switch-on.png
./switch-on.png

View File

@ -421,13 +421,14 @@ console.log("SIM LOAD");
status.classList.remove("error");
}
}
function update_status(data) {
// Some form of error if PDA only panel.
if (data.panel_type.startsWith("PDA")) {
//document.getElementById("status").innerHTML = ' !!! PDA only panels are not Supported !!! '
//document.getElementById("status").classList.add("error");
update_status_message("PDA only panels are not Supported", true);
update_status_message("Some PDA panels do not support OneTouch", true);
}
const versionlabel = document.getElementById("version");