/*
  xsns_102_ld2410.ino - HLK-LD2410 24GHz smart wave motion sensor support for Tasmota

  SPDX-FileCopyrightText: 2022 Theo Arends

  SPDX-License-Identifier: GPL-3.0-only
*/

#ifdef USE_LD2410
/*********************************************************************************************\
 * HLK-LD2410 24GHz smart wave motion sensor
 *
 * LD2410Duration 0                            - Set factory default settings
 * LD2410Duration 1..65535                     - Set no-one duration in seconds (default 5)
 * LD2410MovingSens 50,50,40,30,20,15,15,15,15 - Set moving distance sensitivity for up to 9 gates (at 0.75 meter interval)
 * LD2410StaticSens 0,0,40,40,30,30,20,20,20   - Set static distance sensitivity for up to 9 gates (at 0.75 meter interval)
 *
 * Inspiration:
 * https://community.home-assistant.io/t/mmwave-wars-one-sensor-module-to-rule-them-all/453260/2
 * Resources:
 * https://drive.google.com/drive/folders/1p4dhbEJA3YubyIjIIC7wwVsSo8x29Fq-?spm=a2g0o.detail.1000023.17.93465697yFwVxH
 *
 * Internal info:
 * - After a LD2410 serial command a response takes about 10mS
 * - After a LD2410 restart it takes at least 1000mS before commands are allowed
\*********************************************************************************************/

#define XSNS_102                         102

#define LD2410_BUFFER_SIZE               TM_SERIAL_BUFFER_SIZE  // 64
#define LD2410_MAX_GATES                 8       // 0 to 8 (= 9) - DO NOT CHANGE

#define LD2410_CMND_START_CONFIGURATION  0xFF
#define LD2410_CMND_END_CONFIGURATION    0xFE
#define LD2410_CMND_SET_DISTANCE         0x60
#define LD2410_CMND_READ_PARAMETERS      0x61
#define LD2410_CMND_START_ENGINEERING    0x62
#define LD2410_CMND_END_ENGINEERING      0x63
#define LD2410_CMND_SET_SENSITIVITY      0x64
#define LD2410_CMND_GET_FIRMWARE         0xA0
#define LD2410_CMND_SET_BAUDRATE         0xA1
#define LD2410_CMND_FACTORY_RESET        0xA2
#define LD2410_CMND_REBOOT               0xA3
#define LD2410_CMND_SET_BLUETOOTH        0xA4
#define LD2410_CMND_GET_BLUETOOTH_MAC    0xA5

const uint8_t LD2410_config_header[4] = {0xFD, 0xFC, 0xFB, 0xFA};
const uint8_t LD2410_config_footer[4] = {0x04, 0x03, 0x02, 0x01};
const uint8_t LD2410_target_header[4] = {0xF4, 0xF3, 0xF2, 0xF1};
const uint8_t LD2410_target_footer[4] = {0xF8, 0xF7, 0xF6, 0xF5};

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

struct {
  uint8_t *buffer;
  uint16_t moving_distance;
  uint16_t static_distance;
  uint16_t detect_distance;
  uint16_t no_one_duration;
  uint8_t moving_sensitivity[LD2410_MAX_GATES +1];
  uint8_t static_sensitivity[LD2410_MAX_GATES +1];
  uint8_t max_moving_distance_gate;
  uint8_t max_static_distance_gate;
  uint8_t moving_energy;
  uint8_t static_energy;
  uint8_t step;
  uint8_t retry;
  uint8_t settings;
  uint8_t byte_counter;
  bool valid_response;
} LD2410;

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

uint32_t ToBcd(uint32_t value) {
  return ((value >> 4) * 10) + (value & 0xF);
}

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

