# ---------------------------------------------------------------------------------------
# Project:      Gevako RS6 - Smart Relay Firmware
# Author:       Gevako B.V.
# Website:      https://gevako.nl
# License:      MIT License (see LICENSE file)
# Copyright:    (c) 2026 Gevako B.V.
# ---------------------------------------------------------------------------------------
# DISCLAIMER:
# This software is provided "as is", without warranty of any kind.
# Use and modify at your own risk.
# ---------------------------------------------------------------------------------------
# HARDWARE INFO:
# Model:        Gevako RS6 (ESP32-C6)
# Revision:     V2.0.0 or later
#
# DESCRIPTION:
# This firmware provides control over 6 relays, status LED indication and
# power source detection.
# ---------------------------------------------------------------------------------------

# --- SUBSTITUTIONS ---
# This section allows you to easily customize the device settings
# without having to search through the entire code.
substitutions:
  device_name: "gevako-rs6"
  friendly_name: "Gevako RS6"
  project_name: "Gevako.Gevako RS6 Firmware"
  firmware_version: "1.4.1"
  ota_password: "gevako-ota"
  
  # AP SSID: The name of the hotspot when no wifi connection is available
  ap_ssid: "Gevako-RS6-AP"

  # --- OUTPUT CONFIGURATION ---
  # Configure the Name, GPIO Pin, and Restore Mode for each output below.
  # Restore Mode Options: "RESTORE_DEFAULT_OFF", "RESTORE_DEFAULT_ON", "RESTORE_INVERTED_DEFAULT_OFF", "RESTORE_INVERTED_DEFAULT_ON", "ALWAYS_OFF", "ALWAYS_ON" or "DISABLED"
  
  # Output 1
  gpio_output_1: GPIO0 # Don't modify
  name_output_1: "Output 1"
  rm_output_1: RESTORE_DEFAULT_OFF
  
  # Output 2
  gpio_output_2: GPIO1 # Don't modify
  name_output_2: "Output 2"
  rm_output_2: RESTORE_DEFAULT_OFF

  # Output 3
  gpio_output_3: GPIO2 # Don't modify
  name_output_3: "Output 3"
  rm_output_3: RESTORE_DEFAULT_OFF

  # Output 4
  gpio_output_4: GPIO3 # Don't modify
  name_output_4: "Output 4"
  rm_output_4: RESTORE_DEFAULT_OFF

  # Output 5
  gpio_output_5: GPIO4 # Don't modify
  name_output_5: "Output 5"
  rm_output_5: RESTORE_DEFAULT_OFF

  # Output 6
  gpio_output_6: GPIO5 # Don't modify
  name_output_6: "Output 6"
  rm_output_6: RESTORE_DEFAULT_OFF 
  
  # Green LED
  gpio_green_led_output: GPIO8  # Don't modify

  # --- INPUT CONFIGURATION ---

  # Power Source Detection
  gpio_power_source_state: GPIO14  # Don't modify
  
  # Physical Button
  gpio_physical_button: GPIO9  # Don't modify

# --- CORE CONFIGURATION ---
esphome:
  name: ${device_name}
  friendly_name: ${friendly_name}
  name_add_mac_suffix: true
  project:
    name: ${project_name}
    version: ${firmware_version}

  # CRITICAL: Restore Logic on Boot
  # This ensures that if the switches were OFF before reboot,
  # the actual hardware scanning is stopped immediately after boot.
  on_boot:
    priority: 250 # Run after hardware initialization but before WiFi
    then:
      # 1. Check Master Radio Switch State
      - if:
          condition:
            switch.is_off: bluetooth_radio_switch
          then:
            - logger.log: "Boot: Master Switch is OFF. Disabling BLE Hardware..."
            - ble.disable:
            # Force scanner switch UI to match
            - switch.template.publish:
                id: proxy_scanner_switch
                state: OFF
          else:
            # Radio is ON, check scanner state
            - if:
                condition:
                  switch.is_off: proxy_scanner_switch
                then:
                  - logger.log: "Boot: Radio ON but Scanner OFF. Stopping scan..."
                  - esp32_ble_tracker.stop_scan:

esp32:
  board: esp32-c6-devkitm-1
  variant: esp32c6
  framework:
    type: esp-idf
    version: recommended

# Base BLE component required for hardware control
esp32_ble:
  id: ble_hardware

# --- LOGGER & API ---
# Logger: Outputs logs to the USB serial port and over WiFi
logger:
  level: INFO

# API: Enables the native API for Home Assistant integration
api:
  #encryption:
    #key: "xxx" # Create and fill in api key when using encryption.

