/*
 * 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 <http://www.gnu.org/licenses/>.
 *
 * You are free to use this software under the terms of the GNU General
 * Public License, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 *  https://github.com/sfeakes/aqualinkd
 */

 #define _GNU_SOURCE 1 // for strcasestr

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include <libgen.h>

//#include <locale.h>

#include <sys/ioctl.h>
//#include <sys/socket.h>
//#include <sys/time.h>
//#include <syslog.h>
//#include <unistd.h>
#include <netdb.h>
//#include <linux/if.h>
//#include <sys/types.h>
//#include <unistd.h>
#include <net/if.h>
#include <regex.h>
#include <limits.h>


#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"
#include "net_interface.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);
bool populateLightData(struct aqualinkdata *aqdata, char *lightcfg ,aqkey *button, char *value);
bool populateAltLabel(aqkey *button, char *value);
pump_detail *getPumpFromButtonID(struct aqualinkdata *aqdata, aqkey *button);
clight_detail *getLightFromButtonID(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) {
            *(uint16_t *)parm->value_ptr |= parm->mask;
          } else {
            *(uint16_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_discovery = DEFAULT_DISCOVERY;
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;


const int           _dcfg_sensor_poll_time = 300;

void init_parameters (struct aqconfig * parms)
{
  //#ifdef CONFIG_DEV_TEST
  _numCfgParams = 0;

  //const int unknownInt = TEMP_UNKNOWN;
 
  _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.listen_address;
  _cfgParams[_numCfgParams].value_type = CFG_STRING;
  _cfgParams[_numCfgParams].name = CFG_N_listen_address;
  //_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_.mg_log_level;
  _cfgParams[_numCfgParams].value_type = CFG_INT; // Set with _aqconfig_.log_level = text2elevel(cleanalloc(value));
  _cfgParams[_numCfgParams].name = CFG_N_MG_log_level;
  _cfgParams[_numCfgParams].config_mask |= CFG_READONLY;
  _cfgParams[_numCfgParams].default_value = (void *) &_dcfg_zero;



  _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].config_mask |= CFG_FORCE_RESTART;
  _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;
  _cfgParams[_numCfgParams].config_mask |= CFG_FORCE_RESTART;

  _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;
  _cfgParams[_numCfgParams].config_mask |= CFG_FORCE_RESTART;

  _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;
  _cfgParams[_numCfgParams].config_mask |= CFG_ALLOW_BLANK;
  _cfgParams[_numCfgParams].config_mask |= CFG_FORCE_RESTART;

   _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;
  _cfgParams[_numCfgParams].config_mask |= CFG_ALLOW_BLANK;

   _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;
  _cfgParams[_numCfgParams].config_mask |= CFG_ALLOW_BLANK;

   _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;
  _cfgParams[_numCfgParams].config_mask |= CFG_ALLOW_BLANK;

   _numCfgParams++;
  _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_discovery_topic;
  _cfgParams[_numCfgParams].value_type = CFG_STRING;
  _cfgParams[_numCfgParams].name = CFG_N_mqtt_discovery_topic;
  _cfgParams[_numCfgParams].default_value = (void *)_dcfg_mqtt_discovery;
  _cfgParams[_numCfgParams].config_mask |= CFG_ALLOW_BLANK;

   _numCfgParams++;
  _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.mqtt_discovery_use_mac;
  _cfgParams[_numCfgParams].value_type = CFG_BOOL;
  _cfgParams[_numCfgParams].name = CFG_N_mqtt_discovery_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_.light_programming_mode;
  _cfgParams[_numCfgParams].value_type = CFG_FLOAT;
  _cfgParams[_numCfgParams].name = CFG_N_light_programming_mode;
  _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].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].config_mask |= CFG_GRP_ADVANCED;
  _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_light_programming_initial_off;

  _numCfgParams++;
  _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.light_programming_advance_mode;
  _cfgParams[_numCfgParams].value_type = CFG_BOOL;
  _cfgParams[_numCfgParams].name = CFG_N_light_programming_advance_mode;
  _cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
  _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_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_ChemLink;
  _cfgParams[_numCfgParams].mask = READ_RS485_JAN_CHEM_FEDR;
  _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_ChemAnalyzer;
  _cfgParams[_numCfgParams].mask = READ_RS485_JAN_CHEM_ANLZ;
  _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;
   _cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;

  _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;
   _cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;

  _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;
   _cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;

  _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;
   _cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;

  _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;
   _cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;

  _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;
   _cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;
  
  _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;
   _cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;

  _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

  // Sensor poll time
  _numCfgParams++;
  _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.sensor_poll_time;
  _cfgParams[_numCfgParams].value_type = CFG_INT;
  _cfgParams[_numCfgParams].name =  "sensor_poll_time";
  _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_sensor_poll_time;
  _cfgParams[_numCfgParams].config_mask |= CFG_GRP_ADVANCED;


  // Optional values to store in config
  _numCfgParams++;
  _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.save_debug_log_masks;
  _cfgParams[_numCfgParams].value_type = CFG_BOOL;
  _cfgParams[_numCfgParams].name = CFG_N_save_debug_log_masks;
  _cfgParams[_numCfgParams].config_mask |= CFG_READONLY;
  _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;

   _numCfgParams++;
  _cfgParams[_numCfgParams].value_ptr = &_aqconfig_.save_light_programming_value;
  _cfgParams[_numCfgParams].value_type = CFG_BOOL;
  _cfgParams[_numCfgParams].name = CFG_N_save_light_programming_value;
  _cfgParams[_numCfgParams].config_mask |= CFG_READONLY;
  _cfgParams[_numCfgParams].default_value = (void *)&_dcfg_false;

  //#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

  // CHANGED DEFAULT IN V3.  (WAnt to DELETE this)
  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();
}