void Ld1410HandleTargetData(void) {
  if ((0x0D == LD2410.buffer[4]) && (0x55 == LD2410.buffer[17])) {  // Add bad reception detection
    //  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22
    // F4 F3 F2 F1 0D 00 02 AA 00 00 00 00 00 00 37 00 00 55 00 F8 F7 F6 F5 - No target
    // F4 F3 F2 F1 0D 00 02 AA 00 45 00 3E 00 00 3A 00 00 55 00 F8 F7 F6 F5 - No target
    // F4 F3 F2 F1 0D 00 02 AA 03 46 00 34 00 00 3C 00 00 55 00 F8 F7 F6 F5 - Movement and Stationary target
    // F4 F3 F2 F1 0D 00 02 AA 02 54 00 00 00 00 64 00 00 55 00 F8 F7 F6 F5 - Stationary target
    // F4 F3 F2 F1 0D 00 02 AA 02 96 00 00 00 00 36 00 00 55 00 F8 F7 F6 F5 - Stationary target
    // F4 F3 F2 F1 0D 00 02 AA 03 2A 00 64 00 00 64 00 00 55 00 F8 F7 F6 F5 - Movement and Stationary target
    // header     |len  |dt|hd|st|movin|me|stati|se|detec|tr|ck|trailer
    if (LD2410.buffer[8] != 0x00) {                               // Movement and/or Stationary target
      LD2410.moving_distance = LD2410.buffer[10] << 8 | LD2410.buffer[9];
      LD2410.moving_energy = LD2410.buffer[11];
      LD2410.static_distance = LD2410.buffer[13] << 8 | LD2410.buffer[12];
      LD2410.static_energy = LD2410.buffer[14];
      LD2410.detect_distance = LD2410.buffer[16] << 8 | LD2410.buffer[15];
  /*
      AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LD2: Type %d, State %d, Moving %d/%d%%, Static %d/%d%%, Detect %d"),
        LD2410.buffer[6], LD2410.buffer[8],
        LD2410.moving_distance, LD2410.moving_energy,
        LD2410.static_distance, LD2410.static_energy,
        LD2410.detect_distance);
  */
      if (0x01 == LD2410.buffer[6]) {                             // Engineering mode data
        // Adds 22 extra bytes of data

      }
    } else {
      LD2410.moving_distance = 0;
      LD2410.moving_energy = 0;
      LD2410.static_distance = 0;
      LD2410.static_energy = 0;
      LD2410.detect_distance = 0;
    }
  }
}

void Ld1410HandleConfigData(void) {
  if (LD2410_CMND_READ_PARAMETERS == LD2410.buffer[6]) {           // 0x61
    //  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
    // FD FC FB FA 1C 00 61 01 00 00 AA 08 08 08 32 32 28 1E 14 0F 0F 0F 0F 00 00 28 28 1E 1E 14 14 14 05 00 04 03 02 01 - Default
    // header     |len  |cw cv|ack  |hd|dd|md|sd|moving sensitivity 0..8   |static sensitivity 0..8   |timed|trailer
    //            |   28|     |    0|  | 8| 8| 8|50 50 40 30 20 15 15 15 15| 0  0 40 40 30 30 20 20 20|    5|
    LD2410.max_moving_distance_gate = LD2410.buffer[12];
    LD2410.max_static_distance_gate = LD2410.buffer[13];
    for (uint32_t i = 0; i <= LD2410_MAX_GATES; i++) {
      LD2410. moving_sensitivity[i] = LD2410.buffer[14 +i];
      LD2410.static_sensitivity[i] = LD2410.buffer[23 +i];
    }
    LD2410.no_one_duration = LD2410.buffer[33] << 8 | LD2410.buffer[32];
  }
  else if (LD2410_CMND_START_CONFIGURATION == LD2410.buffer[6]) {  // 0xFF
    //  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17
    // FD FC FB FA 08 00 FF 01 00 00 01 00 40 00 04 03 02 01
    // header     |len  |ty   |ack  |protv|bsize|trailer
    //            |    8|     |    0|    1|   64|
    LD2410.valid_response = true;
  }
  else if (LD2410_CMND_GET_FIRMWARE == LD2410.buffer[6]) {         // 0xA0
    //  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21
    // FD FC FB FA 0C 00 A0 01 00 00 00 01 07 01 16 15 09 22 04 03 02 01
    // header     |len  |ty|hd|ack  |ftype|major|minor      |trailer
    //            |   12|  | 1|    0|  256|  1.7|   22091516|
    AddLog(LOG_LEVEL_INFO, PSTR("LD2: Firmware version V%d.%02d.%02d%02d%02d%02d"),  // Firmware version V1.07.22091516
      ToBcd(LD2410.buffer[13]), ToBcd(LD2410.buffer[12]),
      ToBcd(LD2410.buffer[17]), ToBcd(LD2410.buffer[16]), ToBcd(LD2410.buffer[15]), ToBcd(LD2410.buffer[14]));
  }
}

