/*
* 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
#include
#include
#include
//#include
//#include
//#include
//#include
#include
//#include
//#include
#include
#include
#include
#define CONFIG_C
#include "config.h"
#include "utils.h"
#include "aq_serial.h"
#include "aq_panel.h"
#include "aqualink.h"
#include "iaqualink.h"
#include "color_lights.h"
#include "aq_scheduler.h"
#include "aq_panel.h"
#include "rs_msg_utils.h"
#include "aq_systemutils.h"
#define MAXCFGLINE 256
char *generate_mqtt_id(char *buf, int len);
pump_detail *getpump(struct aqualinkdata *aqdata, int button);
bool populatePumpData(struct aqualinkdata *aqdata, char *pumpcfg ,aqkey *button, char *value);
pump_detail *getPumpFromButtonID(struct aqualinkdata *aqdata, aqkey *button);
aqkey *getVirtualButton(struct aqualinkdata *aqdata, int num);
struct aqualinkdata *_aqdata = NULL;
struct tmpPanelInfo {
int size;
bool rs;
bool combo;
bool dual;
};
struct tmpPanelInfo _defaultPanel;
/*
* initialize data to default values
*/
/* odd ball
ftdi_low_latency
rs485_frame_delay
display_warnings_in_web
sync_panel_time
overide_freeze_protect
*/
void set_cfg_parm_to_default(cfgParam *parm) {
//printf("Setting default %s\n",parm->name);
switch(parm->value_type) {
case CFG_STRING:
*(char **)parm->value_ptr = (char *)parm->default_value;
break;
case CFG_INT:
*(int *)parm->value_ptr = *(int *) parm->default_value;
break;
case CFG_BOOL:
*(bool *)parm->value_ptr = *(bool *) parm->default_value;
break;
case CFG_HEX:
*(unsigned char *)parm->value_ptr = *(unsigned char *)parm->default_value;
break;
case CFG_FLOAT:
*(float *)parm->value_ptr = *(float *)parm->default_value;
break;
case CFG_BITMASK:
if ( *(bool *)parm->default_value == true) {
*(uint8_t *)parm->value_ptr |= parm->mask;
} else {
*(uint8_t *)parm->value_ptr &= ~parm->mask;
}
break;
case CFG_SPECIAL:
if (strncasecmp(parm->name, CFG_N_log_level, strlen(CFG_N_log_level)) == 0) {
*(int *)parm->value_ptr = *(int *)parm->default_value;
} else if (strncasecmp(parm->name, CFG_N_panel_type, strlen(CFG_N_panel_type)) == 0) {
//setPanelByName(aqdata, cleanwhitespace(value));
} else {
LOG(AQUA_LOG,LOG_ERR, "ADD CONFIG DEFAULT FOR %s\n",parm->name);
}
break;
}
}
void set_config_defaults()
{
for (int i=0; i <= _numCfgParams; i++) {
set_cfg_parm_to_default(&_cfgParams[i]);
}
}
/*
#define cfgint "const int"
#define cfgbool "const bool"
#define cfgchar "const char *"
#define cfghex "const unsigned char"
*/
const int _dcfg_unknownInt = TEMP_UNKNOWN;
const unsigned char _dcfg_unknownHex = NUL; // 0x00 don't use
const unsigned char _dcfg_findIDHex = 0xFF;
const bool _dcfg_false = false;
const bool _dcfg_true = true;
const int _dcfg_loglevel = DEFAULT_LOG_LEVEL;
const char *_dcfg_web_port = DEFAULT_WEBPORT;
//const char *_dcfg_web_port = "80";
const char *_dcfg_web_root = DEFAULT_WEBROOT;
const char *_dcfg_serial_port = DEFAULT_SERIALPORT;
const char *_dcfg_mqtt_ha_discover = DEFAULT_HASS_DISCOVER;
const char *_dcfg_mqtt_aq_tp = DEFAULT_MQTT_AQ_TP;
const char *_dcfg_null = NULL;
const int _dcfg_zero = 0;
const int _dcfg_light_programming_mode = 0;
const int _dcfg_light_programming_initial_on = 15;
const int _dcfg_light_programming_initial_off = 12;
void init_parameters (struct aqconfig * parms)
{
//#ifdef CONFIG_DEV_TEST
_numCfgParams = 0;
const int unknownInt = TEMP_UNKNOWN;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.socket_port;
_cfgParams[_numCfgParams].value_type = CFG_STRING;
_cfgParams[_numCfgParams].name = CFG_N_socket_port;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
//_cfgParams[_numCfgParams].config_mask |= CFG_READONLY; // Take out once below is working
_cfgParams[_numCfgParams].config_mask |= CFG_FORCE_RESTART;
_cfgParams[_numCfgParams].default_value = (void *)_dcfg_web_port;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.serial_port;
_cfgParams[_numCfgParams].value_type = CFG_STRING;
_cfgParams[_numCfgParams].name = CFG_N_serial_port;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
//_cfgParams[_numCfgParams].config_mask |= CFG_READONLY; // Take out once below is working
_cfgParams[_numCfgParams].config_mask |= CFG_FORCE_RESTART;
_cfgParams[_numCfgParams].default_value = (void *)_dcfg_serial_port;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.log_level;
_cfgParams[_numCfgParams].value_type = CFG_SPECIAL; // Set with _aqconfig_.log_level = text2elevel(cleanalloc(value));
_cfgParams[_numCfgParams].name = CFG_N_log_level;
_cfgParams[_numCfgParams].valid_values = CFG_V_log_level;
_cfgParams[_numCfgParams].default_value = (void *) &_dcfg_loglevel;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.web_directory;
_cfgParams[_numCfgParams].value_type = CFG_STRING;
_cfgParams[_numCfgParams].name = CFG_N_web_directory;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].config_mask |= CFG_READONLY;
_cfgParams[_numCfgParams].default_value = NULL;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.paneltype_mask;
_cfgParams[_numCfgParams].value_type = CFG_SPECIAL;
_cfgParams[_numCfgParams].name = CFG_N_panel_type;
_cfgParams[_numCfgParams].default_value = NULL;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.device_id;
_cfgParams[_numCfgParams].value_type = CFG_HEX;
_cfgParams[_numCfgParams].name = CFG_N_device_id;
_cfgParams[_numCfgParams].valid_values = CFG_V_device_id;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_findIDHex;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.rssa_device_id;
_cfgParams[_numCfgParams].value_type = CFG_HEX;
_cfgParams[_numCfgParams].name = CFG_N_rssa_device_id;
_cfgParams[_numCfgParams].valid_values = CFG_V_rssa_device_id;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_unknownHex;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.extended_device_id_programming;
_cfgParams[_numCfgParams].value_type = CFG_BOOL;
_cfgParams[_numCfgParams].name = CFG_N_extended_device_id_programming;
_cfgParams[_numCfgParams].valid_values = CFG_V_BOOL;
_cfgParams[_numCfgParams].config_mask |= CFG_HIDE;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.extended_device_id;
_cfgParams[_numCfgParams].value_type = CFG_HEX;
_cfgParams[_numCfgParams].name = CFG_N_extended_device_id;
_cfgParams[_numCfgParams].valid_values = CFG_V_extended_device_id;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_unknownHex;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.enable_iaqualink;
_cfgParams[_numCfgParams].value_type = CFG_BOOL;
_cfgParams[_numCfgParams].name = CFG_N_enable_iaqualink;
_cfgParams[_numCfgParams].valid_values = CFG_V_BOOL;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
#ifndef AQ_MANAGER
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.log_file;
_cfgParams[_numCfgParams].value_type = CFG_STRING;
_cfgParams[_numCfgParams].name = CFG_N_log_file;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = NULL;
#endif
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_server;
_cfgParams[_numCfgParams].value_type = CFG_STRING;
_cfgParams[_numCfgParams].name = CFG_N_mqtt_server;
_cfgParams[_numCfgParams].default_value = (void *)_dcfg_null;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_user;
_cfgParams[_numCfgParams].value_type = CFG_STRING;
_cfgParams[_numCfgParams].name = CFG_N_mqtt_user;
_cfgParams[_numCfgParams].default_value = (void *)NULL;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_passwd;
_cfgParams[_numCfgParams].value_type = CFG_STRING;
_cfgParams[_numCfgParams].name = CFG_N_mqtt_passwd;
_cfgParams[_numCfgParams].config_mask |= CFG_PASSWD_MASK;
_cfgParams[_numCfgParams].default_value = (void *)NULL;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_aq_topic;
_cfgParams[_numCfgParams].value_type = CFG_STRING;
_cfgParams[_numCfgParams].name = CFG_N_mqtt_aq_topic;
_cfgParams[_numCfgParams].default_value = (void *)_dcfg_mqtt_aq_tp;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_hass_discover_topic;
_cfgParams[_numCfgParams].value_type = CFG_STRING;
_cfgParams[_numCfgParams].name = CFG_N_mqtt_hass_discover_topic;
_cfgParams[_numCfgParams].default_value = (void *)_dcfg_mqtt_ha_discover;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_hass_discover_use_mac;
_cfgParams[_numCfgParams].value_type = CFG_BOOL;
_cfgParams[_numCfgParams].name = CFG_N_mqtt_hass_discover_use_mac;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_timed_update;
_cfgParams[_numCfgParams].value_type = CFG_BOOL;
_cfgParams[_numCfgParams].name = CFG_N_mqtt_timed_update;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.convert_mqtt_temp;
_cfgParams[_numCfgParams].value_type = CFG_BOOL;
_cfgParams[_numCfgParams].name = CFG_N_convert_mqtt_temp;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_dz_sub_topic;
_cfgParams[_numCfgParams].value_type = CFG_STRING;
_cfgParams[_numCfgParams].name = CFG_N_mqtt_dz_sub_topic;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = NULL;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_dz_pub_topic;
_cfgParams[_numCfgParams].value_type = CFG_STRING;
_cfgParams[_numCfgParams].name = CFG_N_mqtt_dz_pub_topic;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = NULL;
/*
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_dz_pub_topic;
_cfgParams[_numCfgParams].value_type = CFG_STRING;
_cfgParams[_numCfgParams].name = CFG_N_mqtt_dz_pub_topic;
_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].default_value = NULL;
*/
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.dzidx_air_temp;
_cfgParams[_numCfgParams].value_type = CFG_INT;
_cfgParams[_numCfgParams].name = CFG_N_dzidx_air_temp;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = (void *)&unknownInt;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.dzidx_pool_water_temp;
_cfgParams[_numCfgParams].value_type = CFG_INT;
_cfgParams[_numCfgParams].name = CFG_N_dzidx_pool_water_temp;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = (void *)&unknownInt;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.dzidx_spa_water_temp;
_cfgParams[_numCfgParams].value_type = CFG_INT;
_cfgParams[_numCfgParams].name = CFG_N_dzidx_spa_water_temp;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = (void *)&unknownInt;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.dzidx_swg_percent;
_cfgParams[_numCfgParams].value_type = CFG_INT;
_cfgParams[_numCfgParams].name = CFG_N_dzidx_swg_percent;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = (void *)&unknownInt;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.dzidx_swg_ppm;
_cfgParams[_numCfgParams].value_type = CFG_INT;
_cfgParams[_numCfgParams].name = CFG_N_dzidx_swg_ppm;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = (void *)&unknownInt;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.dzidx_swg_status;
_cfgParams[_numCfgParams].value_type = CFG_INT;
_cfgParams[_numCfgParams].name = CFG_N_dzidx_swg_status;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = (void *)&unknownInt;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.convert_dz_temp;
_cfgParams[_numCfgParams].value_type = CFG_BOOL;
_cfgParams[_numCfgParams].name = CFG_N_convert_dz_temp;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = (void *)&unknownInt;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.light_programming_mode;
_cfgParams[_numCfgParams].value_type = CFG_FLOAT;
_cfgParams[_numCfgParams].name = CFG_N_light_programming_mode;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_light_programming_mode;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.light_programming_initial_on;
_cfgParams[_numCfgParams].value_type = CFG_INT;
_cfgParams[_numCfgParams].name = CFG_N_light_programming_initial_on;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_light_programming_initial_on;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.light_programming_initial_off;
_cfgParams[_numCfgParams].value_type = CFG_INT;
_cfgParams[_numCfgParams].name = CFG_N_light_programming_initial_off;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_light_programming_initial_off;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_read_RS485_swg;
_cfgParams[_numCfgParams].mask = READ_RS485_SWG;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_read_RS485_ePump;
_cfgParams[_numCfgParams].mask = READ_RS485_JAN_PUMP;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_read_RS485_vsfPump;
_cfgParams[_numCfgParams].mask = READ_RS485_PEN_PUMP;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_read_RS485_JXi;
_cfgParams[_numCfgParams].mask = READ_RS485_JAN_JXI;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_read_RS485_LX;
_cfgParams[_numCfgParams].mask = READ_RS485_JAN_LX;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_read_RS485_Chem;
_cfgParams[_numCfgParams].mask = READ_RS485_JAN_CHEM;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_read_RS485_iAqualink;
_cfgParams[_numCfgParams].mask = READ_RS485_IAQUALNK;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.read_RS485_devmask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_read_RS485_HeatPump;
_cfgParams[_numCfgParams].mask = READ_RS485_HEATPUMP;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
// ADD FORCE OPTIONS HERE
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.force_device_devmask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_force_swg;
_cfgParams[_numCfgParams].mask = FORCE_SWG_SP;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.force_device_devmask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_force_ps_setpoints;
_cfgParams[_numCfgParams].mask = FORCE_POOLSPA_SP;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.force_device_devmask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_force_frzprotect_setpoints;
_cfgParams[_numCfgParams].mask = FORCE_FREEZEPROTECT_SP;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.force_device_devmask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_force_chem_feeder;
_cfgParams[_numCfgParams].mask = FORCE_CHEM_FEEDER;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
//When we add chiller support
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.force_device_devmask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_force_chiller;
_cfgParams[_numCfgParams].mask = FORCE_CHILLER;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.enable_scheduler;
_cfgParams[_numCfgParams].value_type = CFG_BOOL;
_cfgParams[_numCfgParams].name = CFG_N_enable_scheduler;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.schedule_event_mask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_event_check_usecron;
_cfgParams[_numCfgParams].mask = AQS_USE_CRON_PUMP_TIME;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.schedule_event_mask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_event_check_poweron;
_cfgParams[_numCfgParams].mask = AQS_POWER_ON;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.schedule_event_mask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_event_check_freezeprotectoff;
_cfgParams[_numCfgParams].mask = AQS_FRZ_PROTECT_OFF;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.schedule_event_mask;
_cfgParams[_numCfgParams].value_type = CFG_BITMASK;
_cfgParams[_numCfgParams].name = CFG_N_event_check_boostoff;
_cfgParams[_numCfgParams].mask = AQS_BOOST_OFF;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.sched_chk_pumpon_hour;
_cfgParams[_numCfgParams].value_type = CFG_INT;
_cfgParams[_numCfgParams].name = CFG_N_event_check_pumpon_hour;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_zero;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.sched_chk_pumpoff_hour;
_cfgParams[_numCfgParams].value_type = CFG_INT;
_cfgParams[_numCfgParams].name = CFG_N_event_check_pumpoff_hour;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_zero;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.sched_chk_booston_device;
_cfgParams[_numCfgParams].value_type = CFG_STRING;
_cfgParams[_numCfgParams].name = CFG_N_event_check_booston_device;
_cfgParams[_numCfgParams].default_value = NULL;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.ftdi_low_latency;
_cfgParams[_numCfgParams].value_type = CFG_BOOL;
_cfgParams[_numCfgParams].name = CFG_N_ftdi_low_latency;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.frame_delay;
_cfgParams[_numCfgParams].value_type = CFG_INT;
_cfgParams[_numCfgParams].name = CFG_N_rs485_frame_delay;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_zero;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.sync_panel_time;
_cfgParams[_numCfgParams].value_type = CFG_BOOL;
_cfgParams[_numCfgParams].name = CFG_N_sync_panel_time;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.display_warnings_web;
_cfgParams[_numCfgParams].value_type = CFG_BOOL;
_cfgParams[_numCfgParams].name = "display_warnings_in_web";
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.override_freeze_protect;
_cfgParams[_numCfgParams].value_type = CFG_BOOL;
_cfgParams[_numCfgParams].name = CFG_N_override_freeze_protect;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.report_zero_spa_temp;
_cfgParams[_numCfgParams].value_type = CFG_BOOL;
_cfgParams[_numCfgParams].name = CFG_N_report_zero_spa_temp;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true;
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.report_zero_pool_temp;
_cfgParams[_numCfgParams].value_type = CFG_BOOL;
_cfgParams[_numCfgParams].name = CFG_N_report_zero_pool_temp;
//_cfgParams[_numCfgParams].advanced = true;
_cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true;
#ifdef AQ_PDA
_numCfgParams++;
_cfgParams[_numCfgParams].value_ptr = &_aqconfig_.pda_sleep_mode;
_cfgParams[_numCfgParams].value_type = CFG_BOOL;
_cfgParams[_numCfgParams].name = CFG_N_pda_sleep_mode;
_cfgParams[_numCfgParams].default_value = (void *)&_dcfg_true;
#endif
//#endif
// Default to daemonize
parms->deamonize = true;
// clear the panel type
parms->paneltype_mask = 0;
// Create default panel if it get's missed from config
_defaultPanel.size = 6;
_defaultPanel.rs = true;
_defaultPanel.combo = true;
_defaultPanel.dual = false;
// Few other defaults we don;t set in general config
parms->log_protocol_packets = false; // Read & Write as packets write to file
parms->log_raw_bytes = false; // bytes read and write to file
#ifdef AQ_NO_THREAD_NETSERVICE
parms->rs_poll_speed = DEFAULT_POLL_SPEED;
parms->thread_netservices = true;
#endif
parms->device_pre_state = true;
clearDebugLogMask();
for (int i=0; i < MAX_RSSD_LOG_FILTERS; i++) {
parms->RSSD_LOG_filter[i] = NUL;
}
generate_mqtt_id(parms->mqtt_ID, MQTT_ID_LEN);
set_config_defaults();
//set_config_defaults();
//int i;
//char *p;
//parms->rs_panel_size = 8;
/*
parms->serial_port = DEFAULT_SERIALPORT;
parms->log_level = DEFAULT_LOG_LEVEL;
parms->socket_port = DEFAULT_WEBPORT;
parms->web_directory = DEFAULT_WEBROOT;
//parms->device_id = strtoul(DEFAULT_DEVICE_ID, &p, 16);
parms->device_id = strtoul(DEFAULT_DEVICE_ID, NULL, 16);
parms->rssa_device_id = NUL;
*/
/*
parms->extended_device_id = NUL;
parms->extended_device_id_programming = false;
*/
/*
//sscanf(DEFAULT_DEVICE_ID, "0x%x", &parms->device_id);
parms->override_freeze_protect = FALSE;
parms->mqtt_dz_sub_topic = DEFAULT_MQTT_DZ_OUT;
parms->mqtt_dz_pub_topic = DEFAULT_MQTT_DZ_IN;
parms->mqtt_hass_discover_topic = DEFAULT_HASS_DISCOVER;
parms->mqtt_aq_topic = DEFAULT_MQTT_AQ_TP;
parms->mqtt_server = DEFAULT_MQTT_SERVER;
parms->mqtt_user = DEFAULT_MQTT_USER;
parms->mqtt_passwd = DEFAULT_MQTT_PASSWD;
parms->mqtt_hass_discover_use_mac = false;
parms->dzidx_air_temp = TEMP_UNKNOWN;
parms->dzidx_pool_water_temp = TEMP_UNKNOWN;
parms->dzidx_spa_water_temp = TEMP_UNKNOWN;
parms->dzidx_swg_percent = TEMP_UNKNOWN;
parms->dzidx_swg_ppm = TEMP_UNKNOWN;
parms->dzidx_swg_status = TEMP_UNKNOWN;
//parms->dzidx_pool_thermostat = TEMP_UNKNOWN; // removed until domoticz has a better virtual thermostat
//parms->dzidx_spa_thermostat = TEMP_UNKNOWN; // removed until domoticz has a better virtual thermostat
parms->light_programming_mode = 0;
parms->light_programming_initial_on = 15;
parms->light_programming_initial_off = 12;
//parms->light_programming_button_pool = TEMP_UNKNOWN;
//parms->light_programming_button_spa = TEMP_UNKNOWN;
parms->deamonize = true;
#ifndef AQ_MANAGER
parms->log_file = '\0';
#endif
#ifdef AQ_PDA
parms->pda_sleep_mode = false;
#endif
//parms->onetouch_mode = false;
parms->convert_mqtt_temp = true;
parms->convert_dz_temp = true;
parms->report_zero_pool_temp = true;
parms->report_zero_spa_temp = true;
//parms->read_all_devices = true;
//parms->read_pentair_packets = false;
parms->read_RS485_devmask = 0;
parms->use_panel_aux_labels = false;
//parms->force_swg = false;
//parms->force_ps_setpoints = false;
//parms->force_frzprotect_setpoints = false;
//parms->force_chem_feeder = false;
//parms->swg_pool_and_spa = false;
//parms->swg_zero_ignore = DEFAULT_SWG_ZERO_IGNORE_COUNT;
parms->display_warnings_web = false;
parms->log_protocol_packets = false; // Read & Write as packets write to file
parms->log_raw_bytes = false; // bytes read and write to file
parms->sync_panel_time = true;
#ifdef AQ_NO_THREAD_NETSERVICE
parms->rs_poll_speed = DEFAULT_POLL_SPEED;
parms->thread_netservices = true;
#endif
parms->enable_scheduler = true;
parms->schedule_event_mask = 0;
//parms->sched_chk_poweron = false;
//parms->sched_chk_freezeprotectoff = false;
//parms->sched_chk_boostoff = false;
parms->sched_chk_pumpon_hour = 0;
parms->sched_chk_pumpoff_hour = 0;
parms->ftdi_low_latency = true;
parms->frame_delay = 0;
parms->device_pre_state = true;
*/
}
/*
strlcpy is pulled from here.
https://github.com/freebsd/freebsd-src/blob/master/sys/libkern/strlcpy.c
*/
/*
* Copy string src to buffer dst of size dsize. At most dsize-1
* chars will be copied. Always NUL terminates (unless dsize == 0).
* Returns strlen(src); if retval >= dsize, truncation occurred.
*/
size_t strlcpy(char * __restrict dst, const char * __restrict src, size_t dsize)
{
const char *osrc = src;
size_t nleft = dsize;
/* Copy as many bytes as will fit. */
if (nleft != 0) {
while (--nleft != 0) {
if ((*dst++ = *src++) == '\0')
break;
}
}
/* Not enough room in dst, add NUL and traverse rest of src. */
if (nleft == 0) {
if (dsize != 0)
*dst = '\0'; /* NUL-terminate dst */
while (*src++)
;
}
return(src - osrc - 1); /* count does not include NUL */
}
char *cleanalloc(char*str)
{
return ncleanalloc(str, 0);
/*
if (str == NULL)
return str;
char *result;
str = cleanwhitespace(str);
result = (char*)malloc(strlen(str)+1);
strcpy ( result, str );
//printf("Result=%s\n",result);
return result;
*/
}
char *ncleanalloc(char*str, int length)
{
if (str == NULL)
return str;
char *result;
str = cleanwhitespace(str);
if (length > 0 ) {
result = (char*)malloc(length);
strlcpy ( result, str, length );
} else {
result = (char*)malloc(strlen(str)+1);
strcpy ( result, str );
}
//printf("Result=%s\n",result);
return result;
}
/*
char *cleanallocindex(char*str, int index)
{
char *result;
int i;
int found = 1;
int loc1=0;
int loc2=strlen(str);
for(i=0;iif_index != 0 || intf->if_name != NULL; intf++)
{
strcpy(s.ifr_name, intf->if_name);
if (0 == ioctl(fd, SIOCGIFHWADDR, &s))
{
if ( s.ifr_addr.sa_data[0] == 0 &&
s.ifr_addr.sa_data[1] == 0 &&
s.ifr_addr.sa_data[2] == 0 &&
s.ifr_addr.sa_data[3] == 0 &&
s.ifr_addr.sa_data[4] == 0 &&
s.ifr_addr.sa_data[5] == 0 ) {
continue;
}
if ((ioctl(fd, SIOCGIFFLAGS, &s) < 0) || !(s.ifr_flags & IFF_RUNNING))
{
continue;
}
int i;
int step=2;
if (useDelimiter) {step=3;}
for (i = 0; i < 6 && i * step < len; ++i)
{
sprintf(&buf[i * step], "%02x", (unsigned char)s.ifr_addr.sa_data[i]);
if (useDelimiter && i<5) {
buf[i * step + 2] = ':';
}
}
return true;
}
}
}
return false;
}
char *generate_mqtt_id(char *buf, int len) {
extern char *__progname; // glibc populates this
int i;
strncpy(buf, basename(__progname), len);
i = strlen(buf);
if ( i > 9) { i=9; } // cut down to 9 characters (aqualinkd)
if (i < len) {
buf[i++] = '_';
// If we can't get MAC to pad mqtt id then use PID
if (!mac(&buf[i], len - i, false)) {
sprintf(&buf[i], "%.*d", (len-i), getpid());
}
}
buf[len] = '\0';
return buf;
}
bool setConfigValue(struct aqualinkdata *aqdata, char *param, char *value) {
bool rtn = false;
char *tmpval;
//#ifdef CONFIG_DEV_TEST
//int val;
//char *sval;
for (int i=0; i <= _numCfgParams; i++) {
//printf ("** Test against '%s' %d\n",_cfgParams[i].name, (int)strlen(_cfgParams[i].name));
if (strncasecmp(param, _cfgParams[i].name, (int)strlen(_cfgParams[i].name) ) == 0) {
//printf ("MATCH = %s\n",param);
rtn=true;
// Any special
if ( _cfgParams[i].valid_values != NULL ) {
//printf("Checking %s in %s\n",value,_cfgParams[i].valid_values);
if ( rsm_strstr(_cfgParams[i].valid_values, cleanwhitespace(value)) == NULL) {
LOG(AQUA_LOG,LOG_ERR, "Config entry '%s', %s is not valid\n",param, value);
//rtn=false;
return false;
}
}
if (strlen(cleanwhitespace(value)) <= 0) {
LOG(AQUA_LOG,LOG_NOTICE,"Set configuration option `%s` to default since value is blank\n",_cfgParams[i].name );
set_cfg_parm_to_default(&_cfgParams[i]);
return true;
}
if (isMASK_SET(_cfgParams[i].config_mask, CFG_PASSWD_MASK)) {
if (strncmp(value, PASSWD_MASK_TEXT, strlen(PASSWD_MASK_TEXT)) == 0) {
// Don't set password when it's the mask text
return false;
}
}
switch (_cfgParams[i].value_type) {
case CFG_STRING:
if (_cfgParams[i].value_ptr != NULL && *(char **)_cfgParams[i].value_ptr != _cfgParams[i].default_value) {
LOG(AQUA_LOG,LOG_DEBUG,"FREE Memory for config %s %s\n",_cfgParams[i].name, *(char **)_cfgParams[i].value_ptr);
free(*(char **)_cfgParams[i].value_ptr);
*(char **)_cfgParams[i].value_ptr = NULL;
}
*(char **)_cfgParams[i].value_ptr = cleanalloc(value);
break;
case CFG_INT:
*(int *)_cfgParams[i].value_ptr = strtoul(cleanwhitespace(value), NULL, 10);
break;
case CFG_BOOL:
*(bool *)_cfgParams[i].value_ptr = text2bool(value);
break;
case CFG_HEX:
*(unsigned char *)_cfgParams[i].value_ptr = strtoul(cleanwhitespace(value), NULL, 16);
break;
case CFG_FLOAT:
tmpval = cleanalloc(value);
*(float *)_cfgParams[i].value_ptr = atof(tmpval);
free(tmpval);
break;
case CFG_BITMASK:
if (text2bool(value))
*(uint8_t *)_cfgParams[i].value_ptr |= _cfgParams[i].mask;
else
*(uint8_t *)_cfgParams[i].value_ptr &= ~_cfgParams[i].mask;
break;
case CFG_SPECIAL:
if (strncasecmp(param, CFG_N_log_level, strlen(CFG_N_log_level)) == 0) {
*(int *)_cfgParams[i].value_ptr = text2elevel(cleanwhitespace(value));
} else if (strncasecmp(param, CFG_N_panel_type, strlen(CFG_N_panel_type)) == 0) {
setPanelByName(aqdata, cleanwhitespace(value));
} else {
LOG(AQUA_LOG,LOG_ERR, "ADD SPECIAL CONFIG FOR '%s'\n",param);
}
break;
}
return rtn;
}
}
//_cfgParams[_numCfgParams].value_ptr = _aqconfig_.testChar;
//_cfgParams[_numCfgParams].value_type = CFG_STRING;
//_cfgParams[_numCfgParams].name = "testChar";
//LOG(AQUA_LOG,LOG_ERR, "Missing cfg for %s\n",param);
//#endif
if (strlen(cleanwhitespace(value)) <= 0) {
LOG(AQUA_LOG,LOG_WARNING,"Configuration value is blank for option `%s`, Ignoring\n",param );
return true;
}
// Values we don't want in general config.
if (strncasecmp(param, "debug_log_mask", 14) == 0) {
addDebugLogMask(strtoul(value, NULL, 10));
rtn=true;
} else if (strncasecmp(param, CFG_N_RSSD_LOG_filter, CFG_C_RSSD_LOG_filter) == 0) {
for (int i=0; i < MAX_RSSD_LOG_FILTERS; i++) {
if (_aqconfig_.RSSD_LOG_filter[i] == NUL) {
_aqconfig_.RSSD_LOG_filter[i] = strtoul(cleanalloc(value), NULL, 16);
break;
}
}
rtn=true;
} else if (strncasecmp (param, "debug_RSProtocol_bytes", 22) == 0) {
_aqconfig_.log_raw_bytes = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "debug_RSProtocol_packets", 24) == 0) {
_aqconfig_.log_protocol_packets = text2bool(value);
rtn=true;
// Build panel without string
} else if (strncasecmp(param, "panel_type_size", 15) == 0) {
_defaultPanel.size = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "panel_type_combo", 16) == 0) {
_defaultPanel.combo = text2bool(value);
rtn=true;
} else if (strncasecmp(param, "panel_type_dual", 15) == 0) {
_defaultPanel.dual = text2bool(value);
rtn=true;
} else if (strncasecmp(param, "panel_type_pda", 14) == 0) {
_defaultPanel.rs = !text2bool(value);
rtn=true;
} else if (strncasecmp(param, "panel_type_rs", 13) == 0) {
_defaultPanel.rs = text2bool(value);
rtn=true;
/*
} else if (strncasecmp(param, CFG_N_panel_type, CFG_C_panel_type) == 0) { // This must be last so it doesn't get picked up by other settings
setPanelByName(aqdata, cleanwhitespace(value));
rtn=true;
*/
// Old names that we will map.
} else if ((strncasecmp(param, CFG_N_mqtt_hass_discover_topic, strlen(CFG_N_mqtt_hass_discover_topic)) == 0) ||
(strncasecmp(param, "mqtt_hassio_discover_topic", 26) == 0) ||
(strncasecmp(param, "mqtt_hass_discover_topic", 24) == 0)) {
_aqconfig_.mqtt_hass_discover_topic = cleanalloc(value);
rtn=true;
} else if ((strncasecmp(param, CFG_N_mqtt_hass_discover_use_mac, strlen(CFG_N_mqtt_hass_discover_use_mac)) == 0) ||
(strncasecmp(param, "mqtt_hassio_discover_use_mac", 28) == 0) ||
(strncasecmp(param, "mqtt_hass_discover_use_mac", 26) == 0)) {
_aqconfig_.mqtt_hass_discover_use_mac = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "keep_paneltime_synced", 21) == 0) {
_aqconfig_.sync_panel_time = text2bool(value);
rtn=true;
} else if (strncasecmp(param, "air_temp_dzidx", 14) == 0) {
_aqconfig_.dzidx_air_temp = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "pool_water_temp_dzidx", 21) == 0) {
_aqconfig_.dzidx_pool_water_temp = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "spa_water_temp_dzidx", 20) == 0) {
_aqconfig_.dzidx_spa_water_temp = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "SWG_percent_dzidx", 17) == 0) {
_aqconfig_.dzidx_swg_percent = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "SWG_PPM_dzidx", 13) == 0) {
_aqconfig_.dzidx_swg_ppm = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param,"SWG_Status_dzidx", 16) == 0) {
_aqconfig_.dzidx_swg_status = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "convert_mqtt_temp_to_c", 22) == 0) {
_aqconfig_.convert_mqtt_temp = text2bool(value);
rtn=true;
} else if (strncasecmp(param, "convert_dz_temp_to_c", 20) == 0) {
_aqconfig_.convert_dz_temp = text2bool(value);
rtn=true;
} else if (strncasecmp (param, "scheduler_check_poweron", 23) == 0) {
if (text2bool(value)) {
_aqconfig_.schedule_event_mask |= AQS_POWER_ON;
}
rtn=true;
} else if (strncasecmp (param, "scheduler_check_freezeprotectoff", 32) == 0) {
if (text2bool(value)) {
_aqconfig_.schedule_event_mask |= AQS_FRZ_PROTECT_OFF;
}
rtn=true;
} else if (strncasecmp (param, "scheduler_check_boostoff", 24) == 0) {
if (text2bool(value)) {
_aqconfig_.schedule_event_mask |= AQS_BOOST_OFF;
}
rtn=true;
} else if (strncasecmp (param, "scheduler_check_pumpon_hour", 27) == 0) {
_aqconfig_.sched_chk_pumpon_hour = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp (param, "scheduler_check_pumpoff_hour", 28) == 0) {
_aqconfig_.sched_chk_pumpoff_hour = strtoul(value, NULL, 10);
rtn=true;
} else if (strncasecmp(param, "light_program_", 14) == 0) {
int num = strtoul(param + 14, NULL, 10);
if ( num >= LIGHT_COLOR_OPTIONS ) {
LOG(AQUA_LOG,LOG_ERR, "Config error, light_program_%d is out of range\n",num);
}
char *name = cleanalloc(value);
int len = strlen(name);
if (len > 0) {
if ( strncmp(name+len-7, " - Show", 7) == 0 ) {
name[len-7] = '\0';
//printf("Value '%s' index %d is show\n",name,num);
set_aqualinkd_light_mode_name(name,num,true);
} else {
set_aqualinkd_light_mode_name(name,num,false);
}
rtn=true;
} else {
LOG(AQUA_LOG,LOG_WARNING, "Config error, light_program_%d is blank\n",num);
rtn=false;
}
} else if (strncasecmp(param, "button_", 7) == 0) {
// Check we have inichalized panel information, if not use any settings we may have
if (_aqconfig_.paneltype_mask == 0)
setPanel(aqdata, _defaultPanel.rs, _defaultPanel.size, _defaultPanel.combo, _defaultPanel.dual);
int num = strtoul(param + 7, NULL, 10) - 1;
if (num > TOTAL_BUTTONS) {
LOG(AQUA_LOG,LOG_ERR, "Config error, button_%d is out of range\n",num+1);
rtn=false;
} else if (strncasecmp(param + 9, "_label", 6) == 0) {
aqdata->aqbuttons[num].label = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param + 9, "_dzidx", 6) == 0) {
aqdata->aqbuttons[num].dz_idx = strtoul(value, NULL, 10);
rtn=true;
#ifdef AQ_PDA
} else if (strncasecmp(param + 9, "_PDA_label", 10) == 0) {
LOG(AQUA_LOG,LOG_WARNING, "Config error, 'button_%d_PDA_label' is no longer supported, please use 'button_%d_label'\n",num,num);
//aqdata->aqbuttons[num].pda_label = cleanalloc(value);
aqdata->aqbuttons[num].label = cleanalloc(value);
rtn=true;
#endif
} else if (strncasecmp(param + 9, "_lightMode", 10) == 0) {
int type = strtoul(value, NULL, 10);
// See if light is existing button / light
if (isPLIGHT(aqdata->aqbuttons[num].special_mask)) {
((clight_detail *)aqdata->aqbuttons[num].special_mask_ptr)->lightType = type;
}
else if (aqdata->num_lights < MAX_LIGHTS)
{
if (type < LC_PROGRAMABLE || type > NUMBER_LIGHT_COLOR_TYPES) {
LOG(AQUA_LOG,LOG_ERR, "Config error, unknown light mode '%s'\n",type);
} else {
aqdata->lights[aqdata->num_lights].button = &aqdata->aqbuttons[num];
aqdata->lights[aqdata->num_lights].lightType = type;
aqdata->aqbuttons[num].special_mask |= PROGRAM_LIGHT;
aqdata->aqbuttons[num].special_mask_ptr = &aqdata->lights[aqdata->num_lights];
if ( aqdata->lights[aqdata->num_lights].lightType == LC_DIMMER2 && _aqconfig_.rssa_device_id != 0x48 ) {
LOG(AQUA_LOG,LOG_ERR, "Config error, button '%s' has light mode '%d' set. This only supported when 'rssa_device_id' is enabled, changing to light mode '%d'\n",
aqdata->aqbuttons[num].label, LC_DIMMER2,LC_DIMMER);
aqdata->lights[aqdata->num_lights].lightType = LC_DIMMER;
}
aqdata->num_lights++;
}
} else {
LOG(AQUA_LOG,LOG_ERR, "Config error, (colored|programmable) Lights limited to %d, ignoring %s'\n",MAX_LIGHTS,param);
}
rtn=true;
} else if (strncasecmp(param + 9, "_pump", 5) == 0) {
if ( ! populatePumpData(aqdata, param + 10, &aqdata->aqbuttons[num], value) )
{
LOG(AQUA_LOG,LOG_ERR, "Config error, VSP Pumps limited to %d, ignoring : %s",MAX_PUMPS,param);
}
rtn=true;
}
//#if defined AQ_IAQTOUCH
} else if (strncasecmp(param, "virtual_button_", 15) == 0) {
rtn=true;
int num = strtoul(param + 15, NULL, 10);
if (_aqconfig_.paneltype_mask == 0) {
// ERROR the vbutton will be irnored.
LOG(AQUA_LOG,LOG_WARNING, "Config error, Panel type must be definied before adding a virtual_button, ignored setting : %s",param);
//} else if (_aqconfig_.extended_device_id < 0x30 || _aqconfig_.extended_device_id > 0x33 ) {
// LOG(AQUA_LOG,LOG_WARNING, "Config error, extended_device_id must on of the folowing (0x30,0x31,0x32,0x33), ignored setting : %s",param);
} else if (strncasecmp(param + 17, "_label", 6) == 0) {
char *label = cleanalloc(value);
aqkey *button = getVirtualButton(aqdata, num);
if (button != NULL) {
setVirtualButtonLabel(button, label);
} else {
LOG(AQUA_LOG,LOG_WARNING, "Error with '%s', total buttons=%d, config has %d already, ignoring!\n",param, TOTAL_BUTTONS, aqdata->total_buttons+1);
}
} else if (strncasecmp(param + 17, "_altLabel", 9) == 0) {
char *label = cleanalloc(value);
aqkey *button = getVirtualButton(aqdata, num);
if (button != NULL) {
setVirtualButtonAltLabel(button, label);
} else {
LOG(AQUA_LOG,LOG_WARNING, "Error with '%s', total buttons=%d, config has %d already, ignoring!\n",param, TOTAL_BUTTONS, aqdata->total_buttons+1);
}
} else if (strncasecmp(param + 17, "_pump", 5) == 0) {
aqkey *vbutton = getVirtualButton(aqdata, num);
if (vbutton != NULL) {
vbutton->led->state = ON; //Virtual pump default to on
if ( ! populatePumpData(aqdata, param + 18, vbutton, value) )
{
LOG(AQUA_LOG,LOG_ERR, "Config error, VSP Pumps limited to %d, ignoring : %s",MAX_PUMPS,param);
}
} else {
LOG(AQUA_LOG,LOG_ERR, "Config error, could not find vitrual button for `%s`",param);
}
} else if (strncasecmp(param + 17, "_onetouchID", 11) == 0) {
aqkey *vbutton = getVirtualButton(aqdata, num);
if (vbutton != NULL) {
switch (strtoul(value, NULL, 10)) {
case 1:
vbutton->code = IAQ_ONETOUCH_1;
vbutton->rssd_code = IAQ_ONETOUCH_1;
break;
case 2:
vbutton->code = IAQ_ONETOUCH_2;
vbutton->rssd_code = IAQ_ONETOUCH_2;
break;
case 3:
vbutton->code = IAQ_ONETOUCH_3;
vbutton->rssd_code = IAQ_ONETOUCH_3;
break;
case 4:
vbutton->code = IAQ_ONETOUCH_4;
vbutton->rssd_code = IAQ_ONETOUCH_4;
break;
case 5:
vbutton->code = IAQ_ONETOUCH_5;
vbutton->rssd_code = IAQ_ONETOUCH_5;
break;
case 6:
vbutton->code = IAQ_ONETOUCH_6;
vbutton->rssd_code = IAQ_ONETOUCH_6;
break;
default:
vbutton->code = NUL;
vbutton->rssd_code = NUL;
break;
}
} else {
LOG(AQUA_LOG,LOG_ERR, "Config error, could not find vitrual button for `%s`",param);
}
}
} else if (strncasecmp(param, "sensor_", 7) == 0) {
int num = strtoul(param + 7, NULL, 10) - 1;
if (num + 1 > MAX_SENSORS || num < 0) {
LOG(AQUA_LOG,LOG_ERR, "Config error, Maximum of %d sensors allowd `%s` ignored!",MAX_SENSORS,param);
} else if (strlen(cleanwhitespace(value)) > 0) {
if ( num + 1 > aqdata->num_sensors ) {
aqdata->num_sensors = num + 1;
}
if (strncasecmp(param + 9, "_label", 6) == 0) {
aqdata->sensors[num].label = ncleanalloc(value, AQ_MSGLEN);
rtn=true;
} else if (strncasecmp(param + 9, "_path", 5) == 0) {
aqdata->sensors[num].path = cleanalloc(value);
rtn=true;
} else if (strncasecmp(param + 9, "_factor", 7) == 0) {
aqdata->sensors[num].factor = atof(value);
//printf("Factor = %f - %s\n",aqdata->sensors[num].factor, value);
if (aqdata->sensors[num].factor == 0) {
LOG(AQUA_LOG,LOG_ERR, "Config error, couldn't understand `%s` from `%s`, using `1.0`!",value,param);
aqdata->sensors[num].factor = 1;
}
}
rtn=true;
} else {
LOG(AQUA_LOG,LOG_ERR, "Config error, blank value for `%s`\n",param);
rtn = false;
}
}
//#endif
return rtn;
}
aqkey *getVirtualButton(struct aqualinkdata *aqdata, int num)
{
aqkey *vbutton = NULL;
//char vbname[10];
//snprintf(vbname, 9, "%s%d", BTN_VAUX, num);
if (aqdata->virtual_button_start <= 0) {
return addVirtualButton(aqdata, NULL, num);
}
// num should be the index of vbutton (which starts at max buttons).
// num should also match the button NAME as in "Aux_V4"
char vbname[10];
snprintf(vbname, 9, "%s%d", BTN_VAUX, num);
for (int i = aqdata->virtual_button_start; i < aqdata->total_buttons; i++)
{
// printf("Checking %s agasinsdt %s\n",aqdata->aqbuttons[i].name, vbname);
if (strcmp(aqdata->aqbuttons[i].name, vbname) == 0)
{
vbutton = &aqdata->aqbuttons[i];
break;
}
}
if (vbutton == NULL) {
return addVirtualButton(aqdata, NULL, num);
}
return vbutton;
}
// pumpcfg is pointer to pumpIndex, pumpName, pumpType pumpID, (ie pull off button_??_ or vurtual_button_??_)
bool populatePumpData(struct aqualinkdata *aqdata, char *pumpcfg ,aqkey *button, char *value)
{
pump_detail *pump = getPumpFromButtonID(aqdata, button);
if (pump == NULL) {
return false;
}
if (strncasecmp(pumpcfg, "pumpIndex", 9) == 0) {
pump->pumpIndex = strtoul(value, NULL, 10);
} else if (strncasecmp(pumpcfg, "pumpType", 8) == 0) {
if ( stristr(value, "Pentair VS") != 0)
pump->pumpType = VSPUMP;
else if ( stristr(value, "Pentair VF") != 0)
pump->pumpType = VFPUMP;
else if ( stristr(value, "Jandy ePump") != 0)
pump->pumpType = EPUMP;
} else if (strncasecmp(pumpcfg, "pumpName", 8) == 0) {
strncpy(pump->pumpName ,cleanwhitespace(value), PUMP_NAME_LENGTH-1);
} else if (strncasecmp(pumpcfg, "pumpID", 6) == 0) {
pump->pumpID = strtoul(cleanalloc(value), NULL, 16);
if ( (int)pump->pumpID >= PENTAIR_DEC_PUMP_MIN && (int)pump->pumpID <= PENTAIR_DEC_PUMP_MAX) {
pump->prclType = PENTAIR;
} else {
pump->prclType = JANDY;
}
} else if (strncasecmp(pumpcfg, "pumpMaxSpeed", 12) == 0) {
pump->maxSpeed = strtoul(value, NULL, 10);
} else if (strncasecmp(pumpcfg, "pumpMinSpeed", 12) == 0) {
pump->minSpeed = strtoul(value, NULL, 10);
}
return true;
}
pump_detail *getPumpFromButtonID(struct aqualinkdata *aqdata, aqkey *button)
{
int pi;
// Does it exist
for (pi=0; pi < aqdata->num_pumps; pi++) {
if (aqdata->pumps[pi].button == button) {
return &aqdata->pumps[pi];
}
}
// Create new entry
if (aqdata->num_pumps < MAX_PUMPS) {
//printf ("Creating pump %d\n",button);
button->special_mask |= VS_PUMP;
button->special_mask_ptr = (void*)&aqdata->pumps[aqdata->num_pumps];
aqdata->pumps[aqdata->num_pumps].button = button;
aqdata->pumps[aqdata->num_pumps].pumpType = PT_UNKNOWN;
aqdata->pumps[aqdata->num_pumps].rpm = TEMP_UNKNOWN;
aqdata->pumps[aqdata->num_pumps].watts = TEMP_UNKNOWN;
aqdata->pumps[aqdata->num_pumps].gpm = TEMP_UNKNOWN;
aqdata->pumps[aqdata->num_pumps].pStatus = PS_OFF;
aqdata->pumps[aqdata->num_pumps].pumpIndex = 0;
aqdata->pumps[aqdata->num_pumps].maxSpeed = TEMP_UNKNOWN;
aqdata->pumps[aqdata->num_pumps].minSpeed = TEMP_UNKNOWN;
//pumpType
aqdata->pumps[aqdata->num_pumps].pumpName[0] = '\0';
aqdata->num_pumps++;
return &aqdata->pumps[aqdata->num_pumps-1];
}
return NULL;
}
/*
pump_detail *getpump(struct aqualinkdata *aqdata, int button)
{
//static int _pumpindex = 0;
//aqdata->num_pumps
int pi;
// Does it exist
for (pi=0; pi < aqdata->num_pumps; pi++) {
if (aqdata->pumps[pi].button == &aqdata->aqbuttons[button]) {
//printf ("Found pump %d\n",button);
return &aqdata->pumps[pi];
}
}
// Create new entry
if (aqdata->num_pumps < MAX_PUMPS) {
//printf ("Creating pump %d\n",button);
aqdata->aqbuttons[button].special_mask |= VS_PUMP;
aqdata->pumps[aqdata->num_pumps].button = &aqdata->aqbuttons[button];
aqdata->pumps[aqdata->num_pumps].pumpType = PT_UNKNOWN;
aqdata->pumps[aqdata->num_pumps].rpm = TEMP_UNKNOWN;
aqdata->pumps[aqdata->num_pumps].watts = TEMP_UNKNOWN;
aqdata->pumps[aqdata->num_pumps].gpm = TEMP_UNKNOWN;
aqdata->pumps[aqdata->num_pumps].pStatus = PS_OFF;
aqdata->pumps[aqdata->num_pumps].pumpIndex = 0;
//pumpType
aqdata->pumps[aqdata->num_pumps].pumpName[0] = '\0';
aqdata->num_pumps++;
return &aqdata->pumps[aqdata->num_pumps-1];
}
return NULL;
}
*/
void init_config()
{
init_parameters(&_aqconfig_);
}
//void readCfg (struct aqconfig *config_parameters, struct aqualinkdata *aqdata, char *cfgFile)
void read_config (struct aqualinkdata *aqdata, char *cfgFile)
{
_aqdata = aqdata;
FILE * fp ;
char bufr[MAXCFGLINE];
//const char delim[2] = ";";
//char *buf;
//int line = 0;
//int tokenindex = 0;
char *b_ptr;
_aqconfig_.config_file = cleanalloc(cfgFile);
if( (fp = fopen(cfgFile, "r")) != NULL){
while(! feof(fp)){
if (fgets(bufr, MAXCFGLINE, fp) != NULL)
{
b_ptr = &bufr[0];
char *indx;
// Eat leading whitespace
while(isspace(*b_ptr)) b_ptr++;
if ( b_ptr[0] != '\0' && b_ptr[0] != '#')
{
indx = strchr(b_ptr, '=');
if ( indx != NULL)
{
if ( ! setConfigValue(aqdata, b_ptr, indx+1)) {
//LOG(AQUA_LOG,LOG_ERR, "Unknown config parameter '%.*s'\n",strlen(b_ptr), b_ptr);
char *end = b_ptr + strlen(b_ptr) - 1;
while(end > b_ptr && isspace(*end)) end--;
LOG(AQUA_LOG,LOG_ERR, "Unknown config parameter '%.*s'\n",end-b_ptr+1, b_ptr);
}
}
}
}
}
fclose(fp);
} else {
/* error processing, couldn't open file */
LOG(AQUA_LOG,LOG_ERR, "Error reading config file '%s'\n",cfgFile);
errno = EBADF;
displayLastSystemError("Error reading config file");
exit (EXIT_FAILURE);
}
//free(_defaultPanel);
}
//DEBUG_DERIAL, DEBUG, INFO, NOTICE, WARNING, ERROR
char *errorlevel2text(int level)
{
switch(level) {
case LOG_DEBUG_SERIAL:
return "DEBUG_SERIAL";
break;
case LOG_DEBUG:
return "DEBUG";
break;
case LOG_INFO:
return "INFO";
break;
case LOG_NOTICE:
return "NOTICE";
break;
case LOG_WARNING:
return "WARNING";
break;
case LOG_ERR:
default:
return "ERROR";
break;
}
return "";
}
//#ifdef CONFIG_EDITOR
#define MAX_PRINTLEN 35
void check_print_config (struct aqualinkdata *aqdata)
{
int i, j;
char name[MAX_PRINTLEN];
// Sanity checks
// If no panel has been set, use default one
if (_aqconfig_.paneltype_mask == 0) {
//printf("Set temp panel info Size=%d, RS=%d, combo=%d, dual=%d\n",_defaultPanel.size,_defaultPanel.rs,_defaultPanel.combo, _defaultPanel.dual);
LOG(AQUA_LOG,LOG_ERR, "No panel set in config, using default values\n");
setPanel(aqdata, _defaultPanel.rs, _defaultPanel.size, _defaultPanel.combo, _defaultPanel.dual);
}
// Make sure all sensors are fully populated
for (i=0; i < aqdata->num_sensors; i++ ) {
if ( aqdata->sensors[i].label == NULL || aqdata->sensors[i].path == NULL ) {
LOG(AQUA_LOG,LOG_ERR, "Invalid sensor %d, removing!\n",i+1);
if (i == (aqdata->num_sensors-1) ) { // last sensor
// don't need to do anything, just reduce total number sensors
} else if (aqdata->num_sensors > 1) { // there are more sensors adter this bad one
for (int j=i; j < aqdata->num_sensors; j++) {
aqdata->sensors[j].label = aqdata->sensors[j+1].label;
aqdata->sensors[j].path = aqdata->sensors[j+1].path;
aqdata->sensors[j].factor = aqdata->sensors[j+1].factor;
}
}
aqdata->num_sensors --;
}
}
// Check chiller
if (ENABLE_CHILLER) {
if (_aqconfig_.extended_device_id >= 0x30 && _aqconfig_.extended_device_id <= 0x33) {
for (i = 0; i < aqdata->total_buttons; i++)
{
if (isVBUTTON_ALTLABEL(aqdata->aqbuttons[i].special_mask) && (rsm_strmatch(((vbutton_detail *)aqdata->aqbuttons[i].special_mask_ptr)->altlabel, "Chiller") == 0) ){
aqdata->chiller_button = &aqdata->aqbuttons[i];
//aqdata->chiller_button->special_mask |= VIRTUAL_BUTTON_CHILLER;
setMASK(aqdata->chiller_button->special_mask, VIRTUAL_BUTTON_CHILLER);
} else if (isVBUTTON(aqdata->aqbuttons[i].special_mask) && rsm_strmatch(aqdata->aqbuttons[i].label, "Heat Pump") == 0 ) {
LOG(AQUA_LOG,LOG_ERR, "Config error, `%s` is enabled, but Virtual Button Heat Pump does not have alt_name Chiller! Creating.",CFG_N_force_chiller);
setVirtualButtonAltLabel(&aqdata->aqbuttons[i], cleanalloc("Chiller")); // Need to malloc this so it can be freed
aqdata->chiller_button = &aqdata->aqbuttons[i];
//aqdata->chiller_button->special_mask |= VIRTUAL_BUTTON_CHILLER;
setMASK(aqdata->chiller_button->special_mask, VIRTUAL_BUTTON_CHILLER);
}
}
if (aqdata->chiller_button == NULL) {
LOG(AQUA_LOG,LOG_ERR, "Config error, `%s` is enabled, but no Virtual Button set for Heat Pump / Chiller! Creating vbutton.",CFG_N_force_chiller);
aqkey *button = getVirtualButton(aqdata, 0);
setVirtualButtonLabel(button, cleanalloc("Heat Pump"));// Need to malloc this so it can be freed
setVirtualButtonAltLabel(button, cleanalloc("Chiller"));// Need to malloc this so it can be freed
aqdata->chiller_button = button;
//aqdata->chiller_button->special_mask |= VIRTUAL_BUTTON_CHILLER;
setMASK(aqdata->chiller_button->special_mask, VIRTUAL_BUTTON_CHILLER);
}
} else {
LOG(AQUA_LOG,LOG_ERR, "Config error, `%s` can only be enabled, if using an iAqualink Touch ID for `%s`, Turning off\n",CFG_N_force_chiller, CFG_N_extended_device_id );
removeMASK(_aqconfig_.force_device_devmask,FORCE_CHILLER);
aqdata->chiller_button = NULL;
}
} else {
aqdata->chiller_button = NULL;
}
// Turn off extended programming if we don't have device
if ( _aqconfig_.extended_device_id == 0x00 )
{
_aqconfig_.extended_device_id_programming = false;
}
/*
_cfgParams[_numCfgParams].mask = READ_RS485_IAQUALNK;
if ( bitmask READ_RS485_IAQUALNK && _aqconfig_.enable_iaqualink ) error and use (_aqconfig_.enable_iaqualink, disable bitmask
*/
if (_aqconfig_.enable_iaqualink==true && (_aqconfig_.extended_device_id < 0x30 || _aqconfig_.extended_device_id > 0x33) )
{
LOG(AQUA_LOG,LOG_WARNING, "Config error, 'enable_iaqualink', is only valed with AqualinkTouch ID's, ignoring!\n");
_aqconfig_.enable_iaqualink = true;
}
// Can't read iaqualink if we are also using iaqualink protocol.
if (isMASK_SET(_aqconfig_.enable_iaqualink, READ_RS485_IAQUALNK) && _aqconfig_.enable_iaqualink == true )
{
LOG(AQUA_LOG,LOG_WARNING, "Config error, 'read_RS485_iAqualink' is not valid when 'enable_iaqualink=yes', ignoring read_RS485_iAqualink!\n");
_aqconfig_.read_RS485_devmask &= ~READ_RS485_IAQUALNK;
}
// Find the boost linked device if one is set
if (_aqconfig_.sched_chk_booston_device != NULL) {
//printf("checking boost linked device\n");
for (i = 0; i < aqdata->total_buttons; i++)
{
if (rsm_strncmp(_aqconfig_.sched_chk_booston_device, aqdata->aqbuttons[i].label, strlen(aqdata->aqbuttons[i].label)) == 0) {
aqdata->boost_linked_device = i;
setMASK(_aqconfig_.schedule_event_mask, AQS_BOOST_ON);
break;
}
}
LOG(AQUA_LOG,LOG_WARNING, "Config error, couldn't find button `%s` from config option `%s`\n",_aqconfig_.sched_chk_booston_device,CFG_N_event_check_booston_device);
} else {
aqdata->boost_linked_device = AQ_UNKNOWN;
}
/*
PDA sleep and PDA ID.
*/
/* Need to handle this case
} else if (strncasecmp (param, CFG_N_scheduler_check_pumpon_hour, CFG_C_scheduler_check_pumpon_hour) == 0) {
_aqconfig_.sched_chk_pumpon_hour = strtoul(value, NULL, 10);
_aqconfig_.schedule_event_mask |= AQS_DONT_USE_CRON_PUMP_TIME;
rtn=true;
} else if (strncasecmp (param, CFG_N_scheduler_check_pumpoff_hour, CFG_C_scheduler_check_pumpoff_hour) == 0) {
_aqconfig_.sched_chk_pumpoff_hour = strtoul(value, NULL, 10);
_aqconfig_.schedule_event_mask |= AQS_DONT_USE_CRON_PUMP_TIME;
rtn=true;
// maybebelow. But this would only work the first time the config it read. (might need to clear CFG???)
if
_aqconfig_.sched_chk_pumpon_hour != TEMP_UNKNOWN ||
_aqconfig_.sched_chk_pumpoff_hour != TEMP_UNKNOWN
then
_aqconfig_.schedule_event_mask |= AQS_DONT_USE_CRON_PUMP_TIME;
*/
for ( i=0; i <= _numCfgParams; i++) {
rsm_nchar_replace(name, MAX_PRINTLEN, _cfgParams[i].name, "_", " ");
switch (_cfgParams[i].value_type) {
case CFG_STRING:
if (*(char **)_cfgParams[i].value_ptr == NULL)
LOG(AQUA_LOG,LOG_NOTICE, "%-35s =\n", name);
else {
if (isMASK_SET(_cfgParams[i].config_mask ,CFG_PASSWD_MASK) )
LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %s\n",name, PASSWD_MASK_TEXT);
else
LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %s\n",name, *(char **)_cfgParams[i].value_ptr);
}
break;
case CFG_INT:
if (*(int *)_cfgParams[i].value_ptr == TEMP_UNKNOWN)
LOG(AQUA_LOG,LOG_NOTICE, "%-35s =\n", name);
else
LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %d\n", name, *(int *)_cfgParams[i].value_ptr);
break;
case CFG_BOOL:
LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %s\n", name, bool2text(*(bool *)_cfgParams[i].value_ptr));
break;
case CFG_HEX:
LOG(AQUA_LOG,LOG_NOTICE, "%-35s = 0x%02hhx\n", name, *(unsigned char *)_cfgParams[i].value_ptr);
break;
case CFG_FLOAT:
LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %f\n", name, *(float *)_cfgParams[i].value_ptr);
break;
case CFG_BITMASK:
LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %s\n", name, (*(uint8_t *)_cfgParams[i].value_ptr & _cfgParams[i].mask) == _cfgParams[i].mask?bool2text(true):bool2text(false));
break;
case CFG_SPECIAL:
if (strncasecmp(_cfgParams[i].name, CFG_N_log_level, strlen(CFG_N_log_level)) == 0) {
LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %s\n", name, loglevel2cgn_name(_aqconfig_.log_level));
} else if (strncasecmp(_cfgParams[i].name, CFG_N_panel_type, strlen(CFG_N_panel_type)) == 0) {
LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %s\n", name, getPanelString());
} else {
LOG(AQUA_LOG,LOG_NOTICE, "%-35s = NEED TO ADD CODE TO HANDLE THIS\n",name);
}
break;
}
}
if (isAQS_START_PUMP_EVENT_ENABLED) {
if (isAQS_USE_CRON_PUMP_TIME_ENABLED) {
if (_aqconfig_.enable_scheduler) {
get_cron_pump_times();
} else {
LOG(AQUA_LOG,LOG_ERR,"Scheduler is disabled, but use pump times from scheduler is enabled\n");
}
}
//LOG(AQUA_LOG,LOG_NOTICE, "Start Pump on events = %s %s %s\n",isAQS_POWER_ON_ENABED?"PowerON":"",AQS_FRZ_PROTECT_OFF?"FreezeProtect":"",AQS_BOOST_OFF?"Boost":"");
LOG(AQUA_LOG,LOG_NOTICE, "%-35s = %d:00 and %d:00\n","Start Pump between times",_aqconfig_.sched_chk_pumpon_hour,_aqconfig_.sched_chk_pumpoff_hour);
} /*else {
LOG(AQUA_LOG,LOG_NOTICE, "Start Pump on events = %s\n", bool2text(false));
}*/
//for (i = 0; i < TOTAL_BUTONS; i++)
for (i = 0; i < aqdata->total_buttons; i++)
{
//char ext[] = " VSP ID None | AL ID 0 ";
char ext[60];
ext[0] = '\0';
for (j = 0; j < aqdata->num_pumps; j++) {
if (aqdata->pumps[j].button == &aqdata->aqbuttons[i]) {
sprintf(ext, "VSP ID 0x%02hhx | PumpID %-1d | %s",
aqdata->pumps[j].pumpID,
aqdata->pumps[j].pumpIndex,
aqdata->pumps[j].pumpName[0]=='\0'?"":aqdata->pumps[j].pumpName);
}
}
for (j = 0; j < aqdata->num_lights; j++) {
if (aqdata->lights[j].button == &aqdata->aqbuttons[i]) {
sprintf(ext,"Light Progm | CTYPE %-1d |",aqdata->lights[j].lightType);
}
}
if (isVBUTTON(aqdata->aqbuttons[i].special_mask)) {
if (aqdata->aqbuttons[i].rssd_code != NUL) {
sprintf(ext,"OneTouch %d |",aqdata->aqbuttons[i].rssd_code - 15);
}
}
if (isVBUTTON_ALTLABEL(aqdata->aqbuttons[i].special_mask)) {
sprintf(ext,"%-12s|", ((vbutton_detail *)aqdata->aqbuttons[i].special_mask_ptr)->altlabel);
}
if (aqdata->aqbuttons[i].dz_idx > 0) {
sprintf(ext+strlen(ext), "dzidx %-3d",aqdata->aqbuttons[i].dz_idx);
}
LOG(AQUA_LOG,LOG_NOTICE, "Button %-13s = label %-15s | %s\n",
aqdata->aqbuttons[i].name, aqdata->aqbuttons[i].label, ext);
if ( ((aqdata->aqbuttons[i].special_mask & VIRTUAL_BUTTON) == VIRTUAL_BUTTON) &&
((aqdata->aqbuttons[i].special_mask & VS_PUMP ) != VS_PUMP) &&
(_aqconfig_.extended_device_id < 0x30 || _aqconfig_.extended_device_id > 0x33 ) ){
LOG(AQUA_LOG,LOG_WARNING, "Config error, extended_device_id must be on of the folowing (0x30,0x31,0x32,0x33) to use virtual button : '%s'",aqdata->aqbuttons[i].label);
}
}
for (i = 0; i < aqdata->num_sensors; i++)
{
LOG(AQUA_LOG,LOG_NOTICE, "Config Sensor %02d = label %-15s | %s\n", i+1, aqdata->sensors[i].label,aqdata->sensors[i].path);
}
}
/*
bool remount_root_ro(bool readonly) {
// NSF Check if config is RO_ROOT set
if (readonly) {} // Dummy to stop compile warnings.
if (readonly) {
LOG(AQUA_LOG,LOG_INFO, "reMounting root RO\n");
mount (NULL, "/", NULL, MS_REMOUNT | MS_RDONLY, NULL);
return true;
} else {
struct statvfs fsinfo;
statvfs("/", &fsinfo);
if ((fsinfo.f_flag & ST_RDONLY) == 0) // We are readwrite, ignore
return false;
LOG(AQUA_LOG,LOG_INFO, "reMounting root RW\n");
mount (NULL, "/", NULL, MS_REMOUNT, NULL);
return true;
}
return true;
}
*/
void writeCharValue (FILE *fp, char *msg, char *value)
{
if (value == NULL)
fprintf(fp, "#%s = \n",msg);
else
fprintf(fp, "%s = %s\n", msg, value);
}
void writeIntValue (FILE *fp, char *msg, int value)
{
if (value <= 0)
fprintf(fp, "#%s = \n",msg);
else
fprintf(fp, "%s = %d\n", msg, value);
}
int save_config_js(const char* inBuf, int inSize, char* outBuf, int outSize, struct aqualinkdata *aqdata)
{
//printf("\n%.*s\n",inSize,inBuf);
//const char *regexString=" *\"([^\",:]+) *\" *: *\"([^\",:]+)\" *,*";
const char *regexString=" *\"([^\",:]+) *\" *: *\"([^\",]*)\" *,*";
size_t maxGroups = 3;
size_t maxMatches = 200;
//size_t maxGroups = 3;
regmatch_t groupArray[maxGroups];
regex_t regexCompiled;
int rc;
char * cursor = (char *)inBuf;;
unsigned int m;
char key[64];
char value[64];
int psize = PANEL_SIZE(); // Get current panel size
int ignodeBtnLabelsGrater = TOTAL_BUTTONS+1;
bool ignorePair = false;
// First clear out all special current config items.
// Light Programs
clear_aqualinkd_light_modes();
// Lights, Buttons, Pumps, VirtButtons
for (int i = 0; i < aqdata->total_buttons; i++)
{
if (isVS_PUMP(aqdata->aqbuttons[i].special_mask)) {
((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->button = NULL;
} else if (isPLIGHT(aqdata->aqbuttons[i].special_mask)) {
((clight_detail *)aqdata->aqbuttons[i].special_mask_ptr)->button = NULL;
}
//printf("Freeing %d of %d - %s\n",i,aqdata->total_buttons,aqdata->aqbuttons[i].label);
free ( aqdata->aqbuttons[i].label);
aqdata->aqbuttons[i].special_mask = 0;
aqdata->aqbuttons[i].special_mask_ptr = NULL;
aqdata->aqbuttons[i].dz_idx = AQ_UNKNOWN;
aqdata->aqbuttons[i].code = NUL;
aqdata->aqbuttons[i].rssd_code = NUL;
}
aqdata->num_pumps = 0;
aqdata->num_lights = 0;
// Sensors
for (int i=0; i < aqdata->num_sensors; i++ ) {
free(aqdata->sensors[i].label);
free(aqdata->sensors[i].path);
aqdata->sensors[i].label = NULL;
aqdata->sensors[i].path = NULL;
}
aqdata->num_sensors=0;
// These should get set once we get panel info.
aqdata->total_buttons = 0;
aqdata->virtual_button_start = 0;
// Now we can loop over the JS and set values.
const char *sptr = strstr( inBuf, "values" );
if (_aqdata == NULL) {
LOG(AQUA_LOG,LOG_ERR, "Saving config, not initialized:\n'%.*s'\n", inSize, inBuf);
return snprintf(outBuf, outSize, "{\"message\":\"ERROR saving config\"}");
}
if (sptr == NULL) {
LOG(AQUA_LOG,LOG_ERR, "Saving config, invalid JSON:\n'%.*s'\n", inSize, inBuf);
return snprintf(outBuf, outSize, "{\"message\":\"ERROR in Config\"}");
}
if (0 != (rc = regcomp(®exCompiled, regexString, REG_EXTENDED))) {
LOG(AQUA_LOG,LOG_ERR, "Saving config regcomp() failed, returning nonzero (%d)\n", rc);
return snprintf(outBuf, outSize, "{\"message\":\"ERROR in Config\"}");
}
//cursor = inBuf+start+1;
for (m = 0; m < maxMatches; m ++)
{
ignorePair = false;
if (0 != (rc = regexec(®exCompiled, cursor, maxGroups, groupArray, 0))) {
//printf("Failed to match '%s' with '%s',returning %d.\n", cursor, pattern, rc);
break;
}
snprintf(key, 64, "%.*s", (groupArray[1].rm_eo - groupArray[1].rm_so), (cursor + groupArray[1].rm_so));
snprintf(value, 64, "%.*s", (groupArray[2].rm_eo - groupArray[2].rm_so), (cursor + groupArray[2].rm_so));
//printf("**** Pair = %s : %s \n",key,value);
// If panel size changed, see if we should ignore the label
if (strncasecmp(key, "button_", 7 ) == 0) {
if ( strtoul(key + 7, NULL, 10) >= ignodeBtnLabelsGrater) {
ignorePair = true;
LOG(AQUA_LOG,LOG_NOTICE, "Panel size changed, ignoring %s\n",key);
}
}
if (!ignorePair) {
setConfigValue(_aqdata ,key,value);
//printf("Config pair %s %s\n",key,value);
}
// Check if panel size has changed
if (strncasecmp(key, CFG_N_panel_type, (int)strlen(CFG_N_panel_type) ) == 0) {
if (psize != PANEL_SIZE()) {
// Panel size changed
ignodeBtnLabelsGrater = PANEL_SIZE();
}
}
cursor += groupArray[0].rm_eo;
}
// The above will reset all the panel profocol masks since it re-sets the panel, so set them back here.
if (_aqconfig_.rssa_device_id >= 0x48 && _aqconfig_.rssa_device_id <= 0x49) {
addPanelRSserialAdapterInterface();
}
if (_aqconfig_.extended_device_id >= 0x40 && _aqconfig_.extended_device_id <= 0x43) {
addPanelOneTouchInterface();
} else if (_aqconfig_.extended_device_id >= 0x30 && _aqconfig_.extended_device_id <= 0x33) {
addPanelIAQTouchInterface();
}
regfree(®exCompiled);
check_print_config(aqdata);
writeCfg(aqdata);
return sprintf(outBuf, "{\"message\":\"Saved Config\"}");
}
//#endif
//#ifdef CONFIG_EDITOR
const char *pumpType2String(pump_type ptype) {
switch (ptype) {
case EPUMP:
return "JANDY ePUMP";
break;
case VSPUMP:
return "Pentair VS";
break;
case VFPUMP:
return "Pentair VF";
break;
case PT_UNKNOWN:
default:
return "unknown";
break;
}
}
bool writeCfg (struct aqualinkdata *aqdata)
{
int i;
FILE *fp;
char *lastName = NULL;
bool ro_root;
bool created_file;
//LOG(AQUA_LOG,LOG_ERR, "writeCfg() not implimented\n");
//fp = fopen(_aqconfig_.config_file, "w");
//fp = fopen("/tmp/aqualinkd.conf", "w");
char backup_file[256];
sprintf(backup_file,"%s.backup",_aqconfig_.config_file);
if (copy_file(_aqconfig_.config_file, backup_file) != true) {
LOG(AQUA_LOG,LOG_WARNING,"Couldn't make a backup `%s` of config file `%s`\n",backup_file, _aqconfig_.config_file);
} else {
LOG(AQUA_LOG,LOG_NOTICE,"Made backup of config %s\n",backup_file);
}
fp = aq_open_file(_aqconfig_.config_file, &ro_root, &created_file);
if (fp == NULL) {
LOG(AQUA_LOG,LOG_ERR, "Open config file failed '%s'\n", _aqconfig_.config_file);
//remount_root_ro(true);
//fprintf(stdout, "Open file failed 'sprinkler.cron'\n");
return false;
}
//char fp[100];
for ( i=0; i <= _numCfgParams; i++) {
if (isMASK_SET(_cfgParams[i].config_mask, CFG_HIDE) ) {
continue;
}
//printf("Writing %s\n",_cfgParams[i].name);
// Group values by fist letter, if the same group together.
if (lastName != NULL && lastName[0] != _cfgParams[i].name[0]) {
if ( lastName != NULL && rsm_strstr(lastName, "device_id") != NULL && rsm_strstr(_cfgParams[i].name, "device_id") != NULL ) {
} else {
fprintf(fp,"\n");
}
}
switch (_cfgParams[i].value_type) {
case CFG_STRING:
if (*(char **)_cfgParams[i].value_ptr == NULL)
fprintf(fp, "#%s=\n", _cfgParams[i].name);
else
fprintf(fp, "%s=%s\n", _cfgParams[i].name, *(char **)_cfgParams[i].value_ptr);
break;
case CFG_INT:
if (*(int *)_cfgParams[i].value_ptr == TEMP_UNKNOWN)
fprintf(fp, "#%s=\n", _cfgParams[i].name);
else
fprintf(fp, "%s=%d\n", _cfgParams[i].name, *(int *)_cfgParams[i].value_ptr);
break;
case CFG_BOOL:
fprintf(fp, "%s=%s\n", _cfgParams[i].name, bool2text(*(bool *)_cfgParams[i].value_ptr));
break;
case CFG_HEX:
fprintf(fp, "%s=0x%02hhx\n", _cfgParams[i].name, *(unsigned char *)_cfgParams[i].value_ptr);
break;
case CFG_FLOAT:
fprintf(fp, "%s=%f\n", _cfgParams[i].name, *(float *)_cfgParams[i].value_ptr);
break;
case CFG_BITMASK:
fprintf(fp, "%s=%s\n", _cfgParams[i].name, (*(uint8_t *)_cfgParams[i].value_ptr & _cfgParams[i].mask) == _cfgParams[i].mask? bool2text(true):bool2text(false));
break;
case CFG_SPECIAL:
if (strncasecmp(_cfgParams[i].name, CFG_N_log_level, strlen(CFG_N_log_level)) == 0) {
fprintf(fp, "%s=%s\n", _cfgParams[i].name, loglevel2cgn_name(_aqconfig_.log_level));
} else if (strncasecmp(_cfgParams[i].name, CFG_N_panel_type, strlen(CFG_N_panel_type)) == 0) {
fprintf(fp, "%s=%s\n", _cfgParams[i].name, getPanelString());
} else {
fprintf(fp, "%s=NEED TO ADD CODE TO HANDLE THIS\n",_cfgParams[i].name);
}
break;
}
lastName = _cfgParams[i].name;
}
fprintf(fp,"\n");
// Add custom censors
for (i = 1; i <= aqdata->num_sensors; i++)
{
fprintf(fp,"\nsensor_%.2d_path=%s\n",i,aqdata->sensors[i-1].path);
fprintf(fp,"sensor_%.2d_label=%s\n",i,aqdata->sensors[i-1].label);
fprintf(fp,"sensor_%.2d_factor=%f\n",i,aqdata->sensors[i-1].factor);
}
fprintf(fp,"\n");
// add custom light modes/colors
bool isShow;
const char *lname;
for (i=1; i < LIGHT_COLOR_OPTIONS; i++) {
if ((lname = get_aqualinkd_light_mode_name(i, &isShow)) != NULL) {
fprintf(fp,"light_program_%.2d=%s%s\n",i,lname,isShow?" - show":"");
} else {
break;
}
}
fprintf(fp,"\n");
// Buttons
for (i = 0; i < aqdata->total_buttons; i++)
{
char prefix[30];
if (isVBUTTON(aqdata->aqbuttons[i].special_mask)) {
sprintf(prefix,"virtual_button_%.2d",(i+1)-aqdata->virtual_button_start);
} else {
sprintf(prefix,"button_%.2d",i+1);
}
fprintf(fp,"\n%s_label=%s\n", prefix, aqdata->aqbuttons[i].label);
if (isVS_PUMP(aqdata->aqbuttons[i].special_mask))
{
if (((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->pumpIndex > 0) {
fprintf(fp,"%s_pumpIndex=%d\n", prefix, ((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->pumpIndex);
}
if (((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->pumpID != NUL) {
fprintf(fp,"%s_pumpID=0x%02hhx\n", prefix, ((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->pumpID);
}
if (((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->pumpName[0] != '\0') {
fprintf(fp,"%s_pumpName=%s\n", prefix, ((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->pumpName);
}
if (((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->pumpType != PT_UNKNOWN) {
fprintf(fp,"%s_pumpType=%s\n", prefix, pumpType2String(((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->pumpType));
}
} else if (isPLIGHT(aqdata->aqbuttons[i].special_mask)) {
//if (((clight_detail *)aqdata->aqbuttons[i].special_mask_ptr)->lightType > 0) {
fprintf(fp,"%s_lightMode=%d\n", prefix, ((clight_detail *)aqdata->aqbuttons[i].special_mask_ptr)->lightType);
//}
} else if ( (isVBUTTON(aqdata->aqbuttons[i].special_mask) && aqdata->aqbuttons[i].rssd_code >= IAQ_ONETOUCH_1 && aqdata->aqbuttons[i].rssd_code <= IAQ_ONETOUCH_6 ) ) {
fprintf(fp,"%s_onetouchID=%d\n", prefix, (aqdata->aqbuttons[i].rssd_code - 15));
}
if (isVBUTTON_ALTLABEL(aqdata->aqbuttons[i].special_mask)) {
fprintf(fp,"%s_altLabel=%s\n", prefix, ((vbutton_detail *)aqdata->aqbuttons[i].special_mask_ptr)->altlabel);
}
}
//remount_root_ro(fs);
fprintf(fp,"\n");
//fclose(fp);
aq_close_file(fp, ro_root);
/*
FILE *fp;
int i;
bool fs = remount_root_ro(false);
fp = fopen(_aqconfig_.config_file, "w");
if (fp == NULL) {
LOG(AQUA_LOG,LOG_ERR, "Open config file failed '%s'\n", _aqconfig_.config_file);
remount_root_ro(true);
//fprintf(stdout, "Open file failed 'sprinkler.cron'\n");
return false;
}
fprintf(fp, "#***** AqualinkD configuration *****\n");
fprintf(fp, "socket_port = %s\n", _aqconfig_.socket_port);
fprintf(fp, "serial_port = %s\n", _aqconfig_.serial_port);
fprintf(fp, "device_id = 0x%02hhx\n", _aqconfig_.device_id);
fprintf(fp, "read_all_devices = %s", bool2text(_aqconfig_.read_all_devices));
writeCharValue(fp, "log_level", errorlevel2text(_aqconfig_.log_level));
writeCharValue(fp, "web_directory", _aqconfig_.web_directory);
writeCharValue(fp, "log_file", _aqconfig_.log_file);
fprintf(fp, "pda_mode = %s\n", bool2text(_aqconfig_.pda_mode));
fprintf(fp, "\n#** MQTT Configuration **\n");
writeCharValue(fp, "mqtt_address", _aqconfig_.mqtt_server);
writeCharValue(fp, "mqtt_dz_sub_topic", _aqconfig_.mqtt_dz_sub_topic);
writeCharValue(fp, "mqtt_dz_pub_topic", _aqconfig_.mqtt_dz_pub_topic);
writeCharValue(fp, "mqtt_aq_topic", _aqconfig_.mqtt_aq_topic);
writeCharValue(fp, "mqtt_user", _aqconfig_.mqtt_user);
writeCharValue(fp, "mqtt_passwd", _aqconfig_.mqtt_passwd);
fprintf(fp, "\n#** General **\n");
fprintf(fp, "convert_mqtt_temp_to_c = %s\n", bool2text(_aqconfig_.convert_mqtt_temp));
fprintf(fp, "override_freeze_protect = %s\n", bool2text(_aqconfig_.override_freeze_protect));
//fprintf(fp, "flash_mqtt_buttons = %s\n", bool2text(_aqconfig_.flash_mqtt_buttons));
fprintf(fp, "report_zero_spa_temp = %s\n", bool2text(_aqconfig_.report_zero_spa_temp));
fprintf(fp, "report_zero_pool_temp = %s\n", bool2text(_aqconfig_.report_zero_pool_temp));
fprintf(fp, "\n#** Programmable light **\n");
//if (_aqconfig_.light_programming_button_pool <= 0) {
// fprintf(fp, "#light_programming_button_pool = %d\n", _aqconfig_.light_programming_button_pool);
// fprintf(fp, "#light_programming_mode = %f\n", _aqconfig_.light_programming_mode);
// fprintf(fp, "#light_programming_initial_on = %d\n", _aqconfig_.light_programming_initial_on);
// fprintf(fp, "#light_programming_initial_off = %d\n", _aqconfig_.light_programming_initial_off);
//} else {
fprintf(fp, "light_programming_button_pool = %d\n", _aqconfig_.light_programming_button_pool);
fprintf(fp, "light_programming_button_spa = %d\n", _aqconfig_.light_programming_button_spa);
fprintf(fp, "light_programming_mode = %f\n", _aqconfig_.light_programming_mode);
fprintf(fp, "light_programming_initial_on = %d\n", _aqconfig_.light_programming_initial_on);
fprintf(fp, "light_programming_initial_off = %d\n", _aqconfig_.light_programming_initial_off);
//}
fprintf(fp, "\n#** Domoticz **\n");
fprintf(fp, "convert_dz_temp_to_c = %s\n", bool2text(_aqconfig_.convert_dz_temp));
writeIntValue(fp, "air_temp_dzidx", _aqconfig_.dzidx_air_temp);
writeIntValue(fp, "pool_water_temp_dzidx", _aqconfig_.dzidx_pool_water_temp);
writeIntValue(fp, "spa_water_temp_dzidx", _aqconfig_.dzidx_spa_water_temp);
writeIntValue(fp, "SWG_percent_dzidx", _aqconfig_.dzidx_swg_percent);
writeIntValue(fp, "SWG_PPM_dzidx", _aqconfig_.dzidx_swg_ppm);
writeIntValue(fp, "SWG_Status_dzidx", _aqconfig_.dzidx_swg_status);
fprintf(fp, "\n#** Buttons **\n");
for (i=0; i < TOTAL_BUTTONS; i++)
{
fprintf(fp, "button_%.2d_label = %s\n", i+1, aqdata->aqbuttons[i].label);
if (aqdata->aqbuttons[i].dz_idx > 0)
fprintf(fp, "button_%.2d_dzidx = %d\n", i+1, aqdata->aqbuttons[i].dz_idx);
if (aqdata->aqbuttons[i].pda_label != NULL)
fprintf(fp, "button_%.2d_PDA_label = %s\n", i+1, aqdata->aqbuttons[i].pda_label);
}
fclose(fp);
remount_root_ro(fs);
*/
return true;
}
//#endif