# !!! WHAT YOU WILL NEED TO CHANGE !!!
# If you need any help, don't hesitate to ask for it in the discord or discussions!

# platform, based on https://esphome.io/components/#supported-microcontrollers 
esp32:
  board: esp32dev
  framework:
    type: esp-idf

# You will need to figure out which pins work on your board for UART.
substitutions:
  uart_tx: <SET BASED ON YOUR DEVICE>
# THIS IS A BETA VERSION! IT MAY BE UNSABLE OR NOT WORKING, FOR THE LATEST PLEASE DOWNLOAD FROM RELEASES!
  uart_rx: <SET BASED ON YOUR DEVICE>

# If you want to have access via the webserver, uncomment the following 2 lines (remove the #)
#web_server:
#  port: 80

esphome:
  name: neato-vacuum # If you want another name, change it here, remeber to replace it in the ha-card.yaml!
##############################################################################################################################



#   Combatability between esphome config and this card is ensured as long as the
#   major version's match, for relase candicates, betas etc (indicated by X.X-a/b/rc.X), the version
#   has to match perfectly otherwise compatability is not promised. 
#
#   Please try to keep the versions the same, and check the github for updates!
  project: 
    name: philip2809.neato-connected
    version: "1.1.1"

  on_boot:
    - priority: 600
      then:
        - lambda: !lambda |-
            id(uiState).publish_state("Starting...");

    - priority: -100
      then:
        - lambda: !lambda |-
            id(uiState).publish_state("Starting...");

            ESPTime t = ESPTime::from_epoch_local(id(g_last_cleaning_start_time));
            std::string ts = t.strftime("%Y-%m-%d %H:%M:%S");
            id(last_cleaning_time).publish_state(ts.c_str());

            if (id(g_last_cleaning_type) == 0) {
              id(last_cleaning_type).publish_state("HOUSE");
            } else if (id(g_last_cleaning_type) == 1) {
              id(last_cleaning_type).publish_state("SPOT");
            }

            id(last_cleaning_duration).publish_state(id(g_last_cleaning_duration));

script:
  - id: get_user_settings
    mode: queued
    then:
      - delay: 100ms
      - uart.write: "GetUserSettings \n"

  - id: get_all_data
    mode: queued
    then: 
      - uart.write: "GetErr \n"  
      - delay: 50ms  
      - uart.write: "GetState \n"    
      - delay: 50ms  
      - uart.write: "GetCharger \n"    
      - delay: 50ms  
      - uart.write: "GetVersion \n"    
      - delay: 50ms  
      - uart.write: "GetUserSettings \n"  
      - delay: 50ms
      - uart.write: "GetWarranty \n"
      - delay: 50ms  
      - uart.write: "TestMode \n"
  
  - id: inital_data_get # when the robot has booted, run this inital fetch after 5 seconds of being able to send commands, the 5 sec needed to the robot can calculate battery levet etc.
    mode: queued
    then: 
      - delay: 5sec
      - script.execute: get_all_data

  - id: robotShutdown
    mode: queued
    then:
      - lambda: 'id(uiState).publish_state("Shutting down...");'
      - uart.write: "TestMode On \n"
      - delay: 100ms
      - uart.write: "SetSystemMode Shutdown \n"

  - id: powercycle
    mode: queued
    then:
      - lambda: 'id(initial_ready) = false;' # I want to refetch all data in case we powercycle
      - lambda: 'id(uiState).publish_state("Shutting down...");'
      - uart.write: "TestMode On \n"
      - delay: 100ms
      - uart.write: "SetSystemMode PowerCycle \n"

  - id: cleaning_stopped
    then: 
      - lambda: !lambda |-
          uint64_t now_ts = id(homeassistant_time).now().timestamp;
          uint64_t start_ts = id(g_last_cleaning_start_time);

          if (start_ts > 0) {
              uint32_t duration_min = (now_ts - start_ts) / 60;
              id(g_last_cleaning_duration) = duration_min;
              id(last_cleaning_duration).publish_state(duration_min);
          }


