/*
  xdrv_85_esp32_ble_eq3_trv.ino - EQ3 radiator valve sense and control via BLE_ESP32 support for Tasmota

  Copyright (C) 2020  Simon Hailes

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  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.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

  --------------------------------------------------------------------------------------------
  Version yyyymmdd  Action    Description
  --------------------------------------------------------------------------------------------
  1.0.0.0 20210910  publish - renamed to xdrv_85, and checked with TAS latest dev branch
  0.0.0.0 20201213  created - initial version
*/


/*

Commands:
e.g.
trv 001A22092EE0 settemp 22.5

trvperiod n - set polling period in seconds (default teleperiod at boot)
trvonlyaliased *0/1 - only hear devices with BLEAlias set
trvMatchPrefix 0/*1 - if set, then it will add trvs to the seen list which have mac starting with :
  macs in macprefixes, currently only 001a22
Note: anything with BLEAlias starting "EQ3" will be added to the seen list.
trvHideFailedPoll 0/*1 - if set, then failed polls will not be sent to EQ3
trvMinRSSI -n - the minimum RSSI value at which to attempt to poll


trv reset - clear device list
trv devlist - report seen devices.  Active scanning required, not passive, as it looks for names
trv scan - same as devlist
trv <mac> state - report general state (see below for MQTT)
trv <mac> raw <hex to send> - send a raw command
trv <mac> on - set temp to 30 -> display ON on EQ3
trv <mac> off - set temp to 4.5 -> display OFF on EQ3
trv <mac> boost - set boost
trv <mac> unboost - turn off boost
trv <mac> lock - manual lock of physical buttons
trv <mac> unlock - manual unlock of physical buttons
trv <mac> auto - set EQ3 to auto mode
trv <mac> manual - set EQ3 to manual mode
trv <mac> mode auto|manual - set EQ3 to mode auto|manual?
trv <mac> day - set EQ3 to day temp
trv <mac> night - set EQ3 to night temp
trv <mac> settemp 20.5 - set EQ3 to temp
trv <mac> settime - set time to Tasmota time (untested)
trv <mac> settime <hex as per esp32_mqtt_eq3> - set time
trv <mac> offset 1.5 - set offset temp
trv <mac> setdaynight 22 17.5 - set day and night mode temps
trv <mac> setwindowtempdur 12.5 30 - set window open temp and duration in mins

trv <mac> reqprofile <0-6> - request a profile for a day fo the week.
trv <mac> setprofile <0-6> 20.5-07:30,17-17:00,22.5-22:00,17-24:00 (up to 7 temp-HH:MM) - set a profile for a day fo the week.

Responses:
normal:
stat/EQ3/001A22092C9A = {
  "cmd":"state",
  "result":"ok",
  "RSSI":-83,
  "stattime":1613814193,
  "temp":21.0,
  "posn":0,
  "mode":"auto",
  "boost":"inactive",
  "dst":"set",
  "window":"closed",
  "state":"unlocked",
  "battery":"GOOD",
  "windowtemp": 12.0,
  "windowdur": 15,
  "day": 21.0,
  "night": 17.0,
  "offset": 0.0
}

holiday:
as above, but adds ,"holidayend":"YY-MM-DD HH:MM"

when trv <mac> reqprofile is used, adds:
  "profiledayN":"20.5-07:30,17.0-17:00,22.5-22:00,17.0-24:00"
where N is the day (0-6) (0 = saturday?).

when trv <mac> setprofile is used, adds:
"profiledayset":N
where N is the day (0-6) (0 = saturday?).

on error:
  "result":"fail",

The driver will try a command three times before reporting


4 digit pin calculation: (just for info)
serialno = "REQ0123456"
pin = []

x = str((ord(serialno[3]) ^ ord(serialno[7])) % 10)
pin.append(x)
x = str((ord(serialno[4]) ^ ord(serialno[8])) % 10)
pin.append(x)
x = str((ord(serialno[5]) ^ ord(serialno[9])) % 10)
pin.append(x)
x = str((ord(serialno[0]) - ord('A') ^ ord(serialno[6]) - ord('0')) % 10)
pin.append(x)
print("".join(pin))

*/




//#define VSCODE_DEV

#ifdef VSCODE_DEV
#define ESP32
#define USE_BLE_ESP32
#define USE_EQ3_ESP32
#endif

// for testing of BLE_ESP32, we remove xsns_62_MI_ESP32.ino completely, and instead add this modified xsns_52_ibeacon_BLE_ESP32.ino
#if CONFIG_IDF_TARGET_ESP32
#ifdef USE_EQ3_ESP32
#ifdef ESP32                       // ESP32 only. Use define USE_HM10 for ESP8266 support
#ifdef USE_BLE_ESP32

#define XDRV_85                    85
#define D_CMND_EQ3 "trv"

// uncomment for more debug messages
//#define EQ3_DEBUG

