#define _GNU_SOURCE 1 // for strcasestr & strptime
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "aqualink.h"
#include "allbutton.h"
#include "rs_msg_utils.h"
#include "devices_jandy.h"
#include "allbutton_aq_programmer.h"
#include "color_lights.h"
#include "aq_scheduler.h"

/* Below can also be called from serialadapter.c */
void processLEDstate(struct aqualinkdata *aq_data, unsigned char *packet, logmask_t from)
{

  int i = 0;
  int byte;
  int bit;

  if (memcmp(aq_data->raw_status, packet + 4, AQ_PSTLEN) != 0) {
    aq_data->updated = true;
    LOG(from,LOG_DEBUG, "Processing LEDs status CHANGED\n");
  } else {
    LOG(from,LOG_DEBUG, "Processing LEDs status\n");
    // Their is no point in continuing here, so we could return if wanted.
    // But for the moment, we don't need to speed up anything.
  }

  memcpy(aq_data->raw_status, packet + 4, AQ_PSTLEN);

  //debuglogPacket(ALLB_LOG, );
  

  for (byte = 0; byte < 5; byte++)
  {
    for (bit = 0; bit < 8; bit += 2)
    {
      if (((aq_data->raw_status[byte] >> (bit + 1)) & 1) == 1)
        aq_data->aqualinkleds[i].state = FLASH;
      else if (((aq_data->raw_status[byte] >> bit) & 1) == 1)
        aq_data->aqualinkleds[i].state = ON;
      else
        aq_data->aqualinkleds[i].state = OFF;

      //LOG(from,LOG_DEBUG,"Led %d state %d",i+1,aq_data->aqualinkleds[i].state);
      i++;
    }
  }
  // Reset enabled state for heaters, as they take 2 led states
  if (aq_data->aqualinkleds[POOL_HTR_LED_INDEX - 1].state == OFF && aq_data->aqualinkleds[POOL_HTR_LED_INDEX].state == ON)
    aq_data->aqualinkleds[POOL_HTR_LED_INDEX - 1].state = ENABLE;

  if (aq_data->aqualinkleds[SPA_HTR_LED_INDEX - 1].state == OFF && aq_data->aqualinkleds[SPA_HTR_LED_INDEX].state == ON)
    aq_data->aqualinkleds[SPA_HTR_LED_INDEX - 1].state = ENABLE;

  if (aq_data->aqualinkleds[SOLAR_HTR_LED_INDEX - 1].state == OFF && aq_data->aqualinkleds[SOLAR_HTR_LED_INDEX].state == ON)
    aq_data->aqualinkleds[SOLAR_HTR_LED_INDEX - 1].state = ENABLE;
  /*
  for (i=0; i < TOTAL_BUTTONS; i++) {
    LOG(from,LOG_NOTICE, "%s = %d", aq_data->aqbuttons[i].name,  aq_data->aqualinkleds[i].state);
  }
*/
#ifdef CLIGHT_PANEL_FIX // Use state from RSSD protocol for color light if it's on.
  for (int i=0; i < aq_data->num_lights; i++) {
    if ( aq_data->lights[i].RSSDstate == ON && aq_data->lights[i].button->led->state != ON ) {
      aq_data->lights[i].button->led->state = aq_data->lights[i].RSSDstate;
      //LOG(from,LOG_WARNING,"Fix Jandy bug, color light '%s' is on, setting status to match!\n", aq_data->lights[i].button->label);
    }
    
    // Below is for aqualinkd programmable light, set color mode to last if something else turns it on or off.
    if (aq_data->lights[i].lightType == LC_PROGRAMABLE && ! in_light_programming_mode(aq_data)) {
      if (aq_data->lights[i].button->led->state == OFF && aq_data->lights[i].currentValue != 0) {
        set_currentlight_value(&aq_data->lights[i], 0);
        //LOG(ALLB_LOG,LOG_NOTICE,"****** SET LIGHT MODE 0 ******\n");
      } else if (aq_data->lights[i].button->led->state == ON && aq_data->lights[i].currentValue == 0 && aq_data->lights[i].lastValue != 0) {
        set_currentlight_value(&aq_data->lights[i], aq_data->lights[i].lastValue);
        //LOG(ALLB_LOG,LOG_NOTICE,"****** SET LIGHT MODE %d ******\n",aq_data->lights[i].lastValue);
      }
    }
  }
#endif
}

void setUnits(char *msg, struct aqualinkdata *aq_data)
{
  char buf[AQ_MSGLEN*3];

  rsm_strncpy(buf, (unsigned char *)msg, AQ_MSGLEN*3, AQ_MSGLONGLEN);

  //ascii(buf, msg);
  LOG(ALLB_LOG,LOG_DEBUG, "Getting temp units from message '%s', looking at '%c'\n", buf, buf[strlen(buf) - 1]);

  if (msg[strlen(msg) - 1] == 'F')
    aq_data->temp_units = FAHRENHEIT;
  else if (msg[strlen(msg) - 1] == 'C')
    aq_data->temp_units = CELSIUS;
  else
    aq_data->temp_units = UNKNOWN;

  LOG(ALLB_LOG,LOG_INFO, "Temp Units set to %d (F=0, C=1, Unknown=2)\n", aq_data->temp_units);
}

