# Board: Generic ESP8266 Board (Generic)
#
# SmartHomeGeeks.io Smart Ice Maker
#
# FINAL CURRENT WIRING
#
# Red    -> D7 -> GPIO13
# Yellow -> D6 -> GPIO12
# Orange -> D5 -> GPIO14
# Green  -> D1 -> GPIO5

esphome:
  name: ice-maker
  friendly_name: Ice Maker


# =========================================================
# ESP8266
# =========================================================

esp8266:
  board: esp01_1m
  early_pin_init: false


# =========================================================
# LOGGING
# =========================================================

logger:


# =========================================================
# HOME ASSISTANT API
# =========================================================

api:
  encryption:
    key: !secret api_key

  reboot_timeout: 0s


# =========================================================
# OTA
# =========================================================

ota:
  - platform: esphome


# =========================================================
# WIFI
# =========================================================

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  ap:
    ssid: Ice Maker Fallback Hotspot


captive_portal:


# =========================================================
# GLOBAL STATE CACHE
#
# Prevent repeated publishing of identical values.
# =========================================================

globals:

  - id: last_raw_state
    type: std::string
    restore_value: no
    initial_value: '""'

  - id: last_ice_state
    type: std::string
    restore_value: no
    initial_value: '""'


# =========================================================
# ICE MAKER INPUT SIGNALS
# =========================================================

binary_sensor:

  # -------------------------------------------------------
  # YELLOW
  #
  # Original Arduino:
  # const int sensor2 = 12;
  #
  # D6 = GPIO12
  # -------------------------------------------------------

  - platform: gpio
    id: ice_signal_yellow

    pin:
      number: GPIO12
      mode:
        input: true

    internal: true


  # -------------------------------------------------------
  # ORANGE
  #
  # Original Arduino:
  # const int sensor3 = 14;
  #
  # D5 = GPIO14
  # -------------------------------------------------------

  - platform: gpio
    id: ice_signal_orange

    pin:
      number: GPIO14
      mode:
        input: true

    internal: true


  # -------------------------------------------------------
  # GREEN
  #
  # Original Arduino:
  # const int sensor4 = 2;
  #
  # Originally:
  # D4 / GPIO2
  #
  # Now:
  # D1 / GPIO5
  #
  # This is primarily the POWER LED/status signal.
  # -------------------------------------------------------

  - platform: gpio
    id: ice_signal_green

    pin:
      number: GPIO5
      mode:
        input: true

    internal: true


  # =======================================================
  # HOME ASSISTANT STATUS SENSORS
  # =======================================================

  - platform: template
    name: "Ice Full"
    id: ice_full
    icon: mdi:tray-full


  - platform: template
    name: "Water Empty"
    id: water_empty
    icon: mdi:water-alert


# =========================================================
# TEXT SENSORS
# =========================================================

text_sensor:

  # -------------------------------------------------------
  # Main decoded ice maker state
  # -------------------------------------------------------

  - platform: template
    name: "State"
    id: ice_maker_state
    icon: mdi:snowflake
    update_interval: never


  # -------------------------------------------------------
  # Raw input state
  #
  # Format:
  #
  # Yellow Orange Green
  #
  # Example:
  #
  # 111 = Water Empty with green signal HIGH
  # -------------------------------------------------------

  - platform: template
    name: "Raw Inputs"
    id: ice_maker_raw_inputs
    icon: mdi:code-binary
    entity_category: diagnostic
    update_interval: never


  # -------------------------------------------------------
  # WiFi information
  # -------------------------------------------------------

  - platform: wifi_info

    ip_address:
      name: "IP Address"
      entity_category: diagnostic


# =========================================================
# WIFI SIGNAL
# =========================================================

sensor:

  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: 60s
    entity_category: diagnostic


# =========================================================
# PANEL READER
#
# Original Arduino polled every 250 ms.
#
# We continue using the same interval.
# =========================================================

