/*
  xdrv_08_serial_bridge.ino - serial bridge support for Tasmota

  Copyright (C) 2021  Theo Arends and Dániel Zoltán Tolnai

  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/>.
*/

#ifdef USE_SERIAL_BRIDGE
/*********************************************************************************************\
 * Serial Bridge using Software Serial library (TasmotaSerial)
 * 
 * SBaudrate <baudrate>          - Set baudrate
 * SBaudrate 9600
 * SSerialConfig <config>        - Set serial config
 * SSerialConfig 8N1
 * SSerialBuffer <size>          - Set serial receive buffer
 * SSerialSend "Hello Tiger"     - Send "Hello Tiger\n"
 * SSerialSend                   - Set to text decoding
 * SSerialSend1 "Hello Tiger"    - Send "Hello Tiger\n"
 * SSerialSend2 "Hello Tiger"    - Send "Hello Tiger"
 * SSerialSend3 "Hello Tiger"    - Send "Hello Tiger\f"
 * SSerialSend4                  - Set to binary decoding
 * SSerialSend4 "Hello Tiger"    - Send "Hello Tiger" and set to binary decoding
 * SSerialSend5 "AA004566"       - Send "AA004566" as hex values
 * SSerialSend6 "72,101,108,108" - Send decimals as hex values
 * SSerialMode 0                 - Default mode (Console tee off, 115200bps, 8N1)
 * SSerialMode 1                 - Enable console tee
 * SSerialMode 2                 - Enable dump of serial data in log level 4
 * SSerialMode 3                 - Enable Sonoff WTS01 sensor decoding (ESP32 only)
 * SerialDelimiter 255           - Receive data between 32 and 127 only
 * SerialDelimiter 254           - Receive hexadecimal data
 * SerialDelimiter 128           - Receive no data between 32 and 127
\*********************************************************************************************/

#define XDRV_08                    8
#define HARDWARE_FALLBACK          2

#define USE_SERIAL_BRIDGE_TEE

#ifdef ESP32
#define USE_SERIAL_BRIDGE_WTS01
#endif  // ESP32

#ifdef SERIAL_BRIDGE_BUFFER_SIZE
const uint16_t SERIAL_BRIDGE_BUFSIZE = SERIAL_BRIDGE_BUFFER_SIZE;
#else
#ifdef ESP8266
const uint16_t SERIAL_BRIDGE_BUFSIZE = MIN_INPUT_BUFFER_SIZE;     // 256
#else
const uint16_t SERIAL_BRIDGE_BUFSIZE = INPUT_BUFFER_SIZE;         // 800
#endif  // ESP32
#endif  // SERIAL_BRIDGE_BUFFER_SIZE

enum SerialBridgeModes { SB_NONE, SB_TEE, SB_DUMP, SB_WTS01 };

const char kSerialBridgeCommands[] PROGMEM = "|"  // No prefix
  D_CMND_SSERIALSEND "|" D_CMND_SBAUDRATE "|" D_CMND_SSERIALBUFFER "|" D_CMND_SSERIALCONFIG "|" D_CMND_SSERIALMODE;

void (* const SerialBridgeCommand[])(void) PROGMEM = {
  &CmndSSerialSend, &CmndSBaudrate, &CmndSSerialBuffer, &CmndSSerialConfig, &CmndSSerialMode };

#include <TasmotaSerial.h>
TasmotaSerial *SerialBridgeSerial = nullptr;

char *serial_bridge_buffer = nullptr;

struct {
  uint32_t polling_window;
  int      in_byte_counter = 0;
  float    temperature;
  uint8_t  log_index;
  bool     raw = false;
} SBridge;

/********************************************************************************************/

bool SetSSerialBegin(void) {
  return SerialBridgeSerial->begin(Settings->sbaudrate * 300, ConvertSerialConfig(Settings->sserial_config));  // Reinitialize serial port with new baud rate
}

void SetSSerialConfig(uint32_t serial_config) {
  if (serial_config > TS_SERIAL_8O2) {
    serial_config = TS_SERIAL_8N1;
  }
  if (serial_config != Settings->sserial_config) {
    Settings->sserial_config = serial_config;
    SetSSerialBegin();
  }
}

