esphome:
  name: ab8ss

esp32:
  board: esp32dev
  framework:
    type: esp-idf

wifi:
  id: wifi_component
  ssid: !secret wifi_ssid
  password: !secret wifi_password

logger:
  level: DEBUG

api:

ota:
  platform: esphome

uart:
  id: uart_bus
  tx_pin: GPIO1   # TXD op D1 Mini ESP32
  rx_pin: GPIO3   # RXD op D1 Mini ESP32
  baud_rate: 9600
  parity: NONE
  stop_bits: 1

# ---------------------------------------------------------
# QUEUE-BRUG VIA STRING
# ---------------------------------------------------------
globals:
  - id: ab8ss_queue_raw
    type: std::string
    initial_value: ""

script:
  - id: ab8ss_enqueue
    parameters:
      cmd: string
    then:
      - lambda: |-
          id(ab8ss_queue_raw) += cmd + ";";

# ---------------------------------------------------------
# DEBUG SWITCH
# ---------------------------------------------------------
switch:
  - platform: template
    id: ab8ss_debug
    name: "AB8SS Debug Mode"
    optimistic: true
    restore_mode: RESTORE_DEFAULT_OFF
# ---------------------------------------------------------
# ZONE SWITCHES → PUSH COMMANDS IN QUEUE
# ---------------------------------------------------------
  - platform: template
    id: ab8ss_zone_1
    name: "AB8SS Zone 1"
    optimistic: false 
    restore_mode: RESTORE_DEFAULT_OFF
    turn_on_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "050001FA*" }
    turn_off_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "060001F9*" }

  - platform: template
    id: ab8ss_zone_2
    name: "AB8SS Zone 2"
    optimistic: false 
    restore_mode: RESTORE_DEFAULT_OFF
    turn_on_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "050002F9*" }
    turn_off_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "060002F8*" }

  - platform: template
    id: ab8ss_zone_3
    name: "AB8SS Zone 3"
    optimistic: false 
    restore_mode: RESTORE_DEFAULT_OFF
    turn_on_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "050004F7*" }
    turn_off_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "060004F6*" }

  - platform: template
    id: ab8ss_zone_4
    name: "AB8SS Zone 4"
    optimistic: false 
    restore_mode: RESTORE_DEFAULT_OFF
    turn_on_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "050008F3*" }
    turn_off_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "060008F2*" }

  - platform: template
    id: ab8ss_zone_5
    name: "AB8SS Zone 5"
    optimistic: false 
    restore_mode: RESTORE_DEFAULT_OFF
    turn_on_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "050010EB*" }
    turn_off_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "060010EA*" }

  - platform: template
    id: ab8ss_zone_6
    name: "AB8SS Zone 6"
    optimistic: false 
    restore_mode: RESTORE_DEFAULT_OFF
    turn_on_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "050020DB*" }
    turn_off_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "060020DA*" }

  - platform: template
    id: ab8ss_zone_7
    name: "AB8SS Zone 7"
    optimistic: false 
    restore_mode: RESTORE_DEFAULT_OFF
    turn_on_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "050040BB*" }
    turn_off_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "060040BA*" }

  - platform: template
    id: ab8ss_zone_8
    name: "AB8SS Zone 8"
    optimistic: false 
    restore_mode: RESTORE_DEFAULT_OFF
    turn_on_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "0500807B*" }
    turn_off_action:
      - script.execute: { id: ab8ss_enqueue, cmd: "0600807A*" }

  - platform: template
    id: ab8ss_input_select
    name: "AB8SS Input A-B"
    optimistic: false
    restore_mode: RESTORE_DEFAULT_OFF
    turn_on_action:
      # Input B
      - script.execute: { id: ab8ss_enqueue, cmd: "050100FA*" }
    turn_off_action:
      # Input A
      - script.execute: { id: ab8ss_enqueue, cmd: "060100F9*" }

