[remote_base] Add the Somfy RTS transmit protocol (#16502)

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
RobertH1993
2026-10-11 20:11:15 -10:00
committed by GitHub
co-authored by Copilot Autofix powered by AI pre-commit-ci-lite[bot] J. Nick Koston
parent 8191cd18d2
commit 36082b23c2
5 changed files with 152 additions and 0 deletions
@@ -27,6 +27,7 @@ from esphome.const import (
CONF_ID,
CONF_INDEX,
CONF_INVERTED,
CONF_KEY,
CONF_LEVEL,
CONF_MAGNITUDE,
CONF_NBITS,
@@ -63,6 +64,9 @@ CONF_TRANSMITTER_ID = "transmitter_id"
CONF_FIRST = "first"
CONF_SUFFIX = "suffix"
CONF_SUFFIX_BITS = "suffix_bits"
CONF_CONTROL = "control"
CONF_CONTROL_IN_KEY = "control_in_key"
CONF_ROLLING_CODE = "rolling_code"
ns = remote_base_ns = cg.esphome_ns.namespace("remote_base")
RemoteProtocol = ns.class_("RemoteProtocol")
@@ -2114,6 +2118,42 @@ register_protocol_action(
)
# SomfyRts
SOMFY_RTS_SCHEMA = cv.Schema(
{
cv.Required(CONF_KEY): cv.hex_uint8_t,
cv.Required(CONF_CONTROL): cv.All(cv.hex_uint8_t, cv.Range(max=0x0F)),
cv.Required(CONF_ROLLING_CODE): cv.hex_uint16_t,
cv.Required(CONF_ADDRESS): cv.All(cv.hex_uint32_t, cv.Range(max=0xFFFFFF)),
cv.Optional(CONF_CONTROL_IN_KEY, default=False): cv.boolean,
}
)
def _validate_somfy_rts(config: ConfigType) -> ConfigType:
key = config[CONF_KEY]
if config[CONF_CONTROL_IN_KEY] is True and isinstance(key, int) and key & 0x0F:
raise cv.Invalid(
f"With {CONF_CONTROL_IN_KEY} the low nibble of {CONF_KEY} carries {CONF_CONTROL} "
"and must be 0",
path=[CONF_KEY],
)
return config
# Transmit only: the protocol has no decoder yet, so no binary sensor, trigger or dumper.
register_protocol_action(
"somfy_rts",
"SomfyRts",
SOMFY_RTS_SCHEMA,
(CONF_KEY, "key", cg.uint8),
(CONF_CONTROL, "control", cg.uint8),
(CONF_ROLLING_CODE, "rolling_code", cg.uint16),
(CONF_ADDRESS, "address", cg.uint32),
(CONF_CONTROL_IN_KEY, "control_in_key", cg.bool_),
validate=_validate_somfy_rts,
)
# Nexa
NexaData, NexaBinarySensor, NexaTrigger, _, NexaDumper = declare_protocol("Nexa")
NEXA_SCHEMA = cv.Schema(
@@ -0,0 +1,69 @@
#include "somfy_rts_protocol.h"
#include "esphome/core/log.h"
namespace esphome::remote_base {
// Timings from https://pushstack.wordpress.com/somfy-rts-protocol/
static constexpr uint8_t FRAME_SIZE_IN_BYTES = 7;
static constexpr uint16_t HALF_BIT_US = 604;
static constexpr uint16_t WAKEUP_HIGH_US = 9415;
static constexpr uint32_t WAKEUP_LOW_US = 89565;
static constexpr uint16_t HW_SYNC_US = 2560;
static constexpr uint16_t SW_SYNC_HIGH_US = 4550;
static constexpr uint16_t SW_SYNC_LOW_US = HALF_BIT_US;
// Wakeup, two hardware syncs and a software sync (8), two entries per bit (112)
static constexpr uint32_t FRAME_ENTRIES = 8 + FRAME_SIZE_IN_BYTES * 8 * 2;
void SomfyRtsProtocol::encode(RemoteTransmitData *dst, const SomfyRtsData &data) {
dst->reserve(FRAME_ENTRIES);
dst->set_carrier_frequency(0);
dst->item(WAKEUP_HIGH_US, WAKEUP_LOW_US);
dst->item(HW_SYNC_US, HW_SYNC_US);
dst->item(HW_SYNC_US, HW_SYNC_US);
dst->item(SW_SYNC_HIGH_US, SW_SYNC_LOW_US);
uint8_t frame[FRAME_SIZE_IN_BYTES];
// Some non standard remotes put the control nibble in the key byte and send 0xF in the control nibble
if (data.control_in_key) {
frame[0] = (data.key & 0xF0) | (data.control & 0x0F);
frame[1] = 0xF0;
} else {
frame[0] = data.key;
frame[1] = (data.control & 0x0F) << 4;
}
frame[2] = data.rolling_code >> 8;
frame[3] = data.rolling_code;
frame[4] = data.address >> 16;
frame[5] = data.address >> 8;
frame[6] = data.address;
uint8_t crc = 0;
for (uint8_t data_byte : frame) {
crc ^= (data_byte >> 4) ^ (data_byte & 0xF);
}
frame[1] |= (crc & 0x0F);
for (uint8_t i = 1; i < FRAME_SIZE_IN_BYTES; i++) {
frame[i] ^= frame[i - 1];
}
// Manchester, MSB first: a 1 is space then mark, a 0 is mark then space. Frames repeat with
// the 30 ms gap through the action's repeat option, not inside the frame.
for (uint8_t data_byte : frame) {
for (int8_t y = 7; y >= 0; y--) {
if (data_byte & (1 << y)) {
dst->space(HALF_BIT_US);
dst->mark(HALF_BIT_US);
} else {
dst->mark(HALF_BIT_US);
dst->space(HALF_BIT_US);
}
}
}
}
} // namespace esphome::remote_base
@@ -0,0 +1,21 @@
#pragma once
#include "remote_base.h"
namespace esphome::remote_base {
struct SomfyRtsData {
uint32_t address;
uint16_t rolling_code;
uint8_t key;
uint8_t control;
bool control_in_key;
};
// Transmit only: there is no decoder yet, so no binary sensor, trigger or dumper is registered.
class SomfyRtsProtocol : public RemoteProtocol<SomfyRtsData> {
public:
void encode(RemoteTransmitData *dst, const SomfyRtsData &data);
};
} // namespace esphome::remote_base
+1
View File
@@ -187,6 +187,7 @@
#define USE_REMOTE_PROTOCOL_ROOMBA
#define USE_REMOTE_PROTOCOL_SAMSUNG
#define USE_REMOTE_PROTOCOL_SAMSUNG36
#define USE_REMOTE_PROTOCOL_SOMFY_RTS
#define USE_REMOTE_PROTOCOL_SONY
#define USE_REMOTE_PROTOCOL_SYMPHONY
#define USE_REMOTE_PROTOCOL_TOSHIBA_AC
@@ -419,3 +419,24 @@ button:
on_press:
remote_transmitter.transmit_roomba:
data: 0x88
- platform: template
name: SomfyRTS
on_press:
- remote_transmitter.transmit_somfy_rts:
address: 0x630139
control: 0x08
key: 0xA0
rolling_code: 1234
control_in_key: false
- platform: template
name: SomfyRTS Control In Key
on_press:
- remote_transmitter.transmit_somfy_rts:
address: 0x630139
control: 0x02
key: 0xA0
rolling_code: !lambda return 1235;
control_in_key: true
repeat:
times: 3
wait_time: 30ms