// Defined as int16_t so 16 bits to mask
#define MSG_FREEZE      (1 << 0) // 1
#define MSG_SERVICE     (1 << 1) // 1
#define MSG_SWG         (1 << 2)
#define MSG_BOOST       (1 << 3)
#define MSG_TIMEOUT     (1 << 4)
#define MSG_RS13BUTTON  (1 << 5)
#define MSG_RS14BUTTON  (1 << 6)
#define MSG_RS15BUTTON  (1 << 7)
#define MSG_RS16BUTTON  (1 << 8)
#define MSG_BATTERY_LOW (1 << 9)
#define MSG_SWG_DEVICE  (1 << 10)
#define MSG_LOOP_POOL_TEMP   (1 << 11)
#define MSG_LOOP_SPA_TEMP    (1 << 12)


int16_t  RS16_endswithLEDstate(char *msg, struct aqualinkdata *aq_data)
{
  char *sp;
  int i;
  aqledstate state = LED_S_UNKNOWN;

  //if (_aqconfig_.rs_panel_size < 16)
  if (PANEL_SIZE() < 16)
    return false;

  sp = strrchr(msg, ' ');

  if( sp == NULL )
    return false;
  
  if (strncasecmp(sp, " on", 3) == 0)
    state = ON;
  else if (strncasecmp(sp, " off", 4) == 0)
    state = OFF;
  else if (strncasecmp(sp, " enabled", 8) == 0) // Total guess, need to check
    state = ENABLE;
  else if (strncasecmp(sp, " no idea", 8) == 0) // need to figure out these states
    state = FLASH;

  if (state == LED_S_UNKNOWN)
    return false;

  // Only need to start at Aux B5->B8 (12-15)
  // Loop over only aqdata->aqbuttons[13] to aqdata->aqbuttons[16]
  for (i = aq_data->rs16_vbutton_start; i <= aq_data->rs16_vbutton_end; i++) {
    //TOTAL_BUTTONS
    if ( stristr(msg, aq_data->aqbuttons[i].label) != NULL) {
      aq_data->aqbuttons[i].led->state = state;
      LOG(ALLB_LOG,LOG_INFO, "Set %s to %d\n", aq_data->aqbuttons[i].label, aq_data->aqbuttons[i].led->state);
      // Return true should be the result, but in the if we want to continue to display message
      //return true;
      if (i == 13)
        return MSG_RS13BUTTON;
      else if (i == 14)
        return MSG_RS14BUTTON;
      else if (i == 15)
        return MSG_RS15BUTTON;
      else if (i == 16)
        return MSG_RS16BUTTON;
      else
      {
        LOG(ALLB_LOG,LOG_ERR, "RS16 Button Set error %s to %d, %d is out of scope\n", aq_data->aqbuttons[i].label, aq_data->aqbuttons[i].led->state, i);
        return false;
      }
      
    }
  }

  return false;
}


void _processMessage(char *message, struct aqualinkdata *aq_data, bool reset);