bool Ld2410Match(const uint8_t *header, uint32_t offset) {
  for (uint32_t i = 0; i < 4; i++) {
    if (LD2410.buffer[offset +i] != header[i]) { return false; }
  }
  return true;
}

void Ld2410Input(void) {
/*
  // Works with TasmotaSerial as SoftwareSerial but fails with HardwareSerial
  uint32_t size = LD2410Serial->read(LD2410.buffer, LD2410_BUFFER_SIZE);
  if (size) {
    AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LD2: Rcvd %*_H"), size, LD2410.buffer);

    bool target_header = (Ld2410Match(LD2410_target_header, 0));  // F4F3F2F1
    bool config_header = (Ld2410Match(LD2410_config_header, 0));  // FDFCFBFA
    if (target_header || config_header) {
      uint32_t len = LD2410.buffer[4] +10;                        // Total packet size
      if (size >= len) {                                          // Handle only the first entry (if there are more)
        if (target_header) {                                      // F4F3F2F1
          if (Ld2410Match(LD2410_target_footer, len -4)) {        // F8F7F6F5
            Ld1410HandleTargetData();
          }
        }
        else if (config_header) {                                 // FDFCFBFA
          if (Ld2410Match(LD2410_config_footer, len -4)) {        // 04030201
            Ld1410HandleConfigData();
          }
        }
      }
    }
  }
*/
  // Works with TasmotaSerial and HardwareSerial
  while (LD2410Serial->available()) {
    yield();                                                    // Fix watchdogs

    LD2410.buffer[LD2410.byte_counter++] = LD2410Serial->read();
    if (LD2410.byte_counter < 4) { continue; }                  // Need first four header bytes

    uint32_t header_start = LD2410.byte_counter -4;             // Fix interrupted header transmits
    bool target_header = (Ld2410Match(LD2410_target_header, header_start));  // F4F3F2F1
    bool config_header = (Ld2410Match(LD2410_config_header, header_start));  // FDFCFBFA
    if ((target_header || config_header) && (header_start != 0)) {
      memmove(LD2410.buffer, LD2410.buffer + header_start, 4);  // Sync buffer with header
      LD2410.byte_counter = 4;
    }
    if (LD2410.byte_counter < 6) { continue; }                  // Need packet size bytes

    target_header = (Ld2410Match(LD2410_target_header, 0));     // F4F3F2F1
    config_header = (Ld2410Match(LD2410_config_header, 0));     // FDFCFBFA
    if (target_header || config_header) {
      uint32_t len = LD2410.buffer[4] +10;                      // Total packet size
      if (len > LD2410_BUFFER_SIZE) {
        LD2410.byte_counter = 0;                                // Invalid data
        break;                                                  // Exit loop to satisfy yields
      }
      if (LD2410.byte_counter < len) { continue; }              // Need complete packet

      AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LD2: Rcvd %*_H"), len, LD2410.buffer);

      if (target_header) {                                      // F4F3F2F1

//        AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LD2: Rcvd %*_H"), len, LD2410.buffer);

        if (Ld2410Match(LD2410_target_footer, len -4)) {        // F8F7F6F5
          Ld1410HandleTargetData();
        }
      }
      else if (config_header) {                                 // FDFCFBFA

//        AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LD2: Rcvd %*_H"), len, LD2410.buffer);

        if (Ld2410Match(LD2410_config_footer, len -4)) {        // 04030201
          Ld1410HandleConfigData();
          LD2410Serial->setReadChunkMode(0);                    // Disable chunk mode fixing Hardware Watchdogs
        }
      }
    }
    LD2410.byte_counter = 0;                                    // Finished or bad received footer
    break;                                                      // Exit loop to satisfy yields
  }
  // If here then LD2410.byte_counter could still be partial correct for next loop
}