globals:
  - id: initial_ready
    type: bool
    restore_value: no
    initial_value: 'false'

  # --- STORE IN FLASH, MAX 96 BYTES! ---
  - id: g_last_cleaning_start_time         # 8 BYTES
    type: uint64_t
    restore_value: yes

  - id: g_last_cleaning_type               # 4 BYTES
    type: uint32_t
    restore_value: yes

  - id: g_last_cleaning_duration           # 4 BYTES
    type: uint32_t
    restore_value: yes
  # --- TOTAL BYTES USED: 16 BYTES ---


interval:
  - interval: 2sec
    then:
      - uart.write: "GetErr \n"
      - uart.write: "GetState \n"
  - interval: 2min
    then: 
      - uart.write: "GetCharger \n"

api:
  encryption:
    key: !secret neato_vacuum_api

  actions:
    - action: send_cmd
      variables:
        command: string
      then:
        - uart.write: !lambda |-
            // Convert to std::string
            std::string s = command;

            // IMPORTANT: keep newline exactly like YAML version
            if (s.empty() || s.back() != '\n')
              s += "\n";

            // convert string → vector<uint8_t>
            return std::vector<uint8_t>(s.begin(), s.end());

    - action: play_sound
      variables:
        soundid: int
      then:
        - uart.write: !lambda |-
            // Create std::string
            std::string s = "PlaySound SoundId " + std::to_string(soundid) + "\n";
            // convert string → vector<uint8_t>
            return std::vector<uint8_t>(s.begin(), s.end());

ota:
  - platform: esphome
    password: !secret neato_vacuum_ota


wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  # If your router sets another TLD for local devices, specify that here
  #domain: .<whatever you are using, e.g. lan>

logger:
  baud_rate: 0

