diff --git a/components/somfy/NVSRollingCodeStorage.cpp b/components/somfy/NVSRollingCodeStorage.cpp new file mode 100644 index 0000000..36fb6f0 --- /dev/null +++ b/components/somfy/NVSRollingCodeStorage.cpp @@ -0,0 +1,44 @@ +#include "NVSRollingCodeStorage.h" + +#include +#include +#include +#include "esphome/core/log.h" + +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: + ESP_LOGD("somfy.nvs", "Error reading!"); + ESP_LOGD("somfy.nvs", esp_err_to_name(err)); + } + err = nvs_set_u16(rcs_handle, key, code + 1); + ESP_LOGD("somfy.nvs", (err != ESP_OK) ? "nvs_set failed!" : "nvs_set done"); + err = nvs_commit(rcs_handle); + ESP_LOGD("somfy.nvs", (err != ESP_OK) ? "nvs_commit failed!" : "nvs_commit done"); + return code; +} diff --git a/components/somfy/NVSRollingCodeStorage.h b/components/somfy/NVSRollingCodeStorage.h new file mode 100644 index 0000000..73b6611 --- /dev/null +++ b/components/somfy/NVSRollingCodeStorage.h @@ -0,0 +1,16 @@ +#pragma once + +#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; +}; diff --git a/components/somfy/RollingCodeStorage.h b/components/somfy/RollingCodeStorage.h new file mode 100644 index 0000000..faa6332 --- /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..9215ef1 --- /dev/null +++ b/components/somfy/SomfyRemote.cpp @@ -0,0 +1,102 @@ +#include "SomfyRemote.h" + +#define SYMBOL 640 + +namespace esphome { +namespace somfy { + +SomfyRemote::SomfyRemote(esphome::InternalGPIOPin *emitterPin, uint32_t remote, RollingCodeStorage *rollingCodeStorage) + : emitterPin(emitterPin), remote(remote), rollingCodeStorage(rollingCodeStorage) {} + +void SomfyRemote::setup() { + this->emitterPin->pin_mode(gpio::FLAG_OUTPUT); + this->emitterPin->digital_write(false); +} + +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) { + uint8_t frame[7]; + buildFrame(frame, command, rollingCode); + sendFrame(frame, 2); + for (int i = 0; i < repeat; i++) { + sendFrame(frame, 7); + } +} + +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) + frame[3] = code; // Rolling code + frame[4] = remote >> 16; // Remote address + frame[5] = remote >> 8; // Remote address + frame[6] = remote; // Remote address + + // 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 + + // Checksum integration + frame[1] |= checksum; + + // Obfuscation: a XOR of all the bytes + for (uint8_t i = 1; i < 7; i++) { + frame[i] ^= frame[i - 1]; + } +} + +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); + delayMicroseconds(80000); + } + + // 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 (uint8_t 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); + delayMicroseconds(30000); +} + +void SomfyRemote::sendHigh(uint16_t durationInMicroseconds) { + emitterPin->digital_write(true); + delayMicroseconds(durationInMicroseconds); +} + +void SomfyRemote::sendLow(uint16_t durationInMicroseconds) { + emitterPin->digital_write(false); + delayMicroseconds(durationInMicroseconds); +} + +} // namespace somfy +} // namespace esphome + diff --git a/components/somfy/SomfyRemote.h b/components/somfy/SomfyRemote.h new file mode 100644 index 0000000..44f9067 --- /dev/null +++ b/components/somfy/SomfyRemote.h @@ -0,0 +1,58 @@ +#pragma once + +#include "RollingCodeStorage.h" +#include "esphome/core/gpio.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace somfy { + +enum class Command : uint8_t { + My = 0x1, + Up = 0x2, + MyUp = 0x3, + Down = 0x4, + MyDown = 0x5, + UpDown = 0x6, + Prog = 0x8, + SunFlag = 0x9, + Flag = 0xA +}; + +class SomfyRemote { +private: + esphome::InternalGPIOPin *emitterPin; + uint32_t remote; + RollingCodeStorage *const rollingCodeStorage; + + 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(esphome::InternalGPIOPin *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); +}; + +} // 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