diff --git a/Makefile b/Makefile index 15fd2ff..c77d79f 100755 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ INCLUDES = -I/nas/data/Development/Raspberry/aqualink/aqualinkd # Add inputs and outputs from these tool invocations to the build variables # define the C source files -SRCS = aqualinkd.c utils.c config.c aq_serial.c init_buttons.c aq_programmer.c net_services.c json_messages.c pda_menu.c mongoose.c +SRCS = aqualinkd.c utils.c config.c aq_serial.c init_buttons.c aq_programmer.c net_services.c json_messages.c pda.c pda_menu.c pda_aq_programmer.c mongoose.c SL_SRC = serial_logger.c aq_serial.c utils.c PDA_SRC = pda_test.c pda_menu.c aq_serial.c utils.c diff --git a/README.md b/README.md index 54d65ef..5275094 100644 --- a/README.md +++ b/README.md @@ -59,15 +59,26 @@ Designed to mimic AqualinkRS6 All Button keypad, and just like the keypad you ca ## All Web interfaces. -* http://aqualink.ip/ <- (New UI) -* http://aqualink.ip/old <- (If you prefer the old UI, this is not maintained) -* http://aqualink.ip/simple.html <- (Anothr opion if you don't like the above) +* http://aqualink.ip/ <- (Standard WEB UI +* http://aqualink.ip/simple.html <- (Simple opion if you don't like the above) * http://aqualink.ip/simulator.html <- (RS8 All Button Control Panel simulator) # +## Update in Release 1.3.0 +* Large update for PDA only control panels (Majority of this is ballle98 work) +* Can distinguish between AquaPalm and PDA supported control panels. +* PDA Freeze & Heater setpoints now supported. +* Added PDA Sleep mode so AqualinkD can work inconjunction with a real Jandy PDA. +* Speeded up many PDA functions. +* Fixed many PDA bugs. +* Non PDA specific updates :- +* Can get button labels from control panel (not in PDA mode) +* RS485 Logging so users can submit information on Variable Speed Pumps & other devices for future support. +* Force SWG status on startup, rather than wait for pump to turn on. +* General bug fixes and improved code in many areas. ## Update in Release 1.2.6f * Solution to overcome bug in Mosquitto 1.6. * Fixed Salt Water Generator when % was set to 0. -* Added support for different SWG % for pool & spa. +* Added support for different SWG % for pool & spa. (SWG reports and sets the mode that's currently active) * Increased speed of SWG messages. * Few other bug fixes (Thanks to ballle98) ## Update in Release 1.2.6e (This is a quick update, please only use if you need one of the items below.) diff --git a/aq_programmer.c b/aq_programmer.c index b6b5185..97b883e 100644 --- a/aq_programmer.c +++ b/aq_programmer.c @@ -26,12 +26,14 @@ #include "utils.h" #include "aq_programmer.h" #include "aq_serial.h" +#include "pda.h" #include "pda_menu.h" #include "init_buttons.h" +#include "pda_aq_programmer.h" bool select_sub_menu_item(struct aqualinkdata *aq_data, char* item_string); bool select_menu_item(struct aqualinkdata *aq_data, char* item_string); -void send_cmd(unsigned char cmd, struct aqualinkdata *aq_data); +//void send_cmd(unsigned char cmd, struct aqualinkdata *aq_data); void cancel_menu(struct aqualinkdata *aq_data); @@ -43,17 +45,18 @@ void *get_aqualink_pool_spa_heater_temps( void *ptr ); void *get_aqualink_programs( void *ptr ); void *get_freeze_protect_temp( void *ptr ); void *get_aqualink_diag_model( void *ptr ); +void *get_aqualink_aux_labels( void *ptr ); void *threadded_send_cmd( void *ptr ); void *set_aqualink_light_colormode( void *ptr ); void *set_aqualink_PDA_init( void *ptr ); void *set_aqualink_SWG( void *ptr ); -void *get_aqualink_PDA_device_status( void *ptr ); -void *set_aqualink_PDA_device_on_off( void *ptr ); +//void *get_aqualink_PDA_device_status( void *ptr ); +//void *set_aqualink_PDA_device_on_off( void *ptr ); bool waitForButtonState(struct aqualinkdata *aq_data, aqkey* button, aqledstate state, int numMessageReceived); -bool waitForMessage(struct aqualinkdata *aq_data, char* message, int numMessageReceived); +//bool waitForMessage(struct aqualinkdata *aq_data, char* message, int numMessageReceived); bool waitForEitherMessage(struct aqualinkdata *aq_data, char* message1, char* message2, int numMessageReceived); bool push_aq_cmd(unsigned char cmd); @@ -204,12 +207,18 @@ void aq_programmer(program_type type, char *args, struct aqualinkdata *aq_data) struct programmingThreadCtrl *programmingthread = malloc(sizeof(struct programmingThreadCtrl)); if (pda_mode() == true) { + pda_reset_sleep(); if (type != AQ_PDA_INIT && + type != AQ_PDA_WAKE_INIT && type != AQ_PDA_DEVICE_STATUS && - type != AQ_PDA_DEVICE_ON_OFF) { + type != AQ_SET_POOL_HEATER_TEMP && + type != AQ_SET_SPA_HEATER_TEMP && + type != AQ_SET_SWG_PERCENT && + type != AQ_PDA_DEVICE_ON_OFF && + type != AQ_GET_POOL_SPA_HEATER_TEMPS ) { logMessage(LOG_ERR, "Selected Programming mode '%d' not supported with PDA mode control panel\n",type); return; - } + } } programmingthread->aq_data = aq_data; @@ -289,6 +298,12 @@ void aq_programmer(program_type type, char *args, struct aqualinkdata *aq_data) return; } break; + case AQ_PDA_WAKE_INIT: + if( pthread_create( &programmingthread->thread_id , NULL , set_aqualink_PDA_wakeinit, (void*)programmingthread) < 0) { + logMessage (LOG_ERR, "could not create thread\n"); + return; + } + break; case AQ_SET_SWG_PERCENT: if( pthread_create( &programmingthread->thread_id , NULL , set_aqualink_SWG, (void*)programmingthread) < 0) { logMessage (LOG_ERR, "could not create thread\n"); @@ -306,6 +321,12 @@ void aq_programmer(program_type type, char *args, struct aqualinkdata *aq_data) logMessage (LOG_ERR, "could not create thread\n"); return; } + break; + case AQ_GET_AUX_LABELS: + if( pthread_create( &programmingthread->thread_id , NULL , get_aqualink_aux_labels, (void*)programmingthread) < 0) { + logMessage (LOG_ERR, "could not create thread\n"); + return; + } break; default: logMessage (LOG_ERR, "Don't understand thread type\n"); @@ -478,17 +499,13 @@ void *set_aqualink_SWG( void *ptr ) int val = atoi((char*)threadCtrl->thread_args); val = setpoint_check(SWG_SETPOINT, val, aq_data); - // Just recheck it's in multiple of 5. - /* - if (0 != (val % 5) ) - val = ((val + 5) / 10) * 10; - if (val > SWG_PERCENT_MAX) { - val = SWG_PERCENT_MAX; - } else if ( val < SWG_PERCENT_MIN) { - val = SWG_PERCENT_MIN; + if (pda_mode() == true) { + set_PDA_aqualink_SWG_setpoint(aq_data, val); + cleanAndTerminateThread(threadCtrl); + return ptr; } - */ + logMessage(LOG_DEBUG, "programming SWG percent to %d\n", val); if ( select_menu_item(aq_data, "SET AQUAPURE") != true ) { @@ -541,258 +558,37 @@ void *set_aqualink_SWG( void *ptr ) return ptr; } -bool select_pda_main_menu(struct aqualinkdata *aq_data) -{ - int i=0; - // Check to see if we are at the main menu - if (pda_m_type() == PM_MAIN) { - return true; - } - // First send back - send_cmd(KEY_PDA_BACK, aq_data); - while (_pgm_command != NUL) { - delay(500); - if (i++ > 6) return false; - } - //delay(1000); - i=0; - while (pda_m_type() != PM_MAIN) { - delay(500); - if (i++ > 6) return false; - } - return true; -} -bool wait_pda_selected_item() -{ - int i=0; - - i=0; - while (pda_m_hlightindex() == -1){ - if (i++ > 10) - break; - delay(100); - } - - if (pda_m_hlightindex() == -1) - return false; - else - return true; -} - -bool select_pda_main_menu_item(struct aqualinkdata *aq_data, pda_menu_type menu_item) -{ - int i=0; - char *menu; - - if (! select_pda_main_menu(aq_data)) - return false; - - logMessage(LOG_DEBUG, "PDA Device programmer at main menu\n"); - - if (menu_item == PM_MAIN) - return true; - else if (menu_item == PM_SETTINGS) - menu = "MENU"; - else if (menu_item == PM_EQUIPTMENT_CONTROL) - menu = "EQUIPMENT ON/OFF"; - else - return false; - - if (!wait_pda_selected_item()){ - logMessage(LOG_ERR, "PDA Device programmer didn't find a selected item\n"); - return false; - } - - while ( strncmp(pda_m_hlight(), menu, strlen(menu)) != 0 ) { - if (_pgm_command == NUL) { - send_cmd(KEY_PDA_DOWN, aq_data); - logMessage(LOG_DEBUG, "PDA Device programmer selected sub menu\n"); - waitForMessage(aq_data, NULL, 1); - } - if (i++ > (PDA_LINES * 2)) - return false; - delay(500); - } - - send_cmd(KEY_PDA_SELECT, aq_data); - while (_pgm_command != NUL) { delay(500); } - - return true; - /* - send_cmd(KEY_PDA_DOWN, aq_data); - while (_pgm_command != NUL) { delay(500); } - */ -} - -void *set_aqualink_PDA_device_on_off( void *ptr ) +void *get_aqualink_aux_labels( void *ptr ) { struct programmingThreadCtrl *threadCtrl; threadCtrl = (struct programmingThreadCtrl *) ptr; struct aqualinkdata *aq_data = threadCtrl->aq_data; - int i=0; - int found; - waitForSingleThreadOrTerminate(threadCtrl, AQ_PDA_DEVICE_STATUS); - - char *buf = (char*)threadCtrl->thread_args; - int device = atoi(&buf[0]); - int state = atoi(&buf[5]); + waitForSingleThreadOrTerminate(threadCtrl, AQ_GET_AUX_LABELS); - if (device < 0 || device > TOTAL_BUTTONS) { - logMessage(LOG_ERR, "PDA Device On/Off :- bad device number '%d'\n",device); + if ( select_menu_item(aq_data, "REVIEW") != true ) { + logMessage(LOG_WARNING, "Could not select REVIEW menu\n"); + cancel_menu(aq_data); + cleanAndTerminateThread(threadCtrl); + return ptr; + } + + if (select_sub_menu_item(aq_data, "AUX LABELS") != true) { + logMessage(LOG_WARNING, "Could not select AUX LABELS menu\n"); + cancel_menu(aq_data); cleanAndTerminateThread(threadCtrl); return ptr; } - logMessage(LOG_INFO, "PDA Device On/Off, device '%s', state %d\n",aq_data->aqbuttons[device].pda_label,state); - - //printf("DEVICE LABEL = %s\n",aq_data->aqbuttons[device].pda_label); - - if (! select_pda_main_menu_item(aq_data, PM_EQUIPTMENT_CONTROL)) { - logMessage(LOG_ERR, "PDA Device On/Off :- can't find main menu\n"); - cleanAndTerminateThread(threadCtrl); - return ptr; - } -/* - i=0; - while (pda_m_hlightindex() == -1){ - if (i++ > 10) - break; - delay(100); - } -*/ - delay(500); -printf("Wait for select\n"); - if (!wait_pda_selected_item()){ - logMessage(LOG_ERR, "PDA Device programmer didn't find a selected item\n"); - return false; - } -printf("End wait select\n"); - i=0; - char labelBuff[AQ_MSGLEN]; - strncpy(labelBuff, pda_m_hlight(), AQ_MSGLEN-4); - labelBuff[AQ_MSGLEN-4] = 0; - - while ( (found = strcasecmp(stripwhitespace(labelBuff), aq_data->aqbuttons[device].pda_label)) != 0 ) { - if (_pgm_command == NUL) { - send_cmd(KEY_PDA_DOWN, aq_data); - //printf("*** Send Down for %s ***\n",pda_m_hlight()); - waitForMessage(aq_data, NULL, 1); - } - if (i++ > (PDA_LINES * 2)) { - break; - } - delay(500); - strncpy(labelBuff, pda_m_hlight(), AQ_MSGLEN-4); - labelBuff[AQ_MSGLEN-4] = 0; - } - - if (found == 0) { - //printf("*** FOUND ITEM %s ***\n",pda_m_hlight()); - if (aq_data->aqbuttons[device].led->state != state) { - //printf("*** Select State ***\n"); - logMessage(LOG_INFO, "PDA Device On/Off, found device '%s', changing state\n",aq_data->aqbuttons[device].pda_label,state); - send_cmd(KEY_PDA_SELECT, aq_data); - while (_pgm_command != NUL) { delay(500); } - } else { - logMessage(LOG_INFO, "PDA Device On/Off, found device '%s', not changing state, is same\n",aq_data->aqbuttons[device].pda_label,state); - } - } else { - //printf("*** NOT FOUND ITEM ***\n"); - logMessage(LOG_ERR, "PDA Device On/Off, device '%s' not found\n",aq_data->aqbuttons[device].pda_label); - } - - select_pda_main_menu_item(aq_data, PM_MAIN); - //while (_pgm_command != NUL) { delay(500); } + waitForMessage(aq_data, NULL, 5); // Receive 5 messages cleanAndTerminateThread(threadCtrl); // just stop compiler error, ptr is not valid as it's just been freed return ptr; } -void *get_aqualink_PDA_device_status( void *ptr ) -{ - struct programmingThreadCtrl *threadCtrl; - threadCtrl = (struct programmingThreadCtrl *) ptr; - struct aqualinkdata *aq_data = threadCtrl->aq_data; - int i; - - waitForSingleThreadOrTerminate(threadCtrl, AQ_PDA_DEVICE_STATUS); - - //int val = atoi((char*)threadCtrl->thread_args); - - logMessage(LOG_DEBUG, "PDA Device Status\n"); - - if (! select_pda_main_menu_item(aq_data, PM_EQUIPTMENT_CONTROL)) { - logMessage(LOG_ERR, "PDA Device Status :- can't find main menu\n"); - cleanAndTerminateThread(threadCtrl); - return ptr; - } - //select_pda_main_menu_item(aq_data, "EQUIPMENT ON/OFF"); - - // Just loop over all the dvices 18 times should do it. - for (i=0; i < 18; i++) { - send_cmd(KEY_PDA_DOWN, aq_data); - while (_pgm_command != NUL) { delay(100); } - } - - //printf("*** GET MAIN MENU ***\n"); - - select_pda_main_menu_item(aq_data, PM_MAIN); - - //printf("*** FINISHED ***\n"); - /* - send_cmd(KEY_PDA_BACK, aq_data); - while (_pgm_command != NUL) { delay(500); } - */ - cleanAndTerminateThread(threadCtrl); - - // just stop compiler error, ptr is not valid as it's just been freed - return ptr; -} - -void *set_aqualink_PDA_init( void *ptr ) -{ - struct programmingThreadCtrl *threadCtrl; - threadCtrl = (struct programmingThreadCtrl *) ptr; - struct aqualinkdata *aq_data = threadCtrl->aq_data; - int i=0; - - waitForSingleThreadOrTerminate(threadCtrl, AQ_PDA_INIT); - - //int val = atoi((char*)threadCtrl->thread_args); - - //logMessage(LOG_DEBUG, "PDA Init\n", val); - - logMessage(LOG_DEBUG, "PDA Init\n"); - - if (! select_pda_main_menu_item(aq_data, PM_EQUIPTMENT_CONTROL)) { - logMessage(LOG_ERR, "PDA Init :- can't find main menu\n"); - cleanAndTerminateThread(threadCtrl); - return ptr; - } - //select_pda_main_menu_item(aq_data, "EQUIPMENT ON/OFF"); - - // Just loop over all the dvices 20 times should do it. - for (i=0; i < 18; i++) { - send_cmd(KEY_PDA_DOWN, aq_data); - while (_pgm_command != NUL) { delay(500); } - } - - select_pda_main_menu_item(aq_data, PM_MAIN); - - printf("*** PDA Init :- add code to find setpoints ***\n"); - - // Run through menu and find freeze setpoints / heater setpoints etc. - - cleanAndTerminateThread(threadCtrl); - - // just stop compiler error, ptr is not valid as it's just been freed - return ptr; -} - void *set_aqualink_light_colormode( void *ptr ) { @@ -894,6 +690,13 @@ void *set_aqualink_pool_heater_temps( void *ptr ) } */ val = setpoint_check(POOL_HTR_SETOINT, val, aq_data); + + if (pda_mode() == true) { + set_PDA_aqualink_heater_setpoint(aq_data, val, true); + cleanAndTerminateThread(threadCtrl); + return ptr; + } + // NSF IF in TEMP1 / TEMP2 mode, we need C range of 1 to 40 is 2 to 40 for TEMP1, 1 to 39 TEMP2 if (aq_data->single_device == true ){ name = "TEMP1"; @@ -960,6 +763,13 @@ void *set_aqualink_spa_heater_temps( void *ptr ) val = MEATER_MIN; }*/ val = setpoint_check(SPA_HTR_SETOINT, val, aq_data); + + if (pda_mode() == true) { + set_PDA_aqualink_heater_setpoint(aq_data, val, true); + cleanAndTerminateThread(threadCtrl); + return ptr; + } + // NSF IF in TEMP1 / TEMP2 mode, we need C range of 1 to 40 is 2 to 40 for TEMP1, 1 to 39 TEMP2 if (aq_data->single_device == true ){ @@ -1148,6 +958,14 @@ void *get_aqualink_pool_spa_heater_temps( void *ptr ) waitForSingleThreadOrTerminate(threadCtrl, AQ_GET_POOL_SPA_HEATER_TEMPS); logMessage(LOG_NOTICE, "Getting pool & spa heat setpoints from aqualink\n"); + if (pda_mode() == true) { + if (!get_PDA_aqualink_pool_spa_heater_temps(aq_data)) { + logMessage(LOG_ERR, "Error Getting PDA pool & spa heat protection setpoints\n"); + } + cleanAndTerminateThread(threadCtrl); + return ptr; + } + if ( select_menu_item(aq_data, "REVIEW") != true ) { logMessage(LOG_WARNING, "Could not select REVIEW menu\n"); cancel_menu(aq_data); @@ -1182,6 +1000,15 @@ void *get_freeze_protect_temp( void *ptr ) waitForSingleThreadOrTerminate(threadCtrl, AQ_GET_FREEZE_PROTECT_TEMP); logMessage(LOG_NOTICE, "Getting freeze protection setpoints\n"); + + if (pda_mode() == true) { + if (! get_PDA_freeze_protect_temp(aq_data)) { + logMessage(LOG_ERR, "Error Getting PDA freeze protection setpoints\n"); + } + cleanAndTerminateThread(threadCtrl); + return ptr; + } + if ( select_menu_item(aq_data, "REVIEW") != true ) { logMessage(LOG_WARNING, "Could not select REVIEW menu\n"); cancel_menu(aq_data); diff --git a/aq_programmer.h b/aq_programmer.h index 75cad40..1335402 100644 --- a/aq_programmer.h +++ b/aq_programmer.h @@ -8,7 +8,7 @@ #define HEATER_MAX_F 104 #define HEATER_MIN_F 36 #define FREEZE_PT_MAX_F 42 -#define FREEZE_PT_MIN_F 36 +#define FREEZE_PT_MIN_F 34 #define HEATER_MAX_C 40 #define HEATER_MIN_C 0 @@ -36,7 +36,9 @@ typedef enum { AQ_PDA_INIT, AQ_SET_SWG_PERCENT, AQ_PDA_DEVICE_STATUS, - AQ_PDA_DEVICE_ON_OFF + AQ_PDA_DEVICE_ON_OFF, + AQ_GET_AUX_LABELS, + AQ_PDA_WAKE_INIT } program_type; struct programmingThreadCtrl { @@ -64,4 +66,12 @@ unsigned char pop_aq_cmd(struct aqualinkdata *aq_data); int get_aq_cmd_length(); int setpoint_check(int type, int value, struct aqualinkdata *aqdata); + +// These shouldn't be here, but just for the PDA AQ PROGRAMMER +void send_cmd(unsigned char cmd, struct aqualinkdata *aq_data); +bool push_aq_cmd(unsigned char cmd); +void waitForSingleThreadOrTerminate(struct programmingThreadCtrl *threadCtrl, program_type type); +void cleanAndTerminateThread(struct programmingThreadCtrl *threadCtrl); +bool waitForMessage(struct aqualinkdata *aq_data, char* message, int numMessageReceived); + #endif diff --git a/aq_serial.c b/aq_serial.c index bf93d23..9d56287 100644 --- a/aq_serial.c +++ b/aq_serial.c @@ -125,6 +125,9 @@ const char* get_packet_type(unsigned char* packet , int length) case CMD_PDA_0x05: return "PDA Unknown"; break; + case CMD_PDA_0x1B: + return "PDA Init (*guess*)"; + break; case CMD_PDA_HIGHLIGHT: return "PDA Hlight"; break; diff --git a/aq_serial.h b/aq_serial.h index 292e91f..a4946ee 100644 --- a/aq_serial.h +++ b/aq_serial.h @@ -53,8 +53,8 @@ #define KEY_PDA_DOWN 0x05 #define KEY_PDA_BACK 0x02 #define KEY_PDA_SELECT 0x04 -#define KEY_PDA_PGUP 0x01 -#define KEY_PDA_PGDN 0x03 +//#define KEY_PDA_PGUP 0x01 // Think these are hot key #1 +//#define KEY_PDA_PGDN 0x03 // Think these are hot key #2 /* KEY/BUTTON CODES */ #define KEY_PUMP 0x02 @@ -178,6 +178,7 @@ SPILLOVER IS DISABLED WHILE SPA IS ON #define SWG_STATUS_CHECK_PCB 0x80 // check PCB 0x80 #define CMD_PDA_0x05 0x05 +#define CMD_PDA_0x1B 0x1b #define CMD_PDA_HIGHLIGHT 0x08 #define CMD_PDA_CLEAR 0x09 #define CMD_PDA_SHIFTLINES 0x0F diff --git a/aqualink.h b/aqualink.h index c983c1c..2c7511c 100644 --- a/aqualink.h +++ b/aqualink.h @@ -97,6 +97,7 @@ struct aqualinkdata bool simulate_panel; aqledstate service_mode_state; aqledstate frz_protect_state; + unsigned char last_packet_type; //bool last_msg_was_status; //bool ar_swg_connected; }; diff --git a/aqualinkd.c b/aqualinkd.c index ef491ae..360f0f5 100644 --- a/aqualinkd.c +++ b/aqualinkd.c @@ -14,7 +14,6 @@ * https://github.com/sfeakes/aqualinkd */ - #define _GNU_SOURCE 1 // for strcasestr & strptime #define __USE_XOPEN 1 #include @@ -27,7 +26,7 @@ #include #include -#include // Need GNU_SOURCE & XOPEN defined for strptime +#include // Need GNU_SOURCE & XOPEN defined for strptime #include "mongoose.h" #include "aqualink.h" @@ -38,99 +37,101 @@ #include "aq_programmer.h" #include "net_services.h" #include "pda_menu.h" +#include "pda.h" #include "version.h" - #define DEFAULT_CONFIG_FILE "./aqualinkd.conf" - static volatile bool _keepRunning = true; static struct aqconfig _config_parameters; static struct aqualinkdata _aqualink_data; - void main_loop(); -void intHandler(int dummy) { +void intHandler(int dummy) +{ _keepRunning = false; logMessage(LOG_NOTICE, "Stopping!"); } - void processLEDstate() { - int i=0; + int i = 0; int byte; int bit; - - for (byte=0;byte<5;byte++) + + for (byte = 0; byte < 5; byte++) { - for (bit=0; bit<8; bit+=2) + for (bit = 0; bit < 8; bit += 2) { - if ( ((_aqualink_data.raw_status[byte] >> (bit+1) ) & 1) == 1 ) - _aqualink_data.aqualinkleds[i].state = FLASH; - else if ( ((_aqualink_data.raw_status[byte] >> bit) & 1) == 1 ) + if (((_aqualink_data.raw_status[byte] >> (bit + 1)) & 1) == 1) + _aqualink_data.aqualinkleds[i].state = FLASH; + else if (((_aqualink_data.raw_status[byte] >> bit) & 1) == 1) _aqualink_data.aqualinkleds[i].state = ON; else _aqualink_data.aqualinkleds[i].state = OFF; - + //logMessage(LOG_DEBUG,"Led %d state %d",i+1,_aqualink_data.aqualinkleds[i].state); i++; } } // Reset enabled state for heaters, as they take 2 led states - if ( _aqualink_data.aqualinkleds[POOL_HTR_LED_INDEX-1].state == OFF && _aqualink_data.aqualinkleds[POOL_HTR_LED_INDEX].state == ON) - _aqualink_data.aqualinkleds[POOL_HTR_LED_INDEX-1].state = ENABLE;\ - - if ( _aqualink_data.aqualinkleds[SPA_HTR_LED_INDEX-1].state == OFF && _aqualink_data.aqualinkleds[SPA_HTR_LED_INDEX].state == ON) - _aqualink_data.aqualinkleds[SPA_HTR_LED_INDEX-1].state = ENABLE; - - if ( _aqualink_data.aqualinkleds[SOLAR_HTR_LED_INDEX-1].state == OFF && _aqualink_data.aqualinkleds[SOLAR_HTR_LED_INDEX].state == ON) - _aqualink_data.aqualinkleds[SOLAR_HTR_LED_INDEX-1].state = ENABLE; -/* + if (_aqualink_data.aqualinkleds[POOL_HTR_LED_INDEX - 1].state == OFF && _aqualink_data.aqualinkleds[POOL_HTR_LED_INDEX].state == ON) + _aqualink_data.aqualinkleds[POOL_HTR_LED_INDEX - 1].state = ENABLE; + + if (_aqualink_data.aqualinkleds[SPA_HTR_LED_INDEX - 1].state == OFF && _aqualink_data.aqualinkleds[SPA_HTR_LED_INDEX].state == ON) + _aqualink_data.aqualinkleds[SPA_HTR_LED_INDEX - 1].state = ENABLE; + + if (_aqualink_data.aqualinkleds[SOLAR_HTR_LED_INDEX - 1].state == OFF && _aqualink_data.aqualinkleds[SOLAR_HTR_LED_INDEX].state == ON) + _aqualink_data.aqualinkleds[SOLAR_HTR_LED_INDEX - 1].state = ENABLE; + /* for (i=0; i < TOTAL_BUTTONS; i++) { logMessage(LOG_NOTICE, "%s = %d", _aqualink_data.aqbuttons[i].name, _aqualink_data.aqualinkleds[i].state); } */ } - bool checkAqualinkTime() { static time_t last_checked; - time_t now = time(0); // get time now + time_t now = time(0); // get time now int time_difference; struct tm aq_tm; time_t aqualink_time; - + time_difference = (int)difftime(now, last_checked); - if (time_difference < TIME_CHECK_INTERVAL) { + if (time_difference < TIME_CHECK_INTERVAL) + { logMessage(LOG_DEBUG, "time not checked, will check in %d seconds", TIME_CHECK_INTERVAL - time_difference); return true; - } else { + } + else + { last_checked = now; //return false; } - + char datestr[DATE_STRING_LEN]; strcpy(&datestr[0], _aqualink_data.date); strcpy(&datestr[12], " "); strcpy(&datestr[13], _aqualink_data.time); - - if (strptime(datestr, "%m/%d/%y %a %I:%M %p", &aq_tm) == NULL) { + + if (strptime(datestr, "%m/%d/%y %a %I:%M %p", &aq_tm) == NULL) + { logMessage(LOG_ERR, "Could not convert RS time string '%s'", datestr); last_checked = (time_t)NULL; return true; } - aq_tm.tm_isdst = -1; // Force mktime to use local timezone + aq_tm.tm_isdst = -1; // Force mktime to use local timezone aqualink_time = mktime(&aq_tm); time_difference = (int)difftime(now, aqualink_time); - - logMessage(LOG_INFO, "Aqualink time is off by %d seconds...\n", time_difference); - - if(abs(time_difference) <= ACCEPTABLE_TIME_DIFF) { + + logMessage(LOG_INFO, "Aqualink time is off by %d seconds...\n", time_difference); + + if (abs(time_difference) <= ACCEPTABLE_TIME_DIFF) + { // Time difference is less than or equal to 90 seconds (1 1/2 minutes). // Set the return value to true. return true; @@ -171,24 +172,27 @@ void queueGetProgramData() // Init string good time to get setpoints aq_programmer(AQ_GET_POOL_SPA_HEATER_TEMPS, NULL, &_aqualink_data); aq_programmer(AQ_GET_FREEZE_PROTECT_TEMP, NULL, &_aqualink_data); + if (_config_parameters.use_panel_aux_labels == true) + { + aq_programmer(AQ_GET_AUX_LABELS, NULL, &_aqualink_data); + } //aq_programmer(AQ_GET_PROGRAMS, NULL, &_aqualink_data); // only displays to log at present, also seems to confuse getting set_points } void setUnits(char *msg) { - logMessage(LOG_DEBUG, "Getting temp units from message %s, looking at %c", msg, msg[strlen(msg)-1]); + logMessage(LOG_DEBUG, "Getting temp units from message %s, looking at %c", msg, msg[strlen(msg) - 1]); - if (msg[strlen(msg)-1] == 'F') - _aqualink_data.temp_units = FAHRENHEIT; - else if (msg[strlen(msg)-1] == 'C') - _aqualink_data.temp_units = CELSIUS; + if (msg[strlen(msg) - 1] == 'F') + _aqualink_data.temp_units = FAHRENHEIT; + else if (msg[strlen(msg) - 1] == 'C') + _aqualink_data.temp_units = CELSIUS; else - _aqualink_data.temp_units = UNKNOWN; + _aqualink_data.temp_units = UNKNOWN; - logMessage(LOG_INFO, "Temp Units set to %d (F=0, C=1, Unknown=3)", _aqualink_data.temp_units); + logMessage(LOG_INFO, "Temp Units set to %d (F=0, C=1, Unknown=3)", _aqualink_data.temp_units); } - void processMessage(char *message) { char *msg; @@ -196,39 +200,42 @@ void processMessage(char *message) static bool _gotREV = false; static int freeze_msg_count = 0; static int service_msg_count = 0; - // NSF replace message with msg + // NSF replace message with msg msg = stripwhitespace(message); strcpy(_aqualink_data.last_message, msg); //_aqualink_data.last_message = _aqualink_data.message; //_aqualink_data.display_message = NULL; - + //aqualink_strcpy(_aqualink_data.message, msg); - - logMessage(LOG_INFO, "RS Message :- '%s'\n",msg); + + logMessage(LOG_INFO, "RS Message :- '%s'\n", msg); //logMessage(LOG_NOTICE, "RS Message :- '%s'\n",msg); - - // Check long messages in this if/elseif block first, as some messages are similar. + + // Check long messages in this if/elseif block first, as some messages are similar. // ie "POOL TEMP" and "POOL TEMP IS SET TO" so want correct match first. // - - if(stristr(msg, "JANDY AquaLinkRS") != NULL) { + + if (stristr(msg, "JANDY AquaLinkRS") != NULL) + { //_aqualink_data.display_message = NULL; _aqualink_data.last_display_message[0] = '\0'; } // If we have more than 10 messages without "Service Mode is active" assume it's off. - if (_aqualink_data.service_mode_state == ON && service_msg_count++ > 10) { + if (_aqualink_data.service_mode_state == ON && service_msg_count++ > 10) + { _aqualink_data.service_mode_state = OFF; service_msg_count = 0; } - + /* // If we have more than 10 messages without "Service Mode is active" assume it's off. if (_aqualink_data.service_mode_state == ON && service_msg_count++ > 10) { _aqualink_data.service_mode_state = OFF; service_msg_count = 0; } - +*/ // If we have more than 10 messages without "FREE PROTECT ACTIVATED" assume it's off. - if (_aqualink_data.frz_protect_state == ON && freeze_msg_count++ > 10) { + if (_aqualink_data.frz_protect_state == ON && freeze_msg_count++ > 10) + { _aqualink_data.frz_protect_state = ENABLE; freeze_msg_count = 0; } @@ -236,154 +243,194 @@ void processMessage(char *message) //else //_aqualink_data.display_last_message = false; - if(stristr(msg, LNG_MSG_BATTERY_LOW) != NULL) { + if (stristr(msg, LNG_MSG_BATTERY_LOW) != NULL) + { _aqualink_data.battery = LOW; strcpy(_aqualink_data.last_display_message, msg); // Also display the message on web UI } - else if(stristr(msg, LNG_MSG_POOL_TEMP_SET) != NULL) { + else if (stristr(msg, LNG_MSG_POOL_TEMP_SET) != NULL) + { //logMessage(LOG_DEBUG, "pool htr long message: %s", &message[20]); - _aqualink_data.pool_htr_set_point = atoi(message+20); + _aqualink_data.pool_htr_set_point = atoi(message + 20); if (_aqualink_data.temp_units == UNKNOWN) setUnits(msg); } - else if(stristr(msg, LNG_MSG_SPA_TEMP_SET) != NULL) { + else if (stristr(msg, LNG_MSG_SPA_TEMP_SET) != NULL) + { //logMessage(LOG_DEBUG, "spa htr long message: %s", &message[19]); - _aqualink_data.spa_htr_set_point = atoi(message+19); + _aqualink_data.spa_htr_set_point = atoi(message + 19); if (_aqualink_data.temp_units == UNKNOWN) setUnits(msg); } - else if(stristr(msg, LNG_MSG_FREEZE_PROTECTION_SET) != NULL) { + else if (stristr(msg, LNG_MSG_FREEZE_PROTECTION_SET) != NULL) + { //logMessage(LOG_DEBUG, "frz protect long message: %s", &message[28]); - _aqualink_data.frz_protect_set_point = atoi(message+28); + _aqualink_data.frz_protect_set_point = atoi(message + 28); _aqualink_data.frz_protect_state = ENABLE; if (_aqualink_data.temp_units == UNKNOWN) setUnits(msg); } - else if(strncasecmp(msg, MSG_AIR_TEMP, MSG_AIR_TEMP_LEN) == 0) { - _aqualink_data.air_temp = atoi(msg+MSG_AIR_TEMP_LEN); - - if (_aqualink_data.temp_units == UNKNOWN) - setUnits(msg); - } - else if(strncasecmp(msg, MSG_POOL_TEMP, MSG_POOL_TEMP_LEN) == 0) { - _aqualink_data.pool_temp = atoi(msg+MSG_POOL_TEMP_LEN); + else if (strncasecmp(msg, MSG_AIR_TEMP, MSG_AIR_TEMP_LEN) == 0) + { + _aqualink_data.air_temp = atoi(msg + MSG_AIR_TEMP_LEN); if (_aqualink_data.temp_units == UNKNOWN) setUnits(msg); } - else if(strncasecmp(msg, MSG_SPA_TEMP, MSG_SPA_TEMP_LEN) == 0) { - _aqualink_data.spa_temp = atoi(msg+MSG_SPA_TEMP_LEN); + else if (strncasecmp(msg, MSG_POOL_TEMP, MSG_POOL_TEMP_LEN) == 0) + { + _aqualink_data.pool_temp = atoi(msg + MSG_POOL_TEMP_LEN); + + if (_aqualink_data.temp_units == UNKNOWN) + setUnits(msg); + } + else if (strncasecmp(msg, MSG_SPA_TEMP, MSG_SPA_TEMP_LEN) == 0) + { + _aqualink_data.spa_temp = atoi(msg + MSG_SPA_TEMP_LEN); if (_aqualink_data.temp_units == UNKNOWN) setUnits(msg); } // NSF If get water temp rather than pool or spa in some cases, then we are in Pool OR Spa ONLY mode - else if(strncasecmp(msg, MSG_WATER_TEMP, MSG_WATER_TEMP_LEN) == 0) { - _aqualink_data.pool_temp = atoi(msg+MSG_WATER_TEMP_LEN); - _aqualink_data.spa_temp = atoi(msg+MSG_WATER_TEMP_LEN); + else if (strncasecmp(msg, MSG_WATER_TEMP, MSG_WATER_TEMP_LEN) == 0) + { + _aqualink_data.pool_temp = atoi(msg + MSG_WATER_TEMP_LEN); + _aqualink_data.spa_temp = atoi(msg + MSG_WATER_TEMP_LEN); if (_aqualink_data.temp_units == UNKNOWN) setUnits(msg); - - if (_aqualink_data.single_device != true) { + + if (_aqualink_data.single_device != true) + { _aqualink_data.single_device = true; - logMessage(LOG_NOTICE, "AqualinkD set to 'Pool OR Spa Only' mode\n"); + logMessage(LOG_NOTICE, "AqualinkD set to 'Pool OR Spa Only' mode\n"); } } - else if(stristr(msg, LNG_MSG_WATER_TEMP1_SET) != NULL) { - _aqualink_data.pool_htr_set_point = atoi(message+28); + else if (stristr(msg, LNG_MSG_WATER_TEMP1_SET) != NULL) + { + _aqualink_data.pool_htr_set_point = atoi(message + 28); if (_aqualink_data.temp_units == UNKNOWN) setUnits(msg); - if (_aqualink_data.single_device != true) { + if (_aqualink_data.single_device != true) + { _aqualink_data.single_device = true; - logMessage(LOG_NOTICE, "AqualinkD set to 'Pool OR Spa Only' mode\n"); + logMessage(LOG_NOTICE, "AqualinkD set to 'Pool OR Spa Only' mode\n"); } } - else if(stristr(msg, LNG_MSG_WATER_TEMP2_SET) != NULL) { - _aqualink_data.spa_htr_set_point = atoi(message+27); + else if (stristr(msg, LNG_MSG_WATER_TEMP2_SET) != NULL) + { + _aqualink_data.spa_htr_set_point = atoi(message + 27); if (_aqualink_data.temp_units == UNKNOWN) setUnits(msg); - if (_aqualink_data.single_device != true) { + if (_aqualink_data.single_device != true) + { _aqualink_data.single_device = true; - logMessage(LOG_NOTICE, "AqualinkD set to 'Pool OR Spa Only' mode\n"); - } + logMessage(LOG_NOTICE, "AqualinkD set to 'Pool OR Spa Only' mode\n"); + } } - else if (stristr(msg, LNG_MSG_SERVICE_ACTIVE) != NULL) { + else if (stristr(msg, LNG_MSG_SERVICE_ACTIVE) != NULL) + { if (_aqualink_data.service_mode_state == OFF) - logMessage(LOG_NOTICE, "AqualinkD set to Service Mode\n"); + logMessage(LOG_NOTICE, "AqualinkD set to Service Mode\n"); _aqualink_data.service_mode_state = ON; service_msg_count = 0; } - else if (stristr(msg, LNG_MSG_FREEZE_PROTECTION_ACTIVATED) != NULL) { + else if (stristr(msg, LNG_MSG_FREEZE_PROTECTION_ACTIVATED) != NULL) + { _aqualink_data.frz_protect_state = ON; freeze_msg_count = 0; strcpy(_aqualink_data.last_display_message, msg); // Also display the message on web UI } - else if(msg[2] == '/' && msg[5] == '/' && msg[8] == ' ') {// date in format '08/29/16 MON' + else if (msg[2] == '/' && msg[5] == '/' && msg[8] == ' ') + { // date in format '08/29/16 MON' strcpy(_aqualink_data.date, msg); } - else if(strncasecmp(msg, MSG_SWG_PCT, MSG_SWG_PCT_LEN) == 0) { - _aqualink_data.swg_percent = atoi(msg+MSG_SWG_PCT_LEN); + else if (strncasecmp(msg, MSG_SWG_PCT, MSG_SWG_PCT_LEN) == 0) + { + _aqualink_data.swg_percent = atoi(msg + MSG_SWG_PCT_LEN); //logMessage(LOG_DEBUG, "Stored SWG Percent as %d\n", _aqualink_data.swg_percent); } - else if(strncasecmp(msg, MSG_SWG_PPM, MSG_SWG_PPM_LEN) == 0) { - _aqualink_data.swg_ppm = atoi(msg+MSG_SWG_PPM_LEN); + else if (strncasecmp(msg, MSG_SWG_PPM, MSG_SWG_PPM_LEN) == 0) + { + _aqualink_data.swg_ppm = atoi(msg + MSG_SWG_PPM_LEN); //logMessage(LOG_DEBUG, "Stored SWG PPM as %d\n", _aqualink_data.swg_ppm); } - else if( (msg[1] == ':' || msg[2] == ':') && msg[strlen(msg)-1] == 'M') { // time in format '9:45 AM' + else if ((msg[1] == ':' || msg[2] == ':') && msg[strlen(msg) - 1] == 'M') + { // time in format '9:45 AM' strcpy(_aqualink_data.time, msg); // Setting time takes a long time, so don't try until we have all other programmed data. - if ( (_initWithRS == true) && strlen(_aqualink_data.date) > 1 && checkAqualinkTime() != true ) { + if ((_initWithRS == true) && strlen(_aqualink_data.date) > 1 && checkAqualinkTime() != true) + { logMessage(LOG_NOTICE, "RS time is NOT accurate '%s %s', re-setting on controller!\n", _aqualink_data.time, _aqualink_data.date); aq_programmer(AQ_SET_TIME, NULL, &_aqualink_data); - } else { + } + else + { logMessage(LOG_DEBUG, "RS time is accurate '%s %s'\n", _aqualink_data.time, _aqualink_data.date); } // If we get a time message before REV, the controller didn't see us as we started too quickly. - if ( _gotREV == false ) { - logMessage(LOG_NOTICE, "Getting control panel information\n",msg); + if (_gotREV == false) + { + logMessage(LOG_NOTICE, "Getting control panel information\n", msg); aq_programmer(AQ_GET_DIAGNOSTICS_MODEL, NULL, &_aqualink_data); _gotREV = true; // Force it to true just incase we don't understand the model# } } - else if(strstr(msg, " REV ") != NULL) { // '8157 REV MMM' + else if (strstr(msg, " REV ") != NULL) + { // '8157 REV MMM' // A master firmware revision message. strcpy(_aqualink_data.version, msg); _gotREV = true; - logMessage(LOG_NOTICE, "Control Panel %s\n",msg); - if ( _initWithRS == false) { + logMessage(LOG_NOTICE, "Control Panel %s\n", msg); + if (_initWithRS == false) + { queueGetProgramData(); _initWithRS = true; } } - else if(stristr(msg, " TURNS ON") != NULL) { - logMessage(LOG_NOTICE, "Program data '%s'\n",msg); + else if (stristr(msg, " TURNS ON") != NULL) + { + logMessage(LOG_NOTICE, "Program data '%s'\n", msg); } - else if(_config_parameters.override_freeze_protect == TRUE && - strncasecmp(msg, "Press Enter* to override Freeze Protection with", 47) == 0 ){ + else if (_config_parameters.override_freeze_protect == TRUE && strncasecmp(msg, "Press Enter* to override Freeze Protection with", 47) == 0) + { //send_cmd(KEY_ENTER, aq_data); aq_programmer(AQ_SEND_CMD, (char *)KEY_ENTER, &_aqualink_data); } - else { - logMessage(LOG_DEBUG_SERIAL, "Ignoring '%s'\n",msg); + else if ((msg[4] == ':') && (strncasecmp(msg, "AUX", 3) == 0)) + { // AUX label "AUX1:" + int labelid = atoi(msg + 3); + if (labelid > 0 && _config_parameters.use_panel_aux_labels == true) + { + // Aux1: on panel = Button 3 in aqualinkd (button 2 in array) + logMessage(LOG_NOTICE, "AUX LABEL %d '%s'\n", labelid + 1, msg); + _aqualink_data.aqbuttons[labelid+1].label = prittyString(cleanalloc(msg+5)); + //_aqualink_data.aqbuttons[labelid + 1].label = cleanalloc(msg + 5); + } + } + else + { + logMessage(LOG_DEBUG_SERIAL, "Ignoring '%s'\n", msg); //_aqualink_data.display_message = msg; if (_aqualink_data.active_thread.thread_id == 0 && stristr(msg, "JANDY AquaLinkRS") == NULL && stristr(msg, "PUMP O") == NULL /*&& // Catch 'PUMP ON' and 'PUMP OFF' but not 'PUMP WILL TURN ON' stristr(msg, "CLEANER O") == NULL && stristr(msg, "SPA O") == NULL && - stristr(msg, "AUX") == NULL*/) { // Catch all AUX1 AUX5 messages + stristr(msg, "AUX") == NULL*/ + ) + { // Catch all AUX1 AUX5 messages //_aqualink_data.display_last_message = true; strcpy(_aqualink_data.last_display_message, msg); } } - + // Send every message if we are in simulate panel mode if (_aqualink_data.simulate_panel) ascii(_aqualink_data.last_display_message, msg); @@ -393,285 +440,43 @@ void processMessage(char *message) } -void set_pda_led(struct aqualinkled *led, char state) -{ - if (state == 'N') - led->state = ON; - else if (state == 'A') - led->state = ENABLE; - else if (state == '*') - led->state = FLASH; - else - led->state = OFF; -} - -void pass_pda_equiptment_status_item(char* msg) -{ - static char *index; - int i; - - // EQUIPMENT STATUS - // - // AquaPure 100% - // SALT 25500 PPM - // FILTER PUMP - // POOL HEAT - // SPA HEAT ENA - - // Check message for status of device - // Loop through all buttons and match the PDA text. - if ((index = strcasestr(msg, MSG_SWG_PCT)) != NULL) - { - //int aq = atoi(index + strlen(MSG_SWG_PCT)); - //printf("Aquapure PERCENT message %d\n", aq); - _aqualink_data.swg_percent = atoi(index + strlen(MSG_SWG_PCT)); - } - else if ((index = strcasestr(msg, MSG_SWG_PPM)) != NULL) - { - //int aq = atoi(index + strlen(MSG_SWG_PPM)); - //printf("Aquapure SALT message %d\n", aq); - _aqualink_data.swg_ppm = atoi(index + strlen(MSG_SWG_PPM)); - } - else - { - char labelBuff[AQ_MSGLEN + 1]; - strncpy (labelBuff, msg, AQ_MSGLEN + 1); - msg = stripwhitespace (labelBuff); - - // These are listed as " FILTER PUMP " - - if (strcasecmp (msg, "POOL HEAT ENA") == 0) - { - _aqualink_data.aqbuttons[POOL_HEAT_INDEX].led->state = ENABLE; - } - else if (strcasecmp (msg, "SPA HEAT ENA") == 0) - { - _aqualink_data.aqbuttons[SPA_HEAT_INDEX].led->state = ENABLE; - } - else - { - for (i = 0; i < TOTAL_BUTTONS; i++) - { - if (strcasecmp (msg, _aqualink_data.aqbuttons[i].pda_label) == 0) - { - logMessage (LOG_DEBUG, "*** Found Status for %s = '%.*s'\n", - _aqualink_data.aqbuttons[i].pda_label, AQ_MSGLEN, - msg); - // It's on (or delayed) if it's listed here. - if (_aqualink_data.aqbuttons[i].led->state != FLASH) - { - _aqualink_data.aqbuttons[i].led->state = ON; - } - break; - } - } - } - } -} - -bool process_pda_packet(unsigned char* packet, int length) -{ - bool rtn = true; - int i; - char *msg; - static bool init = false; - static time_t _lastStatus; - - process_pda_menu_packet(packet, length); - - // NSF. - - //_aqualink_data.last_msg_was_status = false; - - //debugPacketPrint(0x00, packet, length); - - switch (packet[PKT_CMD]) { - - case CMD_ACK: - logMessage(LOG_DEBUG, "RS Received ACK length %d.\n",length); - break; - - case CMD_STATUS: - _aqualink_data.last_display_message[0] = '\0'; - /* - if (!init) { - aq_programmer(AQ_PDA_INIT, NULL, &_aqualink_data); - init=true; - }*/ - - // If we get a status packet, and we are on the status menu, this is a list of what's on - // or pending so unless flash turn everything off, and just turn on items that are listed. - // This is the only way to update a device that's been turned off by a real PDA / keypad. - if (pda_m_type() == PM_EQUIPTMENT_STATUS) { - //printf("*** SET ALL OFF ****\n"); - for (i = 0; i < TOTAL_BUTTONS; i++) { - if (_aqualink_data.aqbuttons[i].led->state != FLASH) { - _aqualink_data.aqbuttons[i].led->state = OFF; - } - } - for (i = 1; i < PDA_LINES; i++) { - pass_pda_equiptment_status_item(pda_m_line(i)); - } - time(&_lastStatus); - } else { - time_t now; - time(&now); - if (init && difftime(now, _lastStatus) > 60){ - logMessage(LOG_DEBUG,"OVER 60 SECONDS SINCE LAST STATUS UPDATE, forcing refresh\n"); - // Reset aquapure to nothing since it must be off at this point - _aqualink_data.pool_temp = TEMP_UNKNOWN; - _aqualink_data.spa_temp = TEMP_UNKNOWN; - time(&_lastStatus); - aq_programmer(AQ_PDA_DEVICE_STATUS, NULL, &_aqualink_data); - } - } - break; - case CMD_MSG_LONG: { - msg = (char*)packet+PKT_DATA+1; - - if (packet[PKT_DATA] == 0x82) { // Air & Water temp is always this ID - // 'AIR POOL' - // ' 86` 86` ' - // 'AIR SPA ' - // ' 86` 86` ' - // 'AIR ' - // ' 86` ' - _aqualink_data.temp_units = FAHRENHEIT; // Force FAHRENHEIT - if (stristr(pda_m_line(1), "AIR") != NULL) - _aqualink_data.air_temp = atoi(msg); - if (stristr(pda_m_line(1), "SPA") != NULL) { - _aqualink_data.spa_temp = atoi(msg+4); - _aqualink_data.pool_temp = TEMP_UNKNOWN; - } else if (stristr(pda_m_line(1), "POOL") != NULL) { - _aqualink_data.pool_temp = atoi(msg+7); - _aqualink_data.spa_temp = TEMP_UNKNOWN; - } - //printf("Air Temp = %d | Water Temp = %d\n",atoi(msg),atoi(msg+7)); - } else if (packet[PKT_DATA] == 0x40) { // Time is always on this ID - // message " SAT 8:46AM " - // " SAT 10:29AM" - // " SAT 4:23PM " - //printf("TIME = '%.*s'\n",AQ_MSGLEN,msg ); - //printf("TIME = '%c'\n",msg[AQ_MSGLEN-1] ); - if (msg[AQ_MSGLEN-1] == ' ') { - strncpy(_aqualink_data.time, msg+9, 6); - } else { - strncpy(_aqualink_data.time, msg+9, 7); - } - strncpy(_aqualink_data.date, msg+5,3); - // NSF Come back and change the above to correctly check date and time in future -// If it wasn't a specific msg, (above) then run through and see what kind of message it is depending on the PDA menu - } else if (pda_m_type() == PM_EQUIPTMENT_STATUS) { - pass_pda_equiptment_status_item(msg); - } else if (pda_m_type() == PM_EQUIPTMENT_CONTROL) { - // These are listed as "FILTER PUMP OFF" - char labelBuff[AQ_MSGLEN+1]; - strncpy(labelBuff, msg, AQ_MSGLEN-4); - labelBuff[AQ_MSGLEN-4] = 0; - - for (i = 0; i < TOTAL_BUTTONS; i++) { - if (strcasecmp(stripwhitespace(labelBuff), _aqualink_data.aqbuttons[i].pda_label) == 0) { - logMessage(LOG_DEBUG, "*** Found EQ CTL Status for %s = '%.*s'\n",_aqualink_data.aqbuttons[i].pda_label, AQ_MSGLEN, msg); - set_pda_led(_aqualink_data.aqbuttons[i].led, msg[AQ_MSGLEN-1]); - } - } - } else if (pda_m_type() == PM_MAIN || pda_m_type() == PM_BUILDING_MAIN) { - if (stristr(msg, "POOL MODE") != NULL) { - // If pool mode is on the filter pump is on but if it is off the filter pump might be on if spa mode is on. - if (msg[AQ_MSGLEN-1] == 'N') { - _aqualink_data.aqbuttons[PUMP_INDEX].led->state = ON; - } else if (msg[AQ_MSGLEN-1] == '*') { - _aqualink_data.aqbuttons[PUMP_INDEX].led->state = FLASH; - } - }else if (stristr(msg, "POOL HEATER") != NULL) { - set_pda_led(_aqualink_data.aqbuttons[POOL_HEAT_INDEX].led, msg[AQ_MSGLEN-1]); - } - else if (stristr(msg, "SPA MODE") != NULL) { - // when SPA mode is on the filter may be on or pending - if (msg[AQ_MSGLEN - 1] == 'N') { - _aqualink_data.aqbuttons[PUMP_INDEX].led->state = ON; - _aqualink_data.aqbuttons[SPA_INDEX].led->state = ON; - } - else if (msg[AQ_MSGLEN - 1] == '*') - { - _aqualink_data.aqbuttons[PUMP_INDEX].led->state = FLASH; - _aqualink_data.aqbuttons[SPA_INDEX].led->state = ON; - } - else - { - _aqualink_data.aqbuttons[SPA_INDEX].led->state = OFF; - } - } - else if (stristr(msg, "SPA HEATER") != NULL) - { - set_pda_led(_aqualink_data.aqbuttons[SPA_HEAT_INDEX].led, msg[AQ_MSGLEN-1]); - } - } else if (pda_m_type() == PM_UNKNOWN) { - // Lets make a guess here and just see if there is an ON/OFF/ENA/*** at the end of the line - // When you turn on/off a piece of equiptment, a clear screen followed by single message is sent. - // So we are not in any PDA menu, try to catch that message here so we catch new device state ASAP. - if ( msg[AQ_MSGLEN-1] == 'N' || msg[AQ_MSGLEN-1] == 'F' || msg[AQ_MSGLEN-1] == 'A' || msg[AQ_MSGLEN-1] == '*') { - for (i = 0; i < TOTAL_BUTTONS; i++) { - if (stristr(msg, _aqualink_data.aqbuttons[i].pda_label) != NULL) { - //printf("*** Found Status for %s = '%.*s'\n",_aqualink_data.aqbuttons[i].pda_label, AQ_MSGLEN, msg); - set_pda_led(_aqualink_data.aqbuttons[i].led, msg[AQ_MSGLEN-1]); - } - } - } - } - // If we haven't initilixed and we are on line 4, then initilize - if (! init && pda_m_hlightindex() == 4) { - //printf("** INITILIZE ADD LINE BACK\n"); - aq_programmer(AQ_PDA_INIT, NULL, &_aqualink_data); - time(&_lastStatus); - init = true; - } - - } - //printf("** Line index='%d' Highligh='%s' Message='%.*s'\n",pda_m_hlightindex(), pda_m_hlight(), AQ_MSGLEN, msg); - logMessage(LOG_INFO,"PDA Menu '%d' Selectedline '%s', Last line received '%.*s'\n", pda_m_type(), pda_m_hlight(), AQ_MSGLEN, msg); - break; - } - - if ( packet[PKT_CMD] == CMD_MSG_LONG || - packet[PKT_CMD] == CMD_PDA_HIGHLIGHT || - packet[PKT_CMD] == CMD_PDA_SHIFTLINES) { - // We processed the next message, kick any threads waiting on the message. - kick_aq_program_thread(&_aqualink_data); - } - return rtn; -} - -bool process_packet(unsigned char* packet, int length) +bool process_packet(unsigned char *packet, int length) { bool rtn = false; static unsigned char last_packet[AQ_MAXPKTLEN]; - static char message[AQ_MSGLONGLEN+1]; + static char message[AQ_MSGLONGLEN + 1]; static int processing_long_msg = 0; - + // Check packet against last check if different. - if (memcmp(packet, last_packet, length) == 0) { - logMessage(LOG_DEBUG_SERIAL, "RS Received duplicate, ignoring.\n",length); + if (memcmp(packet, last_packet, length) == 0) + { + logMessage(LOG_DEBUG_SERIAL, "RS Received duplicate, ignoring.\n", length); return rtn; - } else { + } + else + { memcpy(last_packet, packet, length); + _aqualink_data.last_packet_type = packet[PKT_CMD]; rtn = true; } - - if (_config_parameters.pda_mode == true) { + + if (_config_parameters.pda_mode == true) + { return process_pda_packet(packet, length); } - - if (processing_long_msg > 0 && packet[PKT_CMD] != CMD_MSG_LONG) { + + if (processing_long_msg > 0 && packet[PKT_CMD] != CMD_MSG_LONG) + { processing_long_msg = 0; //logMessage(LOG_ERR, "RS failed to receive complete long message, received '%s'\n",message); //logMessage(LOG_DEBUG, "RS didn't finished receiving of MSG_LONG '%s'\n",message); processMessage(message); } - - switch (packet[PKT_CMD]) { + + switch (packet[PKT_CMD]) + { case CMD_ACK: - logMessage(LOG_DEBUG, "RS Received ACK length %d.\n",length); + logMessage(LOG_DEBUG, "RS Received ACK length %d.\n", length); break; case CMD_STATUS: logMessage(LOG_DEBUG, "RS Received STATUS length %d.\n", length); @@ -694,8 +499,8 @@ bool process_packet(unsigned char* packet, int length) } break; case CMD_MSG: - memset(message, 0, AQ_MSGLONGLEN+1); - strncpy(message, (char*)packet+PKT_DATA+1, AQ_MSGLEN); + memset(message, 0, AQ_MSGLONGLEN + 1); + strncpy(message, (char *)packet + PKT_DATA + 1, AQ_MSGLEN); //logMessage(LOG_DEBUG_SERIAL, "RS Received message '%s'\n",message); if (packet[PKT_DATA] == 1) // Start of long message, get them all before processing { @@ -707,26 +512,27 @@ bool process_packet(unsigned char* packet, int length) break; case CMD_MSG_LONG: // First in sequence is normal message. - processing_long_msg++; - strncpy(&message[processing_long_msg*AQ_MSGLEN], (char*)packet+PKT_DATA+1, AQ_MSGLEN); - //logMessage(LOG_DEBUG_SERIAL, "RS Received long message '%s'\n",message); - if (processing_long_msg == 3) { - //logMessage(LOG_DEBUG, "RS Finished receiving of MSG_LONG '%s'\n",message); - processMessage(message); - processing_long_msg=0; - } + processing_long_msg++; + strncpy(&message[processing_long_msg * AQ_MSGLEN], (char *)packet + PKT_DATA + 1, AQ_MSGLEN); + //logMessage(LOG_DEBUG_SERIAL, "RS Received long message '%s'\n",message); + if (processing_long_msg == 3) + { + //logMessage(LOG_DEBUG, "RS Finished receiving of MSG_LONG '%s'\n",message); + processMessage(message); + processing_long_msg = 0; + } break; case CMD_PROBE: - logMessage(LOG_DEBUG, "RS Received PROBE length %d.\n",length); + logMessage(LOG_DEBUG, "RS Received PROBE length %d.\n", length); //logMessage(LOG_INFO, "Synch'ing with Aqualink master device...\n"); rtn = false; break; default: - logMessage(LOG_INFO, "RS Received unknown packet, 0x%02hhx\n",packet[PKT_CMD]); + logMessage(LOG_INFO, "RS Received unknown packet, 0x%02hhx\n", packet[PKT_CMD]); rtn = false; break; } - + return rtn; } @@ -739,42 +545,64 @@ void action_delayed_request() if (_aqualink_data.temp_units == UNKNOWN && _aqualink_data.unactioned.type != SWG_SETPOINT) return; - if (_aqualink_data.unactioned.type == POOL_HTR_SETOINT) { + if (_aqualink_data.unactioned.type == POOL_HTR_SETOINT) + { _aqualink_data.unactioned.value = setpoint_check(POOL_HTR_SETOINT, _aqualink_data.unactioned.value, &_aqualink_data); - if ( _aqualink_data.pool_htr_set_point != _aqualink_data.unactioned.value ) { + if (_aqualink_data.pool_htr_set_point != _aqualink_data.unactioned.value) + { aq_programmer(AQ_SET_POOL_HEATER_TEMP, sval, &_aqualink_data); - logMessage(LOG_NOTICE, "Setting pool heater setpoint to %d\n",_aqualink_data.unactioned.value); - } else { - logMessage(LOG_NOTICE, "Pool heater setpoint is already %d, not changing\n",_aqualink_data.unactioned.value); + logMessage(LOG_NOTICE, "Setting pool heater setpoint to %d\n", _aqualink_data.unactioned.value); } - } else if (_aqualink_data.unactioned.type == SPA_HTR_SETOINT) { + else + { + logMessage(LOG_NOTICE, "Pool heater setpoint is already %d, not changing\n", _aqualink_data.unactioned.value); + } + } + else if (_aqualink_data.unactioned.type == SPA_HTR_SETOINT) + { _aqualink_data.unactioned.value = setpoint_check(SPA_HTR_SETOINT, _aqualink_data.unactioned.value, &_aqualink_data); - if ( _aqualink_data.spa_htr_set_point != _aqualink_data.unactioned.value ) { + if (_aqualink_data.spa_htr_set_point != _aqualink_data.unactioned.value) + { aq_programmer(AQ_SET_SPA_HEATER_TEMP, sval, &_aqualink_data); - logMessage(LOG_NOTICE, "Setting spa heater setpoint to %d\n",_aqualink_data.unactioned.value); - } else { - logMessage(LOG_NOTICE, "Spa heater setpoint is already %d, not changing\n",_aqualink_data.unactioned.value); + logMessage(LOG_NOTICE, "Setting spa heater setpoint to %d\n", _aqualink_data.unactioned.value); } - } else if (_aqualink_data.unactioned.type == FREEZE_SETPOINT) { + else + { + logMessage(LOG_NOTICE, "Spa heater setpoint is already %d, not changing\n", _aqualink_data.unactioned.value); + } + } + else if (_aqualink_data.unactioned.type == FREEZE_SETPOINT) + { _aqualink_data.unactioned.value = setpoint_check(FREEZE_SETPOINT, _aqualink_data.unactioned.value, &_aqualink_data); - if ( _aqualink_data.frz_protect_set_point != _aqualink_data.unactioned.value ) { + if (_aqualink_data.frz_protect_set_point != _aqualink_data.unactioned.value) + { aq_programmer(AQ_SET_FRZ_PROTECTION_TEMP, sval, &_aqualink_data); - logMessage(LOG_NOTICE, "Setting freeze protect to %d\n",_aqualink_data.unactioned.value); - } else { - logMessage(LOG_NOTICE, "Freeze setpoint is already %d, not changing\n",_aqualink_data.unactioned.value); + logMessage(LOG_NOTICE, "Setting freeze protect to %d\n", _aqualink_data.unactioned.value); } - } else if (_aqualink_data.unactioned.type == SWG_SETPOINT) { + else + { + logMessage(LOG_NOTICE, "Freeze setpoint is already %d, not changing\n", _aqualink_data.unactioned.value); + } + } + else if (_aqualink_data.unactioned.type == SWG_SETPOINT) + { _aqualink_data.unactioned.value = setpoint_check(SWG_SETPOINT, _aqualink_data.unactioned.value, &_aqualink_data); - if (_aqualink_data.ar_swg_status == SWG_STATUS_OFF ) { + if (_aqualink_data.ar_swg_status == SWG_STATUS_OFF) + { // SWG is off, can't set %, so delay the set until it's on. _aqualink_data.swg_delayed_percent = _aqualink_data.unactioned.value; - } else { - if ( _aqualink_data.swg_percent != _aqualink_data.unactioned.value ) { + } + else + { + if (_aqualink_data.swg_percent != _aqualink_data.unactioned.value) + { aq_programmer(AQ_SET_SWG_PERCENT, sval, &_aqualink_data); - logMessage(LOG_NOTICE, "Setting SWG %% to %d\n",_aqualink_data.unactioned.value); - } else { - logMessage(LOG_NOTICE, "SWG % is already %d, not changing\n",_aqualink_data.unactioned.value); - } + logMessage(LOG_NOTICE, "Setting SWG %% to %d\n", _aqualink_data.unactioned.value); + } + else + { + logMessage(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. _aqualink_data.swg_percent = _aqualink_data.unactioned.value; @@ -785,20 +613,22 @@ void action_delayed_request() _aqualink_data.unactioned.requested = 0; } -int main(int argc, char *argv[]) { +int main(int argc, char *argv[]) +{ // main_loop (); int i; char *cfgFile = DEFAULT_CONFIG_FILE; int cmdln_loglevel = -1; - + bool cmdln_debugRS485 = false; // struct lws_context_creation_info info; // Log only NOTICE messages and above. Debug and info messages // will not be logged to syslog. setlogmask(LOG_UPTO(LOG_NOTICE)); - if (getuid() != 0) { + if (getuid() != 0) + { //logMessage(LOG_ERR, "%s Can only be run as root\n", argv[0]); fprintf(stderr, "ERROR %s Can only be run as root\n", argv[0]); return EXIT_FAILURE; @@ -807,14 +637,24 @@ int main(int argc, char *argv[]) { // Initialize the daemon's parameters. init_parameters(&_config_parameters); - for (i = 1; i < argc; i++) { - if (strcmp(argv[i], "-d") == 0) { + for (i = 1; i < argc; i++) + { + if (strcmp(argv[i], "-d") == 0) + { _config_parameters.deamonize = false; - } else if (strcmp(argv[i], "-c") == 0) { + } + else if (strcmp(argv[i], "-c") == 0) + { cfgFile = argv[++i]; - } else if (strcmp(argv[i], "-v") == 0) { + } + else if (strcmp(argv[i], "-v") == 0) + { cmdln_loglevel = LOG_DEBUG; } + else if (strcmp(argv[i], "-rsd") == 0) + { + cmdln_debugRS485 = true; + } } initButtons(&_aqualink_data); @@ -824,6 +664,9 @@ int main(int argc, char *argv[]) { if (cmdln_loglevel != -1) _config_parameters.log_level = cmdln_loglevel; + if (cmdln_debugRS485) + _config_parameters.debug_RSProtocol_packets = true; + setLoggingPrms(_config_parameters.log_level, _config_parameters.deamonize, _config_parameters.log_file); logMessage(LOG_NOTICE, "%s v%s\n", AQUALINKD_NAME, AQUALINKD_VERSION); @@ -834,6 +677,7 @@ int main(int argc, char *argv[]) { logMessage(LOG_NOTICE, "Config web_directory = %s\n", _config_parameters.web_directory); logMessage(LOG_NOTICE, "Config device_id = 0x%02hhx\n", _config_parameters.device_id); logMessage(LOG_NOTICE, "Config read_all_devices = %s\n", bool2text(_config_parameters.read_all_devices)); + logMessage(LOG_NOTICE, "Config use_aux_labels = %s\n", bool2text(_config_parameters.use_panel_aux_labels)); logMessage(LOG_NOTICE, "Config override frz prot = %s\n", bool2text(_config_parameters.override_freeze_protect)); #ifndef MG_DISABLE_MQTT logMessage(LOG_NOTICE, "Config mqtt_server = %s\n", _config_parameters.mqtt_server); @@ -849,6 +693,8 @@ int main(int argc, char *argv[]) { logMessage(LOG_NOTICE, "Config idx SWG Percent = %d\n", _config_parameters.dzidx_swg_percent); logMessage(LOG_NOTICE, "Config idx SWG PPM = %d\n", _config_parameters.dzidx_swg_ppm); logMessage(LOG_NOTICE, "Config PDA Mode = %s\n", bool2text(_config_parameters.pda_mode)); + logMessage(LOG_NOTICE, "Config PDA Sleep Mode = %s\n", bool2text(_config_parameters.pda_sleep_mode)); + logMessage(LOG_NOTICE, "Config force SWG = %s\n", bool2text(_config_parameters.force_swg)); /* removed until domoticz has a better virtual thermostat logMessage(LOG_NOTICE, "Config idx pool thermostat = %d\n", _config_parameters.dzidx_pool_thermostat); logMessage(LOG_NOTICE, "Config idx spa thermostat = %d\n", _config_parameters.dzidx_spa_thermostat); @@ -857,60 +703,72 @@ int main(int argc, char *argv[]) { logMessage(LOG_NOTICE, "Config deamonize = %s\n", bool2text(_config_parameters.deamonize)); logMessage(LOG_NOTICE, "Config log_file = %s\n", _config_parameters.log_file); logMessage(LOG_NOTICE, "Config light_pgm_mode = %.2f\n", _config_parameters.light_programming_mode); + logMessage(LOG_NOTICE, "Debug RS485 protocol = %s\n", bool2text(_config_parameters.debug_RSProtocol_packets)); // logMessage (LOG_NOTICE, "Config serial_port = %s\n", config_parameters->serial_port); - for (i=0; i < TOTAL_BUTONS; i++) { - logMessage(LOG_NOTICE, "Config BTN %-13s = label %-15s | PDAlabel %-15s | dzidx %d\n", _aqualink_data.aqbuttons[i].name, _aqualink_data.aqbuttons[i].label , _aqualink_data.aqbuttons[i].pda_label, _aqualink_data.aqbuttons[i].dz_idx); + for (i = 0; i < TOTAL_BUTONS; i++) + { + logMessage(LOG_NOTICE, "Config BTN %-13s = label %-15s | PDAlabel %-15s | dzidx %d\n", _aqualink_data.aqbuttons[i].name, _aqualink_data.aqbuttons[i].label, _aqualink_data.aqbuttons[i].pda_label, _aqualink_data.aqbuttons[i].dz_idx); //logMessage(LOG_NOTICE, "Button %d\n", i+1, _aqualink_data.aqbuttons[i].label , _aqualink_data.aqbuttons[i].dz_idx); } - if (_config_parameters.deamonize == true) { + if (_config_parameters.deamonize == true) + { char pidfile[256]; // sprintf(pidfile, "%s/%s.pid",PIDLOCATION, basename(argv[0])); sprintf(pidfile, "%s/%s.pid", "/run", basename(argv[0])); daemonise(pidfile, main_loop); - } else { + } + else + { main_loop(); } exit(EXIT_SUCCESS); } - void debugPacketPrint(unsigned char ID, unsigned char *packet_buffer, int packet_length) { char buff[1000]; - int i=0; + int i = 0; int cnt = 0; - cnt = sprintf(buff, "%4.4s 0x%02hhx of type %8.8s", (packet_buffer[PKT_DEST]==0x00?"From":"To"), ID, get_packet_type(packet_buffer, packet_length)); - cnt += sprintf(buff+cnt, " | HEX: "); + cnt = sprintf(buff, "%4.4s 0x%02hhx of type %8.8s", (packet_buffer[PKT_DEST] == 0x00 ? "From" : "To"), ID, get_packet_type(packet_buffer, packet_length)); + cnt += sprintf(buff + cnt, " | HEX: "); //printHex(packet_buffer, packet_length); - for (i=0;i 0) { + // Send command and jump directly "busy but can receive message" + send_ack(rs_fd, pop_aq_cmd(&_aqualink_data)); + delayAckCnt = MAX_BUSY_ACK; // need to test jumping to MAX_BUSY_ACK here + } else { + logMessage(LOG_NOTICE, "Sending display busy due to Simulator mode \n"); + if (delayAckCnt < MAX_BLOCK_ACK) // block all incomming messages + send_extended_ack(rs_fd, ACK_SCREEN_BUSY_BLOCK, pop_aq_cmd(&_aqualink_data)); + else if (delayAckCnt < MAX_BUSY_ACK) // say we are pausing + send_extended_ack(rs_fd, ACK_SCREEN_BUSY, pop_aq_cmd(&_aqualink_data)); + else // We timed out pause, send normal ack (This should also reset the display message on next message received) + send_ack(rs_fd, pop_aq_cmd(&_aqualink_data)); + + delayAckCnt++; + } + } +} + +void main_loop() +{ struct mg_mgr mgr; int rs_fd; int packet_length; unsigned char packet_buffer[AQ_MAXPKTLEN]; bool interestedInNextAck; - int delayAckCnt = 0; + //int delayAckCnt = 0; // NSF need to find a better place to init this. //_aqualink_data.aq_command = 0x00; @@ -962,8 +867,11 @@ void main_loop() { _aqualink_data.service_mode_state = OFF; _aqualink_data.battery = OK; + if (_config_parameters.force_swg == true) + _aqualink_data.swg_percent = 0; - if (!start_net_services(&mgr, &_aqualink_data, &_config_parameters)) { + if (!start_net_services(&mgr, &_aqualink_data, &_config_parameters)) + { logMessage(LOG_ERR, "Can not start webserver on port %s.\n", _config_parameters.socket_port); exit(EXIT_FAILURE); } @@ -976,11 +884,16 @@ void main_loop() { logMessage(LOG_NOTICE, "Listening to Aqualink RS8 on serial port: %s\n", _config_parameters.serial_port); if (_config_parameters.pda_mode == true) - set_pda_mode(true); + { + init_pda(&_aqualink_data); + } - while (_keepRunning == true) { - while ((rs_fd < 0 || blank_read >= MAX_ZERO_READ_BEFORE_RECONNECT) && _keepRunning == true) { - if (rs_fd < 0) { + while (_keepRunning == true) + { + while ((rs_fd < 0 || blank_read >= MAX_ZERO_READ_BEFORE_RECONNECT) && _keepRunning == true) + { + if (rs_fd < 0) + { // sleep(1); sprintf(_aqualink_data.last_display_message, CONNECTION_ERROR); logMessage(LOG_ERR, "Aqualink daemon attempting to connect to master device...\n"); @@ -988,7 +901,9 @@ void main_loop() { mg_mgr_poll(&mgr, 1000); // Sevice messages mg_mgr_poll(&mgr, 3000); // should donothing for 3 seconds. // broadcast_aqualinkstate_error(mgr.active_connections, "No connection to RS control panel"); - } else { + } + else + { logMessage(LOG_ERR, "Aqualink daemon looks like serial error, resetting.\n"); } rs_fd = init_serial_port(_config_parameters.serial_port); @@ -996,99 +911,84 @@ void main_loop() { } packet_length = get_packet(rs_fd, packet_buffer); - if (packet_length == -1) { + if (packet_length == -1) + { // Unrecoverable read error. Force an attempt to reconnect. logMessage(LOG_ERR, "Bad packet length, reconnecting\n"); blank_read = MAX_ZERO_READ_BEFORE_RECONNECT; - } else if (packet_length == 0) { + } + else if (packet_length == 0) + { //logMessage(LOG_DEBUG_SERIAL, "Nothing read on serial\n"); blank_read++; - } else if (packet_length > 0) { + } + else if (packet_length > 0) + { blank_read = 0; -//debugPacket(packet_buffer, packet_length); + if (_config_parameters.debug_RSProtocol_packets) logPacket(packet_buffer, packet_length); + + if (packet_length > 0 && packet_buffer[PKT_DEST] == _config_parameters.device_id) + { - if (packet_length > 0 && packet_buffer[PKT_DEST] == _config_parameters.device_id) { - /* - send_ack(rs_fd, _aqualink_data.aq_command); - _aqualink_data.aq_command = NUL; - */ if (getLogLevel() >= LOG_DEBUG) - logMessage(LOG_DEBUG, "RS received packet of type %s length %d\n", get_packet_type(packet_buffer , packet_length), packet_length); + logMessage(LOG_DEBUG, "RS received packet of type %s length %d\n", get_packet_type(packet_buffer, packet_length), packet_length); - // **** NSF (Taken out while playing with Panel Simulator, put back in. ************) - // send_ack(rs_fd, pop_aq_cmd(&_aqualink_data)); -#define MAX_BLOCK_ACK 12 -#define MAX_BUSY_ACK (50 + MAX_BLOCK_ACK) - // Wrap the mess just for sanity, the pre-process will clean it up. - if (! _aqualink_data.simulate_panel || - _aqualink_data.active_thread.thread_id != 0) - { - // Can only send command to status message on PDA. - if (_config_parameters.pda_mode == true && packet_buffer[PKT_CMD] != CMD_STATUS) - send_ack(rs_fd, NUL); - else - send_ack(rs_fd, pop_aq_cmd(&_aqualink_data)); - } else { // We are in simlator mode, ack get's complicated now. - // If have a command to send, send a normal ack. - // If we last message is waiting for an input "SELECT xxxxx", then sent a pause ack - // pause ack strarts with around 12 ACK_SCREEN_BUSY_DISPLAY acks, then 50 ACK_SCREEN_BUSY acks - // if we send a command (ie keypress), the whole count needs to end and go back to sending normal ack. - // In code below, it jumps to sending ACK_SCREEN_BUSY, which still seems to work ok. - if ( strncasecmp(_aqualink_data.last_display_message, "SELECT", 6) != 0) - { // Nothing to wait for, send normal ack. - send_ack(rs_fd, pop_aq_cmd(&_aqualink_data)); - delayAckCnt = 0; - } else if ( get_aq_cmd_length() > 0 ) { - // Send command and jump directly "busy but can receive message" - send_ack(rs_fd, pop_aq_cmd(&_aqualink_data)); - delayAckCnt = MAX_BUSY_ACK; // need to test jumping to MAX_BUSY_ACK here - } else { - logMessage(LOG_NOTICE, "Sending display busy due to Simulator mode \n"); - if (delayAckCnt < MAX_BLOCK_ACK) // block all incomming messages - send_extended_ack(rs_fd, ACK_SCREEN_BUSY_BLOCK, pop_aq_cmd(&_aqualink_data)); - else if (delayAckCnt < MAX_BUSY_ACK) // say we are pausing - send_extended_ack(rs_fd, ACK_SCREEN_BUSY, pop_aq_cmd(&_aqualink_data)); - else // We timed out pause, send normal ack (This should also reset the display message on next message received) - send_ack(rs_fd, pop_aq_cmd(&_aqualink_data)); - - delayAckCnt++; - } - } + //logMessage(LOG_DEBUG, "RS received packet of type %s length %d\n", get_packet_type(packet_buffer, packet_length), packet_length); + //debugPacketPrint(0x00, packet_buffer, packet_length); + //unsigned char ID, unsigned char *packet_buffer, int packet_length) // Process the packet. This includes deriving general status, and identifying // warnings and errors. If something changed, notify any listeners - if (process_packet(packet_buffer, packet_length) != false) { + if (process_packet(packet_buffer, packet_length) != false) + { broadcast_aqualinkstate(mgr.active_connections); } - } else if (packet_length > 0 && _config_parameters.read_all_devices == true) { + // If we are not in PDA or Simulator mode, just sent ACK & any CMD, else caculate the ACK. + if (!_aqualink_data.simulate_panel && !_config_parameters.pda_mode) + send_ack(rs_fd, pop_aq_cmd(&_aqualink_data)); + else + caculate_ack_packet(rs_fd, packet_buffer); + } + else if (packet_length > 0 && _config_parameters.read_all_devices == true) + { //logPacket(packet_buffer, packet_length); - if (packet_buffer[PKT_DEST] == DEV_MASTER && interestedInNextAck == true) { - if ( packet_buffer[PKT_CMD] == CMD_PPM ) { + if (packet_buffer[PKT_DEST] == DEV_MASTER && interestedInNextAck == true) + { + if (packet_buffer[PKT_CMD] == CMD_PPM) + { _aqualink_data.ar_swg_status = packet_buffer[5]; - if (_aqualink_data.swg_delayed_percent != TEMP_UNKNOWN && _aqualink_data.ar_swg_status == 0x00) { // We have a delayed % to set. + if (_aqualink_data.swg_delayed_percent != TEMP_UNKNOWN && _aqualink_data.ar_swg_status == 0x00) + { // We have a delayed % to set. char sval[10]; snprintf(sval, 9, "%d", _aqualink_data.swg_delayed_percent); aq_programmer(AQ_SET_SWG_PERCENT, sval, &_aqualink_data); - logMessage(LOG_NOTICE, "Setting SWG %% to %d, from delayed message\n",_aqualink_data.swg_delayed_percent); + logMessage(LOG_NOTICE, "Setting SWG %% to %d, from delayed message\n", _aqualink_data.swg_delayed_percent); _aqualink_data.swg_delayed_percent = TEMP_UNKNOWN; } _aqualink_data.swg_ppm = packet_buffer[4] * 100; } interestedInNextAck = false; - } else if (interestedInNextAck == true && packet_buffer[PKT_DEST] != DEV_MASTER && _aqualink_data.ar_swg_status != 0x00 ) { + } + else if (interestedInNextAck == true && packet_buffer[PKT_DEST] != DEV_MASTER && _aqualink_data.ar_swg_status != 0x00) + { _aqualink_data.ar_swg_status = SWG_STATUS_OFF; interestedInNextAck = false; - } else if (packet_buffer[PKT_DEST] == SWG_DEV_ID) { + } + else if (packet_buffer[PKT_DEST] == SWG_DEV_ID) + { interestedInNextAck = true; - if ( packet_buffer[3] == CMD_PERCENT && _aqualink_data.active_thread.thread_id == 0 ) { + if (packet_buffer[3] == CMD_PERCENT && _aqualink_data.active_thread.thread_id == 0) + { // Only read SWG Percent if we are not programming, as we might be changing this _aqualink_data.swg_percent = (int)packet_buffer[4]; } - } else { + } + else + { interestedInNextAck = false; } } @@ -1097,15 +997,16 @@ void main_loop() { logMessage(LOG_DEBUG_SERIAL, "Received Packet for ID 0x%02hhx of type %s %s\n",packet_buffer[PKT_DEST], get_packet_type(packet_buffer, packet_length), (packet_buffer[PKT_DEST] == _config_parameters.device_id)?" <-- Aqualinkd ID":""); }*/ - } mg_mgr_poll(&mgr, 0); // Any unactioned commands - if (_aqualink_data.unactioned.type != NO_ACTION) { + if (_aqualink_data.unactioned.type != NO_ACTION) + { time_t now; time(&now); - if (difftime(now, _aqualink_data.unactioned.requested) > 2){ + if (difftime(now, _aqualink_data.unactioned.requested) > 2) + { logMessage(LOG_DEBUG, "Actioning delayed request\n"); action_delayed_request(); } @@ -1118,7 +1019,8 @@ void main_loop() { #endif //} } - + + if (_config_parameters.debug_RSProtocol_packets) closePacketLog(); // Reset and close the port. close_serial_port(rs_fd); // Clear webbrowser @@ -1129,4 +1031,3 @@ void main_loop() { logMessage(LOG_NOTICE, "Exit!\n"); exit(EXIT_FAILURE); } - diff --git a/config.c b/config.c index da0e5c9..5e5f6a2 100644 --- a/config.c +++ b/config.c @@ -75,11 +75,15 @@ void init_parameters (struct aqconfig * parms) parms->deamonize = true; parms->log_file = '\0'; parms->pda_mode = false; + parms->pda_sleep_mode = false; parms->convert_mqtt_temp = true; parms->convert_dz_temp = true; parms->report_zero_pool_temp = false; parms->report_zero_spa_temp = false; parms->read_all_devices = true; + parms->use_panel_aux_labels = false; + parms->debug_RSProtocol_packets = false; + parms->force_swg = false; generate_mqtt_id(parms->mqtt_ID, MQTT_ID_LEN); } @@ -377,6 +381,10 @@ bool setConfigValue(struct aqconfig *config_parameters, struct aqualinkdata *aqd config_parameters->pda_mode = text2bool(value); set_pda_mode(config_parameters->pda_mode); rtn=true; + } else if (strncasecmp(param, "pda_sleep_mode", 8) == 0) { + config_parameters->pda_sleep_mode = text2bool(value); + //set_pda_mode(config_parameters->pda_mode); + rtn=true; } else if (strncasecmp(param, "convert_mqtt_temp_to_c", 22) == 0) { config_parameters->convert_mqtt_temp = text2bool(value); rtn=true; @@ -396,7 +404,17 @@ bool setConfigValue(struct aqconfig *config_parameters, struct aqualinkdata *aqd } else if (strncasecmp (param, "read_all_devices", 16) == 0) { config_parameters->read_all_devices = text2bool(value); rtn=true; + } else if (strncasecmp (param, "use_panel_aux_labels", 20) == 0) { + config_parameters->use_panel_aux_labels = text2bool(value); + rtn=true; + } else if (strncasecmp (param, "force_SWG", 9) == 0) { + config_parameters->force_swg = text2bool(value); + rtn=true; + } else if (strncasecmp (param, "debug_RSProtocol_packets", 24) == 0) { + config_parameters->debug_RSProtocol_packets = text2bool(value); + rtn=true; } + // removed until domoticz has a better virtual thermostat /*else if (strncasecmp (param, "pool_thermostat_dzidx", 21) == 0) { config_parameters->dzidx_pool_thermostat = strtoul(value, NULL, 10); diff --git a/config.h b/config.h index 7ae6224..7e1e37b 100644 --- a/config.h +++ b/config.h @@ -50,12 +50,16 @@ struct aqconfig int light_programming_button; bool override_freeze_protect; bool pda_mode; + bool pda_sleep_mode; bool convert_mqtt_temp; bool convert_dz_temp; //bool flash_mqtt_buttons; bool report_zero_spa_temp; bool report_zero_pool_temp; bool read_all_devices; + bool use_panel_aux_labels; + bool force_swg; + bool debug_RSProtocol_packets; //int dzidx_pool_thermostat; // Domoticz virtual thermostats are crap removed until better //int dzidx_spa_thermostat; // Domoticz virtual thermostats are crap removed until better //char mqtt_pub_topic[250]; @@ -69,5 +73,6 @@ void init_parameters (struct aqconfig * parms); void readCfg (struct aqconfig *config_parameters, struct aqualinkdata *aqualink_data, char *cfgFile); bool writeCfg (struct aqconfig *config_parameters, struct aqualinkdata *aqdata); bool setConfigValue(struct aqconfig *config_parameters, struct aqualinkdata *aqdata, char *param, char *value); +char *cleanalloc(char*str); #endif diff --git a/net_services.c b/net_services.c index 168d37f..5838803 100644 --- a/net_services.c +++ b/net_services.c @@ -33,6 +33,7 @@ #include "json_messages.h" #include "domoticz.h" #include "aq_mqtt.h" +#include "pda.h" static struct aqconfig *_aqualink_config; @@ -761,7 +762,11 @@ void action_web_request(struct mg_connection *nc, struct http_message *http_msg) void action_websocket_request(struct mg_connection *nc, struct websocket_message *wm) { char buffer[50]; struct JSONwebrequest request; - + + // Any websocket request means UI is active, so don't let AqualinkD go to sleep if in PDA mode + if (pda_mode()) + pda_reset_sleep(); + strncpy(buffer, (char *)wm->data, wm->size); buffer[wm->size] = '\0'; // logMessage (LOG_DEBUG, "buffer '%s'\n", buffer); diff --git a/pda.c b/pda.c new file mode 100644 index 0000000..99cacfe --- /dev/null +++ b/pda.c @@ -0,0 +1,557 @@ + +#define _GNU_SOURCE 1 // for strcasestr & strptime +#include +#include +#include +#include + +#include "aqualink.h" +#include "init_buttons.h" +#include "pda_menu.h" +#include "utils.h" + +// static struct aqualinkdata _aqualink_data; +static struct aqualinkdata *_aqualink_data; +static unsigned char _last_packet_type; +static unsigned long _pda_loop_cnt = 0; +static bool _initWithRS = false; + +// Each RS message is around 0.5 seconds apart, so 2 mins = 120 seconds = 240 polls +#define PDA_LOOP_COUNT 240 // 2 mins in poll (sleep timer) + +void init_pda(struct aqualinkdata *aqdata) +{ + _aqualink_data = aqdata; + set_pda_mode(true); + //clock_gettime(CLOCK_REALTIME, &_aqualink_data->last_active_time); + //aq_programmer(AQ_PDA_INIT, NULL, _aqualink_data); // NEED TO MOVE THIS. Can't run this here incase serial connection fails / needs to be cleaned up. +} + + +bool pda_shouldSleep() { + //logMessage(LOG_DEBUG, "PDA loop count %d, will sleep at %d\n",_pda_loop_cnt,PDA_LOOP_COUNT); + if (_pda_loop_cnt++ < PDA_LOOP_COUNT) { + return false; + } else if (_pda_loop_cnt > PDA_LOOP_COUNT*2) { + _pda_loop_cnt = 0; + return false; + } + + return true; +} + +void pda_wake() { + +} + +void pda_reset_sleep() { + _pda_loop_cnt = 0; +} + +unsigned char get_last_pda_packet_type() +{ + return _last_packet_type; +} + +void set_pda_led(struct aqualinkled *led, char state) +{ + aqledstate old_state = led->state; + if (state == 'N') + { + led->state = ON; + } + else if (state == 'A') + { + led->state = ENABLE; + } + else if (state == '*') + { + led->state = FLASH; + } + else + { + led->state = OFF; + } + if (old_state != led->state) + { + logMessage(LOG_DEBUG, "set_pda_led from %d to %d\n", old_state, led->state); + } +} + +void pass_pda_equiptment_status_item(char *msg) +{ + static char *index; + int i; + + // EQUIPMENT STATUS + // + // AquaPure 100% + // SALT 25500 PPM + // FILTER PUMP + // POOL HEAT + // SPA HEAT ENA + + // EQUIPMENT STATUS + // + // FREEZE PROTECT + // AquaPure 100% + // SALT 25500 PPM + // CHECK AquaPure + // GENERAL FAULT + // FILTER PUMP + // CLEANER + // + + // Check message for status of device + // Loop through all buttons and match the PDA text. + if ((index = strcasestr(msg, "CHECK AquaPure")) != NULL) + { + logMessage(LOG_DEBUG, "CHECK AquaPure\n"); + } + else if ((index = strcasestr(msg, "FREEZE PROTECT")) != NULL) + { + _aqualink_data->frz_protect_state = ON; + } + else if ((index = strcasestr(msg, MSG_SWG_PCT)) != NULL) + { + _aqualink_data->swg_percent = atoi(index + strlen(MSG_SWG_PCT)); + logMessage(LOG_DEBUG, "AquaPure = %d\n", _aqualink_data->swg_percent); + } + else if ((index = strcasestr(msg, MSG_SWG_PPM)) != NULL) + { + _aqualink_data->swg_ppm = atoi(index + strlen(MSG_SWG_PPM)); + logMessage(LOG_DEBUG, "SALT = %d\n", _aqualink_data->swg_ppm); + } + else + { + char labelBuff[AQ_MSGLEN + 1]; + strncpy(labelBuff, msg, AQ_MSGLEN + 1); + msg = stripwhitespace(labelBuff); + + if (strcasecmp(msg, "POOL HEAT ENA") == 0) + { + _aqualink_data->aqbuttons[POOL_HEAT_INDEX].led->state = ENABLE; + } + else if (strcasecmp(msg, "SPA HEAT ENA") == 0) + { + _aqualink_data->aqbuttons[SPA_HEAT_INDEX].led->state = ENABLE; + } + else + { + for (i = 0; i < TOTAL_BUTTONS; i++) + { + if (strcasecmp(msg, _aqualink_data->aqbuttons[i].pda_label) == 0) + { + logMessage(LOG_DEBUG, "*** Found Status for %s = '%.*s'\n", _aqualink_data->aqbuttons[i].pda_label, AQ_MSGLEN, msg); + // It's on (or delayed) if it's listed here. + if (_aqualink_data->aqbuttons[i].led->state != FLASH) + { + _aqualink_data->aqbuttons[i].led->state = ON; + } + break; + } + } + } + } +} + +void process_pda_packet_msg_long_temp(const char *msg) +{ + // 'AIR POOL' + // ' 86` 86` ' + // 'AIR SPA ' + // ' 86` 86` ' + // 'AIR ' + // ' 86` ' + _aqualink_data->temp_units = FAHRENHEIT; // Force FAHRENHEIT + if (stristr(pda_m_line(1), "AIR") != NULL) + _aqualink_data->air_temp = atoi(msg); + + if (stristr(pda_m_line(1), "SPA") != NULL) + { + _aqualink_data->spa_temp = atoi(msg + 4); + _aqualink_data->pool_temp = TEMP_UNKNOWN; + } + else if (stristr(pda_m_line(1), "POOL") != NULL) + { + _aqualink_data->pool_temp = atoi(msg + 7); + _aqualink_data->spa_temp = TEMP_UNKNOWN; + } + else + { + _aqualink_data->pool_temp = TEMP_UNKNOWN; + _aqualink_data->spa_temp = TEMP_UNKNOWN; + } + // printf("Air Temp = %d | Water Temp = %d\n",atoi(msg),atoi(msg+7)); +} + +void process_pda_packet_msg_long_time(const char *msg) +{ + // message " SAT 8:46AM " + // " SAT 10:29AM" + // " SAT 4:23PM " + // printf("TIME = '%.*s'\n",AQ_MSGLEN,msg ); + // printf("TIME = '%c'\n",msg[AQ_MSGLEN-1] ); + if (msg[AQ_MSGLEN - 1] == ' ') + { + strncpy(_aqualink_data->time, msg + 9, 6); + } + else + { + strncpy(_aqualink_data->time, msg + 9, 7); + } + strncpy(_aqualink_data->date, msg + 5, 3); + // :TODO: NSF Come back and change the above to correctly check date and time in future +} + +void process_pda_packet_msg_long_equipment_control(const char *msg) +{ + // These are listed as "FILTER PUMP OFF" + int i; + char labelBuff[AQ_MSGLEN + 1]; + strncpy(labelBuff, msg, AQ_MSGLEN - 4); + labelBuff[AQ_MSGLEN - 4] = 0; + + logMessage(LOG_DEBUG, "*** Checking Equiptment '%s'\n", labelBuff); + + for (i = 0; i < TOTAL_BUTTONS; i++) + { + if (strcasecmp(stripwhitespace(labelBuff), _aqualink_data->aqbuttons[i].pda_label) == 0) + { + logMessage(LOG_DEBUG, "*** Found EQ CTL Status for %s = '%.*s'\n", _aqualink_data->aqbuttons[i].pda_label, AQ_MSGLEN, msg); + set_pda_led(_aqualink_data->aqbuttons[i].led, msg[AQ_MSGLEN - 1]); + } + } +} + +void process_pda_packet_msg_long_home(const char *msg) +{ + if (stristr(msg, "POOL MODE") != NULL) + { + // If pool mode is on the filter pump is on but if it is off the filter pump might be on if spa mode is on. + if (msg[AQ_MSGLEN - 1] == 'N') + { + _aqualink_data->aqbuttons[PUMP_INDEX].led->state = ON; + } + else if (msg[AQ_MSGLEN - 1] == '*') + { + _aqualink_data->aqbuttons[PUMP_INDEX].led->state = FLASH; + } + } + else if (stristr(msg, "POOL HEATER") != NULL) + { + set_pda_led(_aqualink_data->aqbuttons[POOL_HEAT_INDEX].led, msg[AQ_MSGLEN - 1]); + } + else if (stristr(msg, "SPA MODE") != NULL) + { + // when SPA mode is on the filter may be on or pending + if (msg[AQ_MSGLEN - 1] == 'N') + { + _aqualink_data->aqbuttons[PUMP_INDEX].led->state = ON; + _aqualink_data->aqbuttons[SPA_INDEX].led->state = ON; + } + else if (msg[AQ_MSGLEN - 1] == '*') + { + _aqualink_data->aqbuttons[PUMP_INDEX].led->state = FLASH; + _aqualink_data->aqbuttons[SPA_INDEX].led->state = ON; + } + else + { + _aqualink_data->aqbuttons[SPA_INDEX].led->state = OFF; + } + } + else if (stristr(msg, "SPA HEATER") != NULL) + { + set_pda_led(_aqualink_data->aqbuttons[SPA_HEAT_INDEX].led, msg[AQ_MSGLEN - 1]); + } +} + +void process_pda_packet_msg_long_set_temp(const char *msg) +{ + logMessage(LOG_DEBUG, "process_pda_packet_msg_long_set_temp\n"); + + if (stristr(msg, "POOL HEAT") != NULL) + { + _aqualink_data->pool_htr_set_point = atoi(msg + 10); + logMessage(LOG_DEBUG, "pool_htr_set_point = %d\n", _aqualink_data->pool_htr_set_point); + } + else if (stristr(msg, "SPA HEAT") != NULL) + { + _aqualink_data->spa_htr_set_point = atoi(msg + 10); + logMessage(LOG_DEBUG, "spa_htr_set_point = %d\n", _aqualink_data->spa_htr_set_point); + } +} + +void process_pda_packet_msg_long_spa_heat(const char *msg) +{ + if (strncmp(msg, " ENABLED ", 16) == 0) + { + _aqualink_data->aqbuttons[SPA_HEAT_INDEX].led->state = ENABLE; + } + else if (strncmp(msg, " SET TO", 8) == 0) + { + _aqualink_data->spa_htr_set_point = atoi(msg + 8); + logMessage(LOG_DEBUG, "spa_htr_set_point = %d\n", _aqualink_data->spa_htr_set_point); + } +} + +void process_pda_packet_msg_long_pool_heat(const char *msg) +{ + if (strncmp(msg, " ENABLED ", 16) == 0) + { + _aqualink_data->aqbuttons[POOL_HEAT_INDEX].led->state = ENABLE; + } + else if (strncmp(msg, " SET TO", 8) == 0) + { + _aqualink_data->pool_htr_set_point = atoi(msg + 8); + logMessage(LOG_DEBUG, "pool_htr_set_point = %d\n", _aqualink_data->pool_htr_set_point); + } +} + +void process_pda_packet_msg_long_freeze_protect(const char *msg) +{ + if (strncmp(msg, "TEMP ", 10) == 0) + { + _aqualink_data->frz_protect_set_point = atoi(msg + 10); + logMessage(LOG_DEBUG, "frz_protect_set_point = %d\n", _aqualink_data->frz_protect_set_point); + } +} + +void process_pda_packet_msg_long_SWG(const char *msg) +{ + //PDA Line 0 = SET AquaPure + //PDA Line 1 = + //PDA Line 2 = + //PDA Line 3 = SET POOL TO: 45% + //PDA Line 4 = SET SPA TO: 0% + + // If spa is on, read SWG for spa, if not set SWG for pool + if (_aqualink_data->aqbuttons[SPA_INDEX].led->state != OFF) { + if (strncmp(msg, "SET SPA TO:", 11) == 0) + { + _aqualink_data->swg_percent = atoi(msg + 13); + logMessage(LOG_DEBUG, "SPA swg_percent = %d\n", _aqualink_data->swg_percent); + } + } else { + if (strncmp(msg, "SET POOL TO:", 12) == 0) + { + _aqualink_data->swg_percent = atoi(msg + 13); + logMessage(LOG_DEBUG, "POOL swg_percent = %d\n", _aqualink_data->swg_percent); + } + } +} + +void process_pda_packet_msg_long_unknown(const char *msg) +{ + int i; + // Lets make a guess here and just see if there is an ON/OFF/ENA/*** at the end of the line + // When you turn on/off a piece of equiptment, a clear screen followed by single message is sent. + // So we are not in any PDA menu, try to catch that message here so we catch new device state ASAP. + if (msg[AQ_MSGLEN - 1] == 'N' || msg[AQ_MSGLEN - 1] == 'F' || msg[AQ_MSGLEN - 1] == 'A' || msg[AQ_MSGLEN - 1] == '*') + { + for (i = 0; i < TOTAL_BUTTONS; i++) + { + if (stristr(msg, _aqualink_data->aqbuttons[i].pda_label) != NULL) + { + printf("*** UNKNOWN Found Status for %s = '%.*s'\n", _aqualink_data->aqbuttons[i].pda_label, AQ_MSGLEN, msg); + // set_pda_led(_aqualink_data->aqbuttons[i].led, msg[AQ_MSGLEN-1]); + } + } + } +} + +void process_pda_freeze_protect_devices() +{ + // PDA Line 0 = FREEZE PROTECT + // PDA Line 1 = DEVICES + // PDA Line 2 = + // PDA Line 3 = FILTER PUMP X + // PDA Line 4 = SPA + // PDA Line 5 = CLEANER X + // PDA Line 6 = POOL LIGHT + // PDA Line 7 = SPA LIGHT + // PDA Line 8 = EXTRA AUX + // PDA Line 9 = + int i; + logMessage(LOG_DEBUG, "process_pda_freeze_protect_devices\n"); + for (i = 1; i < PDA_LINES; i++) + { + if (pda_m_line(i)[AQ_MSGLEN - 1] == 'X') + { + logMessage(LOG_DEBUG, "PDA freeze protect enabled by %s\n", pda_m_line(i)); + if (_aqualink_data->frz_protect_state == OFF) + { + _aqualink_data->frz_protect_state = ENABLE; + break; + } + } + } +} + +bool process_pda_packet(unsigned char *packet, int length) +{ + bool rtn = true; + int i; + char *msg; + static bool init = false; + static time_t _lastStatus = 0; + + if (_lastStatus == 0) + { + time(&_lastStatus); + } + + process_pda_menu_packet(packet, length); + + // NSF. + + //_aqualink_data->last_msg_was_status = false; + + // debugPacketPrint(0x00, packet, length); + + switch (packet[PKT_CMD]) + { + + case CMD_ACK: + logMessage(LOG_DEBUG, "RS Received ACK length %d.\n", length); + if (init == false) + { + aq_programmer(AQ_PDA_INIT, NULL, _aqualink_data); + init = true; + } + break; + + case CMD_STATUS: + _aqualink_data->last_display_message[0] = '\0'; + + // If we get a status packet, and we are on the status menu, this is a list of what's on + // or pending so unless flash turn everything off, and just turn on items that are listed. + // This is the only way to update a device that's been turned off by a real PDA / keypad. + // Note: if the last line of the status menu is present it may be cut off + if (pda_m_type() == PM_EQUIPTMENT_STATUS) + { + if (_aqualink_data->frz_protect_state == ON) + { + _aqualink_data->frz_protect_state = ENABLE; + } + if (pda_m_line(PDA_LINES - 1)[0] == '\0') + { + for (i = 0; i < TOTAL_BUTTONS; i++) + { + if (_aqualink_data->aqbuttons[i].led->state != FLASH) + { + _aqualink_data->aqbuttons[i].led->state = OFF; + } + } + } + else + { + logMessage(LOG_DEBUG, "PDA Equipment status may be truncated.\n"); + } + for (i = 1; i < PDA_LINES; i++) + { + pass_pda_equiptment_status_item(pda_m_line(i)); + } + time(&_lastStatus); + } + else + { + time_t now; + time(&now); + if (init && difftime(now, _lastStatus) > 60) + { + logMessage(LOG_DEBUG, "OVER 60 SECONDS SINCE LAST STATUS UPDATE, forcing refresh\n"); + // Reset aquapure to nothing since it must be off at this point + _aqualink_data->pool_temp = TEMP_UNKNOWN; + _aqualink_data->spa_temp = TEMP_UNKNOWN; + time(&_lastStatus); + aq_programmer(AQ_PDA_DEVICE_STATUS, NULL, _aqualink_data); + } + } + if (pda_m_type() == PM_FREEZE_PROTECT_DEVICES) + { + process_pda_freeze_protect_devices(); + } + break; + case CMD_MSG_LONG: + { + //printf ("*******************************************************************************************\n"); + //printf ("menu type %d\n",pda_m_type()); + + msg = (char *)packet + PKT_DATA + 1; + + //strcpy(_aqualink_data->last_message, msg); + + if (packet[PKT_DATA] == 0x82) + { // Air & Water temp is always this ID + process_pda_packet_msg_long_temp(msg); + } + else if (packet[PKT_DATA] == 0x40) + { // Time is always on this ID + process_pda_packet_msg_long_time(msg); + // If it wasn't a specific msg, (above) then run through and see what kind + // of message it is depending on the PDA menu. Note don't process EQUIPTMENT + // STATUS menu here, wait until a CMD_STATUS is received. + } + else { + switch (pda_m_type()) { + case PM_EQUIPTMENT_CONTROL: + process_pda_packet_msg_long_equipment_control(msg); + break; + case PM_HOME: + case PM_BUILDING_HOME: + process_pda_packet_msg_long_home(msg); + break; + case PM_SET_TEMP: + process_pda_packet_msg_long_set_temp(msg); + break; + case PM_SPA_HEAT: + process_pda_packet_msg_long_spa_heat(msg); + break; + case PM_POOL_HEAT: + process_pda_packet_msg_long_pool_heat(msg); + break; + case PM_FREEZE_PROTECT: + process_pda_packet_msg_long_freeze_protect(msg); + break; + case PM_AQUAPURE: + process_pda_packet_msg_long_SWG(msg); + break; + case PM_UNKNOWN: + default: + process_pda_packet_msg_long_unknown(msg); + break; + } + } + + // printf("** Line index='%d' Highligh='%s' Message='%.*s'\n",pda_m_hlightindex(), pda_m_hlight(), AQ_MSGLEN, msg); + logMessage(LOG_INFO, "PDA Menu '%d' Selectedline '%s', Last line received '%.*s'\n", pda_m_type(), pda_m_hlight(), AQ_MSGLEN, msg); + break; + } + case CMD_PDA_0x1B: + { + // We get two of these on startup, one with 0x00 another with 0x01 at index 4. Just act on one. + // Think this is PDA finishd showing startup screen + if (packet[4] == 0x00) { + if (_initWithRS == false) + { + _initWithRS = true; + logMessage(LOG_DEBUG, "**** PDA INIT ****"); + aq_programmer(AQ_PDA_INIT, NULL, _aqualink_data); + } else { + logMessage(LOG_DEBUG, "**** PDA WAKE INIT ****"); + aq_programmer(AQ_PDA_WAKE_INIT, NULL, _aqualink_data); + } + } + } + break; + } + + if (packet[PKT_CMD] == CMD_MSG_LONG || packet[PKT_CMD] == CMD_PDA_HIGHLIGHT || + packet[PKT_CMD] == CMD_PDA_SHIFTLINES || packet[PKT_CMD] == CMD_PDA_CLEAR) + { + // We processed the next message, kick any threads waiting on the message. + kick_aq_program_thread(_aqualink_data); + } + return rtn; +} \ No newline at end of file diff --git a/pda.h b/pda.h new file mode 100644 index 0000000..88af360 --- /dev/null +++ b/pda.h @@ -0,0 +1,12 @@ + + +#ifndef PDA_H_ +#define PDA_H_ + +void init_pda(struct aqualinkdata *aqdata); +bool process_pda_packet(unsigned char* packet, int length); +bool pda_shouldSleep(); +void pda_wake(); +void pda_reset_sleep(); + +#endif // PDA_MESSAGES_H_ \ No newline at end of file diff --git a/pda_aq_programmer.c b/pda_aq_programmer.c new file mode 100644 index 0000000..e70b303 --- /dev/null +++ b/pda_aq_programmer.c @@ -0,0 +1,770 @@ +#include +#include +#include +#include +#include +#include + + +#include "aqualink.h" +#include "utils.h" +#include "aq_programmer.h" +#include "aq_serial.h" +#include "pda.h" +#include "pda_menu.h" +#include "pda_aq_programmer.h" + +#include "init_buttons.h" + +bool waitForPDAMessageHighlight(struct aqualinkdata *aq_data, int highlighIndex, int numMessageReceived); +bool waitForPDAMessageType(struct aqualinkdata *aq_data, unsigned char mtype, int numMessageReceived); +bool goto_pda_menu(struct aqualinkdata *aq_data, pda_menu_type menu); +bool wait_pda_selected_item(struct aqualinkdata *aq_data); +bool waitForPDAnextMenu(struct aqualinkdata *aq_data); +bool loopover_devices(struct aqualinkdata *aq_data); +bool find_pda_menu_item(struct aqualinkdata *aq_data, char *menuText, int charlimit); +bool select_pda_menu_item(struct aqualinkdata *aq_data, char *menuText, bool waitForNextMenu); + +static pda_type _PDA_Type; + +bool wait_pda_selected_item(struct aqualinkdata *aq_data) +{ + while (pda_m_hlightindex() == -1){ + waitForPDAMessageType(aq_data,CMD_PDA_HIGHLIGHT,10); + } + + if (pda_m_hlightindex() == -1) + return false; + else + return true; +} + +bool waitForPDAnextMenu(struct aqualinkdata *aq_data) { + waitForPDAMessageType(aq_data,CMD_PDA_CLEAR,10); + return waitForPDAMessageType(aq_data,CMD_PDA_HIGHLIGHT,15); +} + +bool loopover_devices(struct aqualinkdata *aq_data) { + int i; + + if (! goto_pda_menu(aq_data, PM_EQUIPTMENT_CONTROL)) { + //logMessage(LOG_ERR, "PDA :- can't find main menu\n"); + //cleanAndTerminateThread(threadCtrl); + return false; + } + + // Should look for message "ALL OFF", that's end of device list. + for (i=0; i < 18 && pda_find_m_index("ALL OFF") == -1 ; i++) { + send_cmd(KEY_PDA_DOWN, aq_data); + //while (get_aq_cmd_length() > 0) { delay(200); } + //waitForPDAMessageType(aq_data,CMD_PDA_HIGHLIGHT,3); + waitForMessage(aq_data, NULL, 1); + } + + return true; +} + +/* + if charlimit is set, use case insensitive match and limit chars. +*/ +bool find_pda_menu_item(struct aqualinkdata *aq_data, char *menuText, int charlimit) { + int i=pda_m_hlightindex(); + int index = (charlimit == 0)?pda_find_m_index(menuText):pda_find_m_index_case(menuText, charlimit); + + logMessage(LOG_DEBUG, "PDA Device programmer menu text '%s'\n",menuText); + + if (index < 0) { // No menu, is there a page down. "PDA Line 9 = ^^ MORE __" + if (strncmp(pda_m_line(9)," ^^ MORE", 10) == 0) { + int j; + for(j=0; j < 20; j++) { + send_cmd(KEY_PDA_DOWN, aq_data); + //delay(500); + //wait_for_empty_cmd_buffer(); + waitForPDAMessageType(aq_data,CMD_PDA_HIGHLIGHT,2); + //waitForMessage(aq_data, NULL, 1); + index = (charlimit == 0)?pda_find_m_index(menuText):pda_find_m_index_case(menuText, charlimit); + if (index >= 0) { + i=pda_m_hlightindex(); + break; + } + } + if (index < 0) { + logMessage(LOG_ERR, "PDA Device programmer couldn't find menu item on any page '%s'\n",menuText); + return false; + } + } else { + logMessage(LOG_ERR, "PDA Device programmer couldn't find menu item '%s'\n",menuText); + return false; + } + } + + // Found the text we want in the menu, now move to that position and select it. + //logMessage(LOG_DEBUG, "******************PDA Device programmer menu text '%s' is at index %d\n",menuText, index); + + if (i < index) { + for (i=pda_m_hlightindex(); i < index; i++) { + //logMessage(LOG_DEBUG, "******************PDA queue down index %d\n",i); + send_cmd(KEY_PDA_DOWN, aq_data); + } + } else if (i > index) { + for (i=pda_m_hlightindex(); i > index; i--) { + //logMessage(LOG_DEBUG, "******************PDA queue down index %d\n",i); + send_cmd(KEY_PDA_UP, aq_data); + } + } + + return waitForPDAMessageHighlight(aq_data, index, 10); +} + +bool select_pda_menu_item(struct aqualinkdata *aq_data, char *menuText, bool waitForNextMenu) { + + if ( find_pda_menu_item(aq_data, menuText, 0) ) { + send_cmd(KEY_PDA_SELECT, aq_data); + + logMessage(LOG_DEBUG, "PDA Device programmer selected menu item '%s'\n",menuText); + if (waitForNextMenu) + waitForPDAnextMenu(aq_data); + + return true; + } + + logMessage(LOG_ERR, "PDA Device programmer couldn't selected menu item '%s' at index %d\n",menuText, index); + return false; +} + +bool goto_pda_menu(struct aqualinkdata *aq_data, pda_menu_type menu) { + //int i = 0; + //char *menuText; + + logMessage(LOG_DEBUG, "PDA Device programmer request for menu %d\n",menu); + + // Keep going back, checking each time to get to home. + while (pda_m_type() == PM_FW_VERSION || pda_m_type() == PM_BUILDING_HOME) { + //logMessage(LOG_DEBUG, "******************PDA Device programmer delay on firmware or building home menu\n"); + delay(500); + } + + while ( pda_m_type() != menu && pda_m_type() != PM_HOME ) { + if (pda_m_type() != PM_BUILDING_HOME) { + send_cmd(KEY_PDA_BACK, aq_data); + //logMessage(LOG_DEBUG, "******************PDA Device programmer selected back button\n",menu); + waitForPDAnextMenu(aq_data); + } else { + waitForPDAMessageType(aq_data,CMD_PDA_HIGHLIGHT,15); + } + //logMessage(LOG_DEBUG, "******************PDA Device programmer menu type %d\n",pda_m_type()); + //if (!wait_for_empty_cmd_buffer() || i++ > 6) + // return false; + } + + if (pda_m_type() == menu) + return true; + + switch (menu) { + //case PM_SYSTEM_SETUP: + // select_pda_menu_item(aq_data, "MENU"); + //break; + case PM_EQUIPTMENT_CONTROL: + select_pda_menu_item(aq_data, "EQUIPMENT ON/OFF", true); + break; + case PM_PALM_OPTIONS: + select_pda_menu_item(aq_data, "MENU", true); + select_pda_menu_item(aq_data, "PALM OPTIONS", true); + case PM_AUX_LABEL: + if ( _PDA_Type == PDA) { + select_pda_menu_item(aq_data, "MENU", true); + select_pda_menu_item(aq_data, "SYSTEM SETUP", true); // This is a guess, (I have rev#) + select_pda_menu_item(aq_data, "LABEL AUX", true); + } else { + logMessage(LOG_ERR, "PDA in AquaPlalm mode, there is no SYSTEM SETUP / LABEL AUX menu\n"); + } + break; + case PM_SYSTEM_SETUP: + if ( _PDA_Type == PDA) { + select_pda_menu_item(aq_data, "MENU", true); + select_pda_menu_item(aq_data, "SYSTEM SETUP", true); // This is a guess, (I have rev#) + } else { + logMessage(LOG_ERR, "PDA in AquaPlalm mode, there is no SYSTEM SETUP menu\n"); + } + break; + case PM_FREEZE_PROTECT: + if ( _PDA_Type == PDA) { + select_pda_menu_item(aq_data, "MENU", true); + select_pda_menu_item(aq_data, "SYSTEM SETUP", true); // This is a guess, (I have rev#) + select_pda_menu_item(aq_data, "FREEZE PROTECT", true); // This is a guess, (I have rev#) + } else { + logMessage(LOG_ERR, "PDA in AquaPlalm mode, there is no SYSTEM SETUP / FREEZE PROTECT menu\n"); + } + break; + case PM_AQUAPURE: + select_pda_menu_item(aq_data, "MENU", true); + select_pda_menu_item(aq_data, "SET AquaPure", true); + //select_pda_menu_item(aq_data, "LABEL AUX"); + break; + case PM_SET_TEMP: + select_pda_menu_item(aq_data, "MENU", true); + select_pda_menu_item(aq_data, "SET TEMP", true); + //select_pda_menu_item(aq_data, "LABEL AUX"); + break; + case PM_SET_TIME: + select_pda_menu_item(aq_data, "MENU", true); + select_pda_menu_item(aq_data, "SET TIME", true); + //select_pda_menu_item(aq_data, "LABEL AUX"); + break; + default: + logMessage(LOG_ERR, "PDA Device programmer didn't understand requested menu\n"); + return false; + break; + } + + if (pda_m_type() != menu) { + logMessage(LOG_ERR, "PDA Device programmer didn't find a requested menu\n"); + return true; + } + + //logMessage(LOG_DEBUG, "******************PDA Device programmer request for menu %d found\n",menu); + + return true; +} + + + +void *set_aqualink_PDA_device_on_off( void *ptr ) +{ + struct programmingThreadCtrl *threadCtrl; + threadCtrl = (struct programmingThreadCtrl *) ptr; + struct aqualinkdata *aq_data = threadCtrl->aq_data; + //int i=0; + //int found; + char device_name[15]; + + waitForSingleThreadOrTerminate(threadCtrl, AQ_PDA_DEVICE_STATUS); + + char *buf = (char*)threadCtrl->thread_args; + int device = atoi(&buf[0]); + int state = atoi(&buf[5]); + + if (device < 0 || device > TOTAL_BUTTONS) { + logMessage(LOG_ERR, "PDA Device On/Off :- bad device number '%d'\n",device); + cleanAndTerminateThread(threadCtrl); + return ptr; + } + + logMessage(LOG_INFO, "PDA Device On/Off, device '%s', state %d\n",aq_data->aqbuttons[device].pda_label,state); + + if (! goto_pda_menu(aq_data, PM_EQUIPTMENT_CONTROL)) { + logMessage(LOG_ERR, "PDA Device On/Off :- can't find main menu\n"); + cleanAndTerminateThread(threadCtrl); + return ptr; + } + + //Pad name with spaces so something like "SPA" doesn't match "SPA BLOWER" + sprintf(device_name,"%-14s\n",aq_data->aqbuttons[device].pda_label); + if ( find_pda_menu_item(aq_data, device_name, 13) ) { + if (aq_data->aqbuttons[device].led->state != state) { + //printf("*** Select State ***\n"); + logMessage(LOG_INFO, "PDA Device On/Off, found device '%s', changing state\n",aq_data->aqbuttons[device].pda_label,state); + send_cmd(KEY_PDA_SELECT, aq_data); + while (get_aq_cmd_length() > 0) { delay(500); } + } else { + logMessage(LOG_INFO, "PDA Device On/Off, found device '%s', not changing state, is same\n",aq_data->aqbuttons[device].pda_label,state); + } + } else { + logMessage(LOG_ERR, "PDA Device On/Off, device '%s' not found\n",aq_data->aqbuttons[device].pda_label); + } + + goto_pda_menu(aq_data, PM_HOME); + //while (_pgm_command != NUL) { delay(500); } + + cleanAndTerminateThread(threadCtrl); + + // just stop compiler error, ptr is not valid as it's just been freed + return ptr; + +} + + + +void *get_aqualink_PDA_device_status( void *ptr ) +{ + struct programmingThreadCtrl *threadCtrl; + threadCtrl = (struct programmingThreadCtrl *) ptr; + struct aqualinkdata *aq_data = threadCtrl->aq_data; + //int i; + + waitForSingleThreadOrTerminate(threadCtrl, AQ_PDA_DEVICE_STATUS); + + if (! loopover_devices(aq_data)) { + logMessage(LOG_ERR, "PDA Device Status :- failed\n"); + } + + goto_pda_menu(aq_data, PM_HOME); + + cleanAndTerminateThread(threadCtrl); + + // just stop compiler error, ptr is not valid as it's just been freed + return ptr; +} + +void *set_aqualink_PDA_init( void *ptr ) +{ + struct programmingThreadCtrl *threadCtrl; + threadCtrl = (struct programmingThreadCtrl *) ptr; + struct aqualinkdata *aq_data = threadCtrl->aq_data; + //int i=0; + + waitForSingleThreadOrTerminate(threadCtrl, AQ_PDA_INIT); + + //int val = atoi((char*)threadCtrl->thread_args); + + //logMessage(LOG_DEBUG, "PDA Init\n", val); + + logMessage(LOG_DEBUG, "PDA Init\n"); + + if (pda_m_type() == PM_FW_VERSION) { + // check pda_m_line(1) to "AquaPalm" + if (strstr(pda_m_line(1), "AquaPalm") != NULL) { + _PDA_Type = AQUAPALM; + } else { + _PDA_Type = PDA; + } + char *ptr = pda_m_line(5); + ptr[AQ_MSGLEN+1] = '\0'; + strcpy(aq_data->version, stripwhitespace(ptr)); + } + + // Get status of all devices + if (! loopover_devices(aq_data)) { + logMessage(LOG_ERR, "PDA Init :- can't find menu\n"); + } + + // Get heater setpoints + if (! get_PDA_aqualink_pool_spa_heater_temps(aq_data)) { + logMessage(LOG_ERR, "PDA Init :- Error getting heater setpoints\n"); + } + + // Get freeze protect setpoint, AquaPalm doesn't have freeze protect in menu. + if (_PDA_Type != AQUAPALM && ! get_PDA_freeze_protect_temp(aq_data)) { + logMessage(LOG_ERR, "PDA Init :- Error getting freeze setpoints\n"); + } + + + goto_pda_menu(aq_data, PM_HOME); + + pda_reset_sleep(); + + cleanAndTerminateThread(threadCtrl); + + // just stop compiler error, ptr is not valid as it's just been freed + return ptr; +} + + +void *set_aqualink_PDA_wakeinit( void *ptr ) +{ + struct programmingThreadCtrl *threadCtrl; + threadCtrl = (struct programmingThreadCtrl *) ptr; + struct aqualinkdata *aq_data = threadCtrl->aq_data; + //int i=0; + + // At this point, we should probably just exit if there is a thread already going as + // it means the wake was called due to changing a device. + waitForSingleThreadOrTerminate(threadCtrl, AQ_PDA_INIT); + + logMessage(LOG_DEBUG, "PDA Wake Init\n"); + + // Get status of all devices + if (! loopover_devices(aq_data)) { + logMessage(LOG_ERR, "PDA Init :- can't find menu\n"); + } + + cleanAndTerminateThread(threadCtrl); + + // just stop compiler error, ptr is not valid as it's just been freed + return ptr; +} + + +bool get_PDA_freeze_protect_temp(struct aqualinkdata *aq_data) { + + if ( _PDA_Type == PDA) { + if (! goto_pda_menu(aq_data, PM_FREEZE_PROTECT)) { + return false; + } + } else { + logMessage(LOG_INFO, "In PDA AquaPalm mode, freezepoints not supported\n"); + return false; + } + + return true; +} + +bool get_PDA_aqualink_pool_spa_heater_temps(struct aqualinkdata *aq_data) { + + // Get heater setpoints + if (! goto_pda_menu(aq_data, PM_SET_TEMP)) { + return false; + } + + return true; +} + +bool waitForPDAMessageHighlight(struct aqualinkdata *aq_data, int highlighIndex, int numMessageReceived) +{ + logMessage(LOG_DEBUG, "waitForPDAMessageHighlight index %d\n",highlighIndex); + + if(pda_m_hlightindex() == highlighIndex) return true; + + int i=0; + pthread_mutex_init(&aq_data->active_thread.thread_mutex, NULL); + pthread_mutex_lock(&aq_data->active_thread.thread_mutex); + + while( ++i <= numMessageReceived) + { + logMessage(LOG_DEBUG, "waitForPDAMessageHighlight last = 0x%02hhx : index %d : (%d of %d)\n",aq_data->last_packet_type,pda_m_hlightindex(),i,numMessageReceived); + + if (aq_data->last_packet_type == CMD_PDA_HIGHLIGHT && pda_m_hlightindex() == highlighIndex) break; + + pthread_cond_init(&aq_data->active_thread.thread_cond, NULL); + pthread_cond_wait(&aq_data->active_thread.thread_cond, &aq_data->active_thread.thread_mutex); + } + + pthread_mutex_unlock(&aq_data->active_thread.thread_mutex); + + if (pda_m_hlightindex() != highlighIndex) { + //logMessage(LOG_ERR, "Could not select MENU of Aqualink control panel\n"); + logMessage(LOG_DEBUG, "waitForPDAMessageHighlight: did not receive index '%d'\n",highlighIndex); + return false; + } else + logMessage(LOG_DEBUG, "waitForPDAMessageHighlight: received index '%d'\n",highlighIndex); + + return true; +} + + +bool waitForPDAMessageType(struct aqualinkdata *aq_data, unsigned char mtype, int numMessageReceived) +{ + logMessage(LOG_DEBUG, "waitForPDAMessageType 0x%02hhx\n",mtype); + + int i=0; + pthread_mutex_init(&aq_data->active_thread.thread_mutex, NULL); + pthread_mutex_lock(&aq_data->active_thread.thread_mutex); + + while( ++i <= numMessageReceived) + { + logMessage(LOG_DEBUG, "waitForPDAMessageType 0x%02hhx, last message type was 0x%02hhx (%d of %d)\n",mtype,aq_data->last_packet_type,i,numMessageReceived); + + if (aq_data->last_packet_type == mtype) break; + + pthread_cond_init(&aq_data->active_thread.thread_cond, NULL); + pthread_cond_wait(&aq_data->active_thread.thread_cond, &aq_data->active_thread.thread_mutex); + } + + pthread_mutex_unlock(&aq_data->active_thread.thread_mutex); + + if (aq_data->last_packet_type != mtype) { + //logMessage(LOG_ERR, "Could not select MENU of Aqualink control panel\n"); + logMessage(LOG_DEBUG, "waitForPDAMessageType: did not receive 0x%02hhx\n",mtype); + return false; + } else + logMessage(LOG_DEBUG, "waitForPDAMessageType: received 0x%02hhx\n",mtype); + + return true; +} + +bool set_PDA_numeric_field_value(struct aqualinkdata *aq_data, int val, int *cur_val, char *select_label, int step) { + int i; + + // Should probably change below to call find_pda_menu_item(), rather than doing it here + // If we lease this, need to limit on the number of loops + while ( strncmp(pda_m_hlight(), select_label, 8) != 0 ) { + send_cmd(KEY_PDA_DOWN, aq_data); + delay(500); // Last message probably was CMD_PDA_HIGHLIGHT, so wait before checking. + waitForPDAMessageType(aq_data,CMD_PDA_HIGHLIGHT,2); + } + + send_cmd(KEY_PDA_SELECT, aq_data); + + if (val < *cur_val) { + logMessage(LOG_DEBUG, "PDA %s value : lower from %d to %d\n", select_label, *cur_val, val); + for (i = *cur_val; i > val; i=i-step) { + send_cmd(KEY_PDA_DOWN, aq_data); + } + } else if (val > *cur_val) { + logMessage(LOG_DEBUG, "PDA %s value : raise from %d to %d\n", select_label, *cur_val, val); + for (i = *cur_val; i < val; i=i+step) { + send_cmd(KEY_PDA_UP, aq_data); + } + } else { + logMessage(LOG_INFO, "PDA %s value : already at %d\n", select_label, val); + send_cmd(KEY_PDA_BACK, aq_data); + return true; + } + + send_cmd(KEY_PDA_SELECT, aq_data); + logMessage(LOG_DEBUG, "PDA %s value : set to %d\n", select_label, *cur_val); + + return true; +} + +bool set_PDA_aqualink_SWG_setpoint(struct aqualinkdata *aq_data, int val) { + + if (! goto_pda_menu(aq_data, PM_AQUAPURE)) { + logMessage(LOG_ERR, "Error getting setpoints menu\n"); + } + + if (aq_data->aqbuttons[SPA_INDEX].led->state != OFF) + set_PDA_numeric_field_value(aq_data, val, &aq_data->swg_percent, "SET SPA", 5); + else + set_PDA_numeric_field_value(aq_data, val, &aq_data->swg_percent, "SET POOL", 5); + + return true; +} + +bool set_PDA_aqualink_heater_setpoint(struct aqualinkdata *aq_data, int val, bool isPool) { + char label[10]; + int *cur_val; + + if (isPool) { + sprintf(label, "POOL HEAT"); + cur_val = &aq_data->pool_htr_set_point; + } else { + sprintf(label, "SPA HEAT"); + cur_val = &aq_data->spa_htr_set_point; + } + + if (val == *cur_val) { + logMessage(LOG_INFO, "PDA %s setpoint : temp already %d\n", label, val); + send_cmd(KEY_PDA_BACK, aq_data); + return true; + } + + if (! goto_pda_menu(aq_data, PM_SET_TEMP)) { + logMessage(LOG_ERR, "Error getting setpoints menu\n"); + } + + set_PDA_numeric_field_value(aq_data, val, cur_val, label, 1); + + return true; +} + +bool set_PDA_aqualink_freezeprotect_setpoint(struct aqualinkdata *aq_data, int val) { + + if (! goto_pda_menu(aq_data, PM_FREEZE_PROTECT)) { + logMessage(LOG_ERR, "Error getting setpoints menu\n"); + } + + set_PDA_numeric_field_value(aq_data, val, &aq_data->frz_protect_set_point, "TEMP", 1); + + return true; +} + + +/* +bool waitForPDAMessage(struct aqualinkdata *aq_data, int numMessageReceived, unsigned char packettype) +{ + logMessage(LOG_DEBUG, "waitForPDAMessage %s %d\n",message,numMessageReceived); + int i=0; + pthread_mutex_init(&aq_data->active_thread.thread_mutex, NULL); + pthread_mutex_lock(&aq_data->active_thread.thread_mutex); + char* msgS; + char* ptr; + + if (message != NULL) { + if (message[0] == '^') + msgS = &message[1]; + else + msgS = message; + } + + while( ++i <= numMessageReceived) + { + if (message != NULL) + logMessage(LOG_DEBUG, "Programming mode: loop %d of %d looking for '%s' received message '%s'\n",i,numMessageReceived,message,aq_data->last_message); + else + logMessage(LOG_DEBUG, "Programming mode: loop %d of %d waiting for next message, received '%s'\n",i,numMessageReceived,aq_data->last_message); + + if (message != NULL) { + ptr = stristr(aq_data->last_message, msgS); + if (ptr != NULL) { // match + logMessage(LOG_DEBUG, "Programming mode: String MATCH\n"); + if (msgS == message) // match & don't care if first char + break; + else if (ptr == aq_data->last_message) // match & do care if first char + break; + } + } + + //logMessage(LOG_DEBUG, "Programming mode: looking for '%s' received message '%s'\n",message,aq_data->last_message); + pthread_cond_init(&aq_data->active_thread.thread_cond, NULL); + pthread_cond_wait(&aq_data->active_thread.thread_cond, &aq_data->active_thread.thread_mutex); + //logMessage(LOG_DEBUG, "Programming mode: loop %d of %d looking for '%s' received message '%s'\n",i,numMessageReceived,message,aq_data->last_message); + } + + pthread_mutex_unlock(&aq_data->active_thread.thread_mutex); + + if (message != NULL && ptr == NULL) { + //logMessage(LOG_ERR, "Could not select MENU of Aqualink control panel\n"); + logMessage(LOG_DEBUG, "Programming mode: did not find '%s'\n",message); + return false; + } else if (message != NULL) + logMessage(LOG_DEBUG, "Programming mode: found message '%s' in '%s'\n",message,aq_data->last_message); + + return true; +} + +*/ + + +/* +Link to two different menu's used in PDA +http://www.poolequipmentpriceslashers.com.au/wp-content/uploads/2012/11/Jandy-Aqualink-RS-PDA-Wireless-Pool-Controller_manual.pdf +https://www.jandy.com/-/media/zodiac/global/downloads/h/h0574200.pdf +*/ + +/* + List of how menu's display + +PDA Line 0 = +PDA Line 1 = AquaPalm +PDA Line 2 = +PDA Line 3 = Firmware Version +PDA Line 4 = +PDA Line 5 = REV MMM +PDA Line 6 = +PDA Line 7 = +PDA Line 8 = +PDA Line 9 = + +***************** Think this is startup different rev ************* +Line 0 = +Line 1 = PDA-PS4 Combo +Line 2 = +Line 3 = Firmware Version +Line 4 = +Line 5 = PPD: PDA 1.2 + +PDA Line 0 = +PDA Line 1 = AIR POOL +PDA Line 2 = +PDA Line 3 = +PDA Line 4 = POOL MODE ON +PDA Line 5 = POOL HEATER OFF +PDA Line 6 = SPA MODE OFF +PDA Line 7 = SPA HEATER OFF +PDA Line 8 = MENU +PDA Line 9 = EQUIPMENT ON/OFF + +PDA Line 0 = MAIN MENU +PDA Line 1 = +PDA Line 2 = SET TEMP > +PDA Line 3 = SET TIME > +PDA Line 4 = SET AquaPure > +PDA Line 5 = PALM OPTIONS > +PDA Line 6 = +PDA Line 7 = BOOST POOL +PDA Line 8 = +PDA Line 9 = + +**************** OPTION 2 FOR THIS MENU ******************** +PDA Line 0 = MAIN MENU +PDA Line 1 = +PDA Line 2 = HELP > +PDA Line 3 = PROGRAM > +PDA Line 4 = SET TEMP > +PDA Line 5 = SET TIME > +PDA Line 6 = PDA OPTIONS > +PDA Line 7 = SYSTEM SETUP > +PDA Line 8 = +PDA Line 9 = BOOST + +********** Guess at SYSTEM SETUP Menu (not on Rev MMM or before)************ + +// PDA Line 0 = SYSTEM SETUP +// PDA Line 1 = LABEL AUX > +// PDA Line 2 = FREEZE PROTECT > +// PDA Line 3 = AIR TEMP > +// PDA Line 4 = DEGREES C/F > +// PDA Line 5 = TEMP CALIBRATE > +// PDA Line 6 = SOLAR PRIORITY > +// PDA Line 7 = PUMP LOCKOUT > +// PDA Line 8 = ASSIGN JVAs > +// PDA Line 9 = ^^ MORE __ +// PDA Line 5 = COLOR LIGHTS > +// PDA Line 6 = SPA SWITCH > +// PDA Line 7 = SERVICE INFO > +// PDA Line 8 = CLEAR MEMORY > + + + +PDA Line 0 = PALM OPTIONS +PDA Line 1 = +PDA Line 2 = +PDA Line 3 = SET AUTO-OFF > +PDA Line 4 = BACKLIGHT > +PDA Line 5 = ASSIGN HOTKEYS > +PDA Line 6 = +PDA Line 7 = Choose setting +PDA Line 8 = and press SELECT +PDA Line 9 = + +PDA Line 0 = SET AquaPure +PDA Line 1 = +PDA Line 2 = +PDA Line 3 = SET POOL TO: 45% +PDA Line 4 = SET SPA TO: 0% +PDA Line 5 = +PDA Line 6 = +PDA Line 7 = Highlight an +PDA Line 8 = item and press +PDA Line 9 = SELECT + +PDA Line 0 = SET TIME +PDA Line 1 = +PDA Line 2 = 05/22/19 WED +PDA Line 3 = 10:53 AM +PDA Line 4 = +PDA Line 5 = +PDA Line 6 = Use ARROW KEYS +PDA Line 7 = to set value. +PDA Line 8 = Press SELECT +PDA Line 9 = to continue. + +PDA Line 0 = SET TEMP +PDA Line 1 = +PDA Line 2 = POOL HEAT 70`F +PDA Line 3 = SPA HEAT 98`F +PDA Line 4 = +PDA Line 5 = +PDA Line 6 = +PDA Line 7 = Highlight an +PDA Line 8 = item and press +PDA Line 9 = SELECT + + + + + +PDA Line 0 = EQUIPMENT +PDA Line 1 = FILTER PUMP ON +PDA Line 2 = SPA OFF +PDA Line 3 = POOL HEAT OFF +PDA Line 4 = SPA HEAT OFF +PDA Line 5 = CLEANER ON +PDA Line 6 = WATERFALL OFF +PDA Line 7 = AIR BLOWER OFF +PDA Line 8 = LIGHT OFF +PDA Line 9 = ^^ MORE __ + +PDA Line 0 = EQUIPMENT +PDA Line 1 = WATERFALL OFF +PDA Line 2 = AIR BLOWER OFF +PDA Line 3 = LIGHT OFF +PDA Line 4 = AUX5 OFF +PDA Line 5 = EXTRA AUX OFF +PDA Line 6 = SPA MODE OFF +PDA Line 7 = CLEAN MODE OFF +PDA Line 8 = ALL OFF +PDA Line 9 = + +*/ \ No newline at end of file diff --git a/pda_aq_programmer.h b/pda_aq_programmer.h new file mode 100644 index 0000000..3408838 --- /dev/null +++ b/pda_aq_programmer.h @@ -0,0 +1,21 @@ + +#ifndef PDA_AQ_PROGRAMMER_H_ +#define PDA_AQ_PROGRAMMER_H_ + +typedef enum pda_type { + AQUAPALM, + PDA +} pda_type; + +void *get_aqualink_PDA_device_status( void *ptr ); +void *set_aqualink_PDA_device_on_off( void *ptr ); +void *set_aqualink_PDA_wakeinit( void *ptr ); + +bool set_PDA_aqualink_heater_setpoint(struct aqualinkdata *aq_data, int val, bool isPool); +bool set_PDA_aqualink_SWG_setpoint(struct aqualinkdata *aq_data, int val); +bool set_PDA_aqualink_freezeprotect_setpoint(struct aqualinkdata *aq_data, int val); + +bool get_PDA_aqualink_pool_spa_heater_temps(struct aqualinkdata *aq_data); +bool get_PDA_freeze_protect_temp(struct aqualinkdata *aq_data); + +#endif // AQ_PDA_PROGRAMMER_H_ \ No newline at end of file diff --git a/pda_menu.c b/pda_menu.c index 442afae..ef6f8e4 100644 --- a/pda_menu.c +++ b/pda_menu.c @@ -39,23 +39,81 @@ char *pda_m_line(int index) // return NULL; } +int pda_find_m_index(char *text) +{ + int i; + + for (i = 0; i < PDA_LINES; i++) { + if (strncmp(pda_m_line(i), text, strlen(text)) == 0) + return i; + } + + return -1; +} + +int pda_find_m_index_case(char *text, int limit) +{ + int i; + + for (i = 0; i < PDA_LINES; i++) { + //printf ("+++ Compare '%s' to '%s' index %d\n",text,pda_m_line(i),i); + if (strncasecmp(pda_m_line(i), text, limit) == 0) + return i; + } + + return -1; +} + pda_menu_type pda_m_type() { + if (strncmp(_menu[1],"AIR ", 5) == 0) - return PM_MAIN; + return PM_HOME; else if (strncmp(_menu[0],"EQUIPMENT STATUS", 16) == 0) return PM_EQUIPTMENT_STATUS; else if (strncmp(_menu[0]," EQUIPMENT ", 16) == 0) return PM_EQUIPTMENT_CONTROL; else if (strncmp(_menu[0]," MAIN MENU ", 16) == 0) - return PM_SETTINGS; + return PM_MAIN; //else if ((_menu[0] == '\0' && _hlightindex == -1) || strncmp(_menu[4], "POOL MODE", 9) == 0 )// IF we are building the main menu this may be valid - else if (strncmp(_menu[4], "POOL MODE", 9) == 0 ) - return PM_BUILDING_MAIN; - + else if (strncmp(_menu[4], "POOL MODE", 9) == 0 ) { + if (pda_m_hlightindex() == -1) + return PM_BUILDING_HOME; + else + return PM_HOME; + } + else if (strncmp(_menu[0]," SET TEMP ", 16) == 0) + return PM_SET_TEMP; + else if (strncmp(_menu[0]," SET TIME ", 16) == 0) + return PM_SET_TIME; + else if (strncmp(_menu[0]," SET AquaPure ", 16) == 0) + return PM_AQUAPURE; + else if (strncmp(_menu[0]," SPA HEAT ", 16) == 0) + return PM_SPA_HEAT; + else if (strncmp(_menu[0]," POOL HEAT ", 16) == 0) + return PM_POOL_HEAT; + else if (strncmp(_menu[6],"Use ARROW KEYS ", 16) == 0 && + strncmp(_menu[0]," FREEZE PROTECT ", 16) == 0) + return PM_FREEZE_PROTECT; + else if (strncmp(_menu[1]," DEVICES ", 16) == 0 && + strncmp(_menu[0]," FREEZE PROTECT ", 16) == 0) + return PM_FREEZE_PROTECT_DEVICES; + else if (strncmp(_menu[3],"Firmware Version", 16) == 0 || + strncmp(_menu[1]," AquaPalm", 12) == 0 || + strncmp(_menu[1]," PDA-PS4 Combo", 14) == 0) + return PM_FW_VERSION; + return PM_UNKNOWN; } + + + + + + + + /* --- Main Menu --- Line 0 = @@ -86,15 +144,105 @@ bool process_pda_menu_packet(unsigned char* packet, int length) if (getLogLevel() >= LOG_DEBUG){print_menu();} break; case CMD_PDA_HIGHLIGHT: - _hlightindex = packet[4],_menu[packet[4]]; + // when switching from hlight to hlightchars index 255 is sent to turn off hlight + if (packet[4] <= PDA_LINES) { + _hlightindex = packet[4]; + } else { + _hlightindex = -1; + } + if (getLogLevel() >= LOG_DEBUG){print_menu();} + break; + case CMD_PDA_HIGHLIGHTCHARS: + if (packet[4] <= PDA_LINES) { + _hlightindex = packet[4]; + } else { + _hlightindex = -1; + } if (getLogLevel() >= LOG_DEBUG){print_menu();} break; case CMD_PDA_SHIFTLINES: memcpy(_menu[1], _menu[2], (PDA_LINES-1) * (AQ_MSGLEN+1) ); if (getLogLevel() >= LOG_DEBUG){print_menu();} - break; - + break; } return rtn; } + + +#ifdef SOME_CRAP +bool NEW_process_pda_menu_packet_NEW(unsigned char* packet, int length) +{ + bool rtn = true; + signed char first_line; + signed char last_line; + signed char line_shift; + signed char i; + + pthread_mutex_lock(&_pda_menu_mutex); + switch (packet[PKT_CMD]) { + case CMD_STATUS: + pthread_cond_signal(&_pda_menu_update_complete_cond); + break; + case CMD_PDA_CLEAR: + rtn = pda_m_clear(); + break; + case CMD_MSG_LONG: + if (packet[PKT_DATA] < 10) { + memset(_menu[packet[PKT_DATA]], 0, AQ_MSGLEN); + strncpy(_menu[packet[PKT_DATA]], (char*)packet+PKT_DATA+1, AQ_MSGLEN); + _menu[packet[PKT_DATA]][AQ_MSGLEN] = '\0'; + } + if (packet[PKT_DATA] == _hlightindex) { + logMessage(LOG_DEBUG, "process_pda_menu_packet: hlight changed from shift or up/down value\n"); + pthread_cond_signal(&_pda_menu_hlight_change_cond); + } + if (getLogLevel() >= LOG_DEBUG){print_menu();} + update_pda_menu_type(); + break; + case CMD_PDA_HIGHLIGHT: + // when switching from hlight to hlightchars index 255 is sent to turn off hlight + if (packet[4] <= PDA_LINES) { + _hlightindex = packet[4]; + } else { + _hlightindex = -1; + } + pthread_cond_signal(&_pda_menu_hlight_change_cond); + if (getLogLevel() >= LOG_DEBUG){print_menu();} + break; + case CMD_PDA_HIGHLIGHTCHARS: + if (packet[4] <= PDA_LINES) { + _hlightindex = packet[4]; + } else { + _hlightindex = -1; + } + pthread_cond_signal(&_pda_menu_hlight_change_cond); + if (getLogLevel() >= LOG_DEBUG){print_menu();} + break; + case CMD_PDA_SHIFTLINES: + // press up from top - shift menu down by 1 + // PDA Shif | HEX: 0x10|0x02|0x62|0x0f|0x01|0x08|0x01|0x8d|0x10|0x03| + // press down from bottom - shift menu up by 1 + // PDA Shif | HEX: 0x10|0x02|0x62|0x0f|0x01|0x08|0xff|0x8b|0x10|0x03| + first_line = (signed char)(packet[4]); + last_line = (signed char)(packet[5]); + line_shift = (signed char)(packet[6]); + logMessage(LOG_DEBUG, "\n"); + if (line_shift < 0) { + for (i = first_line-line_shift; i <= last_line; i++) { + memcpy(_menu[i+line_shift], _menu[i], AQ_MSGLEN+1); + } + } else { + for (i = last_line; i >= first_line+line_shift; i--) { + memcpy(_menu[i], _menu[i-line_shift], AQ_MSGLEN+1); + } + } + if (getLogLevel() >= LOG_DEBUG){print_menu();} + break; + + } + pthread_mutex_unlock(&_pda_menu_mutex); + + return rtn; +} +#endif \ No newline at end of file diff --git a/pda_menu.h b/pda_menu.h index c07dec1..06e99b4 100644 --- a/pda_menu.h +++ b/pda_menu.h @@ -5,6 +5,38 @@ #define PDA_LINES 10 // There is only 9 lines, but add buffer to make shifting easier +typedef enum pda_menu_type { + PM_UNKNOWN, + PM_FW_VERSION, + PM_HOME, + PM_BUILDING_HOME, + PM_MAIN, + PM_DIAGNOSTICS, + PM_PROGRAM, + PM_SET_TEMP, + PM_SET_TIME, + PM_POOL_HEAT, + PM_SPA_HEAT, + PM_AQUAPURE, + PM_SYSTEM_SETUP, + PM_AUX_LABEL, + PM_FREEZE_PROTECT, + PM_FREEZE_PROTECT_DEVICES, + PM_VSP, + PM_SETTINGS, + PM_EQUIPTMENT_CONTROL, + PM_EQUIPTMENT_STATUS, + PM_PALM_OPTIONS // This seems to be only older revisions +} pda_menu_type; + +/* +typedef enum pda_home_menu_item { + PMI_MAIN, + PMI_EQUIPTMENT_CONTROL +} pda_home_menu_item; +*/ + +/* typedef enum pda_menu_type { PM_UNKNOWN, PM_MAIN, @@ -13,7 +45,25 @@ typedef enum pda_menu_type { PM_EQUIPTMENT_STATUS, PM_BUILDING_MAIN } pda_menu_type; - +*/ +/* +typedef enum pda_menu_type { + PM_UNKNOWN, + PM_FW_VERSION, + PM_HOME, + PM_MAIN_MENU, + PM_EQUIPTMENT_CONTROL, + PM_EQUIPTMENT_STATUS, + PM_BUILDING_HOME, + PM_SYSTEM_SETUP, + PM_SET_TEMP, + PM_POOL_HEAT, + PM_SPA_HEAT, + PM_FREEZE_PROTECT, + PM_FREEZE_PROTECT_DEVICES, + PM_SET_AQUAPURE +} pda_menu_type; +*/ bool pda_mode(); void set_pda_mode(bool val); bool process_pda_menu_packet(unsigned char* packet, int length); @@ -21,5 +71,7 @@ int pda_m_hlightindex(); char *pda_m_hlight(); char *pda_m_line(int index); pda_menu_type pda_m_type(); +int pda_find_m_index(char *text); +int pda_find_m_index_case(char *text, int limit); #endif diff --git a/release/aqualinkd b/release/aqualinkd index 169f968..90b8442 100755 Binary files a/release/aqualinkd and b/release/aqualinkd differ diff --git a/release/aqualinkd.conf b/release/aqualinkd.conf index d2443e1..867a79b 100755 --- a/release/aqualinkd.conf +++ b/release/aqualinkd.conf @@ -60,10 +60,27 @@ report_zero_pool_temp = no # Working RS ID's are 0x0a 0x0b 0x09 0x08 <- 0x08 is usually taken device_id=0x0a +# Please see forum for this, only set to yes when logging information to support +# new devices. Inflrmation will be written to /tmp/RS485.log +#debug_RSProtocol_packets = no + +#Only for PDA mode +# set PDA mode +#pda_mode = yes +# +# Put AqualinkD to sleep when in PDA mode after inactivity. +# If you have Jandy PDA then this MUST be set to yes as the controller can only support one PDA. +# If you don't have a Jandy PDA leave this at no as AqualinkD will be a lot quicker. +# Sleep timer is around 2 mins of inactivity, then wake after 2 mins of sleep. +#pda_sleep_mode = yes + # Read status information from other devices on the RS485 bus. # At the moment just Salt Water Generators are supported. read_all_devices = yes +# If you have a SWG connected to the control panel, set this to yes. +# AqualinkD can only detect a SWG if it's on, so after a restart you will not see/access a SWG until the the next time the pump is on. +force_SWG = no # Button inxed light probramming button is assigned to. (look at your button labels below) light_programming_button = 0 @@ -91,6 +108,10 @@ spa_water_temp_dzidx=0 SWG_percent_dzidx=0 SWG_PPM_dzidx=0 + +# Try to use labels from Control Panel. +use_panel_aux_labels=yes + # Labels for standard butons (shown in web UI), and domoticz idx's button_01_label=Filter Pump #button_01_dzidx=37 diff --git a/release/aqualinkd.pda.conf b/release/aqualinkd.pda.conf index e507a56..3fc9490 100644 --- a/release/aqualinkd.pda.conf +++ b/release/aqualinkd.pda.conf @@ -19,8 +19,8 @@ web_directory=/nas/data/Development/Raspberry/AqualinkD/web # DEBUG would print everything possible #log_level=DEBUG_SERIAL -log_level=DEBUG -#log_level=INFO +#log_level=DEBUG +log_level=INFO #log_level=NOTICE # The socket port that the daemon listens to @@ -33,13 +33,13 @@ serial_port=/dev/ttyUSB0 override_freeze_protect = no # mqtt stuff -mqtt_address = trident:1883 +#mqtt_address = trident:1883 #mqtt_user = someusername #mqtt_passwd = somepassword #mqtt_dz_pub_topic = domoticz/in #mqtt_dz_sub_topic = domoticz/out -mqtt_aq_topic = aqualinkd_test +#mqtt_aq_topic = aqualinkd # The id of the Aqualink terminal device. Devices probed by RS8 master are: # 08-0b, 10-13, 18-1b, 20-23, 28-2b, 30-33, 38-3b, 40-43 @@ -49,6 +49,7 @@ mqtt_aq_topic = aqualinkd_test #device_id=0x09 device_id=0x60 pda_mode = yes +pda_sleep_mode = yes convert_mqtt_temp_to_c = yes convert_dz_temp_to_c = yes @@ -101,15 +102,15 @@ button_03_PDA_label=CLEANER button_04_label=Waterfall #button_04_dzidx=40 -button_04_PDA_label=AUX2 +button_04_PDA_label=WATERFALL button_05_label=Spa Blower #button_05_dzidx=41 -button_05_PDA_label=AUX3 +button_05_PDA_label=AIR BLOWER button_06_label=Pool Light #button_06_dzidx=42 -button_06_PDA_label=AUX4 +button_06_PDA_label=LIGHT button_07_label=NONE #button_07_dzidx=43 diff --git a/release/serial_logger b/release/serial_logger index c3593ce..d8f5225 100755 Binary files a/release/serial_logger and b/release/serial_logger differ diff --git a/serial_logger.c b/serial_logger.c index 222960c..695464c 100644 --- a/serial_logger.c +++ b/serial_logger.c @@ -217,7 +217,7 @@ int main(int argc, char *argv[]) { } if (packet_buffer[PKT_DEST] == DEV_MASTER /*&& packet_buffer[PKT_CMD] == CMD_ACK*/) { - //logMessage(LOG_DEBUG_SERIAL, "ID is in use 0x%02hhx %x\n", lastID, lastID); + //logMessage(LOG_NOTICE, "ID is in use 0x%02hhx %x\n", lastID, lastID); for (i = 0; i <= sindex; i++) { if (slog[i].ID == lastID) { slog[i].inuse = true; @@ -257,7 +257,7 @@ int main(int argc, char *argv[]) { if (sindex >= SLOG_MAX) logMessage(LOG_ERR, "Ran out of storage, some ID's were not captured, please increase SLOG_MAX and recompile\n"); logMessage(LOG_NOTICE, "ID's found\n"); - for (i = 0; i <= sindex; i++) { + for (i = 0; i < sindex; i++) { //logMessage(LOG_NOTICE, "ID 0x%02hhx is %s %s\n", slog[i].ID, (slog[i].inuse == true) ? "in use" : "not used", // (slog[i].inuse == false && canUse(slog[i].ID) == true)? " <-- can use for Aqualinkd" : ""); diff --git a/utils.c b/utils.c index 39098b3..258d38c 100644 --- a/utils.c +++ b/utils.c @@ -482,4 +482,50 @@ int ascii(char *destination, char *source) { } destination[i] = '\0'; return i; +} + +char *prittyString(char *str) +{ + char *ptr = str; + char *end; + bool lastspace=true; + + end = str + strlen(str) - 1; + while(end >= ptr){ + //printf("%d %s ", *ptr, ptr); + if (lastspace && *ptr > 96 && *ptr < 123) { + *ptr = *ptr - 32; + lastspace=false; + //printf("to upper\n"); + } else if (lastspace == false && *ptr > 54 && *ptr < 91) { + *ptr = *ptr + 32; + lastspace=false; + //printf("to lower\n"); + } else if (*ptr == 32) { + lastspace=true; + //printf("space\n"); + } else { + lastspace=false; + //printf("leave\n"); + } + ptr++; + } + + //printf("-- %s --\n", str); + + return str; +} + +static FILE *_packetLogFile = NULL; + +void writePacketLog(char *buffer) { + if (_packetLogFile == NULL) + _packetLogFile = fopen("/tmp/RS485.log", "a"); + + if (_packetLogFile != NULL) { + fputs(buffer, _packetLogFile); + } +} +void closePacketLog() { + fclose(_packetLogFile); } \ No newline at end of file diff --git a/utils.h b/utils.h index a94bae1..c2bdee5 100644 --- a/utils.h +++ b/utils.h @@ -49,7 +49,9 @@ float degFtoC(float degF); float degCtoF(float degC); char* stristr(const char* haystack, const char* needle); int ascii(char *destination, char *source); - +char *prittyString(char *str); +void writePacketLog(char *buff); +void closePacketLog(); //#ifndef _UTILS_C_ extern bool _daemon_; diff --git a/version.h b/version.h index 584842b..e187202 100644 --- a/version.h +++ b/version.h @@ -1,4 +1,4 @@ #define AQUALINKD_NAME "Aqualink Daemon" -#define AQUALINKD_VERSION "1.2.6f" +#define AQUALINKD_VERSION "1.3.0" diff --git a/web/controller.html b/web/controller.html index 7d3f390..092396a 100644 --- a/web/controller.html +++ b/web/controller.html @@ -1178,7 +1178,8 @@ } else if (data.type == 'devices') { check_devices(data); resetBackgroundSize(); - window.setTimeout(get_devices, (300 * 1000)); // Check for new dvices ever 5 mins. + //window.setTimeout(get_devices, (300 * 1000)); // Check for new dvices ever 5 mins. + window.setTimeout(get_devices, (60 * 1000)); // Check for new dvices ever 1 mins. } } socket_di.onclose = function() {