uart:
  id: uart_bus
  baud_rate: 115200
  tx_pin: ${uart_tx}
  rx_pin: ${uart_rx}
  rx_buffer_size: 65535
  debug:
    dummy_receiver: true
    direction: RX
    after:
      bytes: 0
      #timeout: 5sec
      #delimiter: "\n"
      delimiter: "\x1A"
    sequence:
      - lambda: !lambda |-
          UARTDebug::log_string(direction, bytes);

          // convert bytes -> std::string
          std::string str(bytes.begin(), bytes.end());

          // ---- TRIM FUNCTION ----
          auto trim = [](std::string &s) {
            // trim leading
            s.erase(s.begin(), std::find_if(s.begin(), s.end(),
                [](unsigned char ch){ return !std::isspace(ch); }));
            // trim trailing
            s.erase(std::find_if(s.rbegin(), s.rend(),
                [](unsigned char ch){ return !std::isspace(ch); }).base(), s.end());
          };

          // --- HEX DECODER ---
          auto hex_to_uint32 = [](const std::string &hex) -> uint32_t {
              return static_cast<uint32_t>(strtoul(hex.c_str(), nullptr, 16));
          };

          // ---- SPLIT ON CRLF ----
          std::vector<std::string> lines;
          size_t start = 0;
          size_t end = 0;

          while ((end = str.find("\r\n", start)) != std::string::npos) {
            std::string line = str.substr(start, end - start);
            trim(line);
            if (!line.empty()) lines.push_back(line);
            start = end + 2;
          }

          if (start < str.size()) {
            std::string last = str.substr(start);
            trim(last);
            if (!last.empty()) lines.push_back(last);
          }

          if (lines.empty()) return;

          // ---- DETECT WHICH COMMAND WAS USED ----
          std::string command = lines[0];  // first line (GetErr, GetState, etc.)


          ESP_LOGI("uart_parser", "COMMAND: %s", command.c_str());
          // ---- OPTIONAL: print each line for debugging ----
          for (auto &line : lines) {
            std::vector<uint8_t> v(line.begin(), line.end());
            UARTDebug::log_string(direction, v);
            //ESP_LOGI("uart_parser", "LINE: %s", line.c_str());
          }



          // ---- SPECIAL PARSING FOR GetErr ----
          if (command == "GetErr") {
            // A GetErr response looks like:
            // 0: GetErr
            // 1: Error
            // 2: <error1>
            // 3: Alert
            // 4: <error2>
            // 5: USB state
            // 6: NOT connected

            if (lines.size() < 7) return;
            if (lines[1] != "Error" || lines[3] != "Alert") return;

            id(robotError).publish_state(lines[2].c_str());
            id(robotAlert).publish_state(lines[4].c_str());
            id(usbConnected).publish_state(lines[6].find("NOT connected") == std::string::npos);

            // If we can get erros/alerts, the system is ready for other commands:
            if (!id(initial_ready)) {
                id(initial_ready) = true;
                id(inital_data_get).execute();
            }
          }
          else if (command == "GetCharger") {
            // Format after the header:
            // Label,Value
            // FuelPercent,53
            // BatteryOverTemp,0
            // ...

            for (size_t i = 2; i < lines.size(); i++) {
              std::string &line = lines[i];

              size_t comma = line.find(',');
              if (comma == std::string::npos) continue;

              std::string key = line.substr(0, comma);
              std::string value = line.substr(comma + 1);

              // Trim them:
              trim(key);
              trim(value);

              ESP_LOGI("charger", "Parsed: key='%s' value='%s'",
                        key.c_str(), value.c_str());

              // ---- MATCH KEYS AND PUBLISH ----
                   if (key == "FuelPercent") id(chargerFuelPercent).publish_state(atof(value.c_str()));
              else if (key == "BatteryOverTemp") id(chargerBatteryOverTemp).publish_state(value == "1");
              else if (key == "ChargingActive") id(chargerChargingActive).publish_state(value == "1");
              else if (key == "ChargingEnabled") id(chargerChargingEnabled).publish_state(value == "1");
              else if (key == "ConfidentOnFuel") id(chargerConfidentOnFuel).publish_state(value == "1");
              else if (key == "OnReservedFuel") id(chargerOnReservedFuel).publish_state(value == "1");
              else if (key == "EmptyFuel") id(chargerEmptyFuel).publish_state(value == "1");
              else if (key == "BatteryFailure") id(chargerBatteryFailure).publish_state(value == "1");
              else if (key == "ExtPwrPresent") id(chargerExtPwrPresent).publish_state(value == "1");
              else if (key == "ThermistorPresent") id(chargerThermistorPresent).publish_state(value == "1");
              else if (key == "BattTempCAvg") id(chargerBattTempCAvg).publish_state(atof(value.c_str()));
              else if (key == "VBattV") id(chargerVBattV).publish_state(atof(value.c_str()));
              else if (key == "VExtV") id(chargerVExtV).publish_state(atof(value.c_str()));
              else if (key == "Charger_mAH") id(chargerCharger_mAH).publish_state(atof(value.c_str()));
              else if (key == "Discharge_mAH") id(chargerDischarge_mAH).publish_state(atof(value.c_str()));
            }
          }

          else if (command == "GetWarranty") {
              // Format after the header:
              // Item,Value
              // CumulativeCleaningTimeInSecs,001ce34f
              // CumulativeBatteryCycles,01f7
              // ValidationCode,15648b6e

              for (size_t i = 2; i < lines.size(); i++) {
                  std::string &line = lines[i];
                  size_t comma = line.find(',');
                  if (comma == std::string::npos) continue;

                  std::string key = line.substr(0, comma);
                  std::string value = line.substr(comma + 1);

                  // Trim whitespace
                  trim(key);
                  trim(value);
                  
                  if (key == "CumulativeBatteryCycles") {
                      uint32_t cycles = hex_to_uint32(value);
                      id(batteryCycles).publish_state(cycles);
                  }
              }
          }

          else if (command == "GetVersion") {
            for (auto &line : lines) {
              size_t comma = line.find(',');
              if (comma == std::string::npos) continue;

              std::string key = line.substr(0, comma);
              std::string value = line.substr(comma + 1);
              trim(key);
              trim(value);

                   if (key == "MainBoard Serial Number") id(mainBoardSerial).publish_state(value.c_str());
              else if (key == "MainBoard Version") id(mainBoardVersion).publish_state(value.c_str());
              else if (key == "Serial Number") id(robotSerial).publish_state(value.c_str());
              else if (key == "Model") id(robotModel).publish_state(value.c_str());
              else if (key == "Time Local") id(timeLocal).publish_state(value.c_str());
              else if (key == "Time UTC") id(timeUTC).publish_state(value.c_str());
            }
          }
          // ------------------------------ TESTMODE -----------------------------------------
          else if (command == "TestMode") {
            ESP_LOGI("testmode", "LINE1: %s", lines[1].c_str());
            //ESP_LOGI("testmode", "LINE1: %s", lines[1].c_str());
            if (lines[1].find(": On") != std::string::npos) id(testMode).publish_state(true);
            else if (lines[1].find(": Off") != std::string::npos) id(testMode).publish_state(false);
          } 
          
          else if (command == "GetState") {
              std::string &states_line = lines[1];  // "Current UI State is: ...\nCurrent Robot State is: ...\n\x1A"

              size_t sep = states_line.find('\n');
              if (sep == std::string::npos) return;  // safety

              std::string ui_line = states_line.substr(0, sep);
              std::string robot_line = states_line.substr(sep + 1);

              static const char* prefix_ui = "Current UI State is: ";
              static const char* prefix_robot = "Current Robot State is: ";

              // Extract UI state
              if (ui_line.find(prefix_ui) == 0) {
                  std::string ui_state = ui_line.substr(strlen(prefix_ui));
                  trim(ui_state);
                  std::string old_state = id(uiState).state;

                  bool old_spot = (old_state == "UIMGR_STATE_SPOTCLEANINGPAUSED" ||
                                  old_state == "UIMGR_STATE_SPOTCLEANINGRUNNING");

                  bool new_spot = (ui_state == "UIMGR_STATE_SPOTCLEANINGPAUSED" ||
                                  ui_state == "UIMGR_STATE_SPOTCLEANINGRUNNING");

                  bool old_house = (old_state == "UIMGR_STATE_HOUSECLEANINGPAUSED" ||
                                    old_state == "UIMGR_STATE_HOUSECLEANINGRUNNING");

                  bool new_house = (ui_state == "UIMGR_STATE_HOUSECLEANINGPAUSED" ||
                                    ui_state == "UIMGR_STATE_HOUSECLEANINGRUNNING");
                  // Cleaning started
                  if (!old_spot && new_spot) {
                      // Mark start time
                      id(g_last_cleaning_start_time) = id(homeassistant_time).now().timestamp;
                      id(g_last_cleaning_type) = 1;  // SPOT = 1

                      id(last_cleaning_time).publish_state(id(homeassistant_time).now().strftime("%Y-%m-%d %H:%M:%S"));
                      id(last_cleaning_type).publish_state("SPOT");
                  } else if (!old_house && new_house) {
                      id(g_last_cleaning_start_time) = id(homeassistant_time).now().timestamp;
                      id(g_last_cleaning_type) = 0;  // HOUSE = 0

                      id(last_cleaning_time).publish_state(id(homeassistant_time).now().strftime("%Y-%m-%d %H:%M:%S"));
                      id(last_cleaning_type).publish_state("HOUSE");
                  } else {
                      // Cleaning stopped
                      bool old_cleaning = old_spot || old_house;
                      bool new_cleaning = new_spot || new_house;

                      if (old_cleaning && !new_cleaning) id(cleaning_stopped).execute();
                  }

                  id(uiState).publish_state(ui_state.c_str());
              }

              // Extract Robot state
              if (robot_line.find(prefix_robot) == 0) {
                  std::string robot_state = robot_line.substr(strlen(prefix_robot));
                  trim(robot_state);
                  id(robotState).publish_state(robot_state.c_str());
              }
          }


          else if (command == "GetUserSettings") {
            for (auto &line : lines) {
              trim(line);

              // Skip lines that start with binary/invalid chars
              if (!line.empty() && static_cast<unsigned char>(line[0]) >= 0x80) continue;

              // ---- Key,Value lines ----
              size_t comma = line.find(',');
              if (comma != std::string::npos) {
                std::string key = line.substr(0, comma);
                std::string value = line.substr(comma + 1);
                trim(key);
                trim(value);

                     if (key == "Language") id(language).publish_state(value.c_str());
                else if (key == "ClickSounds") id(clickSounds).publish_state(value == "ON");
                else if (key == "LED") id(led).publish_state(value == "ON");
                else if (key == "Wall Enable") id(wallEnable).publish_state(value == "ON");
                else if (key == "Eco Mode") id(ecoMode).publish_state(value == "ON");
                else if (key == "IntenseClean") id(intenseClean).publish_state(value == "ON");
                else if (key == "WiFi") id(wifiOnOff).publish_state(value == "ON");
                else if (key == "Melody Sounds") id(melodySounds).publish_state(value == "ON");
                else if (key == "Warning Sounds") id(warningSounds).publish_state(value == "ON");
                else if (key == "Bin Full Detect") id(binFullDetect).publish_state(value == "ON");
                else if (key == "Filter Change Time (seconds)") id(filterChangeTime).publish_state(atof(value.c_str()));
                else if (key == "Brush Change Time (seconds)") id(brushChangeTime).publish_state(atof(value.c_str()));
                else if (key == "Dirt Bin Alert Reminder Interval (minutes)") id(dirtBinAlertReminder).publish_state(atof(value.c_str()));
              } 
              // ---- Special lines without commas ----
              else if (line.find("Current Dirt Bin Runtime is:") == 0) {
                std::string val = line.substr(strlen("Current Dirt Bin Runtime is:"));
                trim(val);
                id(currentDirtBinRuntime).publish_state(atof(val.c_str()));
              } 
              else if (line.find("Number of Cleanings where Dust Bin was Full is:") == 0) {
                std::string val = line.substr(strlen("Number of Cleanings where Dust Bin was Full is:"));
                trim(val);
                id(numberDustBinFull).publish_state(atof(val.c_str()));
              } 
              else if (line.find("Schedule is Enabled") != std::string::npos) id(scheduleEnabled).publish_state(true);
              else if (line.find("Schedule is Disabled") != std::string::npos) id(scheduleEnabled).publish_state(false);
            }
          } else if (command.find("ARCHES Board") != std::string::npos) id(uiState).publish_state("Starting...");

          //else {
            //  id(uiState).publish_state(command.c_str());
          //}