# OTA: Enables Over-The-Air updates
ota:
  - platform: esphome
    password: ${ota_password}

# --- CONNECTIVITY SETTINGS ---

wifi:
  power_save_mode: none
  #ssid: !secret wifi_ssid
  #password: !secret wifi_password

  # Fill in static IP address and gateway. Enable SSID and password when using static IP.
  #manual_ip:
    #static_ip: 192.168.x.x
    #gateway: 192.168.x.x
    #subnet: 255.255.255.0

  # Fallback AP: Creates a Hotspot when connection to router fails
  ap:
    ssid: ${ap_ssid}

  # Recovery: Re-enable Bluetooth if WiFi is lost for more than 1 minute
  on_disconnect:
    then:
      - delay: 60s
      - if:
          condition:
            not:
              wifi.connected:
          then:
            - logger.log: "WiFi disconnected for >1min. Emergency BLE enable."
            - ble.enable:
            - switch.turn_on: bluetooth_radio_switch

# Captive Portal: Web interface for initial WiFi setup
captive_portal:

# Web Server: Allows viewing device status via a web browser (IP address)
web_server:
  port: 80
  version: 3

# Improv Serial: Allows setting WiFi via USB serial
improv_serial:

# ESP32 Improv: Allows setting WiFi via Bluetooth Low Energy (BLE)
esp32_improv:
  authorizer: none

# Bluetooth Proxy: Turns the device into a Bluetooth gateway for Home Assistant
esp32_ble_tracker:
  id: ble_tracker
  scan_parameters:
    interval: 1100ms
    window: 300ms
    active: false

bluetooth_proxy:
  active: false # Passive scanner for better stability

# --- HARDWARE DEFINITIONS ---

# Globals: Internal variables used for logic/state
globals:
  - id: reset_timer_value
    type: int
    initial_value: '0'
  
  # Physical Button Hold Time Tracking
  - id: button_held_time
    type: int
    initial_value: '0'
    restore_value: false

# GPIO Definition for the Status LED (Green)
output:
  - platform: gpio
    pin: ${gpio_green_led_output}
    id: "green_led_output"

# Light Component: Controls the Status LED
light:
  - platform: binary
    name: "Status LED"
    output: green_led_output
    id: green_led
    internal: true

# --- SENSORS & DIAGNOSTICS ---
# These sensors appear in the 'Diagnostic' section of Home Assistant

sensor:
  # WiFi Signal Strength (dBm)
  - platform: wifi_signal
    name: "3. WiFi Signal Strength"
    update_interval: 60s
    entity_category: diagnostic
    icon: "mdi:wifi"
  
  # Device Uptime
  - platform: uptime
    name: "6. Uptime"
    update_interval: 60s
    entity_category: diagnostic
    icon: "mdi:timer-outline"

  # Internal Chip Temperature
  - platform: internal_temperature
    name: "5. Internal Temperature"
    entity_category: diagnostic
    icon: "mdi:thermometer"

  # Visual Countdown Timer for the Factory Reset sequence
  - platform: template
    name: "7. Factory Reset Timer"
    id: reset_countdown_sensor
    icon: "mdi:timer-alert-outline"
    unit_of_measurement: "s"
    accuracy_decimals: 0
    entity_category: diagnostic

# Text Sensors provide readable status information
text_sensor:
  - platform: wifi_info
    ip_address:
      name: "2. IP Address"
      entity_category: diagnostic
      icon: "mdi:ip-network"
    ssid:
      name: "1. Connected SSID"
      entity_category: diagnostic
      icon: "mdi:wifi-settings"

  # Power Source Logic: Detects if running on Mains or USB
  - platform: template
    name: "4. Power Source"
    icon: "mdi:power-plug"
    lambda: |-
      if (id(power_source_state).state) {
        return {"230 VAC (Mains)"};
      } else {
        return {"5 VDC (USB-C)"};
      }
    update_interval: 10s
    entity_category: diagnostic