void Ld2410SendCommand(uint32_t command, uint8_t *val = nullptr, uint32_t val_len = 0);
void Ld2410SendCommand(uint32_t command, uint8_t *val, uint32_t val_len) {
  uint32_t len = val_len +12;
  uint8_t buffer[len];
  buffer[0] = 0xFD;
  buffer[1] = 0xFC;
  buffer[2] = 0xFB;
  buffer[3] = 0xFA;
  buffer[4] = val_len +2;
  buffer[5] = 0x00;
  buffer[6] = command;
  buffer[7] = 0x00;
  if (val) {
    for (uint32_t i = 0; i < val_len; i++) {
      buffer[8 +i] = val[i];
    }
  }
  buffer[8 +val_len] = 0x04;
  buffer[9 +val_len] = 0x03;
  buffer[10 +val_len] = 0x02;
  buffer[11 +val_len] = 0x01;

  AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("LD2: Send %*_H"), len, buffer);

  LD2410Serial->setReadChunkMode(1);                            // Enable chunk mode introducing possible Hardware Watchdogs
  LD2410Serial->flush();
  LD2410Serial->write(buffer, len);
}

void Ld2410SetConfigMode(void) {                                // 0xFF
  uint8_t value[2] = { 0x01, 0x00 };
  Ld2410SendCommand(LD2410_CMND_START_CONFIGURATION, value, sizeof(value));
}

void Ld2410SetMaxDistancesAndNoneDuration(uint32_t max_moving_distance_range, uint32_t max_static_distance_range, uint32_t no_one_duration) {  // 0x60
  // Distance range value can be set from 1 to 8 (distance gates of 0.75 meter)
  // No-one duration value can be set from 1 to 65535 (seconds)
  // 00 00 08 00 00 00 01 00 08 00 00 00 02 00 05 00 00 00
  // motio|          8|stati|          8|durat|seconds
  uint8_t lsb_nd = no_one_duration & 0xFF;
  uint8_t msb_nd = (no_one_duration >> 8) & 0xFF;
  uint8_t value[18] = { 0x00, 0x00, (uint8_t)max_moving_distance_range, 0x00, 0x00, 0x00, 0x01, 0x00, (uint8_t)max_static_distance_range, 0x00, 0x00, 0x00, 0x02, 0x00, lsb_nd, msb_nd, 0x00, 0x00 };
  Ld2410SendCommand(LD2410_CMND_SET_DISTANCE, value, sizeof(value));
}

void Ld2410SetGateSensitivity(uint32_t gate, uint32_t moving_sensitivity, uint32_t static_sensitivity) {  // 0x64
  // Sensitivity value can be set from 0 to 100 (%) for gates 0 to 8
  // 00 00 03 00 00 00 01 00 28 00 00 00 02 00 28 00 00 00
  // gate |          3|motio|         40|stati|         40
  uint8_t value[18] = { 0x00, 0x00, (uint8_t)gate, 0x00, 0x00, 0x00, 0x01, 0x00, (uint8_t)moving_sensitivity, 0x00, 0x00, 0x00, 0x02, 0x00, (uint8_t)static_sensitivity, 0x00, 0x00, 0x00 };
  Ld2410SendCommand(LD2410_CMND_SET_SENSITIVITY, value, sizeof(value));
}

void Ld2410SetAllSensitivity(uint32_t sensitivity) {            // 0x64
  // Sensitivity value can be set from 0 to 100
  // 00 00 FF FF 00 00 01 00 28 00 00 00 02 00 28 00 00 00
  // gate |all gates  |motio|         40|stati|         40
  uint8_t value[18] = { 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x00, (uint8_t)sensitivity, 0x00, 0x00, 0x00, 0x02, 0x00, (uint8_t)sensitivity, 0x00, 0x00, 0x00 };
  Ld2410SendCommand(LD2410_CMND_SET_SENSITIVITY, value, sizeof(value));
}

void Ld2410SetBaudrate(uint32_t index) {                        // 0xA1
  uint8_t value[2] = { (uint8_t)index, 0x00 };
  Ld2410SendCommand(LD2410_CMND_SET_BAUDRATE, value, sizeof(value));
}

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