sensor:
  # --- GetCharger ---
  - platform: template
    id: chargerFuelPercent
    name: "Fuel Percent"
    unit_of_measurement: "%"

  - platform: template
    id: chargerBattTempCAvg
    name: "Battery Temp C Avg"
    unit_of_measurement: "°C"
    entity_category: "diagnostic"

  - platform: template
    id: chargerVBattV
    name: "Battery Voltage V"
    unit_of_measurement: "V"
    entity_category: "diagnostic"

  - platform: template
    id: chargerVExtV
    name: "External Voltage V"
    unit_of_measurement: "V"
    entity_category: "diagnostic"

  - platform: template
    id: chargerCharger_mAH
    name: "Charger mAh"
    unit_of_measurement: "mAh"
    entity_category: "diagnostic"

  - platform: template
    id: chargerDischarge_mAH
    name: "Discharge mAh"
    unit_of_measurement: "mAh"
    entity_category: "diagnostic"

  # --- GetUserSettings ---
  - platform: template
    name: "Filter Change Time"
    id: filterChangeTime
    unit_of_measurement: "s"
    entity_category: "diagnostic"

  - platform: template
    name: "Brush Change Time"
    id: brushChangeTime
    unit_of_measurement: "s"
    entity_category: "diagnostic"

  - platform: template
    name: "Dirt Bin Alert Reminder"
    id: dirtBinAlertReminder
    unit_of_measurement: "min"
    entity_category: "diagnostic"

  - platform: template
    name: "Current Dirt Bin Runtime"
    id: currentDirtBinRuntime
    unit_of_measurement: "s"
    entity_category: "diagnostic"

  - platform: template
    name: "Number of Full Dust Bin Cleanings"
    id: numberDustBinFull
    unit_of_measurement: "count"
    entity_category: "diagnostic"

  # --- GetWarranty ---
  - platform: template
    name: "Battery Cycles"
    id: batteryCycles
    unit_of_measurement: "cycles"
    entity_category: "diagnostic"

  # --- Last cleaning ---
  - platform: template
    name: "Last cleaning duration"
    id: last_cleaning_duration
    unit_of_measurement: "min"


