# Onkyo RI transmit test rig
#
# Bench validation for `remote_transmitter.transmit_onkyori`. Standalone: there
# is no `api:` block and nothing here talks to Home Assistant. Every command is
# fired by hand from the device's own web page.
#
#   esphome run ri-transmit-test.yaml
#
# The thirteen codes below are the verified Onkyo A-9050 set. RI codes are
# model-specific. See http://fredboboss.free.fr/articles/onkyo_ri.php
#
# Wiring: RI is 0V/5V and the ESP32 is 3.3V, so the transmit pin goes through a
# level shifter (BSS138-type) to the tip of a 3.5mm mono plug. Sleeve to ground,
# shared with the amp. Without the shifter the amp will not see a valid high.

substitutions:
  device_name: onkyo-ri-transmit-test
  ri_pin: GPIO17

esphome:
  name: ${device_name}
  friendly_name: "Onkyo RI Test"
  comment: "Bench test rig for the Onkyo RI transmit protocol."

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

logger:
  # There is no API, so logs come over USB serial or the web page's log panel.
  level: DEBUG

ota:
  - platform: esphome
    password: !secret ota_password

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

web_server:
  port: 80
  version: 3
  # Serve the UI from flash, so the rig works on an isolated bench network.
  local: true

external_components:
  # Onkyo RI is not in any ESPHome release yet. Remove this once #18595 merges.
  - source: github://pr#18595
    components: [remote_base]

remote_transmitter:
  pin: ${ri_pin}
  non_blocking: true
  # RI is baseband: the protocol sets a carrier frequency of 0, so this value
  # never comes into play. 100% is the convention for non-IR protocols.
  carrier_duty_percent: 100%
  # Fires when the RMT peripheral has finished clocking the frame out. If a code
  # logs below but this never appears, the fault is the transmitter or the
  # wiring, not the code.
  on_complete:
    - logger.log:
        level: DEBUG
        tag: "ri"
        format: "   frame complete"

# Every button goes through this script rather than calling the transmitter
# directly, so the log line and the code that actually goes out cannot drift
# apart. `mode: queued` so two quick presses both transmit, in order, instead
# of the second being dropped.
script:
  - id: ri_send
    mode: queued
    parameters:
      code: int
      label: string
    then:
      - logger.log:
          level: INFO
          tag: "ri"
          format: "TX %s -> 0x%03X"
          args:
            - "label.c_str()"
            - "(unsigned) code"
      - remote_transmitter.transmit_onkyori:
          data: !lambda "return (uint16_t) code;"

button:
  - platform: template
    name: "Onkyo RI: Power On"
    icon: "mdi:power-on"
    on_press:
      - script.execute: { id: ri_send, code: 0xD9, label: "Power On" }

  - platform: template
    name: "Onkyo RI: Power Off"
    icon: "mdi:power-off"
    on_press:
      - script.execute: { id: ri_send, code: 0xDA, label: "Power Off" }

  # Discrete On and Off are above. This one flips whatever the current state is,
  # so nothing can predict the result. It is here to be tested, not used.
  - platform: template
    name: "Onkyo RI: Power Toggle"
    icon: "mdi:power"
    on_press:
      - script.execute: { id: ri_send, code: 0x4, label: "Power Toggle" }

  - platform: template
    name: "Onkyo RI: Volume Up"
    icon: "mdi:volume-plus"
    on_press:
      - script.execute: { id: ri_send, code: 0x2, label: "Volume Up" }

  - platform: template
    name: "Onkyo RI: Volume Down"
    icon: "mdi:volume-minus"
    on_press:
      - script.execute: { id: ri_send, code: 0x3, label: "Volume Down" }

  - platform: template
    name: "Onkyo RI: Mute"
    icon: "mdi:volume-low"
    on_press:
      - script.execute: { id: ri_send, code: 0xD7, label: "Mute" }

  - platform: template
    name: "Onkyo RI: Unmute"
    icon: "mdi:volume-high"
    on_press:
      - script.execute: { id: ri_send, code: 0xD8, label: "Unmute" }

  - platform: template
    name: "Onkyo RI: Toggle Mute"
    icon: "mdi:volume-mute"
    on_press:
      - script.execute: { id: ri_send, code: 0x5, label: "Toggle Mute" }

  - platform: template
    name: "Onkyo RI: Next Input"
    icon: "mdi:chevron-right-box"
    on_press:
      - script.execute: { id: ri_send, code: 0xD5, label: "Next Input" }

  - platform: template
    name: "Onkyo RI: Previous Input"
    icon: "mdi:chevron-left-box"
    on_press:
      - script.execute: { id: ri_send, code: 0xD6, label: "Previous Input" }

  - platform: template
    name: "Onkyo RI: Input D1"
    icon: "mdi:numeric-1-box-outline"
    on_press:
      - script.execute: { id: ri_send, code: 0x20, label: "Input D1" }

  - platform: template
    name: "Onkyo RI: Input D2"
    icon: "mdi:numeric-2-box-outline"
    on_press:
      - script.execute: { id: ri_send, code: 0xE0, label: "Input D2" }

  # 0x170 is 9 bits wide. RI frames are 12-bit, so this is a valid code and not
  # a typo. It is the one entry that proves codes above 0xFF survive encoding.
  - platform: template
    name: "Onkyo RI: Input D3"
    icon: "mdi:numeric-3-box-outline"
    on_press:
      - script.execute: { id: ri_send, code: 0x170, label: "Input D3" }

  # Set a value in the number below, press this, and the code goes out verbatim.
  # This is the only control that exercises the templatable form of the action,
  # which is a separate code path from the literal `data:` used above.
  - platform: template
    name: "Onkyo RI: Send Arbitrary Code"
    icon: "mdi:send"
    on_press:
      - script.execute:
          id: ri_send
          code: !lambda "return (int) id(ri_arbitrary_code).state;"
          label: "Arbitrary"

number:
  - platform: template
    id: ri_arbitrary_code
    name: "Onkyo RI: Arbitrary Code"
    icon: "mdi:pound"
    optimistic: true
    min_value: 0
    max_value: 4095      # RI frames are 12-bit
    step: 1
    initial_value: 0xD9
    mode: box