# ---------------------------------------------------------
# STATE MACHINE + ROBUUSTE PARSER + 2s COOLDOWNS
# ---------------------------------------------------------
interval:
  - interval: 20ms
    then:
      - lambda: |-
          static bool command_in_progress = false;
          static bool wifi_ready_logged = false;

          // Wachten op WiFi
          if (!id(wifi_component).is_connected()) {
            if (!wifi_ready_logged) {
              ESP_LOGI("ab8ss", "Wachten op WiFi voordat AB8SS-communicatie start");
              wifi_ready_logged = true;
            }
            return;
          }

          // Startup delay zodat UART stabiel is
          static uint32_t startup_delay = millis() + 1500;
          if (millis() < startup_delay) return;

          // RX helper
          auto read_rx = [&]() -> int {
            if (id(uart_bus).available()) {
              uint8_t c;
              if (id(uart_bus).read_byte(&c)) return (int)c;
            }
            return -1;
          };

          enum State { IDLE, ATTENTION_SEND, ATTENTION_WAIT, CMD_SEND, CMD_WAIT, DONE, ABORT };
          static State state = IDLE;

          static uint32_t deadline = 0;
          static uint32_t next_allowed = 0;
          static uint32_t next_poll = millis() + 60000;   // eerste poll pas na 1 minuut
          static uint32_t last_rx_time = 0;

          static std::string current_cmd;
          static int pos = 0;
          static int retry_count = 0;

          static std::vector<std::string> queue;
          static std::string buffer;

          // ---------------- QUEUE VULLEN ----------------
          if (id(ab8ss_queue_raw).size() > 0) {
            size_t p = 0;
            while ((p = id(ab8ss_queue_raw).find(";")) != std::string::npos) {
              queue.push_back(id(ab8ss_queue_raw).substr(0, p));
              id(ab8ss_queue_raw).erase(0, p + 1);
            }
          }

          // ---------------- PARSER: echo + status ----------------
          int rx;
          while ((rx = read_rx()) != -1) {
            char ch = (char)rx;
            last_rx_time = millis();

            if (id(ab8ss_debug).state) {
              ESP_LOGI("ab8ss", "RX: %c (0x%02X)", ch, (uint8_t)ch);
            }

            // STATUSFRAME VERWERKEN
            if (state != ATTENTION_WAIT && state != CMD_WAIT) {
              buffer.push_back(ch);

              if (ch == '*') {
                std::string frame = buffer;
                buffer.clear();

                if (id(ab8ss_debug).state) {
                  ESP_LOGI("ab8ss", "RX frame: %s", frame.c_str());
                }

                // Lege frame → commando is klaar
                if (frame == "*") {
                  command_in_progress = false;
                  next_allowed = millis() + 50;
                  next_poll = millis() + 60000;
                }

                // Volwaardige statusframe
                if (frame.size() >= 8 && frame[0] == '8' && frame[1] == '4') {
                  int mask = strtol(frame.substr(2, 4).c_str(), nullptr, 16);

                  command_in_progress = false;
                  next_allowed = millis() + 50;
                  next_poll = millis() + 60000;

                  id(ab8ss_zone_1).publish_state(mask & 0x01);
                  id(ab8ss_zone_2).publish_state(mask & 0x02);
                  id(ab8ss_zone_3).publish_state(mask & 0x04);
                  id(ab8ss_zone_4).publish_state(mask & 0x08);
                  id(ab8ss_zone_5).publish_state(mask & 0x10);
                  id(ab8ss_zone_6).publish_state(mask & 0x20);
                  id(ab8ss_zone_7).publish_state(mask & 0x40);
                  id(ab8ss_zone_8).publish_state(mask & 0x80);
                  id(ab8ss_input_select).publish_state(mask & 0x0100);
                }
              }
            }

            // ECHO-VERWERKING
            switch (state) {

              case ATTENTION_WAIT:
                if (ch == '!') {
                  deadline = millis();
                  state = CMD_SEND;
                }
                break;

              case CMD_WAIT:
                if (ch == current_cmd[pos]) {
                  pos++;
                  state = CMD_SEND;
                  deadline = millis() + 5;
                } else {
                  id(uart_bus).write_str("-");
                  if (id(ab8ss_debug).state) ESP_LOGI("ab8ss", "TX: -");
                  state = ABORT;
                }
                break;

              default:
                break;
            }
          }

          // ---------------- TX STATE MACHINE ----------------
          uint32_t now = millis();
          if (now < deadline) return;
          if (now < next_allowed) return;

          switch (state) {

            case IDLE:
              if (command_in_progress) return;

              if (!queue.empty()) {
                current_cmd = queue.front();
                queue.erase(queue.begin());
                pos = 0;
                retry_count = 0;
                ESP_LOGI("ab8ss", "Start queued commando: !%s", current_cmd.c_str());
                state = ATTENTION_SEND;
                return;
              }

              if (now > next_poll) {
                current_cmd = "8400007C*";   // statuspoll
                pos = 0;
                retry_count = 0;
                state = ATTENTION_SEND;
                next_poll = now + 60000;
                return;
              }

              break;

            case ATTENTION_SEND:
              id(uart_bus).write_str("!");
              if (id(ab8ss_debug).state) ESP_LOGI("ab8ss", "TX: !");
              deadline = now + 100;
              state = ATTENTION_WAIT;
              break;

            case CMD_SEND:
              if (pos == 0) {
                command_in_progress = true;
              }

              if (pos >= (int)current_cmd.size()) {
                state = DONE;
                break;
              }

              {
                char out[2] = { current_cmd[pos], 0 };
                id(uart_bus).write_str(out);
                if (id(ab8ss_debug).state) ESP_LOGI("ab8ss", "TX: %c", out[0]);

                bool is_last_char = (current_cmd[pos] == '*');
                deadline = now + (is_last_char ? 10 : 5);
              }

              state = CMD_WAIT;
              break;

            case DONE:
              // Als dit géén statuspoll was, plan er daarna één in
              if (current_cmd != "8400007C*") {
                queue.push_back("8400007C*");
              }

              command_in_progress = false;
              next_allowed = millis() + 50;
              state = IDLE;
              break;



            case ABORT:
              retry_count++;

              if (retry_count < 5) {
                ESP_LOGW("ab8ss", "Commando mislukt, retry %d/5: !%s", retry_count, current_cmd.c_str());
                next_allowed = now + 100;
                pos = 0;
                state = ATTENTION_SEND;
              } else {
                ESP_LOGE("ab8ss", "Commando definitief afgebroken na 5 pogingen: !%s", current_cmd.c_str());
                retry_count = 0;
                command_in_progress = false;
                next_allowed = now + 300;
                next_poll = millis() + 60000;
                state = IDLE;
              }
              break;

            case ATTENTION_WAIT:
            case CMD_WAIT:
              if (now > deadline) {
                id(uart_bus).write_str("-");
                if (id(ab8ss_debug).state) ESP_LOGI("ab8ss", "TX: -");
                state = ABORT;
              }
              break;
          }