binary_sensor:
  # --- GetErr ---
  - platform: template
    id: usbConnected
    name: "USB Connected"
    entity_category: "diagnostic"

  # --- GetCharger ---
  - platform: template
    id: chargerBatteryOverTemp
    name: "Battery Over Temp"
    entity_category: "diagnostic"

  - platform: template
    id: chargerChargingActive
    name: "Charging Active"
    entity_category: "diagnostic"

  - platform: template
    id: chargerChargingEnabled
    name: "Charging Enabled"
    entity_category: "diagnostic"

  - platform: template
    id: chargerConfidentOnFuel
    name: "Confident On Fuel"
    entity_category: "diagnostic"

  - platform: template
    id: chargerOnReservedFuel
    name: "On Reserved Fuel"
    entity_category: "diagnostic"

  - platform: template
    id: chargerEmptyFuel
    name: "Empty Fuel"
    entity_category: "diagnostic"

  - platform: template
    id: chargerBatteryFailure
    name: "Battery Failure"
    entity_category: "diagnostic"

  - platform: template
    id: chargerExtPwrPresent
    name: "Ext Power Present"
    entity_category: "diagnostic"

  - platform: template
    id: chargerThermistorPresent
    name: "Thermistor Present"
    entity_category: "diagnostic"

time:
  - platform: homeassistant
    id: homeassistant_time

