#:########################################################################################:#
# ESP-NOW Test Transmitter 1
#:########################################################################################:#
#
# DEVICE:
#   ESP32-S3-Zero / ESP32-S3 Super Mini
#
# VERSION:
#   1.2 - 2026-07-11
#     - Changed GPIO4 wake source to ESP32 EXT1 ANY_LOW.
#     - Added delay to allow ESP-NOW to initialise before transmission.
#     - Added GPIO4 input and pull-up initialisation.
#     - Waits for the wake button to be released before entering deep sleep.
#     - Logs the ESP32 wake cause.
#     - Sends each message three times for improved broadcast reliability.
#
# WIRING:
#
#              3.3 V
#                |
#               10 kΩ
#                |
#                +-------- GPIO4
#                |
#             Push button
#                |
#               GND
#
# OPERATION:
#   - Power-up or reset causes one transmission.
#   - The transmitter sends the same ESP-NOW message three times.
#   - It waits for GPIO4 to return HIGH.
#   - It then enters deep sleep.
#   - Pulling GPIO4 LOW wakes the transmitter and repeats the sequence.
#
# MESSAGE FORMAT:
#   REMOTE_A:BUTTON_1:<random press ID>
#
# IMPORTANT:
#   - ESP-NOW channel must match the receiver network channel.
#   - The receiver is currently operating on channel 5.
#   - This transmitter deliberately does not connect to normal Wi-Fi.
#   - Firmware updates are performed by USB.
#
#:########################################################################################:#

substitutions:
  device_name: esp-espnow-test-transmitter1
  friendly_name: "ESPNow Test Transmitter 1"

  remote_id: "REMOTE_A"
  button_id: "BUTTON_1"

  espnow_channel: "5"
  wake_gpio: "4"

#:########################################################################################:#
# ESPHome Core
#:########################################################################################:#

esphome:
  name: ${device_name}
  friendly_name: ${friendly_name}

  on_boot:
    priority: -100
    then:
      - lambda: |-
          // Explicitly configure GPIO4 as an input with a pull-up.
          gpio_set_direction(GPIO_NUM_4, GPIO_MODE_INPUT);
          gpio_set_pull_mode(GPIO_NUM_4, GPIO_PULLUP_ONLY);

          const esp_sleep_wakeup_cause_t wake_cause =
            esp_sleep_get_wakeup_cause();

          ESP_LOGI(
            "wake",
            "ESP-NOW transmitter booted. Wake cause: %d",
            static_cast<int>(wake_cause)
          );

          if (wake_cause == ESP_SLEEP_WAKEUP_EXT1) {
            ESP_LOGI("wake", "Wake was caused by the GPIO4 EXT1 input.");
          } else if (wake_cause == ESP_SLEEP_WAKEUP_UNDEFINED) {
            ESP_LOGI("wake", "Wake was caused by power-up or reset.");
          } else {
            ESP_LOGI(
              "wake",
              "Wake was caused by another source: %d",
              static_cast<int>(wake_cause)
            );
          }

      # Allow all ESPHome components, including ESP-NOW, to finish setup.
      - delay: 750ms

      - lambda: |-
          id(press_id) = esp_random();

          char payload_buffer[96];

          snprintf(
            payload_buffer,
            sizeof(payload_buffer),
            "${remote_id}:${button_id}:%lu",
            static_cast<unsigned long>(id(press_id))
          );

          id(transmit_payload) = payload_buffer;

          ESP_LOGI(
            "espnow_tx",
            "Prepared payload: %s",
            id(transmit_payload).c_str()
          );

      - script.execute: send_espnow_press
      - script.wait: send_espnow_press

      - logger.log:
          level: INFO
          format: "Transmission sequence completed."

      # Do not enter sleep while GPIO4 is still held LOW.
      - logger.log:
          level: INFO
          format: "Waiting for GPIO4 button release."

      - wait_until:
          condition:
            lambda: |-
              return gpio_get_level(GPIO_NUM_4) == 1;

      - delay: 100ms

      - logger.log:
          level: INFO
          format: "GPIO4 released. Entering deep sleep."

      - delay: 100ms

      - deep_sleep.enter: deep_sleep_control

#:########################################################################################:#
# ESP32 Hardware
#:########################################################################################:#

esp32:
  board: esp32-s3-devkitc-1

  framework:
    type: esp-idf

#:########################################################################################:#
# Logging
#:########################################################################################:#

logger:
  level: DEBUG

#:########################################################################################:#
# ESP-NOW
#:########################################################################################:#
#
# The transmitter does not connect to Wi-Fi, so its radio channel is set
# explicitly. Both transmitter and receiver must use the same channel.
#

espnow:
  channel: ${espnow_channel}
  enable_on_boot: true

#:########################################################################################:#
# Globals
#:########################################################################################:#

globals:
  - id: press_id
    type: uint32_t
    restore_value: false
    initial_value: '0'

  - id: transmit_payload
    type: std::string
    restore_value: false
    initial_value: '""'

#:########################################################################################:#
# ESP-NOW Transmission Script
#:########################################################################################:#

script:
  - id: send_espnow_press
    mode: single

    then:
      - logger.log:
          level: INFO
          format: "Sending ESP-NOW broadcast 1 of 3."

      - espnow.broadcast:
          data: !lambda |-
            return std::vector<uint8_t>(
              id(transmit_payload).begin(),
              id(transmit_payload).end()
            );

          wait_for_sent: true
          continue_on_error: true

          on_sent:
            - logger.log:
                level: DEBUG
                format: "Broadcast 1 submitted successfully."

          on_error:
            - logger.log:
                level: WARN
                format: "Broadcast 1 reported an error."

      - delay: 75ms

      - logger.log:
          level: INFO
          format: "Sending ESP-NOW broadcast 2 of 3."

      - espnow.broadcast:
          data: !lambda |-
            return std::vector<uint8_t>(
              id(transmit_payload).begin(),
              id(transmit_payload).end()
            );

          wait_for_sent: true
          continue_on_error: true

          on_sent:
            - logger.log:
                level: DEBUG
                format: "Broadcast 2 submitted successfully."

          on_error:
            - logger.log:
                level: WARN
                format: "Broadcast 2 reported an error."

      - delay: 75ms

      - logger.log:
          level: INFO
          format: "Sending ESP-NOW broadcast 3 of 3."

      - espnow.broadcast:
          data: !lambda |-
            return std::vector<uint8_t>(
              id(transmit_payload).begin(),
              id(transmit_payload).end()
            );

          wait_for_sent: true
          continue_on_error: true

          on_sent:
            - logger.log:
                level: DEBUG
                format: "Broadcast 3 submitted successfully."

          on_error:
            - logger.log:
                level: WARN
                format: "Broadcast 3 reported an error."

      - delay: 75ms

#:########################################################################################:#
# Deep Sleep and GPIO4 Wake
#:########################################################################################:#
#
# ESP32-S3 supports EXT1 ANY_LOW wake.
#
# With one configured pin, ANY_LOW means:
#   Wake when GPIO4 is pulled LOW.
#
# An external 10 kΩ pull-up between GPIO4 and 3.3 V is recommended.
#

deep_sleep:
  id: deep_sleep_control

  esp32_ext1_wakeup:
    pins:
      - GPIO${wake_gpio}

    mode: ANY_LOW