Format all files

This commit is contained in:
2026-07-12 12:56:02 +00:00
parent 7612987884
commit a5c5bf6c6b
56 changed files with 2372 additions and 2395 deletions
+35 -35
View File
@@ -8,25 +8,25 @@ esphome:
version: "1.0" version: "1.0"
external_components: external_components:
- source: components - source: components
packages: packages:
device_base: !include templates/esp8266.yaml device_base: !include templates/esp8266.yaml
sensor: !include templates/esp8266-vcc.yaml sensor: !include templates/esp8266-vcc.yaml
one_wire: one_wire:
- platform: gpio - platform: gpio
pin: D1 pin: D1
sensor: sensor:
- platform: dallas_temp - platform: dallas_temp
address: 0x0401212E565C7428 address: 0x0401212E565C7428
id: bathroom_heater_temperature id: bathroom_heater_temperature
- platform: duty_time - platform: duty_time
id: continuous_on_time id: continuous_on_time
name: Continuous on time name: Continuous on time
accuracy_decimals: 0 accuracy_decimals: 0
remote_transmitter: remote_transmitter:
pin: D2 pin: D2
@@ -34,30 +34,30 @@ remote_transmitter:
id: heater_ir_transmitter id: heater_ir_transmitter
climate: climate:
- platform: zehnder - platform: zehnder
name: None name: None
id: bathroom_heater id: bathroom_heater
transmitter_id: heater_ir_transmitter transmitter_id: heater_ir_transmitter
temperature_sensor_id: bathroom_heater_temperature temperature_sensor_id: bathroom_heater_temperature
on_state: on_state:
- if: - if:
condition: condition:
lambda: 'return x.mode == climate::CLIMATE_MODE_HEAT;' lambda: "return x.mode == climate::CLIMATE_MODE_HEAT;"
then: then:
- sensor.duty_time.start: continuous_on_time - sensor.duty_time.start: continuous_on_time
else: else:
- sensor.duty_time.stop: continuous_on_time - sensor.duty_time.stop: continuous_on_time
- sensor.duty_time.reset: continuous_on_time - sensor.duty_time.reset: continuous_on_time
interval: interval:
- interval: 1min - interval: 1min
then: then:
- if: - if:
condition: condition:
sensor.in_range: sensor.in_range:
id: continuous_on_time id: continuous_on_time
above: 43200 # 12h above: 43200 # 12h
then: then:
- climate.control: - climate.control:
id: bathroom_heater id: bathroom_heater
mode: "OFF" mode: "OFF"
+4 -4
View File
@@ -61,8 +61,8 @@ sensor:
device_id: laundry_air_quality device_id: laundry_air_quality
filters: filters:
- calibrate_linear: - calibrate_linear:
- 2.7 -> 0 - 2.7 -> 0
- 4.5 -> 100 - 4.5 -> 100
- platform: airthings_wave_mini - platform: airthings_wave_mini
ble_client_id: airthings_wave_mini_workshop ble_client_id: airthings_wave_mini_workshop
@@ -89,5 +89,5 @@ sensor:
device_id: workshop_air_quality device_id: workshop_air_quality
filters: filters:
- calibrate_linear: - calibrate_linear:
- 2.7 -> 0 - 2.7 -> 0
- 4.5 -> 100 - 4.5 -> 100
+18 -18
View File
@@ -17,21 +17,21 @@ switch:
optimistic: true optimistic: true
time: time:
- platform: sntp - platform: sntp
id: sntp_time id: sntp_time
servers: !secret sntp_servers servers: !secret sntp_servers
timezone: Europe/Zurich timezone: Europe/Zurich
on_time: on_time:
- seconds: 0 - seconds: 0
minutes: 1 minutes: 1
hours: 0-6,19-23 hours: 0-6,19-23
then: then:
- if: - if:
condition: condition:
- switch.is_on: failsafe_schedule - switch.is_on: failsafe_schedule
- switch.is_off: mystrom_relay - switch.is_off: mystrom_relay
then: then:
- logger.log: >- - logger.log: >-
Power relay is off during T2, assuming automation failure and Power relay is off during T2, assuming automation failure and
correcting correcting
- switch.turn_on: mystrom_relay - switch.turn_on: mystrom_relay
+11 -11
View File
@@ -14,9 +14,9 @@ packages:
psram: psram:
i2c: i2c:
- id: camera_i2c - id: camera_i2c
sda: GPIO26 sda: GPIO26
scl: GPIO27 scl: GPIO27
esp32_camera: # Ai-Thinker esp32_camera: # Ai-Thinker
external_clock: external_clock:
@@ -30,16 +30,16 @@ esp32_camera: # Ai-Thinker
power_down_pin: GPIO32 power_down_pin: GPIO32
name: Camera name: Camera
resolution: 800x600 # Max 1600x1200. resolution: 800x600 # Max 1600x1200.
idle_framerate: 0.1fps idle_framerate: 0.1fps
output: output:
- platform: ledc - platform: ledc
pin: GPIO4 pin: GPIO4
channel: 2 # PWM channel 1 is used for esp32_camera. channel: 2 # PWM channel 1 is used for esp32_camera.
id: led id: led
light: light:
- platform: monochromatic - platform: monochromatic
output: led output: led
name: Flash name: Flash
+1 -1
View File
@@ -12,5 +12,5 @@ packages:
climate: climate:
- platform: tcl112 - platform: tcl112
name: None # Main entity for the device. name: None # Main entity for the device.
receiver_id: ir_receiver receiver_id: ir_receiver
+1 -1
View File
@@ -12,5 +12,5 @@ packages:
climate: climate:
- platform: tcl112 - platform: tcl112
name: None # Main entity for the device. name: None # Main entity for the device.
receiver_id: ir_receiver receiver_id: ir_receiver
+7 -9
View File
@@ -8,14 +8,12 @@
namespace esphome::ratgdo { namespace esphome::ratgdo {
class SyncFailed : public Trigger<> { class SyncFailed : public Trigger<> {
public: public:
explicit SyncFailed(RATGDOComponent* parent) explicit SyncFailed(RATGDOComponent* parent) {
{ parent->subscribe_sync_failed([this](optional<bool> state) {
parent->subscribe_sync_failed([this](optional<bool> state) { if (state.value_or(false)) this->trigger();
if (state.value_or(false)) });
this->trigger(); }
});
}
}; };
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+28 -28
View File
@@ -1,8 +1,9 @@
#pragma once #pragma once
#include "observable.h"
#include <cstdint> #include <cstdint>
#include <utility> #include <utility>
#include "observable.h"
namespace esphome::ratgdo { namespace esphome::ratgdo {
void log_once_callbacks_overflow(uint8_t max); void log_once_callbacks_overflow(uint8_t max);
@@ -12,38 +13,37 @@ class OnceCallbacks;
template <typename... Ts> template <typename... Ts>
class OnceCallbacks<void(Ts...)> { class OnceCallbacks<void(Ts...)> {
public: public:
// Runtime max is 1 for all current usage (door_state waits, command_sent waits). // Runtime max is 1 for all current usage (door_state waits, command_sent
// Set to 2 for safety margin. // waits). Set to 2 for safety margin.
static constexpr uint8_t MAX_CALLBACKS = 2; static constexpr uint8_t MAX_CALLBACKS = 2;
template <typename F> template <typename F>
void operator()(F&& callback) void operator()(F&& callback) {
{ if (this->count_ >= MAX_CALLBACKS) {
if (this->count_ >= MAX_CALLBACKS) { log_once_callbacks_overflow(MAX_CALLBACKS);
log_once_callbacks_overflow(MAX_CALLBACKS); return;
return;
}
this->callbacks_[this->count_++] = Callback<Ts...>::create(std::forward<F>(callback));
} }
this->callbacks_[this->count_++] =
Callback<Ts...>::create(std::forward<F>(callback));
}
// Re-entrant safe: count_ is zeroed before invoking callbacks, // Re-entrant safe: count_ is zeroed before invoking callbacks,
// so callbacks can queue new entries during trigger(). // so callbacks can queue new entries during trigger().
void trigger(Ts... args) void trigger(Ts... args) {
{ uint8_t count = this->count_;
uint8_t count = this->count_; this->count_ = 0;
this->count_ = 0; for (uint8_t i = 0; i < count; i++) {
for (uint8_t i = 0; i < count; i++) { this->callbacks_[i].call(args...);
this->callbacks_[i].call(args...);
}
} }
}
void clear() { this->count_ = 0; } void clear() { this->count_ = 0; }
uint8_t count() const { return this->count_; } uint8_t count() const { return this->count_; }
protected: protected:
Callback<Ts...> callbacks_[MAX_CALLBACKS] { }; Callback<Ts...> callbacks_[MAX_CALLBACKS]{};
uint8_t count_ { 0 }; uint8_t count_{0};
}; };
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+21 -26
View File
@@ -7,37 +7,32 @@
namespace esphome::ratgdo { namespace esphome::ratgdo {
class CoverOpeningTrigger : public Trigger<> { class CoverOpeningTrigger : public Trigger<> {
public: public:
CoverOpeningTrigger(cover::Cover* a_cover) CoverOpeningTrigger(cover::Cover* a_cover) {
{ a_cover->add_on_state_callback([this, a_cover]() {
a_cover->add_on_state_callback([this, a_cover]() { if (a_cover->current_operation == cover::COVER_OPERATION_OPENING) {
if (a_cover->current_operation == cover::COVER_OPERATION_OPENING) { this->trigger();
this->trigger(); }
} });
}); }
}
}; };
class CoverClosingTrigger : public Trigger<> { class CoverClosingTrigger : public Trigger<> {
public: public:
CoverClosingTrigger(cover::Cover* a_cover) CoverClosingTrigger(cover::Cover* a_cover) {
{ a_cover->add_on_state_callback([this, a_cover]() {
a_cover->add_on_state_callback([this, a_cover]() { if (a_cover->current_operation == cover::COVER_OPERATION_CLOSING) {
if (a_cover->current_operation == cover::COVER_OPERATION_CLOSING) { this->trigger();
this->trigger(); }
} });
}); }
}
}; };
class CoverStateTrigger : public Trigger<> { class CoverStateTrigger : public Trigger<> {
public: public:
CoverStateTrigger(cover::Cover* a_cover) CoverStateTrigger(cover::Cover* a_cover) {
{ a_cover->add_on_state_callback([this, a_cover]() { this->trigger(); });
a_cover->add_on_state_callback([this, a_cover]() { }
this->trigger();
});
}
}; };
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+62 -68
View File
@@ -1,4 +1,5 @@
#include "ratgdo_cover.h" #include "ratgdo_cover.h"
#include "../ratgdo_state.h" #include "../ratgdo_state.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@@ -8,89 +9,82 @@ using namespace esphome::cover;
static const char* const TAG = "ratgdo.cover"; static const char* const TAG = "ratgdo.cover";
void RATGDOCover::dump_config() void RATGDOCover::dump_config() { LOG_COVER("", "RATGDO Cover", this); }
{
LOG_COVER("", "RATGDO Cover", this); void RATGDOCover::setup() {
auto state = this->restore_state_();
if (state.has_value()) {
this->parent_->set_door_position(state.value().position);
}
this->parent_->subscribe_door_state([this](DoorState state, float position) {
this->on_door_state(state, position);
});
} }
void RATGDOCover::setup() void RATGDOCover::on_door_state(DoorState state, float position) {
{ if (!*this->parent_->synced || state == DoorState::UNKNOWN) {
auto state = this->restore_state_(); return;
if (state.has_value()) { }
this->parent_->set_door_position(state.value().position); bool save_to_flash = true;
} switch (state) {
this->parent_->subscribe_door_state([this](DoorState state, float position) {
this->on_door_state(state, position);
});
}
void RATGDOCover::on_door_state(DoorState state, float position)
{
if (!*this->parent_->synced || state == DoorState::UNKNOWN) {
return;
}
bool save_to_flash = true;
switch (state) {
case DoorState::OPEN: case DoorState::OPEN:
this->position = COVER_OPEN; this->position = COVER_OPEN;
this->current_operation = COVER_OPERATION_IDLE; this->current_operation = COVER_OPERATION_IDLE;
break; break;
case DoorState::CLOSED: case DoorState::CLOSED:
this->position = COVER_CLOSED; this->position = COVER_CLOSED;
this->current_operation = COVER_OPERATION_IDLE; this->current_operation = COVER_OPERATION_IDLE;
break; break;
case DoorState::OPENING: case DoorState::OPENING:
this->current_operation = COVER_OPERATION_OPENING; this->current_operation = COVER_OPERATION_OPENING;
this->position = position; this->position = position;
save_to_flash = false; save_to_flash = false;
break; break;
case DoorState::CLOSING: case DoorState::CLOSING:
this->current_operation = COVER_OPERATION_CLOSING; this->current_operation = COVER_OPERATION_CLOSING;
this->position = position; this->position = position;
save_to_flash = false; save_to_flash = false;
break; break;
case DoorState::STOPPED: case DoorState::STOPPED:
this->current_operation = COVER_OPERATION_IDLE; this->current_operation = COVER_OPERATION_IDLE;
this->position = position; this->position = position;
break; break;
case DoorState::UNKNOWN: case DoorState::UNKNOWN:
default: default:
this->current_operation = COVER_OPERATION_IDLE; this->current_operation = COVER_OPERATION_IDLE;
this->position = position; this->position = position;
break; break;
} }
this->publish_state(save_to_flash); this->publish_state(save_to_flash);
} }
CoverTraits RATGDOCover::get_traits() CoverTraits RATGDOCover::get_traits() {
{ auto traits = CoverTraits();
auto traits = CoverTraits(); traits.set_supports_stop(true);
traits.set_supports_stop(true); traits.set_supports_toggle(true);
traits.set_supports_toggle(true); traits.set_supports_position(true);
traits.set_supports_position(true); return traits;
return traits;
} }
void RATGDOCover::control(const CoverCall& call) void RATGDOCover::control(const CoverCall& call) {
{ if (call.get_stop()) {
if (call.get_stop()) { this->parent_->door_stop();
this->parent_->door_stop(); }
} if (call.get_toggle()) {
if (call.get_toggle()) { this->parent_->door_toggle();
this->parent_->door_toggle(); }
} if (call.get_position().has_value()) {
if (call.get_position().has_value()) { auto pos = *call.get_position();
auto pos = *call.get_position(); if (pos == COVER_OPEN) {
if (pos == COVER_OPEN) { this->parent_->door_open();
this->parent_->door_open(); } else if (pos == COVER_CLOSED) {
} else if (pos == COVER_CLOSED) { this->parent_->door_close();
this->parent_->door_close(); } else {
} else { this->parent_->door_move_to_position(pos);
this->parent_->door_move_to_position(pos);
}
} }
}
} }
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+8 -8
View File
@@ -8,15 +8,15 @@
namespace esphome::ratgdo { namespace esphome::ratgdo {
class RATGDOCover : public cover::Cover, public RATGDOClient, public Component { class RATGDOCover : public cover::Cover, public RATGDOClient, public Component {
public: public:
void dump_config() override; void dump_config() override;
void setup() override; void setup() override;
cover::CoverTraits get_traits() override; cover::CoverTraits get_traits() override;
void on_door_state(DoorState state, float position); void on_door_state(DoorState state, float position);
protected: protected:
void control(const cover::CoverCall& call) override; void control(const cover::CoverCall& call) override;
}; };
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+34 -44
View File
@@ -1,4 +1,5 @@
#include "ratgdo_light_output.h" #include "ratgdo_light_output.h"
#include "../ratgdo_state.h" #include "../ratgdo_state.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@@ -8,59 +9,48 @@ using namespace esphome::light;
static const char* const TAG = "ratgdo.light"; static const char* const TAG = "ratgdo.light";
void RATGDOLightOutput::dump_config() void RATGDOLightOutput::dump_config() { ESP_LOGCONFIG(TAG, "RATGDO Light"); }
{
ESP_LOGCONFIG(TAG, "RATGDO Light"); void RATGDOLightOutput::setup() {
this->parent_->subscribe_light_state(
[this](LightState state) { this->on_light_state(state); });
} }
void RATGDOLightOutput::setup() void RATGDOLightOutput::on_light_state(esphome::ratgdo::LightState state) {
{ if (this->light_state_) {
this->parent_->subscribe_light_state([this](LightState state) { this->has_initial_state_ = true;
this->on_light_state(state); set_state(state);
}); }
} }
void RATGDOLightOutput::on_light_state(esphome::ratgdo::LightState state) void RATGDOLightOutput::set_state(esphome::ratgdo::LightState state) {
{ bool is_on = state == LightState::ON;
if (this->light_state_) { this->light_state_->current_values.set_state(is_on);
this->has_initial_state_ = true; this->light_state_->remote_values.set_state(is_on);
set_state(state); this->light_state_->publish_state();
}
} }
void RATGDOLightOutput::set_state(esphome::ratgdo::LightState state) void RATGDOLightOutput::setup_state(light::LightState* light_state) {
{ esphome::ratgdo::LightState state = this->parent_->get_light_state();
bool is_on = state == LightState::ON; this->light_state_ = light_state;
this->light_state_->current_values.set_state(is_on); this->set_state(state);
this->light_state_->remote_values.set_state(is_on);
this->light_state_->publish_state();
} }
void RATGDOLightOutput::setup_state(light::LightState* light_state) LightTraits RATGDOLightOutput::get_traits() {
{ auto traits = LightTraits();
esphome::ratgdo::LightState state = this->parent_->get_light_state(); traits.set_supported_color_modes({light::ColorMode::ON_OFF});
this->light_state_ = light_state; return traits;
this->set_state(state);
} }
LightTraits RATGDOLightOutput::get_traits() void RATGDOLightOutput::write_state(light::LightState* state) {
{ if (!this->has_initial_state_) return;
auto traits = LightTraits(); bool binary;
traits.set_supported_color_modes({ light::ColorMode::ON_OFF }); state->current_values_as_binary(&binary);
return traits; if (binary) {
this->parent_->light_on();
} else {
this->parent_->light_off();
}
} }
void RATGDOLightOutput::write_state(light::LightState* state) } // namespace esphome::ratgdo
{
if (!this->has_initial_state_)
return;
bool binary;
state->current_values_as_binary(&binary);
if (binary) {
this->parent_->light_on();
} else {
this->parent_->light_off();
}
}
} // namespace esphome::ratgdo
+16 -14
View File
@@ -7,21 +7,23 @@
namespace esphome::ratgdo { namespace esphome::ratgdo {
class RATGDOLightOutput : public light::LightOutput, public RATGDOClient, public Component { class RATGDOLightOutput : public light::LightOutput,
public: public RATGDOClient,
void dump_config() override; public Component {
void setup() override; public:
light::LightTraits get_traits() override; void dump_config() override;
void write_state(light::LightState* state) override; void setup() override;
void setup_state(light::LightState* state) override; light::LightTraits get_traits() override;
void set_state(esphome::ratgdo::LightState state); void write_state(light::LightState* state) override;
light::LightState* get_state() { return this->light_state_; } void setup_state(light::LightState* state) override;
void set_state(esphome::ratgdo::LightState state);
light::LightState* get_state() { return this->light_state_; }
void on_light_state(esphome::ratgdo::LightState state); void on_light_state(esphome::ratgdo::LightState state);
protected: protected:
light::LightState* light_state_; light::LightState* light_state_;
bool has_initial_state_ = false; bool has_initial_state_ = false;
}; };
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+32 -34
View File
@@ -1,4 +1,5 @@
#include "ratgdo_lock.h" #include "ratgdo_lock.h"
#include "../ratgdo_state.h" #include "../ratgdo_state.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@@ -6,48 +7,45 @@ namespace esphome::ratgdo {
static const char* const TAG = "ratgdo.lock"; static const char* const TAG = "ratgdo.lock";
void RATGDOLock::dump_config() void RATGDOLock::dump_config() {
{ LOG_LOCK("", "RATGDO Lock", this);
LOG_LOCK("", "RATGDO Lock", this); ESP_LOGCONFIG(TAG, " Type: Lock");
ESP_LOGCONFIG(TAG, " Type: Lock");
} }
void RATGDOLock::setup() void RATGDOLock::setup() {
{ this->parent_->subscribe_lock_state(
this->parent_->subscribe_lock_state([this](LockState state) { [this](LockState state) { this->on_lock_state(state); });
this->on_lock_state(state);
});
} }
void RATGDOLock::on_lock_state(LockState state) void RATGDOLock::on_lock_state(LockState state) {
{ if (state == LockState::LOCKED &&
if (state == LockState::LOCKED && this->state == lock::LockState::LOCK_STATE_LOCKED) { this->state == lock::LockState::LOCK_STATE_LOCKED) {
return; return;
} }
if (state == LockState::UNLOCKED && this->state == lock::LockState::LOCK_STATE_UNLOCKED) { if (state == LockState::UNLOCKED &&
return; this->state == lock::LockState::LOCK_STATE_UNLOCKED) {
} return;
}
auto call = this->make_call(); auto call = this->make_call();
if (state == LockState::LOCKED) { if (state == LockState::LOCKED) {
call.set_state(lock::LockState::LOCK_STATE_LOCKED); call.set_state(lock::LockState::LOCK_STATE_LOCKED);
} else if (state == LockState::UNLOCKED) { } else if (state == LockState::UNLOCKED) {
call.set_state(lock::LockState::LOCK_STATE_UNLOCKED); call.set_state(lock::LockState::LOCK_STATE_UNLOCKED);
} }
this->publish_state(*call.get_state()); this->publish_state(*call.get_state());
} }
void RATGDOLock::control(const lock::LockCall& call) void RATGDOLock::control(const lock::LockCall& call) {
{ auto state = *call.get_state();
auto state = *call.get_state();
if (state == lock::LockState::LOCK_STATE_LOCKED) { if (state == lock::LockState::LOCK_STATE_LOCKED) {
this->parent_->lock(); this->parent_->lock();
} else if (state == lock::LockState::LOCK_STATE_UNLOCKED) { } else if (state == lock::LockState::LOCK_STATE_UNLOCKED) {
this->parent_->unlock(); this->parent_->unlock();
} }
this->publish_state(state); this->publish_state(state);
} }
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+6 -6
View File
@@ -8,12 +8,12 @@
namespace esphome::ratgdo { namespace esphome::ratgdo {
class RATGDOLock : public lock::Lock, public RATGDOClient, public Component { class RATGDOLock : public lock::Lock, public RATGDOClient, public Component {
public: public:
void dump_config() override; void dump_config() override;
void setup() override; void setup() override;
void on_lock_state(LockState state); void on_lock_state(LockState state);
void control(const lock::LockCall& call) override; void control(const lock::LockCall& call) override;
}; };
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+51 -57
View File
@@ -1,4 +1,5 @@
#include "ratgdo_number.h" #include "ratgdo_number.h"
#include "../ratgdo_state.h" #include "../ratgdo_state.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@@ -6,83 +7,76 @@ namespace esphome::ratgdo {
static const char* const TAG = "ratgdo.number"; static const char* const TAG = "ratgdo.number";
void RATGDONumber::dump_config() void RATGDONumber::dump_config() {
{ LOG_NUMBER("", "RATGDO Number", this);
LOG_NUMBER("", "RATGDO Number", this); switch (this->number_type_) {
switch (this->number_type_) {
case RATGDO_OPENING_DURATION: case RATGDO_OPENING_DURATION:
ESP_LOGCONFIG(TAG, " Type: Opening Duration"); ESP_LOGCONFIG(TAG, " Type: Opening Duration");
break; break;
case RATGDO_CLOSING_DURATION: case RATGDO_CLOSING_DURATION:
ESP_LOGCONFIG(TAG, " Type: Closing Duration"); ESP_LOGCONFIG(TAG, " Type: Closing Duration");
break; break;
default: default:
break; break;
} }
} }
void RATGDONumber::setup() void RATGDONumber::setup() {
{ float value;
float value; this->pref_ = this->make_entity_preference<float>();
this->pref_ = this->make_entity_preference<float>(); if (!this->pref_.load(&value)) {
if (!this->pref_.load(&value)) { value = 0;
value = 0; }
} this->control(value);
this->control(value);
switch (this->number_type_) { switch (this->number_type_) {
case RATGDO_OPENING_DURATION: case RATGDO_OPENING_DURATION:
this->parent_->subscribe_opening_duration([this](float value) { this->parent_->subscribe_opening_duration(
this->update_state(value); [this](float value) { this->update_state(value); });
}); break;
break;
case RATGDO_CLOSING_DURATION: case RATGDO_CLOSING_DURATION:
this->parent_->subscribe_closing_duration([this](float value) { this->parent_->subscribe_closing_duration(
this->update_state(value); [this](float value) { this->update_state(value); });
}); break;
break;
default: default:
break; break;
} }
} }
void RATGDONumber::set_number_type(NumberType number_type_) void RATGDONumber::set_number_type(NumberType number_type_) {
{ this->number_type_ = number_type_;
this->number_type_ = number_type_; switch (this->number_type_) {
switch (this->number_type_) {
case RATGDO_OPENING_DURATION: case RATGDO_OPENING_DURATION:
case RATGDO_CLOSING_DURATION: case RATGDO_CLOSING_DURATION:
this->traits.set_step(0.1); this->traits.set_step(0.1);
this->traits.set_min_value(0.0); this->traits.set_min_value(0.0);
this->traits.set_max_value(180.0); this->traits.set_max_value(180.0);
break; break;
default: default:
break; break;
} }
} }
void RATGDONumber::update_state(float value) void RATGDONumber::update_state(float value) {
{ if (value == this->state) {
if (value == this->state) { return;
return; }
} this->pref_.save(&value);
this->pref_.save(&value); this->publish_state(value);
this->publish_state(value);
} }
void RATGDONumber::control(float value) void RATGDONumber::control(float value) {
{ switch (this->number_type_) {
switch (this->number_type_) {
case RATGDO_OPENING_DURATION: case RATGDO_OPENING_DURATION:
this->parent_->set_opening_duration(value); this->parent_->set_opening_duration(value);
break; break;
case RATGDO_CLOSING_DURATION: case RATGDO_CLOSING_DURATION:
this->parent_->set_closing_duration(value); this->parent_->set_closing_duration(value);
break; break;
default: default:
break; break;
} }
this->update_state(value); this->update_state(value);
} }
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+21 -17
View File
@@ -9,26 +9,30 @@
namespace esphome::ratgdo { namespace esphome::ratgdo {
enum NumberType { enum NumberType {
RATGDO_OPENING_DURATION, RATGDO_OPENING_DURATION,
RATGDO_CLOSING_DURATION, RATGDO_CLOSING_DURATION,
}; };
class RATGDONumber : public number::Number, public RATGDOClient, public Component { class RATGDONumber : public number::Number,
public: public RATGDOClient,
void dump_config() override; public Component {
void setup() override; public:
void set_number_type(NumberType number_type); void dump_config() override;
// other esphome components that persist state in the flash have HARDWARE priority void setup() override;
// ensure we get initialized before them, so that the state doesn't get invalidated void set_number_type(NumberType number_type);
// by components that might be added in the future // other esphome components that persist state in the flash have HARDWARE
float get_setup_priority() const override { return setup_priority::HARDWARE + 1; } // priority ensure we get initialized before them, so that the state doesn't
// get invalidated by components that might be added in the future
float get_setup_priority() const override {
return setup_priority::HARDWARE + 1;
}
void update_state(float value); void update_state(float value);
void control(float value) override; void control(float value) override;
protected: protected:
NumberType number_type_; NumberType number_type_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;
}; };
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+12 -10
View File
@@ -1,4 +1,5 @@
#include "observable.h" #include "observable.h"
#include "callbacks.h" #include "callbacks.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@@ -6,19 +7,20 @@ namespace esphome::ratgdo {
static const char* const TAG = "ratgdo.observable"; static const char* const TAG = "ratgdo.observable";
void log_multiple_subscribers() void log_multiple_subscribers() {
{ ESP_LOGE(TAG,
ESP_LOGE(TAG, "single_observable already has a subscriber! This will overwrite the existing subscriber."); "single_observable already has a subscriber! This will overwrite "
"the existing subscriber.");
} }
void log_observer_overflow() void log_observer_overflow() {
{ ESP_LOGE(TAG,
ESP_LOGE(TAG, "observable has too many subscribers! Ignoring new subscriber."); "observable has too many subscribers! Ignoring new subscriber.");
} }
void log_once_callbacks_overflow(uint8_t max) void log_once_callbacks_overflow(uint8_t max) {
{ ESP_LOGE(TAG, "OnceCallbacks overflow (max %u)! Ignoring callback.",
ESP_LOGE(TAG, "OnceCallbacks overflow (max %u)! Ignoring callback.", static_cast<unsigned>(max)); static_cast<unsigned>(max));
} }
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+102 -115
View File
@@ -11,157 +11,144 @@ void log_multiple_subscribers();
void log_observer_overflow(); void log_observer_overflow();
// Lightweight type-erased callback (16 bytes on 32-bit). // Lightweight type-erased callback (16 bytes on 32-bit).
// For small trivially-copyable callables (like [this], [this, f], or [this, f, id] lambdas), // For small trivially-copyable callables (like [this], [this, f], or [this, f,
// stores the callable inline — zero heap allocation. // id] lambdas), stores the callable inline — zero heap allocation. Supports up
// Supports up to 3 * sizeof(void*) bytes (12 bytes on 32-bit, 24 on 64-bit). // to 3 * sizeof(void*) bytes (12 bytes on 32-bit, 24 on 64-bit).
inline constexpr size_t CALLBACK_STORAGE_SIZE = 3 * sizeof(void*); inline constexpr size_t CALLBACK_STORAGE_SIZE = 3 * sizeof(void*);
template <typename... Ts> template <typename... Ts>
struct Callback { struct Callback {
using fn_t = void (*)(const void*, Ts...); using fn_t = void (*)(const void*, Ts...);
fn_t fn_ { nullptr }; fn_t fn_{nullptr};
alignas(void*) uint8_t storage_[CALLBACK_STORAGE_SIZE] { }; alignas(void*) uint8_t storage_[CALLBACK_STORAGE_SIZE]{};
void call(Ts... args) const { this->fn_(this->storage_, args...); } void call(Ts... args) const { this->fn_(this->storage_, args...); }
explicit operator bool() const { return this->fn_ != nullptr; } explicit operator bool() const { return this->fn_ != nullptr; }
template <typename F> template <typename F>
static Callback create(F&& f) static Callback create(F&& f) {
{ Callback cb;
Callback cb; using Decay = std::decay_t<F>;
using Decay = std::decay_t<F>; static_assert(!std::is_function_v<std::remove_reference_t<F>>,
static_assert(!std::is_function_v<std::remove_reference_t<F>>, "Pass function pointers, not function references");
"Pass function pointers, not function references"); static_assert(std::is_trivially_copyable_v<Decay>,
static_assert(std::is_trivially_copyable_v<Decay>, "Observable callbacks must be trivially copyable (e.g. [this] lambdas)"); "Observable callbacks must be trivially copyable (e.g. "
static_assert(sizeof(Decay) <= CALLBACK_STORAGE_SIZE, "Observable callbacks must fit in storage (capture at most 3 pointers)"); "[this] lambdas)");
cb.fn_ = [](const void* storage, Ts... args) { static_assert(sizeof(Decay) <= CALLBACK_STORAGE_SIZE,
alignas(Decay) char buf[sizeof(Decay)]; "Observable callbacks must fit in storage (capture at most 3 "
__builtin_memcpy(buf, storage, sizeof(Decay)); "pointers)");
(*std::launder(reinterpret_cast<Decay*>(buf)))(args...); cb.fn_ = [](const void* storage, Ts... args) {
}; alignas(Decay) char buf[sizeof(Decay)];
__builtin_memcpy(cb.storage_, &f, sizeof(Decay)); __builtin_memcpy(buf, storage, sizeof(Decay));
return cb; (*std::launder(reinterpret_cast<Decay*>(buf)))(args...);
} };
__builtin_memcpy(cb.storage_, &f, sizeof(Decay));
return cb;
}
}; };
// Primary template for observable with subscribers. // Primary template for observable with subscribers.
template <typename T, uint8_t MaxObservers> template <typename T, uint8_t MaxObservers>
class observable { class observable {
public: public:
observable(const T& value) observable(const T& value) : value_(value) {}
: value_(value)
{ template <typename U>
observable& operator=(U value) {
if (value != this->value_) {
this->value_ = value;
this->notify();
} }
return *this;
}
template <typename U> T const* operator&() const { return &this->value_; }
observable& operator=(U value) T const& operator*() const { return this->value_; }
{
if (value != this->value_) { template <typename F>
this->value_ = value; void subscribe(F&& observer) {
this->notify(); if (this->count_ >= MaxObservers) {
} log_observer_overflow();
return *this; return;
} }
this->observers_[this->count_++] =
Callback<T>::create(std::forward<F>(observer));
}
T const* operator&() const { return &this->value_; } void notify() const {
T const& operator*() const { return this->value_; } for (uint8_t i = 0; i < this->count_; i++) {
this->observers_[i].call(this->value_);
template <typename F>
void subscribe(F&& observer)
{
if (this->count_ >= MaxObservers) {
log_observer_overflow();
return;
}
this->observers_[this->count_++] = Callback<T>::create(std::forward<F>(observer));
} }
}
void notify() const private:
{ T value_;
for (uint8_t i = 0; i < this->count_; i++) { Callback<T> observers_[MaxObservers]{};
this->observers_[i].call(this->value_); uint8_t count_{0};
}
}
private:
T value_;
Callback<T> observers_[MaxObservers] { };
uint8_t count_ { 0 };
}; };
// Specialization for zero subscribers — no array, no count, notify is a no-op. // Specialization for zero subscribers — no array, no count, notify is a no-op.
template <typename T> template <typename T>
class observable<T, 0> { class observable<T, 0> {
public: public:
observable(const T& value) observable(const T& value) : value_(value) {}
: value_(value)
{ template <typename U>
observable& operator=(U value) {
if (value != this->value_) {
this->value_ = value;
} }
return *this;
}
template <typename U> T const* operator&() const { return &this->value_; }
observable& operator=(U value) T const& operator*() const { return this->value_; }
{
if (value != this->value_) {
this->value_ = value;
}
return *this;
}
T const* operator&() const { return &this->value_; } template <typename F>
T const& operator*() const { return this->value_; } void subscribe(F&&) {
log_observer_overflow();
}
template <typename F> void notify() const {}
void subscribe(F&&)
{
log_observer_overflow();
}
void notify() const { } private:
T value_;
private:
T value_;
}; };
template <typename T> template <typename T>
class single_observable { class single_observable {
public: public:
single_observable(const T& value) single_observable(const T& value) : value_(value) {}
: value_(value)
{ template <typename U>
single_observable& operator=(U value) {
if (value != this->value_) {
this->value_ = value;
this->notify();
} }
return *this;
}
template <typename U> T const* operator&() const { return &this->value_; }
single_observable& operator=(U value) T const& operator*() const { return this->value_; }
{
if (value != this->value_) { template <typename F>
this->value_ = value; void subscribe(F&& observer) {
this->notify(); if (this->observer_) {
} log_multiple_subscribers();
return *this;
} }
this->observer_ = Callback<T>::create(std::forward<F>(observer));
}
T const* operator&() const { return &this->value_; } void notify() const {
T const& operator*() const { return this->value_; } if (this->observer_) {
this->observer_.call(this->value_);
template <typename F>
void subscribe(F&& observer)
{
if (this->observer_) {
log_multiple_subscribers();
}
this->observer_ = Callback<T>::create(std::forward<F>(observer));
} }
}
void notify() const private:
{ T value_;
if (this->observer_) { Callback<T> observer_{};
this->observer_.call(this->value_);
}
}
private:
T value_;
Callback<T> observer_ { };
}; };
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+397 -414
View File
@@ -5,19 +5,16 @@
************************************/ ************************************/
#include "ratgdo.h" #include "ratgdo.h"
#include "common.h" #include "common.h"
#include "ratgdo_state.h"
#include "secplus2.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/gpio.h" #include "esphome/core/gpio.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "ratgdo_state.h"
#include "secplus2.h"
namespace esphome::ratgdo { namespace esphome::ratgdo {
static const char* const TAG = "ratgdo"; static const char* const TAG = "ratgdo";
static constexpr int SYNC_DELAY = 5000; static constexpr int SYNC_DELAY = 5000;
// Door state updates arrive over UART every ~200-400ms during movement. // Door state updates arrive over UART every ~200-400ms during movement.
@@ -28,495 +25,481 @@ static constexpr uint32_t DOOR_STATE_CALLBACK_TIMEOUT = 2000;
using namespace scheduler_ids; using namespace scheduler_ids;
void log_subscriber_overflow(const LogString* observable_name, uint32_t max) void log_subscriber_overflow(const LogString* observable_name, uint32_t max) {
{ ESP_LOGE(TAG, "Too many subscribers for %s (max %d)",
ESP_LOGE(TAG, "Too many subscribers for %s (max %d)", LOG_STR_ARG(observable_name), (int)max);
LOG_STR_ARG(observable_name), (int)max);
} }
void RATGDOComponent::setup() void RATGDOComponent::setup() {
{ this->output_gdo_pin_->setup();
this->output_gdo_pin_->setup(); this->output_gdo_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->output_gdo_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->input_gdo_pin_->setup(); this->input_gdo_pin_->setup();
this->input_gdo_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); this->input_gdo_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
this->protocol_->setup(this, &App.scheduler, this->input_gdo_pin_, this->protocol_->setup(this, &App.scheduler, this->input_gdo_pin_,
this->output_gdo_pin_); this->output_gdo_pin_);
// many things happening at startup, use some delay for sync
this->set_timeout(SYNC_DELAY, [this] { this->sync(); });
this->synced.subscribe([this](bool is_synced) {
if (is_synced) {
this->door_state.notify();
this->light_state.notify();
this->lock_state.notify();
}
});
// many things happening at startup, use some delay for sync
this->set_timeout(SYNC_DELAY, [this] { this->sync(); });
this->synced.subscribe([this](bool is_synced) {
if (is_synced) {
this->door_state.notify();
this->light_state.notify();
this->lock_state.notify();
}
});
} }
// initializing protocol, this gets called before setup() because // initializing protocol, this gets called before setup() because
// its children components might require that // its children components might require that
void RATGDOComponent::init_protocol() void RATGDOComponent::init_protocol() {
{ this->protocol_ = new secplus2::Secplus2();
this->protocol_ = new secplus2::Secplus2();
} }
void RATGDOComponent::loop() void RATGDOComponent::loop() { this->protocol_->loop(); }
{
this->protocol_->loop(); void RATGDOComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Setting up RATGDO...");
LOG_PIN(" Output GDO Pin: ", this->output_gdo_pin_);
LOG_PIN(" Input GDO Pin: ", this->input_gdo_pin_);
this->protocol_->dump_config();
} }
void RATGDOComponent::dump_config() void RATGDOComponent::on_shutdown() {
{ if (this->protocol_ != nullptr) {
ESP_LOGCONFIG(TAG, "Setting up RATGDO..."); this->protocol_->on_shutdown();
LOG_PIN(" Output GDO Pin: ", this->output_gdo_pin_); }
LOG_PIN(" Input GDO Pin: ", this->input_gdo_pin_);
this->protocol_->dump_config();
} }
void RATGDOComponent::on_shutdown() void RATGDOComponent::received(const DoorState door_state) {
{ ESP_LOGD(TAG, "Door state=%s", LOG_STR_ARG(DoorState_to_string(door_state)));
if (this->protocol_ != nullptr) {
this->protocol_->on_shutdown(); auto prev_door_state = *this->door_state;
if (prev_door_state == door_state) {
return;
}
// opening duration calibration
if (*this->opening_duration == 0) {
if (door_state == DoorState::OPENING &&
prev_door_state == DoorState::CLOSED) {
this->start_opening = millis();
} }
if (door_state == DoorState::OPEN &&
prev_door_state == DoorState::OPENING && this->start_opening > 0) {
auto duration = (millis() - this->start_opening) / 1000;
this->set_opening_duration(round(duration * 10) / 10);
}
if (door_state == DoorState::STOPPED) {
this->start_opening = -1;
}
}
// closing duration calibration
if (*this->closing_duration == 0) {
if (door_state == DoorState::CLOSING &&
prev_door_state == DoorState::OPEN) {
this->start_closing = millis();
}
if (door_state == DoorState::CLOSED &&
prev_door_state == DoorState::CLOSING && this->start_closing > 0) {
auto duration = (millis() - this->start_closing) / 1000;
this->set_closing_duration(round(duration * 10) / 10);
}
if (door_state == DoorState::STOPPED) {
this->start_closing = -1;
}
}
if (door_state == DoorState::OPENING) {
// door started opening
if (prev_door_state == DoorState::CLOSING) {
this->door_position_update();
this->cancel_position_sync_callbacks();
this->door_move_delta = DOOR_DELTA_UNKNOWN;
}
this->door_start_moving = millis();
this->door_start_position = *this->door_position;
if (this->door_move_delta == DOOR_DELTA_UNKNOWN) {
this->door_move_delta = 1.0 - this->door_start_position;
}
if (*this->opening_duration != 0) {
this->schedule_door_position_sync();
}
} else if (door_state == DoorState::CLOSING) {
// door started closing
if (prev_door_state == DoorState::OPENING) {
this->door_position_update();
this->cancel_position_sync_callbacks();
this->door_move_delta = DOOR_DELTA_UNKNOWN;
}
this->door_start_moving = millis();
this->door_start_position = *this->door_position;
if (this->door_move_delta == DOOR_DELTA_UNKNOWN) {
this->door_move_delta = 0.0 - this->door_start_position;
}
if (*this->closing_duration != 0) {
this->schedule_door_position_sync();
}
} else if (door_state == DoorState::STOPPED) {
this->door_position_update();
if (*this->door_position == DOOR_POSITION_UNKNOWN) {
this->door_position = 0.5; // best guess
}
this->cancel_position_sync_callbacks();
this->cancel_timeout(TIMEOUT_DOOR_QUERY_STATE);
this->target_position_ = DOOR_POSITION_UNKNOWN;
this->target_direction_ = DoorAction::UNKNOWN;
} else if (door_state == DoorState::OPEN) {
this->door_position = 1.0;
this->cancel_position_sync_callbacks();
this->target_position_ = DOOR_POSITION_UNKNOWN;
this->target_direction_ = DoorAction::UNKNOWN;
} else if (door_state == DoorState::CLOSED) {
this->door_position = 0.0;
this->cancel_position_sync_callbacks();
this->target_position_ = DOOR_POSITION_UNKNOWN;
this->target_direction_ = DoorAction::UNKNOWN;
}
if (door_state == DoorState::CLOSED && door_state != prev_door_state) {
this->query_openings();
}
this->door_state = door_state;
this->on_door_state_.trigger(door_state);
} }
void RATGDOComponent::received(const DoorState door_state) void RATGDOComponent::received(const LightState light_state) {
{ ESP_LOGD(TAG, "Light state=%s",
ESP_LOGD(TAG, "Door state=%s", LOG_STR_ARG(DoorState_to_string(door_state))); LOG_STR_ARG(LightState_to_string(light_state)));
this->light_state = light_state;
}
auto prev_door_state = *this->door_state; void RATGDOComponent::received(const LockState lock_state) {
ESP_LOGD(TAG, "Lock state=%s", LOG_STR_ARG(LockState_to_string(lock_state)));
this->lock_state = lock_state;
}
if (prev_door_state == door_state) { void RATGDOComponent::received(const LightAction light_action) {
return; ESP_LOGD(TAG, "Light cmd=%s state=%s",
LOG_STR_ARG(LightAction_to_string(light_action)),
LOG_STR_ARG(LightState_to_string(*this->light_state)));
if (light_action == LightAction::OFF) {
this->light_state = LightState::OFF;
} else if (light_action == LightAction::ON) {
this->light_state = LightState::ON;
} else if (light_action == LightAction::TOGGLE) {
this->light_state = light_state_toggle(*this->light_state);
}
}
void RATGDOComponent::received(const Openings openings) {
if (openings.flag == 0 || *this->openings != 0) {
this->openings = openings.count;
ESP_LOGD(TAG, "Openings: %d", *this->openings);
} else {
ESP_LOGD(TAG, "Ignoring openings, not from our request");
}
}
void RATGDOComponent::schedule_door_position_sync(float update_period) {
ESP_LOG1(
TAG,
"Schedule position sync: delta %f, start position: %f, start moving: %d",
this->door_move_delta, this->door_start_position,
this->door_start_moving);
auto duration = this->door_move_delta > 0 ? *this->opening_duration
: *this->closing_duration;
if (duration == 0) {
return;
}
this->position_sync_remaining_ =
std::max(static_cast<uint16_t>(1000 * duration / update_period),
static_cast<uint16_t>(1));
set_interval(INTERVAL_POSITION_SYNC, static_cast<uint32_t>(update_period),
[this]() {
this->door_position_update();
if (--this->position_sync_remaining_ == 0) {
cancel_interval(INTERVAL_POSITION_SYNC);
}
});
}
void RATGDOComponent::door_position_update() {
if (this->door_start_moving == 0 ||
this->door_start_position == DOOR_POSITION_UNKNOWN ||
this->door_move_delta == DOOR_DELTA_UNKNOWN) {
return;
}
auto now = millis();
auto duration = this->door_move_delta > 0 ? *this->opening_duration
: -*this->closing_duration;
if (duration == 0) {
return;
}
auto position = this->door_start_position +
(now - this->door_start_moving) / (1000 * duration);
position = clamp(position, 0.0f, 1.0f);
ESP_LOG2(TAG, "[%d] Position update: %f", now, position);
this->door_position = position;
// Check if we reached our move-to-position target
if (this->target_position_ != DOOR_POSITION_UNKNOWN &&
this->target_direction_ != DoorAction::UNKNOWN) {
bool reached = false;
if (this->target_direction_ == DoorAction::OPEN &&
position >= this->target_position_) {
reached = true;
} else if (this->target_direction_ == DoorAction::CLOSE &&
position <= this->target_position_) {
reached = true;
} }
// opening duration calibration if (reached) {
if (*this->opening_duration == 0) { ESP_LOGD(TAG, "Reached target position %.2f, stopping door",
if (door_state == DoorState::OPENING && prev_door_state == DoorState::CLOSED) { this->target_position_);
this->start_opening = millis(); this->door_stop();
} this->target_position_ = DOOR_POSITION_UNKNOWN;
if (door_state == DoorState::OPEN && prev_door_state == DoorState::OPENING && this->start_opening > 0) { this->target_direction_ = DoorAction::UNKNOWN;
auto duration = (millis() - this->start_opening) / 1000;
this->set_opening_duration(round(duration * 10) / 10);
}
if (door_state == DoorState::STOPPED) {
this->start_opening = -1;
}
} }
// closing duration calibration }
if (*this->closing_duration == 0) {
if (door_state == DoorState::CLOSING && prev_door_state == DoorState::OPEN) {
this->start_closing = millis();
}
if (door_state == DoorState::CLOSED && prev_door_state == DoorState::CLOSING && this->start_closing > 0) {
auto duration = (millis() - this->start_closing) / 1000;
this->set_closing_duration(round(duration * 10) / 10);
}
if (door_state == DoorState::STOPPED) {
this->start_closing = -1;
}
}
if (door_state == DoorState::OPENING) {
// door started opening
if (prev_door_state == DoorState::CLOSING) {
this->door_position_update();
this->cancel_position_sync_callbacks();
this->door_move_delta = DOOR_DELTA_UNKNOWN;
}
this->door_start_moving = millis();
this->door_start_position = *this->door_position;
if (this->door_move_delta == DOOR_DELTA_UNKNOWN) {
this->door_move_delta = 1.0 - this->door_start_position;
}
if (*this->opening_duration != 0) {
this->schedule_door_position_sync();
}
} else if (door_state == DoorState::CLOSING) {
// door started closing
if (prev_door_state == DoorState::OPENING) {
this->door_position_update();
this->cancel_position_sync_callbacks();
this->door_move_delta = DOOR_DELTA_UNKNOWN;
}
this->door_start_moving = millis();
this->door_start_position = *this->door_position;
if (this->door_move_delta == DOOR_DELTA_UNKNOWN) {
this->door_move_delta = 0.0 - this->door_start_position;
}
if (*this->closing_duration != 0) {
this->schedule_door_position_sync();
}
} else if (door_state == DoorState::STOPPED) {
this->door_position_update();
if (*this->door_position == DOOR_POSITION_UNKNOWN) {
this->door_position = 0.5; // best guess
}
this->cancel_position_sync_callbacks();
this->cancel_timeout(TIMEOUT_DOOR_QUERY_STATE);
this->target_position_ = DOOR_POSITION_UNKNOWN;
this->target_direction_ = DoorAction::UNKNOWN;
} else if (door_state == DoorState::OPEN) {
this->door_position = 1.0;
this->cancel_position_sync_callbacks();
this->target_position_ = DOOR_POSITION_UNKNOWN;
this->target_direction_ = DoorAction::UNKNOWN;
} else if (door_state == DoorState::CLOSED) {
this->door_position = 0.0;
this->cancel_position_sync_callbacks();
this->target_position_ = DOOR_POSITION_UNKNOWN;
this->target_direction_ = DoorAction::UNKNOWN;
}
if (door_state == DoorState::CLOSED && door_state != prev_door_state) {
this->query_openings();
}
this->door_state = door_state;
this->on_door_state_.trigger(door_state);
} }
void RATGDOComponent::received(const LightState light_state) void RATGDOComponent::set_opening_duration(float duration) {
{ ESP_LOGD(TAG, "Set opening duration: %.1fs", duration);
ESP_LOGD(TAG, "Light state=%s", this->opening_duration = duration;
LOG_STR_ARG(LightState_to_string(light_state)));
this->light_state = light_state;
} }
void RATGDOComponent::received(const LockState lock_state) void RATGDOComponent::set_closing_duration(float duration) {
{ ESP_LOGD(TAG, "Set closing duration: %.1fs", duration);
ESP_LOGD(TAG, "Lock state=%s", LOG_STR_ARG(LockState_to_string(lock_state))); this->closing_duration = duration;
this->lock_state = lock_state;
} }
void RATGDOComponent::received(const LightAction light_action) void RATGDOComponent::set_distance_measurement(int16_t distance) {
{ this->last_distance_measurement = distance;
ESP_LOGD(TAG, "Light cmd=%s state=%s",
LOG_STR_ARG(LightAction_to_string(light_action)),
LOG_STR_ARG(LightState_to_string(*this->light_state)));
if (light_action == LightAction::OFF) {
this->light_state = LightState::OFF;
} else if (light_action == LightAction::ON) {
this->light_state = LightState::ON;
} else if (light_action == LightAction::TOGGLE) {
this->light_state = light_state_toggle(*this->light_state);
}
}
void RATGDOComponent::received(const Openings openings)
{
if (openings.flag == 0 || *this->openings != 0) {
this->openings = openings.count;
ESP_LOGD(TAG, "Openings: %d", *this->openings);
} else {
ESP_LOGD(TAG, "Ignoring openings, not from our request");
}
}
void RATGDOComponent::schedule_door_position_sync(float update_period)
{
ESP_LOG1(
TAG,
"Schedule position sync: delta %f, start position: %f, start moving: %d",
this->door_move_delta, this->door_start_position,
this->door_start_moving);
auto duration = this->door_move_delta > 0 ? *this->opening_duration
: *this->closing_duration;
if (duration == 0) {
return;
}
this->position_sync_remaining_ = std::max(static_cast<uint16_t>(1000 * duration / update_period),
static_cast<uint16_t>(1));
set_interval(INTERVAL_POSITION_SYNC, static_cast<uint32_t>(update_period),
[this]() {
this->door_position_update();
if (--this->position_sync_remaining_ == 0) {
cancel_interval(INTERVAL_POSITION_SYNC);
}
});
}
void RATGDOComponent::door_position_update()
{
if (this->door_start_moving == 0 || this->door_start_position == DOOR_POSITION_UNKNOWN || this->door_move_delta == DOOR_DELTA_UNKNOWN) {
return;
}
auto now = millis();
auto duration = this->door_move_delta > 0 ? *this->opening_duration
: -*this->closing_duration;
if (duration == 0) {
return;
}
auto position = this->door_start_position + (now - this->door_start_moving) / (1000 * duration);
position = clamp(position, 0.0f, 1.0f);
ESP_LOG2(TAG, "[%d] Position update: %f", now, position);
this->door_position = position;
// Check if we reached our move-to-position target
if (this->target_position_ != DOOR_POSITION_UNKNOWN && this->target_direction_ != DoorAction::UNKNOWN) {
bool reached = false;
if (this->target_direction_ == DoorAction::OPEN && position >= this->target_position_) {
reached = true;
} else if (this->target_direction_ == DoorAction::CLOSE && position <= this->target_position_) {
reached = true;
}
if (reached) {
ESP_LOGD(TAG, "Reached target position %.2f, stopping door", this->target_position_);
this->door_stop();
this->target_position_ = DOOR_POSITION_UNKNOWN;
this->target_direction_ = DoorAction::UNKNOWN;
}
}
}
void RATGDOComponent::set_opening_duration(float duration)
{
ESP_LOGD(TAG, "Set opening duration: %.1fs", duration);
this->opening_duration = duration;
}
void RATGDOComponent::set_closing_duration(float duration)
{
ESP_LOGD(TAG, "Set closing duration: %.1fs", duration);
this->closing_duration = duration;
}
void RATGDOComponent::set_distance_measurement(int16_t distance)
{
this->last_distance_measurement = distance;
} }
void RATGDOComponent::query_status() { this->protocol_->query_status(); } void RATGDOComponent::query_status() { this->protocol_->query_status(); }
void RATGDOComponent::query_openings() void RATGDOComponent::query_openings() { this->protocol_->query_openings(); }
{
this->protocol_->query_openings(); void RATGDOComponent::sync() { this->protocol_->sync(); }
void RATGDOComponent::set_door_state_expiry() {
this->set_timeout(TIMEOUT_DOOR_STATE_EXPIRY, DOOR_STATE_CALLBACK_TIMEOUT,
[this]() {
ESP_LOGW(TAG, "Door state callback expired, clearing");
this->on_door_state_.clear();
});
} }
void RATGDOComponent::sync() void RATGDOComponent::cancel_door_state_expiry() {
{ this->cancel_timeout(TIMEOUT_DOOR_STATE_EXPIRY);
this->protocol_->sync();
} }
void RATGDOComponent::set_door_state_expiry() void RATGDOComponent::smart_door_action(DoorAction target_direction) {
{ // target_direction must be OPEN or CLOSE
this->set_timeout(TIMEOUT_DOOR_STATE_EXPIRY, DOOR_STATE_CALLBACK_TIMEOUT, if (target_direction != DoorAction::OPEN &&
[this]() { target_direction != DoorAction::CLOSE) {
ESP_LOGW(TAG, "Door state callback expired, clearing"); return;
this->on_door_state_.clear(); }
});
}
void RATGDOComponent::cancel_door_state_expiry() DoorState expected_state = (target_direction == DoorAction::OPEN)
{ ? DoorState::OPENING
this->cancel_timeout(TIMEOUT_DOOR_STATE_EXPIRY); : DoorState::CLOSING;
} DoorState opposite_state = (target_direction == DoorAction::OPEN)
? DoorState::CLOSING
: DoorState::OPENING;
void RATGDOComponent::smart_door_action(DoorAction target_direction) // 1. Try the discrete command first
{ this->door_action(target_direction);
// target_direction must be OPEN or CLOSE
if (target_direction != DoorAction::OPEN && target_direction != DoorAction::CLOSE) {
return;
}
DoorState expected_state = (target_direction == DoorAction::OPEN) ? DoorState::OPENING : DoorState::CLOSING; // 2. Set a timeout to check if it worked
DoorState opposite_state = (target_direction == DoorAction::OPEN) ? DoorState::CLOSING : DoorState::OPENING; this->set_timeout(
2000, [this, target_direction, expected_state, opposite_state]() {
// 1. Try the discrete command first if (*this->door_state == expected_state ||
this->door_action(target_direction); (*this->door_state == DoorState::OPEN &&
target_direction == DoorAction::OPEN) ||
// 2. Set a timeout to check if it worked (*this->door_state == DoorState::CLOSED &&
this->set_timeout(2000, [this, target_direction, expected_state, opposite_state]() { target_direction == DoorAction::CLOSE)) {
if (*this->door_state == expected_state || // It worked (or is already at destination)
(*this->door_state == DoorState::OPEN && target_direction == DoorAction::OPEN) || return;
(*this->door_state == DoorState::CLOSED && target_direction == DoorAction::CLOSE)) {
// It worked (or is already at destination)
return;
} }
ESP_LOGW(TAG, "Discrete %s command ignored. Falling back to TOGGLE...", ESP_LOGW(TAG, "Discrete %s command ignored. Falling back to TOGGLE...",
target_direction == DoorAction::OPEN ? "OPEN" : "CLOSE"); target_direction == DoorAction::OPEN ? "OPEN" : "CLOSE");
// 3. Fallback: Send a TOGGLE // 3. Fallback: Send a TOGGLE
this->door_action(DoorAction::TOGGLE); this->door_action(DoorAction::TOGGLE);
// 4. Check what the toggle did // 4. Check what the toggle did
this->on_door_state([this, target_direction, expected_state, opposite_state](DoorState s) { this->on_door_state([this, target_direction, expected_state,
if (s == opposite_state) { opposite_state](DoorState s) {
// Wrong direction! Stop it. if (s == opposite_state) {
ESP_LOGW(TAG, "Toggle went the wrong way. Stopping..."); // Wrong direction! Stop it.
this->door_action(DoorAction::STOP); ESP_LOGW(TAG, "Toggle went the wrong way. Stopping...");
this->door_action(DoorAction::STOP);
// Once stopped, toggle again to go the right way
this->on_door_state([this](DoorState s2) {
if (s2 == DoorState::STOPPED) {
ESP_LOGD(TAG, "Stopped. Toggling again for correct direction.");
this->door_action(DoorAction::TOGGLE);
}
});
}
});
});
}
void RATGDOComponent::door_open() // Once stopped, toggle again to go the right way
{ this->on_door_state([this](DoorState s2) {
if (*this->door_state == DoorState::OPENING) { if (s2 == DoorState::STOPPED) {
return; // gets ignored by opener ESP_LOGD(TAG, "Stopped. Toggling again for correct direction.");
} this->door_action(DoorAction::TOGGLE);
}
this->smart_door_action(DoorAction::OPEN);
if (*this->opening_duration > 0) {
// query state in case we don't get a status message
this->set_timeout(
TIMEOUT_DOOR_QUERY_STATE, (*this->opening_duration + 5) * 1000,
[this]() {
if (*this->door_state != DoorState::OPEN && *this->door_state != DoorState::STOPPED) {
this->received(DoorState::OPEN); // probably missed a status mesage,
// assume it's open
this->query_status(); // query in case we're wrong and it's stopped
}
}); });
} }
}
void RATGDOComponent::door_close()
{
if (*this->door_state == DoorState::CLOSING) {
return; // gets ignored by opener
}
if (*this->door_state == DoorState::OPENING) {
// have to stop door first, otherwise close command is ignored
this->door_action(DoorAction::STOP);
this->on_door_state([this](DoorState s) {
if (s == DoorState::STOPPED) {
this->smart_door_action(DoorAction::CLOSE);
} else {
ESP_LOGW(TAG, "Door did not stop, ignoring close command");
}
}); });
return; });
}
this->smart_door_action(DoorAction::CLOSE);
if (*this->closing_duration > 0) {
// query state in case we don't get a status message
this->set_timeout(
TIMEOUT_DOOR_QUERY_STATE, (*this->closing_duration + 5) * 1000,
[this]() {
if (*this->door_state != DoorState::CLOSED && *this->door_state != DoorState::STOPPED) {
this->received(DoorState::CLOSED); // probably missed a status
// mesage, assume it's closed
this->query_status(); // query in case we're wrong and it's stopped
}
});
}
} }
void RATGDOComponent::door_stop() void RATGDOComponent::door_open() {
{ if (*this->door_state == DoorState::OPENING) {
if (*this->door_state != DoorState::OPENING && *this->door_state != DoorState::CLOSING) { return; // gets ignored by opener
return; }
}
this->smart_door_action(DoorAction::OPEN);
if (*this->opening_duration > 0) {
// query state in case we don't get a status message
this->set_timeout(
TIMEOUT_DOOR_QUERY_STATE, (*this->opening_duration + 5) * 1000,
[this]() {
if (*this->door_state != DoorState::OPEN &&
*this->door_state != DoorState::STOPPED) {
this->received(DoorState::OPEN); // probably missed a status
// mesage, assume it's open
this->query_status(); // query in case we're wrong and it's stopped
}
});
}
}
void RATGDOComponent::door_close() {
if (*this->door_state == DoorState::CLOSING) {
return; // gets ignored by opener
}
if (*this->door_state == DoorState::OPENING) {
// have to stop door first, otherwise close command is ignored
this->door_action(DoorAction::STOP); this->door_action(DoorAction::STOP);
this->on_door_state([this](DoorState s) {
if (s == DoorState::STOPPED) {
this->smart_door_action(DoorAction::CLOSE);
} else {
ESP_LOGW(TAG, "Door did not stop, ignoring close command");
}
});
return;
}
this->smart_door_action(DoorAction::CLOSE);
if (*this->closing_duration > 0) {
// query state in case we don't get a status message
this->set_timeout(
TIMEOUT_DOOR_QUERY_STATE, (*this->closing_duration + 5) * 1000,
[this]() {
if (*this->door_state != DoorState::CLOSED &&
*this->door_state != DoorState::STOPPED) {
this->received(DoorState::CLOSED); // probably missed a status
// mesage, assume it's closed
this->query_status(); // query in case we're wrong and it's stopped
}
});
}
}
void RATGDOComponent::door_stop() {
if (*this->door_state != DoorState::OPENING &&
*this->door_state != DoorState::CLOSING) {
return;
}
this->door_action(DoorAction::STOP);
} }
void RATGDOComponent::door_toggle() { this->door_action(DoorAction::TOGGLE); } void RATGDOComponent::door_toggle() { this->door_action(DoorAction::TOGGLE); }
void RATGDOComponent::door_action(DoorAction action) void RATGDOComponent::door_action(DoorAction action) {
{ this->protocol_->door_action(action);
this->protocol_->door_action(action);
} }
void RATGDOComponent::door_move_to_position(float position) void RATGDOComponent::door_move_to_position(float position) {
{ if (*this->door_state == DoorState::OPENING ||
if (*this->door_state == DoorState::OPENING || *this->door_state == DoorState::CLOSING) { *this->door_state == DoorState::CLOSING) {
this->door_action(DoorAction::STOP); this->door_action(DoorAction::STOP);
this->on_door_state([this, position](DoorState s) { this->on_door_state([this, position](DoorState s) {
if (s == DoorState::STOPPED) { if (s == DoorState::STOPPED) {
this->door_move_to_position(position); this->door_move_to_position(position);
} }
}); });
return; return;
} }
auto delta = position - *this->door_position; auto delta = position - *this->door_position;
if (delta == 0) { if (delta == 0) {
ESP_LOGD(TAG, "Door is already at position %.2f", position); ESP_LOGD(TAG, "Door is already at position %.2f", position);
return; return;
} }
auto duration = delta > 0 ? *this->opening_duration : -*this->closing_duration; auto duration =
if (duration == 0) { delta > 0 ? *this->opening_duration : -*this->closing_duration;
ESP_LOGW(TAG, "I don't know duration, ignoring move to position"); if (duration == 0) {
return; ESP_LOGW(TAG, "I don't know duration, ignoring move to position");
} return;
}
auto operation_time = 1000 * duration * delta; auto operation_time = 1000 * duration * delta;
this->door_move_delta = delta; this->door_move_delta = delta;
ESP_LOGD(TAG, "Moving to position %.2f (target duration %.1fs)", position, ESP_LOGD(TAG, "Moving to position %.2f (target duration %.1fs)", position,
operation_time / 1000.0); operation_time / 1000.0);
this->target_position_ = position; this->target_position_ = position;
this->target_direction_ = (delta > 0 ? DoorAction::OPEN : DoorAction::CLOSE); this->target_direction_ = (delta > 0 ? DoorAction::OPEN : DoorAction::CLOSE);
this->smart_door_action(this->target_direction_); this->smart_door_action(this->target_direction_);
} }
void RATGDOComponent::cancel_position_sync_callbacks() void RATGDOComponent::cancel_position_sync_callbacks() {
{ if (this->door_start_moving != 0) {
if (this->door_start_moving != 0) { ESP_LOGD(TAG, "Cancelling position callbacks");
ESP_LOGD(TAG, "Cancelling position callbacks"); this->cancel_timeout(TIMEOUT_MOVE_TO_POSITION);
this->cancel_timeout(TIMEOUT_MOVE_TO_POSITION); cancel_interval(INTERVAL_POSITION_SYNC);
cancel_interval(INTERVAL_POSITION_SYNC);
this->door_start_moving = 0; this->door_start_moving = 0;
this->door_start_position = DOOR_POSITION_UNKNOWN; this->door_start_position = DOOR_POSITION_UNKNOWN;
this->door_move_delta = DOOR_DELTA_UNKNOWN; this->door_move_delta = DOOR_DELTA_UNKNOWN;
} }
} }
void RATGDOComponent::light_on() void RATGDOComponent::light_on() {
{ this->light_state = LightState::ON;
this->light_state = LightState::ON; this->protocol_->light_action(LightAction::ON);
this->protocol_->light_action(LightAction::ON);
} }
void RATGDOComponent::light_off() void RATGDOComponent::light_off() {
{ this->light_state = LightState::OFF;
this->light_state = LightState::OFF; this->protocol_->light_action(LightAction::OFF);
this->protocol_->light_action(LightAction::OFF);
} }
LightState RATGDOComponent::get_light_state() const LightState RATGDOComponent::get_light_state() const {
{ return *this->light_state;
return *this->light_state;
} }
// Lock functions // Lock functions
void RATGDOComponent::lock() void RATGDOComponent::lock() {
{ this->lock_state = LockState::LOCKED;
this->lock_state = LockState::LOCKED; this->protocol_->lock_action(LockAction::LOCK);
this->protocol_->lock_action(LockAction::LOCK);
} }
void RATGDOComponent::unlock() void RATGDOComponent::unlock() {
{ this->lock_state = LockState::UNLOCKED;
this->lock_state = LockState::UNLOCKED; this->protocol_->lock_action(LockAction::UNLOCK);
this->protocol_->lock_action(LockAction::UNLOCK);
} }
// Subscribe implementations are now templates in ratgdo.h // Subscribe implementations are now templates in ratgdo.h
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+224 -222
View File
@@ -7,22 +7,22 @@
#pragma once #pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/preferences.h"
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "callbacks.h" #include "callbacks.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/preferences.h"
#include "observable.h" #include "observable.h"
#include "ratgdo_state.h" #include "ratgdo_state.h"
namespace esphome::ratgdo { namespace esphome::ratgdo {
namespace secplus2 { class Secplus2; } namespace secplus2 {
class Secplus2;
}
class RATGDOComponent; class RATGDOComponent;
typedef Parented<RATGDOComponent> RATGDOClient; typedef Parented<RATGDOComponent> RATGDOClient;
@@ -30,214 +30,222 @@ typedef Parented<RATGDOComponent> RATGDOClient;
const float DOOR_POSITION_UNKNOWN = -1.0; const float DOOR_POSITION_UNKNOWN = -1.0;
const float DOOR_DELTA_UNKNOWN = -2.0; const float DOOR_DELTA_UNKNOWN = -2.0;
class RATGDOComponent : public Component { class RATGDOComponent : public Component {
public: public:
RATGDOComponent() RATGDOComponent() {}
{
}
void setup() override; void setup() override;
void loop() override; void loop() override;
void dump_config() override; void dump_config() override;
void on_shutdown() override; void on_shutdown() override;
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } float get_setup_priority() const override {
return setup_priority::AFTER_WIFI;
}
void init_protocol(); void init_protocol();
float start_opening { -1 }; float start_opening{-1};
single_observable<float> opening_duration { 0 }; single_observable<float> opening_duration{0};
float start_closing { -1 }; float start_closing{-1};
single_observable<float> closing_duration { 0 }; single_observable<float> closing_duration{0};
observable<int16_t, RATGDO_MAX_DISTANCE_SUBSCRIBERS> last_distance_measurement { 0 }; observable<int16_t, RATGDO_MAX_DISTANCE_SUBSCRIBERS>
last_distance_measurement{0};
single_observable<uint16_t> openings { 0 }; // number of times the door has been opened single_observable<uint16_t> openings{
0}; // number of times the door has been opened
observable<DoorState, RATGDO_MAX_DOOR_STATE_SUBSCRIBERS> door_state { DoorState::UNKNOWN }; observable<DoorState, RATGDO_MAX_DOOR_STATE_SUBSCRIBERS> door_state{
observable<float, RATGDO_MAX_DOOR_STATE_SUBSCRIBERS> door_position { DOOR_POSITION_UNKNOWN }; DoorState::UNKNOWN};
observable<float, RATGDO_MAX_DOOR_STATE_SUBSCRIBERS> door_position{
DOOR_POSITION_UNKNOWN};
unsigned long door_start_moving { 0 }; unsigned long door_start_moving{0};
float door_start_position { DOOR_POSITION_UNKNOWN }; float door_start_position{DOOR_POSITION_UNKNOWN};
float door_move_delta { DOOR_DELTA_UNKNOWN }; float door_move_delta{DOOR_DELTA_UNKNOWN};
uint16_t position_sync_remaining_ { 0 }; uint16_t position_sync_remaining_{0};
float target_position_ { DOOR_POSITION_UNKNOWN }; float target_position_{DOOR_POSITION_UNKNOWN};
DoorAction target_direction_ { DoorAction::UNKNOWN }; DoorAction target_direction_{DoorAction::UNKNOWN};
single_observable<LightState> light_state { LightState::UNKNOWN }; single_observable<LightState> light_state{LightState::UNKNOWN};
single_observable<LockState> lock_state { LockState::UNKNOWN }; single_observable<LockState> lock_state{LockState::UNKNOWN};
OnceCallbacks<void(DoorState)> on_door_state_; OnceCallbacks<void(DoorState)> on_door_state_;
single_observable<bool> synced { false }; single_observable<bool> synced{false};
single_observable<optional<bool>> sync_failed { nullopt }; single_observable<optional<bool>> sync_failed{nullopt};
void set_output_gdo_pin(InternalGPIOPin* pin) { this->output_gdo_pin_ = pin; } void set_output_gdo_pin(InternalGPIOPin* pin) { this->output_gdo_pin_ = pin; }
void set_input_gdo_pin(InternalGPIOPin* pin) { this->input_gdo_pin_ = pin; } void set_input_gdo_pin(InternalGPIOPin* pin) { this->input_gdo_pin_ = pin; }
void received(const DoorState door_state);
void received(const LightState light_state);
void received(const LockState lock_state);
void received(const LightAction light_action);
void received(const Openings openings);
// door
void door_toggle();
void door_open();
void door_close();
void door_stop();
void received(const DoorState door_state); void door_action(DoorAction action);
void received(const LightState light_state); void smart_door_action(DoorAction target_direction);
void received(const LockState lock_state); void door_move_to_position(float position);
void received(const LightAction light_action); void set_door_position(float door_position) {
void received(const Openings openings); this->door_position = door_position;
}
void set_opening_duration(float duration);
void set_closing_duration(float duration);
void schedule_door_position_sync(float update_period = 500);
void door_position_update();
void cancel_position_sync_callbacks();
void set_distance_measurement(int16_t distance);
// door // light
void door_toggle(); void light_on();
void door_open(); void light_off();
void door_close(); LightState get_light_state() const;
void door_stop();
void door_action(DoorAction action); // lock
void smart_door_action(DoorAction target_direction); void lock();
void door_move_to_position(float position); void unlock();
void set_door_position(float door_position) { this->door_position = door_position; }
void set_opening_duration(float duration);
void set_closing_duration(float duration);
void schedule_door_position_sync(float update_period = 500);
void door_position_update();
void cancel_position_sync_callbacks();
void set_distance_measurement(int16_t distance);
// light // button functionality
void light_on(); void query_status();
void light_off(); void query_openings();
LightState get_light_state() const; void sync();
// lock using Component::set_timeout;
void lock();
void unlock();
// button functionality void set_door_state_expiry();
void query_status(); void cancel_door_state_expiry();
void query_openings();
void sync();
using Component::set_timeout; // Register a one-shot door state callback with automatic expiry.
//
void set_door_state_expiry(); // Handles secplus1's nested callback chains where opening from
void cancel_door_state_expiry(); // STOPPED requires multiple state transitions:
//
// Register a one-shot door state callback with automatic expiry. // on_door_state(outer_cb) // wait for CLOSING
// // → set_door_state_expiry() // expiry A
// Handles secplus1's nested callback chains where opening from // → [door reports CLOSING]
// STOPPED requires multiple state transitions: // → outer_cb fires, calls:
// // toggle_door()
// on_door_state(outer_cb) // wait for CLOSING // on_door_state(inner_cb) // wait for STOPPED
// → set_door_state_expiry() // expiry A // → set_door_state_expiry() // expiry B (replaces A)
// → [door reports CLOSING] // → [door reports STOPPED]
// → outer_cb fires, calls: // → inner_cb fires
// toggle_door() // toggle_door() // door now opening
// on_door_state(inner_cb) // wait for STOPPED // count()==0 → cancel expiry B
// → set_door_state_expiry() // expiry B (replaces A) //
// → [door reports STOPPED] // The user callback runs BEFORE the expiry check because it may
// → inner_cb fires // re-arm the chain by calling on_door_state() again. If it does,
// toggle_door() // door now opening // the new call sets expiry B which replaces expiry A (same timeout
// count()==0 → cancel expiry B // ID = replace, not add). We only cancel expiry when count()==0,
// // meaning no new callback was queued — otherwise we'd cancel
// The user callback runs BEFORE the expiry check because it may // expiry B here and leave the inner callback without protection.
// re-arm the chain by calling on_door_state() again. If it does, template <typename F>
// the new call sets expiry B which replaces expiry A (same timeout void on_door_state(F&& callback) {
// ID = replace, not add). We only cancel expiry when count()==0, using Cb = std::decay_t<F>;
// meaning no new callback was queued — otherwise we'd cancel this->on_door_state_(
// expiry B here and leave the inner callback without protection. [this, cb = Cb(std::forward<F>(callback))](DoorState s) {
template <typename F> cb(s);
void on_door_state(F&& callback) if (!this->on_door_state_.count()) {
{ this->cancel_door_state_expiry();
using Cb = std::decay_t<F>; }
this->on_door_state_([this, cb = Cb(std::forward<F>(callback))](DoorState s) {
cb(s);
if (!this->on_door_state_.count()) {
this->cancel_door_state_expiry();
}
}); });
this->set_door_state_expiry(); this->set_door_state_expiry();
} }
// children subscriptions — type-safe templates (no std::function) // children subscriptions — type-safe templates (no std::function)
// Callbacks must be trivially copyable and fit in Callback storage // Callbacks must be trivially copyable and fit in Callback storage
// (3 * sizeof(void*)), e.g. [this] or [this, f] lambdas. // (3 * sizeof(void*)), e.g. [this] or [this, f] lambdas.
// Enforced at compile time by Callback::create(). // Enforced at compile time by Callback::create().
template <typename F> template <typename F>
void subscribe_opening_duration(F&& f); void subscribe_opening_duration(F&& f);
template <typename F> template <typename F>
void subscribe_closing_duration(F&& f); void subscribe_closing_duration(F&& f);
template <typename F> template <typename F>
void subscribe_openings(F&& f); void subscribe_openings(F&& f);
template <typename F> template <typename F>
void subscribe_door_state(F&& f); void subscribe_door_state(F&& f);
template <typename F> template <typename F>
void subscribe_light_state(F&& f); void subscribe_light_state(F&& f);
template <typename F> template <typename F>
void subscribe_lock_state(F&& f); void subscribe_lock_state(F&& f);
template <typename F> template <typename F>
void subscribe_sync_failed(F&& f); void subscribe_sync_failed(F&& f);
template <typename F> template <typename F>
void subscribe_distance_measurement(F&& f); void subscribe_distance_measurement(F&& f);
protected: protected:
// Pointers first (4-byte aligned) // Pointers first (4-byte aligned)
secplus2::Secplus2* protocol_; secplus2::Secplus2* protocol_;
InternalGPIOPin* output_gdo_pin_; InternalGPIOPin* output_gdo_pin_;
InternalGPIOPin* input_gdo_pin_; InternalGPIOPin* input_gdo_pin_;
// Bool members packed into bitfield // Bool members packed into bitfield
struct { struct {
uint8_t reserved : 8; // Reserved for future use uint8_t reserved : 8; // Reserved for future use
} flags_ { 0 }; } flags_{0};
// Subscriber counters for defer name allocation // Subscriber counters for defer name allocation
uint8_t door_state_sub_num_ { 0 }; uint8_t door_state_sub_num_{0};
uint8_t distance_sub_num_ { 0 }; uint8_t distance_sub_num_{0};
}; // RATGDOComponent }; // RATGDOComponent
void log_subscriber_overflow(const LogString* observable_name, uint32_t max); void log_subscriber_overflow(const LogString* observable_name, uint32_t max);
inline uint32_t get_scheduler_id(uint32_t base, uint32_t count, uint8_t& counter, const LogString* observable_name) inline uint32_t get_scheduler_id(uint32_t base, uint32_t count,
{ uint8_t& counter,
if (count == 0) { const LogString* observable_name) {
log_subscriber_overflow(observable_name, count); if (count == 0) {
return base; log_subscriber_overflow(observable_name, count);
} return base;
if (counter >= count) { }
log_subscriber_overflow(observable_name, count); if (counter >= count) {
return base + count - 1; // reuse last ID to avoid collision with first subscriber log_subscriber_overflow(observable_name, count);
} return base + count -
return base + counter++; 1; // reuse last ID to avoid collision with first subscriber
}
return base + counter++;
} }
// Scheduler IDs using uint32_t ranges to avoid heap allocations // Scheduler IDs using uint32_t ranges to avoid heap allocations
// Bases are auto-generated from counts to prevent ID conflicts // Bases are auto-generated from counts to prevent ID conflicts
namespace scheduler_ids { namespace scheduler_ids {
inline constexpr uint32_t INTERVAL_POSITION_SYNC = 0; inline constexpr uint32_t INTERVAL_POSITION_SYNC = 0;
// Multi-subscriber ranges — counts derived from codegen defines // Multi-subscriber ranges — counts derived from codegen defines
inline constexpr uint32_t DEFER_DOOR_STATE_COUNT = RATGDO_MAX_DOOR_STATE_SUBSCRIBERS; inline constexpr uint32_t DEFER_DOOR_STATE_COUNT =
inline constexpr uint32_t DEFER_DOOR_STATE_BASE = INTERVAL_POSITION_SYNC + 1; RATGDO_MAX_DOOR_STATE_SUBSCRIBERS;
inline constexpr uint32_t DEFER_DOOR_STATE_BASE = INTERVAL_POSITION_SYNC + 1;
inline constexpr uint32_t DEFER_DISTANCE_COUNT = RATGDO_MAX_DISTANCE_SUBSCRIBERS; inline constexpr uint32_t DEFER_DISTANCE_COUNT =
inline constexpr uint32_t DEFER_DISTANCE_BASE = DEFER_DOOR_STATE_BASE + DEFER_DOOR_STATE_COUNT; RATGDO_MAX_DISTANCE_SUBSCRIBERS;
inline constexpr uint32_t DEFER_DISTANCE_END = DEFER_DISTANCE_BASE + DEFER_DISTANCE_COUNT; inline constexpr uint32_t DEFER_DISTANCE_BASE =
DEFER_DOOR_STATE_BASE + DEFER_DOOR_STATE_COUNT;
inline constexpr uint32_t DEFER_DISTANCE_END =
DEFER_DISTANCE_BASE + DEFER_DISTANCE_COUNT;
// Single-subscriber IDs // Single-subscriber IDs
enum : uint32_t { enum : uint32_t {
DEFER_OPENING_DURATION = DEFER_DISTANCE_END, DEFER_OPENING_DURATION = DEFER_DISTANCE_END,
DEFER_CLOSING_DURATION, DEFER_CLOSING_DURATION,
DEFER_OPENINGS, DEFER_OPENINGS,
DEFER_LIGHT_STATE, DEFER_LIGHT_STATE,
DEFER_LOCK_STATE, DEFER_LOCK_STATE,
// Named timeout IDs (replacing string-based names) // Named timeout IDs (replacing string-based names)
TIMEOUT_DOOR_QUERY_STATE, TIMEOUT_DOOR_QUERY_STATE,
TIMEOUT_DOOR_ACTION, TIMEOUT_DOOR_ACTION,
TIMEOUT_MOVE_TO_POSITION, TIMEOUT_MOVE_TO_POSITION,
TIMEOUT_DOOR_STATE_EXPIRY, TIMEOUT_DOOR_STATE_EXPIRY,
TIMEOUT_SYNC, TIMEOUT_SYNC,
}; };
} // namespace scheduler_ids } // namespace scheduler_ids
// Template implementations for subscribe methods. // Template implementations for subscribe methods.
// Each wraps the callback in a deferred call so that if the observable // Each wraps the callback in a deferred call so that if the observable
@@ -245,72 +253,66 @@ namespace scheduler_ids {
// is dispatched to the child component. // is dispatched to the child component.
template <typename F> template <typename F>
void RATGDOComponent::subscribe_opening_duration(F&& f) void RATGDOComponent::subscribe_opening_duration(F&& f) {
{ this->opening_duration.subscribe([this, f](float state) {
this->opening_duration.subscribe([this, f](float state) { defer(scheduler_ids::DEFER_OPENING_DURATION, [f, state] { f(state); });
defer(scheduler_ids::DEFER_OPENING_DURATION, [f, state] { f(state); }); });
});
} }
template <typename F> template <typename F>
void RATGDOComponent::subscribe_closing_duration(F&& f) void RATGDOComponent::subscribe_closing_duration(F&& f) {
{ this->closing_duration.subscribe([this, f](float state) {
this->closing_duration.subscribe([this, f](float state) { defer(scheduler_ids::DEFER_CLOSING_DURATION, [f, state] { f(state); });
defer(scheduler_ids::DEFER_CLOSING_DURATION, [f, state] { f(state); }); });
});
} }
template <typename F> template <typename F>
void RATGDOComponent::subscribe_openings(F&& f) void RATGDOComponent::subscribe_openings(F&& f) {
{ this->openings.subscribe([this, f](uint16_t state) {
this->openings.subscribe([this, f](uint16_t state) { defer(scheduler_ids::DEFER_OPENINGS, [f, state] { f(state); });
defer(scheduler_ids::DEFER_OPENINGS, [f, state] { f(state); }); });
});
} }
template <typename F> template <typename F>
void RATGDOComponent::subscribe_door_state(F&& f) void RATGDOComponent::subscribe_door_state(F&& f) {
{ uint32_t id =
uint32_t id = get_scheduler_id(scheduler_ids::DEFER_DOOR_STATE_BASE, scheduler_ids::DEFER_DOOR_STATE_COUNT, get_scheduler_id(scheduler_ids::DEFER_DOOR_STATE_BASE,
this->door_state_sub_num_, LOG_STR("door_state")); scheduler_ids::DEFER_DOOR_STATE_COUNT,
this->door_state.subscribe([this, f, id](DoorState state) { this->door_state_sub_num_, LOG_STR("door_state"));
defer(id, [this, f, state] { f(state, *this->door_position); }); this->door_state.subscribe([this, f, id](DoorState state) {
}); defer(id, [this, f, state] { f(state, *this->door_position); });
this->door_position.subscribe([this, f, id](float position) { });
defer(id, [this, f, position] { f(*this->door_state, position); }); this->door_position.subscribe([this, f, id](float position) {
}); defer(id, [this, f, position] { f(*this->door_state, position); });
});
} }
template <typename F> template <typename F>
void RATGDOComponent::subscribe_light_state(F&& f) void RATGDOComponent::subscribe_light_state(F&& f) {
{ this->light_state.subscribe([this, f](LightState state) {
this->light_state.subscribe([this, f](LightState state) { defer(scheduler_ids::DEFER_LIGHT_STATE, [f, state] { f(state); });
defer(scheduler_ids::DEFER_LIGHT_STATE, [f, state] { f(state); }); });
});
} }
template <typename F> template <typename F>
void RATGDOComponent::subscribe_lock_state(F&& f) void RATGDOComponent::subscribe_lock_state(F&& f) {
{ this->lock_state.subscribe([this, f](LockState state) {
this->lock_state.subscribe([this, f](LockState state) { defer(scheduler_ids::DEFER_LOCK_STATE, [f, state] { f(state); });
defer(scheduler_ids::DEFER_LOCK_STATE, [f, state] { f(state); }); });
});
} }
template <typename F> template <typename F>
void RATGDOComponent::subscribe_sync_failed(F&& f) void RATGDOComponent::subscribe_sync_failed(F&& f) {
{ this->sync_failed.subscribe(std::forward<F>(f));
this->sync_failed.subscribe(std::forward<F>(f));
} }
template <typename F> template <typename F>
void RATGDOComponent::subscribe_distance_measurement(F&& f) void RATGDOComponent::subscribe_distance_measurement(F&& f) {
{ uint32_t id = get_scheduler_id(
uint32_t id = get_scheduler_id(scheduler_ids::DEFER_DISTANCE_BASE, scheduler_ids::DEFER_DISTANCE_COUNT, scheduler_ids::DEFER_DISTANCE_BASE, scheduler_ids::DEFER_DISTANCE_COUNT,
this->distance_sub_num_, LOG_STR("distance_measurement")); this->distance_sub_num_, LOG_STR("distance_measurement"));
this->last_distance_measurement.subscribe([this, f, id](int16_t state) { this->last_distance_measurement.subscribe(
defer(id, [f, state] { f(state); }); [this, f, id](int16_t state) { defer(id, [f, state] { f(state); }); });
});
} }
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+8 -9
View File
@@ -2,18 +2,17 @@
namespace esphome::ratgdo { namespace esphome::ratgdo {
LightState light_state_toggle(LightState state) LightState light_state_toggle(LightState state) {
{ switch (state) {
switch (state) {
case LightState::OFF: case LightState::OFF:
return LightState::ON; return LightState::ON;
case LightState::ON: case LightState::ON:
return LightState::OFF; return LightState::OFF;
// 2 and 3 appears sometimes // 2 and 3 appears sometimes
case LightState::UNKNOWN: case LightState::UNKNOWN:
default: default:
return LightState::UNKNOWN; return LightState::UNKNOWN;
} }
} }
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+64 -60
View File
@@ -6,121 +6,125 @@
************************************/ ************************************/
#pragma once #pragma once
#include "esphome/core/defines.h"
#include <cstdint> #include <cstdint>
#include "esphome/core/defines.h"
namespace esphome::ratgdo { namespace esphome::ratgdo {
enum class DoorState : uint8_t { enum class DoorState : uint8_t {
UNKNOWN = 0, UNKNOWN = 0,
OPEN = 1, OPEN = 1,
CLOSED = 2, CLOSED = 2,
STOPPED = 3, STOPPED = 3,
OPENING = 4, OPENING = 4,
CLOSING = 5 CLOSING = 5
}; };
inline const char* DoorState_to_string(DoorState e) { inline const char* DoorState_to_string(DoorState e) {
static const char* const names[] = {"UNKNOWN", "OPEN", "CLOSED", "STOPPED", "OPENING", "CLOSING"}; static const char* const names[] = {"UNKNOWN", "OPEN", "CLOSED",
auto i = static_cast<uint8_t>(e); "STOPPED", "OPENING", "CLOSING"};
return (i <= static_cast<uint8_t>(DoorState::CLOSING)) ? names[i] : "UNKNOWN"; auto i = static_cast<uint8_t>(e);
return (i <= static_cast<uint8_t>(DoorState::CLOSING)) ? names[i] : "UNKNOWN";
} }
inline DoorState to_DoorState(uint8_t t, DoorState unknown) { inline DoorState to_DoorState(uint8_t t, DoorState unknown) {
return (t <= static_cast<uint8_t>(DoorState::CLOSING)) ? static_cast<DoorState>(t) : unknown; return (t <= static_cast<uint8_t>(DoorState::CLOSING))
? static_cast<DoorState>(t)
: unknown;
} }
/// Enum for all states a the light can be in. /// Enum for all states a the light can be in.
enum class LightState : uint8_t { enum class LightState : uint8_t { OFF = 0, ON = 1, UNKNOWN = 2 };
OFF = 0,
ON = 1,
UNKNOWN = 2
};
inline const char* LightState_to_string(LightState e) { inline const char* LightState_to_string(LightState e) {
static const char* const names[] = {"OFF", "ON", "UNKNOWN"}; static const char* const names[] = {"OFF", "ON", "UNKNOWN"};
auto i = static_cast<uint8_t>(e); auto i = static_cast<uint8_t>(e);
return (i <= static_cast<uint8_t>(LightState::UNKNOWN)) ? names[i] : "UNKNOWN"; return (i <= static_cast<uint8_t>(LightState::UNKNOWN)) ? names[i]
: "UNKNOWN";
} }
inline LightState to_LightState(uint8_t t, LightState unknown) { inline LightState to_LightState(uint8_t t, LightState unknown) {
return (t <= static_cast<uint8_t>(LightState::UNKNOWN)) ? static_cast<LightState>(t) : unknown; return (t <= static_cast<uint8_t>(LightState::UNKNOWN))
? static_cast<LightState>(t)
: unknown;
} }
LightState light_state_toggle(LightState state); LightState light_state_toggle(LightState state);
/// Enum for all states a the lock can be in. /// Enum for all states a the lock can be in.
enum class LockState : uint8_t { enum class LockState : uint8_t { UNLOCKED = 0, LOCKED = 1, UNKNOWN = 2 };
UNLOCKED = 0,
LOCKED = 1,
UNKNOWN = 2
};
inline const char* LockState_to_string(LockState e) { inline const char* LockState_to_string(LockState e) {
static const char* const names[] = {"UNLOCKED", "LOCKED", "UNKNOWN"}; static const char* const names[] = {"UNLOCKED", "LOCKED", "UNKNOWN"};
auto i = static_cast<uint8_t>(e); auto i = static_cast<uint8_t>(e);
return (i <= static_cast<uint8_t>(LockState::UNKNOWN)) ? names[i] : "UNKNOWN"; return (i <= static_cast<uint8_t>(LockState::UNKNOWN)) ? names[i] : "UNKNOWN";
} }
inline LockState to_LockState(uint8_t t, LockState unknown) { inline LockState to_LockState(uint8_t t, LockState unknown) {
return (t <= static_cast<uint8_t>(LockState::UNKNOWN)) ? static_cast<LockState>(t) : unknown; return (t <= static_cast<uint8_t>(LockState::UNKNOWN))
? static_cast<LockState>(t)
: unknown;
} }
// actions // actions
enum class LightAction : uint8_t { enum class LightAction : uint8_t { OFF = 0, ON = 1, TOGGLE = 2, UNKNOWN = 3 };
OFF = 0,
ON = 1,
TOGGLE = 2,
UNKNOWN = 3
};
inline const char* LightAction_to_string(LightAction e) { inline const char* LightAction_to_string(LightAction e) {
static const char* const names[] = {"OFF", "ON", "TOGGLE", "UNKNOWN"}; static const char* const names[] = {"OFF", "ON", "TOGGLE", "UNKNOWN"};
auto i = static_cast<uint8_t>(e); auto i = static_cast<uint8_t>(e);
return (i <= static_cast<uint8_t>(LightAction::UNKNOWN)) ? names[i] : "UNKNOWN"; return (i <= static_cast<uint8_t>(LightAction::UNKNOWN)) ? names[i]
: "UNKNOWN";
} }
inline LightAction to_LightAction(uint8_t t, LightAction unknown) { inline LightAction to_LightAction(uint8_t t, LightAction unknown) {
return (t <= static_cast<uint8_t>(LightAction::UNKNOWN)) ? static_cast<LightAction>(t) : unknown; return (t <= static_cast<uint8_t>(LightAction::UNKNOWN))
? static_cast<LightAction>(t)
: unknown;
} }
enum class LockAction : uint8_t { enum class LockAction : uint8_t { UNLOCK = 0, LOCK = 1, UNKNOWN = 3 };
UNLOCK = 0,
LOCK = 1,
UNKNOWN = 3
};
inline const char* LockAction_to_string(LockAction e) { inline const char* LockAction_to_string(LockAction e) {
static const char* const names[] = {"UNLOCK", "LOCK", "UNKNOWN", "UNKNOWN"}; static const char* const names[] = {"UNLOCK", "LOCK", "UNKNOWN", "UNKNOWN"};
auto i = static_cast<uint8_t>(e); auto i = static_cast<uint8_t>(e);
return (i <= static_cast<uint8_t>(LockAction::UNKNOWN)) ? names[i] : "UNKNOWN"; return (i <= static_cast<uint8_t>(LockAction::UNKNOWN)) ? names[i]
: "UNKNOWN";
} }
inline LockAction to_LockAction(uint8_t t, LockAction unknown) { inline LockAction to_LockAction(uint8_t t, LockAction unknown) {
return (t == static_cast<uint8_t>(LockAction::UNLOCK) || t == static_cast<uint8_t>(LockAction::LOCK) || t == static_cast<uint8_t>(LockAction::UNKNOWN)) ? static_cast<LockAction>(t) : unknown; return (t == static_cast<uint8_t>(LockAction::UNLOCK) ||
t == static_cast<uint8_t>(LockAction::LOCK) ||
t == static_cast<uint8_t>(LockAction::UNKNOWN))
? static_cast<LockAction>(t)
: unknown;
} }
enum class DoorAction : uint8_t { enum class DoorAction : uint8_t {
CLOSE = 0, CLOSE = 0,
OPEN = 1, OPEN = 1,
TOGGLE = 2, TOGGLE = 2,
STOP = 3, STOP = 3,
UNKNOWN = 4 UNKNOWN = 4
}; };
inline const char* DoorAction_to_string(DoorAction e) { inline const char* DoorAction_to_string(DoorAction e) {
static const char* const names[] = {"CLOSE", "OPEN", "TOGGLE", "STOP", "UNKNOWN"}; static const char* const names[] = {"CLOSE", "OPEN", "TOGGLE", "STOP",
auto i = static_cast<uint8_t>(e); "UNKNOWN"};
return (i <= static_cast<uint8_t>(DoorAction::UNKNOWN)) ? names[i] : "UNKNOWN"; auto i = static_cast<uint8_t>(e);
return (i <= static_cast<uint8_t>(DoorAction::UNKNOWN)) ? names[i]
: "UNKNOWN";
} }
inline DoorAction to_DoorAction(uint8_t t, DoorAction unknown) { inline DoorAction to_DoorAction(uint8_t t, DoorAction unknown) {
return (t <= static_cast<uint8_t>(DoorAction::UNKNOWN)) ? static_cast<DoorAction>(t) : unknown; return (t <= static_cast<uint8_t>(DoorAction::UNKNOWN))
? static_cast<DoorAction>(t)
: unknown;
} }
struct Openings { struct Openings {
uint16_t count; uint16_t count;
uint8_t flag; uint8_t flag;
}; };
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+116 -122
View File
@@ -1,17 +1,16 @@
#include "ratgdo_uart_esp32.h" #include "ratgdo_uart_esp32.h"
#include "esphome/core/log.h"
#include <driver/uart.h>
#include <driver/rmt_tx.h>
#include <esp_private/rmt.h> // for rmt_get_channel_id (used once during init)
#include <driver/gpio.h> #include <driver/gpio.h>
#include <driver/rmt_tx.h>
#include <driver/uart.h>
#include <esp_private/rmt.h> // for rmt_get_channel_id (used once during init)
#include <esp_rom_gpio.h> #include <esp_rom_gpio.h>
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/task.h> #include <freertos/task.h>
#include <soc/gpio_sig_map.h> #include <soc/gpio_sig_map.h>
#include "esphome/core/log.h"
namespace esphome::ratgdo { namespace esphome::ratgdo {
static const char* const TAG = "ratgdo_uart"; static const char* const TAG = "ratgdo_uart";
@@ -24,7 +23,7 @@ static constexpr uint16_t PREAMBLE_MARK_US = 130;
static constexpr uint8_t SIGNAL_SETTLE_US = 5; static constexpr uint8_t SIGNAL_SETTLE_US = 5;
// RMT channel configuration // RMT channel configuration
static constexpr uint32_t RMT_RESOLUTION_HZ = 1000000; // 1MHz = 1us per tick static constexpr uint32_t RMT_RESOLUTION_HZ = 1000000; // 1MHz = 1us per tick
static constexpr size_t RMT_MEM_BLOCK_SYMBOLS = 64; static constexpr size_t RMT_MEM_BLOCK_SYMBOLS = 64;
static constexpr size_t RMT_TRANS_QUEUE_DEPTH = 4; static constexpr size_t RMT_TRANS_QUEUE_DEPTH = 4;
@@ -32,152 +31,147 @@ static constexpr size_t RMT_TRANS_QUEUE_DEPTH = 4;
static constexpr int UART_PORT = UART_NUM_1; static constexpr int UART_PORT = UART_NUM_1;
static constexpr int UART_TX_SIGNAL_IDX = U1TXD_OUT_IDX; static constexpr int UART_TX_SIGNAL_IDX = U1TXD_OUT_IDX;
RatgdoUART::RatgdoUART() { } RatgdoUART::RatgdoUART() {}
RatgdoUART::~RatgdoUART() RatgdoUART::~RatgdoUART() {
{ if (this->is_initialized_) {
if (this->is_initialized_) { uart_driver_delete((uart_port_t)this->uart_num_);
uart_driver_delete((uart_port_t)this->uart_num_); if (this->rmt_copy_encoder_) {
if (this->rmt_copy_encoder_) { rmt_del_encoder(this->rmt_copy_encoder_);
rmt_del_encoder(this->rmt_copy_encoder_); this->rmt_copy_encoder_ = nullptr;
this->rmt_copy_encoder_ = nullptr;
}
if (this->rmt_chan_handle_) {
rmt_disable(this->rmt_chan_handle_);
rmt_del_channel(this->rmt_chan_handle_);
this->rmt_chan_handle_ = nullptr;
}
} }
if (this->rmt_chan_handle_) {
rmt_disable(this->rmt_chan_handle_);
rmt_del_channel(this->rmt_chan_handle_);
this->rmt_chan_handle_ = nullptr;
}
}
} }
void RatgdoUART::begin(int baud, RatgdoUARTConfig config, int rx_pin, void RatgdoUART::begin(int baud, RatgdoUARTConfig config, int rx_pin,
int tx_pin, bool invert) int tx_pin, bool invert) {
{ this->tx_pin_ = tx_pin;
this->tx_pin_ = tx_pin; this->rx_pin_ = rx_pin;
this->rx_pin_ = rx_pin; this->baud_ = baud;
this->baud_ = baud; this->inverted_ = invert;
this->inverted_ = invert;
this->uart_num_ = UART_PORT; this->uart_num_ = UART_PORT;
uart_config_t uart_config = { }; uart_config_t uart_config = {};
uart_config.baud_rate = baud; uart_config.baud_rate = baud;
uart_config.data_bits = UART_DATA_8_BITS; uart_config.data_bits = UART_DATA_8_BITS;
uart_config.parity = (config == RATGDO_UART_8E1) ? UART_PARITY_EVEN : UART_PARITY_DISABLE; uart_config.parity =
uart_config.stop_bits = UART_STOP_BITS_1; (config == RATGDO_UART_8E1) ? UART_PARITY_EVEN : UART_PARITY_DISABLE;
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; uart_config.stop_bits = UART_STOP_BITS_1;
uart_config.source_clk = UART_SCLK_APB; uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
uart_config.source_clk = UART_SCLK_APB;
ESP_ERROR_CHECK( ESP_ERROR_CHECK(uart_driver_install((uart_port_t)this->uart_num_,
uart_driver_install((uart_port_t)this->uart_num_, UART_RX_BUFFER_SIZE, 0, 0, NULL, 0)); UART_RX_BUFFER_SIZE, 0, 0, NULL, 0));
ESP_ERROR_CHECK(uart_param_config((uart_port_t)this->uart_num_, &uart_config)); ESP_ERROR_CHECK(
uart_param_config((uart_port_t)this->uart_num_, &uart_config));
rmt_tx_channel_config_t tx_chan_config = { }; rmt_tx_channel_config_t tx_chan_config = {};
tx_chan_config.gpio_num = (gpio_num_t)tx_pin; tx_chan_config.gpio_num = (gpio_num_t)tx_pin;
tx_chan_config.clk_src = RMT_CLK_SRC_DEFAULT; tx_chan_config.clk_src = RMT_CLK_SRC_DEFAULT;
tx_chan_config.resolution_hz = RMT_RESOLUTION_HZ; tx_chan_config.resolution_hz = RMT_RESOLUTION_HZ;
tx_chan_config.mem_block_symbols = RMT_MEM_BLOCK_SYMBOLS; tx_chan_config.mem_block_symbols = RMT_MEM_BLOCK_SYMBOLS;
tx_chan_config.trans_queue_depth = RMT_TRANS_QUEUE_DEPTH; tx_chan_config.trans_queue_depth = RMT_TRANS_QUEUE_DEPTH;
tx_chan_config.flags.invert_out = 0; tx_chan_config.flags.invert_out = 0;
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &this->rmt_chan_handle_)); ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &this->rmt_chan_handle_));
rmt_copy_encoder_config_t copy_encoder_config = { }; rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_ERROR_CHECK( ESP_ERROR_CHECK(
rmt_new_copy_encoder(&copy_encoder_config, &this->rmt_copy_encoder_)); rmt_new_copy_encoder(&copy_encoder_config, &this->rmt_copy_encoder_));
ESP_ERROR_CHECK(rmt_enable(this->rmt_chan_handle_)); ESP_ERROR_CHECK(rmt_enable(this->rmt_chan_handle_));
// Cache the channel ID for GPIO matrix switching during preamble. // Cache the channel ID for GPIO matrix switching during preamble.
// The RMT driver allocates channels dynamically, so we query it once // The RMT driver allocates channels dynamically, so we query it once
// here rather than relying on the private esp_private/rmt.h API at runtime. // here rather than relying on the private esp_private/rmt.h API at runtime.
ESP_ERROR_CHECK(rmt_get_channel_id(this->rmt_chan_handle_, &this->rmt_channel_id_)); ESP_ERROR_CHECK(
rmt_get_channel_id(this->rmt_chan_handle_, &this->rmt_channel_id_));
ESP_ERROR_CHECK(uart_set_pin((uart_port_t)this->uart_num_, tx_pin, rx_pin, ESP_ERROR_CHECK(uart_set_pin((uart_port_t)this->uart_num_, tx_pin, rx_pin,
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
if (invert) { if (invert) {
uart_set_line_inverse((uart_port_t)this->uart_num_, uart_set_line_inverse((uart_port_t)this->uart_num_,
UART_SIGNAL_TXD_INV | UART_SIGNAL_RXD_INV); UART_SIGNAL_TXD_INV | UART_SIGNAL_RXD_INV);
} }
this->is_initialized_ = true; this->is_initialized_ = true;
ESP_LOGD(TAG, "Hardware UART and RMT initialized on TX=%d RX=%d", tx_pin, ESP_LOGD(TAG, "Hardware UART and RMT initialized on TX=%d RX=%d", tx_pin,
rx_pin); rx_pin);
} }
void RatgdoUART::transmit_secplus2_preamble() void RatgdoUART::transmit_secplus2_preamble() {
{ if (!this->is_initialized_) return;
if (!this->is_initialized_)
return;
// Switch GPIO matrix from UART TX to RMT output // Switch GPIO matrix from UART TX to RMT output
esp_rom_gpio_connect_out_signal(this->tx_pin_, RMT_SIG_OUT0_IDX + this->rmt_channel_id_, esp_rom_gpio_connect_out_signal(
false, false); this->tx_pin_, RMT_SIG_OUT0_IDX + this->rmt_channel_id_, false, false);
esp_rom_delay_us(SIGNAL_SETTLE_US); esp_rom_delay_us(SIGNAL_SETTLE_US);
// Indicate the start of a frame by pulling the 12V line low for at least // Indicate the start of a frame by pulling the 12V line low for at least
// 1 byte followed by one STOP bit, which indicates to the receiving end // 1 byte followed by one STOP bit, which indicates to the receiving end
// that the start of the message follows. // that the start of the message follows.
// The output pin controls a transistor, so the logic is inverted: // The output pin controls a transistor, so the logic is inverted:
// RMT level 1 (HIGH) pulls the wire low, level 0 (LOW) lets it float high. // RMT level 1 (HIGH) pulls the wire low, level 0 (LOW) lets it float high.
rmt_symbol_word_t symbols[1]; rmt_symbol_word_t symbols[1];
symbols[0].duration0 = PREAMBLE_DURATION_US; symbols[0].duration0 = PREAMBLE_DURATION_US;
symbols[0].level0 = 1; symbols[0].level0 = 1;
symbols[0].duration1 = PREAMBLE_MARK_US; symbols[0].duration1 = PREAMBLE_MARK_US;
symbols[0].level1 = 0; symbols[0].level1 = 0;
rmt_transmit_config_t transmit_config = { }; rmt_transmit_config_t transmit_config = {};
transmit_config.loop_count = 0; transmit_config.loop_count = 0;
rmt_transmit(this->rmt_chan_handle_, this->rmt_copy_encoder_, symbols, rmt_transmit(this->rmt_chan_handle_, this->rmt_copy_encoder_, symbols,
sizeof(symbols), &transmit_config); sizeof(symbols), &transmit_config);
rmt_tx_wait_all_done(this->rmt_chan_handle_, -1); rmt_tx_wait_all_done(this->rmt_chan_handle_, -1);
// Switch GPIO matrix back to UART TX // Switch GPIO matrix back to UART TX
esp_rom_gpio_connect_out_signal(this->tx_pin_, UART_TX_SIGNAL_IDX, false, false); esp_rom_gpio_connect_out_signal(this->tx_pin_, UART_TX_SIGNAL_IDX, false,
esp_rom_delay_us(SIGNAL_SETTLE_US); false);
esp_rom_delay_us(SIGNAL_SETTLE_US);
} }
void RatgdoUART::write(const uint8_t* data, size_t len) void RatgdoUART::write(const uint8_t* data, size_t len) {
{ if (this->is_initialized_) {
if (this->is_initialized_) { uart_write_bytes((uart_port_t)this->uart_num_, (const char*)data, len);
uart_write_bytes((uart_port_t)this->uart_num_, (const char*)data, len); uart_wait_tx_done((uart_port_t)this->uart_num_, portMAX_DELAY);
uart_wait_tx_done((uart_port_t)this->uart_num_, portMAX_DELAY); }
}
} }
void RatgdoUART::write(uint8_t data) { write(&data, 1); } void RatgdoUART::write(uint8_t data) { write(&data, 1); }
int RatgdoUART::available() int RatgdoUART::available() {
{ if (!this->is_initialized_) return 0;
if (!this->is_initialized_) size_t length = 0;
return 0; uart_get_buffered_data_len((uart_port_t)this->uart_num_, &length);
size_t length = 0; return length;
uart_get_buffered_data_len((uart_port_t)this->uart_num_, &length);
return length;
} }
int RatgdoUART::read() int RatgdoUART::read() {
{ if (!this->is_initialized_) return -1;
if (!this->is_initialized_) uint8_t data = 0;
return -1; int len = uart_read_bytes((uart_port_t)this->uart_num_, &data, 1, 0);
uint8_t data = 0; if (len > 0) {
int len = uart_read_bytes((uart_port_t)this->uart_num_, &data, 1, 0); return data;
if (len > 0) { }
return data; return -1;
}
return -1;
} }
void RatgdoUART::on_shutdown() void RatgdoUART::on_shutdown() {
{ if (this->is_initialized_) {
if (this->is_initialized_) { // Unmap the matrix output signal so that UART peripheral resets do not
// Unmap the matrix output signal so that UART peripheral resets do not // pull the hardware line dominant.
// pull the hardware line dominant. esp_rom_gpio_connect_out_signal(this->tx_pin_, SIG_GPIO_OUT_IDX, false,
esp_rom_gpio_connect_out_signal(this->tx_pin_, SIG_GPIO_OUT_IDX, false, false); false);
gpio_set_direction((gpio_num_t)this->tx_pin_, GPIO_MODE_INPUT); gpio_set_direction((gpio_num_t)this->tx_pin_, GPIO_MODE_INPUT);
gpio_set_direction((gpio_num_t)this->rx_pin_, GPIO_MODE_INPUT); gpio_set_direction((gpio_num_t)this->rx_pin_, GPIO_MODE_INPUT);
} }
} }
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+33 -33
View File
@@ -1,53 +1,53 @@
#pragma once #pragma once
#include "esphome/core/defines.h"
#include <driver/rmt_tx.h> #include <driver/rmt_tx.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "esphome/core/defines.h"
namespace esphome::ratgdo { namespace esphome::ratgdo {
enum RatgdoUARTConfig { enum RatgdoUARTConfig {
RATGDO_UART_8N1, RATGDO_UART_8N1,
RATGDO_UART_8E1, RATGDO_UART_8E1,
}; };
class RatgdoUART { class RatgdoUART {
public: public:
RatgdoUART(); RatgdoUART();
~RatgdoUART(); ~RatgdoUART();
void begin(int baud, RatgdoUARTConfig config, int rx_pin, int tx_pin, void begin(int baud, RatgdoUARTConfig config, int rx_pin, int tx_pin,
bool invert); bool invert);
void write(const uint8_t* data, size_t len); void write(const uint8_t* data, size_t len);
void write(uint8_t data); void write(uint8_t data);
int available(); int available();
int read(); int read();
void enableIntTx(bool enable) { } void enableIntTx(bool enable) {}
void enableAutoBaud(bool enable) { } void enableAutoBaud(bool enable) {}
int baudRate() { return this->baud_; } int baudRate() { return this->baud_; }
// Sends the SecPlus 2.0 preamble using RMT // Sends the SecPlus 2.0 preamble using RMT
void transmit_secplus2_preamble(); void transmit_secplus2_preamble();
void on_shutdown(); void on_shutdown();
private: private:
// Pointers (4 bytes on 32-bit) // Pointers (4 bytes on 32-bit)
rmt_channel_handle_t rmt_chan_handle_ { nullptr }; rmt_channel_handle_t rmt_chan_handle_{nullptr};
rmt_encoder_handle_t rmt_copy_encoder_ { nullptr }; rmt_encoder_handle_t rmt_copy_encoder_{nullptr};
// 4-byte members // 4-byte members
int tx_pin_ { -1 }; int tx_pin_{-1};
int rx_pin_ { -1 }; int rx_pin_{-1};
int baud_ { 9600 }; int baud_{9600};
int uart_num_ { -1 }; int uart_num_{-1};
int rmt_channel_id_ { 0 }; int rmt_channel_id_{0};
// 1-byte members packed at the end // 1-byte members packed at the end
bool inverted_ { true }; bool inverted_{true};
bool is_initialized_ { false }; bool is_initialized_{false};
}; };
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+380 -362
View File
@@ -1,11 +1,11 @@
#include "secplus2.h" #include "secplus2.h"
#include "ratgdo.h"
#include "esphome/core/gpio.h" #include "esphome/core/gpio.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/scheduler.h" #include "esphome/core/scheduler.h"
#include "ratgdo.h"
extern "C" { extern "C" {
#include "secplus.h" #include "secplus.h"
@@ -14,400 +14,418 @@ extern "C" {
namespace esphome::ratgdo { namespace esphome::ratgdo {
namespace secplus2 { namespace secplus2 {
using namespace scheduler_ids; using namespace scheduler_ids;
// MAX_CODES_WITHOUT_FLASH_WRITE is a bit of a guess // MAX_CODES_WITHOUT_FLASH_WRITE is a bit of a guess
// since we write the flash at most every every 1min // since we write the flash at most every every 1min
// //
// We want the rolling counter to be high enough that the // We want the rolling counter to be high enough that the
// GDO will accept the command after an unexpected reboot // GDO will accept the command after an unexpected reboot
// that did not save the counter to flash in time which // that did not save the counter to flash in time which
// results in the rolling counter being behind what the GDO // results in the rolling counter being behind what the GDO
// expects. // expects.
static const uint8_t MAX_CODES_WITHOUT_FLASH_WRITE = 60; static const uint8_t MAX_CODES_WITHOUT_FLASH_WRITE = 60;
static const char* const TAG = "ratgdo_secplus2"; static const char* const TAG = "ratgdo_secplus2";
void Secplus2::setup(RATGDOComponent* ratgdo, Scheduler* scheduler, InternalGPIOPin* rx_pin, InternalGPIOPin* tx_pin) void Secplus2::setup(RATGDOComponent* ratgdo, Scheduler* scheduler,
{ InternalGPIOPin* rx_pin, InternalGPIOPin* tx_pin) {
this->ratgdo_ = ratgdo; this->ratgdo_ = ratgdo;
this->scheduler_ = scheduler; this->scheduler_ = scheduler;
this->tx_pin_ = tx_pin; this->tx_pin_ = tx_pin;
this->rx_pin_ = rx_pin; this->rx_pin_ = rx_pin;
if (mqtt::global_mqtt_client != nullptr) { if (mqtt::global_mqtt_client != nullptr) {
this->mqtt_rolling_code_topic_ = mqtt::global_mqtt_client->get_topic_prefix() + "/gdo/rolling_code"; this->mqtt_rolling_code_topic_ =
this->mqtt_client_id_topic_ = mqtt::global_mqtt_client->get_topic_prefix() + "/gdo/client_id"; mqtt::global_mqtt_client->get_topic_prefix() + "/gdo/rolling_code";
} this->mqtt_client_id_topic_ =
mqtt::global_mqtt_client->get_topic_prefix() + "/gdo/client_id";
}
this->client_id_pref_ = global_preferences->make_preference<uint32_t>(3497851610U); // fnv1_hash("ratgdo_client_id") this->client_id_pref_ = global_preferences->make_preference<uint32_t>(
uint32_t stored_client_id; 3497851610U); // fnv1_hash("ratgdo_client_id")
if (this->client_id_pref_.load(&stored_client_id)) { uint32_t stored_client_id;
this->client_id_ = stored_client_id; if (this->client_id_pref_.load(&stored_client_id)) {
ESP_LOGI(TAG, "Restored Client ID from flash: 0x%04X", (unsigned)this->client_id_); this->client_id_ = stored_client_id;
if (mqtt::global_mqtt_client != nullptr) { ESP_LOGI(TAG, "Restored Client ID from flash: 0x%04X",
mqtt::global_mqtt_client->publish(this->mqtt_client_id_topic_, std::to_string(this->client_id_), 0, true); (unsigned)this->client_id_);
if (mqtt::global_mqtt_client != nullptr) {
mqtt::global_mqtt_client->publish(this->mqtt_client_id_topic_,
std::to_string(this->client_id_), 0,
true);
}
} else if (mqtt::global_mqtt_client != nullptr) {
ESP_LOGI(TAG, "No Client ID in flash, waiting for MQTT: %s",
this->mqtt_client_id_topic_.c_str());
mqtt::global_mqtt_client->subscribe(
this->mqtt_client_id_topic_,
[this](const std::string& topic, const std::string& payload) {
if (this->client_id_ == 0x539) {
uint32_t cid = strtoul(payload.c_str(), nullptr, 10);
if (cid != 0) {
ESP_LOGI(TAG, "Received Client ID from MQTT: 0x%04X",
(unsigned)cid);
this->set_client_id(cid);
} }
} else if (mqtt::global_mqtt_client != nullptr) { }
ESP_LOGI(TAG, "No Client ID in flash, waiting for MQTT: %s", this->mqtt_client_id_topic_.c_str()); },
mqtt::global_mqtt_client->subscribe( 1);
this->mqtt_client_id_topic_, } else {
[this](const std::string& topic, const std::string& payload) { // Generate a unique ID on first boot.
if (this->client_id_ == 0x539) { // We use a range that avoids common reserved IDs.
uint32_t cid = strtoul(payload.c_str(), nullptr, 10); uint32_t new_id = (random_uint32() & 0xFFFFF) | 0x539;
if (cid != 0) { this->client_id_ = new_id;
ESP_LOGI(TAG, "Received Client ID from MQTT: 0x%04X", (unsigned)cid); this->client_id_pref_.save(&new_id);
this->set_client_id(cid); ESP_LOGI(TAG, "Generated new unique Client ID: 0x%04X", (unsigned)new_id);
} }
}
},
1);
} else {
// Generate a unique ID on first boot.
// We use a range that avoids common reserved IDs.
uint32_t new_id = (random_uint32() & 0xFFFFF) | 0x539;
this->client_id_ = new_id;
this->client_id_pref_.save(&new_id);
ESP_LOGI(TAG, "Generated new unique Client ID: 0x%04X", (unsigned)new_id);
}
this->rolling_code_pref_ = global_preferences->make_preference<uint32_t>(1868352652U); // fnv1_hash("ratgdo_rolling_code") this->rolling_code_pref_ = global_preferences->make_preference<uint32_t>(
uint32_t rolling_code; 1868352652U); // fnv1_hash("ratgdo_rolling_code")
if (this->rolling_code_pref_.load(&rolling_code)) { uint32_t rolling_code;
this->rolling_code_counter_ = rolling_code; if (this->rolling_code_pref_.load(&rolling_code)) {
ESP_LOGI(TAG, "Restored rolling code from flash: %u", rolling_code); this->rolling_code_counter_ = rolling_code;
if (mqtt::global_mqtt_client != nullptr) { ESP_LOGI(TAG, "Restored rolling code from flash: %u", rolling_code);
mqtt::global_mqtt_client->publish(this->mqtt_rolling_code_topic_, std::to_string(rolling_code), 0, true); if (mqtt::global_mqtt_client != nullptr) {
mqtt::global_mqtt_client->publish(this->mqtt_rolling_code_topic_,
std::to_string(rolling_code), 0, true);
}
} else if (mqtt::global_mqtt_client != nullptr) {
ESP_LOGI(TAG, "No rolling code in flash, waiting for MQTT: %s",
this->mqtt_rolling_code_topic_.c_str());
mqtt::global_mqtt_client->subscribe(
this->mqtt_rolling_code_topic_,
[this](const std::string& topic, const std::string& payload) {
if (*this->rolling_code_counter_ == 0) {
uint32_t rc = strtoul(payload.c_str(), nullptr, 10);
if (rc > 0) {
ESP_LOGI(TAG, "Received rolling code from MQTT: %u", rc);
this->set_rolling_code_counter(rc);
} }
} else if (mqtt::global_mqtt_client != nullptr) { }
ESP_LOGI(TAG, "No rolling code in flash, waiting for MQTT: %s", this->mqtt_rolling_code_topic_.c_str()); },
mqtt::global_mqtt_client->subscribe( 1);
this->mqtt_rolling_code_topic_, } else {
[this](const std::string& topic, const std::string& payload) { rolling_code = 1;
if (*this->rolling_code_counter_ == 0) { this->rolling_code_counter_ = rolling_code;
uint32_t rc = strtoul(payload.c_str(), nullptr, 10); this->rolling_code_pref_.save(&rolling_code);
if (rc > 0) { }
ESP_LOGI(TAG, "Received rolling code from MQTT: %u", rc);
this->set_rolling_code_counter(rc);
}
}
},
1);
} else {
rolling_code = 1;
this->rolling_code_counter_ = rolling_code;
this->rolling_code_pref_.save(&rolling_code);
}
this->uart_.begin(9600, RATGDO_UART_8N1, rx_pin->get_pin(), tx_pin->get_pin(), true); this->uart_.begin(9600, RATGDO_UART_8N1, rx_pin->get_pin(), tx_pin->get_pin(),
this->uart_.enableIntTx(false); true);
this->uart_.enableAutoBaud(true); this->uart_.enableIntTx(false);
this->uart_.enableAutoBaud(true);
}
void Secplus2::loop() {
if (this->flags_.transmit_pending) {
if (!this->transmit_packet()) {
return;
} }
}
void Secplus2::loop() auto cmd = this->read_command();
{ if (cmd) {
if (this->flags_.transmit_pending) { this->handle_command(*cmd);
if (!this->transmit_packet()) { }
return; }
}
}
auto cmd = this->read_command(); void Secplus2::dump_config() {
if (cmd) { ESP_LOGCONFIG(TAG, " Rolling Code Counter: %d",
this->handle_command(*cmd); *this->rolling_code_counter_);
} ESP_LOGCONFIG(TAG, " Client ID: %d", this->client_id_);
} ESP_LOGCONFIG(TAG, " Protocol: SEC+ v2");
}
void Secplus2::dump_config() void Secplus2::on_shutdown() { this->uart_.on_shutdown(); }
{
ESP_LOGCONFIG(TAG, " Rolling Code Counter: %d", *this->rolling_code_counter_);
ESP_LOGCONFIG(TAG, " Client ID: %d", this->client_id_);
ESP_LOGCONFIG(TAG, " Protocol: SEC+ v2");
}
void Secplus2::on_shutdown() void Secplus2::sync_helper(uint32_t start, uint32_t delay, uint8_t tries) {
{ if (tries == 0 || *this->ratgdo_->door_state == DoorState::UNKNOWN) {
this->uart_.on_shutdown(); ESP_LOGD(TAG, "Sync: querying status (attempt %d)...", tries);
} this->query_status();
} else if (tries == 1 || *this->ratgdo_->openings == 0) {
ESP_LOGD(TAG, "Sync: querying openings (attempt %d)...", tries);
this->query_openings();
} else {
ESP_LOGD(TAG, "Sync successful!");
this->ratgdo_->synced = true;
this->ratgdo_->sync_failed = false;
return;
}
void Secplus2::sync_helper(uint32_t start, uint32_t delay, uint8_t tries) // not sync-ed after 30s, notify failure
{ if (millis() - start > 30000) {
if (tries == 0 || *this->ratgdo_->door_state == DoorState::UNKNOWN) { ESP_LOGW(TAG, "Triggering sync failed actions.");
ESP_LOGD(TAG, "Sync: querying status (attempt %d)...", tries); this->ratgdo_->synced = false;
this->query_status(); this->ratgdo_->sync_failed = true;
} else if (tries == 1 || *this->ratgdo_->openings == 0) { } else {
ESP_LOGD(TAG, "Sync: querying openings (attempt %d)...", tries); // Use a slightly longer delay between queries during sync to avoid bus
this->query_openings(); // saturation
} else { uint32_t next_delay = (tries < 5) ? 1000 : 2000;
ESP_LOGD(TAG, "Sync successful!"); this->scheduler_->set_timeout(this->ratgdo_, TIMEOUT_SYNC, next_delay,
this->ratgdo_->synced = true; [this, start, next_delay, tries]() {
this->ratgdo_->sync_failed = false; this->sync_helper(start, next_delay,
return; tries + 1);
} });
};
}
// not sync-ed after 30s, notify failure void Secplus2::sync() {
if (millis() - start > 30000) { ESP_LOGD(TAG, "Starting sync...");
ESP_LOGW(TAG, "Triggering sync failed actions."); this->ratgdo_->synced = false;
this->ratgdo_->synced = false; this->scheduler_->cancel_timeout(this->ratgdo_, TIMEOUT_SYNC);
this->ratgdo_->sync_failed = true; this->sync_helper(millis(), 500, 0);
} else { }
// Use a slightly longer delay between queries during sync to avoid bus saturation
uint32_t next_delay = (tries < 5) ? 1000 : 2000;
this->scheduler_->set_timeout(this->ratgdo_, TIMEOUT_SYNC, next_delay, [this, start, next_delay, tries]() {
this->sync_helper(start, next_delay, tries + 1);
});
};
}
void Secplus2::sync() void Secplus2::light_action(LightAction action) {
{ if (action == LightAction::UNKNOWN) {
ESP_LOGD(TAG, "Starting sync..."); return;
this->ratgdo_->synced = false; }
this->scheduler_->cancel_timeout(this->ratgdo_, TIMEOUT_SYNC); this->send_command(Command(CommandType::LIGHT, static_cast<uint8_t>(action)));
this->sync_helper(millis(), 500, 0); }
}
void Secplus2::light_action(LightAction action) void Secplus2::lock_action(LockAction action) {
{ if (action == LockAction::UNKNOWN) {
if (action == LightAction::UNKNOWN) { return;
return; }
} this->send_command(Command(CommandType::LOCK, static_cast<uint8_t>(action)));
this->send_command(Command(CommandType::LIGHT, static_cast<uint8_t>(action))); }
}
void Secplus2::lock_action(LockAction action) void Secplus2::door_action(DoorAction action) {
{ if (action == DoorAction::UNKNOWN) {
if (action == LockAction::UNKNOWN) { return;
return; }
} this->door_command(action);
this->send_command(Command(CommandType::LOCK, static_cast<uint8_t>(action))); }
}
void Secplus2::door_action(DoorAction action) void Secplus2::door_command(DoorAction action) {
{ this->send_command(
if (action == DoorAction::UNKNOWN) { Command(CommandType::DOOR_ACTION, static_cast<uint8_t>(action), 1, 1),
return; IncrementRollingCode::NO, [this, action]() {
} this->ratgdo_->set_timeout(150, [this, action] {
this->door_command(action); this->send_command(Command(CommandType::DOOR_ACTION,
} static_cast<uint8_t>(action), 0, 1));
void Secplus2::door_command(DoorAction action)
{
this->send_command(Command(CommandType::DOOR_ACTION, static_cast<uint8_t>(action), 1, 1), IncrementRollingCode::NO, [this, action]() {
this->ratgdo_->set_timeout(150, [this, action] {
this->send_command(Command(CommandType::DOOR_ACTION, static_cast<uint8_t>(action), 0, 1));
});
}); });
});
}
void Secplus2::query_status() { this->send_command(CommandType::GET_STATUS); }
void Secplus2::query_openings() {
this->send_command(CommandType::GET_OPENINGS);
}
optional<Command> Secplus2::read_command() {
while (this->uart_.available()) {
uint8_t ser_byte = this->uart_.read();
this->rx_last_read_ = millis();
// Shift byte into preamble window
this->rx_msg_start_ = ((this->rx_msg_start_ << 8) | ser_byte) & 0xffffff;
if (this->rx_msg_start_ == 0x550100) {
// Found a preamble! Reset buffer and start fresh.
if (this->flags_.rx_reading_msg) {
ESP_LOGV(TAG, "Preamble detected inside message, resetting...");
}
this->rx_packet_[0] = 0x55;
this->rx_packet_[1] = 0x01;
this->rx_packet_[2] = 0x00;
this->rx_byte_count_ = 3;
this->flags_.rx_reading_msg = true;
continue;
} }
void Secplus2::query_status() if (this->flags_.rx_reading_msg) {
{ this->rx_packet_[this->rx_byte_count_] = ser_byte;
this->send_command(CommandType::GET_STATUS); this->rx_byte_count_++;
if (this->rx_byte_count_ == PACKET_LENGTH) {
this->flags_.rx_reading_msg = false;
this->rx_byte_count_ = 0;
this->rx_msg_start_ =
0; // clear window to prevent immediate re-trigger
this->print_packet(LOG_STR("Received packet"), this->rx_packet_);
return this->decode_packet(this->rx_packet_);
}
} }
}
void Secplus2::query_openings() if (this->flags_.rx_reading_msg && (millis() - this->rx_last_read_ > 100)) {
{ // if we have a partial packet and it's been over 100ms since last byte was
this->send_command(CommandType::GET_OPENINGS); // read, the rest is not coming, discard it.
ESP_LOGW(TAG, "Discard incomplete packet, length: %d",
this->rx_byte_count_);
this->flags_.rx_reading_msg = false;
this->rx_byte_count_ = 0;
this->rx_msg_start_ = 0;
}
return {};
}
void Secplus2::print_packet(const esphome::LogString* prefix,
const WirePacket& packet) const {
constexpr size_t hex_size = format_hex_pretty_size(PACKET_LENGTH);
char hex_buf[hex_size];
ESP_LOGD(TAG, "%s: [%s]", LOG_STR_ARG(prefix),
format_hex_pretty_to(hex_buf, packet, PACKET_LENGTH));
}
optional<Command> Secplus2::decode_packet(const WirePacket& packet) const {
uint32_t rolling = 0;
uint64_t fixed = 0;
uint32_t data = 0;
int err = decode_wireline(packet, &rolling, &fixed, &data);
if (err < 0) {
ESP_LOGW(TAG, "Decode failed (parity error or invalid frame)");
return {};
}
uint16_t cmd = ((fixed >> 24) & 0xf00) | (data & 0xff);
data &= ~0xf000; // clear parity nibble
if ((fixed & 0xFFFFFFFF) == this->client_id_) { // my commands
ESP_LOGD(TAG,
" mine: rolling=%07" PRIx32 " fixed=%010" PRIx64
" data=%08" PRIx32,
rolling, fixed, data);
return {};
} else {
ESP_LOGD(TAG,
" rolling=%07" PRIx32 " fixed=%010" PRIx64 " data=%08" PRIx32,
rolling, fixed, data);
}
CommandType cmd_type = to_CommandType(cmd, CommandType::UNKNOWN);
uint8_t nibble = (data >> 8) & 0xff;
uint8_t byte1 = (data >> 16) & 0xff;
uint8_t byte2 = (data >> 24) & 0xff;
ESP_LOGD(TAG, " cmd=%03x (%s) byte2=%02x byte1=%02x nibble=%01x", cmd,
LOG_STR_ARG(CommandType_to_string(cmd_type)), byte2, byte1, nibble);
return Command{cmd_type, nibble, byte1, byte2};
}
void Secplus2::handle_command(const Command& cmd) {
ESP_LOGD(TAG, "Handle command: %s (nibble=%01x byte1=%02x byte2=%02x)",
LOG_STR_ARG(CommandType_to_string(cmd.type)), cmd.nibble, cmd.byte1,
cmd.byte2);
if (cmd.type == CommandType::STATUS) {
this->ratgdo_->received(to_DoorState(cmd.nibble, DoorState::UNKNOWN));
this->ratgdo_->received(
to_LightState((cmd.byte2 >> 1) & 1, LightState::UNKNOWN));
this->ratgdo_->received(to_LockState((cmd.byte2 & 1), LockState::UNKNOWN));
} else if (cmd.type == CommandType::LIGHT) {
this->ratgdo_->received(to_LightAction(cmd.nibble, LightAction::UNKNOWN));
} else if (cmd.type == CommandType::OPENINGS) {
this->ratgdo_->received(Openings{
static_cast<uint16_t>((cmd.byte1 << 8) | cmd.byte2), cmd.nibble});
}
}
void Secplus2::send_command(Command command, IncrementRollingCode increment) {
{
uint8_t data[] = {command.byte2, command.byte1, command.nibble};
constexpr size_t hex_size = format_hex_pretty_size(3);
char hex[hex_size];
ESP_LOGD(TAG, "Send command: %s, data: %s",
LOG_STR_ARG(CommandType_to_string(command.type)),
format_hex_pretty_to(hex, data, 3));
}
if (!this->flags_.transmit_pending) { // have an untransmitted packet
this->encode_packet(command, this->tx_packet_);
if (increment == IncrementRollingCode::YES) {
this->increment_rolling_code_counter();
} }
} else {
optional<Command> Secplus2::read_command() // unlikely this would happed (unless not connected to GDO), we're ensuring
{ // any pending packet is transmitted each loop before doing anyting else
while (this->uart_.available()) { if (this->transmit_pending_start_ > 0) {
uint8_t ser_byte = this->uart_.read(); ESP_LOGW(TAG, "Have untransmitted packet, ignoring command: %s",
this->rx_last_read_ = millis(); LOG_STR_ARG(CommandType_to_string(command.type)));
} else {
// Shift byte into preamble window ESP_LOGW(TAG, "Not connected to GDO, ignoring command: %s",
this->rx_msg_start_ = ((this->rx_msg_start_ << 8) | ser_byte) & 0xffffff; LOG_STR_ARG(CommandType_to_string(command.type)));
if (this->rx_msg_start_ == 0x550100) {
// Found a preamble! Reset buffer and start fresh.
if (this->flags_.rx_reading_msg) {
ESP_LOGV(TAG, "Preamble detected inside message, resetting...");
}
this->rx_packet_[0] = 0x55;
this->rx_packet_[1] = 0x01;
this->rx_packet_[2] = 0x00;
this->rx_byte_count_ = 3;
this->flags_.rx_reading_msg = true;
continue;
}
if (this->flags_.rx_reading_msg) {
this->rx_packet_[this->rx_byte_count_] = ser_byte;
this->rx_byte_count_++;
if (this->rx_byte_count_ == PACKET_LENGTH) {
this->flags_.rx_reading_msg = false;
this->rx_byte_count_ = 0;
this->rx_msg_start_ = 0; // clear window to prevent immediate re-trigger
this->print_packet(LOG_STR("Received packet"), this->rx_packet_);
return this->decode_packet(this->rx_packet_);
}
}
}
if (this->flags_.rx_reading_msg && (millis() - this->rx_last_read_ > 100)) {
// if we have a partial packet and it's been over 100ms since last byte was read,
// the rest is not coming, discard it.
ESP_LOGW(TAG, "Discard incomplete packet, length: %d", this->rx_byte_count_);
this->flags_.rx_reading_msg = false;
this->rx_byte_count_ = 0;
this->rx_msg_start_ = 0;
}
return { };
} }
}
this->transmit_packet();
}
void Secplus2::print_packet(const esphome::LogString* prefix, const WirePacket& packet) const void Secplus2::encode_packet(Command command, WirePacket& packet) {
{ auto cmd = static_cast<uint64_t>(command.type);
constexpr size_t hex_size = format_hex_pretty_size(PACKET_LENGTH); uint64_t fixed = ((cmd & ~0xff) << 24) | this->client_id_;
char hex_buf[hex_size]; uint32_t data = (static_cast<uint64_t>(command.byte2) << 24) |
ESP_LOGD(TAG, "%s: [%s]", LOG_STR_ARG(prefix), format_hex_pretty_to(hex_buf, packet, PACKET_LENGTH)); (static_cast<uint64_t>(command.byte1) << 16) |
(static_cast<uint64_t>(command.nibble) << 8) | (cmd & 0xff);
ESP_LOGD(TAG,
" transmit: rolling=%07" PRIx32 " fixed=%010" PRIx64
" data=%08" PRIx32,
*this->rolling_code_counter_, fixed, data);
encode_wireline(*this->rolling_code_counter_, fixed, data, packet);
}
bool Secplus2::transmit_packet() {
auto now = micros();
while (micros() - now < 1300) {
if (this->rx_pin_->digital_read()) {
if (!this->flags_.transmit_pending) {
this->flags_.transmit_pending = true;
this->transmit_pending_start_ = millis();
ESP_LOGD(TAG, "Collision detected, waiting to send packet");
} else if (millis() - this->transmit_pending_start_ >= 5000) {
this->transmit_pending_start_ =
0; // to indicate GDO not connected state
}
return false;
} }
delayMicroseconds(100);
}
optional<Command> Secplus2::decode_packet(const WirePacket& packet) const this->print_packet(LOG_STR("Sending packet"), this->tx_packet_);
{
uint32_t rolling = 0;
uint64_t fixed = 0;
uint32_t data = 0;
int err = decode_wireline(packet, &rolling, &fixed, &data); this->uart_.transmit_secplus2_preamble();
if (err < 0) { this->uart_.write(this->tx_packet_, PACKET_LENGTH);
ESP_LOGW(TAG, "Decode failed (parity error or invalid frame)");
return { };
}
uint16_t cmd = ((fixed >> 24) & 0xf00) | (data & 0xff); this->flags_.transmit_pending = false;
data &= ~0xf000; // clear parity nibble this->transmit_pending_start_ = 0;
this->on_command_sent_.trigger();
return true;
}
if ((fixed & 0xFFFFFFFF) == this->client_id_) { // my commands void Secplus2::increment_rolling_code_counter(int delta) {
ESP_LOGD(TAG, " mine: rolling=%07" PRIx32 " fixed=%010" PRIx64 " data=%08" PRIx32, rolling, fixed, data); uint32_t counter = (*this->rolling_code_counter_ + delta) & 0xfffffff;
return { }; this->rolling_code_counter_ = counter;
} else { this->rolling_code_pref_.save(&counter);
ESP_LOGD(TAG, " rolling=%07" PRIx32 " fixed=%010" PRIx64 " data=%08" PRIx32, rolling, fixed, data); if (mqtt::global_mqtt_client != nullptr) {
} mqtt::global_mqtt_client->publish(this->mqtt_rolling_code_topic_,
std::to_string(counter), 0, true);
}
}
CommandType cmd_type = to_CommandType(cmd, CommandType::UNKNOWN); void Secplus2::set_rolling_code_counter(uint32_t counter) {
uint8_t nibble = (data >> 8) & 0xff; ESP_LOGV(TAG, "Set rolling code counter to %d", counter);
uint8_t byte1 = (data >> 16) & 0xff; this->rolling_code_counter_ = counter;
uint8_t byte2 = (data >> 24) & 0xff; this->rolling_code_pref_.save(&counter);
if (mqtt::global_mqtt_client != nullptr) {
mqtt::global_mqtt_client->publish(this->mqtt_rolling_code_topic_,
std::to_string(counter), 0, true);
}
}
ESP_LOGD(TAG, " cmd=%03x (%s) byte2=%02x byte1=%02x nibble=%01x", cmd, LOG_STR_ARG(CommandType_to_string(cmd_type)), byte2, byte1, nibble); void Secplus2::set_client_id(uint64_t client_id) {
uint32_t cid = client_id & 0xFFFFFFFF;
this->client_id_ = cid;
this->client_id_pref_.save(&cid);
if (mqtt::global_mqtt_client != nullptr) {
mqtt::global_mqtt_client->publish(this->mqtt_client_id_topic_,
std::to_string(cid), 0, true);
}
}
return Command { cmd_type, nibble, byte1, byte2 }; } // namespace secplus2
} } // namespace esphome::ratgdo
void Secplus2::handle_command(const Command& cmd)
{
ESP_LOGD(TAG, "Handle command: %s (nibble=%01x byte1=%02x byte2=%02x)", LOG_STR_ARG(CommandType_to_string(cmd.type)), cmd.nibble, cmd.byte1, cmd.byte2);
if (cmd.type == CommandType::STATUS) {
this->ratgdo_->received(to_DoorState(cmd.nibble, DoorState::UNKNOWN));
this->ratgdo_->received(to_LightState((cmd.byte2 >> 1) & 1, LightState::UNKNOWN));
this->ratgdo_->received(to_LockState((cmd.byte2 & 1), LockState::UNKNOWN));
} else if (cmd.type == CommandType::LIGHT) {
this->ratgdo_->received(to_LightAction(cmd.nibble, LightAction::UNKNOWN));
} else if (cmd.type == CommandType::OPENINGS) {
this->ratgdo_->received(Openings { static_cast<uint16_t>((cmd.byte1 << 8) | cmd.byte2), cmd.nibble });
}
}
void Secplus2::send_command(Command command, IncrementRollingCode increment)
{
{
uint8_t data[] = { command.byte2, command.byte1, command.nibble };
constexpr size_t hex_size = format_hex_pretty_size(3);
char hex[hex_size];
ESP_LOGD(TAG, "Send command: %s, data: %s", LOG_STR_ARG(CommandType_to_string(command.type)), format_hex_pretty_to(hex, data, 3));
}
if (!this->flags_.transmit_pending) { // have an untransmitted packet
this->encode_packet(command, this->tx_packet_);
if (increment == IncrementRollingCode::YES) {
this->increment_rolling_code_counter();
}
} else {
// unlikely this would happed (unless not connected to GDO), we're ensuring any pending packet
// is transmitted each loop before doing anyting else
if (this->transmit_pending_start_ > 0) {
ESP_LOGW(TAG, "Have untransmitted packet, ignoring command: %s", LOG_STR_ARG(CommandType_to_string(command.type)));
} else {
ESP_LOGW(TAG, "Not connected to GDO, ignoring command: %s", LOG_STR_ARG(CommandType_to_string(command.type)));
}
}
this->transmit_packet();
}
void Secplus2::encode_packet(Command command, WirePacket& packet)
{
auto cmd = static_cast<uint64_t>(command.type);
uint64_t fixed = ((cmd & ~0xff) << 24) | this->client_id_;
uint32_t data = (static_cast<uint64_t>(command.byte2) << 24) | (static_cast<uint64_t>(command.byte1) << 16) | (static_cast<uint64_t>(command.nibble) << 8) | (cmd & 0xff);
ESP_LOGD(TAG, " transmit: rolling=%07" PRIx32 " fixed=%010" PRIx64 " data=%08" PRIx32, *this->rolling_code_counter_, fixed, data);
encode_wireline(*this->rolling_code_counter_, fixed, data, packet);
}
bool Secplus2::transmit_packet()
{
auto now = micros();
while (micros() - now < 1300) {
if (this->rx_pin_->digital_read()) {
if (!this->flags_.transmit_pending) {
this->flags_.transmit_pending = true;
this->transmit_pending_start_ = millis();
ESP_LOGD(TAG, "Collision detected, waiting to send packet");
} else if (millis() - this->transmit_pending_start_ >= 5000) {
this->transmit_pending_start_ = 0; // to indicate GDO not connected state
}
return false;
}
delayMicroseconds(100);
}
this->print_packet(LOG_STR("Sending packet"), this->tx_packet_);
this->uart_.transmit_secplus2_preamble();
this->uart_.write(this->tx_packet_, PACKET_LENGTH);
this->flags_.transmit_pending = false;
this->transmit_pending_start_ = 0;
this->on_command_sent_.trigger();
return true;
}
void Secplus2::increment_rolling_code_counter(int delta)
{
uint32_t counter = (*this->rolling_code_counter_ + delta) & 0xfffffff;
this->rolling_code_counter_ = counter;
this->rolling_code_pref_.save(&counter);
if (mqtt::global_mqtt_client != nullptr) {
mqtt::global_mqtt_client->publish(this->mqtt_rolling_code_topic_, std::to_string(counter), 0, true);
}
}
void Secplus2::set_rolling_code_counter(uint32_t counter)
{
ESP_LOGV(TAG, "Set rolling code counter to %d", counter);
this->rolling_code_counter_ = counter;
this->rolling_code_pref_.save(&counter);
if (mqtt::global_mqtt_client != nullptr) {
mqtt::global_mqtt_client->publish(this->mqtt_rolling_code_topic_, std::to_string(counter), 0, true);
}
}
void Secplus2::set_client_id(uint64_t client_id)
{
uint32_t cid = client_id & 0xFFFFFFFF;
this->client_id_ = cid;
this->client_id_pref_.save(&cid);
if (mqtt::global_mqtt_client != nullptr) {
mqtt::global_mqtt_client->publish(this->mqtt_client_id_topic_, std::to_string(cid), 0, true);
}
}
} // namespace secplus2
} // namespace esphome::ratgdo
+167 -153
View File
@@ -1,183 +1,197 @@
#pragma once #pragma once
#include "esphome/core/optional.h"
#include "esphome/core/preferences.h"
#include "esphome/components/mqtt/mqtt_client.h"
#include "ratgdo_uart_esp32.h"
#include "callbacks.h" #include "callbacks.h"
#include "common.h" #include "common.h"
#include "esphome/components/mqtt/mqtt_client.h"
#include "esphome/core/optional.h"
#include "esphome/core/preferences.h"
#include "observable.h" #include "observable.h"
#include "ratgdo_state.h" #include "ratgdo_state.h"
#include "ratgdo_uart_esp32.h"
namespace esphome { namespace esphome {
class Scheduler; class Scheduler;
class InternalGPIOPin; class InternalGPIOPin;
} // namespace esphome } // namespace esphome
namespace esphome::ratgdo { namespace esphome::ratgdo {
class RATGDOComponent; class RATGDOComponent;
namespace secplus2 { namespace secplus2 {
static const uint8_t PACKET_LENGTH = 19; static const uint8_t PACKET_LENGTH = 19;
typedef uint8_t WirePacket[PACKET_LENGTH]; typedef uint8_t WirePacket[PACKET_LENGTH];
enum class CommandType : uint16_t { enum class CommandType : uint16_t {
UNKNOWN = 0x000, UNKNOWN = 0x000,
GET_STATUS = 0x080, GET_STATUS = 0x080,
STATUS = 0x081, STATUS = 0x081,
LOCK = 0x18c, LOCK = 0x18c,
DOOR_ACTION = 0x280, DOOR_ACTION = 0x280,
LIGHT = 0x281, LIGHT = 0x281,
GET_OPENINGS = 0x48b, GET_OPENINGS = 0x48b,
OPENINGS = 0x48c // openings = (byte1<<8)+byte2 OPENINGS = 0x48c // openings = (byte1<<8)+byte2
}; };
inline const char* CommandType_to_string(CommandType e) { inline const char* CommandType_to_string(CommandType e) {
switch (e) { switch (e) {
case CommandType::UNKNOWN: return "UNKNOWN"; case CommandType::UNKNOWN:
case CommandType::GET_STATUS: return "GET_STATUS"; return "UNKNOWN";
case CommandType::STATUS: return "STATUS"; case CommandType::GET_STATUS:
case CommandType::LOCK: return "LOCK"; return "GET_STATUS";
case CommandType::DOOR_ACTION: return "DOOR_ACTION"; case CommandType::STATUS:
case CommandType::LIGHT: return "LIGHT"; return "STATUS";
case CommandType::GET_OPENINGS: return "GET_OPENINGS"; case CommandType::LOCK:
case CommandType::OPENINGS: return "OPENINGS"; return "LOCK";
default: return "UNKNOWN"; case CommandType::DOOR_ACTION:
} return "DOOR_ACTION";
case CommandType::LIGHT:
return "LIGHT";
case CommandType::GET_OPENINGS:
return "GET_OPENINGS";
case CommandType::OPENINGS:
return "OPENINGS";
default:
return "UNKNOWN";
}
}
inline CommandType to_CommandType(uint16_t t, CommandType unknown) {
switch (t) {
case static_cast<uint16_t>(CommandType::UNKNOWN):
return CommandType::UNKNOWN;
case static_cast<uint16_t>(CommandType::GET_STATUS):
return CommandType::GET_STATUS;
case static_cast<uint16_t>(CommandType::STATUS):
return CommandType::STATUS;
case static_cast<uint16_t>(CommandType::LOCK):
return CommandType::LOCK;
case static_cast<uint16_t>(CommandType::DOOR_ACTION):
return CommandType::DOOR_ACTION;
case static_cast<uint16_t>(CommandType::LIGHT):
return CommandType::LIGHT;
case static_cast<uint16_t>(CommandType::GET_OPENINGS):
return CommandType::GET_OPENINGS;
case static_cast<uint16_t>(CommandType::OPENINGS):
return CommandType::OPENINGS;
default:
return unknown;
}
}
inline bool operator==(const uint16_t cmd_i, const CommandType& cmd_e) {
return cmd_i == static_cast<uint16_t>(cmd_e);
}
inline bool operator==(const CommandType& cmd_e, const uint16_t cmd_i) {
return cmd_i == static_cast<uint16_t>(cmd_e);
}
enum class IncrementRollingCode {
NO,
YES,
};
struct Command {
CommandType type;
uint8_t nibble;
uint8_t byte1;
uint8_t byte2;
Command() : type(CommandType::UNKNOWN) {}
Command(CommandType type_, uint8_t nibble_ = 0, uint8_t byte1_ = 0,
uint8_t byte2_ = 0)
: type(type_), nibble(nibble_), byte1(byte1_), byte2(byte2_) {}
};
class Secplus2 {
public:
void setup(RATGDOComponent* ratgdo, Scheduler* scheduler,
InternalGPIOPin* rx_pin, InternalGPIOPin* tx_pin);
void loop();
void dump_config();
void on_shutdown();
void sync();
void light_action(LightAction action);
void lock_action(LockAction action);
void door_action(DoorAction action);
void query_status();
void query_openings();
protected:
void increment_rolling_code_counter(int delta = 1);
void set_rolling_code_counter(uint32_t counter);
void set_client_id(uint64_t client_id);
optional<Command> read_command();
void handle_command(const Command& cmd);
void send_command(Command cmd,
IncrementRollingCode increment = IncrementRollingCode::YES);
template <typename F>
void send_command(Command cmd, IncrementRollingCode increment, F&& on_sent) {
// Only register the callback if the command will be accepted.
// If transmit_pending is set the command will be dropped, and
// a stale callback would fire when the previous pending packet
// transmits -- executing logic (e.g. the second phase of a
// door_command) at the wrong time.
//
// Register before send_command() because transmit_packet() may
// succeed immediately and call on_command_sent_.trigger() inline.
if (this->flags_.transmit_pending) {
return;
} }
this->on_command_sent_(std::forward<F>(on_sent));
this->send_command(cmd, increment);
}
void encode_packet(Command cmd, WirePacket& packet);
bool transmit_packet();
inline CommandType to_CommandType(uint16_t t, CommandType unknown) { void door_command(DoorAction action);
switch (t) {
case static_cast<uint16_t>(CommandType::UNKNOWN): return CommandType::UNKNOWN;
case static_cast<uint16_t>(CommandType::GET_STATUS): return CommandType::GET_STATUS;
case static_cast<uint16_t>(CommandType::STATUS): return CommandType::STATUS;
case static_cast<uint16_t>(CommandType::LOCK): return CommandType::LOCK;
case static_cast<uint16_t>(CommandType::DOOR_ACTION): return CommandType::DOOR_ACTION;
case static_cast<uint16_t>(CommandType::LIGHT): return CommandType::LIGHT;
case static_cast<uint16_t>(CommandType::GET_OPENINGS): return CommandType::GET_OPENINGS;
case static_cast<uint16_t>(CommandType::OPENINGS): return CommandType::OPENINGS;
default: return unknown;
}
}
inline bool operator==(const uint16_t cmd_i, const CommandType& cmd_e) { return cmd_i == static_cast<uint16_t>(cmd_e); } void print_packet(const esphome::LogString* prefix,
inline bool operator==(const CommandType& cmd_e, const uint16_t cmd_i) { return cmd_i == static_cast<uint16_t>(cmd_e); } const WirePacket& packet) const;
optional<Command> decode_packet(const WirePacket& packet) const;
enum class IncrementRollingCode { void sync_helper(uint32_t start, uint32_t delay, uint8_t tries);
NO,
YES,
};
struct Command { // 8-byte member first (may require 8-byte alignment on some 32-bit systems)
CommandType type; uint64_t client_id_{0x539};
uint8_t nibble;
uint8_t byte1;
uint8_t byte2;
Command() // Pointers (4-byte aligned)
: type(CommandType::UNKNOWN) InternalGPIOPin* tx_pin_;
{ InternalGPIOPin* rx_pin_;
} RATGDOComponent* ratgdo_;
Command(CommandType type_, uint8_t nibble_ = 0, uint8_t byte1_ = 0, uint8_t byte2_ = 0) Scheduler* scheduler_;
: type(type_)
, nibble(nibble_)
, byte1(byte1_)
, byte2(byte2_)
{
}
};
class Secplus2 { // 4-byte members
public: uint32_t transmit_pending_start_{0};
void setup(RATGDOComponent* ratgdo, Scheduler* scheduler, InternalGPIOPin* rx_pin, InternalGPIOPin* tx_pin); uint32_t rx_msg_start_{0};
void loop(); uint32_t rx_last_read_{0};
void dump_config();
void on_shutdown();
void sync(); // Larger structures
single_observable<uint32_t> rolling_code_counter_{0};
ESPPreferenceObject rolling_code_pref_;
ESPPreferenceObject client_id_pref_;
std::string mqtt_rolling_code_topic_;
std::string mqtt_client_id_topic_;
OnceCallbacks<void()> on_command_sent_;
RatgdoUART uart_;
void light_action(LightAction action); // 19-byte arrays
void lock_action(LockAction action); WirePacket tx_packet_;
void door_action(DoorAction action); WirePacket rx_packet_;
void query_status(); // Small members at the end
void query_openings(); uint16_t rx_byte_count_{0};
struct {
protected: uint8_t transmit_pending : 1;
void increment_rolling_code_counter(int delta = 1); uint8_t rx_reading_msg : 1;
void set_rolling_code_counter(uint32_t counter); } flags_{0};
void set_client_id(uint64_t client_id); };
} // namespace secplus2
optional<Command> read_command(); } // namespace esphome::ratgdo
void handle_command(const Command& cmd);
void send_command(Command cmd, IncrementRollingCode increment = IncrementRollingCode::YES);
template <typename F>
void send_command(Command cmd, IncrementRollingCode increment, F&& on_sent)
{
// Only register the callback if the command will be accepted.
// If transmit_pending is set the command will be dropped, and
// a stale callback would fire when the previous pending packet
// transmits -- executing logic (e.g. the second phase of a
// door_command) at the wrong time.
//
// Register before send_command() because transmit_packet() may
// succeed immediately and call on_command_sent_.trigger() inline.
if (this->flags_.transmit_pending) {
return;
}
this->on_command_sent_(std::forward<F>(on_sent));
this->send_command(cmd, increment);
}
void encode_packet(Command cmd, WirePacket& packet);
bool transmit_packet();
void door_command(DoorAction action);
void print_packet(const esphome::LogString* prefix, const WirePacket& packet) const;
optional<Command> decode_packet(const WirePacket& packet) const;
void sync_helper(uint32_t start, uint32_t delay, uint8_t tries);
// 8-byte member first (may require 8-byte alignment on some 32-bit systems)
uint64_t client_id_ { 0x539 };
// Pointers (4-byte aligned)
InternalGPIOPin* tx_pin_;
InternalGPIOPin* rx_pin_;
RATGDOComponent* ratgdo_;
Scheduler* scheduler_;
// 4-byte members
uint32_t transmit_pending_start_ { 0 };
uint32_t rx_msg_start_ { 0 };
uint32_t rx_last_read_ { 0 };
// Larger structures
single_observable<uint32_t> rolling_code_counter_ { 0 };
ESPPreferenceObject rolling_code_pref_;
ESPPreferenceObject client_id_pref_;
std::string mqtt_rolling_code_topic_;
std::string mqtt_client_id_topic_;
OnceCallbacks<void()> on_command_sent_;
RatgdoUART uart_;
// 19-byte arrays
WirePacket tx_packet_;
WirePacket rx_packet_;
// Small members at the end
uint16_t rx_byte_count_ { 0 };
struct {
uint8_t transmit_pending : 1;
uint8_t rx_reading_msg : 1;
} flags_ { 0 };
};
} // namespace secplus2
} // namespace esphome::ratgdo
+72 -70
View File
@@ -1,94 +1,96 @@
#include "ratgdo_sensor.h" #include "ratgdo_sensor.h"
#include "../ratgdo_state.h" #include "../ratgdo_state.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome::ratgdo { namespace esphome::ratgdo {
static const char* const TAG = "ratgdo.sensor"; static const char* const TAG = "ratgdo.sensor";
static const int MIN_DISTANCE = 100; // ignore bugs crawling on the distance sensor & dust protection film static const int MIN_DISTANCE =
static const int MAX_DISTANCE = 4500; // default maximum distance 100; // ignore bugs crawling on the distance sensor & dust protection film
static const int MAX_DISTANCE = 4500; // default maximum distance
void RATGDOSensor::setup() void RATGDOSensor::setup() {
{ switch (this->ratgdo_sensor_type_) {
switch (this->ratgdo_sensor_type_) {
case RATGDOSensorType::RATGDO_OPENINGS: case RATGDOSensorType::RATGDO_OPENINGS:
this->parent_->subscribe_openings([this](uint16_t value) { this->parent_->subscribe_openings(
this->publish_state(value); [this](uint16_t value) { this->publish_state(value); });
}); break;
break;
case RATGDOSensorType::RATGDO_DISTANCE: case RATGDOSensorType::RATGDO_DISTANCE:
this->distance_sensor_.setI2cDevice(&I2C); this->distance_sensor_.setI2cDevice(&I2C);
this->distance_sensor_.setXShutPin(32); this->distance_sensor_.setXShutPin(32);
I2C.begin(19, 18); I2C.begin(19, 18);
this->distance_sensor_.begin(); this->distance_sensor_.begin();
this->distance_sensor_.VL53L4CX_Off(); this->distance_sensor_.VL53L4CX_Off();
this->distance_sensor_.InitSensor(0x59); this->distance_sensor_.InitSensor(0x59);
this->distance_sensor_.VL53L4CX_SetDistanceMode(VL53L4CX_DISTANCEMODE_LONG); this->distance_sensor_.VL53L4CX_SetDistanceMode(
this->distance_sensor_.VL53L4CX_StartMeasurement(); VL53L4CX_DISTANCEMODE_LONG);
this->parent_->subscribe_distance_measurement([this](int16_t value) { this->distance_sensor_.VL53L4CX_StartMeasurement();
this->publish_state(value); this->parent_->subscribe_distance_measurement(
}); [this](int16_t value) { this->publish_state(value); });
break; break;
default: default:
break; break;
} }
} }
void RATGDOSensor::dump_config() void RATGDOSensor::dump_config() {
{ LOG_SENSOR("", "RATGDO Sensor", this);
LOG_SENSOR("", "RATGDO Sensor", this); switch (this->ratgdo_sensor_type_) {
switch (this->ratgdo_sensor_type_) {
case RATGDOSensorType::RATGDO_OPENINGS: case RATGDOSensorType::RATGDO_OPENINGS:
ESP_LOGCONFIG(TAG, " Type: Openings"); ESP_LOGCONFIG(TAG, " Type: Openings");
break; break;
case RATGDOSensorType::RATGDO_DISTANCE: case RATGDOSensorType::RATGDO_DISTANCE:
ESP_LOGCONFIG(TAG, " Type: Distance"); ESP_LOGCONFIG(TAG, " Type: Distance");
break; break;
default: default:
break; break;
} }
} }
void RATGDOSensor::loop() void RATGDOSensor::loop() {
{ if (this->ratgdo_sensor_type_ == RATGDOSensorType::RATGDO_DISTANCE) {
if (this->ratgdo_sensor_type_ == RATGDOSensorType::RATGDO_DISTANCE) { VL53L4CX_MultiRangingData_t distanceData;
VL53L4CX_MultiRangingData_t distanceData; VL53L4CX_MultiRangingData_t* pDistanceData = &distanceData;
VL53L4CX_MultiRangingData_t* pDistanceData = &distanceData; uint8_t dataReady = 0;
uint8_t dataReady = 0; int objCount = 0;
int objCount = 0; int16_t maxDistance = -1;
int16_t maxDistance = -1; int status;
int status;
if (this->distance_sensor_.VL53L4CX_GetMeasurementDataReady(&dataReady) == 0 && dataReady) { if (this->distance_sensor_.VL53L4CX_GetMeasurementDataReady(&dataReady) ==
status = this->distance_sensor_.VL53L4CX_GetMultiRangingData(pDistanceData); 0 &&
objCount = pDistanceData->NumberOfObjectsFound; dataReady) {
status =
this->distance_sensor_.VL53L4CX_GetMultiRangingData(pDistanceData);
objCount = pDistanceData->NumberOfObjectsFound;
for (int i = 0; i < distanceData.NumberOfObjectsFound; i++) { for (int i = 0; i < distanceData.NumberOfObjectsFound; i++) {
VL53L4CX_TargetRangeData_t* d = &pDistanceData->RangeData[i]; VL53L4CX_TargetRangeData_t* d = &pDistanceData->RangeData[i];
if (d->RangeStatus == 0) { if (d->RangeStatus == 0) {
maxDistance = std::max(maxDistance, d->RangeMilliMeter); maxDistance = std::max(maxDistance, d->RangeMilliMeter);
maxDistance = maxDistance <= MIN_DISTANCE ? -1 : maxDistance; maxDistance = maxDistance <= MIN_DISTANCE ? -1 : maxDistance;
}
}
if (maxDistance < 0)
maxDistance = MAX_DISTANCE;
/*
* if the sensor is pointed at glass, there are many error -1 readings which will fill the
* vector with out of range data. The sensor should be sensitive enough to detect the floor
* in most situations, but daylight and/or really high ceilings can cause long distance
* measurements to be out of range.
*/
this->parent_->set_distance_measurement(maxDistance);
// ESP_LOGD(TAG,"# obj found %d; distance %d",objCount, maxDistance);
if (status == 0) {
status = this->distance_sensor_.VL53L4CX_ClearInterruptAndStartMeasurement();
}
} }
}
if (maxDistance < 0) maxDistance = MAX_DISTANCE;
/*
* if the sensor is pointed at glass, there are many error -1 readings
* which will fill the vector with out of range data. The sensor should be
* sensitive enough to detect the floor in most situations, but daylight
* and/or really high ceilings can cause long distance measurements to be
* out of range.
*/
this->parent_->set_distance_measurement(maxDistance);
// ESP_LOGD(TAG,"# obj found %d; distance %d",objCount, maxDistance);
if (status == 0) {
status =
this->distance_sensor_.VL53L4CX_ClearInterruptAndStartMeasurement();
}
} }
}
} }
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+19 -19
View File
@@ -2,32 +2,32 @@
#include "../ratgdo.h" #include "../ratgdo.h"
#include "../ratgdo_state.h" #include "../ratgdo_state.h"
#include "Wire.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "Wire.h"
#include "vl53l4cx_class.h" #include "vl53l4cx_class.h"
#define I2C Wire #define I2C Wire
namespace esphome::ratgdo { namespace esphome::ratgdo {
enum RATGDOSensorType : uint8_t { enum RATGDOSensorType : uint8_t { RATGDO_OPENINGS, RATGDO_DISTANCE = 6 };
RATGDO_OPENINGS,
RATGDO_DISTANCE = 6 class RATGDOSensor : public sensor::Sensor,
public RATGDOClient,
public Component {
public:
void dump_config() override;
void setup() override;
void loop() override;
void set_ratgdo_sensor_type(RATGDOSensorType ratgdo_sensor_type_) {
this->ratgdo_sensor_type_ = ratgdo_sensor_type_;
}
protected:
RATGDOSensorType ratgdo_sensor_type_;
VL53L4CX distance_sensor_;
}; };
class RATGDOSensor : public sensor::Sensor, public RATGDOClient, public Component { } // namespace esphome::ratgdo
public:
void dump_config() override;
void setup() override;
void loop() override;
void set_ratgdo_sensor_type(RATGDOSensorType ratgdo_sensor_type_) { this->ratgdo_sensor_type_ = ratgdo_sensor_type_; }
protected:
RATGDOSensorType ratgdo_sensor_type_;
VL53L4CX distance_sensor_;
};
} // namespace esphome::ratgdo
+22 -24
View File
@@ -1,4 +1,5 @@
#include "ratgdo_switch.h" #include "ratgdo_switch.h"
#include "../ratgdo_state.h" #include "../ratgdo_state.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@@ -6,39 +7,36 @@ namespace esphome::ratgdo {
static const char* const TAG = "ratgdo.switch"; static const char* const TAG = "ratgdo.switch";
void RATGDOSwitch::dump_config() void RATGDOSwitch::dump_config() {
{ LOG_SWITCH("", "RATGDO Switch", this);
LOG_SWITCH("", "RATGDO Switch", this); switch (this->switch_type_) {
switch (this->switch_type_) {
case SwitchType::RATGDO_LED: case SwitchType::RATGDO_LED:
ESP_LOGCONFIG(TAG, " Type: LED"); ESP_LOGCONFIG(TAG, " Type: LED");
break; break;
default: default:
break; break;
} }
} }
void RATGDOSwitch::setup() void RATGDOSwitch::setup() {
{ switch (this->switch_type_) {
switch (this->switch_type_) {
case SwitchType::RATGDO_LED: case SwitchType::RATGDO_LED:
this->pin_->setup(); this->pin_->setup();
break; break;
default: default:
break; break;
} }
} }
void RATGDOSwitch::write_state(bool state) void RATGDOSwitch::write_state(bool state) {
{ switch (this->switch_type_) {
switch (this->switch_type_) {
case SwitchType::RATGDO_LED: case SwitchType::RATGDO_LED:
this->pin_->digital_write(state); this->pin_->digital_write(state);
this->publish_state(state); this->publish_state(state);
break; break;
default: default:
break; break;
} }
} }
} // namespace esphome::ratgdo } // namespace esphome::ratgdo
+19 -17
View File
@@ -8,22 +8,24 @@
namespace esphome::ratgdo { namespace esphome::ratgdo {
enum SwitchType { enum SwitchType { RATGDO_LED = 1 };
RATGDO_LED = 1
class RATGDOSwitch : public switch_::Switch,
public RATGDOClient,
public Component {
public:
void dump_config() override;
void setup() override;
void set_switch_type(SwitchType switch_type_) {
this->switch_type_ = switch_type_;
}
void write_state(bool state) override;
void set_pin(GPIOPin* pin) { pin_ = pin; }
protected:
SwitchType switch_type_;
GPIOPin* pin_;
}; };
class RATGDOSwitch : public switch_::Switch, public RATGDOClient, public Component { } // namespace esphome::ratgdo
public:
void dump_config() override;
void setup() override;
void set_switch_type(SwitchType switch_type_) { this->switch_type_ = switch_type_; }
void write_state(bool state) override;
void set_pin(GPIOPin* pin) { pin_ = pin; }
protected:
SwitchType switch_type_;
GPIOPin* pin_;
};
} // namespace esphome::ratgdo
+6 -6
View File
@@ -7,7 +7,7 @@
namespace esphome { namespace esphome {
namespace somfy { namespace somfy {
static const char *const TAG = "somfy"; static const char* const TAG = "somfy";
static const uint8_t CMD_STOP = 0x01; static const uint8_t CMD_STOP = 0x01;
static const uint8_t CMD_UP = 0x02; static const uint8_t CMD_UP = 0x02;
@@ -34,8 +34,8 @@ struct SomfyRTSProtocolData {
class SomfyRTSProtocol class SomfyRTSProtocol
: public remote_base::RemoteProtocol<SomfyRTSProtocolData> { : public remote_base::RemoteProtocol<SomfyRTSProtocolData> {
public: public:
virtual void encode(remote_base::RemoteTransmitData *dst, virtual void encode(remote_base::RemoteTransmitData* dst,
const ProtocolData &data) override { const ProtocolData& data) override {
/* /*
* 1. Build a frame. * 1. Build a frame.
*/ */
@@ -101,7 +101,7 @@ class SomfyRTSProtocol
} }
} }
virtual void dump(const ProtocolData &data) override { data.dump(); } virtual void dump(const ProtocolData& data) override { data.dump(); }
virtual optional<ProtocolData> decode( virtual optional<ProtocolData> decode(
remote_base::RemoteReceiveData src) override { remote_base::RemoteReceiveData src) override {
@@ -155,7 +155,7 @@ void SomfyRTSCover::setup() {
// MQTT. // MQTT.
mqtt::global_mqtt_client->subscribe( mqtt::global_mqtt_client->subscribe(
mqtt_topic_prefix_ + std::to_string(remote_id_), mqtt_topic_prefix_ + std::to_string(remote_id_),
[this](const std::string &topic, const std::string &payload) { [this](const std::string& topic, const std::string& payload) {
if (rolling_code_ == 0) { if (rolling_code_ == 0) {
ESP_LOGI(TAG, "Received rolling code for remote #%d from MQTT: %s", ESP_LOGI(TAG, "Received rolling code for remote #%d from MQTT: %s",
remote_id_, payload.c_str()); remote_id_, payload.c_str());
@@ -171,7 +171,7 @@ void SomfyRTSCover::setup() {
} }
} }
void SomfyRTSCover::control(const cover::CoverCall &call) { void SomfyRTSCover::control(const cover::CoverCall& call) {
if (call.get_position().has_value()) { if (call.get_position().has_value()) {
float position = *call.get_position(); float position = *call.get_position();
send(position > 0.5 ? CMD_UP : CMD_DOWN); send(position > 0.5 ? CMD_UP : CMD_DOWN);
+3 -3
View File
@@ -16,7 +16,7 @@ namespace somfy {
class SomfyRTSCover : public cover::Cover, public Component { class SomfyRTSCover : public cover::Cover, public Component {
public: public:
void set_transmitter( void set_transmitter(
remote_transmitter::RemoteTransmitterComponent *transmitter) { remote_transmitter::RemoteTransmitterComponent* transmitter) {
this->transmitter_ = transmitter; this->transmitter_ = transmitter;
} }
void set_remote_id(uint32_t remote_id) { this->remote_id_ = remote_id; } void set_remote_id(uint32_t remote_id) { this->remote_id_ = remote_id; }
@@ -24,7 +24,7 @@ class SomfyRTSCover : public cover::Cover, public Component {
cover::CoverTraits get_traits() override; cover::CoverTraits get_traits() override;
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
void control(const cover::CoverCall &call) override; void control(const cover::CoverCall& call) override;
protected: protected:
/* Wake up the blinds motor controller and send the command. /* Wake up the blinds motor controller and send the command.
@@ -39,7 +39,7 @@ class SomfyRTSCover : public cover::Cover, public Component {
uint32_t rolling_code_; uint32_t rolling_code_;
std::string mqtt_topic_prefix_; std::string mqtt_topic_prefix_;
ESPPreferenceObject rolling_code_pref_; ESPPreferenceObject rolling_code_pref_;
remote_transmitter::RemoteTransmitterComponent *transmitter_{nullptr}; remote_transmitter::RemoteTransmitterComponent* transmitter_{nullptr};
}; };
} // namespace somfy } // namespace somfy
+6 -6
View File
@@ -3,16 +3,16 @@
#include "esphome/components/remote_base/remote_base.h" #include "esphome/components/remote_base/remote_base.h"
namespace { namespace {
static const char *const TAG = __FILE__; static const char* const TAG = __FILE__;
static const uint8_t MIN_TEMPERATURE = 35; static const uint8_t MIN_TEMPERATURE = 35;
static const uint8_t MAX_TEMPERATURE = 70; static const uint8_t MAX_TEMPERATURE = 70;
static const uint8_t TEMPERATURE_LEVELS = 8; static const uint8_t TEMPERATURE_LEVELS = 8;
static void add_header_(esphome::remote_base::RemoteTransmitData *data) { static void add_header_(esphome::remote_base::RemoteTransmitData* data) {
data->item(30, 1000); data->item(30, 1000);
} }
static void add_byte_(esphome::remote_base::RemoteTransmitData *data, static void add_byte_(esphome::remote_base::RemoteTransmitData* data,
uint8_t value) { uint8_t value) {
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
data->item(30, value & 0b10000000 ? 830 : 650); data->item(30, value & 0b10000000 ? 830 : 650);
@@ -20,7 +20,7 @@ static void add_byte_(esphome::remote_base::RemoteTransmitData *data,
} }
} }
static void add_post_(esphome::remote_base::RemoteTransmitData *data) { static void add_post_(esphome::remote_base::RemoteTransmitData* data) {
data->item(30, 460); data->item(30, 460);
data->item(30, 650); data->item(30, 650);
} }
@@ -29,7 +29,7 @@ static void add_post_(esphome::remote_base::RemoteTransmitData *data) {
namespace esphome { namespace esphome {
namespace zehnder { namespace zehnder {
void ZehnderComponent::control(const climate::ClimateCall &call) { void ZehnderComponent::control(const climate::ClimateCall& call) {
float target_temp = this->target_temperature; float target_temp = this->target_temperature;
climate::ClimateMode mode = this->mode; climate::ClimateMode mode = this->mode;
@@ -101,7 +101,7 @@ void ZehnderComponent::dump_config() {
this->has_temperature_sensor_ ? "yes" : "no"); this->has_temperature_sensor_ ? "yes" : "no");
} }
bool ZehnderComponent::transmit_temperature_(float *temp) { bool ZehnderComponent::transmit_temperature_(float* temp) {
uint8_t level; uint8_t level;
if (*temp == 0) { if (*temp == 0) {
level = 0; level = 0;
+5 -5
View File
@@ -22,11 +22,11 @@ class ZehnderComponent : public climate::Climate, public PollingComponent {
// PollingComponent. // PollingComponent.
void update() override; void update() override;
void set_transmitter(remote_transmitter::RemoteTransmitterComponent *t) { void set_transmitter(remote_transmitter::RemoteTransmitterComponent* t) {
this->transmitter_ = t; this->transmitter_ = t;
} }
void add_temperature_sensor(sensor::Sensor *s) { void add_temperature_sensor(sensor::Sensor* s) {
has_temperature_sensor_ = true; has_temperature_sensor_ = true;
s->add_on_state_callback([this](float temperature_value) { s->add_on_state_callback([this](float temperature_value) {
this->current_temperature = temperature_value; this->current_temperature = temperature_value;
@@ -36,13 +36,13 @@ class ZehnderComponent : public climate::Climate, public PollingComponent {
protected: protected:
// climate::Climate. // climate::Climate.
void control(const climate::ClimateCall &call) override; void control(const climate::ClimateCall& call) override;
climate::ClimateTraits traits() override; climate::ClimateTraits traits() override;
bool transmit_temperature_(float *temp); bool transmit_temperature_(float* temp);
bool transmit_level_(uint8_t level); bool transmit_level_(uint8_t level);
remote_transmitter::RemoteTransmitterComponent *transmitter_ = nullptr; remote_transmitter::RemoteTransmitterComponent* transmitter_ = nullptr;
bool has_temperature_sensor_ = false; bool has_temperature_sensor_ = false;
}; };
+8 -8
View File
@@ -27,16 +27,16 @@ esp32_camera:
power_down_pin: GPIO32 power_down_pin: GPIO32
name: Camera name: Camera
resolution: 800x600 # Max 1600x1200. resolution: 800x600 # Max 1600x1200.
idle_framerate: 0.1fps idle_framerate: 0.1fps
output: output:
- platform: ledc - platform: ledc
pin: GPIO4 pin: GPIO4
channel: 2 # PWM channel 1 is used for esp32_camera. channel: 2 # PWM channel 1 is used for esp32_camera.
id: led id: led
light: light:
- platform: monochromatic - platform: monochromatic
output: led output: led
name: Flash name: Flash
+7 -7
View File
@@ -19,7 +19,7 @@ external_components:
- source: - source:
type: local type: local
path: components path: components
components: [ ratgdo ] components: [ratgdo]
packages: packages:
wifi: !include templates/wifi.yaml wifi: !include templates/wifi.yaml
@@ -49,7 +49,7 @@ time:
binary_sensor: binary_sensor:
- platform: template - platform: template
name: "Sync failed" name: "Sync failed"
lambda: 'return *id(garage_gate_controller).sync_failed;' lambda: "return *id(garage_gate_controller).sync_failed;"
device_class: problem device_class: problem
entity_category: diagnostic entity_category: diagnostic
@@ -74,9 +74,9 @@ sensor:
attenuation: auto attenuation: auto
update_interval: 60s update_interval: 60s
filters: filters:
- calibrate_linear: - calibrate_linear:
- 1.16 -> 5 - 1.16 -> 5
- 2.783 -> 12 - 2.783 -> 12
lock: lock:
- platform: ratgdo - platform: ratgdo
@@ -114,7 +114,7 @@ cover:
on_state_change: on_state_change:
- if: - if:
condition: condition:
lambda: 'return id(garage_gate).current_operation == COVER_OPERATION_CLOSING;' lambda: "return id(garage_gate).current_operation == COVER_OPERATION_CLOSING;"
then: then:
- script.execute: play_siren - script.execute: play_siren
else: else:
@@ -167,7 +167,7 @@ script:
then: then:
- while: - while:
condition: condition:
lambda: 'return id(garage_gate).current_operation == COVER_OPERATION_CLOSING;' lambda: "return id(garage_gate).current_operation == COVER_OPERATION_CLOSING;"
then: then:
- rtttl.play: "siren:d=8,o=6,b=100:e,d#,e,d#,e,d#,e,d#" - rtttl.play: "siren:d=8,o=6,b=100:e,d#,e,d#,e,d#,e,d#"
- wait_until: - wait_until:
+8 -9
View File
@@ -40,17 +40,16 @@ ethernet:
domain: "" domain: ""
text_sensor: text_sensor:
- platform: ethernet_info - platform: ethernet_info
ip_address: ip_address:
name: "ESP chip IP (Ethernet)" name: "ESP chip IP (Ethernet)"
icon: mdi:ip-network icon: mdi:ip-network
mac_address: mac_address:
name: "ESP chip MAC (Ethernet)" name: "ESP chip MAC (Ethernet)"
icon: mdi:expansion-card icon: mdi:expansion-card
sn74hc595: # write sn74hc595: # write
- id: 'sn74hc595_hub' - id: "sn74hc595_hub"
clock_pin: GPIO14 # SRCLK (serial clock) clock_pin: GPIO14 # SRCLK (serial clock)
data_pin: GPIO13 # SER (serial input) data_pin: GPIO13 # SER (serial input)
latch_pin: GPIO15 # RCLK (latch for outputs) latch_pin: GPIO15 # RCLK (latch for outputs)
+78 -78
View File
@@ -42,98 +42,98 @@ i2c:
scl: GPIO18 scl: GPIO18
font: font:
- file: "fonts/DejaVuSans.ttf" - file: "fonts/DejaVuSans.ttf"
id: font_sans id: font_sans
size: 20 size: 20
display: display:
- platform: ssd1306_i2c - platform: ssd1306_i2c
model: "SSD1306 128x32" model: "SSD1306 128x32"
id: oled_display id: oled_display
lambda: |- lambda: |-
it.printf( it.printf(
4, 6, 4, 6,
id(font_sans), id(font_sans),
"%.2f C", "%.2f C",
id(boiler_temperature).state); id(boiler_temperature).state);
if (id(furnace_burning).state) { if (id(furnace_burning).state) {
it.filled_circle(112, 16, 12); it.filled_circle(112, 16, 12);
} else { } else {
it.circle(112, 16, 12); it.circle(112, 16, 12);
} }
one_wire: one_wire:
- platform: gpio - platform: gpio
pin: GPIO27 pin: GPIO27
sensor: sensor:
- platform: dallas_temp - platform: dallas_temp
address: 0x133c2cf648f40728 address: 0x133c2cf648f40728
id: boiler_temperature id: boiler_temperature
name: "Boiler temperature" name: "Boiler temperature"
filters: filters:
- offset: 0.5 - offset: 0.5
- platform: dallas_temp - platform: dallas_temp
address: 0x4845b7211864ff28 address: 0x4845b7211864ff28
id: hot_water_temperature id: hot_water_temperature
name: "Hot water temperature" name: "Hot water temperature"
- platform: dallas_temp - platform: dallas_temp
address: 0x6a0316643959ff28 address: 0x6a0316643959ff28
id: floor_temperature id: floor_temperature
name: "Floor temperature" name: "Floor temperature"
- platform: adc - platform: adc
pin: GPIO32 pin: GPIO32
update_interval: 1s update_interval: 1s
id: furnace_burning_led_brightness id: furnace_burning_led_brightness
attenuation: auto attenuation: auto
# Make voltage value available in UI for debugging and history. # Make voltage value available in UI for debugging and history.
name: "Furnace burning - LED brightness" name: "Furnace burning - LED brightness"
entity_category: diagnostic entity_category: diagnostic
accuracy_decimals: 2 accuracy_decimals: 2
- platform: adc - platform: adc
pin: GPIO33 pin: GPIO33
update_interval: 1s update_interval: 1s
id: floor_active_led_brightness id: floor_active_led_brightness
attenuation: auto attenuation: auto
# Make voltage value available in UI for debugging and history. # Make voltage value available in UI for debugging and history.
name: "Floor active - LED brightness" name: "Floor active - LED brightness"
entity_category: diagnostic entity_category: diagnostic
accuracy_decimals: 2 accuracy_decimals: 2
binary_sensor: binary_sensor:
- platform: analog_threshold - platform: analog_threshold
id: furnace_burning id: furnace_burning
name: "Furnace burning" name: "Furnace burning"
sensor_id: furnace_burning_led_brightness sensor_id: furnace_burning_led_brightness
# The reading (V) gets lower as brightness increases. Keep the threshold # The reading (V) gets lower as brightness increases. Keep the threshold
# low (close to LED reading) to avoid triggering with a lamp or natural light. # low (close to LED reading) to avoid triggering with a lamp or natural light.
threshold: 2.2 # LED on = 1.93V, flashlight on = 2.5V. threshold: 2.2 # LED on = 1.93V, flashlight on = 2.5V.
filters: filters:
- invert: - invert:
- platform: analog_threshold - platform: analog_threshold
id: heating_enabled id: heating_enabled
name: "Floor active" name: "Floor active"
sensor_id: floor_active_led_brightness sensor_id: floor_active_led_brightness
# This sensor is more exposed to external light due to construction. # This sensor is more exposed to external light due to construction.
threshold: 1.9 # LED on = 1.76V, flashlight on = 2.1V. threshold: 1.9 # LED on = 1.76V, flashlight on = 2.1V.
filters: filters:
- invert: - invert:
switch: switch:
- platform: gpio - platform: gpio
pin: GPIO25 pin: GPIO25
id: relay_temperature_manual id: relay_temperature_manual
inverted: true inverted: true
# Non-manual is no longer wired to anything. # Non-manual is no longer wired to anything.
restore_mode: ALWAYS_ON restore_mode: ALWAYS_ON
- platform: gpio - platform: gpio
pin: GPIO26 pin: GPIO26
name: "Floor intent" name: "Floor intent"
restore_mode: RESTORE_DEFAULT_OFF restore_mode: RESTORE_DEFAULT_OFF
+4 -4
View File
@@ -72,19 +72,19 @@ binary_sensor:
- platform: template - platform: template
name: "Garden Peach relay" name: "Garden Peach relay"
entity_category: diagnostic entity_category: diagnostic
lambda: 'return id(relay_1).state;' lambda: "return id(relay_1).state;"
- platform: template - platform: template
name: "Garden Berries relay" name: "Garden Berries relay"
entity_category: diagnostic entity_category: diagnostic
lambda: 'return id(relay_2).state;' lambda: "return id(relay_2).state;"
- platform: template - platform: template
name: "Shower relay" name: "Shower relay"
entity_category: diagnostic entity_category: diagnostic
lambda: 'return id(relay_3).state;' lambda: "return id(relay_3).state;"
- platform: template - platform: template
name: "Relay 4" name: "Relay 4"
entity_category: diagnostic entity_category: diagnostic
lambda: 'return id(relay_4).state;' lambda: "return id(relay_4).state;"
switch: switch:
- platform: gpio - platform: gpio
+21 -22
View File
@@ -10,25 +10,24 @@ packages:
time: !include templates/time.yaml time: !include templates/time.yaml
time: time:
- platform: sntp - platform: sntp
id: sntp_time id: sntp_time
servers: !secret sntp_servers servers: !secret sntp_servers
timezone: Europe/Zurich timezone: Europe/Zurich
on_time: on_time:
#
# # Pool pump: must run during saltwater system cycle, and an additional hour.
# Pool pump: must run during saltwater system cycle, and an additional hour. # Saltwater 16:30 - 19:30, so we stop at 20:35 (Pool instructions).
# Saltwater 16:30 - 19:30, so we stop at 20:35 (Pool instructions). #
# - hours: 16
- hours: 16 minutes: 25
minutes: 25 seconds: 0
seconds: 0 months: APR-OCT
months: APR-OCT then:
then: - switch.turn_on: mystrom_relay
- switch.turn_on: mystrom_relay - hours: 20
- hours: 20 minutes: 35
minutes: 35 seconds: 0
seconds: 0 months: APR-OCT
months: APR-OCT then:
then: - switch.turn_off: mystrom_relay
- switch.turn_off: mystrom_relay
+17 -17
View File
@@ -28,24 +28,24 @@ output:
id: mic_power id: mic_power
sensor: sensor:
- platform: pulse_meter - platform: pulse_meter
internal_filter: 50ms internal_filter: 50ms
pin: pin:
number: GPIO15 number: GPIO15
inverted: true inverted: true
mode: mode:
input: true input: true
name: "Level" name: "Level"
unit_of_measurement: "mR/h" unit_of_measurement: "mR/h"
state_class: "measurement" state_class: "measurement"
accuracy_decimals: 3 accuracy_decimals: 3
filters: filters:
- multiply: 0.0000833333 # particles/minute -> milliRad/hour (assuming calibration factor 1200 CPM = 1milliRad) - multiply: 0.0000833333 # particles/minute -> milliRad/hour (assuming calibration factor 1200 CPM = 1milliRad)
- sliding_window_moving_average: - sliding_window_moving_average:
window_size: 20 window_size: 20
send_every: 2 send_every: 2
- throttle_average: 5s - throttle_average: 5s
+9 -10
View File
@@ -8,14 +8,13 @@ esphome:
packages: packages:
device_base: !include templates/mystrom.yaml device_base: !include templates/mystrom.yaml
interval: interval:
- interval: 1min - interval: 1min
then: then:
- if: - if:
condition: condition:
sensor.in_range: sensor.in_range:
id: continuous_on_time id: continuous_on_time
above: 18000 # 5h above: 18000 # 5h
then: then:
- switch.turn_off: mystrom_relay - switch.turn_off: mystrom_relay
+5 -5
View File
@@ -13,7 +13,7 @@ globals:
- id: ble_scans - id: ble_scans
type: int type: int
restore_value: no restore_value: no
initial_value: '0' initial_value: "0"
esp32_ble_tracker: esp32_ble_tracker:
scan_parameters: scan_parameters:
@@ -23,10 +23,10 @@ esp32_ble_tracker:
active: true active: true
on_scan_end: on_scan_end:
- then: - then:
- sensor.template.publish: - sensor.template.publish:
id: ble_scans_finished id: ble_scans_finished
state: !lambda 'return ++id(ble_scans);' state: !lambda "return ++id(ble_scans);"
- script.execute: restart_after_delay - script.execute: restart_after_delay
bluetooth_proxy: bluetooth_proxy:
id: btproxy id: btproxy
+5 -5
View File
@@ -12,8 +12,8 @@ packages:
logger: logger:
switch: switch:
- platform: gpio - platform: gpio
pin: GPIO0 pin: GPIO0
name: None name: None
inverted: true inverted: true
id: relay id: relay
+7 -7
View File
@@ -16,13 +16,13 @@ ethernet:
domain: "" domain: ""
text_sensor: text_sensor:
- platform: ethernet_info - platform: ethernet_info
ip_address: ip_address:
name: "ESP chip IP (Ethernet)" name: "ESP chip IP (Ethernet)"
icon: mdi:ip-network icon: mdi:ip-network
mac_address: mac_address:
name: "ESP chip MAC (Ethernet)" name: "ESP chip MAC (Ethernet)"
icon: mdi:expansion-card icon: mdi:expansion-card
logger: logger:
+4 -4
View File
@@ -1,7 +1,7 @@
# Make sure nothing is connected to the A0 pin. # Make sure nothing is connected to the A0 pin.
sensor: sensor:
- platform: adc - platform: adc
pin: VCC pin: VCC
name: "ESP chip VCC Voltage" name: "ESP chip VCC Voltage"
entity_category: diagnostic entity_category: diagnostic
+82 -82
View File
@@ -41,39 +41,39 @@ status_led:
pin: GPIO16 pin: GPIO16
binary_sensor: binary_sensor:
- platform: gpio - platform: gpio
id: mystrom_side id: mystrom_side
name: "Side button (plus) pressed" name: "Side button (plus) pressed"
pin: pin:
number: GPIO23 number: GPIO23
mode: mode:
input: true input: true
pullup: true pullup: true
inverted: true inverted: true
- platform: gpio - platform: gpio
id: mystrom_bottom id: mystrom_bottom
name: "Bottom button (power) pressed" name: "Bottom button (power) pressed"
pin: pin:
number: GPIO18 number: GPIO18
mode: mode:
input: true input: true
pullup: true pullup: true
inverted: true inverted: true
on_press: on_press:
then: then:
- switch.toggle: mystrom_relay - switch.toggle: mystrom_relay
script: script:
- id: power_cycle_relay - id: power_cycle_relay
mode: single mode: single
then: then:
- if: - if:
condition: condition:
switch.is_on: mystrom_relay switch.is_on: mystrom_relay
then: then:
- switch.turn_off: mystrom_relay - switch.turn_off: mystrom_relay
- delay: 7s - delay: 7s
- switch.turn_on: mystrom_relay - switch.turn_on: mystrom_relay
button: button:
- platform: template - platform: template
@@ -83,60 +83,60 @@ button:
- script.execute: power_cycle_relay - script.execute: power_cycle_relay
sensor: sensor:
- platform: pulse_counter - platform: pulse_counter
pin: 4 pin: 4
update_interval: 10s update_interval: 10s
id: mystrom_power_consumption id: mystrom_power_consumption
name: "Power relay consumption" name: "Power relay consumption"
device_class: "power" device_class: "power"
state_class: "measurement" state_class: "measurement"
unit_of_measurement: "W" unit_of_measurement: "W"
accuracy_decimals: 0 accuracy_decimals: 0
filters:
- multiply: 3.29E-02 # pulse/min -> W
total:
name: "Energy consumption"
unit_of_measurement: "kWh"
device_class: "energy"
state_class: "total_increasing"
accuracy_decimals: 3
filters: filters:
- multiply: 5.48E-07 # pulses -> kWh - multiply: 3.29E-02 # pulse/min -> W
- platform: tmp102 total:
id: mystrom_temperature name: "Energy consumption"
name: "Internal temperature" unit_of_measurement: "kWh"
update_interval: 60s device_class: "energy"
entity_category: diagnostic state_class: "total_increasing"
address: 0x48 accuracy_decimals: 3
on_value_range: filters:
above: 80 - multiply: 5.48E-07 # pulses -> kWh
then:
- switch.turn_off: mystrom_relay
- platform: duty_time - platform: tmp102
id: continuous_on_time id: mystrom_temperature
name: Continuous on time name: "Internal temperature"
lambda: "return id(mystrom_relay).state;" update_interval: 60s
accuracy_decimals: 0 entity_category: diagnostic
address: 0x48
on_value_range:
above: 80
then:
- switch.turn_off: mystrom_relay
- platform: duty_time
id: continuous_on_time
name: Continuous on time
lambda: "return id(mystrom_relay).state;"
accuracy_decimals: 0
switch: switch:
- platform: gpio - platform: gpio
name: None # Main entity for the device. name: None # Main entity for the device.
pin: GPIO27 pin: GPIO27
id: mystrom_relay id: mystrom_relay
restore_mode: RESTORE_DEFAULT_ON restore_mode: RESTORE_DEFAULT_ON
device_class: outlet device_class: outlet
on_turn_off: on_turn_off:
- sensor.duty_time.reset: continuous_on_time - sensor.duty_time.reset: continuous_on_time
- platform: gpio - platform: gpio
# Exposing this as a switch rather than light to avoid # Exposing this as a switch rather than light to avoid
# automations and dashboard picking it up as illuminance # automations and dashboard picking it up as illuminance
# source. # source.
name: "Identify LED (white)" name: "Identify LED (white)"
restore_mode: ALWAYS_OFF restore_mode: ALWAYS_OFF
pin: pin:
number: GPIO17 number: GPIO17
inverted: true inverted: true
+4 -4
View File
@@ -1,5 +1,5 @@
button: button:
- platform: restart - platform: restart
name: "ESP chip restart" name: "ESP chip restart"
entity_category: diagnostic entity_category: diagnostic
id: restart_button id: restart_button
+5 -5
View File
@@ -1,6 +1,6 @@
binary_sensor: binary_sensor:
- platform: template - platform: template
name: "ESP chip clock invalid" name: "ESP chip clock invalid"
lambda: 'return !id(sntp_time).now().is_valid();' lambda: "return !id(sntp_time).now().is_valid();"
entity_category: diagnostic entity_category: diagnostic
device_class: problem device_class: problem
+33 -33
View File
@@ -23,9 +23,9 @@ esphome:
version: "5.0" version: "5.0"
on_boot: on_boot:
priority: 600 # Default; most sensors set up, no network yet. priority: 600 # Default; most sensors set up, no network yet.
then: then:
- script.execute: cc2652_reset - script.execute: cc2652_reset
packages: packages:
device_base: !include esp32-poe.yaml device_base: !include esp32-poe.yaml
@@ -38,38 +38,38 @@ external_components:
- source: github://oxan/esphome-stream-server - source: github://oxan/esphome-stream-server
script: script:
- id: fw_update_mode - id: fw_update_mode
then: then:
- switch.turn_on: zBSL - switch.turn_on: zBSL
- delay: 1s - delay: 1s
- switch.turn_on: zRST_gpio - switch.turn_on: zRST_gpio
- delay: 1s - delay: 1s
- switch.turn_off: zRST_gpio - switch.turn_off: zRST_gpio
- logger.log: "Delaying ~10 seconds for cc2652 to settle" - logger.log: "Delaying ~10 seconds for cc2652 to settle"
- delay: 11s - delay: 11s
- switch.turn_off: zBSL - switch.turn_off: zBSL
- logger.log: "Please try update with cc2538-bsl tool now" - logger.log: "Please try update with cc2538-bsl tool now"
- logger.log: "cc-bsl usage: cc2538-bsl.py -p socket://ip-of-gw:6638 -evw firmware.hex" - logger.log: "cc-bsl usage: cc2538-bsl.py -p socket://ip-of-gw:6638 -evw firmware.hex"
- id: cc2652_reset - id: cc2652_reset
then: then:
- switch.turn_on: zRST_gpio - switch.turn_on: zRST_gpio
- delay: 15ms - delay: 15ms
- switch.turn_off: zRST_gpio - switch.turn_off: zRST_gpio
button: button:
- platform: template - platform: template
name: "ZigBee Module reset" name: "ZigBee Module reset"
disabled_by_default: true disabled_by_default: true
id: zRST id: zRST
on_press: on_press:
- script.execute: cc2652_reset - script.execute: cc2652_reset
- platform: template - platform: template
name: "ZigBee Module enter programming mode" name: "ZigBee Module enter programming mode"
disabled_by_default: true disabled_by_default: true
on_press: on_press:
- script.execute: fw_update_mode - script.execute: fw_update_mode
stream_server: stream_server:
uart_id: uart_bus uart_id: uart_bus
@@ -78,6 +78,6 @@ stream_server:
buffer_size: 4096 buffer_size: 4096
binary_sensor: binary_sensor:
- platform: stream_server - platform: stream_server
connected: connected:
name: "ZigBee Module serial stream connected" name: "ZigBee Module serial stream connected"
+2 -2
View File
@@ -1,3 +1,3 @@
sensor: sensor:
- platform: uptime - platform: uptime
name: "ESP chip uptime" name: "ESP chip uptime"
+15 -15
View File
@@ -1,21 +1,21 @@
sensor: sensor:
- platform: wifi_signal - platform: wifi_signal
name: "ESP chip WiFi signal" name: "ESP chip WiFi signal"
text_sensor: text_sensor:
- platform: wifi_info - platform: wifi_info
mac_address: mac_address:
name: "ESP chip MAC" name: "ESP chip MAC"
icon: mdi:wifi-cog icon: mdi:wifi-cog
ip_address: ip_address:
name: "ESP chip IP" name: "ESP chip IP"
icon: mdi:wifi-cog icon: mdi:wifi-cog
bssid: bssid:
name: "ESP chip connected to BSSID" name: "ESP chip connected to BSSID"
icon: mdi:wifi-settings icon: mdi:wifi-settings
filters: filters:
- to_upper - to_upper
- substitute: !secret wifi_ap_lookup - substitute: !secret wifi_ap_lookup
wifi: wifi:
ssid: !secret wifi_ssid ssid: !secret wifi_ssid
+27 -27
View File
@@ -12,33 +12,33 @@ packages:
sensor: !include templates/esp8266-vcc.yaml sensor: !include templates/esp8266-vcc.yaml
sensor: sensor:
- platform: pulse_meter - platform: pulse_meter
# Our meter (USLC/20) is rated Qmax=5.0 (m3/h). # Our meter (USLC/20) is rated Qmax=5.0 (m3/h).
# The official one is rated 5.0. # The official one is rated 5.0.
# #
# 3600 / 5000 = 0.72 seconds per liter. That's the shortest # 3600 / 5000 = 0.72 seconds per liter. That's the shortest
# pulse length at a maximum flow rate for the meter. # pulse length at a maximum flow rate for the meter.
internal_filter: 720ms internal_filter: 720ms
# Assuming Qmin=50 (l/h), a flow with >72 seconds between pulses # Assuming Qmin=50 (l/h), a flow with >72 seconds between pulses
# is beyond the measuring capabilities. However, one of the # is beyond the measuring capabilities. However, one of the
# irrigation valves only consumes 0.73 L/min, so we push it a bit. # irrigation valves only consumes 0.73 L/min, so we push it a bit.
timeout: 90s timeout: 90s
pin: pin:
number: D2 number: D2
inverted: true inverted: true
mode: mode:
input: true input: true
pullup: true pullup: true
name: "Incoming water flow" name: "Incoming water flow"
unit_of_measurement: "L/min" unit_of_measurement: "L/min"
device_class: volume_flow_rate device_class: volume_flow_rate
state_class: "measurement" state_class: "measurement"
icon: mdi:pipe-valve # device_class? icon: mdi:pipe-valve # device_class?
total: total:
name: "Incoming water total" name: "Incoming water total"
unit_of_measurement: "L" unit_of_measurement: "L"
device_class: "water" device_class: "water"
state_class: "total_increasing" state_class: "total_increasing"
+10 -10
View File
@@ -13,17 +13,17 @@ packages:
device_base: !include templates/tubeszb-coordinator.yaml device_base: !include templates/tubeszb-coordinator.yaml
switch: switch:
- platform: gpio - platform: gpio
pin: 33 pin: 33
id: zRST_gpio id: zRST_gpio
inverted: yes inverted: yes
restore_mode: ALWAYS_OFF restore_mode: ALWAYS_OFF
- platform: gpio - platform: gpio
pin: 32 pin: 32
id: zBSL id: zBSL
inverted: yes inverted: yes
restore_mode: ALWAYS_OFF restore_mode: ALWAYS_OFF
uart: uart:
id: uart_bus id: uart_bus
+10 -10
View File
@@ -16,17 +16,17 @@ packages:
device_base: !include templates/tubeszb-coordinator.yaml device_base: !include templates/tubeszb-coordinator.yaml
switch: switch:
- platform: gpio - platform: gpio
pin: 5 pin: 5
id: zRST_gpio id: zRST_gpio
inverted: yes inverted: yes
restore_mode: ALWAYS_OFF restore_mode: ALWAYS_OFF
- platform: gpio - platform: gpio
pin: 16 pin: 16
id: zBSL id: zBSL
inverted: yes inverted: yes
restore_mode: ALWAYS_OFF restore_mode: ALWAYS_OFF
uart: uart:
id: uart_bus id: uart_bus