Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions components/somfy/NVSRollingCodeStorage.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "NVSRollingCodeStorage.h"

#include <esp_system.h>
#include <nvs.h>
#include <nvs_flash.h>
#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;
}
16 changes: 16 additions & 0 deletions components/somfy/NVSRollingCodeStorage.h
Original file line number Diff line number Diff line change
@@ -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;
};
13 changes: 13 additions & 0 deletions components/somfy/RollingCodeStorage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include <cstdint>

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;
};
102 changes: 102 additions & 0 deletions components/somfy/SomfyRemote.cpp
Original file line number Diff line number Diff line change
@@ -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<uint8_t>(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

58 changes: 58 additions & 0 deletions components/somfy/SomfyRemote.h
Original file line number Diff line number Diff line change
@@ -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
29 changes: 4 additions & 25 deletions components/somfy/somfy_cover.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,8 @@
#include "esphome/components/cover/cover.h"
#include "esphome/components/cc1101/cc1101.h"
#include "esphome/core/component.h"
#include <NVSRollingCodeStorage.h>
#include <SomfyRemote.h>

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 {
Expand All @@ -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_;
Expand All @@ -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 {
Expand Down
3 changes: 0 additions & 3 deletions somfy.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
esphome:
name: somfy
friendly_name: Somfy
libraries:
- Somfy_Remote_Lib@0.5.0
- EEPROM

esp32:
board: nodemcu-32s
Expand Down