text_sensor:
  # --- Last cleaning ---
  - platform: template
    name: "Last cleaning time"
    id: last_cleaning_time

  - platform: template
    name: "Last cleaning type"
    id: last_cleaning_type

  # --- GetErr ---
  - platform: template
    id: robotError
    name: "Robot Error"

  - platform: template
    id: robotAlert
    name: "Robot Alert"

  # --- GetVersion ---
  - platform: template
    id: mainBoardSerial
    name: "MainBoard Serial Number"
    entity_category: "diagnostic"

  - platform: template
    id: mainBoardVersion
    name: "MainBoard Version"
    entity_category: "diagnostic"

  - platform: template
    id: robotSerial
    name: "Serial Number"
    entity_category: "diagnostic"

  - platform: template
    id: robotModel
    name: "Model"
    entity_category: "diagnostic"

  - platform: template
    id: timeLocal
    name: "Time Local"
    entity_category: "diagnostic"

  - platform: template
    id: timeUTC
    name: "Time UTC"
    entity_category: "diagnostic"

  # --- GetState ---
  - platform: template
    name: "UI State"
    id: uiState

  - platform: template
    name: "Robot State"
    id: robotState

  # --- GetUserSettings ---
  - platform: template
    name: "Language"
    id: language
    entity_category: "diagnostic"