void SerialBridgeWrite(char *line, uint32_t len) {
#ifdef USE_SERIAL_BRIDGE_TEE
  if ((SB_TEE == Settings->sserial_mode) && serial_bridge_buffer) {
    if (0 == SBridge.log_index) {
//      SerialBridgeSerial->write("\r\n");
      SerialBridgeGetLog();
    } else {
      SerialBridgeSerial->write(line, len);                                      // This does not resolve "DVES_%06X"
      SerialBridgeSerial->write("\r\n");
    }
  }
#endif  // USE_SERIAL_BRIDGE_TEE
}

#ifdef USE_SERIAL_BRIDGE_TEE
bool SerialBridgeGetLog(void) {
  bool any_line = false;
  if (SB_TEE == Settings->sserial_mode) {
    uint32_t index = SBridge.log_index;                                          // Dump log buffer
    char* line;
    size_t len;
    while (GetLog(TasmotaGlobal.seriallog_level, &index, &line, &len)) {
      any_line = true;
      SBridge.log_index = index;
      SerialBridgeWrite(line, len -1);                                           // This does not resolve "DVES_%06X"
    }
  }
  return any_line;
}
#endif  // USE_SERIAL_BRIDGE_TEE

/********************************************************************************************/

void SerialBridgeLoop(void) {
/*
#ifdef USE_SERIAL_BRIDGE_TEE
  if (SerialBridgeGetLog()) { return; }
#endif  // USE_SERIAL_BRIDGE_TEE
*/
  while (SerialBridgeSerial->available()) {
    yield();
    uint8_t serial_in_byte = SerialBridgeSerial->read();

#ifdef USE_SERIAL_BRIDGE_TEE
    if (SB_TEE == Settings->sserial_mode) {                                      // CMND_SSERIALSEND9 - Enable logging tee to serialbridge
      static bool serial_bridge_overrun = false;

      if (isprint(serial_in_byte)) {                                             // Any char between 32 and 127
        if (SBridge.in_byte_counter < SERIAL_BRIDGE_BUFSIZE -1) {                // Add char to string if it still fits
          serial_bridge_buffer[SBridge.in_byte_counter++] = serial_in_byte;
        } else {
          serial_bridge_overrun = true;                                          // Signal overrun but continue reading input to flush until '\n' (EOL)
        }
      }
      else if (serial_in_byte == '\n') {
        serial_bridge_buffer[SBridge.in_byte_counter] = 0;                       // Serial data completed
        SetMinimumSeriallog();
        if (serial_bridge_overrun) {
          AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "SSerial buffer overrun"));
        } else {
          AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "%s"), serial_bridge_buffer);
          ExecuteCommand(serial_bridge_buffer, SRC_SSERIAL);
        }
        SBridge.in_byte_counter = 0;
        serial_bridge_overrun = false;
        SerialBridgeSerial->flush();
        return;
      }
    } else {
#endif  // USE_SERIAL_BRIDGE_TEE
      SBridge.raw = (Settings->sserial_mode > SB_TEE) ? true : (254 == Settings->serial_delimiter); // SerialDelimiter
      if ((serial_in_byte > 127) && !SBridge.raw) {                              // Discard binary data above 127 if no raw reception allowed
        SBridge.in_byte_counter = 0;
        SerialBridgeSerial->flush();
        return;
      }
      if (serial_in_byte || SBridge.raw) {                                       // Any char between 1 and 127 or any char (0 - 255)
        bool in_byte_is_delimiter =                                              // Char is delimiter when...
          (((Settings->serial_delimiter < 128) && (serial_in_byte == Settings->serial_delimiter)) || // Any char between 1 and 127 and being delimiter
          ((Settings->serial_delimiter == 128) && !isprint(serial_in_byte))) &&  // Any char not between 32 and 127
          !SBridge.raw;                                                          // In raw mode (CMND_SERIALSEND3) there is never a delimiter

        if ((SBridge.in_byte_counter < SERIAL_BRIDGE_BUFSIZE -1) &&              // Add char to string if it still fits and ...
            !in_byte_is_delimiter) {                                             // Char is not a delimiter
          serial_bridge_buffer[SBridge.in_byte_counter++] = serial_in_byte;
        }

        if ((SBridge.in_byte_counter >= SERIAL_BRIDGE_BUFSIZE -1) ||             // Send message when buffer is full or ...
            in_byte_is_delimiter) {                                              // Char is delimiter
          SBridge.polling_window = 0;                                            // Publish now
          break;
        }
      }
      SBridge.polling_window = millis();                                         // Wait for more data
#ifdef USE_SERIAL_BRIDGE_TEE
    }
#endif  // USE_SERIAL_BRIDGE_TEE
  }