/*

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;i<loc2;i++) {
    if ( str[i] == ';' ) {
      found++;
      if (found == index)
        loc1 = i;
      else if (found == (index+1))
        loc2 = i;
    }
  }
  
  if (found < index)
    return NULL;

  // Trim leading & trailing spaces
  loc1++;
  while(isspace(str[loc1])) loc1++;
  loc2--;
  while(isspace(str[loc2])) loc2--;
  
  // Allocate and copy
  result = (char*)malloc(loc2-loc1+2*sizeof(char));
  strncpy ( result, &str[loc1], loc2-loc1+1 );
  result[loc2-loc1+1] = '\0';

  return result;
}
*/
// Find the first network interface with valid MAC and put mac address into buffer upto length
/*
bool mac(char *buf, int len, bool useDelimiter)
{
  struct ifreq s;
  int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
  struct if_nameindex *if_nidxs, *intf;

  if_nidxs = if_nameindex();
  if (if_nidxs != NULL)
  {
    for (intf = if_nidxs; intf->if_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;
}
*/





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_INFO,"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))
            *(uint16_t *)_cfgParams[i].value_ptr |= _cfgParams[i].mask;
          else
            *(uint16_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, "socket_port", 11) == 0) {
    if ( strcasestr(value, "http") != NULL ) {
      _aqconfig_.listen_address = cleanalloc(value);
    } else {
      // Only allowing IPv4 here
      _aqconfig_.listen_address = malloc(30 * sizeof(char));
      sprintf(_aqconfig_.listen_address, "http://0.0.0.0:%s", cleanwhitespace(value));
    }
    rtn=true;
  } else if (strncasecmp(param, "read_RS485_Chem", 15) == 0) {
    // Renamed to read_RS485_ChemLink now
    if (text2bool(value))
       _aqconfig_.read_RS485_devmask |= READ_RS485_JAN_CHEM_FEDR;
    else
      _aqconfig_.read_RS485_devmask &= ~ READ_RS485_JAN_CHEM_FEDR;
    rtn=true;
  } else if ((strncasecmp(param, CFG_N_mqtt_discovery_topic, strlen(CFG_N_mqtt_discovery_topic)) == 0) ||
             (strncasecmp(param, "mqtt_ha_discover_topic", 22) == 0) || 
             (strncasecmp(param, "mqtt_hassio_discover_topic", 26) == 0) || 
             (strncasecmp(param, "mqtt_hass_discover_topic", 24) == 0)) {
    _aqconfig_.mqtt_discovery_topic = cleanalloc(value);
    rtn=true;
  } else if ((strncasecmp(param, CFG_N_mqtt_discovery_use_mac, strlen(CFG_N_mqtt_discovery_use_mac)) == 0) ||
             (strncasecmp(param, "mqtt_ha_discover_use_mac", 24) == 0) ||
             (strncasecmp(param, "mqtt_hassio_discover_use_mac", 28) == 0) ||
             (strncasecmp(param, "mqtt_hass_discover_use_mac", 26) == 0)) {
    _aqconfig_.mqtt_discovery_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, "convert_mqtt_temp_to_c", 22) == 0) {
    _aqconfig_.convert_mqtt_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 || num < 0 ) {
      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 ( strncasecmp(name+len-7, " - show", 7) == 0 ) {
        name[len-7] = '\0';
        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;
#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, "_lightModeCacheValue", 20) == 0) {
      if (isPLIGHT(aqdata->aqbuttons[num].special_mask)) {
        if (  ((clight_detail *)aqdata->aqbuttons[num].special_mask_ptr)->lightType == LC_PROGRAMABLE ) {
          int val = strtoul(value, NULL, 10);
          ((clight_detail *)aqdata->aqbuttons[num].special_mask_ptr)->lastValue = val;
          printf("**** Set lastValue=%d for %s\n",val,aqdata->aqbuttons[num].label);
        } else {
          LOG(AQUA_LOG,LOG_ERR, "Config error, '%s' is invalied for light type '%s'\n",value,((clight_detail *)aqdata->aqbuttons[num].special_mask_ptr)->lightType);
        }
      } else {
        LOG(AQUA_LOG,LOG_ERR, "Config error, Couldn't find light for '%s'\n",value);
      }
      rtn=true;
    } 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 '%d' for '%s'\n",type,param);
        } 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, "_light", 6) == 0) {

      if ( ! populateLightData(aqdata, param + 10, &aqdata->aqbuttons[num], value) ) 
      {
        LOG(AQUA_LOG,LOG_ERR, "Config error, %s Ignored!",param,value);
      }

      rtn=true;
     
      /*
    } else if (strncasecmp(param + 9, "_lightModeCacheValue", 20) == 0) {
      int val = strtoul(value, NULL, 20);
      if (isPLIGHT(aqdata->aqbuttons[num].special_mask)) {
        if (  ((clight_detail *)aqdata->aqbuttons[num].special_mask_ptr)->lightType == LC_PROGRAMABLE ) {
          ((clight_detail *)aqdata->aqbuttons[num].special_mask_ptr)->lastValue = val;
        } else {
          LOG(AQUA_LOG,LOG_ERR, "Config error, '%s' is invalied for light type '%s'\n",value,((clight_detail *)aqdata->aqbuttons[num].special_mask_ptr)->lightType);
        }
      } else {
        LOG(AQUA_LOG,LOG_ERR, "Config error, Couldn't find light for '%s'\n",value);
      }*/
    } 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) {
    
    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);
      }
      rtn=true;
    } else if (strncasecmp(param + 17, "_altLabel", 9) == 0) {
      //char *label = cleanalloc(value);
      aqkey *vbutton = getVirtualButton(aqdata, num);
      if (vbutton != NULL) {
        //setVirtualButtonAltLabel(vbutton, label);
        if ( ! populateAltLabel(vbutton, value) ) 
        {
          LOG(AQUA_LOG,LOG_ERR, "Config error, setting alt_label for %d\n",vbutton->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);
      }
      rtn=true;
    } 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);
      }
      rtn=true;
    } else if (strncasecmp(param + 17, "_light", 6) == 0) {
      aqkey *vbutton = getVirtualButton(aqdata, num);
      if (vbutton != NULL) {
        if ( ! populateLightData(aqdata, param + 18, vbutton, value) ) 
        {
          LOG(AQUA_LOG,LOG_ERR, "Config error, couldn't find light. `%s` Ignored!",param);
        }
      } else {
        LOG(AQUA_LOG,LOG_ERR, "Config error, could not find vitrual button for `%s`",param);
      }
      rtn=true;
    } 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);
      }
      rtn=true;
    }
  } 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;
      }
      sprintf(aqdata->sensors[num].ID, "Aux_S%d", 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 if (strncasecmp(param + 9, "_regex", 5) == 0) {
        aqdata->sensors[num].regex = cleanalloc(value);
        rtn=true;
      } else if (strncasecmp(param + 9, "_uom", 3) == 0) {
        aqdata->sensors[num].uom = cleanalloc(value);
        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);
  } else {
    return false;
  }

  return true;
}