switch:
  - platform: template
    name: "Test Mode"
    # optimistic: true
    id: testMode
    turn_on_action:
      - uart.write: "TestMode On \n"
      - delay: 500ms
      - uart.write: "TestMode \n"
    turn_off_action:
      - uart.write: "TestMode Off \n"
      - delay: 500ms
      - uart.write: "TestMode \n"
    entity_category: "diagnostic"

  - platform: template
    name: "Click sounds"
    optimistic: true
    id: clickSounds
    turn_on_action:
      - uart.write: "SetUserSettings ButtonClick ON \n"
      - script.execute: get_user_settings
    turn_off_action:
      - uart.write: "SetUserSettings ButtonClick OFF \n"
      - script.execute: get_user_settings
    entity_category: "diagnostic"

  - platform: template
    name: "LED"
    optimistic: true
    id: led
    turn_on_action:
      - uart.write: "SetUserSettings StealthLED ON \n"
      - script.execute: get_user_settings
    turn_off_action:
      - uart.write: "SetUserSettings StealthLED OFF \n"
      - script.execute: get_user_settings
    entity_category: "diagnostic"

  - platform: template
    name: "Wall Enable"
    optimistic: true
    id: wallEnable
    turn_on_action:
      - uart.write: "SetUserSettings WallEnable ON \n"
      - delay: 100ms
      - uart.write: "GetUserSettings \n"
    turn_off_action:
      - uart.write: "SetUserSettings WallEnable OFF \n"
      - delay: 100ms
      - uart.write: "GetUserSettings \n"
    entity_category: "diagnostic"

  - platform: template
    name: "Eco Mode"
    optimistic: true
    id: ecoMode
    turn_on_action:
      - uart.write: "SetUserSettings EcoMode ON \n"
      - script.execute: get_user_settings
    turn_off_action:
      - uart.write: "SetUserSettings EcoMode OFF \n"
      - script.execute: get_user_settings
    entity_category: "diagnostic"

  - platform: template
    name: "IntenseClean"
    optimistic: true
    id: intenseClean
    turn_on_action:
      - uart.write: "SetUserSettings IntenseClean ON \n"
      - script.execute: get_user_settings
    turn_off_action:
      - uart.write: "SetUserSettings IntenseClean OFF \n"
      - script.execute: get_user_settings
    entity_category: "diagnostic"

  - platform: template
    name: "WiFi"
    optimistic: true
    id: wifiOnOff
    turn_on_action:
      - uart.write: "SetUserSettings WiFi ON \n"
      - script.execute: get_user_settings
    turn_off_action:
      - uart.write: "SetUserSettings WiFi OFF \n"
      - script.execute: get_user_settings
    entity_category: "diagnostic"

  - platform: template
    name: "Melody Sounds"
    optimistic: true
    id: melodySounds
    turn_on_action:
      - uart.write: "SetUserSettings Melodies ON \n"
      - script.execute: get_user_settings
    turn_off_action:
      - uart.write: "SetUserSettings Melodies OFF \n"
      - script.execute: get_user_settings
    entity_category: "diagnostic"

  - platform: template
    name: "Warning Sounds"
    optimistic: true
    id: warningSounds
    turn_on_action:
      - uart.write: "SetUserSettings Warnings ON \n"
      - script.execute: get_user_settings
    turn_off_action:
      - uart.write: "SetUserSettings Warnings OFF \n"
      - script.execute: get_user_settings
    entity_category: "diagnostic"

  - platform: template
    name: "Bin Full Detect"
    optimistic: true
    id: binFullDetect
    turn_on_action:
      - uart.write: "SetUserSettings BinFullDetect ON \n"
      - script.execute: get_user_settings
    turn_off_action:
      - uart.write: "SetUserSettings BinFullDetect OFF \n"
      - script.execute: get_user_settings
    entity_category: "diagnostic"

  - platform: template
    name: "Schedule"
    optimistic: true
    id: scheduleEnabled
    turn_on_action:
      - uart.write: "SetUserSettings Schedule ON \n"
      - script.execute: get_user_settings
    turn_off_action:
      - uart.write: "SetUserSettings Schedule OFF \n"
      - script.execute: get_user_settings
    entity_category: "diagnostic"