#ifdef USE_SERIAL_BRIDGE_TEE
  if (SB_TEE == Settings->sserial_mode) {                                        // CMND_SSERIALSEND9 - Enable logging tee to serialbridge
    return;
  }
#endif  // USE_SERIAL_BRIDGE_TEE

  if (SBridge.in_byte_counter && (millis() > (SBridge.polling_window + SERIAL_POLLING))) {
    serial_bridge_buffer[SBridge.in_byte_counter] = 0;                           // Serial data completed

    if (SB_NONE == Settings->sserial_mode) {
      bool assume_json = (!SBridge.raw && (serial_bridge_buffer[0] == '{'));
      TasmotaGlobal.serial_skip++;                                               // SetOption35  Skip number of serial messages received (default 0)
      if (TasmotaGlobal.serial_skip > Settings->param[P_SERIAL_SKIP]) {          // Handle intermediate changes to SetOption35
        TasmotaGlobal.serial_skip = 0;
        Response_P(PSTR("{\"" D_JSON_SSERIALRECEIVED "\":"));
        if (assume_json) {
          ResponseAppend_P(serial_bridge_buffer);
        } else {
          ResponseAppend_P(PSTR("\""));
          if (SBridge.raw) {
            ResponseAppend_P(PSTR("%*_H"), SBridge.in_byte_counter, serial_bridge_buffer);
          } else {
            ResponseAppend_P(EscapeJSONString(serial_bridge_buffer).c_str());
          }
          ResponseAppend_P(PSTR("\""));
        }
        ResponseJsonEnd();
        if (Settings->flag6.mqtt_disable_publish ) {  // SetOption147 - If it is activated, Tasmota will not publish SSerialReceived MQTT messages, but it will proccess event trigger rules
          XdrvRulesProcess(0);
        } else {
          MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_SSERIALRECEIVED));
        }
      }
    }

    if (Settings->sserial_mode > SB_TEE) {
      AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SBR: Rcvd '%*_H'"), SBridge.in_byte_counter, serial_bridge_buffer);
    }

#ifdef USE_SERIAL_BRIDGE_WTS01
    // Sonoff WTS01 9600bps, 8N1 datagram every second
    //  0  1  2  3  4  5  6  7  8
    // 55 01 01 04 01 11 16 12 95
    // header            T  Td Ck  - T = Temperature, Td = Temperature decimal, Ck = Checksum
    if (SB_WTS01 == Settings->sserial_mode) {
      if (9 == SBridge.in_byte_counter) {
        uint32_t *header = (uint32_t*)serial_bridge_buffer;
        if (0x04010155 == *header) {
          /*
          14:43:26.718 WTS: buf6 0x01, buf7 0x5d, temp 1, dec 93     1.93
          14:43:33.175 WTS: buf6 0x01, buf7 0x12, temp 1, dec 18     1.18
          14:43:34.252 WTS: buf6 0x01, buf7 0x06, temp 1, dec 6      1.06
          14:43:35.328 WTS: buf6 0x00, buf7 0x5d, temp 0, dec 93     0.93
          14:43:42.862 WTS: buf6 0x00, buf7 0x0c, temp 0, dec 12     0.12
          14:43:43.938 WTS: buf6 0x00, buf7 0x00, temp 0, dec 0      0.00
          14:43:45.015 WTS: buf6 0x80, buf7 0x0c, temp 128, dec 12  -0.12
          14:43:53.624 WTS: buf6 0x80, buf7 0x5d, temp 128, dec 93  -0.93
          14:43:54.700 WTS: buf6 0x81, buf7 0x06, temp 129, dec 6   -1.06
          */
          uint8_t temp = serial_bridge_buffer[6];
          int sign = 1;
          if (temp > 127) {
            temp -= 128;
            sign = -1;
          }
          SBridge.temperature = sign * ((float)temp + ((float)serial_bridge_buffer[7] / 100.0f));
        }
      }
    }
#endif  // USE_SERIAL_BRIDGE_WTS01

    SBridge.in_byte_counter = 0;
  }
}

/********************************************************************************************/

void SerialBridgeInit(void) {
  if (PinUsed(GPIO_SBR_RX) || PinUsed(GPIO_SBR_TX)) {
    SerialBridgeSerial = new TasmotaSerial(Pin(GPIO_SBR_RX),
                                           Pin(GPIO_SBR_TX),
                                           HARDWARE_FALLBACK,
                                           0,                                   // Software receive mode (FALLING edge)
                                           MIN_INPUT_BUFFER_SIZE,               // 256
                                           Settings->flag3.sb_receive_invert);  // SetOption69  - (Serial) Invert Serial receive on SerialBridge
    if (SetSSerialBegin()) {
      if (SerialBridgeSerial->hardwareSerial()) {
        ClaimSerial();
        serial_bridge_buffer = TasmotaGlobal.serial_in_buffer;  // Use idle serial buffer to save RAM
      } else {
        serial_bridge_buffer = (char*)(malloc(SERIAL_BRIDGE_BUFSIZE));
      }
#ifdef ESP32
      AddLog(LOG_LEVEL_DEBUG, PSTR("SBR: Serial UART%d"), SerialBridgeSerial->getUart());
#endif
      SerialBridgeSerial->flush();
//      SerialBridgeWrite((char*)"", 0);
    }
  }
}

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

void CmndSSerialSend(void) {
  if (Settings->sserial_mode > SB_DUMP) { return; }                         // Not supported in receive only mode
  if (XdrvMailbox.index > 9) { XdrvMailbox.index -= 10; }
  if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 6)) {
    SBridge.raw = (XdrvMailbox.index > 3);
    if (XdrvMailbox.data_len > 0) {
      if (1 == XdrvMailbox.index) {
        SerialBridgeSerial->write(XdrvMailbox.data, XdrvMailbox.data_len);  // "Hello Tiger"
        SerialBridgeSerial->write("\n");                                    // "\n"
      }
      else if ((2 == XdrvMailbox.index) || (4 == XdrvMailbox.index)) {
        SerialBridgeSerial->write(XdrvMailbox.data, XdrvMailbox.data_len);  // "Hello Tiger" or "A0"
      }
      else if (3 == XdrvMailbox.index) {                                    // "Hello\f"
        SerialBridgeSerial->write(Unescape(XdrvMailbox.data, &XdrvMailbox.data_len), XdrvMailbox.data_len);
      }
      else if (5 == XdrvMailbox.index) {
        char *p;
        char stemp[3];
        uint8_t code;

        char *codes = RemoveSpace(XdrvMailbox.data);
        int size = strlen(XdrvMailbox.data);

        while (size > 1) {
          strlcpy(stemp, codes, sizeof(stemp));
          code = strtol(stemp, &p, 16);
          SerialBridgeSerial->write(code);                                  // "AA004566" as hex values
          size -= 2;
          codes += 2;
        }
      }
      else if (6 == XdrvMailbox.index) {
        char *p;
        uint8_t code;
        char *values = XdrvMailbox.data;
        for (char* str = strtok_r(values, ",", &p); str; str = strtok_r(nullptr, ",", &p)) {
          code = (uint8_t)atoi(str);
          SerialBridgeSerial->write(code);                                  // "72,101,108,108"
        }
      }
      ResponseCmndDone();
    }
  }
}

void CmndSBaudrate(void) {
  if (XdrvMailbox.payload >= 300) {
    XdrvMailbox.payload /= 300;  // Make it a valid baudrate
    Settings->sbaudrate = XdrvMailbox.payload;
    SetSSerialBegin();
  }
  ResponseCmndNumber(Settings->sbaudrate * 300);
}

void CmndSSerialBuffer(void) {
  // Allow non-pesistent serial receive buffer size change
  if (SerialBridgeSerial->hardwareSerial()) {
    // between MIN_INPUT_BUFFER_SIZE and MAX_INPUT_BUFFER_SIZE characters
    CmndSerialBuffer();
  } else {
    // ESP8266 (software serial): between MIN_INPUT_BUFFER_SIZE and SERIAL_BRIDGE_BUFSIZE characters
    // ESP32 (hardware serial only): between MIN_INPUT_BUFFER_SIZE and MAX_INPUT_BUFFER_SIZE characters
    if (XdrvMailbox.data_len > 0) {
      size_t size = XdrvMailbox.payload;
      if (XdrvMailbox.payload < MIN_INPUT_BUFFER_SIZE) {
        size = MIN_INPUT_BUFFER_SIZE;          // 256 / 256
      }
      else if (XdrvMailbox.payload > SERIAL_BRIDGE_BUFSIZE) {
        size = SERIAL_BRIDGE_BUFSIZE;      // 256 / 800
      }
      SerialBridgeSerial->setRxBufferSize(size);
    }
    ResponseCmndNumber(SerialBridgeSerial->getRxBufferSize());
  }
}

