From 5f9a509bdcb80bd74fa5dcd8f7ec88cb766675d8 Mon Sep 17 00:00:00 2001 From: cvwillegen Date: Tue, 29 Apr 2025 10:21:05 +0200 Subject: [PATCH] Add code to send/receive GoBox infrared control messages. (#7554) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/remote_base/__init__.py | 43 ++++++ .../components/remote_base/gobox_protocol.cpp | 131 ++++++++++++++++++ .../components/remote_base/gobox_protocol.h | 54 ++++++++ .../remote_receiver/common-actions.yaml | 5 + 4 files changed, 233 insertions(+) create mode 100644 esphome/components/remote_base/gobox_protocol.cpp create mode 100644 esphome/components/remote_base/gobox_protocol.h diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index adacb83a30..836b98104b 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -929,6 +929,49 @@ async def pronto_action(var, config, args): cg.add(var.set_data(template_)) +# Gobox +( + GoboxData, + GoboxBinarySensor, + GoboxTrigger, + GoboxAction, + GoboxDumper, +) = declare_protocol("Gobox") +GOBOX_SCHEMA = cv.Schema( + { + cv.Required(CONF_CODE): cv.int_, + } +) + + +@register_binary_sensor("gobox", GoboxBinarySensor, GOBOX_SCHEMA) +def gobox_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + GoboxData, + ("code", config[CONF_CODE]), + ) + ) + ) + + +@register_trigger("gobox", GoboxTrigger, GoboxData) +def gobox_trigger(var, config): + pass + + +@register_dumper("gobox", GoboxDumper) +def gobox_dumper(var, config): + pass + + +@register_action("gobox", GoboxAction, GOBOX_SCHEMA) +async def gobox_action(var, config, args): + template_ = await cg.templatable(config[CONF_CODE], args, cg.int_) + cg.add(var.set_code(template_)) + + # Roomba ( RoombaData, diff --git a/esphome/components/remote_base/gobox_protocol.cpp b/esphome/components/remote_base/gobox_protocol.cpp new file mode 100644 index 0000000000..54e0dff663 --- /dev/null +++ b/esphome/components/remote_base/gobox_protocol.cpp @@ -0,0 +1,131 @@ +#include "gobox_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.gobox"; + +constexpr uint32_t BIT_MARK_US = 580; // 70us seems like a safe time delta for the receiver... +constexpr uint32_t BIT_ONE_SPACE_US = 1640; +constexpr uint32_t BIT_ZERO_SPACE_US = 545; +constexpr uint64_t HEADER = 0b011001001100010uL; // 15 bits +constexpr uint64_t HEADER_SIZE = 15; +constexpr uint64_t CODE_SIZE = 17; + +void GoboxProtocol::dump_timings_(const RawTimings &timings) const { + ESP_LOGD(TAG, "Gobox: size=%u", timings.size()); + for (int32_t timing : timings) { + ESP_LOGD(TAG, "Gobox: timing=%ld", (long) timing); + } +} + +void GoboxProtocol::encode(RemoteTransmitData *dst, const GoboxData &data) { + ESP_LOGI(TAG, "Send Gobox: code=0x%x", data.code); + dst->set_carrier_frequency(38000); + dst->reserve((HEADER_SIZE + CODE_SIZE + 1) * 2); + uint64_t code = (HEADER << CODE_SIZE) | (data.code & ((1UL << CODE_SIZE) - 1)); + ESP_LOGI(TAG, "Send Gobox: code=0x%Lx", code); + for (int16_t i = (HEADER_SIZE + CODE_SIZE - 1); i >= 0; i--) { + if (code & ((uint64_t) 1 << i)) { + dst->item(BIT_MARK_US, BIT_ONE_SPACE_US); + } else { + dst->item(BIT_MARK_US, BIT_ZERO_SPACE_US); + } + } + dst->item(BIT_MARK_US, 2000); + + dump_timings_(dst->get_data()); +} + +optional GoboxProtocol::decode(RemoteReceiveData src) { + if (src.size() < ((HEADER_SIZE + CODE_SIZE) * 2 + 1)) { + return {}; + } + + // First check for the header + uint64_t code = HEADER; + for (int16_t i = HEADER_SIZE - 1; i >= 0; i--) { + if (code & ((uint64_t) 1 << i)) { + if (!src.expect_item(BIT_MARK_US, BIT_ONE_SPACE_US)) { + return {}; + } + } else { + if (!src.expect_item(BIT_MARK_US, BIT_ZERO_SPACE_US)) { + return {}; + } + } + } + + // Next, build up the code + code = 0UL; + for (int16_t i = CODE_SIZE - 1; i >= 0; i--) { + if (!src.expect_mark(BIT_MARK_US)) { + return {}; + } + if (src.expect_space(BIT_ONE_SPACE_US)) { + code |= (1UL << i); + } else if (!src.expect_space(BIT_ZERO_SPACE_US)) { + return {}; + } + } + + if (!src.expect_mark(BIT_MARK_US)) { + return {}; + } + + dump_timings_(src.get_raw_data()); + + GoboxData out; + out.code = code; + + return out; +} + +void GoboxProtocol::dump(const GoboxData &data) { + ESP_LOGI(TAG, "Received Gobox: code=0x%x", data.code); + switch (data.code) { + case GOBOX_MENU: + ESP_LOGI(TAG, "Received Gobox: key=MENU"); + break; + case GOBOX_RETURN: + ESP_LOGI(TAG, "Received Gobox: key=RETURN"); + break; + case GOBOX_UP: + ESP_LOGI(TAG, "Received Gobox: key=UP"); + break; + case GOBOX_LEFT: + ESP_LOGI(TAG, "Received Gobox: key=LEFT"); + break; + case GOBOX_RIGHT: + ESP_LOGI(TAG, "Received Gobox: key=RIGHT"); + break; + case GOBOX_DOWN: + ESP_LOGI(TAG, "Received Gobox: key=DOWN"); + break; + case GOBOX_OK: + ESP_LOGI(TAG, "Received Gobox: key=OK"); + break; + case GOBOX_TOGGLE: + ESP_LOGI(TAG, "Received Gobox: key=TOGGLE"); + break; + case GOBOX_PROFILE: + ESP_LOGI(TAG, "Received Gobox: key=PROFILE"); + break; + case GOBOX_FASTER: + ESP_LOGI(TAG, "Received Gobox: key=FASTER"); + break; + case GOBOX_SLOWER: + ESP_LOGI(TAG, "Received Gobox: key=SLOWER"); + break; + case GOBOX_LOUDER: + ESP_LOGI(TAG, "Received Gobox: key=LOUDER"); + break; + case GOBOX_SOFTER: + ESP_LOGI(TAG, "Received Gobox: key=SOFTER"); + break; + } +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/gobox_protocol.h b/esphome/components/remote_base/gobox_protocol.h new file mode 100644 index 0000000000..7e18b61458 --- /dev/null +++ b/esphome/components/remote_base/gobox_protocol.h @@ -0,0 +1,54 @@ +#pragma once + +#include "esphome/core/component.h" +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct GoboxData { + int code; + bool operator==(const GoboxData &rhs) const { return code == rhs.code; } +}; + +enum { + GOBOX_MENU = 0xaa55, + GOBOX_RETURN = 0x22dd, + GOBOX_UP = 0x0af5, + GOBOX_LEFT = 0x8a75, + GOBOX_RIGHT = 0x48b7, + GOBOX_DOWN = 0xa25d, + GOBOX_OK = 0xc837, + GOBOX_TOGGLE = 0xb847, + GOBOX_PROFILE = 0xfa05, + GOBOX_FASTER = 0xf00f, + GOBOX_SLOWER = 0xd02f, + GOBOX_LOUDER = 0xb04f, + GOBOX_SOFTER = 0xf807, +}; + +class GoboxProtocol : public RemoteProtocol { + private: + void dump_timings_(const RawTimings &timings) const; + + public: + void encode(RemoteTransmitData *dst, const GoboxData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const GoboxData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(Gobox) + +template class GoboxAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint64_t, code); + + void encode(RemoteTransmitData *dst, Ts... x) override { + GoboxData data{}; + data.code = this->code_.value(x...); + GoboxProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/tests/components/remote_receiver/common-actions.yaml b/tests/components/remote_receiver/common-actions.yaml index 08b1091116..ca7713f58a 100644 --- a/tests/components/remote_receiver/common-actions.yaml +++ b/tests/components/remote_receiver/common-actions.yaml @@ -48,6 +48,11 @@ on_drayton: - logger.log: format: "on_drayton: %u %u %u" args: ["x.address", "x.channel", "x.command"] +on_gobox: + then: + - logger.log: + format: "on_gobox: %d" + args: ["x.code"] on_jvc: then: - logger.log: