Rewrite blinds in external component
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
BasedOnStyle: Google
|
||||||
+36
-36
@@ -5,11 +5,7 @@ esphome:
|
|||||||
|
|
||||||
project:
|
project:
|
||||||
name: dasfoo.blinds
|
name: dasfoo.blinds
|
||||||
version: "2.0"
|
version: "3.0"
|
||||||
|
|
||||||
includes:
|
|
||||||
- blinds/somfy.h
|
|
||||||
- blinds/cc1101.h
|
|
||||||
|
|
||||||
libraries:
|
libraries:
|
||||||
- SPI
|
- SPI
|
||||||
@@ -18,52 +14,56 @@ esphome:
|
|||||||
packages:
|
packages:
|
||||||
device_base: !include templates/esp32-poe.yaml
|
device_base: !include templates/esp32-poe.yaml
|
||||||
|
|
||||||
globals:
|
external_components:
|
||||||
- id: somfy_remote_id_base
|
# Path is relative to the main YAML file, not this device_base template!
|
||||||
type: int
|
- source: components
|
||||||
# Can be retrieved from MQTT server. Take the key with lowest integer value
|
|
||||||
# from /rolling_code/ path.
|
|
||||||
initial_value: !secret somfy_remote_id_base
|
|
||||||
- id: somfy_rolling_codes
|
|
||||||
type: 'int[13]'
|
|
||||||
initial_value: '{0,0,0,0,0,0,0,0,0,0,0,0,0}'
|
|
||||||
restore_value: yes
|
|
||||||
|
|
||||||
cover:
|
cover:
|
||||||
- platform: custom
|
|
||||||
lambda: |-
|
|
||||||
auto rfPin = 4;
|
|
||||||
std::vector<esphome::cover::Cover*> blinds;
|
|
||||||
|
|
||||||
for (int i = 0; i < 13; ++i) {
|
|
||||||
auto cover = new SomfyRTS(
|
|
||||||
rfPin,
|
|
||||||
id(somfy_remote_id_base) + i,
|
|
||||||
id(mqtt_homeassistant),
|
|
||||||
&(id(somfy_rolling_codes)[i]));
|
|
||||||
blinds.push_back(cover);
|
|
||||||
App.register_component(cover);
|
|
||||||
}
|
|
||||||
|
|
||||||
// SomfyRTS ctor will set correct pin modes, now we can initialize cc1101.
|
|
||||||
cc1101_Init();
|
|
||||||
|
|
||||||
return blinds;
|
|
||||||
|
|
||||||
covers:
|
|
||||||
- name: "Office window"
|
- name: "Office window"
|
||||||
|
remote_id_offset: 0
|
||||||
|
<<: &somfy_cover
|
||||||
|
platform: somfy
|
||||||
|
rf_pin: 4
|
||||||
|
# Can be retrieved from MQTT server. Take the key with lowest integer value
|
||||||
|
# from /$board/rolling_code/ path.
|
||||||
|
remote_id_base: !secret somfy_remote_id_base
|
||||||
|
|
||||||
- name: "Bedroom"
|
- name: "Bedroom"
|
||||||
|
remote_id_offset: 1
|
||||||
|
<<: *somfy_cover
|
||||||
- name: "Living Room side"
|
- name: "Living Room side"
|
||||||
|
remote_id_offset: 2
|
||||||
|
<<: *somfy_cover
|
||||||
- name: "Wardrobe"
|
- name: "Wardrobe"
|
||||||
|
remote_id_offset: 3
|
||||||
|
<<: *somfy_cover
|
||||||
- name: "Child West door"
|
- name: "Child West door"
|
||||||
|
remote_id_offset: 4
|
||||||
|
<<: *somfy_cover
|
||||||
- name: "Child West window"
|
- name: "Child West window"
|
||||||
|
remote_id_offset: 5
|
||||||
|
<<: *somfy_cover
|
||||||
- name: "Hallway Stairs window"
|
- name: "Hallway Stairs window"
|
||||||
|
remote_id_offset: 6
|
||||||
|
<<: *somfy_cover
|
||||||
- name: "Kitchen window"
|
- name: "Kitchen window"
|
||||||
|
remote_id_offset: 7
|
||||||
|
<<: *somfy_cover
|
||||||
- name: "Kitchen door"
|
- name: "Kitchen door"
|
||||||
|
remote_id_offset: 8
|
||||||
|
<<: *somfy_cover
|
||||||
- name: "Living Room door"
|
- name: "Living Room door"
|
||||||
|
remote_id_offset: 9
|
||||||
|
<<: *somfy_cover
|
||||||
- name: "Living Room window"
|
- name: "Living Room window"
|
||||||
|
remote_id_offset: 10
|
||||||
|
<<: *somfy_cover
|
||||||
- name: "Child East window"
|
- name: "Child East window"
|
||||||
|
remote_id_offset: 11
|
||||||
|
<<: *somfy_cover
|
||||||
- name: "Child East door"
|
- name: "Child East door"
|
||||||
|
remote_id_offset: 12
|
||||||
|
<<: *somfy_cover
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
name: "Wintergarten"
|
name: "Wintergarten"
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
#include <ELECHOUSE_CC1101_SRC_DRV.h>
|
|
||||||
|
|
||||||
void cc1101_Init() {
|
|
||||||
ELECHOUSE_cc1101.addSpiPin(14, 16, 15, 13, 0);
|
|
||||||
ELECHOUSE_cc1101.setModul(0);
|
|
||||||
ELECHOUSE_cc1101.Init();
|
|
||||||
ELECHOUSE_cc1101.setPA(7);
|
|
||||||
ELECHOUSE_cc1101.setMHZ(433.34);
|
|
||||||
ELECHOUSE_cc1101.SetTx();
|
|
||||||
}
|
|
||||||
-170
@@ -1,170 +0,0 @@
|
|||||||
#include "esphome.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
static const char* const TAG = __FILE__;
|
|
||||||
|
|
||||||
static const uint8_t CMD_STOP = 0x01;
|
|
||||||
static const uint8_t CMD_UP = 0x02;
|
|
||||||
static const uint8_t CMD_DOWN = 0x04;
|
|
||||||
static const uint8_t CMD_PROG = 0x08;
|
|
||||||
|
|
||||||
static const int RF_SYMBOL = 640;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SomfyRTS : public Component, public Cover {
|
|
||||||
public:
|
|
||||||
SomfyRTS(int rfPin, int remoteId, esphome::mqtt::MQTTClientComponent* mqtt, int *rollingCode) {
|
|
||||||
this->rfPin = rfPin;
|
|
||||||
this->remoteId = remoteId;
|
|
||||||
this->mqtt = mqtt;
|
|
||||||
this->mqttTopicPrefix = mqtt->get_topic_prefix() + "/rolling_code/";
|
|
||||||
this->rollingCode = rollingCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() override {
|
|
||||||
pinMode(rfPin, OUTPUT);
|
|
||||||
digitalWrite(rfPin, LOW);
|
|
||||||
|
|
||||||
if (*rollingCode == 0) {
|
|
||||||
// Either a new version was flashed, flash was corrupt, or this firmware
|
|
||||||
// was flashed to a different ESP chip. Either way, try to restore from
|
|
||||||
// MQTT.
|
|
||||||
mqtt->subscribe(mqttTopicPrefix + std::to_string(remoteId),
|
|
||||||
[=](const std::string &topic, const std::string &payload) {
|
|
||||||
if (*rollingCode == 0) {
|
|
||||||
ESP_LOGI(TAG, "Received rolling code for remote #%d from MQTT: %s",
|
|
||||||
remoteId, payload.c_str());
|
|
||||||
*this->rollingCode = atoi(payload.c_str());
|
|
||||||
}
|
|
||||||
}, /*qos=*/1 /* (at least once) */);
|
|
||||||
} else {
|
|
||||||
ESP_LOGI(TAG, "Restored rolling code for remote #%d from flash: %d",
|
|
||||||
remoteId, *rollingCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CoverTraits get_traits() override {
|
|
||||||
auto traits = CoverTraits();
|
|
||||||
traits.set_is_assumed_state(true);
|
|
||||||
traits.set_supports_position(false);
|
|
||||||
traits.set_supports_tilt(false);
|
|
||||||
traits.set_supports_toggle(false);
|
|
||||||
return traits;
|
|
||||||
}
|
|
||||||
|
|
||||||
void control(const CoverCall &call) override {
|
|
||||||
if (call.get_position().has_value()) {
|
|
||||||
float position = *call.get_position();
|
|
||||||
send(position > 0.5 ? CMD_UP : CMD_DOWN);
|
|
||||||
this->position = position;
|
|
||||||
this->publish_state();
|
|
||||||
} else if (call.get_stop()) {
|
|
||||||
send(CMD_STOP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
int rfPin;
|
|
||||||
int remoteId;
|
|
||||||
int *rollingCode;
|
|
||||||
esphome::mqtt::MQTTClientComponent* mqtt;
|
|
||||||
std::string mqttTopicPrefix;
|
|
||||||
|
|
||||||
/* Wake up the blinds motor controller and send the command.
|
|
||||||
* Overall, with repeats and syncs, takes about 510ms.
|
|
||||||
*
|
|
||||||
* mqtt.publish is synchronous unless idf_send_async is set, and may
|
|
||||||
* introduce an unknown delay into the method execution timeline.
|
|
||||||
*/
|
|
||||||
void send(uint8_t command) {
|
|
||||||
if (*rollingCode == 0) {
|
|
||||||
ESP_LOGE(TAG, "Remote #%d was requested to run command %d, but has not "
|
|
||||||
"yet received its rolling code. Refusing to proceed, to "
|
|
||||||
"avoid crippling MQTT rolling code storage",
|
|
||||||
remoteId, command);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Remote #%d sending command %d with rolling code %d",
|
|
||||||
remoteId, command, *rollingCode);
|
|
||||||
|
|
||||||
uint8_t frame[] = {
|
|
||||||
0xA7, // Encryption key.
|
|
||||||
static_cast<uint8_t>(command << 4), // 4 lsb will be checksum.
|
|
||||||
|
|
||||||
static_cast<uint8_t>(*rollingCode >> 8),
|
|
||||||
static_cast<uint8_t>(*rollingCode),
|
|
||||||
|
|
||||||
static_cast<uint8_t>(remoteId >> 16),
|
|
||||||
static_cast<uint8_t>(remoteId >> 8),
|
|
||||||
static_cast<uint8_t>(remoteId),
|
|
||||||
};
|
|
||||||
|
|
||||||
++*rollingCode;
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
digitalWrite(rfPin, HIGH);
|
|
||||||
delayMicroseconds(9415);
|
|
||||||
digitalWrite(rfPin, LOW);
|
|
||||||
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++) {
|
|
||||||
digitalWrite(rfPin, HIGH);
|
|
||||||
delayMicroseconds(4*RF_SYMBOL);
|
|
||||||
digitalWrite(rfPin, LOW);
|
|
||||||
delayMicroseconds(4*RF_SYMBOL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Software sync.
|
|
||||||
digitalWrite(rfPin, HIGH);
|
|
||||||
delayMicroseconds(4550);
|
|
||||||
digitalWrite(rfPin, LOW);
|
|
||||||
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) {
|
|
||||||
digitalWrite(rfPin, LOW);
|
|
||||||
delayMicroseconds(RF_SYMBOL);
|
|
||||||
digitalWrite(rfPin, HIGH);
|
|
||||||
delayMicroseconds(RF_SYMBOL);
|
|
||||||
} else {
|
|
||||||
digitalWrite(rfPin, HIGH);
|
|
||||||
delayMicroseconds(RF_SYMBOL);
|
|
||||||
digitalWrite(rfPin, LOW);
|
|
||||||
delayMicroseconds(RF_SYMBOL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
digitalWrite(rfPin, LOW);
|
|
||||||
// Inter-frame silence.
|
|
||||||
delayMicroseconds(30415);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Publish the new rolling code at the end to ensure fast reaction time
|
|
||||||
// when the component is called. This method may be synchronous and
|
|
||||||
// introduces an unpredictable delay.
|
|
||||||
mqtt->publish(mqttTopicPrefix + std::to_string(remoteId),
|
|
||||||
std::to_string(*rollingCode), /*qos=*/1, /*retain=*/true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#include <ELECHOUSE_CC1101_SRC_DRV.h>
|
||||||
|
|
||||||
|
#ifndef cc1101_h
|
||||||
|
#define cc1101_h
|
||||||
|
|
||||||
|
static bool cc1101_initialized = false;
|
||||||
|
|
||||||
|
static void cc1101_Init() {
|
||||||
|
if (!cc1101_initialized) {
|
||||||
|
ELECHOUSE_cc1101.addSpiPin(14, 16, 15, 13, 0);
|
||||||
|
ELECHOUSE_cc1101.setModul(0);
|
||||||
|
ELECHOUSE_cc1101.Init();
|
||||||
|
ELECHOUSE_cc1101.setPA(7);
|
||||||
|
ELECHOUSE_cc1101.setMHZ(433.34);
|
||||||
|
ELECHOUSE_cc1101.SetTx();
|
||||||
|
}
|
||||||
|
cc1101_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import cover, mqtt
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
DEPENDENCIES = ["mqtt"]
|
||||||
|
AUTO_LOAD = ["mqtt"]
|
||||||
|
|
||||||
|
somfy_ns = cg.esphome_ns.namespace("somfy")
|
||||||
|
SomfyRTSCover = somfy_ns.class_("SomfyRTSCover", cover.Cover, cg.Component)
|
||||||
|
|
||||||
|
CONF_RF_PIN = "rf_pin"
|
||||||
|
CONF_REMOTE_ID_BASE = "remote_id_base"
|
||||||
|
CONF_REMOTE_ID_OFFSET = "remote_id_offset"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(SomfyRTSCover),
|
||||||
|
cv.Required(CONF_RF_PIN): cv.uint8_t,
|
||||||
|
cv.Required(CONF_REMOTE_ID_BASE): cv.uint32_t,
|
||||||
|
cv.Required(CONF_REMOTE_ID_OFFSET): cv.uint32_t,
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await cover.register_cover(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_rf_pin(config[CONF_RF_PIN]))
|
||||||
|
cg.add(var.set_remote_id(
|
||||||
|
config[CONF_REMOTE_ID_BASE] + config[CONF_REMOTE_ID_OFFSET]
|
||||||
|
))
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
#include "somfy.h"
|
||||||
|
|
||||||
|
#include "cc1101.h"
|
||||||
|
#include "esphome/components/mqtt/mqtt_client.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace somfy {
|
||||||
|
static const char *const TAG = "somfy";
|
||||||
|
|
||||||
|
static const uint8_t CMD_STOP = 0x01;
|
||||||
|
static const uint8_t CMD_UP = 0x02;
|
||||||
|
static const uint8_t CMD_DOWN = 0x04;
|
||||||
|
static const uint8_t CMD_PROG = 0x08;
|
||||||
|
|
||||||
|
static const int RF_SYMBOL = 640;
|
||||||
|
|
||||||
|
void SomfyRTSCover::dump_config() {
|
||||||
|
LOG_COVER("", "Somfy RTS Cover", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " RF Pin: %d", this->rf_pin_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Remote ID: %d", this->remote_id_);
|
||||||
|
if (this->rolling_code_) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Rolling Code: %d", this->rolling_code_);
|
||||||
|
} else {
|
||||||
|
ESP_LOGCONFIG(TAG, " Rolling Code: N/A");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cover::CoverTraits SomfyRTSCover::get_traits() {
|
||||||
|
auto traits = cover::CoverTraits();
|
||||||
|
traits.set_is_assumed_state(true);
|
||||||
|
traits.set_supports_position(false);
|
||||||
|
traits.set_supports_tilt(false);
|
||||||
|
traits.set_supports_toggle(false);
|
||||||
|
return traits;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SomfyRTSCover::setup() {
|
||||||
|
pinMode(rf_pin_, OUTPUT);
|
||||||
|
digitalWrite(rf_pin_, LOW);
|
||||||
|
cc1101_Init();
|
||||||
|
|
||||||
|
rolling_code_pref_ =
|
||||||
|
global_preferences->make_preference<int>(this->get_object_id_hash());
|
||||||
|
|
||||||
|
if (mqtt::global_mqtt_client != nullptr) {
|
||||||
|
this->mqtt_topic_prefix_ =
|
||||||
|
mqtt::global_mqtt_client->get_topic_prefix() + "/rolling_code/";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->rolling_code_pref_.load(&this->rolling_code_)) {
|
||||||
|
ESP_LOGI(TAG, "Restored rolling code for remote #%d from flash: %d",
|
||||||
|
remote_id_, rolling_code_);
|
||||||
|
} else if (mqtt::global_mqtt_client != nullptr) {
|
||||||
|
// Either a new version was flashed, flash was corrupt, or this firmware
|
||||||
|
// was flashed to a different ESP chip. Either way, try to restore from
|
||||||
|
// MQTT.
|
||||||
|
mqtt::global_mqtt_client->subscribe(
|
||||||
|
mqtt_topic_prefix_ + std::to_string(remote_id_),
|
||||||
|
[this](const std::string &topic, const std::string &payload) {
|
||||||
|
if (rolling_code_ == 0) {
|
||||||
|
ESP_LOGI(TAG, "Received rolling code for remote #%d from MQTT: %s",
|
||||||
|
remote_id_, payload.c_str());
|
||||||
|
this->rolling_code_ = atoi(payload.c_str());
|
||||||
|
this->rolling_code_pref_.save(&this->rolling_code_);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/*qos=*/1 /* (at least once) */);
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "No rolling code in flash, and MQTT client unavailable");
|
||||||
|
rolling_code_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SomfyRTSCover::control(const cover::CoverCall &call) {
|
||||||
|
if (call.get_position().has_value()) {
|
||||||
|
float position = *call.get_position();
|
||||||
|
send(position > 0.5 ? CMD_UP : CMD_DOWN);
|
||||||
|
this->position = position;
|
||||||
|
this->publish_state();
|
||||||
|
} else if (call.get_stop()) {
|
||||||
|
send(CMD_STOP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SomfyRTSCover::send(uint8_t command) {
|
||||||
|
if (rolling_code_ == 0) {
|
||||||
|
ESP_LOGE(TAG,
|
||||||
|
"Remote #%d was requested to run command %d, but has not "
|
||||||
|
"yet received its rolling code. Refusing to proceed, to "
|
||||||
|
"avoid crippling MQTT rolling code storage",
|
||||||
|
remote_id_, command);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<uint8_t>(command << 4), // 4 lsb will be checksum.
|
||||||
|
static_cast<uint8_t>(rolling_code_ >> 8),
|
||||||
|
static_cast<uint8_t>(rolling_code_),
|
||||||
|
static_cast<uint8_t>(remote_id_ >> 16),
|
||||||
|
static_cast<uint8_t>(remote_id_ >> 8),
|
||||||
|
static_cast<uint8_t>(remote_id_),
|
||||||
|
};
|
||||||
|
|
||||||
|
++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.
|
||||||
|
digitalWrite(rf_pin_, HIGH);
|
||||||
|
delayMicroseconds(9415);
|
||||||
|
digitalWrite(rf_pin_, LOW);
|
||||||
|
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++) {
|
||||||
|
digitalWrite(rf_pin_, HIGH);
|
||||||
|
delayMicroseconds(4 * RF_SYMBOL);
|
||||||
|
digitalWrite(rf_pin_, LOW);
|
||||||
|
delayMicroseconds(4 * RF_SYMBOL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Software sync.
|
||||||
|
digitalWrite(rf_pin_, HIGH);
|
||||||
|
delayMicroseconds(4550);
|
||||||
|
digitalWrite(rf_pin_, LOW);
|
||||||
|
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) {
|
||||||
|
digitalWrite(rf_pin_, LOW);
|
||||||
|
delayMicroseconds(RF_SYMBOL);
|
||||||
|
digitalWrite(rf_pin_, HIGH);
|
||||||
|
delayMicroseconds(RF_SYMBOL);
|
||||||
|
} else {
|
||||||
|
digitalWrite(rf_pin_, HIGH);
|
||||||
|
delayMicroseconds(RF_SYMBOL);
|
||||||
|
digitalWrite(rf_pin_, LOW);
|
||||||
|
delayMicroseconds(RF_SYMBOL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
digitalWrite(rf_pin_, LOW);
|
||||||
|
// 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
|
||||||
|
// introduces an unpredictable delay.
|
||||||
|
mqtt::global_mqtt_client->publish(
|
||||||
|
mqtt_topic_prefix_ + std::to_string(remote_id_),
|
||||||
|
std::to_string(rolling_code_),
|
||||||
|
/*qos=*/1, /*retain=*/true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace somfy
|
||||||
|
} // namespace esphome
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "esphome/components/cover/cover.h"
|
||||||
|
#include "esphome/components/mqtt/mqtt_component.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace somfy {
|
||||||
|
|
||||||
|
class SomfyRTSCover : public cover::Cover, public Component {
|
||||||
|
public:
|
||||||
|
void set_rf_pin(uint8_t rf_pin) { this->rf_pin_ = rf_pin; }
|
||||||
|
void set_remote_id(uint32_t remote_id) { this->remote_id_ = remote_id; }
|
||||||
|
|
||||||
|
cover::CoverTraits get_traits() override;
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
void control(const cover::CoverCall &call) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/* Wake up the blinds motor controller and send the command.
|
||||||
|
* Overall, with repeats and syncs, takes about 510ms.
|
||||||
|
*
|
||||||
|
* mqtt.publish is synchronous unless idf_send_async is set, and may
|
||||||
|
* introduce an unknown delay into the method execution timeline.
|
||||||
|
*/
|
||||||
|
void send(uint8_t command);
|
||||||
|
|
||||||
|
uint8_t rf_pin_;
|
||||||
|
uint32_t remote_id_;
|
||||||
|
uint32_t rolling_code_;
|
||||||
|
std::string mqtt_topic_prefix_;
|
||||||
|
ESPPreferenceObject rolling_code_pref_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace somfy
|
||||||
|
} // namespace esphome
|
||||||
Reference in New Issue
Block a user