void CmndSSerialConfig(void) {
  // See TasmotaSerialConfig for possible options
  // SSerialConfig 0..23 where 3 equals 8N1
  // SSerialConfig 8N1

  if (XdrvMailbox.data_len > 0) {
    if (XdrvMailbox.data_len < 3) {                    // Use 0..23 as serial config option
      if ((XdrvMailbox.payload >= TS_SERIAL_5N1) && (XdrvMailbox.payload <= TS_SERIAL_8O2)) {
        SetSSerialConfig(XdrvMailbox.payload);
      }
    }
    else if ((XdrvMailbox.payload >= 5) && (XdrvMailbox.payload <= 8)) {
      int8_t serial_config = ParseSerialConfig(XdrvMailbox.data);
      if (serial_config >= 0) {
        SetSSerialConfig(serial_config);
      }
    }
  }
  ResponseCmndChar(GetSerialConfig(Settings->sserial_config).c_str());
}

void CmndSSerialMode(void) {
  // SSerialMode 0  - Default mode (Tee off, 115200bps, 8N1)
  // SSerialMode 1  - Toggle console tee
  // SSerialMode 2  - Dump hex data to log level 4 for analysis
  // SSerialMode 3  - Sonoff WTS01 decode
  if (XdrvMailbox.payload >= 0) {
    switch (XdrvMailbox.payload) {
      case SB_NONE:
        Settings->sserial_mode = XdrvMailbox.payload;
  //      Settings->serial_delimiter = 255;
        Settings->sbaudrate = 115200 / 300;        // 115200bps
        SetSSerialConfig(3);                       // 8N1
        break;
#ifdef USE_SERIAL_BRIDGE_TEE
      case SB_TEE:
        Settings->sserial_mode = XdrvMailbox.payload;
        break;
#endif  // USE_SERIAL_BRIDGE_TEE
      case SB_DUMP:
        Settings->sserial_mode = XdrvMailbox.payload;
        break;
#ifdef USE_SERIAL_BRIDGE_WTS01
      case SB_WTS01:
        Settings->sserial_mode = XdrvMailbox.payload;
        Settings->flag3.sb_receive_invert = 0;     // SetOption69  - (Serial) Invert Serial receive on SerialBridge
        Settings->sbaudrate = 9600 / 300;          // 9600bps
        SetSSerialConfig(3);                       // 8N1
        break;
#endif  // USE_SERIAL_BRIDGE_WTS01
      default:
        return;                                    // Not supported
    }
  }
  ResponseCmndNumber(Settings->sserial_mode);
}

/*********************************************************************************************\
 * Presentation
\*********************************************************************************************/

void SerialBridgeShow(bool json) {
#ifdef USE_SERIAL_BRIDGE_WTS01
  if (SB_WTS01 == Settings->sserial_mode) {
    float temperature = ConvertTemp(SBridge.temperature);
    if (json) {
      ResponseAppend_P(PSTR(",\"WTS01\":{\"" D_JSON_TEMPERATURE "\":%*_f}"), Settings->flag2.temperature_resolution, &temperature);
  #ifdef USE_DOMOTICZ
      if (0 == TasmotaGlobal.tele_period) { DomoticzFloatSensor(DZ_TEMP, temperature); }
  #endif  // USE_DOMOTICZ
  #ifdef USE_KNX
      if (0 == TasmotaGlobal.tele_period) { KnxSensor(KNX_TEMPERATURE, temperature); }
  #endif  // USE_KNX
  #ifdef USE_WEBSERVER
    } else {
      WSContentSend_Temp("WTS01", temperature);
  #endif  // USE_WEBSERVER
    }
  }
#endif  // USE_SERIAL_BRIDGE_WTS01
}

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

bool Xdrv08(uint32_t function) {
  bool result = false;

  if (FUNC_PRE_INIT == function) {
    SerialBridgeInit();
  }
  else if (serial_bridge_buffer) {
    switch (function) {
      case FUNC_LOOP:
      case FUNC_SLEEP_LOOP:
        SerialBridgeLoop();
        break;
      case FUNC_JSON_APPEND:
        SerialBridgeShow(1);
        break;
#ifdef USE_WEBSERVER
      case FUNC_WEB_SENSOR:
        SerialBridgeShow(0);
        break;
#endif  // USE_WEBSERVER
      case FUNC_COMMAND:
        result = DecodeCommand(kSerialBridgeCommands, SerialBridgeCommand);
        break;
      case FUNC_ACTIVE:
        result = true;
        break;
    }
  }
  return result;
}

#endif // USE_SERIAL_BRIDGE
