From e0dddf52458f7b46cd88ab072c3a3670c4047697 Mon Sep 17 00:00:00 2001 From: Artem Sheremet Date: Mon, 12 May 2025 18:13:07 +0200 Subject: [PATCH] Rewrite somfy in transmitter compatible code --- blinds.yaml | 21 ++++- components/somfy/cover.py | 2 +- components/somfy/somfy.cpp | 182 +++++++++++++++++++++++-------------- 3 files changed, 130 insertions(+), 75 deletions(-) diff --git a/blinds.yaml b/blinds.yaml index 5f5069b..e83dcf4 100644 --- a/blinds.yaml +++ b/blinds.yaml @@ -38,10 +38,23 @@ spi: mosi_pin: 15 cc1101: - id: transceiver - cs_pin: 13 - tuner: - frequency: 433340 + id: transceiver + cs_pin: 13 + tuner: + frequency: 433340 + +remote_transmitter: + pin: + number: 4 + allow_other_uses: true + carrier_duty_percent: 100% + id: somfy_remote_transmitter + on_transmit: + then: + - cc1101.begin_tx: transceiver + on_complete: + then: + - cc1101.end_tx: transceiver cover: - name: "Office window" diff --git a/components/somfy/cover.py b/components/somfy/cover.py index b3efb61..366bca9 100644 --- a/components/somfy/cover.py +++ b/components/somfy/cover.py @@ -4,7 +4,7 @@ from esphome.components import cover from esphome import pins from esphome.const import CONF_ID, CONF_ALLOW_OTHER_USES -DEPENDENCIES = ["mqtt"] +DEPENDENCIES = ["mqtt", "remote_transmitter"] AUTO_LOAD = ["mqtt"] somfy_ns = cg.esphome_ns.namespace("somfy") diff --git a/components/somfy/somfy.cpp b/components/somfy/somfy.cpp index d21195e..fc02223 100644 --- a/components/somfy/somfy.cpp +++ b/components/somfy/somfy.cpp @@ -1,6 +1,7 @@ #include "somfy.h" #include "esphome/components/mqtt/mqtt_client.h" +#include "esphome/components/remote_transmitter/remote_transmitter.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -13,8 +14,101 @@ static const uint8_t CMD_UP = 0x02; static const uint8_t CMD_DOWN = 0x04; static const uint8_t CMD_PROG = 0x08; +namespace { + static const int RF_SYMBOL = 640; +struct SomfyRTSProtocolData { + uint8_t command; + uint32_t rolling_code; + uint32_t remote_id; +}; + +class SomfyRTSProtocol + : public remote_base::RemoteProtocol { + public: + virtual void encode(remote_base::RemoteTransmitData *dst, + const ProtocolData &data) override { + /* + * 1. Build a frame. + */ + uint8_t frame[] = { + 0xA7, // Encryption key. + static_cast(data.command << 4), // 4 lsb will be checksum. + static_cast(data.rolling_code >> 8), + static_cast(data.rolling_code), + static_cast(data.remote_id >> 16), + static_cast(data.remote_id >> 8), + static_cast(data.remote_id), + }; + + // Checksum calculation: a XOR of all the nibbles. + uint8_t checksum = 0; + for (uint8_t i = 0; i < 7; i++) { + checksum = checksum ^ frame[i] ^ (frame[i] >> 4); + } + checksum &= 0b1111; // We keep the last 4 bits only. + frame[1] |= checksum; + + // Obfuscation: a XOR of all the bytes. + for (uint8_t i = 1; i < 7; i++) { + frame[i] ^= frame[i - 1]; + } + + /* + * 2. Encode the frame. + */ + dst->reset(); + dst->reserve(400); + dst->set_carrier_frequency(433340); + + // Wake-up pulse & silence. + dst->item(9415, 89565); + + for (int repeat = 0; repeat < 3; ++repeat) { + // Hardware sync. + uint8_t sync = repeat == 0 ? 2 : 7; + for (uint8_t i = 0; i < sync; i++) { + dst->item(4 * RF_SYMBOL, 4 * RF_SYMBOL); + } + + // Software sync. + dst->item(4550, RF_SYMBOL); + + // Data: bits are sent one by one. + for (uint8_t octet = 0; octet < 7; ++octet) { + // Starting with MSB. + for (signed char bit = 7; bit >= 0; --bit) { + uint8_t value = (frame[octet] >> bit) & 1; + if (value == 1) { + dst->space(RF_SYMBOL); + dst->mark(RF_SYMBOL); + } else { + dst->item(RF_SYMBOL, RF_SYMBOL); + } + } + } + + // Inter-frame silence. + dst->space(30415); + } + } + + virtual void dump(const ProtocolData &data) override { + ESP_LOGI(TAG, " Remote ID: %d", data.remote_id); + ESP_LOGI(TAG, " Rolling Code: %d", data.rolling_code); + ESP_LOGI(TAG, " Command: %d", data.command); + } + + virtual optional decode( + remote_base::RemoteReceiveData src) override { + // Not implemented. + return nullopt; + } +}; + +} // namespace + void SomfyRTSCover::dump_config() { LOG_COVER("", "Somfy RTS Cover", this); LOG_PIN(" RF Pin", this->rf_pin_); @@ -109,79 +203,27 @@ void SomfyRTSCover::send(uint8_t command) { ESP_LOGI(TAG, "Remote #%d sending command %d with rolling code %d", remote_id_, command, rolling_code_); - uint8_t frame[] = { - 0xA7, // Encryption key. - static_cast(command << 4), // 4 lsb will be checksum. - static_cast(rolling_code_ >> 8), - static_cast(rolling_code_), - static_cast(remote_id_ >> 16), - static_cast(remote_id_ >> 8), - static_cast(remote_id_), - }; + SomfyRTSProtocolData data{.command = command, + .rolling_code = rolling_code_, + .remote_id = remote_id_}; + + remote_base::RemoteTransmitData transmit_data; + SomfyRTSProtocol().encode(&transmit_data, data); + remote_base::RawTimings td = transmit_data.get_data(); + + for (int i = 0; i < td.size(); ++i) { + int32_t pulse = td[i]; + if (pulse > 0) { + rf_pin_->digital_write(true); + delayMicroseconds(pulse); + } else { + rf_pin_->digital_write(false); + delayMicroseconds(-pulse); + } + } ++rolling_code_; - - // Checksum calculation: a XOR of all the nibbles. - uint8_t checksum = 0; - for (uint8_t i = 0; i < 7; i++) { - checksum = checksum ^ frame[i] ^ (frame[i] >> 4); - } - checksum &= 0b1111; // We keep the last 4 bits only. - frame[1] |= checksum; - - // Obfuscation: a XOR of all the bytes. - for (uint8_t i = 1; i < 7; i++) { - frame[i] ^= frame[i - 1]; - } - - // Wake-up pulse & silence. - rf_pin_->digital_write(true); - delayMicroseconds(9415); - rf_pin_->digital_write(false); - delayMicroseconds(89565); - - for (int repeat = 0; repeat < 3; ++repeat) { - // Hardware sync. - uint8_t sync = repeat == 0 ? 2 : 7; - for (uint8_t i = 0; i < sync; i++) { - rf_pin_->digital_write(true); - delayMicroseconds(4 * RF_SYMBOL); - rf_pin_->digital_write(false); - delayMicroseconds(4 * RF_SYMBOL); - } - - // Software sync. - rf_pin_->digital_write(true); - delayMicroseconds(4550); - rf_pin_->digital_write(false); - delayMicroseconds(RF_SYMBOL); - - // Data: bits are sent one by one. - for (uint8_t octet = 0; octet < 7; ++octet) { - // Starting with MSB. - for (signed char bit = 7; bit >= 0; --bit) { - uint8_t value = (frame[octet] >> bit) & 1; - if (value == 1) { - rf_pin_->digital_write(false); - delayMicroseconds(RF_SYMBOL); - rf_pin_->digital_write(true); - delayMicroseconds(RF_SYMBOL); - } else { - rf_pin_->digital_write(true); - delayMicroseconds(RF_SYMBOL); - rf_pin_->digital_write(false); - delayMicroseconds(RF_SYMBOL); - } - } - } - - rf_pin_->digital_write(false); - // Inter-frame silence. - delayMicroseconds(30415); - } - this->rolling_code_pref_.save(&rolling_code_); - if (mqtt::global_mqtt_client != nullptr) { // Publish the new rolling code at the end to ensure fast reaction time // when the component is called. This method may be synchronous and