void processMessage(char *message, struct aqualinkdata *aq_data)
{
  _processMessage(message, aq_data, false);
}
void processMessageReset(struct aqualinkdata *aq_data)
{
  _processMessage(NULL, aq_data, true);
}
void _processMessage(char *message, struct aqualinkdata *aq_data, bool reset)
{
  char *msg;
  static bool _initWithRS = false;
  //static bool _gotREV = false;
  //static int freeze_msg_count = 0;
  //static int service_msg_count = 0;
  //static int swg_msg_count = 0;
  //static int boost_msg_count = 0;
  static int16_t msg_loop = 0;
  static aqledstate default_frz_protect_state = OFF;
  static bool boostInLastLoop = false;
  // NSF replace message with msg

  int16_t rs16;


  //msg = stripwhitespace(message);
  //strcpy(aq_data->last_message, msg);
  //LOG(ALLB_LOG,LOG_INFO, "RS Message :- '%s'\n", msg);

  

  // Check long messages in this if/elseif block first, as some messages are similar.
  // ie "POOL TEMP" and "POOL TEMP IS SET TO"  so want correct match first.
  //

  //if (stristr(msg, "JANDY AquaLinkRS") != NULL) {
  if (!reset) {
    msg = stripwhitespace(message);
    strcpy(aq_data->last_message, msg);
    LOG(ALLB_LOG,LOG_INFO, "RS Message :- '%s'\n", msg);
    // Just set this to off, it will re-set since it'll be the only message we get if on
    aq_data->service_mode_state = OFF;
  } else {
    //aq_data->display_message = NULL;
    aq_data->last_display_message[0] = ' ';
    aq_data->last_display_message[1] = '\0';

    // Anything that wasn't on during the last set of messages, turn off
    if ((msg_loop & MSG_FREEZE) != MSG_FREEZE) {
      if (aq_data->frz_protect_state != default_frz_protect_state) {
        LOG(ALLB_LOG,LOG_INFO, "Freeze protect turned off\n");
        event_happened_set_device_state(AQS_FRZ_PROTECT_OFF, aq_data);
        // Add code to check Pump if to turn it on (was scheduled) ie time now is inbetween ON / OFF schedule 
      }
      aq_data->frz_protect_state = default_frz_protect_state;
    }

    if ((msg_loop & MSG_SERVICE) != MSG_SERVICE &&
        (msg_loop & MSG_TIMEOUT) != MSG_TIMEOUT ) {
      aq_data->service_mode_state = OFF; // IF we get this message then Service / Timeout is off
    }

    if ( ((msg_loop & MSG_SWG_DEVICE) != MSG_SWG_DEVICE) && aq_data->swg_led_state != LED_S_UNKNOWN) {
      // No Additional SWG devices messages like "no flow"
      if ((msg_loop & MSG_SWG) != MSG_SWG && aq_data->aqbuttons[PUMP_INDEX].led->state == OFF )
        setSWGdeviceStatus(aq_data, ALLBUTTON, SWG_STATUS_OFF);
      else
        setSWGdeviceStatus(aq_data, ALLBUTTON, SWG_STATUS_ON);
    }

    // If no AQUAPURE message, either (no SWG, it's set 0, or it's off).
    if ((msg_loop & MSG_SWG) != MSG_SWG && aq_data->swg_led_state != LED_S_UNKNOWN ) {
      if (aq_data->swg_percent != 0 || aq_data->swg_led_state == ON) {
        // Something is wrong here.  Let's check pump, if on set SWG to 0, if off turn SWE off
        if ( aq_data->aqbuttons[PUMP_INDEX].led->state == OFF) {
          LOG(ALLB_LOG,LOG_INFO, "No AQUAPURE message in cycle, pump is off so setting SWG to off\n");
          setSWGoff(aq_data);
        } else {
          LOG(ALLB_LOG,LOG_INFO, "No AQUAPURE message in cycle, pump is on so setting SWG to 0%%\n");
          changeSWGpercent(aq_data, 0);
        }
      } else if (isIAQT_ENABLED == false && isONET_ENABLED == false && READ_RSDEV_SWG == false ) {
        //We have no other way to read SWG %=0, so turn SWG on with pump
        if ( aq_data->aqbuttons[PUMP_INDEX].led->state == ON) {
           LOG(ALLB_LOG,LOG_INFO, "No AQUAPURE message in cycle, pump is off so setting SWG to off\n");
           //changeSWGpercent(aq_data, 0);
           setSWGenabled(aq_data);  
        }
      }
        // NSF Need something to catch startup when SWG=0 so we set it to enabeled.
        // when other ways/protocols to detect SWG=0 are turned off.
    }

    if ((msg_loop & MSG_LOOP_POOL_TEMP) != MSG_LOOP_POOL_TEMP && aq_data->pool_temp != TEMP_UNKNOWN ) {
      aq_data->pool_temp = TEMP_UNKNOWN;
    }
    if ((msg_loop & MSG_LOOP_SPA_TEMP) != MSG_LOOP_SPA_TEMP && aq_data->spa_temp != TEMP_UNKNOWN ) {
      aq_data->spa_temp = TEMP_UNKNOWN;
    }

    /*
    //  AQUAPURE=0 we never get that message on ALLBUTTON so don't turn off unless filter pump if off
    if ((msg_loop & MSG_SWG) != MSG_SWG && aq_data->aqbuttons[PUMP_INDEX].led->state == OFF ) {
       //aq_data->ar_swg_status = SWG_STATUS_OFF;
       setSWGoff(aq_data);
    }
    */
    if ((msg_loop & MSG_BOOST) != MSG_BOOST) {
      if (aq_data->boost == true || boostInLastLoop == true) {
        LOG(ALLB_LOG,LOG_INFO, "Boost turned off\n");
        event_happened_set_device_state(AQS_BOOST_OFF, aq_data);
        // Add code to check Pump if to turn it on (was scheduled) ie time now is inbetween ON / OFF schedule
      }
      aq_data->boost = false;
      aq_data->boost_msg[0] = '\0';
      aq_data->boost_duration = 0;
      boostInLastLoop = false;
      //if (aq_data->swg_percent >= 101)
      //  aq_data->swg_percent = 0;
    }

    if ((msg_loop & MSG_BATTERY_LOW) != MSG_BATTERY_LOW)
      aq_data->battery = OK;


    //if ( _aqconfig_.rs_panel_size >= 16) {
    //if ( (int)PANEL_SIZE >= 16) { // NSF No idea why this fails on RS-4, but it does.  Come back and find out why
    if ( PANEL_SIZE() >= 16 ) {
      //printf("Panel size %d What the fuck am I doing here\n",PANEL_SIZE());
      if ((msg_loop & MSG_RS13BUTTON) != MSG_RS13BUTTON)
        aq_data->aqbuttons[13].led->state = OFF;
      if ((msg_loop & MSG_RS14BUTTON) != MSG_RS14BUTTON)
        aq_data->aqbuttons[14].led->state = OFF;
      if ((msg_loop & MSG_RS15BUTTON) != MSG_RS15BUTTON)
        aq_data->aqbuttons[15].led->state = OFF;
      if ((msg_loop & MSG_RS16BUTTON) != MSG_RS16BUTTON)
        aq_data->aqbuttons[16].led->state = OFF;
    }

    msg_loop = 0;
    return;
  }

  if (stristr(msg, LNG_MSG_BATTERY_LOW) != NULL)
  {
    aq_data->battery = LOW;
    msg_loop |= MSG_BATTERY_LOW;
    strcpy(aq_data->last_display_message, msg); // Also display the message on web UI
  }
  else if (stristr(msg, LNG_MSG_POOL_TEMP_SET) != NULL)
  {
    //LOG(ALLB_LOG,LOG_DEBUG, "**************** pool htr long message: %s", &message[20]);
    aq_data->pool_htr_set_point = atoi(message + 20);

    if (aq_data->temp_units == UNKNOWN)
      setUnits(msg, aq_data);
  }
  else if (stristr(msg, LNG_MSG_SPA_TEMP_SET) != NULL)
  {
    //LOG(ALLB_LOG,LOG_DEBUG, "spa htr long message: %s", &message[19]);
    aq_data->spa_htr_set_point = atoi(message + 19);

    if (aq_data->temp_units == UNKNOWN)
      setUnits(msg, aq_data);
  }
  else if (stristr(msg, LNG_MSG_FREEZE_PROTECTION_SET) != NULL)
  {
    //LOG(ALLB_LOG,LOG_DEBUG, "frz protect long message: %s", &message[28]);
    aq_data->frz_protect_set_point = atoi(message + 28);
    aq_data->frz_protect_state = ENABLE;
    default_frz_protect_state = ENABLE;

    if (aq_data->temp_units == UNKNOWN)
      setUnits(msg, aq_data);
  }
  else if (strncasecmp(msg, MSG_AIR_TEMP, MSG_AIR_TEMP_LEN) == 0)
  {
    aq_data->air_temp = atoi(msg + MSG_AIR_TEMP_LEN);

    if (aq_data->temp_units == UNKNOWN)
      setUnits(msg, aq_data);
  }
  else if (strncasecmp(msg, MSG_POOL_TEMP, MSG_POOL_TEMP_LEN) == 0)
  {
    msg_loop |= MSG_LOOP_POOL_TEMP;
    aq_data->pool_temp = atoi(msg + MSG_POOL_TEMP_LEN);

    if (aq_data->temp_units == UNKNOWN)
      setUnits(msg, aq_data);
  }
  else if (strncasecmp(msg, MSG_SPA_TEMP, MSG_SPA_TEMP_LEN) == 0)
  {
    msg_loop |= MSG_LOOP_SPA_TEMP;
    aq_data->spa_temp = atoi(msg + MSG_SPA_TEMP_LEN);

    if (aq_data->temp_units == UNKNOWN)
      setUnits(msg, aq_data);
  }
  // NSF If get water temp rather than pool or spa in some cases, then we are in Pool OR Spa ONLY mode
  else if (strncasecmp(msg, MSG_WATER_TEMP, MSG_WATER_TEMP_LEN) == 0)
  {
    aq_data->pool_temp = atoi(msg + MSG_WATER_TEMP_LEN);
    aq_data->spa_temp = atoi(msg + MSG_WATER_TEMP_LEN);
    if (aq_data->temp_units == UNKNOWN)
      setUnits(msg, aq_data);

    if (isSINGLE_DEV_PANEL != true)
    {
      changePanelToMode_Only();
      LOG(ALLB_LOG,LOG_ERR, "AqualinkD set to 'Combo Pool & Spa' but detected 'Only Pool OR Spa' panel, please change config\n");
    }
  }
  else if (stristr(msg, LNG_MSG_WATER_TEMP1_SET) != NULL)
  {
    aq_data->pool_htr_set_point = atoi(message + 28);

    if (aq_data->temp_units == UNKNOWN)
      setUnits(msg, aq_data);

    if (isSINGLE_DEV_PANEL != true)
    {
      changePanelToMode_Only();
      LOG(ALLB_LOG,LOG_ERR, "AqualinkD set to 'Combo Pool & Spa' but detected 'Only Pool OR Spa' panel, please change config\n");
    }
  }
  else if (stristr(msg, LNG_MSG_WATER_TEMP2_SET) != NULL)
  {
    aq_data->spa_htr_set_point = atoi(message + 27);

    if (aq_data->temp_units == UNKNOWN)
      setUnits(msg, aq_data);

    if (isSINGLE_DEV_PANEL != true)
    {
      changePanelToMode_Only();
      LOG(ALLB_LOG,LOG_ERR, "AqualinkD set to 'Combo Pool & Spa' but detected 'Only Pool OR Spa' panel, please change config\n");
    }
  }
  else if (stristr(msg, LNG_MSG_SERVICE_ACTIVE) != NULL)
  {
    if (aq_data->service_mode_state == OFF)
      LOG(ALLB_LOG,LOG_NOTICE, "AqualinkD set to Service Mode\n");
    aq_data->service_mode_state = ON;
     msg_loop |= MSG_SERVICE;
    //service_msg_count = 0;
  }
  else if (stristr(msg, LNG_MSG_TIMEOUT_ACTIVE) != NULL)
  {
    if (aq_data->service_mode_state == OFF)
      LOG(ALLB_LOG,LOG_NOTICE, "AqualinkD set to Timeout Mode\n");
    aq_data->service_mode_state = FLASH;
     msg_loop |= MSG_TIMEOUT;
    //service_msg_count = 0;
  }
  else if (stristr(msg, LNG_MSG_FREEZE_PROTECTION_ACTIVATED) != NULL)
  {
    msg_loop |= MSG_FREEZE;
    //aq_data->frz_protect_state = default_frz_protect_state;
    aq_data->frz_protect_state = ON;
    //freeze_msg_count = 0;
    strcpy(aq_data->last_display_message, msg); // Also display the message on web UI
  }
  
  else if (ENABLE_CHILLER && (stristr(msg,"Chiller") != NULL || stristr(msg,"Heat Pump") != NULL)) {
    processHeatPumpDisplayMessage(msg, aq_data); // This doesn;t exist yet
  }
  
  /* // Not sure when to do with these for the moment, so no need to compile in the test.
  else if (stristr(msg, LNG_MSG_CHEM_FEED_ON) != NULL) {
  }
  else if (stristr(msg, LNG_MSG_CHEM_FEED_OFF) != NULL) {
  }
  */
  else if (msg[2] == '/' && msg[5] == '/' && msg[8] == ' ')
  { // date in format '08/29/16 MON'
    strcpy(aq_data->date, msg);
  }
  else if (stristr(msg, MSG_SWG_PCT) != NULL) 
  {
    if (strncasecmp(msg, MSG_SWG_PCT, MSG_SWG_PCT_LEN) == 0 && strncasecmp(msg, "AQUAPURE HRS", 12) != 0) {
      changeSWGpercent(aq_data, atoi(msg + MSG_SWG_PCT_LEN));
    } 
    else if (strncasecmp(msg, "AQUAPURE HRS", 12) != 0 && strncasecmp(msg, "SET AQUAPURE", 12) != 0) 
    {
      if (strcasestr(msg, MSG_SWG_NO_FLOW) != NULL)
        setSWGdeviceStatus(aq_data, ALLBUTTON, SWG_STATUS_NO_FLOW);
      else if (strcasestr(msg, MSG_SWG_LOW_SALT) != NULL)
        setSWGdeviceStatus(aq_data, ALLBUTTON, SWG_STATUS_LOW_SALT);
      else if (strcasestr(msg, MSG_SWG_HIGH_SALT) != NULL)
        setSWGdeviceStatus(aq_data, ALLBUTTON, SWG_STATUS_HI_SALT);
      else if (strcasestr(msg, MSG_SWG_FAULT) != NULL)
        setSWGdeviceStatus(aq_data, ALLBUTTON, SWG_STATUS_GENFAULT);
        //setSWGdeviceStatus(aq_data, ALLBUTTON, SWG_STATUS_CHECK_PCB);
      
      // Any of these messages want to display.
      strcpy(aq_data->last_display_message, msg);

      msg_loop |= MSG_SWG_DEVICE;
    }
    msg_loop |= MSG_SWG;
  }
  else if (strncasecmp(msg, MSG_SWG_PPM, MSG_SWG_PPM_LEN) == 0)
  {
    aq_data->swg_ppm = atoi(msg + MSG_SWG_PPM_LEN);
    msg_loop |= MSG_SWG;
  }
  else if ((msg[1] == ':' || msg[2] == ':') && msg[strlen(msg) - 1] == 'M')
  { // time in format '9:45 AM'
    strcpy(aq_data->time, msg);
    // Setting time takes a long time, so don't try until we have all other programmed data.
    if (_initWithRS == true && strlen(aq_data->date) > 1 && checkAqualinkTime() != true)
    {
      LOG(ALLB_LOG,LOG_NOTICE, "RS time is NOT accurate '%s %s', re-setting on controller!\n", aq_data->time, aq_data->date);
      aq_programmer(AQ_SET_TIME, NULL, aq_data);
    }
    else if (_initWithRS == false || _aqconfig_.sync_panel_time == false)
    {
      LOG(ALLB_LOG,LOG_DEBUG, "RS time '%s %s' not checking\n", aq_data->time, aq_data->date);
    }
    else if (_initWithRS == true)
    {
      LOG(ALLB_LOG,LOG_DEBUG, "RS time is accurate '%s %s'\n", aq_data->time, aq_data->date);
    }
    // If we get a time message before REV, the controller didn't see us as we started too quickly.
    /* Don't need to check this anymore with the check for probe before startup.
    if (_gotREV == false)
    {
      LOG(ALLB_LOG,LOG_NOTICE, "Getting control panel information\n", msg);
      aq_programmer(AQ_GET_DIAGNOSTICS_MODEL, NULL, aq_data);
      _gotREV = true; // Force it to true just incase we don't understand the model#
    }
    */
  }
  else if (strstr(msg, " REV ") != NULL || strstr(msg, " REV. ") != NULL)
  { // '8157 REV MMM'
    // A master firmware revision message.
    strcpy(aq_data->version, msg);
    rsm_get_revision(aq_data->revision, aq_data->version, strlen(aq_data->version));
    setPanelInformationFromPanelMsg(aq_data, msg, PANEL_CPU | PANEL_REV, ALLBUTTON);
    //setBoardCPURevision(aq_data, aq_data->version, strlen(aq_data->version), ALLB_LOG);
    //_gotREV = true;
    LOG(ALLB_LOG,LOG_DEBUG, "Control Panel version %s\n", aq_data->version);
    LOG(ALLB_LOG,LOG_DEBUG, "Control Panel revision %s\n", aq_data->revision);
    if (_initWithRS == false)
    {
      //LOG(ALLBUTTON,LOG_NOTICE, "Standard protocol initialization complete\n");
      queueGetProgramData(ALLBUTTON, aq_data);
      event_happened_set_device_state(AQS_POWER_ON, aq_data);
      //queueGetExtendedProgramData(ALLBUTTON, aq_data, _aqconfig_.use_panel_aux_labels);
      _initWithRS = true;
    }
  }
  else if (stristr(msg, " TURNS ON") != NULL)
  {
    LOG(ALLB_LOG,LOG_NOTICE, "Program data '%s'\n", msg);
  }
  else if (_aqconfig_.override_freeze_protect == TRUE && strncasecmp(msg, "Press Enter* to override Freeze Protection with", 47) == 0)
  {
    //send_cmd(KEY_ENTER, aq_data);
    //aq_programmer(AQ_SEND_CMD, (char *)KEY_ENTER, aq_data);
    aq_send_allb_cmd(KEY_ENTER);
  }
  // Process any button states (fake LED) for RS12 and above keypads
  // Text will be button label on or off  ie Aux_B2 off or WaterFall off
  

  //else if ( _aqconfig_.rs_panel_size >= 16 && (rs16 = RS16_endswithLEDstate(msg)) != 0 )
  else if (PANEL_SIZE() >= 16 && (rs16 = RS16_endswithLEDstate(msg, aq_data)) != 0 )
  {
    msg_loop |= rs16;
    // Do nothing, just stop other else if statments executing
    // make sure we also display the message.
    // Note we only get ON messages here, Off messages will not be sent if something else turned it off
    // use the Onetouch or iAqua equiptment page for off.
    strcpy(aq_data->last_display_message, msg);
  }

  else if (((msg[4] == ':') || (msg[6] == ':')) && (strncasecmp(msg, "AUX", 3) == 0) )
  { // Should probable check we are in programming mode.
    // 'Aux3: No Label'
    // 'Aux B1: No Label'
    int labelid;
    int ni = 3;
    if (msg[4] == 'B') { ni = 5; }
    labelid = atoi(msg + ni);
    if (labelid > 0 && _aqconfig_.use_panel_aux_labels == true)
    {
      if (ni == 5)
        labelid = labelid + 8;
      else
        labelid = labelid + 1;
      // Aux1: on panel = Button 3 in aqualinkd  (button 2 in array)
      if (strncasecmp(msg+ni+3, "No Label", 8) != 0) {
        aq_data->aqbuttons[labelid].label = prittyString(cleanalloc(msg+ni+2));
        LOG(ALLB_LOG,LOG_NOTICE, "AUX ID %s label set to '%s'\n", aq_data->aqbuttons[labelid].name, aq_data->aqbuttons[labelid].label);
      } else {
        LOG(ALLB_LOG,LOG_NOTICE, "AUX ID %s has no control panel label using '%s'\n", aq_data->aqbuttons[labelid].name, aq_data->aqbuttons[labelid].label);
      }
      //aq_data->aqbuttons[labelid + 1].label = cleanalloc(msg + 5);
    }
  }
  // BOOST POOL 23:59 REMAINING
  else if ( (strncasecmp(msg, "BOOST POOL", 10) == 0) && (strcasestr(msg, "REMAINING") != NULL) ) {
    // Ignore messages if in programming mode.  We get one of these turning off for some strange reason.
    if (in_programming_mode(aq_data) == false) {

      if (aq_data->boost == false || boostInLastLoop == false) {
        event_happened_set_device_state(AQS_BOOST_ON, aq_data);
      }

      snprintf(aq_data->boost_msg, 6, "%s", &msg[11]);
      aq_data->boost_duration = rsm_HHMM2min(aq_data->boost_msg);
      aq_data->boost = true;
      msg_loop |= MSG_BOOST;
      msg_loop |= MSG_SWG;
      boostInLastLoop = true;
      //convert_boost_to_duration(aq_data->boost_msg)
      //if (aq_data->ar_swg_status != SWG_STATUS_ON) {aq_data->ar_swg_status = SWG_STATUS_ON;}
      if (aq_data->swg_percent != 101) {changeSWGpercent(aq_data, 101);}
      //boost_msg_count = 0;
    //if (aq_data->active_thread.thread_id == 0)
      strcpy(aq_data->last_display_message, msg); // Also display the message on web UI if not in programming mode
    }
  }
  else
  {
    LOG(ALLB_LOG,LOG_DEBUG_SERIAL, "Ignoring '%s'\n", msg);
    //aq_data->display_message = msg;
    //if (in_programming_mode(aq_data) == false && aq_data->simulate_panel == false &&
    if (in_programming_mode(aq_data) == false &&
        stristr(msg, "JANDY AquaLinkRS") == NULL &&
        //stristr(msg, "PUMP O") == NULL  &&// Catch 'PUMP ON' and 'PUMP OFF' but not 'PUMP WILL TURN ON'
        strncasecmp(msg, "PUMP O", 6) != 0  &&// Catch 'PUMP ON' and 'PUMP OFF' but not 'PUMP WILL TURN ON'
        stristr(msg, "MAINTAIN") == NULL && // Catch 'MAINTAIN TEMP IS OFF'
        stristr(msg, "0 PSI") == NULL /* // Catch some erronious message on test harness
        stristr(msg, "CLEANER O") == NULL &&
        stristr(msg, "SPA O") == NULL &&
        stristr(msg, "AUX") == NULL*/
    )
    { // Catch all AUX1 AUX5 messages
      //aq_data->display_last_message = true;
      strcpy(aq_data->last_display_message, msg);
      //rsm_strncpy(aq_data->last_display_message, (unsigned char *)msg, AQ_MSGLONGLEN, AQ_MSGLONGLEN);
    }
  }

  // Send every message if we are in simulate panel mode
  //if (aq_data->simulate_panel)
  //  strcpy(aq_data->last_display_message, msg);
    //rsm_strncpy(aq_data->last_display_message, (unsigned char *)msg, AQ_MSGLONGLEN, AQ_MSGLONGLEN);
    //ascii(aq_data->last_display_message, msg);


  //LOG(ALLB_LOG,LOG_INFO, "RS Message loop :- '%d'\n", msg_loop);

  // We processed the next message, kick any threads waiting on the message.
//printf ("Message kicking\n");


  kick_aq_program_thread(aq_data, ALLBUTTON);
}

