From 37f6e53b4e8b9016a41807764f7fa72e7c51c3d0 Mon Sep 17 00:00:00 2001 From: Evgeni Golov Date: Sun, 21 Dec 2025 15:29:54 +0100 Subject: [PATCH 1/2] vendor relevant parts of Somfy_Remote_Lib 0.5.0 --- components/somfy/NVSRollingCodeStorage.cpp | 51 +++++++ components/somfy/NVSRollingCodeStorage.h | 20 +++ components/somfy/RollingCodeStorage.h | 13 ++ components/somfy/SomfyRemote.cpp | 159 +++++++++++++++++++++ components/somfy/SomfyRemote.h | 54 +++++++ 5 files changed, 297 insertions(+) create mode 100644 components/somfy/NVSRollingCodeStorage.cpp create mode 100644 components/somfy/NVSRollingCodeStorage.h create mode 100644 components/somfy/RollingCodeStorage.h create mode 100644 components/somfy/SomfyRemote.cpp create mode 100644 components/somfy/SomfyRemote.h diff --git a/components/somfy/NVSRollingCodeStorage.cpp b/components/somfy/NVSRollingCodeStorage.cpp new file mode 100644 index 0000000..4893e02 --- /dev/null +++ b/components/somfy/NVSRollingCodeStorage.cpp @@ -0,0 +1,51 @@ +#include "NVSRollingCodeStorage.h" + +#ifdef ESP32 + +#include +#include +#include + +NVSRollingCodeStorage::NVSRollingCodeStorage(const char *name, const char *key) : name(name), key(key) {} + +uint16_t NVSRollingCodeStorage::nextCode() { + uint16_t code; + esp_err_t err; + nvs_handle rcs_handle; + + // Initialize NVS + err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + // NVS partition was truncated and needs to be erased + // Retry nvs_flash_init + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK(err); + + err = nvs_open(name, NVS_READWRITE, &rcs_handle); + ESP_ERROR_CHECK(err); + + err = nvs_get_u16(rcs_handle, key, &code); + switch (err) { + case ESP_OK: + break; + case ESP_ERR_NVS_NOT_FOUND: + code = 1; + break; + default: + Serial.print("Error reading!"); + Serial.println(esp_err_to_name(err)); + } + err = nvs_set_u16(rcs_handle, key, code + 1); +#ifdef DEBUG + Serial.println((err != ESP_OK) ? "nvs_set failed!" : "nvs_set done"); +#endif + err = nvs_commit(rcs_handle); +#ifdef DEBUG + Serial.println((err != ESP_OK) ? "nvs_commit failed!" : "nvs_commit done"); +#endif + return code; +} + +#endif diff --git a/components/somfy/NVSRollingCodeStorage.h b/components/somfy/NVSRollingCodeStorage.h new file mode 100644 index 0000000..f4b6b8d --- /dev/null +++ b/components/somfy/NVSRollingCodeStorage.h @@ -0,0 +1,20 @@ +#pragma once + +#ifdef ESP32 + +#include "RollingCodeStorage.h" + +/** + * Stores the rolling codes in the NVS of an ESP32, the codes require two bytes. + */ +class NVSRollingCodeStorage : public RollingCodeStorage { +private: + const char *name; + const char *key; + +public: + NVSRollingCodeStorage(const char *name, const char *key); + uint16_t nextCode() override; +}; + +#endif diff --git a/components/somfy/RollingCodeStorage.h b/components/somfy/RollingCodeStorage.h new file mode 100644 index 0000000..94eb5ed --- /dev/null +++ b/components/somfy/RollingCodeStorage.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class RollingCodeStorage { +public: + /** + * Get the next rolling code from the store. This should also increase the rolling code and store it persistently. + * + * @return next rolling code + */ + virtual uint16_t nextCode() = 0; +}; diff --git a/components/somfy/SomfyRemote.cpp b/components/somfy/SomfyRemote.cpp new file mode 100644 index 0000000..0139114 --- /dev/null +++ b/components/somfy/SomfyRemote.cpp @@ -0,0 +1,159 @@ +#include "SomfyRemote.h" + +#define SYMBOL 640 + +SomfyRemote::SomfyRemote(byte emitterPin, uint32_t remote, RollingCodeStorage *rollingCodeStorage) + : emitterPin(emitterPin), remote(remote), rollingCodeStorage(rollingCodeStorage) {} + +void SomfyRemote::setup() { + pinMode(emitterPin, OUTPUT); + digitalWrite(emitterPin, LOW); +} + +void SomfyRemote::sendCommand(Command command, int repeat) { + const uint16_t rollingCode = rollingCodeStorage->nextCode(); + sendCommandWithCode(command, rollingCode, repeat); +} + +void SomfyRemote::sendCommandWithCode(Command command, uint16_t rollingCode, int repeat) { + byte frame[7]; + buildFrame(frame, command, rollingCode); + sendFrame(frame, 2); + for (int i = 0; i < repeat; i++) { + sendFrame(frame, 7); + } +} + +void SomfyRemote::printFrame(byte *frame) { + for (byte i = 0; i < 7; i++) { + if (frame[i] >> 4 == 0) { // Displays leading zero in case the most significant + Serial.print("0"); // nibble is a 0. + } + Serial.print(frame[i], HEX); + Serial.print(" "); + } + Serial.println(); +} + +void SomfyRemote::buildFrame(byte *frame, Command command, uint16_t code) { + const byte button = static_cast(command); + frame[0] = 0xA7; // Encryption key. Doesn't matter much + frame[1] = button << 4; // Which button did you press? The 4 LSB will be the checksum + frame[2] = code >> 8; // Rolling code (big endian) + frame[3] = code; // Rolling code + frame[4] = remote >> 16; // Remote address + frame[5] = remote >> 8; // Remote address + frame[6] = remote; // Remote address + +#ifdef DEBUG + Serial.print("Frame : "); + printFrame(frame); +#endif + + // Checksum calculation: a XOR of all the nibbles + byte checksum = 0; + for (byte i = 0; i < 7; i++) { + checksum = checksum ^ frame[i] ^ (frame[i] >> 4); + } + checksum &= 0b1111; // We keep the last 4 bits only + + // Checksum integration + frame[1] |= checksum; + +#ifdef DEBUG + Serial.print("With checksum : "); + printFrame(frame); +#endif + + // Obfuscation: a XOR of all the bytes + for (byte i = 1; i < 7; i++) { + frame[i] ^= frame[i - 1]; + } + +#ifdef DEBUG + Serial.print("Obfuscated : "); + printFrame(frame); +#endif +} + +void SomfyRemote::sendFrame(byte *frame, byte sync) { + if (sync == 2) { // Only with the first frame. + // Wake-up pulse & Silence + sendHigh(9415); + sendLow(9565); + delay(80); + } + + // Hardware sync: two sync for the first frame, seven for the following ones. + for (int i = 0; i < sync; i++) { + sendHigh(4 * SYMBOL); + sendLow(4 * SYMBOL); + } + + // Software sync + sendHigh(4550); + sendLow(SYMBOL); + + // Data: bits are sent one by one, starting with the MSB. + for (byte i = 0; i < 56; i++) { + if (((frame[i / 8] >> (7 - (i % 8))) & 1) == 1) { + sendLow(SYMBOL); + sendHigh(SYMBOL); + } else { + sendHigh(SYMBOL); + sendLow(SYMBOL); + } + } + + // Inter-frame silence + sendLow(415); + delay(30); +} + +void SomfyRemote::sendHigh(uint16_t durationInMicroseconds) { +#if defined(ESP32) || defined(ESP8266) + digitalWrite(emitterPin, HIGH); + delayMicroseconds(durationInMicroseconds); +#elif defined(ARDUINO_ARCH_AVR) + // TODO fast write + digitalWrite(emitterPin, HIGH); + delayMicroseconds(durationInMicroseconds); +#endif +} + +void SomfyRemote::sendLow(uint16_t durationInMicroseconds) { +#if defined(ESP32) || defined(ESP8266) + digitalWrite(emitterPin, LOW); + delayMicroseconds(durationInMicroseconds); +#elif defined(ARDUINO_ARCH_AVR) + // TODO fast write + digitalWrite(emitterPin, LOW); + delayMicroseconds(durationInMicroseconds); +#endif +} + +Command getSomfyCommand(const String &string) { + if (string.equalsIgnoreCase("My")) { + return Command::My; + } else if (string.equalsIgnoreCase("Up")) { + return Command::Up; + } else if (string.equalsIgnoreCase("MyUp")) { + return Command::MyUp; + } else if (string.equalsIgnoreCase("Down")) { + return Command::Down; + } else if (string.equalsIgnoreCase("MyDown")) { + return Command::MyDown; + } else if (string.equalsIgnoreCase("UpDown")) { + return Command::UpDown; + } else if (string.equalsIgnoreCase("Prog")) { + return Command::Prog; + } else if (string.equalsIgnoreCase("SunFlag")) { + return Command::SunFlag; + } else if (string.equalsIgnoreCase("Flag")) { + return Command::Flag; + } else if (string.length() == 1) { + return static_cast(strtol(string.c_str(), nullptr, 16)); + } else { + return Command::My; + } +} diff --git a/components/somfy/SomfyRemote.h b/components/somfy/SomfyRemote.h new file mode 100644 index 0000000..6a8d76a --- /dev/null +++ b/components/somfy/SomfyRemote.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include "RollingCodeStorage.h" + +enum class Command : byte { + My = 0x1, + Up = 0x2, + MyUp = 0x3, + Down = 0x4, + MyDown = 0x5, + UpDown = 0x6, + Prog = 0x8, + SunFlag = 0x9, + Flag = 0xA +}; + +class SomfyRemote { +private: + byte emitterPin; + uint32_t remote; + RollingCodeStorage *const rollingCodeStorage; + + void buildFrame(byte *frame, Command command, uint16_t code); + void sendFrame(byte *frame, byte sync); + void printFrame(byte *frame); + + virtual void sendHigh(uint16_t durationInMicroseconds); + virtual void sendLow(uint16_t durationInMicroseconds); + +public: + SomfyRemote(byte emitterPin, uint32_t remote, RollingCodeStorage *rollingCodeStorage); + void setup(); + /** + * Send a command with this SomfyRemote. + * + * @param command the command to send + * @param repeat the number how often the command should be repeated, default 4. Should + * only be used when simulating holding a button. + */ + void sendCommand(Command command, int repeat = 4); + /** + * Send a command with this SomfyRemote. + * + * @param command the command to send + * @param rollingCode the rolling code to use. Should only be used with external storage. + * @param repeat the number how often the command should be repeated, default 4. Should + * only be used when simulating holding a button. + */ + void sendCommandWithCode(Command command, uint16_t rollingCode, int repeat = 4); +}; + +Command getSomfyCommand(const String &string); From 9599607ed9fa80d7e890cd4e653233e8f303119c Mon Sep 17 00:00:00 2001 From: Evgeni Golov Date: Sun, 21 Dec 2025 15:58:21 +0100 Subject: [PATCH 2/2] make somfy compile on idf --- components/somfy/NVSRollingCodeStorage.cpp | 17 +--- components/somfy/NVSRollingCodeStorage.h | 4 - components/somfy/RollingCodeStorage.h | 2 +- components/somfy/SomfyRemote.cpp | 103 +++++---------------- components/somfy/SomfyRemote.h | 22 +++-- components/somfy/somfy_cover.h | 29 +----- somfy.yaml | 3 - 7 files changed, 46 insertions(+), 134 deletions(-) diff --git a/components/somfy/NVSRollingCodeStorage.cpp b/components/somfy/NVSRollingCodeStorage.cpp index 4893e02..36fb6f0 100644 --- a/components/somfy/NVSRollingCodeStorage.cpp +++ b/components/somfy/NVSRollingCodeStorage.cpp @@ -1,10 +1,9 @@ #include "NVSRollingCodeStorage.h" -#ifdef ESP32 - #include #include #include +#include "esphome/core/log.h" NVSRollingCodeStorage::NVSRollingCodeStorage(const char *name, const char *key) : name(name), key(key) {} @@ -34,18 +33,12 @@ uint16_t NVSRollingCodeStorage::nextCode() { code = 1; break; default: - Serial.print("Error reading!"); - Serial.println(esp_err_to_name(err)); + ESP_LOGD("somfy.nvs", "Error reading!"); + ESP_LOGD("somfy.nvs", esp_err_to_name(err)); } err = nvs_set_u16(rcs_handle, key, code + 1); -#ifdef DEBUG - Serial.println((err != ESP_OK) ? "nvs_set failed!" : "nvs_set done"); -#endif + ESP_LOGD("somfy.nvs", (err != ESP_OK) ? "nvs_set failed!" : "nvs_set done"); err = nvs_commit(rcs_handle); -#ifdef DEBUG - Serial.println((err != ESP_OK) ? "nvs_commit failed!" : "nvs_commit done"); -#endif + ESP_LOGD("somfy.nvs", (err != ESP_OK) ? "nvs_commit failed!" : "nvs_commit done"); return code; } - -#endif diff --git a/components/somfy/NVSRollingCodeStorage.h b/components/somfy/NVSRollingCodeStorage.h index f4b6b8d..73b6611 100644 --- a/components/somfy/NVSRollingCodeStorage.h +++ b/components/somfy/NVSRollingCodeStorage.h @@ -1,7 +1,5 @@ #pragma once -#ifdef ESP32 - #include "RollingCodeStorage.h" /** @@ -16,5 +14,3 @@ class NVSRollingCodeStorage : public RollingCodeStorage { NVSRollingCodeStorage(const char *name, const char *key); uint16_t nextCode() override; }; - -#endif diff --git a/components/somfy/RollingCodeStorage.h b/components/somfy/RollingCodeStorage.h index 94eb5ed..faa6332 100644 --- a/components/somfy/RollingCodeStorage.h +++ b/components/somfy/RollingCodeStorage.h @@ -1,6 +1,6 @@ #pragma once -#include +#include class RollingCodeStorage { public: diff --git a/components/somfy/SomfyRemote.cpp b/components/somfy/SomfyRemote.cpp index 0139114..9215ef1 100644 --- a/components/somfy/SomfyRemote.cpp +++ b/components/somfy/SomfyRemote.cpp @@ -2,12 +2,15 @@ #define SYMBOL 640 -SomfyRemote::SomfyRemote(byte emitterPin, uint32_t remote, RollingCodeStorage *rollingCodeStorage) +namespace esphome { +namespace somfy { + +SomfyRemote::SomfyRemote(esphome::InternalGPIOPin *emitterPin, uint32_t remote, RollingCodeStorage *rollingCodeStorage) : emitterPin(emitterPin), remote(remote), rollingCodeStorage(rollingCodeStorage) {} void SomfyRemote::setup() { - pinMode(emitterPin, OUTPUT); - digitalWrite(emitterPin, LOW); + this->emitterPin->pin_mode(gpio::FLAG_OUTPUT); + this->emitterPin->digital_write(false); } void SomfyRemote::sendCommand(Command command, int repeat) { @@ -16,7 +19,7 @@ void SomfyRemote::sendCommand(Command command, int repeat) { } void SomfyRemote::sendCommandWithCode(Command command, uint16_t rollingCode, int repeat) { - byte frame[7]; + uint8_t frame[7]; buildFrame(frame, command, rollingCode); sendFrame(frame, 2); for (int i = 0; i < repeat; i++) { @@ -24,19 +27,8 @@ void SomfyRemote::sendCommandWithCode(Command command, uint16_t rollingCode, int } } -void SomfyRemote::printFrame(byte *frame) { - for (byte i = 0; i < 7; i++) { - if (frame[i] >> 4 == 0) { // Displays leading zero in case the most significant - Serial.print("0"); // nibble is a 0. - } - Serial.print(frame[i], HEX); - Serial.print(" "); - } - Serial.println(); -} - -void SomfyRemote::buildFrame(byte *frame, Command command, uint16_t code) { - const byte button = static_cast(command); +void SomfyRemote::buildFrame(uint8_t *frame, Command command, uint16_t code) { + const uint8_t button = static_cast(command); frame[0] = 0xA7; // Encryption key. Doesn't matter much frame[1] = button << 4; // Which button did you press? The 4 LSB will be the checksum frame[2] = code >> 8; // Rolling code (big endian) @@ -45,14 +37,9 @@ void SomfyRemote::buildFrame(byte *frame, Command command, uint16_t code) { frame[5] = remote >> 8; // Remote address frame[6] = remote; // Remote address -#ifdef DEBUG - Serial.print("Frame : "); - printFrame(frame); -#endif - // Checksum calculation: a XOR of all the nibbles - byte checksum = 0; - for (byte i = 0; i < 7; i++) { + 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 @@ -60,28 +47,18 @@ void SomfyRemote::buildFrame(byte *frame, Command command, uint16_t code) { // Checksum integration frame[1] |= checksum; -#ifdef DEBUG - Serial.print("With checksum : "); - printFrame(frame); -#endif - // Obfuscation: a XOR of all the bytes - for (byte i = 1; i < 7; i++) { + for (uint8_t i = 1; i < 7; i++) { frame[i] ^= frame[i - 1]; } - -#ifdef DEBUG - Serial.print("Obfuscated : "); - printFrame(frame); -#endif } -void SomfyRemote::sendFrame(byte *frame, byte sync) { +void SomfyRemote::sendFrame(uint8_t *frame, uint8_t sync) { if (sync == 2) { // Only with the first frame. // Wake-up pulse & Silence sendHigh(9415); sendLow(9565); - delay(80); + delayMicroseconds(80000); } // Hardware sync: two sync for the first frame, seven for the following ones. @@ -95,7 +72,7 @@ void SomfyRemote::sendFrame(byte *frame, byte sync) { sendLow(SYMBOL); // Data: bits are sent one by one, starting with the MSB. - for (byte i = 0; i < 56; i++) { + for (uint8_t i = 0; i < 56; i++) { if (((frame[i / 8] >> (7 - (i % 8))) & 1) == 1) { sendLow(SYMBOL); sendHigh(SYMBOL); @@ -107,53 +84,19 @@ void SomfyRemote::sendFrame(byte *frame, byte sync) { // Inter-frame silence sendLow(415); - delay(30); + delayMicroseconds(30000); } void SomfyRemote::sendHigh(uint16_t durationInMicroseconds) { -#if defined(ESP32) || defined(ESP8266) - digitalWrite(emitterPin, HIGH); - delayMicroseconds(durationInMicroseconds); -#elif defined(ARDUINO_ARCH_AVR) - // TODO fast write - digitalWrite(emitterPin, HIGH); - delayMicroseconds(durationInMicroseconds); -#endif + emitterPin->digital_write(true); + delayMicroseconds(durationInMicroseconds); } void SomfyRemote::sendLow(uint16_t durationInMicroseconds) { -#if defined(ESP32) || defined(ESP8266) - digitalWrite(emitterPin, LOW); - delayMicroseconds(durationInMicroseconds); -#elif defined(ARDUINO_ARCH_AVR) - // TODO fast write - digitalWrite(emitterPin, LOW); - delayMicroseconds(durationInMicroseconds); -#endif + emitterPin->digital_write(false); + delayMicroseconds(durationInMicroseconds); } -Command getSomfyCommand(const String &string) { - if (string.equalsIgnoreCase("My")) { - return Command::My; - } else if (string.equalsIgnoreCase("Up")) { - return Command::Up; - } else if (string.equalsIgnoreCase("MyUp")) { - return Command::MyUp; - } else if (string.equalsIgnoreCase("Down")) { - return Command::Down; - } else if (string.equalsIgnoreCase("MyDown")) { - return Command::MyDown; - } else if (string.equalsIgnoreCase("UpDown")) { - return Command::UpDown; - } else if (string.equalsIgnoreCase("Prog")) { - return Command::Prog; - } else if (string.equalsIgnoreCase("SunFlag")) { - return Command::SunFlag; - } else if (string.equalsIgnoreCase("Flag")) { - return Command::Flag; - } else if (string.length() == 1) { - return static_cast(strtol(string.c_str(), nullptr, 16)); - } else { - return Command::My; - } -} +} // namespace somfy +} // namespace esphome + diff --git a/components/somfy/SomfyRemote.h b/components/somfy/SomfyRemote.h index 6a8d76a..44f9067 100644 --- a/components/somfy/SomfyRemote.h +++ b/components/somfy/SomfyRemote.h @@ -1,10 +1,13 @@ #pragma once -#include - #include "RollingCodeStorage.h" +#include "esphome/core/gpio.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace somfy { -enum class Command : byte { +enum class Command : uint8_t { My = 0x1, Up = 0x2, MyUp = 0x3, @@ -18,19 +21,19 @@ enum class Command : byte { class SomfyRemote { private: - byte emitterPin; + esphome::InternalGPIOPin *emitterPin; uint32_t remote; RollingCodeStorage *const rollingCodeStorage; - void buildFrame(byte *frame, Command command, uint16_t code); - void sendFrame(byte *frame, byte sync); - void printFrame(byte *frame); + void buildFrame(uint8_t *frame, Command command, uint16_t code); + void sendFrame(uint8_t *frame, uint8_t sync); + void printFrame(uint8_t *frame); virtual void sendHigh(uint16_t durationInMicroseconds); virtual void sendLow(uint16_t durationInMicroseconds); public: - SomfyRemote(byte emitterPin, uint32_t remote, RollingCodeStorage *rollingCodeStorage); + SomfyRemote(esphome::InternalGPIOPin *emitterPin, uint32_t remote, RollingCodeStorage *rollingCodeStorage); void setup(); /** * Send a command with this SomfyRemote. @@ -51,4 +54,5 @@ class SomfyRemote { void sendCommandWithCode(Command command, uint16_t rollingCode, int repeat = 4); }; -Command getSomfyCommand(const String &string); +} // namespace somfy +} // namespace esphome diff --git a/components/somfy/somfy_cover.h b/components/somfy/somfy_cover.h index 53b143d..f7833e4 100644 --- a/components/somfy/somfy_cover.h +++ b/components/somfy/somfy_cover.h @@ -3,29 +3,8 @@ #include "esphome/components/cover/cover.h" #include "esphome/components/cc1101/cc1101.h" #include "esphome/core/component.h" -#include -#include - -class SomfyESPRemote : public SomfyRemote { -public: - SomfyESPRemote(esphome::InternalGPIOPin *emitterGPIOPin, uint32_t remote, - RollingCodeStorage *rollingCodeStorage) - : SomfyRemote(0, remote, rollingCodeStorage), - emitterGPIOPin(emitterGPIOPin) {} - -private: - esphome::InternalGPIOPin *emitterGPIOPin; - - void sendHigh(uint16_t durationInMicroseconds) override { - emitterGPIOPin->digital_write(true); - delayMicroseconds(durationInMicroseconds); - } - - void sendLow(uint16_t durationInMicroseconds) override { - emitterGPIOPin->digital_write(false); - delayMicroseconds(durationInMicroseconds); - } -}; +#include "NVSRollingCodeStorage.h" +#include "SomfyRemote.h" namespace esphome { namespace somfy { @@ -36,7 +15,7 @@ static const char *const TAG = "somfy.cover"; class SomfyCover : public Cover, public Component { protected: - SomfyESPRemote *remote_; + SomfyRemote *remote_; NVSRollingCodeStorage *storage_; const char *storage_namespace_; const char *storage_key_; @@ -51,7 +30,7 @@ class SomfyCover : public Cover, public Component { this->emitter_pin_->digital_write(false); storage_ = new NVSRollingCodeStorage(storage_namespace_, storage_key_); - remote_ = new SomfyESPRemote(emitter_pin_, remote_address_, storage_); + remote_ = new SomfyRemote(emitter_pin_, remote_address_, storage_); } CoverTraits get_traits() override { diff --git a/somfy.yaml b/somfy.yaml index 0d2e590..2273a00 100644 --- a/somfy.yaml +++ b/somfy.yaml @@ -1,9 +1,6 @@ esphome: name: somfy friendly_name: Somfy - libraries: - - Somfy_Remote_Lib@0.5.0 - - EEPROM esp32: board: nodemcu-32s