interval:

  - interval: 250ms

    then:

      - lambda: |-

          // ------------------------------------------------
          // Read all three signals.
          // ------------------------------------------------

          const bool yellow = id(ice_signal_yellow).state;
          const bool orange = id(ice_signal_orange).state;
          const bool green  = id(ice_signal_green).state;


          // ------------------------------------------------
          // Construct diagnostic raw state.
          //
          // Order is identical to original Arduino:
          //
          // Yellow Orange Green
          // ------------------------------------------------

          char raw_buffer[4];

          snprintf(
            raw_buffer,
            sizeof(raw_buffer),
            "%d%d%d",
            yellow ? 1 : 0,
            orange ? 1 : 0,
            green ? 1 : 0
          );

          const std::string raw_state(raw_buffer);


          // ------------------------------------------------
          // Publish raw input only when it changes.
          // ------------------------------------------------

          if (raw_state != id(last_raw_state)) {

            id(last_raw_state) = raw_state;

            id(ice_maker_raw_inputs).publish_state(raw_state);

            ESP_LOGI(
              "ice_maker",
              "Raw inputs: %s",
              raw_state.c_str()
            );
          }


          // ------------------------------------------------
          // IMPORTANT:
          //
          // Yellow + Orange encode the machine state.
          //
          // Green is ignored for main state decoding.
          //
          //
          // Y O
          //
          // 0 0 = Off
          // 0 1 = On
          // 1 0 = Full
          // 1 1 = Empty
          // ------------------------------------------------

          std::string decoded_state;


          if (!yellow && !orange) {

            decoded_state = "Off";

          } else if (!yellow && orange) {

            decoded_state = "On";

          } else if (yellow && !orange) {

            decoded_state = "Full";

          } else {

            decoded_state = "Empty";
          }


          // ------------------------------------------------
          // Publish only when actual state changes.
          // ------------------------------------------------

          if (decoded_state != id(last_ice_state)) {

            id(last_ice_state) = decoded_state;

            id(ice_maker_state).publish_state(decoded_state);


            // ----------------------------------------------
            // Derived binary sensors.
            // ----------------------------------------------

            id(ice_full).publish_state(
              decoded_state == "Full"
            );

            id(water_empty).publish_state(
              decoded_state == "Empty"
            );


            ESP_LOGI(
              "ice_maker",
              "Decoded state: %s",
              decoded_state.c_str()
            );
          }


# =========================================================
# SWITCHES
# =========================================================

switch:

  - platform: gpio
    id: ice_maker_button_output
    internal: true

    pin:
      number: GPIO13

      mode:
        output: true

      inverted: true

    # Logical OFF = physical GPIO HIGH.
    restore_mode: ALWAYS_OFF


  # =======================================================
  # HOME ASSISTANT POWER SWITCH
  # =======================================================

  - platform: template
    name: "Power"
    id: ice_maker_power
    icon: mdi:power
    optimistic: false


    # -----------------------------------------------------
    # Display the ACTUAL machine state.
    # -----------------------------------------------------

    lambda: |-

      if (!id(ice_maker_state).has_state()) {
        return {};
      }

      const std::string state =
        id(ice_maker_state).state;


      if (state == "Off") {
        return false;
      }


      // On, Full and Empty are all powered states.
      return true;


    # -----------------------------------------------------
    # TURN ON
    #
    # Only simulate a button press when currently Off.
    # -----------------------------------------------------

    turn_on_action:

      - if:

          condition:

            lambda: |-

              return
                id(ice_maker_state).has_state() &&
                id(ice_maker_state).state == "Off";

          then:

            - logger.log:
                level: INFO
                format: "HA requested ON"

            - script.execute: press_ice_maker_power_button


    # -----------------------------------------------------
    # TURN OFF
    #
    # On, Full and Empty all mean the machine itself is
    # powered on.
    # -----------------------------------------------------

    turn_off_action:

      - if:

          condition:

            lambda: |-

              if (!id(ice_maker_state).has_state()) {
                return false;
              }

              return
                id(ice_maker_state).state != "Off";

          then:

            - logger.log:
                level: INFO
                format: "HA requested OFF"

            - script.execute: press_ice_maker_power_button


# =========================================================
# PHYSICAL BUTTON SCRIPT
# =========================================================

script:

  - id: press_ice_maker_power_button
    mode: single

    then:

      - logger.log:
          level: INFO
          format: "Ice Maker: pressing power button"


      # Physical GPIO13 LOW.
      - switch.turn_on: ice_maker_button_output


      # Same pulse length as original Arduino firmware.
      - delay: 200ms


      # Physical GPIO13 HIGH.
      - switch.turn_off: ice_maker_button_output


      - logger.log:
          level: INFO
          format: "Ice Maker: power button released"


      # Small lockout to prevent accidental repeated presses.
      - delay: 500ms


# =========================================================
# HOME ASSISTANT BUTTONS
# =========================================================

button:

  # -------------------------------------------------------
  # Direct physical button press.
  #
  # Disabled by default because normal operation should use
  # the Power switch above.
  #
  # Useful for diagnostics.
  # -------------------------------------------------------

  - platform: template
    name: "Press Physical Power Button"
    id: ice_maker_force_power_button
    icon: mdi:gesture-tap-button
    entity_category: diagnostic
    disabled_by_default: true

    on_press:
      - script.execute: press_ice_maker_power_button


  # -------------------------------------------------------
  # ESP restart
  # -------------------------------------------------------

  - platform: restart
    name: "Restart"
    entity_category: diagnostic