bool process_allbutton_packet(unsigned char *packet, int length, struct aqualinkdata *aq_data)
{
  bool rtn = false;
  //static unsigned char last_packet[AQ_MAXPKTLEN];
  static unsigned char last_checksum;
  static char message[AQ_MSGLONGLEN + 1];
  static int processing_long_msg = 0;

  // Check packet against last check if different.
  // Should only use the checksum, not whole packet since it's status messages.
  /*
  if ( packet[PKT_CMD] == CMD_STATUS && (memcmp(packet, last_packet, length) == 0))
  {
    LOG(ALLB_LOG,LOG_DEBUG_SERIAL, "RS Received duplicate, ignoring.\n", length);
    return rtn;
  }
  else
  {
    memcpy(last_packet, packet, length);
   aq_data->last_packet_type = packet[PKT_CMD];
    rtn = true;
  }
  */

  aq_data->last_packet_type = packet[PKT_CMD];


  if ( packet[PKT_CMD] == CMD_STATUS && packet[length-3] == last_checksum && ! in_programming_mode(aq_data) )
  {
    LOG(ALLB_LOG,LOG_DEBUG_SERIAL, "RS Received duplicate, ignoring.\n", length);
    return false;
  }
  else
  {
    last_checksum = packet[length-3];
    rtn = true;
  }

  if (processing_long_msg > 0 && packet[PKT_CMD] != CMD_MSG_LONG)
  {
    processing_long_msg = 0;
    //LOG(ALLB_LOG,LOG_ERR, "RS failed to receive complete long message, received '%s'\n",message);
    //LOG(ALLB_LOG,LOG_DEBUG, "RS didn't finished receiving of MSG_LONG '%s'\n",message);
    processMessage(message, aq_data);
  }

  LOG(ALLB_LOG,LOG_DEBUG_SERIAL, "RS Received packet type 0x%02hhx length %d.\n", packet[PKT_CMD], length);

  switch (packet[PKT_CMD])
  {
  case CMD_ACK:
    //LOG(ALLB_LOG,LOG_DEBUG_SERIAL, "RS Received ACK length %d.\n", length);
    break;
  case CMD_STATUS:
    //LOG(ALLB_LOG,LOG_DEBUG, "RS Received STATUS length %d.\n", length);
    //debuglogPacket(ALLB_LOG, packet, length, true, true);
    
    //memcpy(aq_data->raw_status, packet + 4, AQ_PSTLEN);
    //processLEDstate(aq_data);
    processLEDstate(aq_data, packet, ALLB_LOG);

    /* NSF Take this out, and use the ALLButton loop cycle to determin if we get spa/pool temp
       messages.  Works better for dual equiptment when both pool & spa pumps and dual temp sensors */
    /*
    if (aq_data->aqbuttons[PUMP_INDEX].led->state == OFF)
    {
     aq_data->pool_temp = TEMP_UNKNOWN;
     aq_data->spa_temp = TEMP_UNKNOWN;
      //aq_data->spa_temp = _aqconfig_.report_zero_spa_temp?-18:TEMP_UNKNOWN;
    }
    else if (aq_data->aqbuttons[SPA_INDEX].led->state == OFF && isSINGLE_DEV_PANEL != true)
    {
      //aq_data->spa_temp = _aqconfig_.report_zero_spa_temp?-18:TEMP_UNKNOWN;
     aq_data->spa_temp = TEMP_UNKNOWN;
    }
    else if (aq_data->aqbuttons[SPA_INDEX].led->state == ON && isSINGLE_DEV_PANEL != true)
    {
     aq_data->pool_temp = TEMP_UNKNOWN;
    }
    */

    // COLOR MODE programming relies on state changes, so let any threads know
    //if (aq_data->active_thread.ptype == AQ_SET_LIGHTPROGRAM_MODE) {
    if ( in_light_programming_mode(aq_data) ) {
      kick_aq_program_thread(aq_data, ALLBUTTON);
    }
    break;
  case CMD_MSG:
  case CMD_MSG_LONG:
     {
       int index = packet[PKT_DATA]; // Will get 0x00 for complete message, 0x01 for start on long message 0x05 last of long message
       //printf("RSM received message at index %d '%.*s'\n",index,AQ_MSGLEN,(char *)packet + PKT_DATA + 1);
       if (index <= 1){
         memset(message, 0, AQ_MSGLONGLEN + 1);
         //strncpy(message, (char *)packet + PKT_DATA + 1, AQ_MSGLEN);
         rsm_strncpy(message, packet + PKT_DATA + 1, AQ_MSGLONGLEN, AQ_MSGLEN);
         processing_long_msg = index;
         //LOG(ALLB_LOG,LOG_ERR, "Message %s\n",message);
       } else {
         //strncpy(&message[(processing_long_msg * AQ_MSGLEN)], (char *)packet + PKT_DATA + 1, AQ_MSGLEN);
         //rsm_strncpy(&message[(processing_long_msg * AQ_MSGLEN)], (unsigned char *)packet + PKT_DATA + 1, AQ_MSGLONGLEN, AQ_MSGLEN);
         rsm_strncpy(&message[( (index-1) * AQ_MSGLEN)], (unsigned char *)packet + PKT_DATA + 1, AQ_MSGLONGLEN, AQ_MSGLEN);
         //LOG(ALLB_LOG,LOG_ERR, "Long Message %s\n",message);
         if (++processing_long_msg != index) {
           LOG(ALLB_LOG,LOG_DEBUG, "Long message index %d doesn't match buffer %d\n",index,processing_long_msg);
           //printf("RSM Long message index %d doesn't match buffer %d\n",index,processing_long_msg);
         }
         #ifdef  PROCESS_INCOMPLETE_MESSAGES
           kick_aq_program_thread(aq_data, ALLBUTTON);
         #endif
       }

       if (index == 0 || index == 5) {
         //printf("RSM process message '%s'\n",message);
         
         // MOVED FROM LINE 701 see if less errors
         //kick_aq_program_thread(aq_data, ALLBUTTON);

         LOG(ALLB_LOG,LOG_DEBUG, "Processing Message - '%s'\n",message);
         processMessage(message, aq_data); // This will kick thread
       }
       
     }
    break;
  case CMD_PROBE:
    LOG(ALLB_LOG,LOG_DEBUG, "RS Received PROBE length %d.\n", length);
    //LOG(ALLB_LOG,LOG_INFO, "Synch'ing with Aqualink master device...\n");
    rtn = false;
    break;
  case CMD_MSG_LOOP_ST:
    LOG(ALLB_LOG,LOG_INFO, "RS Received message loop start\n");
    processMessageReset(aq_data);
    rtn = false;
    break;
  default:
    LOG(ALLB_LOG,LOG_INFO, "RS Received unknown packet, 0x%02hhx\n", packet[PKT_CMD]);
    rtn = false;
    break;
  }

  return rtn;
}