namespace EQ3_ESP32 {

void CmndTrv(void);
void CmndTrvPeriod(void);
void CmndTrvOnlyAliased(void);
void CmndTrvMatchPrefix(void);
void CmndTrvMinRSSI(void);
void CmndTrvHideFailedPoll(void);

const char kEQ3_Commands[] PROGMEM = D_CMND_EQ3"|"
  "|"
  "period|"
  "onlyaliased|"
  "MatchPrefix|"
  "MinRSSI|"
  "HideFailedPoll";

void (*const EQ3_Commands[])(void) PROGMEM = {
  &CmndTrv,
  &CmndTrvPeriod,
  &CmndTrvOnlyAliased,
  &CmndTrvMatchPrefix,
  &CmndTrvMinRSSI,
  &CmndTrvHideFailedPoll
};


const char *cmdnames[] = {
  "poll",
  "raw",
  "state",
  "settime",
  "settemp",
  "offset",
  "setdaynight",
  "setwindowtempdur",
  "setholiday",
  "boost",
  "unboost",
  "unlock",
  "auto",
  "manual",
  "eco",
  "on",
  "off",
  "valve",
  "mode",
  "day",
  "night",
  "reqprofile",
  "setprofile",
  "lock",
};

const uint8_t *macprefixes[1] = {
  (uint8_t *)"\x00\x1a\x22"
};

int EQ3GenericOpCompleteFn(BLE_ESP32::generic_sensor_t *pStruct);

const char EQ3_Svc[] PROGMEM = "3e135142-654f-9090-134a-a6ff5bb77046";
const char EQ3_rw_Char[] PROGMEM = "3fa4585a-ce4a-3bad-db4b-b8df8179ea09";
const char EQ3_notify_Char[] PROGMEM = "d0e8434d-cd29-0996-af41-6c90f4e0eb2a";

struct eq3_device_tag{
  uint8_t addr[7];
  int8_t RSSI;
  uint64_t timeoutTime;
  uint8_t pairing;
  uint8_t lastStatus[16]; // last received 02 stat
  uint8_t lastStatusLen;
  uint32_t lastStatusTime; // in utc
  uint8_t nextDiscoveryData;
} eq3_device_t;

/*********************************************************************************************\
 * variables to control operation
\*********************************************************************************************/
int retries = 0;
// allow 240s before timeout of sa device - based on that we restart BLE if we don't see adverts for 120s
#define EQ3_TIMEOUT 240L

uint8_t pairingaddr[7] = {0,0,0,0,0,0};
char pairingserial[20];
uint8_t pairing = 0;

#define EQ3_NUM_DEVICESLOTS 16
eq3_device_tag EQ3Devices[EQ3_NUM_DEVICESLOTS];
SemaphoreHandle_t EQ3mutex = nullptr;

int EQ3Period = 300;
uint8_t EQ3OnlyAliased = 0;
uint8_t EQ3MatchPrefix = 1;
uint8_t opInProgress = 0;
int seconds = 20;
int EQ3CurrentSingleSlot = 0;

uint8_t EQ3TopicStyle = 1;
uint8_t EQ3HideFailedPoll = 1;
int8_t trvMinRSSI = -99;

// control of timing of sending polling.
// we leave an interval between polls to allow scans to take place
int intervalSeconds = 10; // min seconds between operations
int intervalSecondsCounter = 0; // set when an operation is over to intervalSeconds
int nextEQ3Poll = EQ3_NUM_DEVICESLOTS; // set to zero to start a poll cycle

#pragma pack( push, 1 )  // aligned structures for size
struct op_t {
  uint8_t addr[7];
  uint8_t towrite[16];
  uint8_t writelen;
  uint8_t cmdtype;
};
#pragma pack(pop)

std::deque<EQ3_ESP32::op_t*> opQueue;


/*********************************************************************************************\
 * Functions
\*********************************************************************************************/

const char *addrStr(const uint8_t *addr, int useAlias = 0){
  static char addrstr[32];

  const char *id = nullptr;
  if (useAlias){
    id = BLE_ESP32::getAlias(addr);
  }
  if (!id || !(*id)){
    id = addrstr;
    BLE_ESP32::dump(addrstr, 13, addr, 6);
  } else {
  }

  return id;
}

char *topicPrefix(int prefix, const uint8_t *addr, int useAlias){
  static char stopic[TOPSZ];
  const char *id = addrStr(addr, useAlias);
  if (!EQ3TopicStyle){
    GetTopic_P(stopic, prefix, TasmotaGlobal.mqtt_topic, PSTR(""));
    strcat(stopic, PSTR("EQ3/"));
    strcat(stopic, id);
  } else {
    char p[] = "EQ3";
    GetTopic_P(stopic, prefix, p, id);
  }
  return stopic;
}



// return 0+ if we find the addr has one of our listed prefixes
// return -1 if we don't recognise the mac
int matchPrefix(const uint8_t *addr){
  for (int i = 0; i < sizeof(macprefixes)/sizeof(*macprefixes); i++){
    if (!memcmp(addr, macprefixes[i], 3)){
      return i;
    }
  }
  return -1;
}


bool EQ3Operation(const uint8_t *MAC, const uint8_t *data, int datalen, int cmdtype, int retries_in = 0) {
  BLE_ESP32::generic_sensor_t *op = nullptr;

  // ALWAYS use this function to create a new one.
  int res = BLE_ESP32::newOperation(&op);
  if (!res){
    AddLog(LOG_LEVEL_ERROR,PSTR("EQ3 %s:Can't get a newOperation from BLE"), addrStr(MAC, cmdtype & 0x80));
    retries = 0;
    return 0;
  } else {
#ifdef EQ3_DEBUG
    AddLog(LOG_LEVEL_DEBUG,PSTR("EQ3 %s:got a newOperation from BLE"), addrStr(MAC, cmdtype & 0x80));
#endif
  }

  NimBLEAddress addr((uint8_t *)MAC);
  op->addr = addr;

  bool havechar = false;
  op->serviceUUID = NimBLEUUID(EQ3_Svc);
  op->characteristicUUID = NimBLEUUID(EQ3_rw_Char);
  op->notificationCharacteristicUUID = NimBLEUUID(EQ3_notify_Char);

  if (data && datalen) {
    op->writelen = datalen;
    memcpy(op->dataToWrite, data, datalen);
  } else {
    op->writelen = 1;
    op->dataToWrite[0] = 0x03; // just request status
  }

  // this op will call us back on complete or failure.
  op->completecallback = (void *)EQ3GenericOpCompleteFn;
  // store this away for later
  op->context = (void *)cmdtype;

  res = BLE_ESP32::extQueueOperation(&op);
  if (!res){
    // if it fails to add to the queue, do please delete it
    BLE_ESP32::freeOperation(&op);
    AddLog(LOG_LEVEL_ERROR,PSTR("EQ3 %s:Failed to queue new operation - deleted"), addrStr(MAC, cmdtype & 0x80));
    retries = 0;
  } else {
    if (retries_in){
      retries = retries_in;
    }
  }

  return res;
}

int EQ3DoOp(){
  if (!opInProgress){
    if (opQueue.size()){
      op_t* op = opQueue[0];
      if (EQ3Operation(op->addr, op->towrite, op->writelen, op->cmdtype, 4)){
        opQueue.pop_front();
        opInProgress = 1;
        AddLog(LOG_LEVEL_DEBUG, PSTR("EQ3 %s:Op dequeued len now %d"), addrStr(op->addr, (op->cmdtype & 0x80)), opQueue.size());
        delete op;
        return 1;
      } else {
        AddLog(LOG_LEVEL_ERROR, PSTR("EQ3 %s:Op BLE could not start op queue len %d"), addrStr(op->addr, (op->cmdtype & 0x80)), opQueue.size());
      }
    }
  }
  return 0;
}

int EQ3QueueOp(const uint8_t *MAC, const uint8_t *data, int datalen, int cmdtype, int useAlias) {
  op_t* newop = new op_t;
  memcpy(newop->addr, MAC, 6);
  memcpy(newop->towrite, data, datalen);
  newop->writelen = datalen;
  newop->cmdtype = cmdtype | (useAlias?0x80:0);
  opQueue.push_back(newop);
  int qlen = opQueue.size();
  AddLog(LOG_LEVEL_DEBUG, PSTR("EQ3 %s: Op queued len now %d"), addrStr(newop->addr, (newop->cmdtype & 0x80)), qlen);
  EQ3DoOp();
  return qlen;
}

int EQ3ParseOp(BLE_ESP32::generic_sensor_t *op, bool success, int retries){
  int res = 0;
  opInProgress = 0;
  ResponseClear();

  uint8_t addrev[7];
  const uint8_t *native = op->addr.getNative();
  memcpy(addrev, native, 6);
  BLE_ESP32::ReverseMAC(addrev);

  eq3_device_tag *eq3 = nullptr;

  int free = -1;
  for (int i = 0; i < EQ3_NUM_DEVICESLOTS; i++){
    if (!memcmp(EQ3Devices[i].addr, addrev, 6)){
      eq3 = &EQ3Devices[i];
      break;
    }
  }

  int cmdtype = (((uint32_t)op->context) & 0xff);
  const char *cmdType = PSTR("invalid");
  int useAlias = cmdtype & 0x80;
  cmdtype &= 0x7f;
  if ((cmdtype >= 0) && (cmdtype < sizeof(cmdnames)/sizeof(*cmdnames))){
    cmdType = cmdnames[cmdtype];
  }

  ResponseAppend_P(PSTR("{"));
  ResponseAppend_P(PSTR("\"cmd\":\"%s\""), cmdType);
  ResponseAppend_P(PSTR(",\"result\":\"%s\""), success? "ok":"fail");
  ResponseAppend_P(PSTR(",\"MAC\":\"%s\""), addrStr(addrev));
  const char *host = NetworkHostname();
  ResponseAppend_P(PSTR(",\"tas\":\"%s\""), host);
  if (cmdtype == 1){
    char raw[40];
    BLE_ESP32::dump(raw, 40, op->dataNotify, op->notifylen);
    ResponseAppend_P(PSTR(",\"raw\":\"%s\""), raw);
  }

  uint8_t *status = {0};
  uint8_t statlen = 0;
  uint32_t stattime = 0;

  if (success){
    if ((op->notifylen >= 6) && (op->dataNotify[0] == 2) && (op->dataNotify[1] == 1)){
      if (eq3){
        memcpy(eq3->lastStatus, op->dataNotify, (op->notifylen <= 10)?op->notifylen:16);
        eq3->lastStatusLen = (op->notifylen <= 10)?op->notifylen:16;
        eq3->lastStatusTime = UtcTime();
      }
    }

    status = op->dataNotify;
    statlen = op->notifylen;
    stattime = UtcTime();
  }

  if (eq3){
    status = eq3->lastStatus;
    statlen = eq3->lastStatusLen;
    stattime = eq3->lastStatusTime;
    ResponseAppend_P(PSTR(",\"RSSI\":%d"), eq3->RSSI);
  }

  if ((statlen >= 6) && (status[0] == 2) && (status[1] == 1)){
    ResponseAppend_P(PSTR(",\"stattime\":%u"), stattime);
    ResponseAppend_P(PSTR(",\"temp\":%2.1f"), ((float)status[5])/2);
    ResponseAppend_P(PSTR(",\"posn\":%d"), status[3]);
    int stat = status[2];
    ResponseAppend_P(PSTR(",\"mode\":"));
    switch (stat & 3){
      case 0:
        ResponseAppend_P(PSTR("\"auto\""));
        break;
      case 1:
        ResponseAppend_P(PSTR("\"manual\""));
        break;
      case 2:
        ResponseAppend_P(PSTR("\"holiday\""));
        break;
      case 3:
        ResponseAppend_P(PSTR("\"manualholiday\""));
        break;
    }

    ResponseAppend_P(PSTR(",\"hassmode\":"));
    do {
      //0201283B042A
      // its in auto
      if ((stat & 3) == 0) { ResponseAppend_P(PSTR("\"auto\"")); break; }
      // it's set to 'OFF'
      if (((stat & 3) == 1) && (status[5] == 9)) { ResponseAppend_P(PSTR("\"off\"")); break; }
      // it's actively heating (valve open)
      if (((stat & 3) == 1) && (status[5] > 9) && (status[3] > 0)) { ResponseAppend_P(PSTR("\"heat\"")); break; }
      // it's achieved temp (valve closed)
      if (((stat & 3) == 1) && (status[5] > 9)) { ResponseAppend_P(PSTR("\"idle\"")); break; }
      ResponseAppend_P(PSTR("\"idle\""));
      break;
    } while (0);

    ResponseAppend_P(PSTR(",\"boost\":\"%s\""), (stat & 4)?"active":"inactive");
    ResponseAppend_P(PSTR(",\"dst\":\"%s\""), (stat & 8)?"set":"unset");
    ResponseAppend_P(PSTR(",\"window\":\"%s\""), (stat & 16)?"open":"closed");
    ResponseAppend_P(PSTR(",\"state\":\"%s\""), (stat & 32)?"locked":"unlocked");
    ResponseAppend_P(PSTR(",\"battery\":\"%s\""), (stat & 128)?"LOW":"GOOD");
  }

  if ((statlen >= 10) && (status[0] == 2) && (status[1] == 1)){
    int mm = status[8] * 30;
    int hh = mm/60;
    mm = mm % 60;
    ResponseAppend_P(PSTR(",\"holidayend\":\"%02d-%02d-%02d %02d:%02d\""),
      status[7],
      status[9],
      status[6],
      hh, mm
      );

    if (statlen >= 15) {
      ResponseAppend_P(PSTR(",\"windowtemp\":%2.1f"), ((float)status[10])/2);
      ResponseAppend_P(PSTR(",\"windowdur\":%d"), ((int)status[11])*5);
      ResponseAppend_P(PSTR(",\"day\":%2.1f"), ((float)status[12])/2);
      ResponseAppend_P(PSTR(",\"night\":%2.1f"), ((float)status[13])/2);
      ResponseAppend_P(PSTR(",\"offset\":%2.1f"), ((float)status[14]-7) /2);
    }

  }

  if (success) {
    // now to parse other data - this may not have been a stat message
    if ((op->notifylen >= 3) && (op->dataNotify[0] == 2) && (op->dataNotify[1] == 2)){
      ResponseAppend_P(PSTR(",\"profiledayset\":%d"), op->dataNotify[2]);
    }

    if ((op->notifylen >= 16) && (op->dataNotify[0] == 0x21)){
//YY is the time, coded as (minutes/10), up to which to maintain the temperature declared in XX
//XX represents the temperature to be maintained until then, codified as (temperature*2)
// byte 0: 21 (default value)
// byte 1: 02 (Monday = 0x02)
// byte (2,3): 22 24 (17°C up to 06:00)
// byte (4,5): 2A 36 (21°C up to 09:00)
// byte (6,7): 22 66 (17°C up to 17:00)
// byte (8,9): 2A 8A (21°C up to 23:00)
// byte (10,11): 22 90 (17°C up to 24:00)
// byte (12,13): 22 90 (unused)
// byte (14,15): 22 90 (unused)
      ResponseAppend_P(PSTR(",\"profileday%d\":\""), op->dataNotify[1]);
      uint8_t *data = op->dataNotify + 2;
      for (int i = 0; i < 7; i++){
        float t = *(data++);
        t /= 2;
        int mm = *(data++);
        mm *= 10;
        int hh = mm / 60;
        mm = mm % 60;
        ResponseAppend_P(PSTR("%2.1f-%02d:%02d"), t, hh, mm);
        // stop if the last one is 24.
        if (hh == 24){
          break;
        }

        if (i < 6){
          ResponseAppend_P(PSTR(","));
        }
      }
      ResponseAppend_P(PSTR("\""));
    }

    res = 1;
  }

  ResponseAppend_P(PSTR("}"));

  int type = STAT;
  if (cmdtype){
    type = STAT;
  } else {
    // it IS a poll command
    if (EQ3HideFailedPoll){
      if (!success){
        AddLog(LOG_LEVEL_DEBUG, PSTR("EQ3 %s poll fail not sent because EQ3HideFailedPoll"), addrStr(addrev));
        return res;
      }
    }
  }

  char *topic = topicPrefix(type, addrev, useAlias);
  MqttPublish(topic, false);
  return res;
}

int EQ3GenericOpCompleteFn(BLE_ESP32::generic_sensor_t *op){
  uint32_t context = (uint32_t) op->context;
  opInProgress = 0;

  if (op->state <= GEN_STATE_FAILED){
    uint8_t addrev[7];
    const uint8_t *native = op->addr.getNative();
    memcpy(addrev, native, 6);
    BLE_ESP32::ReverseMAC(addrev);

    if (retries > 1){
      retries--;

      if (EQ3Operation(addrev, op->dataToWrite, op->writelen, (int)op->context)){
        //EQ3ParseOp(op, false, retries);
        AddLog(LOG_LEVEL_ERROR,PSTR("EQ3 %s: trv operation failed - retrying %d"), addrStr(addrev), op->state);
        opInProgress = 1;
      } else {
        retries = 0;
        EQ3ParseOp(op, false, 0);
        AddLog(LOG_LEVEL_ERROR,PSTR("EQ3 %s: trv operation failed to send op %d"), addrStr(addrev), op->state);
      }
    } else {
      retries = 0;
      EQ3ParseOp(op, false, 0);
      AddLog(LOG_LEVEL_ERROR,PSTR("EQ3 %s: trv operation failed - no more retries %d"), addrStr(addrev), op->state);
    }
    return 0;
  }

  retries = 0;

  EQ3ParseOp(op, true, 0);
  return 0;
}



/*********************************************************************************************\
 * Functons actualy called from within the BLE task
\*********************************************************************************************/

int ispairing2(const uint8_t *payload, int len, char *name, int namelen, char *serial, int seriallen ){
  while (len){
    int l = *payload;
    //BLE_ESP32::dump(temp, 40, payload, l+1);
    //AddLog(LOG_LEVEL_ERROR,PSTR("EQ3: %s"), temp);

    payload++;
    len--;
    if (len < l){
      //AddLog(LOG_LEVEL_ERROR,PSTR("EQ3: part len er %d<%d"),len, l);
      return 0;
    }
    switch (*payload){
      case 0xff: {// parse the EQ3 advert payload looking for nnFF01ssssssss
        payload++;
        len--;
        l--;
        if (*payload == 1){
          payload++;
          len--;
          l--;
          //char serialstr[20];
          //strncpy(serialstr, (const char *)payload, l);
          //AddLog(LOG_LEVEL_DEBUG,PSTR("EQ3: adv part FF01 detected %s"), serialstr);
          // we don;t use these, but that's what they seem to be....
          uint8_t copylen = (l > seriallen)?seriallen:l;
          strncpy(serial, (const char *)payload, copylen);
          serial[seriallen-1] = 0;
          payload += l;
          len -= l;
          return 1;
        } else {
          payload += l;
          len -= l;
        }
      } break;
      case 0x09: {
        payload++;
        len--;
        l--;
        if (*payload == 1){
          payload++;
          len--;
          l--;
          //char serialstr[20];
          //strncpy(serialstr, (const char *)payload, l);
          //AddLog(LOG_LEVEL_DEBUG,PSTR("EQ3: adv part FF01 detected %s"), serialstr);
          // we don;t use these, but that's what they seem to be....
          uint8_t copylen = (l > namelen)?namelen:l;
          strncpy(name, (const char *)payload, copylen);
          name[namelen-1] = 0;
          payload += l;
          len -= l;
          //return 1;
        } else {
          payload += l;
          len -= l;
        }
      } break;
      default:{
        payload += l;
        len -= l;
      } break;
    }
  }
  return 0;
}

int ispairing(const uint8_t *payload, int len){
  //char temp[40];
  //BLE_ESP32::dump(temp, 40, payload, len);
  //AddLog(LOG_LEVEL_DEBUG,PSTR("EQ3: pair%d %s"), len, temp);
  while (len){
    int l = *payload;
    //BLE_ESP32::dump(temp, 40, payload, l+1);
    //AddLog(LOG_LEVEL_ERROR,PSTR("EQ3: %s"), temp);

    payload++;
    len--;
    if (len < l){
      //AddLog(LOG_LEVEL_ERROR,PSTR("EQ3: part len er %d<%d"),len, l);
      return 0;
    }
    if (*payload == 0xff){
      payload++;
      len--;
      l--;
      if (*payload == 1){
        payload++;
        len--;
        l--;
        //char serialstr[20];
        //strncpy(serialstr, (const char *)payload, l);
        //AddLog(LOG_LEVEL_DEBUG,PSTR("EQ3: adv part FF01 detected %s"), serialstr);
        // we don;t use these, but that's what they seem to be....
        const uint8_t *serial = payload;
        uint8_t seriallen = l;
        payload += l;
        len -= l;
        return 1;
      } else {
        payload += l;
        len -= l;
      }
    } else {
      payload += l;
      len -= l;
    }
  }
  return 0;
}

int TaskEQ3AddDevice(int8_t RSSI, const uint8_t* addr, char *serial){
  int free = -1;
  int i = 0;
  uint64_t now = esp_timer_get_time();

  if (serial && serial[0] && !pairing){
    memcpy(pairingaddr, addr, 6);
    strncpy(pairingserial, serial, sizeof(pairingserial));
    pairingserial[sizeof(pairingserial)-1] = 0;
    pairing = 1;
  }

  for(i = 0; i < EQ3_NUM_DEVICESLOTS; i++){
    if(memcmp(addr,EQ3Devices[i].addr,6)==0){
      break;
    }
    if (EQ3Devices[i].timeoutTime && (EQ3Devices[i].timeoutTime < now)) {
#ifdef EQ3_DEBUG
    AddLog(LOG_LEVEL_DEBUG,PSTR("EQ3 %s timeout at %d"), addrStr(EQ3Devices[i].addr), i);
#endif
      EQ3Devices[i].timeoutTime = 0L;
    }
    if (!EQ3Devices[i].timeoutTime){
      if (free == -1){
        free = i;
      }
    }
  }

  if (i == EQ3_NUM_DEVICESLOTS){
    if (free >= 0){
      i = free;
    } else {
      AddLog(LOG_LEVEL_ERROR,PSTR("EQ3 lost %s: > %d devices"), addrStr(addr), EQ3_NUM_DEVICESLOTS);
      return 0;
    }
  }

#ifdef EQ3_DEBUG
  if (!EQ3Devices[i].timeoutTime)
    AddLog(LOG_LEVEL_INFO,PSTR("EQ3 %s: added at %d"), addrStr(addr), i);
#endif

  EQ3Devices[i].timeoutTime = now + (1000L*1000L)*EQ3_TIMEOUT;
  memcpy(EQ3Devices[i].addr, addr, 6);
  EQ3Devices[i].RSSI = RSSI;

  EQ3Devices[i].pairing = (serial && serial[0])?1:0;

  return 1;
}


const char *EQ3Names[] = {
  "CC-RT-BLE",
  "CC-RT-BLE-EQ",
  "CC-RT-M-BLE"
};

int TaskEQ3advertismentCallback(BLE_ESP32::ble_advertisment_t *pStruct)
{
  // we will try not to use this...
  BLEAdvertisedDevice *advertisedDevice = pStruct->advertisedDevice;

  std::string sname = advertisedDevice->getName();

  bool found = false;
  const char *nameStr = sname.c_str();
  int8_t RSSI = pStruct->RSSI;
  const uint8_t *addr = pStruct->addr;


  const char *alias = BLE_ESP32::getAlias(addr);
  if (EQ3OnlyAliased){
    // ignore unless we have an alias.
    if (!alias || !(*alias)){
      return 0;
    }
  }
  if (!alias) alias = "";

  for (int i = 0; i < sizeof(EQ3Names)/sizeof(*EQ3Names); i++){
    if (!strcmp(nameStr, EQ3Names[i])){
      found = true;
      break;
    }
  }

  if (!found && !strncmp(alias, "EQ3", 3)){
    found = true;
  }

  // if the addr matches the EQ2 mfg prefix, add it?
  if (!found && EQ3MatchPrefix && (matchPrefix(addr) >= 0)){
    found = true;
  }

  if (!found) return 0;

#ifdef EQ3_DEBUG
  if (BLE_ESP32::BLEDebugMode > 0) AddLog(LOG_LEVEL_DEBUG,PSTR("EQ3Device: saw %s"),advertisedDevice->getAddress().toString().c_str());
#endif

  uint8_t* payload = advertisedDevice->getPayload();
  size_t payloadlen = advertisedDevice->getPayloadLength();

  char name[20] = {0};
  char serial[20] = {0};
  int pairing = 0;
  ispairing2(payload, payloadlen, name, 20, serial, 20);

  // this will take and keep the mutex until the function is over
  TasAutoMutex localmutex(&EQ3mutex);
  TaskEQ3AddDevice(RSSI, addr, serial);
  return 0;
}




/*********************************************************************************************\
 * Helper functions
\*********************************************************************************************/



/*********************************************************************************************\
 * init
\*********************************************************************************************/
void EQ3Init(void) {
  memset(&EQ3Devices, 0, sizeof(EQ3Devices));
  BLE_ESP32::registerForAdvertismentCallbacks((const char *)"EQ3", TaskEQ3advertismentCallback);
#ifdef EQ3_DEBUG
  AddLog(LOG_LEVEL_INFO,PSTR("EQ3: init: request callbacks"));
#endif

  EQ3Period = Settings->tele_period;

  return;
}

/***********************************************************************\
 * Regular
\***********************************************************************/

void EQ3Every50mSecond(){

}

/**
 * @brief Main loop of the driver, "high level"-loop
 *
 */
int EQ3Send(const uint8_t* addr, const char *cmd, char* param, char* param2, int useAlias);

void EQ3EverySecond(bool restart){
  if (pairing){
    char p[40]; // used in dump
    BLE_ESP32::dump(p, 20, pairingaddr, 6);
    Response_P(PSTR("{\"pairing\":\"%s\",\"serial\":\"%s\"}"), p, pairingserial);
    char addrstr[4+8*2+2] = "EQ3/";
    BLE_ESP32::dump(&addrstr[4], 8*2+2, pairingaddr, 6);
    char *topic = topicPrefix(STAT, pairingaddr, 1);
    MqttPublish(topic, false);
    pairing = 0;
  }

  seconds --;
  if (seconds <= 0){
    if (EQ3Period){
      if (nextEQ3Poll >= EQ3_NUM_DEVICESLOTS){
        AddLog(LOG_LEVEL_DEBUG, PSTR("EQ3 poll cycle starting"));
        nextEQ3Poll = 0;
      } else {
        AddLog(LOG_LEVEL_ERROR, PSTR("EQ3 poll overrun, deferred - last loop only got to %d, not %d"), nextEQ3Poll, EQ3_NUM_DEVICESLOTS);
      }
    }
    seconds = EQ3Period;
  }

  if (EQ3Period){
    int qlen = opQueue.size();
    if ((nextEQ3Poll < EQ3_NUM_DEVICESLOTS) && (qlen == 0) && (!opInProgress)){
      if (intervalSecondsCounter){
        intervalSecondsCounter--;
      } else {
        // queue a EQ3Status op against each known EQ3.
        // mark it as a regular stat rather than a use cmd.
        for(int i = nextEQ3Poll; i < EQ3_NUM_DEVICESLOTS; i++){
          if (!EQ3Devices[i].timeoutTime){
            nextEQ3Poll = i+1;
            continue;
          }

          // trvMinRSSI
          // find the device in BLE to get RSSI
          if (EQ3Devices[i].RSSI < trvMinRSSI){
            AddLog(LOG_LEVEL_DEBUG, PSTR("EQ3 %s RSSI %d < min %d, poll suppressed"), addrStr(EQ3Devices[i].addr), EQ3Devices[i].RSSI, trvMinRSSI);
            nextEQ3Poll = i+1;
            continue;
          }

          EQ3Send(EQ3Devices[i].addr, PSTR("poll"), nullptr, nullptr, 1);
          nextEQ3Poll = i+1;
          intervalSecondsCounter = intervalSeconds;
          break;
        }
      }
    }
  }

  // start next op now, if we have any queued
  EQ3DoOp();

}


/*********************************************************************************************\
 * Presentation
\*********************************************************************************************/
int EQ3SendCurrentDevices(){
  // send the active devices
  ResponseClear();
  ResponseAppend_P(PSTR("{\"devices\":{"));
  int added = 0;
  for(int i = 0; i < EQ3_NUM_DEVICESLOTS; i++){
    char p[40];
    if (!EQ3Devices[i].timeoutTime)
      continue;
    if (added){
      ResponseAppend_P(PSTR(","));
    }
    BLE_ESP32::dump(p, 20, EQ3Devices[i].addr, 6);
    ResponseAppend_P(PSTR("\"%s\":%d"), p, EQ3Devices[i].RSSI);
    added = 1;
  }
  ResponseAppend_P(PSTR("}}"));
  MqttPublishPrefixTopic_P(STAT, PSTR("EQ3"), false);
  return 0;
}

int EQ3SendResult(char *requested, const char *result){
  // send the result
  Response_P(PSTR("{\"result\":\"%s\"}"), result);
  static char stopic[TOPSZ];
  GetTopic_P(stopic, STAT, TasmotaGlobal.mqtt_topic, PSTR(""));
  strcat(stopic, PSTR("EQ3/"));
  strcat(stopic, requested);
  MqttPublish(stopic, false);
  return 0;
}


/*********************************************************************************************\
 * Commands
\*********************************************************************************************/

void simpletolower(char *p){
  if (!p) return;
  while (*p){
    *p = *p | 0x20;
    p++;
  }
}

//
// great description here:
// https://reverse-engineering-ble-devices.readthedocs.io/en/latest/protocol_description/00_protocol_description.html
// not all implemented yet.
//
int EQ3Send(const uint8_t* addr, const char *cmd, char* param, char* param2, int useAlias){

  char p[] = "";
  if (!param) param = p;
  if (!param2) param2 = p;
  uint8_t d[20];
  memset(d, 0, sizeof(d));
  int dlen = 0;
#ifdef EQ3_DEBUG
  AddLog(LOG_LEVEL_INFO,PSTR("EQ3 %s: cmd: [%s] [%s] [%s]"), addrStr(addr), cmd, param, param2);
#endif

/* done on whole string before here.
  simpletolower(cmd);
  simpletolower(param);
  simpletolower(param2);
*/

  int cmdtype = 0;

  do {
    if (!strcmp(cmd, "raw")){
      cmdtype = 1;
      if (!param || param[0] == 0){
        return -1;
      }
      int len = strlen(param) / 2;
      if (len > 20){
        AddLog(LOG_LEVEL_ERROR,PSTR("EQ3 raw len of %s = %d > 20"), param, len);
        return -1;
      }
      BLE_ESP32::fromHex(d, param, len);
      dlen = len;
      break;
    }

/*    if (!strcmp(cmd, "state")){
      d[0] = 0x03;
      dlen = 1;
      break;
    }
*/
    if (!strcmp(cmd, "settime") || !strcmp(cmd, "state") || !strcmp(cmd, "poll")){
      if (!strcmp(cmd, "poll")){
        cmdtype = 0;
      }
      if (!strcmp(cmd, "state")){
        cmdtype = 2;
      }
      if (!strcmp(cmd, "settime")){
        cmdtype = 3;
      }
      if (!param || param[0] == 0){

        if (RtcTime.valid) {
          d[0] = 0x03;
          d[1] = (RtcTime.year % 100);
          d[2] = RtcTime.month;
          d[3] = RtcTime.day_of_month;
          d[4] = RtcTime.hour;
          d[5] = RtcTime.minute;
          d[6] = RtcTime.second;
        } else {
          return -1;
        }

        // time_t now = 0;
        // struct tm timeinfo = { 0 };
        // time(&now);
        // localtime_r(&now, &timeinfo);
        // d[0] = 0x03;
        // d[1] = timeinfo.tm_year % 100;
        // d[2] = timeinfo.tm_mon + 1;
        // d[3] = timeinfo.tm_mday;
        // d[4] = timeinfo.tm_hour;
        // d[5] = timeinfo.tm_min;
        // d[6] = timeinfo.tm_sec;

      } else {
        d[0] = 0x03;
        BLE_ESP32::fromHex(d+1, param, 6);
      }
      dlen = 7;
      break;
    }

    if (!strcmp(cmd, "settemp")){
      cmdtype = 4;
      if (!param || param[0] == 0){
        return -1;
      }
      float ftemp = 20;
      sscanf(param, "%f", &ftemp);
      if (ftemp < 4.5) ftemp = 4.5;
      if (ftemp > 30) ftemp = 30;
      ftemp *= 2;
      uint8_t ctemp = (uint8_t) ftemp;
      d[0] = 0x41; d[1] = ctemp; dlen = 2;
      break;
    }

    if (!strcmp(cmd, "offset")){
      cmdtype = 5;
      if (!param || param[0] == 0){
        return 0;
      }
      float ftemp = 20;
      sscanf(param, "%f", &ftemp);
      ftemp *= 2;
      int8_t ctemp = (int8_t) ftemp;
      ctemp += 7;
      d[0] = 0x13; d[1] = ctemp; dlen = 2;
      break;
    }

    if (!strcmp(cmd, "setdaynight")){
      cmdtype = 6;
      if (!param || param[0] == 0){
        return -1;
      }
      if (!param2 || param2[0] == 0){
        return -1;
      }
      float ftemp = 15;
      sscanf(param, "%f", &ftemp);
      if (ftemp < 5) ftemp = 5;
      ftemp *= 2;
      uint8_t dtemp = (uint8_t) ftemp;

      ftemp = 20;
      sscanf(param2, "%f", &ftemp);
      if (ftemp < 5) ftemp = 5;
      ftemp *= 2;
      uint8_t ntemp = (uint8_t) ftemp;

      d[0] = 0x11; d[1] = dtemp; d[2] = ntemp; dlen = 3;
      break;
    }

    if (!strcmp(cmd, "setwindowtempdur")){
      cmdtype = 7;
      if (!param || param[0] == 0){
        return -1;
      }
      if (!param2 || param2[0] == 0){
        return -1;
      }
      float ftemp = 15;
      sscanf(param, "%f", &ftemp);
      if (ftemp < 5) ftemp = 5;
      ftemp *= 2;
      uint8_t temp = (uint8_t) ftemp;

      int dur = 0;
      sscanf(param2, "%d", &dur);
      d[0] = 0x14; d[1] = temp; d[2] = (dur/5); dlen = 3;
      break;
    }

    if (!strcmp(cmd, "setholiday")){
      cmdtype = 8;
      //40941C152402
      // 40 94
      if (!param || param[0] == 0){
        return -1;
      }
      if (!param2 || param2[0] == 0){
        return -1;
      }

      int yy = 0;
      int mm = 0;
      int dd = 0;
      int hour = 0;
      int min = 0;
      char *p = param;
      p = strtok(p, "-");
      if (!p || p[0] == 0) return -1;
      sscanf(p, "%d", &yy);
      p = strtok(nullptr, "-");
      if (!p || p[0] == 0) return -1;
      sscanf(p, "%d", &mm);
      p = strtok(nullptr, ",");
      if (!p || p[0] == 0) return -1;
      sscanf(p, "%d", &dd);
      p = strtok(nullptr, ":");
      if (!p || p[0] == 0) return -1;
      sscanf(p, "%d", &hour);
      p = strtok(nullptr, "");
      if (!p || p[0] == 0) return -1;
      sscanf(p, "%d", &min);

      min += hour*60;
      int tt = min / 30;

      float ftemp = 15;
      sscanf(param2, "%f", &ftemp);
      if (ftemp < 5) ftemp = 5;
      ftemp *= 2;
      uint8_t temp = (uint8_t) ftemp + 128;

      d[0] = 0x40;
      d[1] = temp;
      d[2] = dd;
      d[3] = yy;
      d[4] = tt;
      d[5] = mm;
      dlen = 6;
      break;
    }


    if (!strcmp(cmd, "boost"))    {
      cmdtype = 9;
      d[0] = 0x45; d[1] = 0x01;
      if (param && (!strcmp(param, "off") || param[0] == '0')){
        d[1] = 0x00;
      }
      dlen = 2; break;
    }
    if (!strcmp(cmd, "unboost"))  {
      cmdtype = 10;
      d[0] = 0x45; d[1] = 0x00; dlen = 2; break; }
    if (!strcmp(cmd, "lock"))     { cmdtype = 23; d[0] = 0x80; d[1] = 0x01;
      if (param && (!strcmp(param, "off") || param[0] == '0')){
        d[1] = 0x00;
      }
      dlen = 2; break;
    }
    if (!strcmp(cmd, "unlock"))   { cmdtype = 11; d[0] = 0x80; d[1] = 0x00; dlen = 2; break; }
    if (!strcmp(cmd, "auto"))     { cmdtype = 12; d[0] = 0x40; d[1] = 0x00; dlen = 2; break; }
    if (!strcmp(cmd, "manual"))   { cmdtype = 13; d[0] = 0x40; d[1] = 0x40; dlen = 2; break; }
    // this is basically 'cancel holiday' - mode auto does that.
    //if (!strcmp(cmd, "eco"))      { cmdtype = 14; d[0] = 0x40; d[1] = 0x80; dlen = 2; break; }
    if (!strcmp(cmd, "on"))       {
      int res = EQ3Send(addr, "manual", nullptr, nullptr, useAlias);
      char tmp[] = "30";
      int res2 = EQ3Send(addr, "settemp", tmp, nullptr, useAlias);
      return res2;
    }
    if (!strcmp(cmd, "off"))      {
      int res = EQ3Send(addr, "manual", nullptr, nullptr, useAlias);
      char tmp[] = "4.5";
      int res2 = EQ3Send(addr, "settemp", tmp, nullptr, useAlias);
      return res2;
    }
    if (!strcmp(cmd, "valve"))     { cmdtype = 17; d[0] = 0x41; d[1] = 0x3c;
      if (!param || param[0] == 0){
        return -1;
      }
      if ((!strcmp(param, "off") || param[0] == '0')){
        d[1] = 0x09;
      }
      dlen = 2; break;
    }
    if (!strcmp(cmd, "mode"))     { cmdtype = 18; d[0] = 0x40; d[1] = 0xff;// invlaid

      if (!param || param[0] == 0){
        return -1;
      }
      if (!strcmp(param, "auto")){
        d[1] = 0x00;
      }
      if (!strcmp(param, "manual")){
        d[1] = 0x40;
      }
      if (!strcmp(param, "on") || !strcmp(param, "heat")) {
        int res = EQ3Send(addr, "manual", nullptr, nullptr, useAlias);
        char tmp[] = "30";
        int res2 = EQ3Send(addr, "settemp", tmp, nullptr, useAlias);
        return res2;
      }
      if (!strcmp(param, "off") || !strcmp(param, "cool")) {
        int res = EQ3Send(addr, "manual", nullptr, nullptr, useAlias);
        char tmp[] = "4.5";
        int res2 = EQ3Send(addr, "settemp", tmp, nullptr, useAlias);
        return res2;
      }

      if (d[1] == 0xff){ // no valid mode selection found
        return -1;
      }
      // this is basically 'cancel holiday' - mode auto does that.
      //if (!strcmp(param, "eco")){
      //  d[1] = 0x80;
      //}
      dlen = 2; break;
    }
    if (!strcmp(cmd, "day"))      { cmdtype = 19; d[0] = 0x43; dlen = 1; break; }
    if (!strcmp(cmd, "night"))    { cmdtype = 20; d[0] = 0x44; dlen = 1; break; }

    if (!strcmp(cmd, "reqprofile"))     { cmdtype = 21;
      if (!param || param[0] == 0){
        return -1;
      }
      d[0] = 0x20; d[1] = atoi(param); dlen = 2;
      break;
    }

    if (!strcmp(cmd, "setprofile"))     { cmdtype = 22;
      if (!param || param[0] == 0){
        return -1;
      }
      if (!param2 || param2[0] == 0){
        return -1;
      }
      d[0] = 0x10; d[1] = atoi(param);

      // default
      uint8_t temps[7] = {0x22,0x22,0x22,0x22,0x22,0x22,0x22};
      uint8_t times[7] = {0x90,0x90,0x90,0x90,0x90,0x90,0x90};

      // 20.5-17:30,
      const char *p = strtok(param2, ",");
      int i = 0;
      while (p){
        float t = 17;
        int mm = 0;
        int hh = 24;
        sscanf(p, "%f-%d:%d", &t, &hh, &mm);
        t *= 2;
        temps[i] = (uint8_t) t;
        int time = hh*60+mm;
        time = time / 10;
        times[i] = time;
        p = strtok(nullptr, ",");
        i++;
        if (i >= 7) break;
      }

      // remaining left at 00 00
      for (int j = 0; j < 7; j++){
        d[2+j*2] = temps[j];
        d[2+j*2+1] = times[j];
      }

      dlen = 2+14;
      break;
    }

    break;
  } while(0);

  if (dlen){
    dlen = 16;
    return EQ3QueueOp(addr, d, dlen, cmdtype, useAlias);

    //return EQ3Operation(addr, d, dlen, 4);
  }

  return -1;
}


const char *responses[] = {
  PSTR("Done"),
  PSTR("queued"),
  PSTR("ignoredbusy"),
  PSTR("invcmd"),
  PSTR("cmdfail"),
  PSTR("invidx"),
  PSTR("invaddr")
};


int CmndTrvNext(int index, char *data){
  AddLog(LOG_LEVEL_DEBUG,PSTR("EQ3 cmd index: %d"), index);
  //simpletolower(data);

  switch(index){
    case 0:
    case 1: {

      char *p = strtok(data, " ");
      bool trigger = false;
      if (!strcmp(p, "reset")){
        retries = 0;
        for (int i = 0; i < EQ3_NUM_DEVICESLOTS; i++){
          EQ3Devices[i].timeoutTime = 0L;
        }
        return 0;
      }

      if (!strcmp(p, "scan")){
#ifdef EQ3_DEBUG
        AddLog(LOG_LEVEL_DEBUG,PSTR("EQ3 cmd: %s"), p);
#endif
        EQ3SendCurrentDevices();
        return 0;
      }
      if (!strcmp(p, "devlist")){
#ifdef EQ3_DEBUG
        AddLog(LOG_LEVEL_DEBUG,PSTR("EQ3 cmd: %s"), p);
#endif
        EQ3SendCurrentDevices();
        return 0;
      }

      // only allow one command in progress
      if (retries){
        //return 2;
      }


      int useAlias = 0;
      uint8_t addrbin[7];
      int addrres = BLE_ESP32::getAddr(addrbin, p);
      if (addrres){
        if (addrres == 2){
          AddLog(LOG_LEVEL_DEBUG,PSTR("EQ3 addr used alias: %s"), p);
          useAlias = 1;
        }
        NimBLEAddress addr(addrbin, addrbin[6]);

#ifdef EQ3_DEBUG
        //AddLog(LOG_LEVEL_INFO,PSTR("EQ3 cmd addr: %s -> %s"), p, addr.toString().c_str());
#endif
      } else {
        AddLog(LOG_LEVEL_ERROR,PSTR("EQ3 addr invalid: %s"), p);
        return 3;
      }

      // get next part of cmd
      char *cmd = strtok(nullptr, " ");
      if (!cmd){
        return 3;
      }

      char *param = strtok(nullptr, " ");
      char *param2 = nullptr;
      if (param){
        param2 = strtok(nullptr, " ");
      }
      int res = EQ3Send(addrbin, cmd, param, param2, useAlias);
      if (res > 0) {
        // succeeded to queue
        AddLog(LOG_LEVEL_ERROR,PSTR("EQ3 queued"));
        return 1;
      }

      if (res < 0) { // invalid in some way
        AddLog(LOG_LEVEL_ERROR,PSTR("EQ3 invalid"));
        return 3;
      }

      AddLog(LOG_LEVEL_ERROR,PSTR("EQ3 failed to queue"));
      // failed to queue
      return 4;
    } break;

    case 2:
      retries = 0;
      return 0;
      break;
  }

  return 4;
}

void CmndTrv(void) {
  int res = CmndTrvNext(XdrvMailbox.index, XdrvMailbox.data);
  ResponseCmndChar(responses[res]);
}

void CmndTrvPeriod(void) {
  if (XdrvMailbox.data_len > 0) {
    if (1 == XdrvMailbox.payload){
      seconds = 0;
    } else {
      EQ3Period = XdrvMailbox.payload;
      if (seconds > EQ3Period){
        seconds = EQ3Period;
      }
    }
  }
  ResponseCmndNumber(EQ3Period);
}

void CmndTrvOnlyAliased(void){
  if (XdrvMailbox.data_len > 0) {
    EQ3OnlyAliased = XdrvMailbox.payload;
  }
  ResponseCmndNumber(EQ3OnlyAliased);
}

void CmndTrvMatchPrefix(void){
  if (XdrvMailbox.data_len > 0) {
    EQ3MatchPrefix = XdrvMailbox.payload;
  }
  ResponseCmndNumber(EQ3MatchPrefix);
}

void CmndTrvMinRSSI(void){
  if (XdrvMailbox.data_len > 0) {
    trvMinRSSI = atoi(XdrvMailbox.data);
  }
  // signed number
  Response_P(PSTR("{\"%s\":%d}"), XdrvMailbox.command, trvMinRSSI);
}

void CmndTrvHideFailedPoll(void){
  if (XdrvMailbox.data_len > 0) {
    EQ3HideFailedPoll = XdrvMailbox.payload;
  }
  ResponseCmndNumber(EQ3HideFailedPoll);
}


#define EQ3_TOPIC "EQ3"
static char tmp[120];

bool mqtt_direct(){
  char stopic[TOPSZ];
  strncpy(stopic, XdrvMailbox.topic, TOPSZ);
  XdrvMailbox.topic[TOPSZ-1] = 0;

  AddLog(LOG_LEVEL_DEBUG,PSTR("EQ3 mqtt: %s:%s"), stopic, XdrvMailbox.data);

  char *items[10];
  char *p = stopic;
  int cnt = 0;
  do {
    items[cnt] = strtok(p, "/");
    cnt++;
    p = nullptr;
  } while (items[cnt-1]);
  cnt--; // repreents the number of items

  if (cnt < 4){ // not for us?
    //AddLog(LOG_LEVEL_INFO,PSTR("cnt: %d < 4"), cnt);
    return false;
  }

  for (int i = 0; i < cnt; i++){
    //AddLog(LOG_LEVEL_INFO,PSTR("cnt %d:%s"), i, items[i]);
  }


  int EQ3index = 0;
  int MACindex = 0;
  int CMDindex = 0;
  if (strcasecmp_P(items[cnt-3], PSTR(EQ3_TOPIC)) != 0) {
    //AddLog(LOG_LEVEL_INFO,PSTR("cnt-3 not %s"), PSTR(EQ3_TOPIC));
    if (strcasecmp_P(items[cnt-2], PSTR(EQ3_TOPIC)) != 0) {
      //AddLog(LOG_LEVEL_INFO,PSTR("cnt-2 not %s"), PSTR(EQ3_TOPIC));
      return false; // not for us
    } else {
      EQ3index = cnt-2;
      MACindex = cnt-1;
    }
  } else {
    EQ3index = cnt-3;
    MACindex = cnt-2;
    CMDindex = cnt-1;
  }

  int remains = 120;
  memset(tmp, 0, sizeof(tmp));
  p = tmp;
  uint8_t addr[7];
  int useAlias = BLE_ESP32::getAddr(addr, items[MACindex]);
  int res = 6; // invalid address/alias

  // if address or alias valid
  if (useAlias){
    strncpy(p, items[MACindex], remains-6);
    p += strlen(p);
    *(p++) = 0x20;
    remains = 120 - (p-tmp);

    if (CMDindex){
      strncpy(p, items[CMDindex], remains-6);
      p += strlen(p);
      *(p++) = 0x20;
      remains = 120 - (p-tmp);
    }

    strncpy(p, XdrvMailbox.data, remains-6);
    p += strlen(p);
    *(p++) = 0x20;
    remains = 120 - (p-tmp);
    *(p++) = 0;

    AddLog(LOG_LEVEL_DEBUG,PSTR("EQ3:mqtt->cmdstr %s"), tmp);
    res = CmndTrvNext(1, tmp);
  }

  // post result to stat/tas/EQ3/<MAC> {"result":"<string>"}
  EQ3SendResult(items[MACindex], responses[res]);

  return true;
}


///////////////////////////////////////////////
// starts a completely fresh MQTT message.
// sends ONE sensor's worth of HA discovery msg
const char EQ3_HA_DISCOVERY_TEMPLATE[] PROGMEM =
  "{\"availability\":[],\"device\":"
  "{\"identifiers\":[\"BLE%s\"],"
  "\"name\":\"%s\","
  "\"manufacturer\":\"tas\","
  "\"model\":\"%s\","
  "\"via_device\":\"%s\""
  "},"
  "\"dev_cla\":\"%s\","
  "\"expire_after\":600,"
  "\"json_attr_t\":\"%s\","
  "\"name\":\"%s_%s\","
  "\"state_topic\":\"%s\","
  "\"uniq_id\":\"%s_%s\","
  "\"unit_of_meas\":\"%s\","
  "\"val_tpl\":\"{{ value_json.%s }}\"}";

///////////TODO - unfinished.....
void EQ3DiscoveryOneEQ3(){
  // don't detect half-added ones here
  if (EQ3CurrentSingleSlot >= EQ3_NUM_DEVICESLOTS){
    // if we got to the end of the sensors, then don't send more
    return;
  }

#ifdef USE_HOME_ASSISTANT
  if(Settings->flag.hass_discovery){
    eq3_device_tag *p;
    do {
      p = &EQ3Devices[EQ3CurrentSingleSlot];
      if (0 == p->timeoutTime){
        EQ3CurrentSingleSlot++;
      }
    } while ((0 == p->timeoutTime) && (EQ3CurrentSingleSlot <= EQ3_NUM_DEVICESLOTS));

    if (EQ3CurrentSingleSlot >= EQ3_NUM_DEVICESLOTS){
      return;
    }

    // careful - a missing comma causes a crash!!!!
    // because of the way we loop?
    const char *classes[] = {
      "temperature",
      "temp",
      "°C",
      "signal_strength",
      "RSSI",
      "dB"
    };

    int datacount = (sizeof(classes)/sizeof(*classes))/3;

    if (p->nextDiscoveryData >= datacount){
      p->nextDiscoveryData = 0;
    }

    char DiscoveryTopic[80];
    const char *host = NetworkHostname();
    const char *devtype = PSTR("EQ3");
    char idstr[32];
    const char *alias = BLE_ESP32::getAlias(p->addr);
    const char *id = idstr;
    if (alias && *alias){
      id = alias;
    } else {
      sprintf(idstr, PSTR("%s%02x%02x%02x"),
            devtype,
            p->addr[3], p->addr[4], p->addr[5]);
    }

    char SensorTopic[60];
    sprintf(SensorTopic, "stat/%s/EQ3/%s",
      host, id);

    //int i = p->nextDiscoveryData*3;
    for (int i = 0; i < datacount*3; i += 3){
      if (!classes[i] || !classes[i+1] || !classes[i+2]){
        return;
      }

      ResponseClear();

    /*
    {"availability":[],"device":{"identifiers":["TasmotaBLEa4c1387fc1e1"],"manufacturer":"simon","model":"someBLEsensor","name":"TASBLEa4c1387fc1e1","sw_version":"0.0.0"},"dev_cla":"temperature","json_attr_t":"stat/tasmota_esp32/SENSOR","name":"TASLYWSD037fc1e1Temp","state_topic":"tele/tasmota_esp32/SENSOR","uniq_id":"Tasmotaa4c1387fc1e1temp","unit_of_meas":"°C","val_tpl":"{{ value_json.LYWSD037fc1e1.Temperature }}"}
    {"availability":[],"device":{"identifiers":["TasmotaBLEa4c1387fc1e1"],
    "name":"TASBLEa4c1387fc1e1"},"dev_cla":"temperature",
    "json_attr_t":"tele/tasmota_esp32/SENSOR",
    "name":"TASLYWSD037fc1e1Temp","state_topic":  "tele/tasmota_esp32/SENSOR",
    "uniq_id":"Tasmotaa4c1387fc1e1temp","unit_of_meas":"°C",
    "val_tpl":"{{ value_json.LYWSD037fc1e1.Temperature }}"}
    */

      ResponseAppend_P(EQ3_HA_DISCOVERY_TEMPLATE,
      //"{\"identifiers\":[\"BLE%s\"],"
        id,
      //"\"name\":\"%s\"},"
        id,
      //\"model\":\"%s\",
        devtype,
      //\"via_device\":\"%s\"
        host,
      //"\"dev_cla\":\"%s\","
        classes[i],
      //"\"json_attr_t\":\"%s\"," - the topic the sensor publishes on
        SensorTopic,
      //"\"name\":\"%s_%s\"," - the name of this DATA
        id, classes[i+1],
      //"\"state_topic\":\"%s\","  - the topic the sensor publishes on?
        SensorTopic,
      //"\"uniq_id\":\"%s_%s\"," - unique for this data,
        id, classes[i+1],
      //"\"unit_of_meas\":\"%s\"," - the measure of this type of data
        classes[i+2],
      //"\"val_tpl\":\"{{ value_json.%s }}") // e.g. Temperature
        classes[i+1]
          //
      );

      sprintf(DiscoveryTopic, "homeassistant/sensor/%s/%s/config",
        id, classes[i+1]);

      MqttPublish(DiscoveryTopic);
      p->nextDiscoveryData++;
      //vTaskDelay(100/ portTICK_PERIOD_MS);
    }
  } // end if hass discovery
  //AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: %s: show some %d %s"),D_CMND_MI32, MI32.mqttCurrentSlot, TasmotaGlobal.mqtt_data);
#endif //USE_HOME_ASSISTANT

}




} // end namespace EQ3_ESP32

/*********************************************************************************************\
 * Interface
\*********************************************************************************************/

bool Xdrv85(uint8_t function)
{
  bool result = false;

  switch (function) {
    case FUNC_INIT:
      EQ3_ESP32::EQ3Init();
      break;
    case FUNC_EVERY_50_MSECOND:
      EQ3_ESP32::EQ3Every50mSecond();
      break;
    case FUNC_EVERY_SECOND:
      EQ3_ESP32::EQ3EverySecond(false);
      break;
    case FUNC_COMMAND:
      result = DecodeCommand(EQ3_ESP32::kEQ3_Commands, EQ3_ESP32::EQ3_Commands);
      break;
    case FUNC_MQTT_DATA:
      //AddLog(LOG_LEVEL_INFO,PSTR("topic %s"), XdrvMailbox.topic);
      result = EQ3_ESP32::mqtt_direct();
      break;
    case FUNC_JSON_APPEND:
      break;
#ifdef USE_WEBSERVER
    case FUNC_WEB_SENSOR:
      break;
#endif  // USE_WEBSERVER
    }
  return result;
}
#endif  //
#endif  // ESP32

#endif
#endif  // CONFIG_IDF_TARGET_ESP32