bool populateAltLabel(aqkey *button, char *value)
{
  return setVirtualButtonAltLabel(button, cleanalloc(value));
  /*
  setButtonSpecialMask(button, VIRTUAL_BUTTON_ALT_LABEL);

  button->special_mask_ptr = malloc(sizeof(altlabel_detail));
  ((altlabel_detail *)button->special_mask_ptr)->altlabel = cleanalloc(value);
  
  return true;
  */
}

// lightcfg is pointer to lightMode, lightID, lightModeCacheValue  (ie pull off button_??_ or vurtual_button_??_)
bool populateLightData(struct aqualinkdata *aqdata, char *lightcfg ,aqkey *button, char *value)
{

  clight_detail *light = getLightFromButtonID(aqdata, button);
  if (light == NULL) {
    return false;
  }

  if (strncasecmp(lightcfg, "lightModeCacheValue", 19) == 0) {
    light->lastValue = strtoul(value, NULL, 10);
    return true;
  } else if (strncasecmp(lightcfg, "lightMode", 9) == 0) {
    light->lightType = strtoul(value, NULL, 10);
    if (light->lightType < LC_PROGRAMABLE || light->lightType >= NUMBER_LIGHT_COLOR_TYPES) {
      LOG(AQUA_LOG,LOG_ERR, "Config error, unknown light mode '%d'\n",light->lightType);
    }
    if ( light->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",
                button->label, LC_DIMMER2,LC_DIMMER);
      light->lightType = LC_DIMMER;
    }
    return true;
  } else if (strncasecmp(lightcfg, "lightID", 7) == 0) {
    light->lightID = strtoul(cleanwhitespace(value), NULL, 16);
    return true;
  } else {
    return false;
  }

  return false;
}



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);
    
    setButtonSpecialMask(button, VS_PUMP);
    //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;
}