number:
  # --- Spot cleaning ---
  - platform: template
    id: spot_width
    min_value: 100
    max_value: 400
    step: 1
    unit_of_measurement: cm
    mode: SLIDER
    name: Spot Clean Width
    optimistic: true

  - platform: template
    id: spot_height
    min_value: 100
    max_value: 400
    step: 1
    unit_of_measurement: cm
    mode: SLIDER
    name: Spot Clean Height
    optimistic: true


select:
  - platform: template
    name: "Navigation Mode"
    id: navigationMode
    optimistic: true
    options:
      - "Normal"
      - "Gentle"
      - "Deep"
      - "Quick"
    icon: "mdi:robot-vacuum"
    on_value:
      then:
        - uart.write: !lambda |-
            std::string cmd = std::string("SetNavigationMode ") + x + "\n";
            return std::vector<uint8_t>(cmd.begin(), cmd.end());


button:
  - platform: template
    name: "House Clean"
    icon: mdi:home
    on_press:
      - uart.write: "Clean \n"

  - platform: template
    name: "Spot Clean"
    icon: mdi:target
    on_press:
      - uart.write: "Clean Spot \n"

  - platform: template
    name: "Spot Clean (Height & Width)"
    icon: mdi:target
    on_press:
      then:
        - uart.write: !lambda |-
            // Convert numbers to integers
            int w = (int) id(spot_width).state;
            int h = (int) id(spot_height).state;

            std::string cmd = "Clean Spot Width " + std::to_string(w) + " Height " + std::to_string(h) + "\n";
            return std::vector<uint8_t>(cmd.begin(), cmd.end());

  - platform: template
    name: "Stop Cleaning"
    icon: mdi:stop
    on_press:
      - uart.write: "Clean Stop \n"
      - script.execute: cleaning_stopped
      - delay: 100ms
      - script.execute: powercycle

  - platform: template
    name: "Pause Cleaning"
    icon: mdi:pause
    on_press:
      # Set SetUIError dance makes sure the robot realizes it is in the "Paused" state, otherwise it will keep reporting "Running" until you restart or press the button
      - uart.write: "Clean Stop \n"
      - delay: 50ms
      - uart.write: "SetUIError setalert UI_ALERT_OLD_ERROR \n"
      - delay: 50ms
      - uart.write: "SetUIError clearalert UI_ALERT_OLD_ERROR \n"

  - platform: template
    name: "Resume Cleaning"
    icon: mdi:play
    on_press:
      - uart.write: !lambda |-
            // If we are in house cleaning, to resume just send clean, if we are in spot cleaning we append spot
            std::string state = id(uiState).state;
            std::string cmd = "Clean";

            if (state == "UIMGR_STATE_SPOTCLEANINGPAUSED") cmd += " Spot";

            cmd += " \n";
            return std::vector<uint8_t>(cmd.begin(), cmd.end());

  - platform: template
    name: "Locate Robot"
    icon: mdi:volume-high
    on_press:
      - uart.write: "PlaySound SoundId 20 \n"  

  - platform: template
    name: "Update status"
    icon: mdi:refresh
    on_press:
      - script.execute: get_all_data 
    entity_category: "diagnostic"

  - platform: template
    name: "Shutdown"
    icon: mdi:power
    on_press:
      - script.execute: robotShutdown 
    entity_category: "diagnostic"

  - platform: template
    name: "PowerCycle"
    icon: mdi:restart
    on_press:
      - script.execute: powercycle 
    entity_category: "diagnostic"

  - platform: restart
    name: Reboot ESP
    