# Binary Sensors
binary_sensor:
  # Internal Binary Sensor helper for Power Source detection
  - platform: gpio
    pin:
      number: ${gpio_power_source_state}
      mode: INPUT
    id: power_source_state
    internal: true
  
  # Physical Button on GPIO9 (with external pull-up, active low)
  - platform: gpio
    pin:
      number: ${gpio_physical_button}
      mode: INPUT
      inverted: true  # Pull to 0 when pressed
    id: physical_button
    name: "Physical Button"
    internal: true
    filters:
      - delayed_on: 50ms  # Debounce
    on_press:
      - script.execute: button_press_handler
    on_release:
      - script.stop: button_press_handler
      - if:
          condition:
            lambda: 'return id(button_held_time) >= 2000 && id(button_held_time) < 10000;'
          then:
            - logger.log: "Button held 2-10s: Triggering Reboot"
            - repeat:
                count: 3
                then:
                  - light.toggle: green_led
                  - delay: 100ms
            - delay: 500ms
            - button.press: internal_reboot_core
      # If button released before 2s, restore LED state
      - if:
          condition:
            lambda: 'return id(button_held_time) < 2000;'
          then:
            - if:
                condition:
                  wifi.connected:
                then:
                  - light.turn_on: green_led
                else:
                  # LED will continue blinking via the 1s interval

# --- MAIN OUTPUT CONTROLS ---
# The relays controlling the connected devices

switch:
  - platform: gpio
    pin: ${gpio_output_1}
    name: ${name_output_1}
    restore_mode: ${rm_output_1}
    id: output_1
  - platform: gpio
    pin: ${gpio_output_2}
    name: ${name_output_2}
    restore_mode: ${rm_output_2}
    id: output_2
  - platform: gpio
    pin: ${gpio_output_3}
    name: ${name_output_3}
    restore_mode: ${rm_output_3}
    id: output_3
  - platform: gpio
    pin: ${gpio_output_4}
    name: ${name_output_4}
    restore_mode: ${rm_output_4}
    id: output_4
  - platform: gpio
    pin: ${gpio_output_5}
    name: ${name_output_5}
    restore_mode: ${rm_output_5}
    id: output_5
  - platform: gpio
    pin: ${gpio_output_6}
    name: ${name_output_6}
    restore_mode: ${rm_output_6}
    id: output_6

  # --- CONFIGURATION / SAFEGUARDS ---
  # These controls are located in the 'Configuration' card in Home Assistant.
  # Numbering is used to enforce the correct display order.

  # Master Bluetooth Radio Switch
  - platform: template
    name: "1. Bluetooth Radio"
    id: bluetooth_radio_switch
    icon: "mdi:bluetooth"
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    optimistic: true
    turn_on_action:
      - ble.enable:
      - logger.log: "BLE Hardware enabled"
      # Note: Scanner switch remains in its current state (doesn't auto-start)
    turn_off_action:
      # First turn off the scanner cleanly
      - if:
          condition:
            switch.is_on: proxy_scanner_switch
          then:
            - switch.turn_off: proxy_scanner_switch
      - ble.disable:
      - logger.log: "BLE Hardware disabled"

  # Proxy Scanner Switch
  - platform: template
    name: "2. Bluetooth Proxy Scanner"
    id: proxy_scanner_switch
    icon: "mdi:bluetooth-transfer"
    entity_category: config
    restore_mode: RESTORE_DEFAULT_ON
    # Important: Optimistic false forces the switch to wait for our confirmation.
    # If the condition fails, we never confirm ON, and it stays OFF.
    optimistic: false
    turn_on_action:
      - if:
          condition:
            switch.is_on: bluetooth_radio_switch
          then:
            # Radio is already on, just start scanner
            - esp32_ble_tracker.start_scan:
            - logger.log: "Scanner started (radio already active)"
            - switch.template.publish:
                id: proxy_scanner_switch
                state: ON
          else:
            # Radio is off, enable it first, then start scanner
            - logger.log: "Enabling BLE Radio first, then starting scanner..."
            - switch.turn_on: bluetooth_radio_switch
            - delay: 500ms  # Give hardware time to initialize
            - esp32_ble_tracker.start_scan:
            - logger.log: "Scanner started (auto-enabled radio)"
            - switch.template.publish:
                id: proxy_scanner_switch
                state: ON
    turn_off_action:
      - esp32_ble_tracker.stop_scan:
      - logger.log: "Scanner stopped"
      # We must manually publish the new state because optimistic is false
      - switch.template.publish:
          id: proxy_scanner_switch
          state: OFF

  # STEP 2: Unlock Switch
  # This switch must be enabled before a Reset or WiFi clear can be performed.
  - platform: template
    name: "4. Unlock Factory Reset"
    id: factory_reset_unlock
    icon: "mdi:shield-lock-open-outline"
    entity_category: config
    optimistic: true
    turn_on_action:
      - script.execute: factory_reset_sequence
    turn_off_action:
      - script.stop: factory_reset_sequence
      - globals.set:
          id: reset_timer_value
          value: '0'
      - sensor.template.publish:
          id: reset_countdown_sensor
          state: 0
      - light.turn_on: green_led