clight_detail *getLightFromButtonID(struct aqualinkdata *aqdata, aqkey *button)
{
  int li;

  // Does it exist
  for (li=0; li < aqdata->num_lights; li++) {
    if (aqdata->lights[li].button == button) {
      return &aqdata->lights[li];
    }
  }

  // Create new entry
  if (aqdata->num_lights < MAX_LIGHTS) {
    setButtonSpecialMask(button, PROGRAM_LIGHT);
    //button->special_mask |= PROGRAM_LIGHT;
    button->special_mask_ptr = (void*)&aqdata->lights[aqdata->num_lights];
    aqdata->lights[aqdata->num_lights].button = button;
    aqdata->num_lights++;
    return &aqdata->lights[aqdata->num_lights-1];
  }

  LOG(AQUA_LOG,LOG_ERR, "Config error couldn't add light, max lights of %d reached\n",MAX_LIGHTS);

  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;


  //setlocale(LC_ALL, "en_US.UTF-8"); 

  _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].uom == NULL) {
    //  aqdata->sensors[i].uom = cleanalloc("°C");
    //}
    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->sensors[j].regex = aqdata->sensors[j+1].regex;
          aqdata->sensors[j].uom = aqdata->sensors[j+1].uom;
          //aqdata->sensors[j].ID = aqdata->sensors[j+1].ID;
          sprintf(aqdata->sensors[j].ID, "Aux_S%d", j+1);
          //printf("Sensor %d = %s, %s\n",j,aqdata->sensors[j].ID,aqdata->sensors[j].label);
        }
      }
      if (aqdata->num_sensors > 1) {
        aqdata->num_sensors --;
      }
    }
    //printf("Num Sensors=%d\n",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(((altlabel_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);
        if (button != NULL) {
          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_WARNING, "Error can't create Heat Pump / Chiller, total buttons=%d, config has %d already, ignoring!\n", TOTAL_BUTTONS, aqdata->total_buttons);
        }
      }
    } 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;
      }
    }
    if (i >= aqdata->total_buttons)
      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;
  }

  // Check we have pump speed set
  for (i=0; i < aqdata->num_pumps; i++) {
    if (aqdata->pumps[i].maxSpeed <= 0) {
      aqdata->pumps[i].maxSpeed = getPumpDefaultSpeed(&aqdata->pumps[i], true);
      //aqdata.pumps[i].maxSpeed = (_aqualink_data.pumps[i].pumpType==VFPUMP?PUMP_GPM_MAX:PUMP_RPM_MAX);
    }
    if (aqdata->pumps[i].minSpeed <= 0) {
      //aqdata.pumps[i].minSpeed = (_aqualink_data.pumps[i].pumpType==VFPUMP?PUMP_GPM_MIN:PUMP_RPM_MIN);
      aqdata->pumps[i].minSpeed = getPumpDefaultSpeed(&aqdata->pumps[i], false);
    }
  }
  
  // We need to store light values if using light advance mode
  if (_aqconfig_.light_programming_advance_mode) {
    if (_aqconfig_.save_light_programming_value == false) {
      LOG(AQUA_LOG,LOG_WARNING, "Config error, `%s` must be enabled for `%s`, enabeling!",CFG_N_save_light_programming_value,CFG_N_light_programming_advance_mode);
      _aqconfig_.save_light_programming_value = true;
    }
  }

  /* 
     PDA Mode

     No light mode 11
     (chiller????? )
     <probably ton of other stuff>
  */

    if (_aqconfig_.device_id == 0x60) {
      // Check lights.
      for (i = 0; i < aqdata->total_buttons; i++)
      {
        if (isPLIGHT(aqdata->aqbuttons[i].special_mask)) {
          if ( ((clight_detail *)aqdata->aqbuttons[i].special_mask_ptr)->lightType == LC_DIMMER2 ) {
            LOG(AQUA_LOG,LOG_WARNING, "Config error, PDA does not support lightmode %d setting to %d\n",LC_DIMMER2,LC_DIMMER);
            ((clight_detail *)aqdata->aqbuttons[i].special_mask_ptr)->lightType = LC_DIMMER;
          }
        }
      }
    }
  /*
    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++) {

    // Reconfigure some CFG values depending on PDA
    if ( strcmp(_cfgParams[i].name, CFG_N_device_id) == 0 ) {
      if (isPDA_PANEL) {
        _cfgParams[i].valid_values = CFG_V_device_id_PDA; 
      } else {
        _cfgParams[i].valid_values = CFG_V_device_id_RS; 
      }
    }
    if ( strcmp(_cfgParams[i].name, CFG_N_rssa_device_id) == 0 && isPDA_PANEL) {
      _cfgParams[i].config_mask |= CFG_GREYED_OUT;
    }
    if ( strcmp(_cfgParams[i].name, CFG_N_extended_device_id) == 0 && isPDA_PANEL) {
      _cfgParams[i].config_mask |= CFG_GREYED_OUT;
    }

    // Don't show PDA stuff in config editor on RS panel
    if ( strcmp(_cfgParams[i].name, CFG_N_pda_sleep_mode) == 0 && !isPDA_PANEL) {
      _cfgParams[i].config_mask |= CFG_GREYED_OUT;
      _cfgParams[i].config_mask |= CFG_READONLY;
    }

    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, (*(uint16_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[120];
    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 %-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|", ((altlabel_detail *)aqdata->aqbuttons[i].special_mask_ptr)->altlabel);
    }

    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 one 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, "Sensor %-13s = label %-15s | %s\n", aqdata->sensors[i].ID, aqdata->sensors[i].label,aqdata->sensors[i].path);
    LOG(AQUA_LOG,LOG_NOTICE, "Sensor %-13s = label %-15s | %s | %s%s %s\n", 
         aqdata->sensors[i].ID, 
         aqdata->sensors[i].label,
         aqdata->sensors[i].path,
         (aqdata->sensors[i].uom==NULL?"":aqdata->sensors[i].uom),
         (aqdata->sensors[i].uom==NULL?"":" |"),
         (aqdata->sensors[i].regex==NULL?"":aqdata->sensors[i].regex));
  }

  

}



/*
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].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;
    // NSF When fixed the JSON & config editor, put these lines back.
    //free(aqdata->sensors[i].regex);
    //aqdata->sensors[i].regex = NULL;
    free(aqdata->sensors[i].uom);
    aqdata->sensors[i].uom = 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(&regexCompiled, 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(&regexCompiled, 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);

    LOG(AQUA_LOG,LOG_DEBUG, "Read json cfg 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(&regexCompiled);

  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];

  // Testing shit
  if (_aqconfig_.save_debug_log_masks) {
    for (int i = 0; i < (sizeof(logmask_t) * CHAR_BIT); i++) {
      if(isDebugLogMaskSet((1 << i))) {
        fprintf(fp, "debug_log_mask=%d\n", (1 << i));
      }
    }
    fprintf(fp,"\n");
  }

  // Loop over config parameters.

  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, (*(uint16_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);
    if (aqdata->sensors[i-1].regex != NULL) {
      fprintf(fp,"sensor_%.2d_regex=%s\n",i,aqdata->sensors[i-1].regex);
    }
    if (aqdata->sensors[i-1].uom != NULL) {
      fprintf(fp,"sensor_%.2d_uom=%s\n",i,aqdata->sensors[i-1].uom);
    }
    /*
    if (aqdata->sensors[i-1].regex != NULL) {
      fprintf(fp,"sensor_%.2d_regex=%f\n",i,aqdata->sensors[i-1].regex);
    }
    */
  }

  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));
      }

      //if (((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->minSpeed != PT_UNKNOWN) {
      if (((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->minSpeed != PT_UNKNOWN &&
          ((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->minSpeed != getPumpDefaultSpeed((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr, false) ) 
      {
        fprintf(fp,"%s_pumpMinSpeed=%d\n", prefix, ((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->minSpeed);
      }

      //if (((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->maxSpeed != PT_UNKNOWN) {
      if (((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->maxSpeed != PT_UNKNOWN &&
          ((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->maxSpeed != getPumpDefaultSpeed((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr, true) ) 
      {   
        fprintf(fp,"%s_pumpMaxSpeed=%d\n", prefix, ((pump_detail *)aqdata->aqbuttons[i].special_mask_ptr)->maxSpeed);
      }

    } 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);
      if (_aqconfig_.save_light_programming_value && ((clight_detail *)aqdata->aqbuttons[i].special_mask_ptr)->lightType == LC_PROGRAMABLE ) {
        fprintf(fp,"%s_lightModeCacheValue=%d\n", prefix, ((clight_detail *)aqdata->aqbuttons[i].special_mask_ptr)->lastValue);
      }
      //}
    } 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, ((altlabel_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