void Ld2410Every100MSecond(void) {
  if (LD2410.step) {
    LD2410.step--;
    switch (LD2410.step) {
      // case 60: Set default settings
      case 59:
        Ld2410SetConfigMode();                                  // Stop running mode
        break;
      case 57:
        Ld2410SendCommand(LD2410_CMND_FACTORY_RESET);
        break;
      case 56:
        Ld2410SendCommand(LD2410_CMND_REBOOT);                  // Wait at least 1 second
        break;
      case 51:
        LD2410.step = 12;
        AddLog(LOG_LEVEL_DEBUG, PSTR("LD2: Settings factory reset"));
        break;

      // case 40: Save settings
      case 39:
        Ld2410SetConfigMode();                                  // Stop running mode
        break;
      case 37:
        Ld2410SetMaxDistancesAndNoneDuration(8, 8, LD2410.no_one_duration);
        break;
      case 28 ... 36: {
          uint32_t index = LD2410.step -28;
          Ld2410SetGateSensitivity(index, LD2410.moving_sensitivity[index], LD2410.static_sensitivity[index]);
        }
        break;
      case 27:
        LD2410.step = 3;
        AddLog(LOG_LEVEL_DEBUG, PSTR("LD2: Settings saved"));
        break;
/*
      // case 24: pre-POC using 57600 bps instead of default 256000 bps
      case 23:
        AddLog(LOG_LEVEL_DEBUG, PSTR("LD2: Switch to 57600 bps"));
        LD2410Serial->flush();
        LD2410Serial->begin(256000);
        break;
      case 21:
        Ld2410SetConfigMode();                                  // Stop running mode
        break;
      case 19:
        Ld2410SetBaudrate(4);                                   // 57600 bps
        break;
      case 18:
        Ld2410SendCommand(LD2410_CMND_REBOOT);                  // Wait at least 1 second
        LD2410Serial->flush();
        LD2410Serial->begin(57600);
      break;
*/
      // case 12: Init
      case 5:
        Ld2410SetConfigMode();                                  // Stop running mode
        break;
      case 3:
        if (!LD2410.valid_response && LD2410.retry) {
          LD2410.retry--;
          if (LD2410.retry) {
//            LD2410.step = 24;                                   // Change baudrate
            LD2410.step = 7;                                    // Retry
          } else {
            LD2410.step = 0;
            AddLog(LOG_LEVEL_DEBUG, PSTR("LD2: Not detected"));
          }
        } else {
          Ld2410SendCommand(LD2410_CMND_GET_FIRMWARE);
        }
        break;
      case 2:
        Ld2410SendCommand(LD2410_CMND_READ_PARAMETERS);
        break;
      case 1:
        Ld2410SendCommand(LD2410_CMND_END_CONFIGURATION);
        break;
    }
  } else {
    if (1 == LD2410.settings) {
      LD2410.settings = 0;
      LD2410.step = 40;
    }
    else if (2 == LD2410.settings) {
      LD2410.settings = 0;
      LD2410.step = 60;
    }
  }
}

void Ld2410EverySecond(void) {
  if (LD2410.moving_energy) {
    // Send state change to be captured by rules
    // {"Time":"2022-11-26T10:48:16","Switch1":"ON","LD2410":{"Distance":[125.0,0.0,0.0],"Energy":[0,100]}}
    MqttPublishSensor();
  }
}

void Ld2410Detect(void) {
  if (PinUsed(GPIO_LD2410_RX) && PinUsed(GPIO_LD2410_TX)) {
    LD2410.buffer = (uint8_t*)malloc(LD2410_BUFFER_SIZE);    // Default 64
    if (!LD2410.buffer) { return; }
    LD2410Serial = new TasmotaSerial(Pin(GPIO_LD2410_RX), Pin(GPIO_LD2410_TX), 2);
    if (LD2410Serial->begin(256000)) {
      if (LD2410Serial->hardwareSerial()) { ClaimSerial(); }
#ifdef ESP32
      AddLog(LOG_LEVEL_DEBUG, PSTR("LD2: Serial UART%d"), LD2410Serial->getUart());
#endif

      LD2410.retry = 4;
      LD2410.step = 12;
    }
  }
}

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

const char kLd2410Commands[] PROGMEM = "LD2410|"  // Prefix
  "Duration|MovingSens|StaticSens";

void (* const Ld2410Command[])(void) PROGMEM = {
  &CmndLd2410Duration, &CmndLd2410MovingSensitivity, &CmndLd2410StaticSensitivity };

