Compare commits

...
10 changed files with 338 additions and 14 deletions
+120 -1
View File
@@ -1,14 +1,22 @@
from esphome import automation, pins
import esphome.codegen as cg
from esphome.components import uart
from esphome.components.esp32 import include_builtin_idf_component
from esphome.components.logger import UART1
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
CONF_DATA,
CONF_HARDWARE_UART,
CONF_ID,
CONF_LOGGER,
CONF_NUMBER,
CONF_ON_MESSAGE,
CONF_PIN,
CONF_RX_PIN,
CONF_SOURCE,
CONF_TX_PIN,
CONF_UART_ID,
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
@@ -17,6 +25,7 @@ from esphome.const import (
PLATFORM_RTL87XX,
)
from esphome.core import CORE
import esphome.final_validate as fv
from esphome.types import ConfigType
CODEOWNERS = ["@JosVanEijndhoven"]
@@ -24,10 +33,14 @@ DOMAIN = "hdmi_cec"
MAX_LOGICAL_ADDRESS = 15
MAX_PAYLOAD_LENGTH = 15
MAX_OSD_NAME_LENGTH = MAX_PAYLOAD_LENGTH - 1 # the opcode takes one byte
UART_BAUD_RATE = 2083 # five UART bits per 2.4 ms CEC bit
CONF_PROMISCUOUS_MODE = "promiscuous_mode"
CONF_MONITOR_MODE = "monitor_mode"
CONF_DESTINATION = "destination"
CONF_OPCODE = "opcode"
CONF_PHYSICAL_ADDRESS = "physical_address"
CONF_OSD_NAME = "osd_name"
hdmi_cec_ns = cg.esphome_ns.namespace("hdmi_cec")
HDMICEC = hdmi_cec_ns.class_("HDMICEC", cg.Component)
@@ -46,6 +59,14 @@ payload = cv.All(cv.ensure_list(cv.hex_uint8_t), cv.Length(max=MAX_PAYLOAD_LENGT
payload_prefix = cv.All(payload, cv.Length(min=1))
def _osd_name(value: object) -> str:
# The name goes on the bus as plain ASCII, one byte per character
value = cv.All(cv.string_strict, cv.Length(min=1, max=MAX_OSD_NAME_LENGTH))(value)
if not all(0x20 <= ord(char) <= 0x7E for char in value):
raise cv.Invalid("osd_name may only contain printable ASCII characters")
return value
def _validate_message_filter(config: ConfigType) -> ConfigType:
# The opcode is the first payload byte: fold it into the data prefix
if (opcode := config.pop(CONF_OPCODE, None)) is not None:
@@ -76,6 +97,9 @@ CONFIG_SCHEMA = cv.All(
cv.GenerateID(): cv.declare_id(HDMICEC),
cv.Required(CONF_PIN): pins.internal_gpio_input_pullup_pin_schema,
cv.Required(CONF_ADDRESS): logical_address,
cv.Optional(CONF_PHYSICAL_ADDRESS): cv.hex_uint16_t,
cv.Optional(CONF_OSD_NAME): _osd_name,
cv.Optional(CONF_UART_ID): cv.use_id(uart.UARTComponent),
cv.Optional(CONF_PROMISCUOUS_MODE, default=False): cv.boolean,
cv.Optional(CONF_MONITOR_MODE, default=False): cv.boolean,
cv.Optional(CONF_ON_MESSAGE): automation.validate_automation(
@@ -102,6 +126,52 @@ CONFIG_SCHEMA = cv.All(
)
# On the ESP8266 only UART1, transmit only on GPIO2, is free of the logger; UART0 carries the log
# and any other pin gets a software UART that sends with interrupts off, which holds the main loop
# and hides our own frame from the receive interrupt
ESP8266_UART1_TX_PIN = 2
def _esp8266_uart1(hub_config: ConfigType) -> ConfigType:
if (
hub_config[CONF_TX_PIN][CONF_NUMBER] != ESP8266_UART1_TX_PIN
or CONF_RX_PIN in hub_config
):
raise cv.Invalid(
f"{DOMAIN} needs the hardware UART1 on the ESP8266: tx_pin GPIO2 and no rx_pin"
)
logger = fv.full_config.get().get(CONF_LOGGER, {})
if logger.get(CONF_HARDWARE_UART) == UART1:
raise cv.Invalid(f"{DOMAIN} needs UART1 on the ESP8266, which the logger uses")
return hub_config
def _final_validate(config: ConfigType) -> ConfigType:
if CONF_UART_ID in config:
# The UART only drives the line, through a diode; its settings make one CEC bit of five UART bits
uart.final_validate_device_schema(
DOMAIN,
baud_rate=UART_BAUD_RATE,
require_tx=True,
data_bits=8,
parity="NONE",
stop_bits=1,
)(config)
if CORE.is_esp8266:
cv.Schema(
{
cv.Required(CONF_UART_ID): fv.id_declaration_match_schema(
_esp8266_uart1
)
},
extra=cv.ALLOW_EXTRA,
)(config)
return config
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config: ConfigType) -> None:
if CORE.is_esp32:
# The acknowledge bit is released from a general purpose timer, left out of the build by default
@@ -113,8 +183,36 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_promiscuous_mode(True))
if config[CONF_MONITOR_MODE]:
cg.add(var.set_monitor_mode(True))
if (physical_address := config.get(CONF_PHYSICAL_ADDRESS)) is not None:
cg.add(var.set_physical_address(physical_address))
if (osd_name := config.get(CONF_OSD_NAME)) is not None:
cg.extern_progmem_array("esphome::hdmi_cec::OSD_NAME", cg.char, osd_name)
cg.add_define("HDMI_CEC_OSD_NAME_LENGTH", len(osd_name))
if (uart_id := config.get(CONF_UART_ID)) is not None:
cg.add_define("USE_HDMI_CEC_UART")
cg.add(var.set_uart(await cg.get_variable(uart_id)))
for conf in config.get(CONF_ON_MESSAGE, []):
on_message = config.get(CONF_ON_MESSAGE, [])
# An automation for frames addressed to us with a data prefix claims its opcode from every
# initiator, whatever its source filter or the rest of its prefix, so the built-in replies never
# contradict an answer it sends. One without a prefix sees every such frame, so nothing is
# unhandled and no Feature Abort is sent
address = config[CONF_ADDRESS]
for_us = [
conf for conf in on_message if conf.get(CONF_DESTINATION, address) == address
]
claimed_opcodes = sorted(
{conf[CONF_DATA][0] for conf in for_us if CONF_DATA in conf}
)
if claimed_opcodes:
cg.extern_progmem_array(
"esphome::hdmi_cec::CLAIMED_OPCODES", cg.uint8, claimed_opcodes
)
cg.add_define("HDMI_CEC_CLAIMED_OPCODE_COUNT", len(claimed_opcodes))
if all(CONF_DATA in conf for conf in for_us):
cg.add_define("USE_HDMI_CEC_FEATURE_ABORT")
for conf in on_message:
_request_message_callback_slot()
await automation.build_callback_automation(
var,
@@ -139,3 +237,24 @@ automation.register_apply_action(
((CONF_DESTINATION, cg.uint8), (CONF_DATA, _PAYLOAD_VECTOR)),
),
)
# A frame under another logical address, for a device that stands in for several
automation.register_apply_action(
"hdmi_cec.send_as",
cv.Schema(
{
cv.GenerateID(): cv.use_id(HDMICEC),
cv.Required(CONF_SOURCE): cv.templatable(logical_address),
cv.Required(CONF_DESTINATION): cv.templatable(logical_address),
cv.Required(CONF_DATA): cv.templatable(payload),
}
),
automation.ApplyCall(
"send({}, {}, {})",
(
(CONF_SOURCE, cg.uint8),
(CONF_DESTINATION, cg.uint8),
(CONF_DATA, _PAYLOAD_VECTOR),
),
),
)
+127 -11
View File
@@ -2,6 +2,9 @@
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#ifdef USE_HDMI_CEC_UART
#include "esphome/components/uart/uart_component.h"
#endif
#ifdef USE_ESP8266
#include <Arduino.h>
#endif
@@ -33,6 +36,24 @@ static constexpr uint32_t SIGNAL_FREE_TIME_AFTER_XMIT_SUCCESS = TOTAL_BIT_US * 7
static constexpr uint32_t ABORTED_FRAME_US = TOTAL_BIT_US * 15; // no edge for this long: our frame was cut short
static constexpr uint8_t MAX_ATTEMPTS = 8; // CEC allows 5, but a busy bus counts as an attempt here too
// Opcodes of the built-in replies
static constexpr uint8_t OPCODE_FEATURE_ABORT = 0x00;
static constexpr uint8_t OPCODE_GIVE_OSD_NAME = 0x46;
static constexpr uint8_t OPCODE_SET_OSD_NAME = 0x47;
static constexpr uint8_t OPCODE_GIVE_PHYSICAL_ADDRESS = 0x83;
static constexpr uint8_t OPCODE_REPORT_PHYSICAL_ADDRESS = 0x84;
static constexpr uint8_t OPCODE_GET_CEC_VERSION = 0x9F;
static constexpr uint8_t OPCODE_CEC_VERSION = 0x9E;
static constexpr uint8_t CEC_VERSION_1_4 = 0x05;
/// The CEC device type a logical address stands for, for "Report Physical Address": one nibble per
/// address, from 0 up. 0 TV, 1 Recording Device, 3 Tuner, 4 Playback Device, 5 Audio System; the
/// reserved, specific use and unregistered addresses count as a playback device
static uint8_t device_type_of(uint8_t logical_address) {
constexpr uint64_t device_types = 0x4444431433543110ULL;
return (device_types >> (4 * logical_address)) & 0xF;
}
Frame::Frame(uint8_t initiator_addr, uint8_t destination_addr, std::span<const uint8_t> payload) {
this->data_[0] = ((initiator_addr & 0xf) << 4) | (destination_addr & 0xf);
this->size_ = 1 + payload.size();
@@ -63,6 +84,20 @@ void HDMICEC::dump_config() {
" Promiscuous mode: %s",
this->address_, YESNO(this->monitor_mode_), YESNO(this->promiscuous_mode_));
LOG_PIN(" Pin: ", this->pin_);
if (this->physical_address_ != UNSET_PHYSICAL_ADDRESS) {
ESP_LOGCONFIG(TAG, " Physical address: %X.%X.%X.%X", this->physical_address_ >> 12,
(this->physical_address_ >> 8) & 0xF, (this->physical_address_ >> 4) & 0xF,
this->physical_address_ & 0xF);
}
#ifdef HDMI_CEC_OSD_NAME_LENGTH
char name[HDMI_CEC_OSD_NAME_LENGTH + 1];
progmem_memcpy(name, OSD_NAME, HDMI_CEC_OSD_NAME_LENGTH);
name[HDMI_CEC_OSD_NAME_LENGTH] = '\0';
ESP_LOGCONFIG(TAG, " OSD name: %s", name);
#endif
#ifdef USE_HDMI_CEC_UART
ESP_LOGCONFIG(TAG, " Transmit: UART");
#endif
}
void HDMICEC::loop() {
@@ -82,6 +117,13 @@ void HDMICEC::loop() {
#ifdef HDMI_CEC_MESSAGE_CALLBACK_COUNT
this->message_callbacks_.call(frame->initiator_addr(), frame->destination_addr(), frame->payload());
#endif
// Only a frame directed at us from a registered device gets a built-in reply: a broadcast is
// never answered (so an instance on the unregistered address 0xF answers nothing, though it may
// still send), and an unregistered initiator cannot be answered directly
if (!this->monitor_mode_ && !frame->is_broadcast() && frame->destination_addr() == this->address_ &&
frame->initiator_addr() != Frame::BROADCAST_ADDRESS && !is_claimed(frame->payload()[0])) {
this->reply_builtin_(frame);
}
this->frames_queue_.pop_front();
}
// A frame being sent stays at the front of the queue until its attempts are over
@@ -93,6 +135,50 @@ void HDMICEC::loop() {
}
}
bool HDMICEC::is_claimed(uint8_t opcode) {
#ifdef HDMI_CEC_CLAIMED_OPCODE_COUNT
for (uint8_t i = 0; i < HDMI_CEC_CLAIMED_OPCODE_COUNT; i++) {
if (progmem_read_byte(&CLAIMED_OPCODES[i]) == opcode) {
return true;
}
}
#endif
return false;
}
void HDMICEC::reply_builtin_(const Frame *frame) {
const uint8_t source = frame->initiator_addr();
const uint8_t opcode = frame->payload()[0];
switch (opcode) {
case OPCODE_FEATURE_ABORT:
return; // never answered
case OPCODE_GET_CEC_VERSION:
this->send(source, {OPCODE_CEC_VERSION, CEC_VERSION_1_4});
return;
case OPCODE_GIVE_PHYSICAL_ADDRESS:
if (this->physical_address_ != UNSET_PHYSICAL_ADDRESS) {
// Reported to everybody, with the device type the logical address stands for
this->send(Frame::BROADCAST_ADDRESS,
{OPCODE_REPORT_PHYSICAL_ADDRESS, static_cast<uint8_t>(this->physical_address_ >> 8),
static_cast<uint8_t>(this->physical_address_), device_type_of(this->address_)});
return;
}
break;
#ifdef HDMI_CEC_OSD_NAME_LENGTH
case OPCODE_GIVE_OSD_NAME: {
uint8_t reply[1 + HDMI_CEC_OSD_NAME_LENGTH] = {OPCODE_SET_OSD_NAME};
progmem_memcpy(reply + 1, OSD_NAME, HDMI_CEC_OSD_NAME_LENGTH);
this->send(source, std::span<const uint8_t>(reply, sizeof(reply)));
return;
}
#endif
}
#ifdef USE_HDMI_CEC_FEATURE_ABORT
// Nothing here or in an automation answers it: tell the initiator, as the bus expects
this->send(source, {OPCODE_FEATURE_ABORT, opcode, 0x00}); // reason: unrecognized opcode
#endif
}
bool HDMICEC::send(uint8_t source, uint8_t destination, std::span<const uint8_t> payload) {
if (this->monitor_mode_) {
ESP_LOGW(TAG, "Monitor mode, not sending 0x%X->0x%X", source, destination);
@@ -194,25 +280,55 @@ bool HDMICEC::transmit_frame_(const Frame *frame) {
if (!this->send_start_bit_()) {
return false;
}
// The four initiator address bits, most significant first, watch for a collision with another
// initiator that started at the same moment; a lower address wins by holding the line
const uint8_t header = frame->bytes()[0];
for (int8_t i = 7; i >= 4; i--) {
if (!this->send_bit_((header >> i) & 0x1)) {
ESP_LOGD(TAG, "Bus collision on sending initiator address 0x%X on attempt %u", frame->initiator_addr(),
this->transmit_attempts_);
return false;
}
}
this->transmit_body_(frame);
return true;
}
#ifdef USE_HDMI_CEC_UART
void HDMICEC::transmit_body_(const Frame *frame) {
// At 2083 baud one UART byte (start bit, 8 data bits, stop bit) spans two CEC bits of five UART
// bits each. The UART start bit is low and its stop bit high, so a byte always begins the first
// CEC bit low and ends the second one high; the data bits, sent least significant first, fill in
// the rest: a logical 0 is low for three UART bits, a logical 1 for one.
static const uint8_t TWO_BITS_TO_UART_BYTE[4] PROGMEM = {0x8C, 0xEC, 0x8F, 0xEF}; // 00, 01, 10, 11
uint8_t data[Frame::MAX_LENGTH * 5 - 2]; // ten CEC bits per byte, two per UART byte, minus the address
size_t length = 0;
const auto bytes = frame->bytes();
for (size_t byte_inx = 0; byte_inx < bytes.size(); byte_inx++) {
const uint8_t current_byte = bytes[byte_inx];
// Most significant bit first. The four initiator address bits watch for a collision with
// another initiator that started at the same moment; a lower address wins by holding the line
for (int8_t i = 7; i >= 0; i--) {
const bool bit_value = (current_byte >> i) & 0x1;
if (!this->send_bit_(bit_value) && byte_inx == 0 && i >= 4) {
ESP_LOGD(TAG, "Bus collision on sending initiator address 0x%X on attempt %u", frame->initiator_addr(),
this->transmit_attempts_);
return false;
}
// The byte, its end of message bit and the acknowledge bit (sent high, the receiver pulls it low)
const uint16_t block =
(static_cast<uint16_t>(bytes[byte_inx]) << 2) | (byte_inx == bytes.size() - 1 ? 0x2 : 0) | 0x1;
// The header starts after its initiator address bits
for (int8_t pair = (byte_inx == 0 ? 2 : 4); pair >= 0; pair--) {
data[length++] = progmem_read_byte(&TWO_BITS_TO_UART_BYTE[(block >> (2 * pair)) & 0x3]);
}
}
this->uart_->write_array(data, length);
}
#else
void HDMICEC::transmit_body_(const Frame *frame) {
const auto bytes = frame->bytes();
for (size_t byte_inx = 0; byte_inx < bytes.size(); byte_inx++) {
// The header starts after its initiator address bits
for (int8_t i = (byte_inx == 0 ? 3 : 7); i >= 0; i--) {
this->send_bit_((bytes[byte_inx] >> i) & 0x1);
}
// End of message bit, then the acknowledge bit which the receiver may pull low
this->send_bit_(byte_inx == bytes.size() - 1);
this->send_bit_(true);
}
return true;
}
#endif
bool HDMICEC::send_start_bit_() {
if (!this->isr_pin_.digital_read()) {
+41 -2
View File
@@ -17,8 +17,22 @@
#include <hardware/timer.h>
#endif
#ifdef USE_HDMI_CEC_UART
namespace esphome::uart {
class UARTComponent;
}
#endif
namespace esphome::hdmi_cec {
// Generated from the configuration, in flash
#ifdef HDMI_CEC_OSD_NAME_LENGTH
extern const char OSD_NAME[] PROGMEM;
#endif
#ifdef HDMI_CEC_CLAIMED_OPCODE_COUNT
extern const uint8_t CLAIMED_OPCODES[] PROGMEM;
#endif
// Release/acquire store and load. The read-modify-write forms of std::atomic are not available on
// every target (the ESP8266 has none), so a writer computes the new value itself; that is safe here
// because every atomic in this component has exactly one writer.
@@ -36,6 +50,7 @@ class Frame {
public:
static constexpr size_t MAX_LENGTH = 16; // HDMI-CEC 1.4
static constexpr size_t MAX_PAYLOAD_LENGTH = MAX_LENGTH - 1;
static constexpr uint8_t BROADCAST_ADDRESS = 0xF; // also the address of an unregistered device
Frame() = default;
/// The payload is at most MAX_PAYLOAD_LENGTH bytes; send() checks that
@@ -43,7 +58,7 @@ class Frame {
ESPHOME_ALWAYS_INLINE uint8_t initiator_addr() const { return this->data_[0] >> 4; }
ESPHOME_ALWAYS_INLINE uint8_t destination_addr() const { return this->data_[0] & 0xf; }
ESPHOME_ALWAYS_INLINE bool is_broadcast() const { return this->destination_addr() == 0xf; }
ESPHOME_ALWAYS_INLINE bool is_broadcast() const { return this->destination_addr() == BROADCAST_ADDRESS; }
ESPHOME_ALWAYS_INLINE size_t size() const { return this->size_; }
std::span<const uint8_t> bytes() const { return {this->data_.data(), this->size_}; }
/// The bytes after the header, starting with the opcode
@@ -103,6 +118,14 @@ class HDMICEC : public Component {
void set_promiscuous_mode(bool promiscuous) { this->promiscuous_mode_ = promiscuous; }
void set_monitor_mode(bool monitor_mode) { this->monitor_mode_ = monitor_mode; }
/// Answer "Give Physical Address" with this address and the device type of the logical address.
/// A lambda may change it at run time, for instance after reading it from the HDMI sink's EDID
void set_physical_address(uint16_t physical_address) { this->physical_address_ = physical_address; }
uint16_t get_physical_address() const { return this->physical_address_; }
#ifdef USE_HDMI_CEC_UART
/// Send the frame body from this UART (its TX pin on the line through a diode) instead of bit-banging it
void set_uart(uart::UARTComponent *uart) { this->uart_ = uart; }
#endif
#ifdef HDMI_CEC_MESSAGE_CALLBACK_COUNT
/// Called with the initiator, the destination and the payload (opcode first) of every received
/// frame; the payload is only valid during the call
@@ -120,6 +143,9 @@ class HDMICEC : public Component {
bool send(uint8_t destination, std::initializer_list<uint8_t> payload) {
return this->send(destination, std::span<const uint8_t>(payload.begin(), payload.size()));
}
bool send(uint8_t source, uint8_t destination, std::initializer_list<uint8_t> payload) {
return this->send(source, destination, std::span<const uint8_t>(payload.begin(), payload.size()));
}
protected:
enum class ReceiverState : uint8_t {
@@ -150,6 +176,10 @@ class HDMICEC : public Component {
static void ack_timer_isr_s(uint alarm_num);
#endif
/// The built-in replies to a frame directed at us that no automation claims
void reply_builtin_(const Frame *frame);
static bool is_claimed(uint8_t opcode);
// Transmit side, in loop()
void transmit_message_();
/// Keep the frame for another attempt, or drop it once the attempts are used up
@@ -159,6 +189,9 @@ class HDMICEC : public Component {
bool send_start_bit_();
/// Send one bit; false when a logical 1 found the line held low by another initiator
bool send_bit_(bool bit_value);
/// Drive the frame after the initiator address: bit-banged, or from the UART, five UART bits
/// per CEC bit, while loop() goes on
void transmit_body_(const Frame *frame);
ESPHOME_ALWAYS_INLINE void set_pin_input_high_() { this->isr_pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); }
ESPHOME_ALWAYS_INLINE void set_pin_output_low_() {
this->isr_pin_.pin_mode(gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN);
@@ -179,6 +212,9 @@ class HDMICEC : public Component {
#endif
InternalGPIOPin *pin_;
ISRInternalGPIOPin isr_pin_;
#ifdef USE_HDMI_CEC_UART
uart::UARTComponent *uart_{nullptr};
#endif
#ifdef USE_ESP32
gptimer_handle_t ack_timer_{nullptr};
#endif
@@ -192,6 +228,7 @@ class HDMICEC : public Component {
uint32_t last_falling_edge_us_{0}; // interrupt only
uint32_t last_attempt_us_{0}; // loop() only; a refused start bit drives no edge, so it is timed here
uint16_t required_idle_period_{0}; // loop() only
uint16_t physical_address_{UNSET_PHYSICAL_ADDRESS};
// Shared with the interrupt. The tally of our own frame is written by the interrupt while
// transmitting_ is set and no end of message has passed yet; loop() reads it once eom_received_
@@ -210,9 +247,11 @@ class HDMICEC : public Component {
bool recv_ack_queued_{false};
bool prev_pin_level_{true};
uint8_t address_; // logical address, 0xf when only listening
uint8_t address_; // logical address, Frame::BROADCAST_ADDRESS when only listening
bool promiscuous_mode_{false};
bool monitor_mode_{false};
static constexpr uint16_t UNSET_PHYSICAL_ADDRESS = 0xFFFF; // the bus reserves this value
};
} // namespace esphome::hdmi_cec
+4
View File
@@ -154,7 +154,11 @@
#define USE_LVGL_TOUCHSCREEN
#define USE_MDNS
#define USE_MDNS_STORE_SERVICES
#define HDMI_CEC_CLAIMED_OPCODE_COUNT 1
#define HDMI_CEC_MESSAGE_CALLBACK_COUNT 2
#define HDMI_CEC_OSD_NAME_LENGTH 7
#define USE_HDMI_CEC_FEATURE_ABORT
#define USE_HDMI_CEC_UART
#define I2C_PORT_FREQUENCY_COUNT 1
#define MDNS_SERVICE_COUNT 3
#define USE_MDNS_DYNAMIC_TXT
@@ -0,0 +1,5 @@
hdmi_cec:
id: cec
pin: ${cec_pin} # HDMI pin 13, with HDMI pin 17 as ground
address: 0x5
uart_id: uart_bus # its tx_pin drives the same line through a diode
+9
View File
@@ -2,6 +2,8 @@ hdmi_cec:
id: cec
pin: ${cec_pin} # HDMI pin 13, with HDMI pin 17 as ground
address: 0x5
physical_address: 0x4000
osd_name: ESPHome
promiscuous_mode: true
on_message:
- opcode: 0x8F # Give Device Power Status
@@ -13,6 +15,13 @@ hdmi_cec:
then:
- lambda: |-
id(cec).send(0xf, {0x87, 0x00, 0x09, 0xb0});
- opcode: 0x9F # Get CEC Version, asked of a playback device we send for (frames to it are not acknowledged)
destination: 0x4
then:
- hdmi_cec.send_as:
source: 0x4
destination: !lambda return source;
data: [0x9E, 0x05] # CEC Version 1.4
- data: [0x44, 0x41] # User Control Pressed: Volume Up
then:
- logger.log: "HDMI-CEC Volume Up"
@@ -0,0 +1,6 @@
substitutions:
cec_pin: GPIO14
packages:
uart: !include ../../test_build_components/common/uart_2083/esp32-idf.yaml
hdmi_cec: !include common-uart.yaml
@@ -0,0 +1,6 @@
substitutions:
cec_pin: GPIO14
packages:
uart: !include ../../test_build_components/common/uart_2083/esp8266-ard.yaml
hdmi_cec: !include common-uart.yaml
@@ -0,0 +1,11 @@
# Common UART configuration for ESP32 IDF tests - 2083 baud
substitutions:
tx_pin: GPIO17
rx_pin: GPIO16
uart:
- id: uart_bus
tx_pin: ${tx_pin}
rx_pin: ${rx_pin}
baud_rate: 2083
@@ -0,0 +1,9 @@
# Common UART configuration for ESP8266 Arduino tests - 2083 baud on the hardware UART1 (transmit only)
substitutions:
tx_pin: GPIO2
uart:
- id: uart_bus
tx_pin: ${tx_pin}
baud_rate: 2083