/* * 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 */ #include #include #include #include #include "iaqtouch_aq_programmer.h" #include "aq_serial.h" #include "utils.h" #include "aq_programmer.h" #include "aqualink.h" //#include "packetLogger.h" #include "iaqtouch.h" #include "rs_msg_utils.h" #include "aq_panel.h" #include "config.h" #include "devices_jandy.h" #include "packetLogger.h" // System Page is obfiously fixed and not dynamic loaded, so set buttons to stop confustion. #define KEY_IAQTCH_SCHEDULE KEY_IAQTCH_KEY01 #define KEY_IAQTCH_CUSTOMIZE_HOME KEY_IAQTCH_KEY02 #define KEY_IAQTCH_PROGRAM_GROUP KEY_IAQTCH_KEY03 #define KEY_IAQTCH_SET_TEMP KEY_IAQTCH_KEY04 #define KEY_IAQTCH_SYSTEM_SETUP KEY_IAQTCH_KEY05 #define KEY_IAQTCH_TOUCH_SETUP KEY_IAQTCH_KEY06 #define KEY_IAQTCH_SET_DATETIME KEY_IAQTCH_KEY07 #define KEY_IAQTCH_LOCKOUT_PASSWD KEY_IAQTCH_KEY08 #define KEY_IAQTCH_SET_ACQUAPURE KEY_IAQTCH_KEY09 bool _cansend = false; unsigned char _iaqt_pgm_command = NUL; /************** * * Command queue stuff * */ // External command bool iaqt_queue_cmd(unsigned char cmd) { if (_iaqt_pgm_command == NUL) { _iaqt_pgm_command = cmd; return true; } return false; } void set_iaq_cansend(bool cansend){ _cansend = cansend; } unsigned char pop_iaqt_cmd(unsigned char receive_type) { unsigned char cmd = NUL; if (!_cansend) return cmd; if (receive_type == CMD_IAQ_POLL) { cmd = _iaqt_pgm_command; _iaqt_pgm_command = NUL; } if (cmd != NUL) LOG(IAQT_LOG,LOG_DEBUG, "Sending '0x%02hhx' to controller\n", cmd); return cmd; } void waitfor_iaqt_queue2empty() { int i=0; while ( (_iaqt_pgm_command != NUL) && ( i++ < PROGRAMMING_POLL_COUNTER) ) { delay(PROGRAMMING_POLL_DELAY_TIME); } // Initial startup can take some time, _cansend should be false during this time. // If we start programming before we receive the first status page, nothing works, this forces that wait while(_cansend == false) { delay(PROGRAMMING_POLL_DELAY_TIME * 2); } if (_iaqt_pgm_command != NUL) { // Wait for longer interval while ( (_iaqt_pgm_command != NUL) && ( i++ < PROGRAMMING_POLL_COUNTER * 2 ) ) { delay(PROGRAMMING_POLL_DELAY_TIME * 2); } } if (_iaqt_pgm_command != NUL) { LOG(IAQT_LOG,LOG_WARNING, "Send command Queue did not empty, timeout\n"); } } void send_aqt_cmd(unsigned char cmd) { waitfor_iaqt_queue2empty(); iaqt_queue_cmd(cmd); LOG(IAQT_LOG,LOG_DEBUG, "Queue send '0x%02hhx' to controller (programming)\n", _iaqt_pgm_command); } /************** * * Control Command queue stuff * */ unsigned char _iaqt_control_cmd[AQ_MAXPKTLEN]; int _iaqt_control_cmd_len; int ref_iaqt_control_cmd(unsigned char **cmd) { //printf("*********** GET READY SENDING CONTROL ****************\n"); *cmd = _iaqt_control_cmd; if ( getLogLevel(IAQT_LOG) >= LOG_DEBUG ) { char buff[1000]; //sprintf("Sending control command:") beautifyPacket(buff, _iaqt_control_cmd, _iaqt_control_cmd_len, false); LOG(IAQT_LOG,LOG_DEBUG, "Sending commandsed : %s\n", buff); } return _iaqt_control_cmd_len; } void rem_iaqt_control_cmd(unsigned char *cmd) { memset(_iaqt_control_cmd, 0, AQ_MAXPKTLEN * sizeof(unsigned char)); _iaqt_control_cmd_len = 0; } bool waitfor_iaqt_ctrl_queue2empty() { int i=0; while ( (_iaqt_control_cmd_len >0 ) && ( i++ < 100) ) { LOG(IAQT_LOG,LOG_DEBUG, "Waiting for commandset to send\n"); delay(50); } LOG(IAQT_LOG,LOG_DEBUG, "Wait for commandset over!\n"); if (_iaqt_control_cmd_len > 0 ) { LOG(IAQT_LOG,LOG_WARNING, "Send control command Queue did not empty, timeout\n"); return false; } return true; } unsigned const char waitfor_iaqt_nextPage(struct aqualinkdata *aq_data) { waitfor_iaqt_queue2empty(); int i=0; const int numMessageReceived = 30; pthread_mutex_lock(&aq_data->active_thread.thread_mutex); while( ++i <= numMessageReceived) { //LOG(IAQT_LOG,LOG_DEBUG, "waitfor_iaqt_nextPage (%d of %d)\n",i,numMessageReceived); pthread_cond_wait(&aq_data->active_thread.thread_cond, &aq_data->active_thread.thread_mutex); if(wasiaqtThreadKickTypePage()) break; } LOG(IAQT_LOG,LOG_DEBUG, "waitfor_iaqt_nextPage finished in (%d of %d)\n",i,numMessageReceived); pthread_mutex_unlock(&aq_data->active_thread.thread_mutex); if(wasiaqtThreadKickTypePage()) return iaqtCurrentPage(); else return NUL; } unsigned const char waitfor_iaqt_nextMessage(struct aqualinkdata *aq_data, const unsigned char msg_type) { waitfor_iaqt_queue2empty(); int i=0; const int numMessageReceived = 30; pthread_mutex_lock(&aq_data->active_thread.thread_mutex); while( ++i <= numMessageReceived) { LOG(IAQT_LOG,LOG_DEBUG, "waitfor_iaqt_nextMessage 0x%02hhx (%d of %d)\n",msg_type,i,numMessageReceived); pthread_cond_wait(&aq_data->active_thread.thread_cond, &aq_data->active_thread.thread_mutex); if( msg_type == NUL || iaqtLastMsg() == msg_type) break; } pthread_mutex_unlock(&aq_data->active_thread.thread_mutex); return iaqtLastMsg(); } //typedef enum iaqt_control_command_type() typedef enum {icct_setrpm, icct_settime, icct_setdate} iaqtControlCmdYype; // Type is always 0 at the moment, haven't found any void queue_iaqt_control_command(iaqtControlCmdYype type, int num) { //unsigned char packets[AQ_MAXPKTLEN]; //int cnt; if (waitfor_iaqt_ctrl_queue2empty() == false) return; _iaqt_control_cmd[0] = DEV_MASTER; _iaqt_control_cmd[1] = 0x24; _iaqt_control_cmd[2] = 0x31; _iaqt_control_cmd_len = num2iaqtRSset(&_iaqt_control_cmd[3], num, true); // Pad with 0xcd for some reason. for(_iaqt_control_cmd_len = _iaqt_control_cmd_len+3; _iaqt_control_cmd_len <= 18; _iaqt_control_cmd_len++) _iaqt_control_cmd[_iaqt_control_cmd_len] = 0xcd; // Tell the control panel we are ready to send this shit. send_aqt_cmd(ACK_CMD_READY_CTRL); LOG(IAQT_LOG,LOG_DEBUG, "Queued extended commandsed of length %d\n",_iaqt_control_cmd_len); //printHex(packets, 19); //printf("\n"); //send_jandy_command(NULL, packets, cnt); } bool queue_iaqt_control_command_str(iaqtControlCmdYype type, char *str) { if (waitfor_iaqt_ctrl_queue2empty() == false) return false; _iaqt_control_cmd[0] = DEV_MASTER; _iaqt_control_cmd[1] = 0x24; _iaqt_control_cmd[2] = 0x31; _iaqt_control_cmd_len = char2iaqtRSset(&_iaqt_control_cmd[3], str, strlen(str)); // Need to bad time for some reason not yet known if (type == icct_settime) { //Debug: RS Serial: To 0x00 of type iAq pBut | HEX: 0x10|0x02|0x00|0x24|0x31|0x30|0x31|0x3a|0x30|0x31|0x00|0x30|0x32|0x00|0xcd|0xcd|0xcd|0xcd|0xcd|0xcd|0xcd|0x60|0x10|0x03| // From position 11 (8 without pre) add 0x30|0x32|0x00 _iaqt_control_cmd_len += 3; _iaqt_control_cmd[9] = 0x30; _iaqt_control_cmd[10] = 0x32; _iaqt_control_cmd[11] = 0x00; } // Pad with 0xcd for some reason. for(_iaqt_control_cmd_len = _iaqt_control_cmd_len+3; _iaqt_control_cmd_len <= 18; _iaqt_control_cmd_len++) _iaqt_control_cmd[_iaqt_control_cmd_len] = 0xcd; // Tell the control panel we are ready to send this shit. send_aqt_cmd(ACK_CMD_READY_CTRL); return true; //debuglogPacket() } bool goto_iaqt_page(const unsigned char pageID, struct aqualinkdata *aq_data) { if (iaqtCurrentPage() == pageID) return true; // If we go to Other Status Page, do that so we can quit if (pageID == IAQ_PAGE_STATUS) { send_aqt_cmd(KEY_IAQTCH_STATUS); unsigned char rtn = waitfor_iaqt_nextPage(aq_data); if (rtn != IAQ_PAGE_STATUS && rtn != IAQ_PAGE_STATUS2) { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find Status page\n"); return false; } LOG(IAQT_LOG, LOG_DEBUG, "IAQ Touch got to Status page\n"); return true; } if (pageID == IAQ_PAGE_HOME || pageID == IAQ_PAGE_DEVICES) { if (iaqtCurrentPage() != IAQ_PAGE_HOME) { send_aqt_cmd(KEY_IAQTCH_HOME); if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_HOME) { //LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find Home page\n"); // We reset to home page after every programming cycle and error really don't care, so don't throw the error. return false; } LOG(IAQT_LOG, LOG_DEBUG, "IAQ Touch got to Home page\n"); } if (pageID == IAQ_PAGE_DEVICES) { send_aqt_cmd(KEY_IAQTCH_HOMEP_KEY08); if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_DEVICES) { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find Device page\n"); return false; } } LOG(IAQT_LOG, LOG_DEBUG, "IAQ Touch got to Device page\n"); return true; } else if (pageID == IAQ_PAGE_MENU || pageID == IAQ_PAGE_SET_TEMP || pageID == IAQ_PAGE_SET_TIME || pageID == IAQ_PAGE_SET_SWG || pageID == IAQ_PAGE_SYSTEM_SETUP || pageID == IAQ_PAGE_FREEZE_PROTECT || pageID == IAQ_PAGE_LABEL_AUX || pageID == IAQ_PAGE_VSP_SETUP) { // All other pages require us to go to Menu page send_aqt_cmd(KEY_IAQTCH_MENU); if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_MENU) { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find Menu page\n"); return false; } else LOG(IAQT_LOG, LOG_INFO, "IAQ Touch got to Menu page\n"); // There are several pages from the MENU page that have no other pages, // So hit those first if (pageID == IAQ_PAGE_SET_TEMP) { send_aqt_cmd(KEY_IAQTCH_SET_TEMP); if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_SET_TEMP) { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find Set Temp page\n"); return false; } LOG(IAQT_LOG, LOG_INFO, "IAQ Touch got to Set Temp page\n"); return true; } if (pageID == IAQ_PAGE_SET_TIME) { send_aqt_cmd(KEY_IAQTCH_SET_DATETIME); if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_SET_TIME) { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find Set Time page\n"); return false; } LOG(IAQT_LOG, LOG_INFO, "IAQ Touch got to Set Time page\n"); return true; } if (pageID == IAQ_PAGE_SET_SWG) { send_aqt_cmd(KEY_IAQTCH_SET_ACQUAPURE); if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_SET_SWG) { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find Aquapure page\n"); return false; } LOG(IAQT_LOG, LOG_INFO, "IAQ Touch got to Aquapure page\n"); return true; } // All pages now require us to goto System Setup send_aqt_cmd(KEY_IAQTCH_SYSTEM_SETUP); if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_SYSTEM_SETUP) { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find System Setup page\n"); return false; } LOG(IAQT_LOG, LOG_INFO, "IAQ Touch got to System Setup page\n"); // Look for menu items for the next pages as they can change char *menuText; struct iaqt_page_button *button; switch (pageID) { case IAQ_PAGE_FREEZE_PROTECT: menuText = "Freeze Protection"; break; case IAQ_PAGE_LABEL_AUX: menuText = "Label Aux"; break; case IAQ_PAGE_VSP_SETUP: menuText = "VSP Setup"; break; default: LOG(IAQT_LOG, LOG_ERR, "IAQ Touch unknown menu '0x%02hhx'\n", pageID); return false; break; } button = iaqtFindButtonByLabel(menuText); if (button == NULL) { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find '%s' button on page setup\n", menuText); return false; } // send_aqt_cmd(KEY_IAQTCH_KEY01); send_aqt_cmd(button->keycode); if (waitfor_iaqt_nextPage(aq_data) != pageID) { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find %s page\n", menuText); return false; } else LOG(IAQT_LOG, LOG_INFO, "IAQ Touch got to %s page\n", menuText); return true; } LOG(IAQT_LOG, LOG_ERR, "IAQ Touch unknown menu '0x%02hhx'\n", pageID); return false; } void *set_aqualink_iaqtouch_pump_rpm( void *ptr ) { struct programmingThreadCtrl *threadCtrl; threadCtrl = (struct programmingThreadCtrl *) ptr; struct aqualinkdata *aq_data = threadCtrl->aq_data; char *buf = (char*)threadCtrl->thread_args; char VSPstr[20]; int structIndex; struct iaqt_page_button *button; //printf("**** program string '%s'\n",buf); int pumpIndex = atoi(&buf[0]); int pumpRPM = -1; //int pumpRPM = atoi(&buf[2]); for (structIndex=0; structIndex < aq_data->num_pumps; structIndex++) { if (aq_data->pumps[structIndex].pumpIndex == pumpIndex) { if (aq_data->pumps[structIndex].pumpType == PT_UNKNOWN) { LOG(IAQT_LOG,LOG_ERR, "Can't set Pump RPM/GPM until type is known\n"); cleanAndTerminateThread(threadCtrl); return ptr; } pumpRPM = RPM_check(aq_data->pumps[structIndex].pumpType, atoi(&buf[2]), aq_data); break; } } //int pumpRPM = atoi(&buf[2]); //int pumpIndex = 1; // Just force to pump 1 for testing sprintf(VSPstr, "VSP%1d Spd ADJ",pumpIndex); // NSF Should probably check pumpRPM is not -1 here waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_PUMP_RPM); LOG(IAQT_LOG,LOG_NOTICE, "IAQ Touch Set Pump %d to RPM %d\n",pumpIndex,pumpRPM); if ( goto_iaqt_page(IAQ_PAGE_DEVICES, aq_data) == false ) goto f_end; button = iaqtFindButtonByLabel(VSPstr); if (button == NULL) { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find '%s' button on page setup\n", VSPstr); goto f_end; } send_aqt_cmd(button->keycode); if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_SET_VSP) { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find %s page\n", VSPstr); goto f_end; } LOG(IAQT_LOG, LOG_INFO, "IAQ Touch got to %s page\n", VSPstr); //send_aqt_cmd(ACK_CMD_READY_CTRL); queue_iaqt_control_command(0, pumpRPM); waitfor_iaqt_ctrl_queue2empty(); LOG(IAQT_LOG, LOG_INFO, "IAQ Touch got to %s page\n", VSPstr); // We should get the device page back. waitfor_iaqt_nextPage(aq_data); //goto_iaqt_page(IAQ_PAGE_STATUS, aq_data); /* // Send Devices button. send_aqt_cmd(KEY_IAQTCH_HOME); waitfor_iaqt_queue2empty(); sleep(1); send_aqt_cmd(KEY_IAQTCH_HOMEP_KEY08); waitfor_iaqt_queue2empty(); sleep(1); send_aqt_cmd(KEY_IAQTCH_KEY03); */ //send_aqt_cmd(0x80); // Go to status page on startup to read devices goto_iaqt_page(IAQ_PAGE_STATUS, aq_data); f_end: goto_iaqt_page(IAQ_PAGE_HOME, aq_data); cleanAndTerminateThread(threadCtrl); // just stop compiler error, ptr is not valid as it's just been freed return ptr; } void *set_aqualink_iaqtouch_vsp_assignments( void *ptr ) { struct programmingThreadCtrl *threadCtrl; threadCtrl = (struct programmingThreadCtrl *) ptr; struct aqualinkdata *aq_data = threadCtrl->aq_data; waitForSingleThreadOrTerminate(threadCtrl, AQ_GET_IAQTOUCH_VSP_ASSIGNMENT); if ( goto_iaqt_page(IAQ_PAGE_VSP_SETUP, aq_data) == false ) goto f_end; /* Info: Button 00| ePump | type=0xff | state=0x00 | unknown=0xff * Info: Button 01| Intelliflo VF | type=0xff | state=0x00 | unknown=0xff * Info: Button 02| Intelliflo VS | type=0xff | state=0x00 | unknown=0xff * Info: Button 03| ePump | type=0xff | state=0x00 | unknown=0xff * ---- This is next column * Info: Button 04| Filtration | type=0xff | state=0x00 | unknown=0xff * Info: Button 05| Filtration | type=0xff | state=0x00 | unknown=0xff * Info: Button 06| Filtration | type=0xff | state=0x00 | unknown=0xff * Info: Button 07| Not Installed | type=0xff | state=0x00 | unknown=0xff * ----- Next column * Info: Button 08| 600 RPM | type=0xff | state=0x00 | unknown=0xff * Info: Button 09| 15 GPM | type=0xff | state=0x00 | unknown=0xff * Info: Button 10| 600 RPM | type=0xff | state=0x00 | unknown=0xff * Info: Button 11| 600 RPM | type=0xff | state=0x00 | unknown=0xff * ----- Next colums * Info: Button 12| 3450 RPM | type=0xff | state=0x00 | unknown=0xff * Info: Button 13| 130 GPM | type=0xff | state=0x00 | unknown=0xff * Info: Button 14| 3450 RPM | type=0xff | state=0x00 | unknown=0xff * Info: Button 15| 3450 RPM | type=0xff | state=0x00 | unknown=0xff * ----- Next column * Info: Button 16| 2500 | type=0xff | state=0x00 | unknown=0xff * Info: Button 17| N/A | type=0xff | state=0x00 | unknown=0xff * Info: Button 18| 2500 | type=0xff | state=0x00 | unknown=0xff * Info: Button 19| 2500 | type=0xff | state=0x00 | unknown=0xff * ----- Next column * Info: Button 20| :03 | type=0xff | state=0x00 | unknown=0xff * Info: Button 21| N/A | type=0xff | state=0x00 | unknown=0xff * Info: Button 22| :03 | type=0xff | state=0x00 | unknown=0xff * Info: Button 23| :03 | type=0xff | state=0x00 | unknown=0xff * */ f_end: goto_iaqt_page(IAQ_PAGE_HOME, aq_data); cleanAndTerminateThread(threadCtrl); // just stop compiler error, ptr is not valid as it's just been freed return ptr; } void *get_aqualink_iaqtouch_freezeprotect( void *ptr ) { LOG(IAQT_LOG,LOG_ERR, "IAQ Touch get_aqualink_iaqtouch_freezeprotect has not beed tested\n"); LOG(IAQT_LOG,LOG_ERR, "**** We should not be here ****\n"); struct programmingThreadCtrl *threadCtrl; threadCtrl = (struct programmingThreadCtrl *) ptr; struct aqualinkdata *aq_data = threadCtrl->aq_data; waitForSingleThreadOrTerminate(threadCtrl, AQ_GET_IAQTOUCH_FREEZEPROTECT); if ( goto_iaqt_page(IAQ_PAGE_FREEZE_PROTECT, aq_data) == false ) goto f_end; // The Message at index 0 is the deg that freeze protect is set to. int frz = rsm_atoi(iaqtGetMessageLine(0)); if (frz >= 0) { aq_data->frz_protect_set_point = frz; LOG(IAQT_LOG,LOG_NOTICE, "IAQ Touch Freeze Protection setpoint %d\n",frz); } // Need to run over table messages and check ens with X for on off. // Go to status page on startup to read devices goto_iaqt_page(IAQ_PAGE_STATUS, aq_data); f_end: goto_iaqt_page(IAQ_PAGE_HOME, aq_data); cleanAndTerminateThread(threadCtrl); // just stop compiler error, ptr is not valid as it's just been freed return ptr; } void *get_aqualink_iaqtouch_setpoints( void *ptr ) { struct programmingThreadCtrl *threadCtrl; threadCtrl = (struct programmingThreadCtrl *) ptr; struct aqualinkdata *aq_data = threadCtrl->aq_data; struct iaqt_page_button *button; waitForSingleThreadOrTerminate(threadCtrl, AQ_GET_IAQTOUCH_SETPOINTS); if ( goto_iaqt_page(IAQ_PAGE_SET_TEMP, aq_data) == false ) goto f_end; // Button 0 is "Pool Heat 50" // Button 2 is "Spa Heat 100" button = iaqtFindButtonByLabel("Pool Heat"); if (button != NULL) { aq_data->pool_htr_set_point = rsm_atoi((char *)&button->name + 9); LOG(IAQT_LOG,LOG_DEBUG, "IAQ Touch got to Pool heater setpoint %d\n",aq_data->pool_htr_set_point); } else { button = iaqtFindButtonByLabel("Temp1"); if (button != NULL) { aq_data->pool_htr_set_point = rsm_atoi((char *)&button->name + 5); LOG(IAQT_LOG,LOG_DEBUG, "IAQ Touch got to Temp1 setpoint %d\n",aq_data->pool_htr_set_point); if (isSINGLE_DEV_PANEL != true) { changePanelToMode_Only(); LOG(AQRS_LOG,LOG_ERR, "AqualinkD set to 'Combo Pool & Spa' but detected 'Only Pool OR Spa' panel, please change config\n"); } } } button = iaqtFindButtonByLabel("Spa Heat"); if (button != NULL) { aq_data->spa_htr_set_point = rsm_atoi((char *)&button->name + 8); LOG(IAQT_LOG,LOG_DEBUG, "IAQ Touch got to Spa heater setpoint %d\n",aq_data->spa_htr_set_point); } else { button = iaqtFindButtonByLabel("Temp2"); if (button != NULL) { aq_data->spa_htr_set_point = rsm_atoi((char *)&button->name + 5); LOG(IAQT_LOG,LOG_DEBUG, "IAQ Touch got to Temp2 setpoint %d\n",aq_data->spa_htr_set_point); if (isSINGLE_DEV_PANEL != true) { changePanelToMode_Only(); LOG(AQRS_LOG,LOG_ERR, "AqualinkD set to 'Combo Pool & Spa' but detected 'Only Pool OR Spa' panel, please change config\n"); } } } if ( goto_iaqt_page(IAQ_PAGE_FREEZE_PROTECT, aq_data) == false ) goto f_end; // The Message at index 0 is the deg that freeze protect is set to. int frz = rsm_atoi(iaqtGetMessageLine(0)); if (frz >= 0) { aq_data->frz_protect_set_point = frz; LOG(IAQT_LOG,LOG_NOTICE, "IAQ Touch Freeze Protection setpoint %d\n",frz); } // Need to run over table messages and check ens with X for on off. // Go to status page on startup to read devices goto_iaqt_page(IAQ_PAGE_STATUS, aq_data); f_end: goto_iaqt_page(IAQ_PAGE_HOME, aq_data); cleanAndTerminateThread(threadCtrl); // just stop compiler error, ptr is not valid as it's just been freed return ptr; } // THIS IS NOT FINISHED. void *get_aqualink_iaqtouch_aux_labels( void *ptr ) { struct programmingThreadCtrl *threadCtrl; threadCtrl = (struct programmingThreadCtrl *) ptr; struct aqualinkdata *aq_data = threadCtrl->aq_data; int i; waitForSingleThreadOrTerminate(threadCtrl, AQ_GET_IAQTOUCH_AUX_LABELS); if ( goto_iaqt_page(IAQ_PAGE_LABEL_AUX, aq_data) == false ) goto f_end; // Need to loop over messages. Tab 0x09 is next in each message /* * Info: Table Messages 00| Circuit Circuit Name Freeze * Info: Table Messages 01| Aux1 Aux1 No * Info: Table Messages 02| Aux2 Aux2 No * Info: Table Messages 03| Aux3 Pool Light No * Info: Table Messages 04| Aux4 Aux4 No * Info: Table Messages 05| Aux5 Aux5 No * Info: Table Messages 06| Aux6 Aux6 No * Info: Table Messages 07| Aux7 Aux7 No * * Info: Table Messages ??| Aux B7 Aux B7 No */ const char *buf; int aux; // Loop over panel buttons or lines which ever is lowest. //for(i=1; i < 18; i++) // NSF Need to take out hard code of 18 for(i=1; i < PANEL_SIZE(); i++) { buf = iaqtGetTableInfoLine(i); LOG(IAQT_LOG,LOG_INFO, "From Messages %.2d| %s\n",i,buf); //printf("**** BUF '%s'\n",aux,buf); aux = rsm_atoi(buf + 3); LOG(IAQT_LOG,LOG_INFO, "**** AUX %d = '%s'\n",aux,buf + 5); } f_end: goto_iaqt_page(IAQ_PAGE_HOME, aq_data); cleanAndTerminateThread(threadCtrl); return ptr; } bool set_aqualink_iaqtouch_aquapure( struct aqualinkdata *aq_data, bool boost, int val ) { struct iaqt_page_button *button; static unsigned char b_pool = NUL; static unsigned char b_spa = NUL; static unsigned char b_boost = NUL; if ( goto_iaqt_page(IAQ_PAGE_SET_SWG, aq_data) == false ) return false; // IF pool button not set, first time here, need to cache the buttons // as we only get them once. if (b_pool == NUL) { button = iaqtFindButtonByLabel("Pool"); if (button != NULL) b_pool = button->keycode; button = iaqtFindButtonByLabel("Spa"); if (button != NULL) b_spa = button->keycode; button = iaqtFindButtonByLabel("Quick Boost"); if (button != NULL) b_boost = button->keycode; } // If spa is on, set SWG for spa, if not set SWG for pool if (boost) { if (b_boost != NUL) send_aqt_cmd(b_boost); else { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find Boost button on SWG page\n"); return false; } waitfor_iaqt_queue2empty(); if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_SET_QBOOST) { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find Boost Start button on SWG page\n"); } if(val==true) button = iaqtFindButtonByLabel("Start"); else button = iaqtFindButtonByLabel("Stop"); send_aqt_cmd(button->keycode); waitfor_iaqt_queue2empty(); waitfor_iaqt_nextPage(aq_data); aq_data->boost = val; } else { if (aq_data->aqbuttons[SPA_INDEX].led->state != OFF) { if (b_spa != NUL) send_aqt_cmd(b_spa); else { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find Spa button on SWG page\n"); return false; } } else { if (b_pool != NUL) send_aqt_cmd(b_pool); else { LOG(IAQT_LOG, LOG_ERR, "IAQ Touch did not find Pool button on SWG page\n"); return false;; } } waitfor_iaqt_queue2empty(); queue_iaqt_control_command(0, val); waitfor_iaqt_ctrl_queue2empty(); waitfor_iaqt_nextMessage(aq_data, CMD_IAQ_PAGE_BUTTON); } //LOG(IAQT_LOG, LOG_NOTICE, "IAQ Touch got to %s page\n", VSPstr); // We should get the device page back. //waitfor_iaqt_nextPage(aq_data); goto_iaqt_page(IAQ_PAGE_STATUS, aq_data); return true; } void *set_aqualink_iaqtouch_swg_percent( void *ptr ) { struct programmingThreadCtrl *threadCtrl; threadCtrl = (struct programmingThreadCtrl *) ptr; struct aqualinkdata *aq_data = threadCtrl->aq_data; waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_SWG_PERCENT); int val = atoi((char*)threadCtrl->thread_args); val = setpoint_check(SWG_SETPOINT, val, aq_data); if (set_aqualink_iaqtouch_aquapure(aq_data, false, val)) setSWGpercent(aq_data, val); goto_iaqt_page(IAQ_PAGE_HOME, aq_data); cleanAndTerminateThread(threadCtrl); return ptr; } void *set_aqualink_iaqtouch_swg_boost( void *ptr ) { struct programmingThreadCtrl *threadCtrl; threadCtrl = (struct programmingThreadCtrl *) ptr; struct aqualinkdata *aq_data = threadCtrl->aq_data; waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_SWG_BOOST); int val = atoi((char*)threadCtrl->thread_args); //logMessage(LOG_DEBUG, "programming BOOST to %s\n", val==true?"On":"Off"); set_aqualink_iaqtouch_aquapure(aq_data, true, val); goto_iaqt_page(IAQ_PAGE_HOME, aq_data); cleanAndTerminateThread(threadCtrl); return ptr; } bool set_aqualink_iaqtouch_heater_setpoint( struct aqualinkdata *aq_data, bool pool, int val) { struct iaqt_page_button *button; char *name; if ( goto_iaqt_page(IAQ_PAGE_SET_TEMP, aq_data) == false ) return false; if (isCOMBO_PANEL) { if (pool) name = "Pool Heat"; else name = "Spa Heat"; } else { if (pool) name = "Temp1"; else name = "Temp2"; } button = iaqtFindButtonByLabel(name); if (button == NULL) { //aq_data->pool_htr_set_point = rsm_atoi((char *)&button->name + 9); LOG(IAQT_LOG,LOG_ERR, "IAQ Touch did not find heater setpoint '%s' on page\n",name); return false; } send_aqt_cmd(button->keycode); waitfor_iaqt_queue2empty(); queue_iaqt_control_command(0, val); waitfor_iaqt_ctrl_queue2empty(); waitfor_iaqt_nextMessage(aq_data, CMD_IAQ_PAGE_BUTTON); button = iaqtFindButtonByLabel(name); if (button != NULL) { if (pool) aq_data->pool_htr_set_point = rsm_atoi((char *)&button->name + strlen(name)); else aq_data->spa_htr_set_point = rsm_atoi((char *)&button->name + strlen(name)); LOG(IAQT_LOG,LOG_DEBUG, "IAQ Touch set %s heater setpoint to %d\n",name,pool?aq_data->pool_htr_set_point:aq_data->spa_htr_set_point); } return true; } void *set_aqualink_iaqtouch_spa_heater_temp( void *ptr ) { struct programmingThreadCtrl *threadCtrl; threadCtrl = (struct programmingThreadCtrl *) ptr; struct aqualinkdata *aq_data = threadCtrl->aq_data; waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_SPA_HEATER_TEMP); int val = atoi((char*)threadCtrl->thread_args); val = setpoint_check(SPA_HTR_SETOINT, val, aq_data); set_aqualink_iaqtouch_heater_setpoint(aq_data, false, val); goto_iaqt_page(IAQ_PAGE_HOME, aq_data); cleanAndTerminateThread(threadCtrl); return ptr; } void *set_aqualink_iaqtouch_pool_heater_temp( void *ptr ) { struct programmingThreadCtrl *threadCtrl; threadCtrl = (struct programmingThreadCtrl *) ptr; struct aqualinkdata *aq_data = threadCtrl->aq_data; waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_POOL_HEATER_TEMP); int val = atoi((char*)threadCtrl->thread_args); val = setpoint_check(POOL_HTR_SETOINT, val, aq_data); set_aqualink_iaqtouch_heater_setpoint(aq_data, true, val); goto_iaqt_page(IAQ_PAGE_HOME, aq_data); cleanAndTerminateThread(threadCtrl); return ptr; } void *set_aqualink_iaqtouch_pump_vs_program( void *ptr ) { struct programmingThreadCtrl *threadCtrl; threadCtrl = (struct programmingThreadCtrl *) ptr; struct aqualinkdata *aq_data = threadCtrl->aq_data; char *buf = (char*)threadCtrl->thread_args; char VSPstr[20]; int structIndex; struct iaqt_page_button *button; waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_PUMP_VS_PROGRAM); int pumpIndex = atoi(&buf[0]); //int pumpRPM = -1; int vspindex = atoi(&buf[2]); for (structIndex=0; structIndex < aq_data->num_pumps; structIndex++) { if (aq_data->pumps[structIndex].pumpIndex == pumpIndex) { if (aq_data->pumps[structIndex].pumpType == PT_UNKNOWN) { LOG(IAQT_LOG,LOG_ERR, "Can't set Pump RPM/GPM until type is known\n"); cleanAndTerminateThread(threadCtrl); return ptr; } //pumpRPM = RPM_check(aq_data->pumps[structIndex].pumpType, atoi(&buf[2]), aq_data); break; } } //int pumpRPM = atoi(&buf[2]); //int pumpIndex = 1; sprintf(VSPstr, "VSP%1d Spd ADJ",pumpIndex); LOG(IAQT_LOG,LOG_NOTICE, "Set Pump %d to VSP Index %d\n",pumpIndex,vspindex); if ( goto_iaqt_page(IAQ_PAGE_DEVICES, aq_data) == false ) goto f_end; button = iaqtFindButtonByLabel(VSPstr); if (button == NULL) { LOG(IAQT_LOG, LOG_ERR, "Did not find '%s' button on page setup\n", VSPstr); goto f_end; } send_aqt_cmd(button->keycode); if (waitfor_iaqt_nextPage(aq_data) != IAQ_PAGE_SET_VSP) { LOG(IAQT_LOG, LOG_ERR, "Did not find %s page\n", VSPstr); goto f_end; } LOG(IAQT_LOG, LOG_INFO, "Got to %s page\n", VSPstr); // Select the button index. button = iaqtFindButtonByIndex(vspindex); if (button == NULL) { LOG(IAQT_LOG, LOG_ERR, "Did not find '%d' button on page\n", vspindex); goto f_end; } send_aqt_cmd(button->keycode); waitfor_iaqt_queue2empty(); // Probably wait. f_end: goto_iaqt_page(IAQ_PAGE_HOME, aq_data); cleanAndTerminateThread(threadCtrl); return ptr; } void *set_aqualink_iaqtouch_time( void *ptr ) { struct programmingThreadCtrl *threadCtrl; threadCtrl = (struct programmingThreadCtrl *) ptr; struct aqualinkdata *aq_data = threadCtrl->aq_data; struct iaqt_page_button *button; char buf[20]; //int len; int i; //bool AM; waitForSingleThreadOrTerminate(threadCtrl, AQ_SET_IAQTOUCH_SET_TIME); time_t now = time(0); // get time now struct tm *result = localtime(&now); if ( goto_iaqt_page(IAQ_PAGE_SET_TIME, aq_data) == false ) { LOG(IAQT_LOG,LOG_ERR, "IAQ Touch didn't find set time page\n"); goto f_end; } // Button 0 is date. button = iaqtFindButtonByIndex(0); if (button == NULL) { LOG(IAQT_LOG,LOG_ERR, "IAQ Touch date button on set time page\n"); goto f_end; } // Print DD/MM/YY into string strftime(buf, 20, "%D", result); // Do we need to set the date if (rsm_strcmp(buf, (char *)button->name) != 0) { // Press date button. send_aqt_cmd(button->keycode); waitfor_iaqt_queue2empty(); // Queue the date string if ( queue_iaqt_control_command_str(icct_setdate, buf)) { LOG(IAQT_LOG,LOG_NOTICE, "Set date to %s\n",buf); waitfor_iaqt_ctrl_queue2empty(); } else { LOG(IAQT_LOG,LOG_ERR, "Failed to queue commandset for setting date\n"); } } else { LOG(IAQT_LOG,LOG_DEBUG, "Date %s is accurate, not changing\n",button->name); } // Always assume time is incorrect if wer have been called. // Button 1 is time. button = iaqtFindButtonByIndex(1); if (button == NULL) { LOG(IAQT_LOG,LOG_ERR, "IAQ Touch time button on set time page\n"); goto f_end; } // Press time button. send_aqt_cmd(button->keycode); waitfor_iaqt_queue2empty(); // Set AM/PM button, not sure how to get default state since it's blank to start, so keep pressing till we get AM or PM we want i = 0; strftime(buf, 20, "%p", result); LOG(IAQT_LOG,LOG_DEBUG, "IAQ Touch AM/PM check '%s' to '%s'\n",buf,iaqtGetMessageLine(2)); while ( rsm_strcmp(buf, iaqtGetMessageLine(2)) != 0 && i < 3) { send_aqt_cmd(0x13); waitfor_iaqt_queue2empty(); waitfor_iaqt_nextMessage(aq_data, CMD_IAQ_PAGE_MSG); i++; } LOG(IAQT_LOG,LOG_DEBUG, "IAQ Touch AM/PM is now '%s'\n",iaqtGetMessageLine(2)); // Print HH:MM into string strftime(buf, 20, "%I:%M", result); if (queue_iaqt_control_command_str(icct_settime, buf)) { LOG(IAQT_LOG,LOG_NOTICE, "Set time to %s\n",buf); waitfor_iaqt_ctrl_queue2empty(); } else { LOG(IAQT_LOG,LOG_ERR, "Failed to queue commandset for setting time\n"); } f_end: goto_iaqt_page(IAQ_PAGE_HOME, aq_data); cleanAndTerminateThread(threadCtrl); return ptr; } /* Infor on setting time //Info: iAQ Touch: Button 00| 07/05/20 | type=0x01 | state=0x00 | unknown=0x00 | keycode=0x11 //Info: iAQ Touch: Button 01| 1:02 PM | type=0x01 | state=0x00 | unknown=0x00 | keycode=0x12 // Send button to select time / date. // Date //Debug: RS Serial: To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x11|0x24|0x10|0x03| // Set Time //Debug: RS Serial: To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x12|0x25|0x10|0x03| // Wait for Time page comes back //Info: iAQ Touch: Page: Set Time | 0x4b // Set time to 01/01/01 //Debug: RS Serial: To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x80|0x93|0x10|0x03| //Debug: iAQ Touch: To 0x33 of type Unknown | HEX: 0x10|0x02|0x33|0x31|0x01|0x77|0x10|0x03| //Debug: RS Serial: To 0x00 of type iAq pBut | HEX: 0x10|0x02|0x00|0x24|0x31|0x30|0x31|0x2f|0x30|0x31|0x2f|0x30|0x31|0x00|0xcd|0xcd|0xcd|0xcd|0xcd|0xcd|0xcd|0x83|0x10|0x03| //0x30|0x31|0x2f|0x30|0x31|0x2f|0x30|0x31| // Set Time to 02/02/02 //Debug: RS Serial: To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x80|0x93|0x10|0x03| //Debug: iAQ Touch: To 0x33 of type Unknown | HEX: 0x10|0x02|0x33|0x31|0x01|0x77|0x10|0x03| //Debug: RS Serial: To 0x00 of type iAq pBut | HEX: 0x10|0x02|0x00|0x24|0x31|0x30|0x32|0x2f|0x30|0x32|0x2f|0x30|0x32|0x00|0xcd|0xcd|0xcd|0xcd|0xcd|0xcd|0xcd|0x86|0x10|0x03| // So 0x30 is base numbers again (ie 0) //0x24|0x31 Set time & start? //0x2f is seperator or / //0x00 is end //0xcd padding // Set time 01:01 AM //Debug: RS Serial: To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x80|0x93|0x10|0x03| //Debug: iAQ Touch: To 0x33 of type Unknown | HEX: 0x10|0x02|0x33|0x31|0x01|0x77|0x10|0x03| //Debug: RS Serial: To 0x00 of type iAq pBut | HEX: 0x10|0x02|0x00|0x24|0x31|0x30|0x31|0x3a|0x30|0x31|0x00|0x30|0x32|0x00|0xcd|0xcd|0xcd|0xcd|0xcd|0xcd|0xcd|0x60|0x10|0x03| // Set time 01:01 PM //Debug: RS Serial: To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x80|0x93|0x10|0x03| //Debug: iAQ Touch: To 0x33 of type Unknown | HEX: 0x10|0x02|0x33|0x31|0x01|0x77|0x10|0x03| //Debug: RS Serial: To 0x00 of type iAq pBut | HEX: 0x10|0x02|0x00|0x24|0x31|0x30|0x31|0x3a|0x30|0x31|0x00|0x30|0x32|0x00|0xcd|0xcd|0xcd|0xcd|0xcd|0xcd|0xcd|0x60|0x10|0x03| // Set time 09:55 //Debug: RS Serial: To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x80|0x93|0x10|0x03| //Debug: iAQ Touch: To 0x33 of type Unknown | HEX: 0x10|0x02|0x33|0x31|0x01|0x77|0x10|0x03| //Debug: RS Serial: To 0x00 of type iAq pBut | HEX: 0x10|0x02|0x00|0x24|0x31|0x30|0x39|0x3a|0x35|0x35|0x00|0x30|0x32|0x00|0xcd|0xcd|0xcd|0xcd|0xcd|0xcd|0xcd|0x71|0x10|0x03| // 0x3a seperator : // Select PM // Debug: RS Serial: To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x13|0x26|0x10|0x03| // Debug: iAQ Touch: To 0x33 of type iAq pMes | HEX: 0x10|0x02|0x33|0x25|0x02|0x50|0x4d|0x00|0x09|0x10|0x03| // Select AM // Debug: RS Serial: To 0x00 of type Ack | HEX: 0x10|0x02|0x00|0x01|0x00|0x13|0x26|0x10|0x03| // Debug: iAQ Touch: To 0x33 of type iAq pMes | HEX: 0x10|0x02|0x33|0x25|0x02|0x41|0x4d|0x00|0xfa|0x10|0x03| //LOG(IAQT_LOG,LOG_DEBUG "Setting time to %d/%d/%d %d:%d\n", result->tm_mon + 1, result->tm_mday, result->tm_year + 1900, result->tm_hour + 1, result->tm_min); */