# --- BUTTONS ---

button:
  # Reboot Button
  - platform: restart
    name: "3. Reboot Device"
    icon: "mdi:restart"
    entity_category: config

  # Internal helpers (hidden actions)
  - platform: factory_reset
    id: internal_factory_reset_core
    internal: true

  - platform: restart
    id: internal_reboot_core
    internal: true

  # Confirm Factory Reset
  # Wipes ALL data and resets device to factory defaults.
  # Only works if the "Unlock" switch is ON.
  - platform: template
    name: "5. Confirm Factory Reset"
    id: factory_reset_confirm
    icon: "mdi:alert-remove"
    entity_category: config
    on_press:
      then:
        - if:
            condition:
              switch.is_on: factory_reset_unlock
            then:
              - logger.log: "FACTORY RESET TRIGGERED BY USER"
              - script.stop: factory_reset_sequence
              - repeat:
                  count: 10
                  then:
                    - light.toggle: green_led
                    - delay: 50ms
              - button.press: internal_factory_reset_core
            else:
              - logger.log: "Refused: Device locked."

# --- AUTOMATION & SCRIPTS ---

# Script: Handles the 10-second safety countdown
# It blinks the LED and updates the timer sensor.
script:
  - id: factory_reset_sequence
    then:
      - logger.log: "Unlock Active! Countdown started."
      - globals.set:
          id: reset_timer_value
          value: '10'
      
      # Countdown Loop (10 seconds)
      - repeat:
          count: 10
          then:
            - sensor.template.publish:
                id: reset_countdown_sensor
                state: !lambda 'return id(reset_timer_value);'
            
            # Blink LED (250ms period: 125ms ON / 125ms OFF)
            - repeat:
                count: 4
                then:
                   - light.toggle: green_led
                   - delay: 125ms
                   - light.toggle: green_led
                   - delay: 125ms
            
            # Decrement Timer
            - globals.set:
                id: reset_timer_value
                value: !lambda 'return id(reset_timer_value) - 1;'
      
      # Timeout Action: Lock the device again
      - logger.log: "Timeout: Reset options locked automatically."
      - switch.turn_off: factory_reset_unlock
      - sensor.template.publish:
          id: reset_countdown_sensor
          state: 0

  # Script: Physical Button Press Handler
  # Handles timing and LED behavior for button hold actions
  - id: button_press_handler
    then:
      # Turn off LED immediately when button pressed
      - light.turn_off: green_led
      - globals.set:
          id: button_held_time
          value: '0'
      
      # Wait 2 seconds
      - delay: 2s
      - globals.set:
          id: button_held_time
          value: '2000'
      
      # Start LED blinking (250ms period for 8 seconds = 32 cycles)
      - repeat:
          count: 32
          then:
            - light.toggle: green_led
            - delay: 125ms
            - light.toggle: green_led
            - delay: 125ms
            - globals.set:
                id: button_held_time
                value: !lambda 'return id(button_held_time) + 250;'
      
      # If we reach here, button was held for 10+ seconds
      - logger.log: "Button held 10s: Triggering Factory Reset"
      
      # Fast blink confirmation (10 times)
      - repeat:
          count: 10
          then:
            - light.toggle: green_led
            - delay: 50ms
      
      # Trigger factory reset
      - button.press: internal_factory_reset_core

# Intervals
interval:
  # Checks connectivity every second and updates LED status.
  - interval: 1s
    then:
      - if:
          condition:
            # Only run when NOT in reset mode (script not running)
            and:
              - not:
                  script.is_running: factory_reset_sequence
              - not:
                  script.is_running: button_press_handler
          then:
            - if:
                condition:
                  not:
                    wifi.connected:
                then:
                  # Blink if no WiFi connection
                  - light.toggle: green_led
                else:
                  # Solid ON if connected
                  - light.turn_on: green_led

  # Restarts the BLE Scan every 5 minutes to prevent freeze/buffer issues.
  # This is a proven workaround for ESP32-C6 BLE stability with passive scanning.
  - interval: 5min
    then:
      - if:
          condition:
            and:
              # Only perform maintenance if both Radio and Scanner are active
              - switch.is_on: bluetooth_radio_switch
              - switch.is_on: proxy_scanner_switch
          then:
            - logger.log: "Maintenance: Restarting BLE Scanner to prevent buffer overflow..."
            - esp32_ble_tracker.stop_scan:
            - delay: 2s
            - esp32_ble_tracker.start_scan:
            - logger.log: "BLE Scanner restarted successfully"