void Ld2410Response(void) {
  Response_P(PSTR("{\"LD2410\":{\"Duration\":%d,\"Moving\":{\"Gates\":%d,\"Sensitivity\":["),
    LD2410.no_one_duration, LD2410.max_moving_distance_gate);
  for (uint32_t i = 0; i <= LD2410_MAX_GATES; i++) {
    ResponseAppend_P(PSTR("%s%d"), (i==0)?"":",", LD2410.moving_sensitivity[i]);
  }
  ResponseAppend_P(PSTR("]},\"Static\":{\"Gates\":%d,\"Sensitivity\":["), LD2410.max_static_distance_gate);
  for (uint32_t i = 0; i <= LD2410_MAX_GATES; i++) {
    ResponseAppend_P(PSTR("%s%d"), (i==0)?"":",", LD2410.static_sensitivity[i]);
  }
  ResponseAppend_P(PSTR("]}}}"));
}

void CmndLd2410Duration(void) {
  // LD2410Duration 0  - Set default settings
  if (0 == XdrvMailbox.payload) {
    LD2410.settings = 2;
  }
  // LD2410Duration 5
  else if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 65535)) {
    LD2410.no_one_duration = XdrvMailbox.payload;
    LD2410.settings = 1;
  }
  Ld2410Response();
}

void CmndLd2410MovingSensitivity(void) {
  // LD2410MovingSens 50,50,40,30,20,15,15,15,15
  uint32_t parm[LD2410_MAX_GATES +1] = { 0 };
  uint32_t count = ParseParameters(LD2410_MAX_GATES +1, parm);
  if (count) {
    for (uint32_t i = 0; i < count; i++) {
      if ((parm[i] >= 0) && (parm[i] <= 100)) {
        LD2410.moving_sensitivity[i] = parm[i];
      }
    }
    LD2410.settings = 1;
  }
  Ld2410Response();
}

void CmndLd2410StaticSensitivity(void) {
  // LD2410StaticSens 0,0,40,40,30,30,20,20,20
  uint32_t parm[LD2410_MAX_GATES +1] = { 0 };
  uint32_t count = ParseParameters(LD2410_MAX_GATES +1, parm);
  if (count) {
    for (uint32_t i = 0; i < count; i++) {
      if ((parm[i] >= 0) && (parm[i] <= 100)) {
        LD2410.static_sensitivity[i] = parm[i];
      }
    }
    LD2410.settings = 1;
  }
  Ld2410Response();
}

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

#ifdef USE_WEBSERVER
const char HTTP_SNS_LD2410_CM[] PROGMEM =
  "{s}LD2410 " D_MOVING_DISTANCE "{m}%1_f " D_UNIT_CENTIMETER "{e}"
  "{s}LD2410 " D_STATIC_DISTANCE "{m}%1_f " D_UNIT_CENTIMETER "{e}"
  "{s}LD2410 " D_DETECT_DISTANCE "{m}%1_f " D_UNIT_CENTIMETER "{e}";
#endif

void Ld2410Show(bool json) {
  float moving_distance = LD2410.moving_distance;
  float static_distance = LD2410.static_distance;
  float detect_distance = LD2410.detect_distance;
  if (json) {
    //                                                             cm   cm   cm                          %  %
    ResponseAppend_P(PSTR(",\"LD2410\":{\"" D_JSON_DISTANCE "\":[%1_f,%1_f,%1_f],\"" D_JSON_ENERGY "\":[%d,%d]}"),
      &moving_distance, &static_distance, &detect_distance, LD2410.moving_energy, LD2410.static_energy);
#ifdef USE_WEBSERVER
  } else {
    WSContentSend_PD(HTTP_SNS_LD2410_CM, &moving_distance, &static_distance, &detect_distance);
#endif
  }
}

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

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

  if (FUNC_INIT == function) {
    Ld2410Detect();
  }
  else if (LD2410Serial) {
    switch (function) {
      case FUNC_LOOP:
      case FUNC_SLEEP_LOOP:
        Ld2410Input();
        break;
      case FUNC_EVERY_100_MSECOND:
        Ld2410Every100MSecond();
        break;
      case FUNC_EVERY_SECOND:
        Ld2410EverySecond();
        break;
      case FUNC_JSON_APPEND:
        Ld2410Show(1);
        break;
#ifdef USE_WEBSERVER
      case FUNC_WEB_SENSOR:
        Ld2410Show(0);
        break;
#endif  // USE_WEBSERVER
      case FUNC_COMMAND:
        result = DecodeCommand(kLd2410Commands, Ld2410Command);
        break;
    }
  }
  return result;
}

#endif  // USE_LD2410
