[remote_base] Add Onkyo RI Wired Serial Support (#18595)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
Steve Vigneau
2026-10-09 19:23:10 +00:00
committed by GitHub
co-authored by Claude Opus 5.5 J. Nick Koston J. Nick Koston
parent 9d02ab3734
commit 8084916e30
5 changed files with 144 additions and 0 deletions
@@ -1019,6 +1019,43 @@ async def nec_action(var, config, args):
cg.add(var.set_command_repeats(template_))
# Onkyo RI
OnkyoRIData, OnkyoRIBinarySensor, OnkyoRITrigger, OnkyoRIAction, OnkyoRIDumper = (
declare_protocol("OnkyoRI")
)
ONKYORI_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_int_range(0, 0xFFF)})
@register_binary_sensor("onkyori", OnkyoRIBinarySensor, ONKYORI_SCHEMA)
def onkyori_binary_sensor(var: MockObj, config: ConfigType) -> None:
cg.add(
var.set_data(
cg.StructInitializer(
OnkyoRIData,
("data", config[CONF_DATA]),
)
)
)
@register_trigger("onkyori", OnkyoRITrigger, OnkyoRIData)
def onkyori_trigger(var: MockObj, config: ConfigType) -> None:
"""The trigger takes no options beyond the automation."""
@register_dumper("onkyori", OnkyoRIDumper)
def onkyori_dumper(var: MockObj, config: ConfigType) -> None:
"""The dumper takes no options."""
@register_action("onkyori", OnkyoRIAction, ONKYORI_SCHEMA)
async def onkyori_action(
var: MockObj, config: ConfigType, args: TemplateArgsType
) -> None:
template_ = await cg.templatable(config[CONF_DATA], args, cg.uint16)
cg.add(var.set_data(template_))
# Pioneer
(
PioneerData,
@@ -0,0 +1,59 @@
#include "onkyori_protocol.h"
#include "esphome/core/log.h"
namespace esphome::remote_base {
ESPHOME_LOG_TAG(TAG, "remote.onkyori");
static constexpr uint8_t NBITS = 12;
static constexpr uint16_t MAX_DATA = (1U << NBITS) - 1;
static constexpr uint32_t HEADER_HIGH_US = 3000;
static constexpr uint32_t HEADER_LOW_US = 1000;
static constexpr uint32_t BIT_HIGH_US = 1000;
static constexpr uint32_t BIT_ONE_LOW_US = 2000;
static constexpr uint32_t BIT_ZERO_LOW_US = 1000;
static constexpr uint32_t TRAILER_HIGH_US = 1000;
void OnkyoRIProtocol::encode(RemoteTransmitData *dst, const OnkyoRIData &data) {
if (data.data > MAX_DATA) {
ESP_LOGE(TAG, "Data 0x%04X does not fit in a 12-bit RI frame, not sending", data.data);
return;
}
// RI is a baseband signal on a wire, so it has no carrier to modulate.
dst->set_carrier_frequency(0);
dst->reserve(2 + NBITS * 2u + 1);
dst->item(HEADER_HIGH_US, HEADER_LOW_US);
for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) {
dst->item(BIT_HIGH_US, (data.data & mask) != 0 ? BIT_ONE_LOW_US : BIT_ZERO_LOW_US);
}
dst->mark(TRAILER_HIGH_US);
}
optional<OnkyoRIData> OnkyoRIProtocol::decode(RemoteReceiveData src) {
OnkyoRIData out{.data = 0};
if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US))
return {};
for (uint8_t i = 0; i < NBITS; i++) {
out.data <<= 1;
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
out.data |= 1;
} else if (!src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
return {};
}
}
if (!src.expect_mark(TRAILER_HIGH_US))
return {};
return out;
}
void OnkyoRIProtocol::dump(const OnkyoRIData &data) { ESP_LOGI(TAG, "Received OnkyoRI: data=0x%03X", data.data); }
} // namespace esphome::remote_base
@@ -0,0 +1,33 @@
#pragma once
#include "remote_base.h"
namespace esphome::remote_base {
struct OnkyoRIData {
uint16_t data;
bool operator==(const OnkyoRIData &rhs) const { return data == rhs.data; }
};
class OnkyoRIProtocol : public RemoteProtocol<OnkyoRIData> {
public:
void encode(RemoteTransmitData *dst, const OnkyoRIData &data);
optional<OnkyoRIData> decode(RemoteReceiveData src);
void dump(const OnkyoRIData &data);
};
DECLARE_REMOTE_PROTOCOL(OnkyoRI)
template<typename... Ts> class OnkyoRIAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(uint16_t, data)
void encode(RemoteTransmitData *dst, Ts... x) override {
OnkyoRIData data{};
data.data = this->data_.value(x...);
OnkyoRIProtocol().encode(dst, data);
}
};
} // namespace esphome::remote_base
@@ -103,6 +103,11 @@ on_nexa:
- logger.log:
format: "on_nexa: %lu %u %u %u %u"
args: ["long(x.device)", "x.group", "x.state", "x.channel", "x.level"]
on_onkyori:
then:
- logger.log:
format: "on_onkyori: 0x%03X"
args: ["x.data"]
on_panasonic:
then:
- logger.log:
@@ -252,6 +252,16 @@ button:
channel: 1
button: 1
check: 1
- platform: template
name: OnkyoRI
on_press:
remote_transmitter.transmit_onkyori:
data: 0xD9
- platform: template
name: OnkyoRI 12-bit
on_press:
remote_transmitter.transmit_onkyori:
data: 0x170
- platform: template
name: Toto
on_press: