/* * Copyright (c) 2017 Shaun Feakes - All rights reserved * * You may use redistribute and/or modify this code under the terms of * the GNU General Public License version 2 as published by the * Free Software Foundation. For the terms of this license, * see . * * You are free to use this software under the terms of the GNU General * Public License, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * https://github.com/sfeakes/aqualinkd */ #define _GNU_SOURCE 1 // for strcasestr & strptime #define __USE_XOPEN 1 #include #include #include #include #include #include #include #include #include #include // Need GNU_SOURCE & XOPEN defined for strptime #define AQUALINKD_C #include "mongoose.h" #include "aqualink.h" #include "utils.h" #include "config.h" #include "aq_serial.h" #include "init_buttons.h" #include "aq_programmer.h" #include "net_services.h" #include "pda_menu.h" #include "pda.h" #include "devices_pentair.h" #include "pda_aq_programmer.h" #include "packetLogger.h" #include "devices_jandy.h" #include "onetouch.h" #include "onetouch_aq_programmer.h" #include "version.h" //#define DEFAULT_CONFIG_FILE "./aqualinkd.conf" static volatile bool _keepRunning = true; //static struct aqconfig _aqconfig_; static struct aqualinkdata _aqualink_data; void main_loop(); void intHandler(int dummy) { _keepRunning = false; logMessage(LOG_NOTICE, "Stopping!"); if (dummy){}// stop compile warnings } void processLEDstate() { int i = 0; int byte; int bit; for (byte = 0; byte < 5; byte++) { 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) _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; /* 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 int time_difference; struct tm aq_tm; time_t aqualink_time; time_difference = (int)difftime(now, last_checked); 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 { 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) { 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 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) { // Time difference is less than or equal to 90 seconds (1 1/2 minutes). // Set the return value to true. return true; } /* char buff[30]; struct tm * timeinfo; timeinfo = localtime (&now); strftime(buff, sizeof(buff), "%y %b %d %H:%M:%S", timeinfo); logMessage(LOG_DEBUG, "System time %s\n", buff); timeinfo = localtime (&aqualink_time); strftime(buff, sizeof(buff), "%y %b %d %H:%M:%s", timeinfo); logMessage(LOG_DEBUG, "Aqualink time %s\n", buff); char datestring[256]; //time_t t = time(0); // get time now struct tm * tm = localtime( & now ); strftime (datestring, sizeof(datestring), "%m/%d/%y %a %I:%M %p %z", tm); printf("Computer '%s'\n",datestring); strftime (datestring, sizeof(datestring), "%m/%d/%y %a %I:%M %p %z", &aq_tm); printf("Aqualink '%s'\n",datestring); printf("test '%s'\n",datestring); */ return false; } /* void aqualink_strcpy(char *dest, char *src) { int i; for (i=0; i 125 || src[i] < 32 || src[i] == 34 || src[i] == 42 || src[i] == 96 || src[i] == 39 || src[i] == 92) dest[i] = 32; else dest[i] = src[i]; } dest[i] = '\0'; //dest[10] = '\0'; } */ /* void queueGetProgramData() { //aq_programmer(AQ_GET_DIAGNOSTICS_MODEL, NULL, &_aqualink_data); // Init string good time to get setpoints //aq_programmer(AQ_SEND_CMD, (char *)KEY_ENTER, &_aqualink_data); //aq_programmer(AQ_SEND_CMD, (char *)*NUL, &_aqualink_data); #ifndef DEBUG_NO_INIT_TEST_REMOVE aq_send_cmd(NUL); aq_programmer(AQ_GET_POOL_SPA_HEATER_TEMPS, NULL, &_aqualink_data); aq_programmer(AQ_GET_FREEZE_PROTECT_TEMP, NULL, &_aqualink_data); #endif //aq_programmer(AQ_GET_POOL_SPA_HEATER_TEMPS, NULL, &_aqualink_data); if (_aqconfig_.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) { char buf[AQ_MSGLEN*3]; ascii(buf, msg); logMessage(LOG_DEBUG, "Getting temp units from message '%s', looking at '%c'\n", buf, buf[strlen(buf) - 1]); 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; logMessage(LOG_INFO, "Temp Units set to %d (F=0, C=1, Unknown=2)\n", _aqualink_data.temp_units); } #ifdef AQ_RS16 bool RS16_endswithLEDstate(char *msg) { char *sp; int i; aqledstate state = LED_S_UNKNOWN; if (_aqconfig_.rs_panel_size < 16) return false; sp = strrchr(msg, ' '); if( sp == NULL ) return false; if (strncasecmp(sp, " on", 3) == 0) state = ON; else if (strncasecmp(sp, " off", 4) == 0) state = OFF; else if (strncasecmp(sp, " enabled", 8) == 0) // Total guess, need to check state = ENABLE; else if (strncasecmp(sp, " no idea", 8) == 0) // need to figure out these states state = FLASH; if (state == LED_S_UNKNOWN) return false; // Only need to start at Aux B5->B8 (12-15) // Loop over only aqdata->aqbuttons[13] to aqdata->aqbuttons[16] for (i = RS16_VBUTTONS_START; i <= RS16_VBUTTONS_END; i++) { //TOTAL_BUTTONS if ( stristr(msg, _aqualink_data.aqbuttons[i].label) != NULL) { _aqualink_data.aqbuttons[i].led->state = state; logMessage(LOG_INFO, "Set %s to %d\n", _aqualink_data.aqbuttons[i].label, _aqualink_data.aqbuttons[i].led->state); // Return true should be the result, but in the if we want to continue to display message return true; } } return false; } #endif #define MSG_FREEZE 1 // 2^0, bit 0 #define MSG_SERVICE 2 // 2^1, bit 1 #define MSG_SWG 4 // 2^2, bit 2 #define MSG_BOOST 8 // 2^3, bit 3 void processMessage(char *message) { char *msg; static bool _initWithRS = false; static bool _gotREV = false; //static int freeze_msg_count = 0; //static int service_msg_count = 0; //static int swg_msg_count = 0; //static int boost_msg_count = 0; static unsigned char msg_loop = '\0'; // 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_NOTICE, "RS Message :- '%s'\n",msg); // Just set this to off, it will re-set since it'll be the only message we get if on _aqualink_data.service_mode_state = OFF; // 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) { //_aqualink_data.display_message = NULL; _aqualink_data.last_display_message[0] = '\0'; // Anything that wasn't on during the last set of messages, turn off if ((msg_loop & MSG_FREEZE) != MSG_FREEZE) _aqualink_data.frz_protect_state = OFF; //if ((msg_loop & MSG_SERVICE) != MSG_SERVICE) // _aqualink_data.service_mode_state = OFF; // IF we get this message then Service / Timeout is off if ((msg_loop & MSG_SWG) != MSG_SWG) _aqualink_data.ar_swg_status = SWG_STATUS_OFF; if ((msg_loop & MSG_BOOST) != MSG_BOOST) { _aqualink_data.boost = false; _aqualink_data.boost_msg[0] = '\0'; if (_aqualink_data.swg_percent >= 101) _aqualink_data.swg_percent = 0; } msg_loop = '\0'; } 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) { //logMessage(LOG_DEBUG, "**************** pool htr long message: %s", &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) { //logMessage(LOG_DEBUG, "spa htr long message: %s", &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) { //logMessage(LOG_DEBUG, "frz protect long message: %s", &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); 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); if (_aqualink_data.temp_units == UNKNOWN) setUnits(msg); if (_aqualink_data.single_device != true) { _aqualink_data.single_device = true; 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); if (_aqualink_data.temp_units == UNKNOWN) setUnits(msg); if (_aqualink_data.single_device != true) { _aqualink_data.single_device = true; 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); if (_aqualink_data.temp_units == UNKNOWN) setUnits(msg); if (_aqualink_data.single_device != true) { _aqualink_data.single_device = true; logMessage(LOG_NOTICE, "AqualinkD set to 'Pool OR Spa Only' mode\n"); } } 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"); _aqualink_data.service_mode_state = ON; msg_loop |= MSG_SERVICE; //service_msg_count = 0; } else if (stristr(msg, LNG_MSG_TIMEOUT_ACTIVE) != NULL) { if (_aqualink_data.service_mode_state == OFF) logMessage(LOG_NOTICE, "AqualinkD set to Timeout Mode\n"); _aqualink_data.service_mode_state = FLASH; msg_loop |= MSG_SERVICE; //service_msg_count = 0; } else if (stristr(msg, LNG_MSG_FREEZE_PROTECTION_ACTIVATED) != NULL) { msg_loop |= MSG_FREEZE; _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' strcpy(_aqualink_data.date, msg); } //else if (strncasecmp(msg, MSG_SWG_PCT, MSG_SWG_PCT_LEN) == 0) else if (strncasecmp(msg, MSG_SWG_PCT, MSG_SWG_PCT_LEN) == 0 && strncasecmp(msg, "AQUAPURE HRS", 12) != 0) { _aqualink_data.swg_percent = atoi(msg + MSG_SWG_PCT_LEN); if (_aqualink_data.ar_swg_status == SWG_STATUS_OFF) {_aqualink_data.ar_swg_status = SWG_STATUS_ON;} //swg_msg_count = 0; msg_loop |= MSG_SWG; //logMessage(LOG_DEBUG, "*** '%s' ***\n", msg); //logMessage(LOG_DEBUG, "SWG set to %d due to message from control panel\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); if (_aqualink_data.ar_swg_status == SWG_STATUS_OFF) {_aqualink_data.ar_swg_status = SWG_STATUS_ON;} msg_loop |= MSG_SWG; //swg_msg_count = 0; //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' 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) { 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 { 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); 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' // A master firmware revision message. strcpy(_aqualink_data.version, msg); _gotREV = true; logMessage(LOG_NOTICE, "Control Panel %s\n", msg); if (_initWithRS == false) { queueGetProgramData(ALLBUTTON, &_aqualink_data); //queueGetExtendedProgramData(ALLBUTTON, &_aqualink_data, _aqconfig_.use_panel_aux_labels); _initWithRS = true; } } else if (stristr(msg, " TURNS ON") != NULL) { logMessage(LOG_NOTICE, "Program data '%s'\n", msg); } else if (_aqconfig_.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); aq_send_cmd(KEY_ENTER); } // Process any button states (fake LED) for RS12 and above keypads // Text will be button label on or off ie Aux_B2 off or WaterFall off #ifdef AQ_RS16 else if ( _aqconfig_.rs_panel_size >= 16 && RS16_endswithLEDstate(msg) == true ) { // Do nothing, just stop other else if statments executing // make sure we also display the message. // Note we only get ON messages here, Off messages will not be sent if something else turned it off // use the Onetouch or iAqua equiptment page for off. strcpy(_aqualink_data.last_display_message, msg); } #endif else if (((msg[4] == ':') || (msg[6] == ':')) && (strncasecmp(msg, "AUX", 3) == 0) ) { // Should probable check we are in programming mode. // 'Aux3: No Label' // 'Aux B1: No Label' int labelid; int ni = 3; if (msg[4] == 'B') { ni = 5; } labelid = atoi(msg + ni); if (labelid > 0 && _aqconfig_.use_panel_aux_labels == true) { if (ni == 5) labelid = labelid + 8; else labelid = labelid + 1; // Aux1: on panel = Button 3 in aqualinkd (button 2 in array) if (strncasecmp(msg+ni+3, "No Label", 8) != 0) { _aqualink_data.aqbuttons[labelid].label = prittyString(cleanalloc(msg+ni+2)); logMessage(LOG_NOTICE, "AUX ID %s label set to '%s'\n", _aqualink_data.aqbuttons[labelid].name, _aqualink_data.aqbuttons[labelid].label); } else { logMessage(LOG_NOTICE, "AUX ID %s has no control panel label using '%s'\n", _aqualink_data.aqbuttons[labelid].name, _aqualink_data.aqbuttons[labelid].label); } //_aqualink_data.aqbuttons[labelid + 1].label = cleanalloc(msg + 5); } } // BOOST POOL 23:59 REMAINING else if ( (strncasecmp(msg, "BOOST POOL", 10) == 0) && (strcasestr(msg, "REMAINING") != NULL) ) { // Ignore messages if in programming mode. We get one of these turning off for some strange reason. if (_aqualink_data.active_thread.thread_id == 0) { snprintf(_aqualink_data.boost_msg, 6, &msg[11]); _aqualink_data.boost = true; msg_loop |= MSG_BOOST; msg_loop |= MSG_SWG; if (_aqualink_data.ar_swg_status != SWG_STATUS_ON) {_aqualink_data.ar_swg_status = SWG_STATUS_ON;} if (_aqualink_data.swg_percent != 101) {_aqualink_data.swg_percent = 101;} //boost_msg_count = 0; //if (_aqualink_data.active_thread.thread_id == 0) strcpy(_aqualink_data.last_display_message, msg); // Also display the message on web UI if not in programming mode } } 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' strncasecmp(msg, "PUMP O", 6) != 0 &&// Catch 'PUMP ON' and 'PUMP OFF' but not 'PUMP WILL TURN ON' stristr(msg, "MAINTAIN") == NULL && // Catch 'MAINTAIN TEMP IS OFF' stristr(msg, "0 PSI") == NULL /* // Catch some erronious message on test harness stristr(msg, "CLEANER O") == NULL && stristr(msg, "SPA O") == NULL && 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); // We processed the next message, kick any threads waiting on the message. //printf ("Message kicking\n"); kick_aq_program_thread(&_aqualink_data, ALLBUTTON); } 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 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); return rtn; } else { memcpy(last_packet, packet, length); _aqualink_data.last_packet_type = packet[PKT_CMD]; rtn = true; } #ifdef AQ_PDA if (_aqconfig_.pda_mode == true) { return process_pda_packet(packet, length); } #endif 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]) { case CMD_ACK: //logMessage(LOG_DEBUG, "RS Received ACK length %d.\n", length); break; case CMD_STATUS: //logMessage(LOG_DEBUG, "RS Received STATUS length %d.\n", length); memcpy(_aqualink_data.raw_status, packet + 4, AQ_PSTLEN); processLEDstate(); if (_aqualink_data.aqbuttons[PUMP_INDEX].led->state == OFF) { _aqualink_data.pool_temp = TEMP_UNKNOWN; _aqualink_data.spa_temp = TEMP_UNKNOWN; //_aqualink_data.spa_temp = _aqconfig_.report_zero_spa_temp?-18:TEMP_UNKNOWN; } else if (_aqualink_data.aqbuttons[SPA_INDEX].led->state == OFF && _aqualink_data.single_device != true) { //_aqualink_data.spa_temp = _aqconfig_.report_zero_spa_temp?-18:TEMP_UNKNOWN; _aqualink_data.spa_temp = TEMP_UNKNOWN; } else if (_aqualink_data.aqbuttons[SPA_INDEX].led->state == ON && _aqualink_data.single_device != true) { _aqualink_data.pool_temp = TEMP_UNKNOWN; } // COLOR MODE programming relies on state changes, so let any threads know if (_aqualink_data.active_thread.ptype == AQ_SET_LIGHTPROGRAM_MODE) { //printf ("Light thread kicking\n"); kick_aq_program_thread(&_aqualink_data, ALLBUTTON); } break; case CMD_MSG: case CMD_MSG_LONG: { int index = packet[PKT_DATA]; // Will get 0x00 for complete message, 0x01 for start on long message 0x05 last of long message //printf("RSM received message at index %d '%.*s'\n",index,AQ_MSGLEN,(char *)packet + PKT_DATA + 1); if (index <= 1){ memset(message, 0, AQ_MSGLONGLEN + 1); strncpy(message, (char *)packet + PKT_DATA + 1, AQ_MSGLEN); processing_long_msg = index; } else { strncpy(&message[(processing_long_msg * AQ_MSGLEN)], (char *)packet + PKT_DATA + 1, AQ_MSGLEN); if (++processing_long_msg != index) { logMessage(LOG_ERR, "Long message index %d doesn't match buffer %d\n",index,processing_long_msg); //printf("RSM Long message index %d doesn't match buffer %d\n",index,processing_long_msg); } #ifdef PROCESS_INCOMPLETE_MESSAGES kick_aq_program_thread(&_aqualink_data, ALLBUTTON); #endif } if (index == 0 || index == 5) { //printf("RSM process message '%s'\n",message); processMessage(message); // This will kick thread } } break; case CMD_PROBE: logMessage(LOG_DEBUG, "RS Received PROBE length %d.\n", length); //logMessage(LOG_INFO, "Synch'ing with Aqualink master device...\n"); rtn = false; break; case CMD_RS_UNKNOWN: logMessage(LOG_INFO, "RS Received command, 0x%02hhx, not sure what this is for!\n", packet[PKT_CMD]); rtn = false; break; default: logMessage(LOG_INFO, "RS Received unknown packet, 0x%02hhx\n", packet[PKT_CMD]); rtn = false; break; } return rtn; } void action_delayed_request() { char sval[10]; snprintf(sval, 9, "%d", _aqualink_data.unactioned.value); // If we don't know the units yet, we can't action setpoint, so wait until we do. if (_aqualink_data.temp_units == UNKNOWN && (_aqualink_data.unactioned.type == POOL_HTR_SETOINT || _aqualink_data.unactioned.type == SPA_HTR_SETOINT || _aqualink_data.unactioned.type == FREEZE_SETPOINT)) return; 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) { 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); } } 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) { 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); } } 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) { 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); } } 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) { // 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) { 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); } } // 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; } else if (_aqualink_data.unactioned.type == SWG_BOOST) { //logMessage(LOG_NOTICE, "SWG BOST to %d\n", _aqualink_data.unactioned.value); if (_aqualink_data.ar_swg_status == SWG_STATUS_OFF) { logMessage(LOG_ERR, "SWG is off, can't Boost pool\n"); } else if (_aqualink_data.unactioned.value == _aqualink_data.boost ) { logMessage(LOG_ERR, "Request to turn Boost %s ignored, Boost is already %s\n",_aqualink_data.unactioned.value?"On":"Off", _aqualink_data.boost?"On":"Off"); } else { aq_programmer(AQ_SET_BOOST, sval, &_aqualink_data); } // Let's just tell everyone we set it, before we actually did. Makes homekit happy, and it will re-correct on error. _aqualink_data.boost = _aqualink_data.unactioned.value; } else if (_aqualink_data.unactioned.type == PUMP_RPM) { snprintf(sval, 9, "%1d|%d", _aqualink_data.unactioned.id, _aqualink_data.unactioned.value); //printf("**** program string '%s'\n",sval); aq_programmer(AQ_SET_ONETOUCH_PUMP_RPM, sval, &_aqualink_data); } _aqualink_data.unactioned.type = NO_ACTION; _aqualink_data.unactioned.value = -1; _aqualink_data.unactioned.id = -1; _aqualink_data.unactioned.requested = 0; } void printHelp() { printf("%s %s\n",AQUALINKD_NAME,AQUALINKD_VERSION); printf("\t-h (this message)\n"); printf("\t-d (do not deamonize)\n"); printf("\t-c (Configuration file)\n"); printf("\t-v (Debug logging)\n"); printf("\t-vv (Serial Debug logging)\n"); printf("\t-rsd (RS485 debug)\n"); printf("\t-rsrd (RS485 raw debug)\n"); } int main(int argc, char *argv[]) { int i, j; //char *cfgFile = DEFAULT_CONFIG_FILE; char defaultCfg[] = "./aqualinkd.conf"; char *cfgFile; int cmdln_loglevel = -1; bool cmdln_debugRS485 = false; bool cmdln_lograwRS485 = false; _aqualink_data.num_pumps = 0; _aqualink_data.num_lights = 0; /* static unsigned char msg_loop; // = '\0'; //msg_loop &= ~MSG_SERVICE; if ((msg_loop & MSG_SERVICE) != MSG_SERVICE) printf("Off\n"); else printf("On\n"); //msg_loop &= ~MSG_SERVICE; msg_loop |= MSG_SERVICE; if ((msg_loop & MSG_SERVICE) != MSG_SERVICE) printf("Off\n"); else printf("On\n"); msg_loop |= MSG_SERVICE; if ((msg_loop & MSG_SERVICE) != MSG_SERVICE) printf("Off\n"); else printf("On\n"); msg_loop |= MSG_SERVICE; if ((msg_loop & MSG_SERVICE) != MSG_SERVICE) printf("Off\n"); else printf("On\n"); return 0; */ if (argc > 1 && strcmp(argv[1], "-h") == 0) { printHelp(); return 0; } // 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) { //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; } // Initialize the daemon's parameters. //init_parameters(&_aqconfig_); init_config(); cfgFile = defaultCfg; //sprintf(cfgFile, "%s", DEFAULT_CONFIG_FILE); for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-h") == 0) { printHelp(); return 0; } if (strcmp(argv[i], "-d") == 0) { _aqconfig_.deamonize = false; } else if (strcmp(argv[i], "-c") == 0) { cfgFile = argv[++i]; } else if (strcmp(argv[i], "-vv") == 0) { cmdln_loglevel = LOG_DEBUG_SERIAL; } else if (strcmp(argv[i], "-v") == 0) { cmdln_loglevel = LOG_DEBUG; } else if (strcmp(argv[i], "-rsd") == 0) { cmdln_debugRS485 = true; } else if (strcmp(argv[i], "-rsrd") == 0) { cmdln_lograwRS485 = true; } } initButtons(&_aqualink_data); read_config(&_aqualink_data, cfgFile); // Just so we catch the heaters, make all panels RS8 or RS16 //_aqualink_data.total_buttons = _aqconfig_.rs_panel_size + 4; // This would be correct if we re-index heaters. #ifdef AQ_RS16 if (_aqconfig_.rs_panel_size >= 12) _aqualink_data.total_buttons = 20; else #endif _aqualink_data.total_buttons = 12; if (cmdln_loglevel != -1) _aqconfig_.log_level = cmdln_loglevel; if (cmdln_debugRS485) _aqconfig_.debug_RSProtocol_packets = true; if (cmdln_lograwRS485) _aqconfig_.log_raw_RS_bytes = true; if (_aqconfig_.display_warnings_web == true) setLoggingPrms(_aqconfig_.log_level, _aqconfig_.deamonize, _aqconfig_.log_file, _aqualink_data.last_display_message); else setLoggingPrms(_aqconfig_.log_level, _aqconfig_.deamonize, _aqconfig_.log_file, NULL); logMessage(LOG_NOTICE, "%s v%s\n", AQUALINKD_NAME, AQUALINKD_VERSION); logMessage(LOG_NOTICE, "Config log_level = %d\n", _aqconfig_.log_level); logMessage(LOG_NOTICE, "Config device_id = 0x%02hhx\n", _aqconfig_.device_id); logMessage(LOG_NOTICE, "Config extra_device_id = 0x%02hhx\n", _aqconfig_.onetouch_device_id); logMessage(LOG_NOTICE, "Config serial_port = %s\n", _aqconfig_.serial_port); logMessage(LOG_NOTICE, "Config rs_panel_size = %d\n", _aqconfig_.rs_panel_size); logMessage(LOG_NOTICE, "Config socket_port = %s\n", _aqconfig_.socket_port); logMessage(LOG_NOTICE, "Config web_directory = %s\n", _aqconfig_.web_directory); logMessage(LOG_NOTICE, "Config extra_device_prog = %s\n", bool2text(_aqconfig_.extended_device_id_programming)); logMessage(LOG_NOTICE, "Config read_all_devices = %s\n", bool2text(_aqconfig_.read_all_devices)); logMessage(LOG_NOTICE, "Config use_aux_labels = %s\n", bool2text(_aqconfig_.use_panel_aux_labels)); logMessage(LOG_NOTICE, "Config override frz prot = %s\n", bool2text(_aqconfig_.override_freeze_protect)); #ifndef MG_DISABLE_MQTT logMessage(LOG_NOTICE, "Config mqtt_server = %s\n", _aqconfig_.mqtt_server); logMessage(LOG_NOTICE, "Config mqtt_dz_sub_topic = %s\n", _aqconfig_.mqtt_dz_sub_topic); logMessage(LOG_NOTICE, "Config mqtt_dz_pub_topic = %s\n", _aqconfig_.mqtt_dz_pub_topic); logMessage(LOG_NOTICE, "Config mqtt_aq_topic = %s\n", _aqconfig_.mqtt_aq_topic); logMessage(LOG_NOTICE, "Config mqtt_user = %s\n", _aqconfig_.mqtt_user); logMessage(LOG_NOTICE, "Config mqtt_passwd = %s\n", _aqconfig_.mqtt_passwd); logMessage(LOG_NOTICE, "Config mqtt_ID = %s\n", _aqconfig_.mqtt_ID); logMessage(LOG_NOTICE, "Config idx water temp = %d\n", _aqconfig_.dzidx_air_temp); logMessage(LOG_NOTICE, "Config idx pool temp = %d\n", _aqconfig_.dzidx_pool_water_temp); logMessage(LOG_NOTICE, "Config idx spa temp = %d\n", _aqconfig_.dzidx_spa_water_temp); logMessage(LOG_NOTICE, "Config idx SWG Percent = %d\n", _aqconfig_.dzidx_swg_percent); logMessage(LOG_NOTICE, "Config idx SWG PPM = %d\n", _aqconfig_.dzidx_swg_ppm); #ifdef AQ_PDA logMessage(LOG_NOTICE, "Config PDA Mode = %s\n", bool2text(_aqconfig_.pda_mode)); logMessage(LOG_NOTICE, "Config PDA Sleep Mode = %s\n", bool2text(_aqconfig_.pda_sleep_mode)); #endif logMessage(LOG_NOTICE, "Config force SWG = %s\n", bool2text(_aqconfig_.force_swg)); /* removed until domoticz has a better virtual thermostat logMessage(LOG_NOTICE, "Config idx pool thermostat = %d\n", _aqconfig_.dzidx_pool_thermostat); logMessage(LOG_NOTICE, "Config idx spa thermostat = %d\n", _aqconfig_.dzidx_spa_thermostat); */ #endif // MG_DISABLE_MQTT logMessage(LOG_NOTICE, "Config deamonize = %s\n", bool2text(_aqconfig_.deamonize)); logMessage(LOG_NOTICE, "Config log_file = %s\n", _aqconfig_.log_file); logMessage(LOG_NOTICE, "Config light_pgm_mode = %.2f\n", _aqconfig_.light_programming_mode); logMessage(LOG_NOTICE, "Debug RS485 protocol = %s\n", bool2text(_aqconfig_.debug_RSProtocol_packets)); //logMessage(LOG_NOTICE, "Use PDA 4 auxiliary info = %s\n", bool2text(_aqconfig_.use_PDA_auxiliary)); logMessage(LOG_NOTICE, "Read Pentair Packets = %s\n", bool2text(_aqconfig_.read_pentair_packets)); // logMessage (LOG_NOTICE, "Config serial_port = %s\n", config_parameters->serial_port); logMessage(LOG_NOTICE, "Display warnings in web = %s\n", bool2text(_aqconfig_.display_warnings_web)); if (_aqconfig_.swg_zero_ignore > 0) logMessage(LOG_NOTICE, "Ignore SWG 0 msg count = %d\n", _aqconfig_.swg_zero_ignore); //for (i = 0; i < TOTAL_BUTONS; i++) for (i = 0; i < _aqualink_data.total_buttons; i++) { //char ext[] = " VSP ID None | AL ID 0 "; char ext[40]; ext[0] = '\0'; for (j = 0; j < _aqualink_data.num_pumps; j++) { if (_aqualink_data.pumps[j].button == &_aqualink_data.aqbuttons[i]) { sprintf(ext, "VSP ID 0x%02hhx | PMP ID %-1d |",_aqualink_data.pumps[j].pumpID, _aqualink_data.pumps[j].pumpIndex); } } for (j = 0; j < _aqualink_data.num_lights; j++) { if (_aqualink_data.lights[j].button == &_aqualink_data.aqbuttons[i]) { sprintf(ext,"Light Progm | CTYPE %-1d |",_aqualink_data.lights[j].lightType); } } if (_aqualink_data.aqbuttons[i].dz_idx > 0) sprintf(ext+strlen(ext), "dzidx %-3d", _aqualink_data.aqbuttons[i].dz_idx); #ifdef AQ_PDA if (_aqconfig_.pda_mode) { logMessage(LOG_NOTICE, "Config BTN %-13s = label %-15s | PDAlabel %-15s | %s\n", _aqualink_data.aqbuttons[i].name, _aqualink_data.aqbuttons[i].label, _aqualink_data.aqbuttons[i].pda_label, ext); } else #endif { logMessage(LOG_NOTICE, "Config BTN %-13s = label %-15s | %s\n", _aqualink_data.aqbuttons[i].name, _aqualink_data.aqbuttons[i].label, ext); } } if (_aqconfig_.deamonize == true) { char pidfile[256]; // sprintf(pidfile, "%s/%s.pid",PIDLOCATION, basename(argv[0])); sprintf(pidfile, "%s/%s.pid", "/run", basename(argv[0])); daemonise(pidfile, main_loop); } else { main_loop(); } exit(EXIT_SUCCESS); } #define MAX_BLOCK_ACK 12 #define MAX_BUSY_ACK (50 + MAX_BLOCK_ACK) void caculate_ack_packet(int rs_fd, unsigned char *packet_buffer) { static int delayAckCnt = 0; if (packet_buffer[PKT_DEST] == _aqconfig_.onetouch_device_id) { send_extended_ack(rs_fd, ACK_ONETOUCH, pop_ot_cmd(packet_buffer[PKT_CMD])); return; } // if PDA mode, should we sleep? if not Can only send command to status message on PDA. #ifdef AQ_PDA if (_aqconfig_.pda_mode == true) { //pda_programming_thread_check(&_aqualink_data); if (_aqconfig_.pda_sleep_mode && pda_shouldSleep()) { logMessage(LOG_DEBUG, "PDA Aqualink daemon in sleep mode\n"); return; } else { send_extended_ack(rs_fd, ACK_PDA, pop_aq_cmd(&_aqualink_data)); } } else #endif if (_aqualink_data.simulate_panel && _aqualink_data.active_thread.thread_id == 0) { // 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 starts 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 (_aqualink_data.last_packet_type == CMD_MSG_LONG) { send_extended_ack(rs_fd, ACK_SCREEN_BUSY, pop_aq_cmd(&_aqualink_data)); } 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++; } } else { // We are in simulate panel mode, but a thread is active, so ignore simulate panel send_ack(rs_fd, pop_aq_cmd(&_aqualink_data)); } } unsigned char find_unused_address(unsigned char* packet) { static int ID[4] = {0,0,0,0}; // 0=0x08, 1=0x09, 2=0x0A, 3=0x0B static unsigned char lastID = 0x00; if (packet[PKT_DEST] >= 0x08 && packet[PKT_DEST] <= 0x0B && packet[PKT_CMD] == CMD_PROBE) { //printf("Probe packet to keypad ID 0x%02hhx\n",packet[PKT_DEST]); lastID = packet[PKT_DEST]; } else if (packet[PKT_DEST] == DEV_MASTER && lastID != 0x00) { lastID = 0x00; } else if (lastID != 0x00) { ID[lastID-8]++; if (ID[lastID-8] >= 3) { logMessage(LOG_NOTICE, "Found valid unused ID 0x%02hhx\n",lastID); return lastID; } lastID = 0x00; } else { lastID = 0x00; } return 0x00; } void main_loop() { struct mg_mgr mgr; int rs_fd; int packet_length; unsigned char packet_buffer[AQ_MAXPKTLEN+1]; //bool interestedInNextAck = false; rsDeviceType interestedInNextAck = DRS_NONE; bool changed = false; //int swg_zero_cnt = 0; int swg_noreply_cnt = 0; int i; //int delayAckCnt = 0; // NSF need to find a better place to init this. //_aqualink_data.aq_command = 0x00; _aqualink_data.simulate_panel = false; _aqualink_data.active_thread.thread_id = 0; _aqualink_data.air_temp = TEMP_UNKNOWN; _aqualink_data.pool_temp = TEMP_UNKNOWN; _aqualink_data.spa_temp = TEMP_UNKNOWN; _aqualink_data.frz_protect_set_point = TEMP_UNKNOWN; _aqualink_data.pool_htr_set_point = TEMP_UNKNOWN; _aqualink_data.spa_htr_set_point = TEMP_UNKNOWN; _aqualink_data.unactioned.type = NO_ACTION; _aqualink_data.swg_percent = TEMP_UNKNOWN; _aqualink_data.swg_ppm = TEMP_UNKNOWN; _aqualink_data.ar_swg_status = SWG_STATUS_OFF; _aqualink_data.swg_delayed_percent = TEMP_UNKNOWN; _aqualink_data.temp_units = UNKNOWN; _aqualink_data.single_device = false; _aqualink_data.service_mode_state = OFF; _aqualink_data.frz_protect_state = OFF; _aqualink_data.battery = OK; _aqualink_data.open_websockets = 0; _aqualink_data.ph = TEMP_UNKNOWN; _aqualink_data.orp = TEMP_UNKNOWN; pthread_mutex_init(&_aqualink_data.active_thread.thread_mutex, NULL); pthread_cond_init(&_aqualink_data.active_thread.thread_cond, NULL); for (i=0; i < MAX_PUMPS; i++) { _aqualink_data.pumps[i].rpm = TEMP_UNKNOWN; _aqualink_data.pumps[i].gpm = TEMP_UNKNOWN; _aqualink_data.pumps[i].watts = TEMP_UNKNOWN; } if (_aqconfig_.force_swg == true) { _aqualink_data.swg_percent = 0; _aqualink_data.swg_ppm = 0; } if (!start_net_services(&mgr, &_aqualink_data)) { logMessage(LOG_ERR, "Can not start webserver on port %s.\n", _aqconfig_.socket_port); exit(EXIT_FAILURE); } startPacketLogger(_aqconfig_.debug_RSProtocol_packets, _aqconfig_.read_pentair_packets); signal(SIGINT, intHandler); signal(SIGTERM, intHandler); int blank_read = 0; rs_fd = init_serial_port(_aqconfig_.serial_port); logMessage(LOG_NOTICE, "Listening to Aqualink RS8 on serial port: %s\n", _aqconfig_.serial_port); #ifdef AQ_PDA if (_aqconfig_.pda_mode == true) { #ifdef BETA_PDA_AUTOLABEL init_pda(&_aqualink_data, &_aqconfig_); #else init_pda(&_aqualink_data); #endif } #endif if (_aqconfig_.onetouch_device_id != 0x00) { set_onetouch_enabled(true); } if (_aqconfig_.extended_device_id_programming == true) { set_extended_device_id_programming(true); } if (_aqconfig_.device_id == 0x00) { logMessage(LOG_NOTICE, "Searching for valid ID, please configure one for faster startup\n"); } 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"); broadcast_aqualinkstate_error(mgr.active_connections, CONNECTION_ERROR); 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 { logMessage(LOG_ERR, "Aqualink daemon looks like serial error, resetting.\n"); close_serial_port(rs_fd); } rs_fd = init_serial_port(_aqconfig_.serial_port); blank_read = 0; } if (_aqconfig_.log_raw_RS_bytes) packet_length = get_packet_lograw(rs_fd, packet_buffer); else packet_length = get_packet(rs_fd, packet_buffer); 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) { //logMessage(LOG_DEBUG_SERIAL, "Nothing read on serial\n"); blank_read++; } else if (_aqconfig_.device_id == 0x00) { blank_read = 0; _aqconfig_.device_id = find_unused_address(packet_buffer); continue; } else if (packet_length > 0) { /* // Use this to check wait time in mg_mgr_poll(&mgr, xx); at bottom of for, and adjust as needed. if (blank_read > 0) { logMessage(LOG_NOTICE, "RS empry reads %d\n", blank_read); } */ blank_read = 0; changed = false; if (packet_length > 0 && packet_buffer[PKT_DEST] == _aqconfig_.device_id && getProtocolType(packet_buffer) == JANDY) { 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); changed = process_packet(packet_buffer, packet_length); // If we are not in PDA or Simulator mode, just sent ACK & any CMD, else caculate the ACK. if (!_aqualink_data.simulate_panel #ifdef AQ_PDA && !_aqconfig_.pda_mode #endif ) { //send_ack(rs_fd, pop_aq_cmd(&_aqualink_data)); send_extended_ack(rs_fd, (_aqualink_data.last_packet_type==CMD_MSG_LONG?ACK_SCREEN_BUSY_SCROLL:ACK_NORMAL), pop_aq_cmd(&_aqualink_data)); } else caculate_ack_packet(rs_fd, packet_buffer); } else if (packet_length > 0 && onetouch_enabled() && packet_buffer[PKT_DEST] == _aqconfig_.onetouch_device_id && getProtocolType(packet_buffer) == JANDY) { //if (getLogLevel() >= LOG_DEBUG) // logMessage(LOG_DEBUG, "RS received ONETOUCH packet of type %s length %d\n", get_packet_type(packet_buffer, packet_length), packet_length); changed = process_onetouch_packet(packet_buffer, packet_length, &_aqualink_data); caculate_ack_packet(rs_fd, packet_buffer); } else if (packet_length > 0 && _aqconfig_.read_all_devices == true) { //logPacket(packet_buffer, packet_length); if (packet_buffer[PKT_DEST] == DEV_MASTER && interestedInNextAck != DRS_NONE) { if (interestedInNextAck == DRS_SWG) { swg_noreply_cnt = 0; changed = processPacketFromSWG(packet_buffer, packet_length, &_aqualink_data); } else if (interestedInNextAck == DRS_EPUMP) { changed = processPacketFromJandyPump(packet_buffer, packet_length, &_aqualink_data); } interestedInNextAck = DRS_NONE; } else if ( packet_buffer[PKT_DEST] != DEV_MASTER && interestedInNextAck != DRS_NONE ) { // We were expecting an ack from device as next message but didn;t get it, device must be off if (interestedInNextAck == DRS_SWG && _aqualink_data.ar_swg_status != SWG_STATUS_OFF) { if ( ++swg_noreply_cnt < 3 ) { _aqualink_data.ar_swg_status = SWG_STATUS_OFF; changed = true; } } interestedInNextAck = DRS_NONE; } else if (packet_buffer[PKT_DEST] == SWG_DEV_ID) { interestedInNextAck = DRS_SWG; changed = processPacketToSWG(packet_buffer, packet_length, &_aqualink_data, _aqconfig_.swg_zero_ignore); } else if (packet_buffer[PKT_DEST] >= JANDY_DEC_PUMP_MIN && packet_buffer[PKT_DEST] <= JANDY_DEC_PUMP_MAX) { interestedInNextAck = DRS_EPUMP; changed = processPacketToJandyPump(packet_buffer, packet_length, &_aqualink_data); } else { interestedInNextAck = DRS_NONE; } if (_aqconfig_.read_pentair_packets && getProtocolType(packet_buffer) == PENTAIR) { if (processPentairPacket(packet_buffer, packet_length, &_aqualink_data)) { //broadcast_aqualinkstate(mgr.active_connections); changed = true; } } } if (changed) broadcast_aqualinkstate(mgr.active_connections); } //mg_mgr_poll(&mgr, 10); mg_mgr_poll(&mgr, 5); tcdrain(rs_fd); // Make sure buffer has been sent. // Any unactioned commands if (_aqualink_data.unactioned.type != NO_ACTION) { time_t now; time(&now); if (difftime(now, _aqualink_data.unactioned.requested) > 2) { logMessage(LOG_DEBUG, "Actioning delayed request\n"); action_delayed_request(); } } //tcdrain(rs_fd); // Make sure buffer has been sent. //delay(10); } //if (_aqconfig_.debug_RSProtocol_packets) stopPacketLogger(); stopPacketLogger(); // Reset and close the port. close_serial_port(rs_fd); // Clear webbrowser mg_mgr_free(&mgr); // NSF need to run through config memory and clean up. logMessage(LOG_NOTICE, "Exit!\n"); exit(EXIT_FAILURE); }