diff --git a/bathroomheater.yaml b/bathroomheater.yaml index b76d68b..18c23ce 100644 --- a/bathroomheater.yaml +++ b/bathroomheater.yaml @@ -8,25 +8,25 @@ esphome: version: "1.0" external_components: -- source: components + - source: components packages: device_base: !include templates/esp8266.yaml sensor: !include templates/esp8266-vcc.yaml one_wire: -- platform: gpio - pin: D1 + - platform: gpio + pin: D1 sensor: -- platform: dallas_temp - address: 0x0401212E565C7428 - id: bathroom_heater_temperature + - platform: dallas_temp + address: 0x0401212E565C7428 + id: bathroom_heater_temperature -- platform: duty_time - id: continuous_on_time - name: Continuous on time - accuracy_decimals: 0 + - platform: duty_time + id: continuous_on_time + name: Continuous on time + accuracy_decimals: 0 remote_transmitter: pin: D2 @@ -34,30 +34,30 @@ remote_transmitter: id: heater_ir_transmitter climate: -- platform: zehnder - name: None - id: bathroom_heater - transmitter_id: heater_ir_transmitter - temperature_sensor_id: bathroom_heater_temperature - on_state: - - if: - condition: - lambda: 'return x.mode == climate::CLIMATE_MODE_HEAT;' - then: - - sensor.duty_time.start: continuous_on_time - else: - - sensor.duty_time.stop: continuous_on_time - - sensor.duty_time.reset: continuous_on_time + - platform: zehnder + name: None + id: bathroom_heater + transmitter_id: heater_ir_transmitter + temperature_sensor_id: bathroom_heater_temperature + on_state: + - if: + condition: + lambda: "return x.mode == climate::CLIMATE_MODE_HEAT;" + then: + - sensor.duty_time.start: continuous_on_time + else: + - sensor.duty_time.stop: continuous_on_time + - sensor.duty_time.reset: continuous_on_time interval: -- interval: 1min - then: - - if: - condition: - sensor.in_range: - id: continuous_on_time - above: 43200 # 12h - then: - - climate.control: - id: bathroom_heater - mode: "OFF" + - interval: 1min + then: + - if: + condition: + sensor.in_range: + id: continuous_on_time + above: 43200 # 12h + then: + - climate.control: + id: bathroom_heater + mode: "OFF" diff --git a/btproxy-laundry.yaml b/btproxy-laundry.yaml index b63a15a..7e6278c 100644 --- a/btproxy-laundry.yaml +++ b/btproxy-laundry.yaml @@ -61,8 +61,8 @@ sensor: device_id: laundry_air_quality filters: - calibrate_linear: - - 2.7 -> 0 - - 4.5 -> 100 + - 2.7 -> 0 + - 4.5 -> 100 - platform: airthings_wave_mini ble_client_id: airthings_wave_mini_workshop @@ -89,5 +89,5 @@ sensor: device_id: workshop_air_quality filters: - calibrate_linear: - - 2.7 -> 0 - - 4.5 -> 100 + - 2.7 -> 0 + - 4.5 -> 100 diff --git a/car-charger.yaml b/car-charger.yaml index ba03e0b..52ec8c8 100644 --- a/car-charger.yaml +++ b/car-charger.yaml @@ -17,21 +17,21 @@ switch: optimistic: true time: -- platform: sntp - id: sntp_time - servers: !secret sntp_servers - timezone: Europe/Zurich - on_time: - - seconds: 0 - minutes: 1 - hours: 0-6,19-23 - then: - - if: - condition: - - switch.is_on: failsafe_schedule - - switch.is_off: mystrom_relay - then: - - logger.log: >- - Power relay is off during T2, assuming automation failure and - correcting - - switch.turn_on: mystrom_relay + - platform: sntp + id: sntp_time + servers: !secret sntp_servers + timezone: Europe/Zurich + on_time: + - seconds: 0 + minutes: 1 + hours: 0-6,19-23 + then: + - if: + condition: + - switch.is_on: failsafe_schedule + - switch.is_off: mystrom_relay + then: + - logger.log: >- + Power relay is off during T2, assuming automation failure and + correcting + - switch.turn_on: mystrom_relay diff --git a/cat-feeder-cam.yaml b/cat-feeder-cam.yaml index 60e7c65..912b4f8 100644 --- a/cat-feeder-cam.yaml +++ b/cat-feeder-cam.yaml @@ -14,9 +14,9 @@ packages: psram: i2c: -- id: camera_i2c - sda: GPIO26 - scl: GPIO27 + - id: camera_i2c + sda: GPIO26 + scl: GPIO27 esp32_camera: # Ai-Thinker external_clock: @@ -30,16 +30,16 @@ esp32_camera: # Ai-Thinker power_down_pin: GPIO32 name: Camera - resolution: 800x600 # Max 1600x1200. + resolution: 800x600 # Max 1600x1200. idle_framerate: 0.1fps output: -- platform: ledc - pin: GPIO4 - channel: 2 # PWM channel 1 is used for esp32_camera. - id: led + - platform: ledc + pin: GPIO4 + channel: 2 # PWM channel 1 is used for esp32_camera. + id: led light: -- platform: monochromatic - output: led - name: Flash + - platform: monochromatic + output: led + name: Flash diff --git a/child-east-ac.yaml b/child-east-ac.yaml index 85dee75..47f2e0e 100644 --- a/child-east-ac.yaml +++ b/child-east-ac.yaml @@ -12,5 +12,5 @@ packages: climate: - platform: tcl112 - name: None # Main entity for the device. + name: None # Main entity for the device. receiver_id: ir_receiver diff --git a/child-west-ac.yaml b/child-west-ac.yaml index a2983b7..bb96cba 100644 --- a/child-west-ac.yaml +++ b/child-west-ac.yaml @@ -12,5 +12,5 @@ packages: climate: - platform: tcl112 - name: None # Main entity for the device. + name: None # Main entity for the device. receiver_id: ir_receiver diff --git a/components/ratgdo/automation.h b/components/ratgdo/automation.h index f0db6d9..14df911 100644 --- a/components/ratgdo/automation.h +++ b/components/ratgdo/automation.h @@ -8,14 +8,12 @@ namespace esphome::ratgdo { class SyncFailed : public Trigger<> { -public: - explicit SyncFailed(RATGDOComponent* parent) - { - parent->subscribe_sync_failed([this](optional state) { - if (state.value_or(false)) - this->trigger(); - }); - } + public: + explicit SyncFailed(RATGDOComponent* parent) { + parent->subscribe_sync_failed([this](optional state) { + if (state.value_or(false)) this->trigger(); + }); + } }; -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/callbacks.h b/components/ratgdo/callbacks.h index d8d8e7c..1106bf4 100644 --- a/components/ratgdo/callbacks.h +++ b/components/ratgdo/callbacks.h @@ -1,8 +1,9 @@ #pragma once -#include "observable.h" #include #include +#include "observable.h" + namespace esphome::ratgdo { void log_once_callbacks_overflow(uint8_t max); @@ -12,38 +13,37 @@ class OnceCallbacks; template class OnceCallbacks { -public: - // Runtime max is 1 for all current usage (door_state waits, command_sent waits). - // Set to 2 for safety margin. - static constexpr uint8_t MAX_CALLBACKS = 2; + public: + // Runtime max is 1 for all current usage (door_state waits, command_sent + // waits). Set to 2 for safety margin. + static constexpr uint8_t MAX_CALLBACKS = 2; - template - void operator()(F&& callback) - { - if (this->count_ >= MAX_CALLBACKS) { - log_once_callbacks_overflow(MAX_CALLBACKS); - return; - } - this->callbacks_[this->count_++] = Callback::create(std::forward(callback)); + template + void operator()(F&& callback) { + if (this->count_ >= MAX_CALLBACKS) { + log_once_callbacks_overflow(MAX_CALLBACKS); + return; } + this->callbacks_[this->count_++] = + Callback::create(std::forward(callback)); + } - // Re-entrant safe: count_ is zeroed before invoking callbacks, - // so callbacks can queue new entries during trigger(). - void trigger(Ts... args) - { - uint8_t count = this->count_; - this->count_ = 0; - for (uint8_t i = 0; i < count; i++) { - this->callbacks_[i].call(args...); - } + // Re-entrant safe: count_ is zeroed before invoking callbacks, + // so callbacks can queue new entries during trigger(). + void trigger(Ts... args) { + uint8_t count = this->count_; + this->count_ = 0; + for (uint8_t i = 0; i < count; i++) { + this->callbacks_[i].call(args...); } + } - void clear() { this->count_ = 0; } - uint8_t count() const { return this->count_; } + void clear() { this->count_ = 0; } + uint8_t count() const { return this->count_; } -protected: - Callback callbacks_[MAX_CALLBACKS] { }; - uint8_t count_ { 0 }; + protected: + Callback callbacks_[MAX_CALLBACKS]{}; + uint8_t count_{0}; }; -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/cover/automation.h b/components/ratgdo/cover/automation.h index 395ddc0..fd36c09 100644 --- a/components/ratgdo/cover/automation.h +++ b/components/ratgdo/cover/automation.h @@ -7,37 +7,32 @@ namespace esphome::ratgdo { class CoverOpeningTrigger : public Trigger<> { -public: - CoverOpeningTrigger(cover::Cover* a_cover) - { - a_cover->add_on_state_callback([this, a_cover]() { - if (a_cover->current_operation == cover::COVER_OPERATION_OPENING) { - this->trigger(); - } - }); - } + public: + CoverOpeningTrigger(cover::Cover* a_cover) { + a_cover->add_on_state_callback([this, a_cover]() { + if (a_cover->current_operation == cover::COVER_OPERATION_OPENING) { + this->trigger(); + } + }); + } }; class CoverClosingTrigger : public Trigger<> { -public: - CoverClosingTrigger(cover::Cover* a_cover) - { - a_cover->add_on_state_callback([this, a_cover]() { - if (a_cover->current_operation == cover::COVER_OPERATION_CLOSING) { - this->trigger(); - } - }); - } + public: + CoverClosingTrigger(cover::Cover* a_cover) { + a_cover->add_on_state_callback([this, a_cover]() { + if (a_cover->current_operation == cover::COVER_OPERATION_CLOSING) { + this->trigger(); + } + }); + } }; class CoverStateTrigger : public Trigger<> { -public: - CoverStateTrigger(cover::Cover* a_cover) - { - a_cover->add_on_state_callback([this, a_cover]() { - this->trigger(); - }); - } + public: + CoverStateTrigger(cover::Cover* a_cover) { + a_cover->add_on_state_callback([this, a_cover]() { this->trigger(); }); + } }; -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/cover/ratgdo_cover.cpp b/components/ratgdo/cover/ratgdo_cover.cpp index 9a33f03..402899f 100644 --- a/components/ratgdo/cover/ratgdo_cover.cpp +++ b/components/ratgdo/cover/ratgdo_cover.cpp @@ -1,4 +1,5 @@ #include "ratgdo_cover.h" + #include "../ratgdo_state.h" #include "esphome/core/log.h" @@ -8,89 +9,82 @@ using namespace esphome::cover; static const char* const TAG = "ratgdo.cover"; -void RATGDOCover::dump_config() -{ - LOG_COVER("", "RATGDO Cover", this); +void RATGDOCover::dump_config() { 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() -{ - 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::on_door_state(DoorState state, float position) -{ - if (!*this->parent_->synced || state == DoorState::UNKNOWN) { - return; - } - bool save_to_flash = true; - switch (state) { +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: - this->position = COVER_OPEN; - this->current_operation = COVER_OPERATION_IDLE; - break; + this->position = COVER_OPEN; + this->current_operation = COVER_OPERATION_IDLE; + break; case DoorState::CLOSED: - this->position = COVER_CLOSED; - this->current_operation = COVER_OPERATION_IDLE; - break; + this->position = COVER_CLOSED; + this->current_operation = COVER_OPERATION_IDLE; + break; case DoorState::OPENING: - this->current_operation = COVER_OPERATION_OPENING; - this->position = position; - save_to_flash = false; - break; + this->current_operation = COVER_OPERATION_OPENING; + this->position = position; + save_to_flash = false; + break; case DoorState::CLOSING: - this->current_operation = COVER_OPERATION_CLOSING; - this->position = position; - save_to_flash = false; - break; + this->current_operation = COVER_OPERATION_CLOSING; + this->position = position; + save_to_flash = false; + break; case DoorState::STOPPED: - this->current_operation = COVER_OPERATION_IDLE; - this->position = position; - break; + this->current_operation = COVER_OPERATION_IDLE; + this->position = position; + break; case DoorState::UNKNOWN: default: - this->current_operation = COVER_OPERATION_IDLE; - this->position = position; + this->current_operation = COVER_OPERATION_IDLE; + this->position = position; - break; - } + break; + } - this->publish_state(save_to_flash); + this->publish_state(save_to_flash); } -CoverTraits RATGDOCover::get_traits() -{ - auto traits = CoverTraits(); - traits.set_supports_stop(true); - traits.set_supports_toggle(true); - traits.set_supports_position(true); - return traits; +CoverTraits RATGDOCover::get_traits() { + auto traits = CoverTraits(); + traits.set_supports_stop(true); + traits.set_supports_toggle(true); + traits.set_supports_position(true); + return traits; } -void RATGDOCover::control(const CoverCall& call) -{ - if (call.get_stop()) { - this->parent_->door_stop(); - } - if (call.get_toggle()) { - this->parent_->door_toggle(); - } - if (call.get_position().has_value()) { - auto pos = *call.get_position(); - if (pos == COVER_OPEN) { - this->parent_->door_open(); - } else if (pos == COVER_CLOSED) { - this->parent_->door_close(); - } else { - this->parent_->door_move_to_position(pos); - } +void RATGDOCover::control(const CoverCall& call) { + if (call.get_stop()) { + this->parent_->door_stop(); + } + if (call.get_toggle()) { + this->parent_->door_toggle(); + } + if (call.get_position().has_value()) { + auto pos = *call.get_position(); + if (pos == COVER_OPEN) { + this->parent_->door_open(); + } else if (pos == COVER_CLOSED) { + this->parent_->door_close(); + } else { + this->parent_->door_move_to_position(pos); } + } } -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/cover/ratgdo_cover.h b/components/ratgdo/cover/ratgdo_cover.h index 1495197..4ebeada 100644 --- a/components/ratgdo/cover/ratgdo_cover.h +++ b/components/ratgdo/cover/ratgdo_cover.h @@ -8,15 +8,15 @@ namespace esphome::ratgdo { class RATGDOCover : public cover::Cover, public RATGDOClient, public Component { -public: - void dump_config() override; - void setup() override; + public: + void dump_config() override; + void setup() override; - cover::CoverTraits get_traits() override; - void on_door_state(DoorState state, float position); + cover::CoverTraits get_traits() override; + void on_door_state(DoorState state, float position); -protected: - void control(const cover::CoverCall& call) override; + protected: + void control(const cover::CoverCall& call) override; }; -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/light/ratgdo_light_output.cpp b/components/ratgdo/light/ratgdo_light_output.cpp index 82f071d..d31efb1 100644 --- a/components/ratgdo/light/ratgdo_light_output.cpp +++ b/components/ratgdo/light/ratgdo_light_output.cpp @@ -1,4 +1,5 @@ #include "ratgdo_light_output.h" + #include "../ratgdo_state.h" #include "esphome/core/log.h" @@ -8,59 +9,48 @@ using namespace esphome::light; static const char* const TAG = "ratgdo.light"; -void RATGDOLightOutput::dump_config() -{ - ESP_LOGCONFIG(TAG, "RATGDO Light"); +void RATGDOLightOutput::dump_config() { ESP_LOGCONFIG(TAG, "RATGDO Light"); } + +void RATGDOLightOutput::setup() { + this->parent_->subscribe_light_state( + [this](LightState state) { this->on_light_state(state); }); } -void RATGDOLightOutput::setup() -{ - this->parent_->subscribe_light_state([this](LightState state) { - this->on_light_state(state); - }); +void RATGDOLightOutput::on_light_state(esphome::ratgdo::LightState state) { + if (this->light_state_) { + this->has_initial_state_ = true; + set_state(state); + } } -void RATGDOLightOutput::on_light_state(esphome::ratgdo::LightState state) -{ - if (this->light_state_) { - this->has_initial_state_ = true; - set_state(state); - } +void RATGDOLightOutput::set_state(esphome::ratgdo::LightState state) { + bool is_on = state == LightState::ON; + this->light_state_->current_values.set_state(is_on); + this->light_state_->remote_values.set_state(is_on); + this->light_state_->publish_state(); } -void RATGDOLightOutput::set_state(esphome::ratgdo::LightState state) -{ - bool is_on = state == LightState::ON; - this->light_state_->current_values.set_state(is_on); - this->light_state_->remote_values.set_state(is_on); - this->light_state_->publish_state(); +void RATGDOLightOutput::setup_state(light::LightState* light_state) { + esphome::ratgdo::LightState state = this->parent_->get_light_state(); + this->light_state_ = light_state; + this->set_state(state); } -void RATGDOLightOutput::setup_state(light::LightState* light_state) -{ - esphome::ratgdo::LightState state = this->parent_->get_light_state(); - this->light_state_ = light_state; - this->set_state(state); +LightTraits RATGDOLightOutput::get_traits() { + auto traits = LightTraits(); + traits.set_supported_color_modes({light::ColorMode::ON_OFF}); + return traits; } -LightTraits RATGDOLightOutput::get_traits() -{ - auto traits = LightTraits(); - traits.set_supported_color_modes({ light::ColorMode::ON_OFF }); - return traits; +void RATGDOLightOutput::write_state(light::LightState* state) { + 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(); + } } -void RATGDOLightOutput::write_state(light::LightState* state) -{ - 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 +} // namespace esphome::ratgdo diff --git a/components/ratgdo/light/ratgdo_light_output.h b/components/ratgdo/light/ratgdo_light_output.h index 9f6ad31..117c01b 100644 --- a/components/ratgdo/light/ratgdo_light_output.h +++ b/components/ratgdo/light/ratgdo_light_output.h @@ -7,21 +7,23 @@ namespace esphome::ratgdo { -class RATGDOLightOutput : public light::LightOutput, public RATGDOClient, public Component { -public: - void dump_config() override; - void setup() override; - light::LightTraits get_traits() override; - void write_state(light::LightState* state) override; - void setup_state(light::LightState* state) override; - void set_state(esphome::ratgdo::LightState state); - light::LightState* get_state() { return this->light_state_; } +class RATGDOLightOutput : public light::LightOutput, + public RATGDOClient, + public Component { + public: + void dump_config() override; + void setup() override; + light::LightTraits get_traits() override; + void write_state(light::LightState* state) override; + 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: - light::LightState* light_state_; - bool has_initial_state_ = false; + protected: + light::LightState* light_state_; + bool has_initial_state_ = false; }; -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/lock/ratgdo_lock.cpp b/components/ratgdo/lock/ratgdo_lock.cpp index 0c290e9..e88be8c 100644 --- a/components/ratgdo/lock/ratgdo_lock.cpp +++ b/components/ratgdo/lock/ratgdo_lock.cpp @@ -1,4 +1,5 @@ #include "ratgdo_lock.h" + #include "../ratgdo_state.h" #include "esphome/core/log.h" @@ -6,48 +7,45 @@ namespace esphome::ratgdo { static const char* const TAG = "ratgdo.lock"; -void RATGDOLock::dump_config() -{ - LOG_LOCK("", "RATGDO Lock", this); - ESP_LOGCONFIG(TAG, " Type: Lock"); +void RATGDOLock::dump_config() { + LOG_LOCK("", "RATGDO Lock", this); + ESP_LOGCONFIG(TAG, " Type: Lock"); } -void RATGDOLock::setup() -{ - this->parent_->subscribe_lock_state([this](LockState state) { - this->on_lock_state(state); - }); +void RATGDOLock::setup() { + this->parent_->subscribe_lock_state( + [this](LockState state) { this->on_lock_state(state); }); } -void RATGDOLock::on_lock_state(LockState state) -{ - if (state == LockState::LOCKED && this->state == lock::LockState::LOCK_STATE_LOCKED) { - return; - } - if (state == LockState::UNLOCKED && this->state == lock::LockState::LOCK_STATE_UNLOCKED) { - return; - } +void RATGDOLock::on_lock_state(LockState state) { + if (state == LockState::LOCKED && + this->state == lock::LockState::LOCK_STATE_LOCKED) { + return; + } + if (state == LockState::UNLOCKED && + this->state == lock::LockState::LOCK_STATE_UNLOCKED) { + return; + } - auto call = this->make_call(); - if (state == LockState::LOCKED) { - call.set_state(lock::LockState::LOCK_STATE_LOCKED); - } else if (state == LockState::UNLOCKED) { - call.set_state(lock::LockState::LOCK_STATE_UNLOCKED); - } - this->publish_state(*call.get_state()); + auto call = this->make_call(); + if (state == LockState::LOCKED) { + call.set_state(lock::LockState::LOCK_STATE_LOCKED); + } else if (state == LockState::UNLOCKED) { + call.set_state(lock::LockState::LOCK_STATE_UNLOCKED); + } + this->publish_state(*call.get_state()); } -void RATGDOLock::control(const lock::LockCall& call) -{ - auto state = *call.get_state(); +void RATGDOLock::control(const lock::LockCall& call) { + auto state = *call.get_state(); - if (state == lock::LockState::LOCK_STATE_LOCKED) { - this->parent_->lock(); - } else if (state == lock::LockState::LOCK_STATE_UNLOCKED) { - this->parent_->unlock(); - } + if (state == lock::LockState::LOCK_STATE_LOCKED) { + this->parent_->lock(); + } else if (state == lock::LockState::LOCK_STATE_UNLOCKED) { + this->parent_->unlock(); + } - this->publish_state(state); + this->publish_state(state); } -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/lock/ratgdo_lock.h b/components/ratgdo/lock/ratgdo_lock.h index 94a0962..a68c1ca 100644 --- a/components/ratgdo/lock/ratgdo_lock.h +++ b/components/ratgdo/lock/ratgdo_lock.h @@ -8,12 +8,12 @@ namespace esphome::ratgdo { class RATGDOLock : public lock::Lock, public RATGDOClient, public Component { -public: - void dump_config() override; - void setup() override; + public: + void dump_config() override; + void setup() override; - void on_lock_state(LockState state); - void control(const lock::LockCall& call) override; + void on_lock_state(LockState state); + void control(const lock::LockCall& call) override; }; -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/number/ratgdo_number.cpp b/components/ratgdo/number/ratgdo_number.cpp index 7baf728..cada660 100644 --- a/components/ratgdo/number/ratgdo_number.cpp +++ b/components/ratgdo/number/ratgdo_number.cpp @@ -1,4 +1,5 @@ #include "ratgdo_number.h" + #include "../ratgdo_state.h" #include "esphome/core/log.h" @@ -6,83 +7,76 @@ namespace esphome::ratgdo { static const char* const TAG = "ratgdo.number"; -void RATGDONumber::dump_config() -{ - LOG_NUMBER("", "RATGDO Number", this); - switch (this->number_type_) { +void RATGDONumber::dump_config() { + LOG_NUMBER("", "RATGDO Number", this); + switch (this->number_type_) { case RATGDO_OPENING_DURATION: - ESP_LOGCONFIG(TAG, " Type: Opening Duration"); - break; + ESP_LOGCONFIG(TAG, " Type: Opening Duration"); + break; case RATGDO_CLOSING_DURATION: - ESP_LOGCONFIG(TAG, " Type: Closing Duration"); - break; + ESP_LOGCONFIG(TAG, " Type: Closing Duration"); + break; default: - break; - } + break; + } } -void RATGDONumber::setup() -{ - float value; - this->pref_ = this->make_entity_preference(); - if (!this->pref_.load(&value)) { - value = 0; - } - this->control(value); +void RATGDONumber::setup() { + float value; + this->pref_ = this->make_entity_preference(); + if (!this->pref_.load(&value)) { + value = 0; + } + this->control(value); - switch (this->number_type_) { + switch (this->number_type_) { case RATGDO_OPENING_DURATION: - this->parent_->subscribe_opening_duration([this](float value) { - this->update_state(value); - }); - break; + this->parent_->subscribe_opening_duration( + [this](float value) { this->update_state(value); }); + break; case RATGDO_CLOSING_DURATION: - this->parent_->subscribe_closing_duration([this](float value) { - this->update_state(value); - }); - break; + this->parent_->subscribe_closing_duration( + [this](float value) { this->update_state(value); }); + break; default: - break; - } + break; + } } -void RATGDONumber::set_number_type(NumberType number_type_) -{ - this->number_type_ = number_type_; - switch (this->number_type_) { +void RATGDONumber::set_number_type(NumberType number_type_) { + this->number_type_ = number_type_; + switch (this->number_type_) { case RATGDO_OPENING_DURATION: case RATGDO_CLOSING_DURATION: - this->traits.set_step(0.1); - this->traits.set_min_value(0.0); - this->traits.set_max_value(180.0); - break; + this->traits.set_step(0.1); + this->traits.set_min_value(0.0); + this->traits.set_max_value(180.0); + break; default: - break; - } + break; + } } -void RATGDONumber::update_state(float value) -{ - if (value == this->state) { - return; - } - this->pref_.save(&value); - this->publish_state(value); +void RATGDONumber::update_state(float value) { + if (value == this->state) { + return; + } + this->pref_.save(&value); + this->publish_state(value); } -void RATGDONumber::control(float value) -{ - switch (this->number_type_) { +void RATGDONumber::control(float value) { + switch (this->number_type_) { case RATGDO_OPENING_DURATION: - this->parent_->set_opening_duration(value); - break; + this->parent_->set_opening_duration(value); + break; case RATGDO_CLOSING_DURATION: - this->parent_->set_closing_duration(value); - break; + this->parent_->set_closing_duration(value); + break; default: - break; - } - this->update_state(value); + break; + } + this->update_state(value); } -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/number/ratgdo_number.h b/components/ratgdo/number/ratgdo_number.h index 0f50fe9..6c16102 100644 --- a/components/ratgdo/number/ratgdo_number.h +++ b/components/ratgdo/number/ratgdo_number.h @@ -9,26 +9,30 @@ namespace esphome::ratgdo { enum NumberType { - RATGDO_OPENING_DURATION, - RATGDO_CLOSING_DURATION, + RATGDO_OPENING_DURATION, + RATGDO_CLOSING_DURATION, }; -class RATGDONumber : public number::Number, public RATGDOClient, public Component { -public: - void dump_config() override; - void setup() override; - void set_number_type(NumberType number_type); - // other esphome components that persist state in the flash have HARDWARE 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; } +class RATGDONumber : public number::Number, + public RATGDOClient, + public Component { + public: + void dump_config() override; + void setup() override; + void set_number_type(NumberType number_type); + // other esphome components that persist state in the flash have HARDWARE + // 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 control(float value) override; + void update_state(float value); + void control(float value) override; -protected: - NumberType number_type_; - ESPPreferenceObject pref_; + protected: + NumberType number_type_; + ESPPreferenceObject pref_; }; -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/observable.cpp b/components/ratgdo/observable.cpp index fd8d336..b2c2044 100644 --- a/components/ratgdo/observable.cpp +++ b/components/ratgdo/observable.cpp @@ -1,4 +1,5 @@ #include "observable.h" + #include "callbacks.h" #include "esphome/core/log.h" @@ -6,19 +7,20 @@ namespace esphome::ratgdo { static const char* const TAG = "ratgdo.observable"; -void log_multiple_subscribers() -{ - ESP_LOGE(TAG, "single_observable already has a subscriber! This will overwrite the existing subscriber."); +void log_multiple_subscribers() { + ESP_LOGE(TAG, + "single_observable already has a subscriber! This will overwrite " + "the existing subscriber."); } -void log_observer_overflow() -{ - ESP_LOGE(TAG, "observable has too many subscribers! Ignoring new subscriber."); +void log_observer_overflow() { + ESP_LOGE(TAG, + "observable has too many subscribers! Ignoring new subscriber."); } -void log_once_callbacks_overflow(uint8_t max) -{ - ESP_LOGE(TAG, "OnceCallbacks overflow (max %u)! Ignoring callback.", static_cast(max)); +void log_once_callbacks_overflow(uint8_t max) { + ESP_LOGE(TAG, "OnceCallbacks overflow (max %u)! Ignoring callback.", + static_cast(max)); } -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/observable.h b/components/ratgdo/observable.h index 653e98c..e04417d 100644 --- a/components/ratgdo/observable.h +++ b/components/ratgdo/observable.h @@ -11,157 +11,144 @@ void log_multiple_subscribers(); void log_observer_overflow(); // Lightweight type-erased callback (16 bytes on 32-bit). -// For small trivially-copyable callables (like [this], [this, f], or [this, f, id] lambdas), -// stores the callable inline — zero heap allocation. -// Supports up to 3 * sizeof(void*) bytes (12 bytes on 32-bit, 24 on 64-bit). +// For small trivially-copyable callables (like [this], [this, f], or [this, f, +// id] lambdas), stores the callable inline — zero heap allocation. Supports up +// to 3 * sizeof(void*) bytes (12 bytes on 32-bit, 24 on 64-bit). inline constexpr size_t CALLBACK_STORAGE_SIZE = 3 * sizeof(void*); template struct Callback { - using fn_t = void (*)(const void*, Ts...); - fn_t fn_ { nullptr }; - alignas(void*) uint8_t storage_[CALLBACK_STORAGE_SIZE] { }; + using fn_t = void (*)(const void*, Ts...); + fn_t fn_{nullptr}; + alignas(void*) uint8_t storage_[CALLBACK_STORAGE_SIZE]{}; - void call(Ts... args) const { this->fn_(this->storage_, args...); } - explicit operator bool() const { return this->fn_ != nullptr; } + void call(Ts... args) const { this->fn_(this->storage_, args...); } + explicit operator bool() const { return this->fn_ != nullptr; } - template - static Callback create(F&& f) - { - Callback cb; - using Decay = std::decay_t; - static_assert(!std::is_function_v>, - "Pass function pointers, not function references"); - static_assert(std::is_trivially_copyable_v, "Observable callbacks must be trivially copyable (e.g. [this] lambdas)"); - static_assert(sizeof(Decay) <= CALLBACK_STORAGE_SIZE, "Observable callbacks must fit in storage (capture at most 3 pointers)"); - cb.fn_ = [](const void* storage, Ts... args) { - alignas(Decay) char buf[sizeof(Decay)]; - __builtin_memcpy(buf, storage, sizeof(Decay)); - (*std::launder(reinterpret_cast(buf)))(args...); - }; - __builtin_memcpy(cb.storage_, &f, sizeof(Decay)); - return cb; - } + template + static Callback create(F&& f) { + Callback cb; + using Decay = std::decay_t; + static_assert(!std::is_function_v>, + "Pass function pointers, not function references"); + static_assert(std::is_trivially_copyable_v, + "Observable callbacks must be trivially copyable (e.g. " + "[this] lambdas)"); + static_assert(sizeof(Decay) <= CALLBACK_STORAGE_SIZE, + "Observable callbacks must fit in storage (capture at most 3 " + "pointers)"); + cb.fn_ = [](const void* storage, Ts... args) { + alignas(Decay) char buf[sizeof(Decay)]; + __builtin_memcpy(buf, storage, sizeof(Decay)); + (*std::launder(reinterpret_cast(buf)))(args...); + }; + __builtin_memcpy(cb.storage_, &f, sizeof(Decay)); + return cb; + } }; // Primary template for observable with subscribers. template class observable { -public: - observable(const T& value) - : value_(value) - { + public: + observable(const T& value) : value_(value) {} + + template + observable& operator=(U value) { + if (value != this->value_) { + this->value_ = value; + this->notify(); } + return *this; + } - template - observable& operator=(U value) - { - if (value != this->value_) { - this->value_ = value; - this->notify(); - } - return *this; + T const* operator&() const { return &this->value_; } + T const& operator*() const { return this->value_; } + + template + void subscribe(F&& observer) { + if (this->count_ >= MaxObservers) { + log_observer_overflow(); + return; } + this->observers_[this->count_++] = + Callback::create(std::forward(observer)); + } - T const* operator&() const { return &this->value_; } - T const& operator*() const { return this->value_; } - - template - void subscribe(F&& observer) - { - if (this->count_ >= MaxObservers) { - log_observer_overflow(); - return; - } - this->observers_[this->count_++] = Callback::create(std::forward(observer)); + void notify() const { + for (uint8_t i = 0; i < this->count_; i++) { + this->observers_[i].call(this->value_); } + } - void notify() const - { - for (uint8_t i = 0; i < this->count_; i++) { - this->observers_[i].call(this->value_); - } - } - -private: - T value_; - Callback observers_[MaxObservers] { }; - uint8_t count_ { 0 }; + private: + T value_; + Callback observers_[MaxObservers]{}; + uint8_t count_{0}; }; // Specialization for zero subscribers — no array, no count, notify is a no-op. template class observable { -public: - observable(const T& value) - : value_(value) - { + public: + observable(const T& value) : value_(value) {} + + template + observable& operator=(U value) { + if (value != this->value_) { + this->value_ = value; } + return *this; + } - template - observable& operator=(U value) - { - if (value != this->value_) { - this->value_ = value; - } - return *this; - } + T const* operator&() const { return &this->value_; } + T const& operator*() const { return this->value_; } - T const* operator&() const { return &this->value_; } - T const& operator*() const { return this->value_; } + template + void subscribe(F&&) { + log_observer_overflow(); + } - template - void subscribe(F&&) - { - log_observer_overflow(); - } + void notify() const {} - void notify() const { } - -private: - T value_; + private: + T value_; }; template class single_observable { -public: - single_observable(const T& value) - : value_(value) - { + public: + single_observable(const T& value) : value_(value) {} + + template + single_observable& operator=(U value) { + if (value != this->value_) { + this->value_ = value; + this->notify(); } + return *this; + } - template - single_observable& operator=(U value) - { - if (value != this->value_) { - this->value_ = value; - this->notify(); - } - return *this; + T const* operator&() const { return &this->value_; } + T const& operator*() const { return this->value_; } + + template + void subscribe(F&& observer) { + if (this->observer_) { + log_multiple_subscribers(); } + this->observer_ = Callback::create(std::forward(observer)); + } - T const* operator&() const { return &this->value_; } - T const& operator*() const { return this->value_; } - - template - void subscribe(F&& observer) - { - if (this->observer_) { - log_multiple_subscribers(); - } - this->observer_ = Callback::create(std::forward(observer)); + void notify() const { + if (this->observer_) { + this->observer_.call(this->value_); } + } - void notify() const - { - if (this->observer_) { - this->observer_.call(this->value_); - } - } - -private: - T value_; - Callback observer_ { }; + private: + T value_; + Callback observer_{}; }; -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/ratgdo.cpp b/components/ratgdo/ratgdo.cpp index e8a65c9..59ea8fd 100644 --- a/components/ratgdo/ratgdo.cpp +++ b/components/ratgdo/ratgdo.cpp @@ -5,19 +5,16 @@ ************************************/ #include "ratgdo.h" + #include "common.h" -#include "ratgdo_state.h" - -#include "secplus2.h" - #include "esphome/core/application.h" #include "esphome/core/gpio.h" #include "esphome/core/log.h" +#include "ratgdo_state.h" +#include "secplus2.h" namespace esphome::ratgdo { - - static const char* const TAG = "ratgdo"; static constexpr int SYNC_DELAY = 5000; // 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; -void log_subscriber_overflow(const LogString* observable_name, uint32_t max) -{ - ESP_LOGE(TAG, "Too many subscribers for %s (max %d)", - LOG_STR_ARG(observable_name), (int)max); +void log_subscriber_overflow(const LogString* observable_name, uint32_t max) { + ESP_LOGE(TAG, "Too many subscribers for %s (max %d)", + LOG_STR_ARG(observable_name), (int)max); } -void RATGDOComponent::setup() -{ - this->output_gdo_pin_->setup(); - this->output_gdo_pin_->pin_mode(gpio::FLAG_OUTPUT); +void RATGDOComponent::setup() { + this->output_gdo_pin_->setup(); + this->output_gdo_pin_->pin_mode(gpio::FLAG_OUTPUT); - this->input_gdo_pin_->setup(); - this->input_gdo_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + this->input_gdo_pin_->setup(); + this->input_gdo_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); - this->protocol_->setup(this, &App.scheduler, this->input_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(); - } - }); + this->protocol_->setup(this, &App.scheduler, this->input_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(); + } + }); } // initializing protocol, this gets called before setup() because // its children components might require that -void RATGDOComponent::init_protocol() -{ - this->protocol_ = new secplus2::Secplus2(); +void RATGDOComponent::init_protocol() { + this->protocol_ = new secplus2::Secplus2(); } -void RATGDOComponent::loop() -{ - this->protocol_->loop(); +void RATGDOComponent::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() -{ - 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::on_shutdown() { + if (this->protocol_ != nullptr) { + this->protocol_->on_shutdown(); + } } -void RATGDOComponent::on_shutdown() -{ - if (this->protocol_ != nullptr) { - this->protocol_->on_shutdown(); +void RATGDOComponent::received(const DoorState door_state) { + ESP_LOGD(TAG, "Door state=%s", LOG_STR_ARG(DoorState_to_string(door_state))); + + 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) -{ - ESP_LOGD(TAG, "Door state=%s", LOG_STR_ARG(DoorState_to_string(door_state))); +void RATGDOComponent::received(const LightState light_state) { + ESP_LOGD(TAG, "Light state=%s", + 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) { - return; +void RATGDOComponent::received(const LightAction light_action) { + 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(1000 * duration / update_period), + static_cast(1)); + set_interval(INTERVAL_POSITION_SYNC, static_cast(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 (*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; - } + 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; } - // 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) -{ - ESP_LOGD(TAG, "Light state=%s", - LOG_STR_ARG(LightState_to_string(light_state))); - this->light_state = light_state; +void RATGDOComponent::set_opening_duration(float duration) { + ESP_LOGD(TAG, "Set opening duration: %.1fs", duration); + this->opening_duration = duration; } -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; +void RATGDOComponent::set_closing_duration(float duration) { + ESP_LOGD(TAG, "Set closing duration: %.1fs", duration); + this->closing_duration = duration; } -void RATGDOComponent::received(const LightAction light_action) -{ - 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(1000 * duration / update_period), - static_cast(1)); - set_interval(INTERVAL_POSITION_SYNC, static_cast(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::set_distance_measurement(int16_t distance) { + this->last_distance_measurement = distance; } void RATGDOComponent::query_status() { this->protocol_->query_status(); } -void RATGDOComponent::query_openings() -{ - this->protocol_->query_openings(); +void RATGDOComponent::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() -{ - this->protocol_->sync(); +void RATGDOComponent::cancel_door_state_expiry() { + this->cancel_timeout(TIMEOUT_DOOR_STATE_EXPIRY); } -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::smart_door_action(DoorAction target_direction) { + // target_direction must be OPEN or CLOSE + if (target_direction != DoorAction::OPEN && + target_direction != DoorAction::CLOSE) { + return; + } -void RATGDOComponent::cancel_door_state_expiry() -{ - this->cancel_timeout(TIMEOUT_DOOR_STATE_EXPIRY); -} + DoorState expected_state = (target_direction == DoorAction::OPEN) + ? DoorState::OPENING + : DoorState::CLOSING; + DoorState opposite_state = (target_direction == DoorAction::OPEN) + ? DoorState::CLOSING + : DoorState::OPENING; -void RATGDOComponent::smart_door_action(DoorAction target_direction) -{ - // target_direction must be OPEN or CLOSE - if (target_direction != DoorAction::OPEN && target_direction != DoorAction::CLOSE) { - return; - } + // 1. Try the discrete command first + this->door_action(target_direction); - DoorState expected_state = (target_direction == DoorAction::OPEN) ? DoorState::OPENING : DoorState::CLOSING; - DoorState opposite_state = (target_direction == DoorAction::OPEN) ? DoorState::CLOSING : DoorState::OPENING; - - // 1. Try the discrete command first - this->door_action(target_direction); - - // 2. Set a timeout to check if it worked - this->set_timeout(2000, [this, target_direction, expected_state, opposite_state]() { - if (*this->door_state == expected_state || - (*this->door_state == DoorState::OPEN && target_direction == DoorAction::OPEN) || - (*this->door_state == DoorState::CLOSED && target_direction == DoorAction::CLOSE)) { - // It worked (or is already at destination) - return; + // 2. Set a timeout to check if it worked + this->set_timeout( + 2000, [this, target_direction, expected_state, opposite_state]() { + if (*this->door_state == expected_state || + (*this->door_state == DoorState::OPEN && + target_direction == DoorAction::OPEN) || + (*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...", - target_direction == DoorAction::OPEN ? "OPEN" : "CLOSE"); + ESP_LOGW(TAG, "Discrete %s command ignored. Falling back to TOGGLE...", + target_direction == DoorAction::OPEN ? "OPEN" : "CLOSE"); // 3. Fallback: Send a TOGGLE this->door_action(DoorAction::TOGGLE); // 4. Check what the toggle did - this->on_door_state([this, target_direction, expected_state, opposite_state](DoorState s) { - if (s == opposite_state) { - // Wrong direction! Stop it. - 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); - } - }); - } - }); - }); -} + this->on_door_state([this, target_direction, expected_state, + opposite_state](DoorState s) { + if (s == opposite_state) { + // Wrong direction! Stop it. + ESP_LOGW(TAG, "Toggle went the wrong way. Stopping..."); + this->door_action(DoorAction::STOP); -void RATGDOComponent::door_open() -{ - if (*this->door_state == DoorState::OPENING) { - return; // gets ignored by opener - } - - 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 - } + // 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_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() -{ - if (*this->door_state != DoorState::OPENING && *this->door_state != DoorState::CLOSING) { - return; - } +void RATGDOComponent::door_open() { + if (*this->door_state == DoorState::OPENING) { + return; // gets ignored by opener + } + + 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() { + 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_action(DoorAction action) -{ - this->protocol_->door_action(action); +void RATGDOComponent::door_action(DoorAction action) { + this->protocol_->door_action(action); } -void RATGDOComponent::door_move_to_position(float position) -{ - if (*this->door_state == DoorState::OPENING || *this->door_state == DoorState::CLOSING) { - this->door_action(DoorAction::STOP); - this->on_door_state([this, position](DoorState s) { - if (s == DoorState::STOPPED) { - this->door_move_to_position(position); - } - }); - return; - } +void RATGDOComponent::door_move_to_position(float position) { + if (*this->door_state == DoorState::OPENING || + *this->door_state == DoorState::CLOSING) { + this->door_action(DoorAction::STOP); + this->on_door_state([this, position](DoorState s) { + if (s == DoorState::STOPPED) { + this->door_move_to_position(position); + } + }); + return; + } - auto delta = position - *this->door_position; - if (delta == 0) { - ESP_LOGD(TAG, "Door is already at position %.2f", position); - return; - } + auto delta = position - *this->door_position; + if (delta == 0) { + ESP_LOGD(TAG, "Door is already at position %.2f", position); + return; + } - auto duration = delta > 0 ? *this->opening_duration : -*this->closing_duration; - if (duration == 0) { - ESP_LOGW(TAG, "I don't know duration, ignoring move to position"); - return; - } + auto duration = + delta > 0 ? *this->opening_duration : -*this->closing_duration; + if (duration == 0) { + ESP_LOGW(TAG, "I don't know duration, ignoring move to position"); + return; + } - auto operation_time = 1000 * duration * delta; - this->door_move_delta = delta; + auto operation_time = 1000 * duration * delta; + this->door_move_delta = delta; - ESP_LOGD(TAG, "Moving to position %.2f (target duration %.1fs)", position, - operation_time / 1000.0); + ESP_LOGD(TAG, "Moving to position %.2f (target duration %.1fs)", position, + operation_time / 1000.0); - this->target_position_ = position; - this->target_direction_ = (delta > 0 ? DoorAction::OPEN : DoorAction::CLOSE); + this->target_position_ = position; + 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() -{ - if (this->door_start_moving != 0) { - ESP_LOGD(TAG, "Cancelling position callbacks"); - this->cancel_timeout(TIMEOUT_MOVE_TO_POSITION); - cancel_interval(INTERVAL_POSITION_SYNC); +void RATGDOComponent::cancel_position_sync_callbacks() { + if (this->door_start_moving != 0) { + ESP_LOGD(TAG, "Cancelling position callbacks"); + this->cancel_timeout(TIMEOUT_MOVE_TO_POSITION); + cancel_interval(INTERVAL_POSITION_SYNC); - this->door_start_moving = 0; - this->door_start_position = DOOR_POSITION_UNKNOWN; - this->door_move_delta = DOOR_DELTA_UNKNOWN; - } + this->door_start_moving = 0; + this->door_start_position = DOOR_POSITION_UNKNOWN; + this->door_move_delta = DOOR_DELTA_UNKNOWN; + } } -void RATGDOComponent::light_on() -{ - this->light_state = LightState::ON; - this->protocol_->light_action(LightAction::ON); +void RATGDOComponent::light_on() { + this->light_state = LightState::ON; + this->protocol_->light_action(LightAction::ON); } -void RATGDOComponent::light_off() -{ - this->light_state = LightState::OFF; - this->protocol_->light_action(LightAction::OFF); +void RATGDOComponent::light_off() { + this->light_state = LightState::OFF; + this->protocol_->light_action(LightAction::OFF); } -LightState RATGDOComponent::get_light_state() const -{ - return *this->light_state; +LightState RATGDOComponent::get_light_state() const { + return *this->light_state; } // Lock functions -void RATGDOComponent::lock() -{ - this->lock_state = LockState::LOCKED; - this->protocol_->lock_action(LockAction::LOCK); +void RATGDOComponent::lock() { + this->lock_state = LockState::LOCKED; + this->protocol_->lock_action(LockAction::LOCK); } -void RATGDOComponent::unlock() -{ - this->lock_state = LockState::UNLOCKED; - this->protocol_->lock_action(LockAction::UNLOCK); +void RATGDOComponent::unlock() { + this->lock_state = LockState::UNLOCKED; + this->protocol_->lock_action(LockAction::UNLOCK); } // Subscribe implementations are now templates in ratgdo.h -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/ratgdo.h b/components/ratgdo/ratgdo.h index 98ddec1..1837036 100644 --- a/components/ratgdo/ratgdo.h +++ b/components/ratgdo/ratgdo.h @@ -7,22 +7,22 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/core/defines.h" -#include "esphome/core/hal.h" -#include "esphome/core/preferences.h" - #include #include #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 "ratgdo_state.h" namespace esphome::ratgdo { -namespace secplus2 { class Secplus2; } +namespace secplus2 { +class Secplus2; +} class RATGDOComponent; typedef Parented RATGDOClient; @@ -30,214 +30,222 @@ typedef Parented RATGDOClient; const float DOOR_POSITION_UNKNOWN = -1.0; const float DOOR_DELTA_UNKNOWN = -2.0; - - class RATGDOComponent : public Component { -public: - RATGDOComponent() - { - } + public: + RATGDOComponent() {} - void setup() override; - void loop() override; - void dump_config() override; - void on_shutdown() override; + void setup() override; + void loop() override; + void dump_config() 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 }; - single_observable opening_duration { 0 }; - float start_closing { -1 }; - single_observable closing_duration { 0 }; + float start_opening{-1}; + single_observable opening_duration{0}; + float start_closing{-1}; + single_observable closing_duration{0}; - observable last_distance_measurement { 0 }; + observable + last_distance_measurement{0}; - single_observable openings { 0 }; // number of times the door has been opened + single_observable openings{ + 0}; // number of times the door has been opened - observable door_state { DoorState::UNKNOWN }; - observable door_position { DOOR_POSITION_UNKNOWN }; + observable door_state{ + DoorState::UNKNOWN}; + observable door_position{ + DOOR_POSITION_UNKNOWN}; - unsigned long door_start_moving { 0 }; - float door_start_position { DOOR_POSITION_UNKNOWN }; - float door_move_delta { DOOR_DELTA_UNKNOWN }; - uint16_t position_sync_remaining_ { 0 }; - float target_position_ { DOOR_POSITION_UNKNOWN }; - DoorAction target_direction_ { DoorAction::UNKNOWN }; + unsigned long door_start_moving{0}; + float door_start_position{DOOR_POSITION_UNKNOWN}; + float door_move_delta{DOOR_DELTA_UNKNOWN}; + uint16_t position_sync_remaining_{0}; + float target_position_{DOOR_POSITION_UNKNOWN}; + DoorAction target_direction_{DoorAction::UNKNOWN}; - single_observable light_state { LightState::UNKNOWN }; - single_observable lock_state { LockState::UNKNOWN }; + single_observable light_state{LightState::UNKNOWN}; + single_observable lock_state{LockState::UNKNOWN}; - OnceCallbacks on_door_state_; + OnceCallbacks on_door_state_; - single_observable synced { false }; - single_observable> sync_failed { nullopt }; + single_observable synced{false}; + single_observable> sync_failed{nullopt}; - 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_output_gdo_pin(InternalGPIOPin* pin) { this->output_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 received(const LightState light_state); - void received(const LockState lock_state); - void received(const LightAction light_action); - void received(const Openings openings); + void door_action(DoorAction action); + void smart_door_action(DoorAction target_direction); + void door_move_to_position(float position); + 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); - // door - void door_toggle(); - void door_open(); - void door_close(); - void door_stop(); + // light + void light_on(); + void light_off(); + LightState get_light_state() const; - void door_action(DoorAction action); - void smart_door_action(DoorAction target_direction); - void door_move_to_position(float position); - 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); + // lock + void lock(); + void unlock(); - // light - void light_on(); - void light_off(); - LightState get_light_state() const; + // button functionality + void query_status(); + void query_openings(); + void sync(); - // lock - void lock(); - void unlock(); + using Component::set_timeout; - // button functionality - void query_status(); - void query_openings(); - void sync(); + void set_door_state_expiry(); + void cancel_door_state_expiry(); - using Component::set_timeout; - - void set_door_state_expiry(); - void cancel_door_state_expiry(); - - // Register a one-shot door state callback with automatic expiry. - // - // Handles secplus1's nested callback chains where opening from - // STOPPED requires multiple state transitions: - // - // on_door_state(outer_cb) // wait for CLOSING - // → set_door_state_expiry() // expiry A - // → [door reports CLOSING] - // → outer_cb fires, calls: - // toggle_door() - // on_door_state(inner_cb) // wait for STOPPED - // → set_door_state_expiry() // expiry B (replaces A) - // → [door reports STOPPED] - // → inner_cb fires - // toggle_door() // door now opening - // count()==0 → cancel expiry B - // - // The user callback runs BEFORE the expiry check because it may - // re-arm the chain by calling on_door_state() again. If it does, - // the new call sets expiry B which replaces expiry A (same timeout - // ID = replace, not add). We only cancel expiry when count()==0, - // meaning no new callback was queued — otherwise we'd cancel - // expiry B here and leave the inner callback without protection. - template - void on_door_state(F&& callback) - { - using Cb = std::decay_t; - this->on_door_state_([this, cb = Cb(std::forward(callback))](DoorState s) { - cb(s); - if (!this->on_door_state_.count()) { - this->cancel_door_state_expiry(); - } + // Register a one-shot door state callback with automatic expiry. + // + // Handles secplus1's nested callback chains where opening from + // STOPPED requires multiple state transitions: + // + // on_door_state(outer_cb) // wait for CLOSING + // → set_door_state_expiry() // expiry A + // → [door reports CLOSING] + // → outer_cb fires, calls: + // toggle_door() + // on_door_state(inner_cb) // wait for STOPPED + // → set_door_state_expiry() // expiry B (replaces A) + // → [door reports STOPPED] + // → inner_cb fires + // toggle_door() // door now opening + // count()==0 → cancel expiry B + // + // The user callback runs BEFORE the expiry check because it may + // re-arm the chain by calling on_door_state() again. If it does, + // the new call sets expiry B which replaces expiry A (same timeout + // ID = replace, not add). We only cancel expiry when count()==0, + // meaning no new callback was queued — otherwise we'd cancel + // expiry B here and leave the inner callback without protection. + template + void on_door_state(F&& callback) { + using Cb = std::decay_t; + this->on_door_state_( + [this, cb = Cb(std::forward(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) - // Callbacks must be trivially copyable and fit in Callback storage - // (3 * sizeof(void*)), e.g. [this] or [this, f] lambdas. - // Enforced at compile time by Callback::create(). - template - void subscribe_opening_duration(F&& f); - template - void subscribe_closing_duration(F&& f); - template - void subscribe_openings(F&& f); - template - void subscribe_door_state(F&& f); - template - void subscribe_light_state(F&& f); - template - void subscribe_lock_state(F&& f); - template - void subscribe_sync_failed(F&& f); - template - void subscribe_distance_measurement(F&& f); + // children subscriptions — type-safe templates (no std::function) + // Callbacks must be trivially copyable and fit in Callback storage + // (3 * sizeof(void*)), e.g. [this] or [this, f] lambdas. + // Enforced at compile time by Callback::create(). + template + void subscribe_opening_duration(F&& f); + template + void subscribe_closing_duration(F&& f); + template + void subscribe_openings(F&& f); + template + void subscribe_door_state(F&& f); + template + void subscribe_light_state(F&& f); + template + void subscribe_lock_state(F&& f); + template + void subscribe_sync_failed(F&& f); + template + void subscribe_distance_measurement(F&& f); -protected: - // Pointers first (4-byte aligned) - secplus2::Secplus2* protocol_; - InternalGPIOPin* output_gdo_pin_; - InternalGPIOPin* input_gdo_pin_; + protected: + // Pointers first (4-byte aligned) + secplus2::Secplus2* protocol_; + InternalGPIOPin* output_gdo_pin_; + InternalGPIOPin* input_gdo_pin_; - // Bool members packed into bitfield - struct { - uint8_t reserved : 8; // Reserved for future use - } flags_ { 0 }; + // Bool members packed into bitfield + struct { + uint8_t reserved : 8; // Reserved for future use + } flags_{0}; - // Subscriber counters for defer name allocation - uint8_t door_state_sub_num_ { 0 }; - uint8_t distance_sub_num_ { 0 }; -}; // RATGDOComponent + // Subscriber counters for defer name allocation + uint8_t door_state_sub_num_{0}; + uint8_t distance_sub_num_{0}; +}; // RATGDOComponent 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) -{ - if (count == 0) { - log_subscriber_overflow(observable_name, count); - return base; - } - if (counter >= count) { - log_subscriber_overflow(observable_name, count); - return base + count - 1; // reuse last ID to avoid collision with first subscriber - } - return base + counter++; +inline uint32_t get_scheduler_id(uint32_t base, uint32_t count, + uint8_t& counter, + const LogString* observable_name) { + if (count == 0) { + log_subscriber_overflow(observable_name, count); + return base; + } + if (counter >= count) { + log_subscriber_overflow(observable_name, count); + return base + count - + 1; // reuse last ID to avoid collision with first subscriber + } + return base + counter++; } // Scheduler IDs using uint32_t ranges to avoid heap allocations // Bases are auto-generated from counts to prevent ID conflicts 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 - inline constexpr uint32_t DEFER_DOOR_STATE_COUNT = RATGDO_MAX_DOOR_STATE_SUBSCRIBERS; - inline constexpr uint32_t DEFER_DOOR_STATE_BASE = INTERVAL_POSITION_SYNC + 1; +// 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_BASE = INTERVAL_POSITION_SYNC + 1; - inline constexpr uint32_t DEFER_DISTANCE_COUNT = RATGDO_MAX_DISTANCE_SUBSCRIBERS; - 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; +inline constexpr uint32_t DEFER_DISTANCE_COUNT = + RATGDO_MAX_DISTANCE_SUBSCRIBERS; +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 - enum : uint32_t { - DEFER_OPENING_DURATION = DEFER_DISTANCE_END, - DEFER_CLOSING_DURATION, - DEFER_OPENINGS, - DEFER_LIGHT_STATE, - DEFER_LOCK_STATE, +// Single-subscriber IDs +enum : uint32_t { + DEFER_OPENING_DURATION = DEFER_DISTANCE_END, + DEFER_CLOSING_DURATION, + DEFER_OPENINGS, + DEFER_LIGHT_STATE, + DEFER_LOCK_STATE, - // Named timeout IDs (replacing string-based names) - TIMEOUT_DOOR_QUERY_STATE, - TIMEOUT_DOOR_ACTION, - TIMEOUT_MOVE_TO_POSITION, - TIMEOUT_DOOR_STATE_EXPIRY, - TIMEOUT_SYNC, - }; -} // namespace scheduler_ids + // Named timeout IDs (replacing string-based names) + TIMEOUT_DOOR_QUERY_STATE, + TIMEOUT_DOOR_ACTION, + TIMEOUT_MOVE_TO_POSITION, + TIMEOUT_DOOR_STATE_EXPIRY, + TIMEOUT_SYNC, +}; +} // namespace scheduler_ids // Template implementations for subscribe methods. // 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. template -void RATGDOComponent::subscribe_opening_duration(F&& f) -{ - this->opening_duration.subscribe([this, f](float state) { - defer(scheduler_ids::DEFER_OPENING_DURATION, [f, state] { f(state); }); - }); +void RATGDOComponent::subscribe_opening_duration(F&& f) { + this->opening_duration.subscribe([this, f](float state) { + defer(scheduler_ids::DEFER_OPENING_DURATION, [f, state] { f(state); }); + }); } template -void RATGDOComponent::subscribe_closing_duration(F&& f) -{ - this->closing_duration.subscribe([this, f](float state) { - defer(scheduler_ids::DEFER_CLOSING_DURATION, [f, state] { f(state); }); - }); +void RATGDOComponent::subscribe_closing_duration(F&& f) { + this->closing_duration.subscribe([this, f](float state) { + defer(scheduler_ids::DEFER_CLOSING_DURATION, [f, state] { f(state); }); + }); } template -void RATGDOComponent::subscribe_openings(F&& f) -{ - this->openings.subscribe([this, f](uint16_t state) { - defer(scheduler_ids::DEFER_OPENINGS, [f, state] { f(state); }); - }); +void RATGDOComponent::subscribe_openings(F&& f) { + this->openings.subscribe([this, f](uint16_t state) { + defer(scheduler_ids::DEFER_OPENINGS, [f, state] { f(state); }); + }); } template -void RATGDOComponent::subscribe_door_state(F&& f) -{ - uint32_t id = get_scheduler_id(scheduler_ids::DEFER_DOOR_STATE_BASE, scheduler_ids::DEFER_DOOR_STATE_COUNT, - this->door_state_sub_num_, LOG_STR("door_state")); - 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); }); - }); +void RATGDOComponent::subscribe_door_state(F&& f) { + uint32_t id = + get_scheduler_id(scheduler_ids::DEFER_DOOR_STATE_BASE, + scheduler_ids::DEFER_DOOR_STATE_COUNT, + this->door_state_sub_num_, LOG_STR("door_state")); + 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); }); + }); } template -void RATGDOComponent::subscribe_light_state(F&& f) -{ - this->light_state.subscribe([this, f](LightState state) { - defer(scheduler_ids::DEFER_LIGHT_STATE, [f, state] { f(state); }); - }); +void RATGDOComponent::subscribe_light_state(F&& f) { + this->light_state.subscribe([this, f](LightState state) { + defer(scheduler_ids::DEFER_LIGHT_STATE, [f, state] { f(state); }); + }); } template -void RATGDOComponent::subscribe_lock_state(F&& f) -{ - this->lock_state.subscribe([this, f](LockState state) { - defer(scheduler_ids::DEFER_LOCK_STATE, [f, state] { f(state); }); - }); +void RATGDOComponent::subscribe_lock_state(F&& f) { + this->lock_state.subscribe([this, f](LockState state) { + defer(scheduler_ids::DEFER_LOCK_STATE, [f, state] { f(state); }); + }); } template -void RATGDOComponent::subscribe_sync_failed(F&& f) -{ - this->sync_failed.subscribe(std::forward(f)); +void RATGDOComponent::subscribe_sync_failed(F&& f) { + this->sync_failed.subscribe(std::forward(f)); } template -void RATGDOComponent::subscribe_distance_measurement(F&& f) -{ - uint32_t id = get_scheduler_id(scheduler_ids::DEFER_DISTANCE_BASE, scheduler_ids::DEFER_DISTANCE_COUNT, - this->distance_sub_num_, LOG_STR("distance_measurement")); - this->last_distance_measurement.subscribe([this, f, id](int16_t state) { - defer(id, [f, state] { f(state); }); - }); +void RATGDOComponent::subscribe_distance_measurement(F&& f) { + uint32_t id = get_scheduler_id( + scheduler_ids::DEFER_DISTANCE_BASE, scheduler_ids::DEFER_DISTANCE_COUNT, + this->distance_sub_num_, LOG_STR("distance_measurement")); + this->last_distance_measurement.subscribe( + [this, f, id](int16_t state) { defer(id, [f, state] { f(state); }); }); } -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/ratgdo_state.cpp b/components/ratgdo/ratgdo_state.cpp index c2ba994..b3822a0 100644 --- a/components/ratgdo/ratgdo_state.cpp +++ b/components/ratgdo/ratgdo_state.cpp @@ -2,18 +2,17 @@ namespace esphome::ratgdo { -LightState light_state_toggle(LightState state) -{ - switch (state) { +LightState light_state_toggle(LightState state) { + switch (state) { case LightState::OFF: - return LightState::ON; + return LightState::ON; case LightState::ON: - return LightState::OFF; - // 2 and 3 appears sometimes + return LightState::OFF; + // 2 and 3 appears sometimes case LightState::UNKNOWN: default: - return LightState::UNKNOWN; - } + return LightState::UNKNOWN; + } } -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/ratgdo_state.h b/components/ratgdo/ratgdo_state.h index e68ac8a..4495997 100644 --- a/components/ratgdo/ratgdo_state.h +++ b/components/ratgdo/ratgdo_state.h @@ -6,121 +6,125 @@ ************************************/ #pragma once -#include "esphome/core/defines.h" #include +#include "esphome/core/defines.h" + namespace esphome::ratgdo { enum class DoorState : uint8_t { - UNKNOWN = 0, - OPEN = 1, - CLOSED = 2, - STOPPED = 3, - OPENING = 4, - CLOSING = 5 + UNKNOWN = 0, + OPEN = 1, + CLOSED = 2, + STOPPED = 3, + OPENING = 4, + CLOSING = 5 }; inline const char* DoorState_to_string(DoorState e) { - static const char* const names[] = {"UNKNOWN", "OPEN", "CLOSED", "STOPPED", "OPENING", "CLOSING"}; - auto i = static_cast(e); - return (i <= static_cast(DoorState::CLOSING)) ? names[i] : "UNKNOWN"; + static const char* const names[] = {"UNKNOWN", "OPEN", "CLOSED", + "STOPPED", "OPENING", "CLOSING"}; + auto i = static_cast(e); + return (i <= static_cast(DoorState::CLOSING)) ? names[i] : "UNKNOWN"; } inline DoorState to_DoorState(uint8_t t, DoorState unknown) { - return (t <= static_cast(DoorState::CLOSING)) ? static_cast(t) : unknown; + return (t <= static_cast(DoorState::CLOSING)) + ? static_cast(t) + : unknown; } /// Enum for all states a the light can be in. -enum class LightState : uint8_t { - OFF = 0, - ON = 1, - UNKNOWN = 2 -}; +enum class LightState : uint8_t { OFF = 0, ON = 1, UNKNOWN = 2 }; inline const char* LightState_to_string(LightState e) { - static const char* const names[] = {"OFF", "ON", "UNKNOWN"}; - auto i = static_cast(e); - return (i <= static_cast(LightState::UNKNOWN)) ? names[i] : "UNKNOWN"; + static const char* const names[] = {"OFF", "ON", "UNKNOWN"}; + auto i = static_cast(e); + return (i <= static_cast(LightState::UNKNOWN)) ? names[i] + : "UNKNOWN"; } inline LightState to_LightState(uint8_t t, LightState unknown) { - return (t <= static_cast(LightState::UNKNOWN)) ? static_cast(t) : unknown; + return (t <= static_cast(LightState::UNKNOWN)) + ? static_cast(t) + : unknown; } LightState light_state_toggle(LightState state); /// Enum for all states a the lock can be in. -enum class LockState : uint8_t { - UNLOCKED = 0, - LOCKED = 1, - UNKNOWN = 2 -}; +enum class LockState : uint8_t { UNLOCKED = 0, LOCKED = 1, UNKNOWN = 2 }; inline const char* LockState_to_string(LockState e) { - static const char* const names[] = {"UNLOCKED", "LOCKED", "UNKNOWN"}; - auto i = static_cast(e); - return (i <= static_cast(LockState::UNKNOWN)) ? names[i] : "UNKNOWN"; + static const char* const names[] = {"UNLOCKED", "LOCKED", "UNKNOWN"}; + auto i = static_cast(e); + return (i <= static_cast(LockState::UNKNOWN)) ? names[i] : "UNKNOWN"; } inline LockState to_LockState(uint8_t t, LockState unknown) { - return (t <= static_cast(LockState::UNKNOWN)) ? static_cast(t) : unknown; + return (t <= static_cast(LockState::UNKNOWN)) + ? static_cast(t) + : unknown; } // actions -enum class LightAction : uint8_t { - OFF = 0, - ON = 1, - TOGGLE = 2, - UNKNOWN = 3 -}; +enum class LightAction : uint8_t { OFF = 0, ON = 1, TOGGLE = 2, UNKNOWN = 3 }; inline const char* LightAction_to_string(LightAction e) { - static const char* const names[] = {"OFF", "ON", "TOGGLE", "UNKNOWN"}; - auto i = static_cast(e); - return (i <= static_cast(LightAction::UNKNOWN)) ? names[i] : "UNKNOWN"; + static const char* const names[] = {"OFF", "ON", "TOGGLE", "UNKNOWN"}; + auto i = static_cast(e); + return (i <= static_cast(LightAction::UNKNOWN)) ? names[i] + : "UNKNOWN"; } inline LightAction to_LightAction(uint8_t t, LightAction unknown) { - return (t <= static_cast(LightAction::UNKNOWN)) ? static_cast(t) : unknown; + return (t <= static_cast(LightAction::UNKNOWN)) + ? static_cast(t) + : unknown; } -enum class LockAction : uint8_t { - UNLOCK = 0, - LOCK = 1, - UNKNOWN = 3 -}; +enum class LockAction : uint8_t { UNLOCK = 0, LOCK = 1, UNKNOWN = 3 }; inline const char* LockAction_to_string(LockAction e) { - static const char* const names[] = {"UNLOCK", "LOCK", "UNKNOWN", "UNKNOWN"}; - auto i = static_cast(e); - return (i <= static_cast(LockAction::UNKNOWN)) ? names[i] : "UNKNOWN"; + static const char* const names[] = {"UNLOCK", "LOCK", "UNKNOWN", "UNKNOWN"}; + auto i = static_cast(e); + return (i <= static_cast(LockAction::UNKNOWN)) ? names[i] + : "UNKNOWN"; } inline LockAction to_LockAction(uint8_t t, LockAction unknown) { - return (t == static_cast(LockAction::UNLOCK) || t == static_cast(LockAction::LOCK) || t == static_cast(LockAction::UNKNOWN)) ? static_cast(t) : unknown; + return (t == static_cast(LockAction::UNLOCK) || + t == static_cast(LockAction::LOCK) || + t == static_cast(LockAction::UNKNOWN)) + ? static_cast(t) + : unknown; } enum class DoorAction : uint8_t { - CLOSE = 0, - OPEN = 1, - TOGGLE = 2, - STOP = 3, - UNKNOWN = 4 + CLOSE = 0, + OPEN = 1, + TOGGLE = 2, + STOP = 3, + UNKNOWN = 4 }; inline const char* DoorAction_to_string(DoorAction e) { - static const char* const names[] = {"CLOSE", "OPEN", "TOGGLE", "STOP", "UNKNOWN"}; - auto i = static_cast(e); - return (i <= static_cast(DoorAction::UNKNOWN)) ? names[i] : "UNKNOWN"; + static const char* const names[] = {"CLOSE", "OPEN", "TOGGLE", "STOP", + "UNKNOWN"}; + auto i = static_cast(e); + return (i <= static_cast(DoorAction::UNKNOWN)) ? names[i] + : "UNKNOWN"; } inline DoorAction to_DoorAction(uint8_t t, DoorAction unknown) { - return (t <= static_cast(DoorAction::UNKNOWN)) ? static_cast(t) : unknown; + return (t <= static_cast(DoorAction::UNKNOWN)) + ? static_cast(t) + : unknown; } struct Openings { - uint16_t count; - uint8_t flag; + uint16_t count; + uint8_t flag; }; -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/ratgdo_uart_esp32.cpp b/components/ratgdo/ratgdo_uart_esp32.cpp index ac3c11d..2f42d0e 100644 --- a/components/ratgdo/ratgdo_uart_esp32.cpp +++ b/components/ratgdo/ratgdo_uart_esp32.cpp @@ -1,17 +1,16 @@ #include "ratgdo_uart_esp32.h" -#include "esphome/core/log.h" -#include - -#include -#include // for rmt_get_channel_id (used once during init) - #include +#include +#include +#include // for rmt_get_channel_id (used once during init) #include #include #include #include +#include "esphome/core/log.h" + namespace esphome::ratgdo { 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; // 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_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_TX_SIGNAL_IDX = U1TXD_OUT_IDX; -RatgdoUART::RatgdoUART() { } +RatgdoUART::RatgdoUART() {} -RatgdoUART::~RatgdoUART() -{ - if (this->is_initialized_) { - uart_driver_delete((uart_port_t)this->uart_num_); - if (this->rmt_copy_encoder_) { - rmt_del_encoder(this->rmt_copy_encoder_); - 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; - } +RatgdoUART::~RatgdoUART() { + if (this->is_initialized_) { + uart_driver_delete((uart_port_t)this->uart_num_); + if (this->rmt_copy_encoder_) { + rmt_del_encoder(this->rmt_copy_encoder_); + 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; + } + } } void RatgdoUART::begin(int baud, RatgdoUARTConfig config, int rx_pin, - int tx_pin, bool invert) -{ - this->tx_pin_ = tx_pin; - this->rx_pin_ = rx_pin; - this->baud_ = baud; - this->inverted_ = invert; + int tx_pin, bool invert) { + this->tx_pin_ = tx_pin; + this->rx_pin_ = rx_pin; + this->baud_ = baud; + this->inverted_ = invert; - this->uart_num_ = UART_PORT; + this->uart_num_ = UART_PORT; - uart_config_t uart_config = { }; - uart_config.baud_rate = baud; - uart_config.data_bits = UART_DATA_8_BITS; - uart_config.parity = (config == RATGDO_UART_8E1) ? UART_PARITY_EVEN : UART_PARITY_DISABLE; - uart_config.stop_bits = UART_STOP_BITS_1; - uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; - uart_config.source_clk = UART_SCLK_APB; + uart_config_t uart_config = {}; + uart_config.baud_rate = baud; + uart_config.data_bits = UART_DATA_8_BITS; + uart_config.parity = + (config == RATGDO_UART_8E1) ? UART_PARITY_EVEN : UART_PARITY_DISABLE; + uart_config.stop_bits = UART_STOP_BITS_1; + uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; + uart_config.source_clk = UART_SCLK_APB; - ESP_ERROR_CHECK( - uart_driver_install((uart_port_t)this->uart_num_, 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_driver_install((uart_port_t)this->uart_num_, + UART_RX_BUFFER_SIZE, 0, 0, NULL, 0)); + ESP_ERROR_CHECK( + uart_param_config((uart_port_t)this->uart_num_, &uart_config)); - rmt_tx_channel_config_t tx_chan_config = { }; - tx_chan_config.gpio_num = (gpio_num_t)tx_pin; - tx_chan_config.clk_src = RMT_CLK_SRC_DEFAULT; - tx_chan_config.resolution_hz = RMT_RESOLUTION_HZ; - tx_chan_config.mem_block_symbols = RMT_MEM_BLOCK_SYMBOLS; - tx_chan_config.trans_queue_depth = RMT_TRANS_QUEUE_DEPTH; - tx_chan_config.flags.invert_out = 0; - ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &this->rmt_chan_handle_)); + rmt_tx_channel_config_t tx_chan_config = {}; + tx_chan_config.gpio_num = (gpio_num_t)tx_pin; + tx_chan_config.clk_src = RMT_CLK_SRC_DEFAULT; + tx_chan_config.resolution_hz = RMT_RESOLUTION_HZ; + tx_chan_config.mem_block_symbols = RMT_MEM_BLOCK_SYMBOLS; + tx_chan_config.trans_queue_depth = RMT_TRANS_QUEUE_DEPTH; + tx_chan_config.flags.invert_out = 0; + ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &this->rmt_chan_handle_)); - rmt_copy_encoder_config_t copy_encoder_config = { }; - ESP_ERROR_CHECK( - rmt_new_copy_encoder(©_encoder_config, &this->rmt_copy_encoder_)); + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_ERROR_CHECK( + rmt_new_copy_encoder(©_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. - // 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. - ESP_ERROR_CHECK(rmt_get_channel_id(this->rmt_chan_handle_, &this->rmt_channel_id_)); + // Cache the channel ID for GPIO matrix switching during preamble. + // 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. + 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, - UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + ESP_ERROR_CHECK(uart_set_pin((uart_port_t)this->uart_num_, tx_pin, rx_pin, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); - if (invert) { - uart_set_line_inverse((uart_port_t)this->uart_num_, - UART_SIGNAL_TXD_INV | UART_SIGNAL_RXD_INV); - } + if (invert) { + uart_set_line_inverse((uart_port_t)this->uart_num_, + UART_SIGNAL_TXD_INV | UART_SIGNAL_RXD_INV); + } - this->is_initialized_ = true; - ESP_LOGD(TAG, "Hardware UART and RMT initialized on TX=%d RX=%d", tx_pin, - rx_pin); + this->is_initialized_ = true; + ESP_LOGD(TAG, "Hardware UART and RMT initialized on TX=%d RX=%d", tx_pin, + rx_pin); } -void RatgdoUART::transmit_secplus2_preamble() -{ - if (!this->is_initialized_) - return; +void RatgdoUART::transmit_secplus2_preamble() { + if (!this->is_initialized_) return; - // 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_, - false, false); + // 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_, 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 - // 1 byte followed by one STOP bit, which indicates to the receiving end - // that the start of the message follows. - // 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_symbol_word_t symbols[1]; - symbols[0].duration0 = PREAMBLE_DURATION_US; - symbols[0].level0 = 1; - symbols[0].duration1 = PREAMBLE_MARK_US; - symbols[0].level1 = 0; + // 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 + // that the start of the message follows. + // 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_symbol_word_t symbols[1]; + symbols[0].duration0 = PREAMBLE_DURATION_US; + symbols[0].level0 = 1; + symbols[0].duration1 = PREAMBLE_MARK_US; + symbols[0].level1 = 0; - rmt_transmit_config_t transmit_config = { }; - transmit_config.loop_count = 0; - rmt_transmit(this->rmt_chan_handle_, this->rmt_copy_encoder_, symbols, - sizeof(symbols), &transmit_config); - rmt_tx_wait_all_done(this->rmt_chan_handle_, -1); + rmt_transmit_config_t transmit_config = {}; + transmit_config.loop_count = 0; + rmt_transmit(this->rmt_chan_handle_, this->rmt_copy_encoder_, symbols, + sizeof(symbols), &transmit_config); + rmt_tx_wait_all_done(this->rmt_chan_handle_, -1); - // Switch GPIO matrix back to UART TX - esp_rom_gpio_connect_out_signal(this->tx_pin_, UART_TX_SIGNAL_IDX, false, false); - esp_rom_delay_us(SIGNAL_SETTLE_US); + // Switch GPIO matrix back to UART TX + esp_rom_gpio_connect_out_signal(this->tx_pin_, UART_TX_SIGNAL_IDX, false, + false); + esp_rom_delay_us(SIGNAL_SETTLE_US); } -void RatgdoUART::write(const uint8_t* data, size_t len) -{ - if (this->is_initialized_) { - 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); - } +void RatgdoUART::write(const uint8_t* data, size_t len) { + if (this->is_initialized_) { + 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); + } } void RatgdoUART::write(uint8_t data) { write(&data, 1); } -int RatgdoUART::available() -{ - if (!this->is_initialized_) - return 0; - size_t length = 0; - uart_get_buffered_data_len((uart_port_t)this->uart_num_, &length); - return length; +int RatgdoUART::available() { + if (!this->is_initialized_) return 0; + size_t length = 0; + uart_get_buffered_data_len((uart_port_t)this->uart_num_, &length); + return length; } -int RatgdoUART::read() -{ - if (!this->is_initialized_) - return -1; - uint8_t data = 0; - int len = uart_read_bytes((uart_port_t)this->uart_num_, &data, 1, 0); - if (len > 0) { - return data; - } - return -1; +int RatgdoUART::read() { + if (!this->is_initialized_) return -1; + uint8_t data = 0; + int len = uart_read_bytes((uart_port_t)this->uart_num_, &data, 1, 0); + if (len > 0) { + return data; + } + return -1; } -void RatgdoUART::on_shutdown() -{ - if (this->is_initialized_) { - // Unmap the matrix output signal so that UART peripheral resets do not - // pull the hardware line dominant. - esp_rom_gpio_connect_out_signal(this->tx_pin_, SIG_GPIO_OUT_IDX, false, false); - gpio_set_direction((gpio_num_t)this->tx_pin_, GPIO_MODE_INPUT); - gpio_set_direction((gpio_num_t)this->rx_pin_, GPIO_MODE_INPUT); - } +void RatgdoUART::on_shutdown() { + if (this->is_initialized_) { + // Unmap the matrix output signal so that UART peripheral resets do not + // pull the hardware line dominant. + esp_rom_gpio_connect_out_signal(this->tx_pin_, SIG_GPIO_OUT_IDX, false, + false); + gpio_set_direction((gpio_num_t)this->tx_pin_, GPIO_MODE_INPUT); + gpio_set_direction((gpio_num_t)this->rx_pin_, GPIO_MODE_INPUT); + } } -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/ratgdo_uart_esp32.h b/components/ratgdo/ratgdo_uart_esp32.h index e61d1c0..34304b5 100644 --- a/components/ratgdo/ratgdo_uart_esp32.h +++ b/components/ratgdo/ratgdo_uart_esp32.h @@ -1,53 +1,53 @@ #pragma once -#include "esphome/core/defines.h" - #include #include #include +#include "esphome/core/defines.h" + namespace esphome::ratgdo { enum RatgdoUARTConfig { - RATGDO_UART_8N1, - RATGDO_UART_8E1, + RATGDO_UART_8N1, + RATGDO_UART_8E1, }; class RatgdoUART { -public: - RatgdoUART(); - ~RatgdoUART(); + public: + RatgdoUART(); + ~RatgdoUART(); - void begin(int baud, RatgdoUARTConfig config, int rx_pin, int tx_pin, - bool invert); - void write(const uint8_t* data, size_t len); - void write(uint8_t data); - int available(); - int read(); - void enableIntTx(bool enable) { } - void enableAutoBaud(bool enable) { } - int baudRate() { return this->baud_; } + void begin(int baud, RatgdoUARTConfig config, int rx_pin, int tx_pin, + bool invert); + void write(const uint8_t* data, size_t len); + void write(uint8_t data); + int available(); + int read(); + void enableIntTx(bool enable) {} + void enableAutoBaud(bool enable) {} + int baudRate() { return this->baud_; } - // Sends the SecPlus 2.0 preamble using RMT - void transmit_secplus2_preamble(); + // Sends the SecPlus 2.0 preamble using RMT + void transmit_secplus2_preamble(); - void on_shutdown(); + void on_shutdown(); -private: - // Pointers (4 bytes on 32-bit) - rmt_channel_handle_t rmt_chan_handle_ { nullptr }; - rmt_encoder_handle_t rmt_copy_encoder_ { nullptr }; + private: + // Pointers (4 bytes on 32-bit) + rmt_channel_handle_t rmt_chan_handle_{nullptr}; + rmt_encoder_handle_t rmt_copy_encoder_{nullptr}; - // 4-byte members - int tx_pin_ { -1 }; - int rx_pin_ { -1 }; - int baud_ { 9600 }; - int uart_num_ { -1 }; - int rmt_channel_id_ { 0 }; + // 4-byte members + int tx_pin_{-1}; + int rx_pin_{-1}; + int baud_{9600}; + int uart_num_{-1}; + int rmt_channel_id_{0}; - // 1-byte members packed at the end - bool inverted_ { true }; - bool is_initialized_ { false }; + // 1-byte members packed at the end + bool inverted_{true}; + bool is_initialized_{false}; }; -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/secplus2.cpp b/components/ratgdo/secplus2.cpp index a4b9849..d1bd5f9 100644 --- a/components/ratgdo/secplus2.cpp +++ b/components/ratgdo/secplus2.cpp @@ -1,11 +1,11 @@ #include "secplus2.h" -#include "ratgdo.h" #include "esphome/core/gpio.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/scheduler.h" +#include "ratgdo.h" extern "C" { #include "secplus.h" @@ -14,400 +14,418 @@ extern "C" { namespace esphome::ratgdo { namespace secplus2 { - using namespace scheduler_ids; +using namespace scheduler_ids; - // MAX_CODES_WITHOUT_FLASH_WRITE is a bit of a guess - // since we write the flash at most every every 1min - // - // We want the rolling counter to be high enough that the - // GDO will accept the command after an unexpected reboot - // that did not save the counter to flash in time which - // results in the rolling counter being behind what the GDO - // expects. - static const uint8_t MAX_CODES_WITHOUT_FLASH_WRITE = 60; +// MAX_CODES_WITHOUT_FLASH_WRITE is a bit of a guess +// since we write the flash at most every every 1min +// +// We want the rolling counter to be high enough that the +// GDO will accept the command after an unexpected reboot +// that did not save the counter to flash in time which +// results in the rolling counter being behind what the GDO +// expects. +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) - { - this->ratgdo_ = ratgdo; - this->scheduler_ = scheduler; - this->tx_pin_ = tx_pin; - this->rx_pin_ = rx_pin; +void Secplus2::setup(RATGDOComponent* ratgdo, Scheduler* scheduler, + InternalGPIOPin* rx_pin, InternalGPIOPin* tx_pin) { + this->ratgdo_ = ratgdo; + this->scheduler_ = scheduler; + this->tx_pin_ = tx_pin; + this->rx_pin_ = rx_pin; - if (mqtt::global_mqtt_client != nullptr) { - this->mqtt_rolling_code_topic_ = 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"; - } + if (mqtt::global_mqtt_client != nullptr) { + this->mqtt_rolling_code_topic_ = + 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(3497851610U); // fnv1_hash("ratgdo_client_id") - uint32_t stored_client_id; - if (this->client_id_pref_.load(&stored_client_id)) { - this->client_id_ = stored_client_id; - ESP_LOGI(TAG, "Restored Client ID from flash: 0x%04X", (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); + this->client_id_pref_ = global_preferences->make_preference( + 3497851610U); // fnv1_hash("ratgdo_client_id") + uint32_t stored_client_id; + if (this->client_id_pref_.load(&stored_client_id)) { + this->client_id_ = stored_client_id; + ESP_LOGI(TAG, "Restored Client ID from flash: 0x%04X", + (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( - 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); - } - } - }, - 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); - } + } + }, + 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(1868352652U); // fnv1_hash("ratgdo_rolling_code") - uint32_t rolling_code; - if (this->rolling_code_pref_.load(&rolling_code)) { - this->rolling_code_counter_ = rolling_code; - ESP_LOGI(TAG, "Restored rolling code from flash: %u", rolling_code); - if (mqtt::global_mqtt_client != nullptr) { - mqtt::global_mqtt_client->publish(this->mqtt_rolling_code_topic_, std::to_string(rolling_code), 0, true); + this->rolling_code_pref_ = global_preferences->make_preference( + 1868352652U); // fnv1_hash("ratgdo_rolling_code") + uint32_t rolling_code; + if (this->rolling_code_pref_.load(&rolling_code)) { + this->rolling_code_counter_ = rolling_code; + ESP_LOGI(TAG, "Restored rolling code from flash: %u", rolling_code); + 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( - 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); - } - } - }, - 1); - } else { - rolling_code = 1; - this->rolling_code_counter_ = rolling_code; - this->rolling_code_pref_.save(&rolling_code); - } + } + }, + 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_.enableIntTx(false); - this->uart_.enableAutoBaud(true); + this->uart_.begin(9600, RATGDO_UART_8N1, rx_pin->get_pin(), tx_pin->get_pin(), + 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() - { - if (this->flags_.transmit_pending) { - if (!this->transmit_packet()) { - return; - } - } + auto cmd = this->read_command(); + if (cmd) { + this->handle_command(*cmd); + } +} - auto cmd = this->read_command(); - if (cmd) { - this->handle_command(*cmd); - } - } +void Secplus2::dump_config() { + 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::dump_config() - { - 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() { this->uart_.on_shutdown(); } - void Secplus2::on_shutdown() - { - this->uart_.on_shutdown(); - } +void Secplus2::sync_helper(uint32_t start, uint32_t delay, uint8_t tries) { + if (tries == 0 || *this->ratgdo_->door_state == DoorState::UNKNOWN) { + 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) - { - if (tries == 0 || *this->ratgdo_->door_state == DoorState::UNKNOWN) { - 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; - } + // not sync-ed after 30s, notify failure + if (millis() - start > 30000) { + ESP_LOGW(TAG, "Triggering sync failed actions."); + this->ratgdo_->synced = false; + this->ratgdo_->sync_failed = true; + } 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); + }); + }; +} - // not sync-ed after 30s, notify failure - if (millis() - start > 30000) { - ESP_LOGW(TAG, "Triggering sync failed actions."); - this->ratgdo_->synced = false; - this->ratgdo_->sync_failed = true; - } 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() { + ESP_LOGD(TAG, "Starting sync..."); + this->ratgdo_->synced = false; + this->scheduler_->cancel_timeout(this->ratgdo_, TIMEOUT_SYNC); + this->sync_helper(millis(), 500, 0); +} - void Secplus2::sync() - { - ESP_LOGD(TAG, "Starting sync..."); - this->ratgdo_->synced = false; - this->scheduler_->cancel_timeout(this->ratgdo_, TIMEOUT_SYNC); - this->sync_helper(millis(), 500, 0); - } +void Secplus2::light_action(LightAction action) { + if (action == LightAction::UNKNOWN) { + return; + } + this->send_command(Command(CommandType::LIGHT, static_cast(action))); +} - void Secplus2::light_action(LightAction action) - { - if (action == LightAction::UNKNOWN) { - return; - } - this->send_command(Command(CommandType::LIGHT, static_cast(action))); - } +void Secplus2::lock_action(LockAction action) { + if (action == LockAction::UNKNOWN) { + return; + } + this->send_command(Command(CommandType::LOCK, static_cast(action))); +} - void Secplus2::lock_action(LockAction action) - { - if (action == LockAction::UNKNOWN) { - return; - } - this->send_command(Command(CommandType::LOCK, static_cast(action))); - } +void Secplus2::door_action(DoorAction action) { + if (action == DoorAction::UNKNOWN) { + return; + } + this->door_command(action); +} - void Secplus2::door_action(DoorAction action) - { - if (action == DoorAction::UNKNOWN) { - return; - } - this->door_command(action); - } - - - - void Secplus2::door_command(DoorAction action) - { - this->send_command(Command(CommandType::DOOR_ACTION, static_cast(action), 1, 1), IncrementRollingCode::NO, [this, action]() { - this->ratgdo_->set_timeout(150, [this, action] { - this->send_command(Command(CommandType::DOOR_ACTION, static_cast(action), 0, 1)); - }); +void Secplus2::door_command(DoorAction action) { + this->send_command( + Command(CommandType::DOOR_ACTION, static_cast(action), 1, 1), + IncrementRollingCode::NO, [this, action]() { + this->ratgdo_->set_timeout(150, [this, action] { + this->send_command(Command(CommandType::DOOR_ACTION, + static_cast(action), 0, 1)); }); + }); +} + +void Secplus2::query_status() { this->send_command(CommandType::GET_STATUS); } + +void Secplus2::query_openings() { + this->send_command(CommandType::GET_OPENINGS); +} + +optional 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() - { - this->send_command(CommandType::GET_STATUS); + 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_); + } } + } - void Secplus2::query_openings() - { - this->send_command(CommandType::GET_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 + // 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 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((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(); } - - optional 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; - } - - 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 { }; + } 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::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)); +void Secplus2::encode_packet(Command command, WirePacket& packet) { + auto cmd = static_cast(command.type); + uint64_t fixed = ((cmd & ~0xff) << 24) | this->client_id_; + uint32_t data = (static_cast(command.byte2) << 24) | + (static_cast(command.byte1) << 16) | + (static_cast(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 Secplus2::decode_packet(const WirePacket& packet) const - { - uint32_t rolling = 0; - uint64_t fixed = 0; - uint32_t data = 0; + this->print_packet(LOG_STR("Sending packet"), this->tx_packet_); - int err = decode_wireline(packet, &rolling, &fixed, &data); - if (err < 0) { - ESP_LOGW(TAG, "Decode failed (parity error or invalid frame)"); - return { }; - } + this->uart_.transmit_secplus2_preamble(); + this->uart_.write(this->tx_packet_, PACKET_LENGTH); - uint16_t cmd = ((fixed >> 24) & 0xf00) | (data & 0xff); - data &= ~0xf000; // clear parity nibble + this->flags_.transmit_pending = false; + this->transmit_pending_start_ = 0; + this->on_command_sent_.trigger(); + return true; +} - 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); - } +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); + } +} - 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; +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); + } +} - 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 }; - } - - 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((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(command.type); - uint64_t fixed = ((cmd & ~0xff) << 24) | this->client_id_; - uint32_t data = (static_cast(command.byte2) << 24) | (static_cast(command.byte1) << 16) | (static_cast(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 +} // namespace secplus2 +} // namespace esphome::ratgdo diff --git a/components/ratgdo/secplus2.h b/components/ratgdo/secplus2.h index 6da1b1c..753accb 100644 --- a/components/ratgdo/secplus2.h +++ b/components/ratgdo/secplus2.h @@ -1,183 +1,197 @@ #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 "common.h" +#include "esphome/components/mqtt/mqtt_client.h" +#include "esphome/core/optional.h" +#include "esphome/core/preferences.h" #include "observable.h" #include "ratgdo_state.h" +#include "ratgdo_uart_esp32.h" namespace esphome { class Scheduler; class InternalGPIOPin; -} // namespace esphome +} // namespace esphome namespace esphome::ratgdo { class RATGDOComponent; namespace secplus2 { - static const uint8_t PACKET_LENGTH = 19; - typedef uint8_t WirePacket[PACKET_LENGTH]; +static const uint8_t PACKET_LENGTH = 19; +typedef uint8_t WirePacket[PACKET_LENGTH]; - enum class CommandType : uint16_t { - UNKNOWN = 0x000, - GET_STATUS = 0x080, - STATUS = 0x081, - LOCK = 0x18c, - DOOR_ACTION = 0x280, - LIGHT = 0x281, - GET_OPENINGS = 0x48b, - OPENINGS = 0x48c // openings = (byte1<<8)+byte2 - }; +enum class CommandType : uint16_t { + UNKNOWN = 0x000, + GET_STATUS = 0x080, + STATUS = 0x081, + LOCK = 0x18c, + DOOR_ACTION = 0x280, + LIGHT = 0x281, + GET_OPENINGS = 0x48b, + OPENINGS = 0x48c // openings = (byte1<<8)+byte2 +}; - inline const char* CommandType_to_string(CommandType e) { - switch (e) { - case CommandType::UNKNOWN: return "UNKNOWN"; - case CommandType::GET_STATUS: return "GET_STATUS"; - case CommandType::STATUS: return "STATUS"; - case CommandType::LOCK: return "LOCK"; - 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 const char* CommandType_to_string(CommandType e) { + switch (e) { + case CommandType::UNKNOWN: + return "UNKNOWN"; + case CommandType::GET_STATUS: + return "GET_STATUS"; + case CommandType::STATUS: + return "STATUS"; + case CommandType::LOCK: + return "LOCK"; + 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(CommandType::UNKNOWN): + return CommandType::UNKNOWN; + case static_cast(CommandType::GET_STATUS): + return CommandType::GET_STATUS; + case static_cast(CommandType::STATUS): + return CommandType::STATUS; + case static_cast(CommandType::LOCK): + return CommandType::LOCK; + case static_cast(CommandType::DOOR_ACTION): + return CommandType::DOOR_ACTION; + case static_cast(CommandType::LIGHT): + return CommandType::LIGHT; + case static_cast(CommandType::GET_OPENINGS): + return CommandType::GET_OPENINGS; + case static_cast(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(cmd_e); +} +inline bool operator==(const CommandType& cmd_e, const uint16_t cmd_i) { + return cmd_i == static_cast(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 read_command(); + void handle_command(const Command& cmd); + + void send_command(Command cmd, + IncrementRollingCode increment = IncrementRollingCode::YES); + template + 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(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) { - switch (t) { - case static_cast(CommandType::UNKNOWN): return CommandType::UNKNOWN; - case static_cast(CommandType::GET_STATUS): return CommandType::GET_STATUS; - case static_cast(CommandType::STATUS): return CommandType::STATUS; - case static_cast(CommandType::LOCK): return CommandType::LOCK; - case static_cast(CommandType::DOOR_ACTION): return CommandType::DOOR_ACTION; - case static_cast(CommandType::LIGHT): return CommandType::LIGHT; - case static_cast(CommandType::GET_OPENINGS): return CommandType::GET_OPENINGS; - case static_cast(CommandType::OPENINGS): return CommandType::OPENINGS; - default: return unknown; - } - } + void door_command(DoorAction action); - inline bool operator==(const uint16_t cmd_i, const CommandType& cmd_e) { return cmd_i == static_cast(cmd_e); } - inline bool operator==(const CommandType& cmd_e, const uint16_t cmd_i) { return cmd_i == static_cast(cmd_e); } + void print_packet(const esphome::LogString* prefix, + const WirePacket& packet) const; + optional decode_packet(const WirePacket& packet) const; - enum class IncrementRollingCode { - NO, - YES, - }; + void sync_helper(uint32_t start, uint32_t delay, uint8_t tries); - struct Command { - CommandType type; - uint8_t nibble; - uint8_t byte1; - uint8_t byte2; + // 8-byte member first (may require 8-byte alignment on some 32-bit systems) + uint64_t client_id_{0x539}; - 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_) - { - } - }; + // Pointers (4-byte aligned) + InternalGPIOPin* tx_pin_; + InternalGPIOPin* rx_pin_; + RATGDOComponent* ratgdo_; + Scheduler* scheduler_; - class Secplus2 { - public: - void setup(RATGDOComponent* ratgdo, Scheduler* scheduler, InternalGPIOPin* rx_pin, InternalGPIOPin* tx_pin); - void loop(); - void dump_config(); - void on_shutdown(); + // 4-byte members + uint32_t transmit_pending_start_{0}; + uint32_t rx_msg_start_{0}; + uint32_t rx_last_read_{0}; - void sync(); + // Larger structures + single_observable 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 on_command_sent_; + RatgdoUART uart_; - void light_action(LightAction action); - void lock_action(LockAction action); - void door_action(DoorAction action); + // 19-byte arrays + WirePacket tx_packet_; + WirePacket rx_packet_; - 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 read_command(); - void handle_command(const Command& cmd); - - void send_command(Command cmd, IncrementRollingCode increment = IncrementRollingCode::YES); - template - 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(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 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 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 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 + // 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 diff --git a/components/ratgdo/sensor/ratgdo_sensor.cpp b/components/ratgdo/sensor/ratgdo_sensor.cpp index 33f2805..fb48527 100644 --- a/components/ratgdo/sensor/ratgdo_sensor.cpp +++ b/components/ratgdo/sensor/ratgdo_sensor.cpp @@ -1,94 +1,96 @@ #include "ratgdo_sensor.h" + #include "../ratgdo_state.h" #include "esphome/core/log.h" namespace esphome::ratgdo { 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 MAX_DISTANCE = 4500; // default maximum distance +static const int MIN_DISTANCE = + 100; // ignore bugs crawling on the distance sensor & dust protection film +static const int MAX_DISTANCE = 4500; // default maximum distance -void RATGDOSensor::setup() -{ - switch (this->ratgdo_sensor_type_) { +void RATGDOSensor::setup() { + switch (this->ratgdo_sensor_type_) { case RATGDOSensorType::RATGDO_OPENINGS: - this->parent_->subscribe_openings([this](uint16_t value) { - this->publish_state(value); - }); - break; + this->parent_->subscribe_openings( + [this](uint16_t value) { this->publish_state(value); }); + break; case RATGDOSensorType::RATGDO_DISTANCE: - this->distance_sensor_.setI2cDevice(&I2C); - this->distance_sensor_.setXShutPin(32); - I2C.begin(19, 18); - this->distance_sensor_.begin(); - this->distance_sensor_.VL53L4CX_Off(); - this->distance_sensor_.InitSensor(0x59); - this->distance_sensor_.VL53L4CX_SetDistanceMode(VL53L4CX_DISTANCEMODE_LONG); - this->distance_sensor_.VL53L4CX_StartMeasurement(); - this->parent_->subscribe_distance_measurement([this](int16_t value) { - this->publish_state(value); - }); - break; + this->distance_sensor_.setI2cDevice(&I2C); + this->distance_sensor_.setXShutPin(32); + I2C.begin(19, 18); + this->distance_sensor_.begin(); + this->distance_sensor_.VL53L4CX_Off(); + this->distance_sensor_.InitSensor(0x59); + this->distance_sensor_.VL53L4CX_SetDistanceMode( + VL53L4CX_DISTANCEMODE_LONG); + this->distance_sensor_.VL53L4CX_StartMeasurement(); + this->parent_->subscribe_distance_measurement( + [this](int16_t value) { this->publish_state(value); }); + break; default: - break; - } + break; + } } -void RATGDOSensor::dump_config() -{ - LOG_SENSOR("", "RATGDO Sensor", this); - switch (this->ratgdo_sensor_type_) { +void RATGDOSensor::dump_config() { + LOG_SENSOR("", "RATGDO Sensor", this); + switch (this->ratgdo_sensor_type_) { case RATGDOSensorType::RATGDO_OPENINGS: - ESP_LOGCONFIG(TAG, " Type: Openings"); - break; + ESP_LOGCONFIG(TAG, " Type: Openings"); + break; case RATGDOSensorType::RATGDO_DISTANCE: - ESP_LOGCONFIG(TAG, " Type: Distance"); - break; + ESP_LOGCONFIG(TAG, " Type: Distance"); + break; default: - break; - } + break; + } } -void RATGDOSensor::loop() -{ - if (this->ratgdo_sensor_type_ == RATGDOSensorType::RATGDO_DISTANCE) { - VL53L4CX_MultiRangingData_t distanceData; - VL53L4CX_MultiRangingData_t* pDistanceData = &distanceData; - uint8_t dataReady = 0; - int objCount = 0; - int16_t maxDistance = -1; - int status; +void RATGDOSensor::loop() { + if (this->ratgdo_sensor_type_ == RATGDOSensorType::RATGDO_DISTANCE) { + VL53L4CX_MultiRangingData_t distanceData; + VL53L4CX_MultiRangingData_t* pDistanceData = &distanceData; + uint8_t dataReady = 0; + int objCount = 0; + int16_t maxDistance = -1; + int status; - if (this->distance_sensor_.VL53L4CX_GetMeasurementDataReady(&dataReady) == 0 && dataReady) { - status = this->distance_sensor_.VL53L4CX_GetMultiRangingData(pDistanceData); - objCount = pDistanceData->NumberOfObjectsFound; + if (this->distance_sensor_.VL53L4CX_GetMeasurementDataReady(&dataReady) == + 0 && + dataReady) { + status = + this->distance_sensor_.VL53L4CX_GetMultiRangingData(pDistanceData); + objCount = pDistanceData->NumberOfObjectsFound; - for (int i = 0; i < distanceData.NumberOfObjectsFound; i++) { - VL53L4CX_TargetRangeData_t* d = &pDistanceData->RangeData[i]; - if (d->RangeStatus == 0) { - maxDistance = std::max(maxDistance, d->RangeMilliMeter); - 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(); - } + for (int i = 0; i < distanceData.NumberOfObjectsFound; i++) { + VL53L4CX_TargetRangeData_t* d = &pDistanceData->RangeData[i]; + if (d->RangeStatus == 0) { + maxDistance = std::max(maxDistance, d->RangeMilliMeter); + 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(); + } } + } } -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/sensor/ratgdo_sensor.h b/components/ratgdo/sensor/ratgdo_sensor.h index 1ef784b..79ed028 100644 --- a/components/ratgdo/sensor/ratgdo_sensor.h +++ b/components/ratgdo/sensor/ratgdo_sensor.h @@ -2,32 +2,32 @@ #include "../ratgdo.h" #include "../ratgdo_state.h" +#include "Wire.h" #include "esphome/components/sensor/sensor.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" - -#include "Wire.h" #include "vl53l4cx_class.h" #define I2C Wire namespace esphome::ratgdo { -enum RATGDOSensorType : uint8_t { - RATGDO_OPENINGS, - RATGDO_DISTANCE = 6 +enum RATGDOSensorType : uint8_t { 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 { -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 +} // namespace esphome::ratgdo diff --git a/components/ratgdo/switch/ratgdo_switch.cpp b/components/ratgdo/switch/ratgdo_switch.cpp index f19e38f..319db82 100644 --- a/components/ratgdo/switch/ratgdo_switch.cpp +++ b/components/ratgdo/switch/ratgdo_switch.cpp @@ -1,4 +1,5 @@ #include "ratgdo_switch.h" + #include "../ratgdo_state.h" #include "esphome/core/log.h" @@ -6,39 +7,36 @@ namespace esphome::ratgdo { static const char* const TAG = "ratgdo.switch"; -void RATGDOSwitch::dump_config() -{ - LOG_SWITCH("", "RATGDO Switch", this); - switch (this->switch_type_) { +void RATGDOSwitch::dump_config() { + LOG_SWITCH("", "RATGDO Switch", this); + switch (this->switch_type_) { case SwitchType::RATGDO_LED: - ESP_LOGCONFIG(TAG, " Type: LED"); - break; + ESP_LOGCONFIG(TAG, " Type: LED"); + break; default: - break; - } + break; + } } -void RATGDOSwitch::setup() -{ - switch (this->switch_type_) { +void RATGDOSwitch::setup() { + switch (this->switch_type_) { case SwitchType::RATGDO_LED: - this->pin_->setup(); - break; + this->pin_->setup(); + break; default: - break; - } + break; + } } -void RATGDOSwitch::write_state(bool state) -{ - switch (this->switch_type_) { +void RATGDOSwitch::write_state(bool state) { + switch (this->switch_type_) { case SwitchType::RATGDO_LED: - this->pin_->digital_write(state); - this->publish_state(state); - break; + this->pin_->digital_write(state); + this->publish_state(state); + break; default: - break; - } + break; + } } -} // namespace esphome::ratgdo +} // namespace esphome::ratgdo diff --git a/components/ratgdo/switch/ratgdo_switch.h b/components/ratgdo/switch/ratgdo_switch.h index b68b731..df54835 100644 --- a/components/ratgdo/switch/ratgdo_switch.h +++ b/components/ratgdo/switch/ratgdo_switch.h @@ -8,22 +8,24 @@ namespace esphome::ratgdo { -enum SwitchType { - RATGDO_LED = 1 +enum SwitchType { 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 { -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 +} // namespace esphome::ratgdo diff --git a/components/somfy/somfy.cpp b/components/somfy/somfy.cpp index daa8921..f2e3820 100644 --- a/components/somfy/somfy.cpp +++ b/components/somfy/somfy.cpp @@ -7,7 +7,7 @@ namespace esphome { 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_UP = 0x02; @@ -34,8 +34,8 @@ struct SomfyRTSProtocolData { class SomfyRTSProtocol : public remote_base::RemoteProtocol { public: - virtual void encode(remote_base::RemoteTransmitData *dst, - const ProtocolData &data) override { + virtual void encode(remote_base::RemoteTransmitData* dst, + const ProtocolData& data) override { /* * 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 decode( remote_base::RemoteReceiveData src) override { @@ -155,7 +155,7 @@ void SomfyRTSCover::setup() { // MQTT. mqtt::global_mqtt_client->subscribe( 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) { ESP_LOGI(TAG, "Received rolling code for remote #%d from MQTT: %s", 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()) { float position = *call.get_position(); send(position > 0.5 ? CMD_UP : CMD_DOWN); diff --git a/components/somfy/somfy.h b/components/somfy/somfy.h index 2d5170a..0b63861 100644 --- a/components/somfy/somfy.h +++ b/components/somfy/somfy.h @@ -16,7 +16,7 @@ namespace somfy { class SomfyRTSCover : public cover::Cover, public Component { public: void set_transmitter( - remote_transmitter::RemoteTransmitterComponent *transmitter) { + remote_transmitter::RemoteTransmitterComponent* transmitter) { this->transmitter_ = transmitter; } 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; void setup() override; void dump_config() override; - void control(const cover::CoverCall &call) override; + void control(const cover::CoverCall& call) override; protected: /* 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_; std::string mqtt_topic_prefix_; ESPPreferenceObject rolling_code_pref_; - remote_transmitter::RemoteTransmitterComponent *transmitter_{nullptr}; + remote_transmitter::RemoteTransmitterComponent* transmitter_{nullptr}; }; } // namespace somfy diff --git a/components/zehnder/zehnder.cpp b/components/zehnder/zehnder.cpp index 58333de..71ca14c 100644 --- a/components/zehnder/zehnder.cpp +++ b/components/zehnder/zehnder.cpp @@ -3,16 +3,16 @@ #include "esphome/components/remote_base/remote_base.h" namespace { -static const char *const TAG = __FILE__; +static const char* const TAG = __FILE__; static const uint8_t MIN_TEMPERATURE = 35; static const uint8_t MAX_TEMPERATURE = 70; 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); } -static void add_byte_(esphome::remote_base::RemoteTransmitData *data, +static void add_byte_(esphome::remote_base::RemoteTransmitData* data, uint8_t value) { for (int i = 0; i < 8; ++i) { 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, 650); } @@ -29,7 +29,7 @@ static void add_post_(esphome::remote_base::RemoteTransmitData *data) { namespace esphome { namespace zehnder { -void ZehnderComponent::control(const climate::ClimateCall &call) { +void ZehnderComponent::control(const climate::ClimateCall& call) { float target_temp = this->target_temperature; climate::ClimateMode mode = this->mode; @@ -101,7 +101,7 @@ void ZehnderComponent::dump_config() { this->has_temperature_sensor_ ? "yes" : "no"); } -bool ZehnderComponent::transmit_temperature_(float *temp) { +bool ZehnderComponent::transmit_temperature_(float* temp) { uint8_t level; if (*temp == 0) { level = 0; diff --git a/components/zehnder/zehnder.h b/components/zehnder/zehnder.h index c3d3b3b..2a37677 100644 --- a/components/zehnder/zehnder.h +++ b/components/zehnder/zehnder.h @@ -22,11 +22,11 @@ class ZehnderComponent : public climate::Climate, public PollingComponent { // PollingComponent. void update() override; - void set_transmitter(remote_transmitter::RemoteTransmitterComponent *t) { + void set_transmitter(remote_transmitter::RemoteTransmitterComponent* t) { this->transmitter_ = t; } - void add_temperature_sensor(sensor::Sensor *s) { + void add_temperature_sensor(sensor::Sensor* s) { has_temperature_sensor_ = true; s->add_on_state_callback([this](float temperature_value) { this->current_temperature = temperature_value; @@ -36,13 +36,13 @@ class ZehnderComponent : public climate::Climate, public PollingComponent { protected: // climate::Climate. - void control(const climate::ClimateCall &call) override; + void control(const climate::ClimateCall& call) override; climate::ClimateTraits traits() override; - bool transmit_temperature_(float *temp); + bool transmit_temperature_(float* temp); bool transmit_level_(uint8_t level); - remote_transmitter::RemoteTransmitterComponent *transmitter_ = nullptr; + remote_transmitter::RemoteTransmitterComponent* transmitter_ = nullptr; bool has_temperature_sensor_ = false; }; diff --git a/empty/empty32-cam.yaml b/empty/empty32-cam.yaml index c30dae9..ef9dbca 100644 --- a/empty/empty32-cam.yaml +++ b/empty/empty32-cam.yaml @@ -27,16 +27,16 @@ esp32_camera: power_down_pin: GPIO32 name: Camera - resolution: 800x600 # Max 1600x1200. + resolution: 800x600 # Max 1600x1200. idle_framerate: 0.1fps output: -- platform: ledc - pin: GPIO4 - channel: 2 # PWM channel 1 is used for esp32_camera. - id: led + - platform: ledc + pin: GPIO4 + channel: 2 # PWM channel 1 is used for esp32_camera. + id: led light: -- platform: monochromatic - output: led - name: Flash + - platform: monochromatic + output: led + name: Flash diff --git a/garage-gate.yaml b/garage-gate.yaml index 91e340d..cb6dc47 100644 --- a/garage-gate.yaml +++ b/garage-gate.yaml @@ -19,7 +19,7 @@ external_components: - source: type: local path: components - components: [ ratgdo ] + components: [ratgdo] packages: wifi: !include templates/wifi.yaml @@ -49,7 +49,7 @@ time: binary_sensor: - platform: template name: "Sync failed" - lambda: 'return *id(garage_gate_controller).sync_failed;' + lambda: "return *id(garage_gate_controller).sync_failed;" device_class: problem entity_category: diagnostic @@ -74,9 +74,9 @@ sensor: attenuation: auto update_interval: 60s filters: - - calibrate_linear: - - 1.16 -> 5 - - 2.783 -> 12 + - calibrate_linear: + - 1.16 -> 5 + - 2.783 -> 12 lock: - platform: ratgdo @@ -114,7 +114,7 @@ cover: on_state_change: - if: condition: - lambda: 'return id(garage_gate).current_operation == COVER_OPERATION_CLOSING;' + lambda: "return id(garage_gate).current_operation == COVER_OPERATION_CLOSING;" then: - script.execute: play_siren else: @@ -167,7 +167,7 @@ script: then: - while: condition: - lambda: 'return id(garage_gate).current_operation == COVER_OPERATION_CLOSING;' + lambda: "return id(garage_gate).current_operation == COVER_OPERATION_CLOSING;" then: - rtttl.play: "siren:d=8,o=6,b=100:e,d#,e,d#,e,d#,e,d#" - wait_until: diff --git a/heating-og.yaml b/heating-og.yaml index 36bee5b..2c02ced 100644 --- a/heating-og.yaml +++ b/heating-og.yaml @@ -40,17 +40,16 @@ ethernet: domain: "" text_sensor: -- platform: ethernet_info - ip_address: - name: "ESP chip IP (Ethernet)" - icon: mdi:ip-network - mac_address: - name: "ESP chip MAC (Ethernet)" - icon: mdi:expansion-card - + - platform: ethernet_info + ip_address: + name: "ESP chip IP (Ethernet)" + icon: mdi:ip-network + mac_address: + name: "ESP chip MAC (Ethernet)" + icon: mdi:expansion-card sn74hc595: # write - - id: 'sn74hc595_hub' + - id: "sn74hc595_hub" clock_pin: GPIO14 # SRCLK (serial clock) data_pin: GPIO13 # SER (serial input) latch_pin: GPIO15 # RCLK (latch for outputs) diff --git a/heating.yaml b/heating.yaml index 953537e..4d0e4fc 100644 --- a/heating.yaml +++ b/heating.yaml @@ -42,98 +42,98 @@ i2c: scl: GPIO18 font: -- file: "fonts/DejaVuSans.ttf" - id: font_sans - size: 20 + - file: "fonts/DejaVuSans.ttf" + id: font_sans + size: 20 display: -- platform: ssd1306_i2c - model: "SSD1306 128x32" - id: oled_display - lambda: |- - it.printf( - 4, 6, - id(font_sans), - "%.2f C", - id(boiler_temperature).state); + - platform: ssd1306_i2c + model: "SSD1306 128x32" + id: oled_display + lambda: |- + it.printf( + 4, 6, + id(font_sans), + "%.2f C", + id(boiler_temperature).state); - if (id(furnace_burning).state) { - it.filled_circle(112, 16, 12); - } else { - it.circle(112, 16, 12); - } + if (id(furnace_burning).state) { + it.filled_circle(112, 16, 12); + } else { + it.circle(112, 16, 12); + } one_wire: -- platform: gpio - pin: GPIO27 + - platform: gpio + pin: GPIO27 sensor: -- platform: dallas_temp - address: 0x133c2cf648f40728 - id: boiler_temperature - name: "Boiler temperature" - filters: - - offset: 0.5 + - platform: dallas_temp + address: 0x133c2cf648f40728 + id: boiler_temperature + name: "Boiler temperature" + filters: + - offset: 0.5 -- platform: dallas_temp - address: 0x4845b7211864ff28 - id: hot_water_temperature - name: "Hot water temperature" + - platform: dallas_temp + address: 0x4845b7211864ff28 + id: hot_water_temperature + name: "Hot water temperature" -- platform: dallas_temp - address: 0x6a0316643959ff28 - id: floor_temperature - name: "Floor temperature" + - platform: dallas_temp + address: 0x6a0316643959ff28 + id: floor_temperature + name: "Floor temperature" -- platform: adc - pin: GPIO32 - update_interval: 1s - id: furnace_burning_led_brightness - attenuation: auto - # Make voltage value available in UI for debugging and history. - name: "Furnace burning - LED brightness" - entity_category: diagnostic - accuracy_decimals: 2 + - platform: adc + pin: GPIO32 + update_interval: 1s + id: furnace_burning_led_brightness + attenuation: auto + # Make voltage value available in UI for debugging and history. + name: "Furnace burning - LED brightness" + entity_category: diagnostic + accuracy_decimals: 2 -- platform: adc - pin: GPIO33 - update_interval: 1s - id: floor_active_led_brightness - attenuation: auto - # Make voltage value available in UI for debugging and history. - name: "Floor active - LED brightness" - entity_category: diagnostic - accuracy_decimals: 2 + - platform: adc + pin: GPIO33 + update_interval: 1s + id: floor_active_led_brightness + attenuation: auto + # Make voltage value available in UI for debugging and history. + name: "Floor active - LED brightness" + entity_category: diagnostic + accuracy_decimals: 2 binary_sensor: -- platform: analog_threshold - id: furnace_burning - name: "Furnace burning" - sensor_id: furnace_burning_led_brightness - # 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. - threshold: 2.2 # LED on = 1.93V, flashlight on = 2.5V. - filters: - - invert: + - platform: analog_threshold + id: furnace_burning + name: "Furnace burning" + sensor_id: furnace_burning_led_brightness + # 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. + threshold: 2.2 # LED on = 1.93V, flashlight on = 2.5V. + filters: + - invert: -- platform: analog_threshold - id: heating_enabled - name: "Floor active" - sensor_id: floor_active_led_brightness - # This sensor is more exposed to external light due to construction. - threshold: 1.9 # LED on = 1.76V, flashlight on = 2.1V. - filters: - - invert: + - platform: analog_threshold + id: heating_enabled + name: "Floor active" + sensor_id: floor_active_led_brightness + # This sensor is more exposed to external light due to construction. + threshold: 1.9 # LED on = 1.76V, flashlight on = 2.1V. + filters: + - invert: switch: -- platform: gpio - pin: GPIO25 - id: relay_temperature_manual - inverted: true - # Non-manual is no longer wired to anything. - restore_mode: ALWAYS_ON + - platform: gpio + pin: GPIO25 + id: relay_temperature_manual + inverted: true + # Non-manual is no longer wired to anything. + restore_mode: ALWAYS_ON -- platform: gpio - pin: GPIO26 - name: "Floor intent" - restore_mode: RESTORE_DEFAULT_OFF + - platform: gpio + pin: GPIO26 + name: "Floor intent" + restore_mode: RESTORE_DEFAULT_OFF diff --git a/irrigation.yaml b/irrigation.yaml index 78ab7f6..03016b0 100644 --- a/irrigation.yaml +++ b/irrigation.yaml @@ -72,19 +72,19 @@ binary_sensor: - platform: template name: "Garden Peach relay" entity_category: diagnostic - lambda: 'return id(relay_1).state;' + lambda: "return id(relay_1).state;" - platform: template name: "Garden Berries relay" entity_category: diagnostic - lambda: 'return id(relay_2).state;' + lambda: "return id(relay_2).state;" - platform: template name: "Shower relay" entity_category: diagnostic - lambda: 'return id(relay_3).state;' + lambda: "return id(relay_3).state;" - platform: template name: "Relay 4" entity_category: diagnostic - lambda: 'return id(relay_4).state;' + lambda: "return id(relay_4).state;" switch: - platform: gpio diff --git a/outdoor-equipment.yaml b/outdoor-equipment.yaml index ccd34de..23b1951 100644 --- a/outdoor-equipment.yaml +++ b/outdoor-equipment.yaml @@ -10,25 +10,24 @@ packages: time: !include templates/time.yaml time: -- platform: sntp - id: sntp_time - servers: !secret sntp_servers - timezone: Europe/Zurich - on_time: - - # - # 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). - # - - hours: 16 - minutes: 25 - seconds: 0 - months: APR-OCT - then: - - switch.turn_on: mystrom_relay - - hours: 20 - minutes: 35 - seconds: 0 - months: APR-OCT - then: - - switch.turn_off: mystrom_relay + - platform: sntp + id: sntp_time + servers: !secret sntp_servers + timezone: Europe/Zurich + on_time: + # + # 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). + # + - hours: 16 + minutes: 25 + seconds: 0 + months: APR-OCT + then: + - switch.turn_on: mystrom_relay + - hours: 20 + minutes: 35 + seconds: 0 + months: APR-OCT + then: + - switch.turn_off: mystrom_relay diff --git a/radiation.yaml b/radiation.yaml index eb54150..dbc0cc2 100644 --- a/radiation.yaml +++ b/radiation.yaml @@ -28,24 +28,24 @@ output: id: mic_power sensor: -- platform: pulse_meter - internal_filter: 50ms + - platform: pulse_meter + internal_filter: 50ms - pin: - number: GPIO15 - inverted: true - mode: - input: true + pin: + number: GPIO15 + inverted: true + mode: + input: true - name: "Level" - unit_of_measurement: "mR/h" - state_class: "measurement" + name: "Level" + unit_of_measurement: "mR/h" + state_class: "measurement" - accuracy_decimals: 3 + accuracy_decimals: 3 - filters: - - multiply: 0.0000833333 # particles/minute -> milliRad/hour (assuming calibration factor 1200 CPM = 1milliRad) - - sliding_window_moving_average: - window_size: 20 - send_every: 2 - - throttle_average: 5s + filters: + - multiply: 0.0000833333 # particles/minute -> milliRad/hour (assuming calibration factor 1200 CPM = 1milliRad) + - sliding_window_moving_average: + window_size: 20 + send_every: 2 + - throttle_average: 5s diff --git a/storage-heater.yaml b/storage-heater.yaml index 8962089..8470bd3 100644 --- a/storage-heater.yaml +++ b/storage-heater.yaml @@ -8,14 +8,13 @@ esphome: packages: device_base: !include templates/mystrom.yaml - interval: -- interval: 1min - then: - - if: - condition: - sensor.in_range: - id: continuous_on_time - above: 18000 # 5h - then: - - switch.turn_off: mystrom_relay + - interval: 1min + then: + - if: + condition: + sensor.in_range: + id: continuous_on_time + above: 18000 # 5h + then: + - switch.turn_off: mystrom_relay diff --git a/templates/btproxy.yaml b/templates/btproxy.yaml index 1c894c9..425be75 100644 --- a/templates/btproxy.yaml +++ b/templates/btproxy.yaml @@ -13,7 +13,7 @@ globals: - id: ble_scans type: int restore_value: no - initial_value: '0' + initial_value: "0" esp32_ble_tracker: scan_parameters: @@ -23,10 +23,10 @@ esp32_ble_tracker: active: true on_scan_end: - then: - - sensor.template.publish: - id: ble_scans_finished - state: !lambda 'return ++id(ble_scans);' - - script.execute: restart_after_delay + - sensor.template.publish: + id: ble_scans_finished + state: !lambda "return ++id(ble_scans);" + - script.execute: restart_after_delay bluetooth_proxy: id: btproxy diff --git a/templates/esp01s.yaml b/templates/esp01s.yaml index bfa8de0..94085bf 100644 --- a/templates/esp01s.yaml +++ b/templates/esp01s.yaml @@ -12,8 +12,8 @@ packages: logger: switch: -- platform: gpio - pin: GPIO0 - name: None - inverted: true - id: relay + - platform: gpio + pin: GPIO0 + name: None + inverted: true + id: relay diff --git a/templates/esp32-poe.yaml b/templates/esp32-poe.yaml index 486d4af..4622856 100644 --- a/templates/esp32-poe.yaml +++ b/templates/esp32-poe.yaml @@ -16,13 +16,13 @@ ethernet: domain: "" text_sensor: -- platform: ethernet_info - ip_address: - name: "ESP chip IP (Ethernet)" - icon: mdi:ip-network - mac_address: - name: "ESP chip MAC (Ethernet)" - icon: mdi:expansion-card + - platform: ethernet_info + ip_address: + name: "ESP chip IP (Ethernet)" + icon: mdi:ip-network + mac_address: + name: "ESP chip MAC (Ethernet)" + icon: mdi:expansion-card logger: diff --git a/templates/esp8266-vcc.yaml b/templates/esp8266-vcc.yaml index f776dfe..be41f0b 100644 --- a/templates/esp8266-vcc.yaml +++ b/templates/esp8266-vcc.yaml @@ -1,7 +1,7 @@ # Make sure nothing is connected to the A0 pin. sensor: -- platform: adc - pin: VCC - name: "ESP chip VCC Voltage" - entity_category: diagnostic + - platform: adc + pin: VCC + name: "ESP chip VCC Voltage" + entity_category: diagnostic diff --git a/templates/mystrom.yaml b/templates/mystrom.yaml index 028a542..9f8df08 100644 --- a/templates/mystrom.yaml +++ b/templates/mystrom.yaml @@ -41,39 +41,39 @@ status_led: pin: GPIO16 binary_sensor: -- platform: gpio - id: mystrom_side - name: "Side button (plus) pressed" - pin: - number: GPIO23 - mode: - input: true - pullup: true - inverted: true -- platform: gpio - id: mystrom_bottom - name: "Bottom button (power) pressed" - pin: - number: GPIO18 - mode: - input: true - pullup: true - inverted: true - on_press: - then: - - switch.toggle: mystrom_relay + - platform: gpio + id: mystrom_side + name: "Side button (plus) pressed" + pin: + number: GPIO23 + mode: + input: true + pullup: true + inverted: true + - platform: gpio + id: mystrom_bottom + name: "Bottom button (power) pressed" + pin: + number: GPIO18 + mode: + input: true + pullup: true + inverted: true + on_press: + then: + - switch.toggle: mystrom_relay script: -- id: power_cycle_relay - mode: single - then: - - if: - condition: - switch.is_on: mystrom_relay - then: - - switch.turn_off: mystrom_relay - - delay: 7s - - switch.turn_on: mystrom_relay + - id: power_cycle_relay + mode: single + then: + - if: + condition: + switch.is_on: mystrom_relay + then: + - switch.turn_off: mystrom_relay + - delay: 7s + - switch.turn_on: mystrom_relay button: - platform: template @@ -83,60 +83,60 @@ button: - script.execute: power_cycle_relay sensor: -- platform: pulse_counter - pin: 4 - update_interval: 10s - id: mystrom_power_consumption - name: "Power relay consumption" - device_class: "power" - state_class: "measurement" - unit_of_measurement: "W" - 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 + - platform: pulse_counter + pin: 4 + update_interval: 10s + id: mystrom_power_consumption + name: "Power relay consumption" + device_class: "power" + state_class: "measurement" + unit_of_measurement: "W" + accuracy_decimals: 0 filters: - - multiply: 5.48E-07 # pulses -> kWh + - multiply: 3.29E-02 # pulse/min -> W -- platform: tmp102 - id: mystrom_temperature - name: "Internal temperature" - update_interval: 60s - entity_category: diagnostic - address: 0x48 - on_value_range: - above: 80 - then: - - switch.turn_off: mystrom_relay + total: + name: "Energy consumption" + unit_of_measurement: "kWh" + device_class: "energy" + state_class: "total_increasing" + accuracy_decimals: 3 + filters: + - multiply: 5.48E-07 # pulses -> kWh -- platform: duty_time - id: continuous_on_time - name: Continuous on time - lambda: "return id(mystrom_relay).state;" - accuracy_decimals: 0 + - platform: tmp102 + id: mystrom_temperature + name: "Internal temperature" + update_interval: 60s + 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: -- platform: gpio - name: None # Main entity for the device. - pin: GPIO27 - id: mystrom_relay - restore_mode: RESTORE_DEFAULT_ON - device_class: outlet - on_turn_off: - - sensor.duty_time.reset: continuous_on_time + - platform: gpio + name: None # Main entity for the device. + pin: GPIO27 + id: mystrom_relay + restore_mode: RESTORE_DEFAULT_ON + device_class: outlet + on_turn_off: + - sensor.duty_time.reset: continuous_on_time -- platform: gpio - # Exposing this as a switch rather than light to avoid - # automations and dashboard picking it up as illuminance - # source. - name: "Identify LED (white)" - restore_mode: ALWAYS_OFF - pin: - number: GPIO17 - inverted: true + - platform: gpio + # Exposing this as a switch rather than light to avoid + # automations and dashboard picking it up as illuminance + # source. + name: "Identify LED (white)" + restore_mode: ALWAYS_OFF + pin: + number: GPIO17 + inverted: true diff --git a/templates/restart.yaml b/templates/restart.yaml index e304e03..381bb5a 100644 --- a/templates/restart.yaml +++ b/templates/restart.yaml @@ -1,5 +1,5 @@ button: -- platform: restart - name: "ESP chip restart" - entity_category: diagnostic - id: restart_button + - platform: restart + name: "ESP chip restart" + entity_category: diagnostic + id: restart_button diff --git a/templates/time.yaml b/templates/time.yaml index ecaa877..ba4b293 100644 --- a/templates/time.yaml +++ b/templates/time.yaml @@ -1,6 +1,6 @@ binary_sensor: -- platform: template - name: "ESP chip clock invalid" - lambda: 'return !id(sntp_time).now().is_valid();' - entity_category: diagnostic - device_class: problem + - platform: template + name: "ESP chip clock invalid" + lambda: "return !id(sntp_time).now().is_valid();" + entity_category: diagnostic + device_class: problem diff --git a/templates/tubeszb-coordinator.yaml b/templates/tubeszb-coordinator.yaml index 58a4fdc..06d50d9 100644 --- a/templates/tubeszb-coordinator.yaml +++ b/templates/tubeszb-coordinator.yaml @@ -23,9 +23,9 @@ esphome: version: "5.0" on_boot: - priority: 600 # Default; most sensors set up, no network yet. + priority: 600 # Default; most sensors set up, no network yet. then: - - script.execute: cc2652_reset + - script.execute: cc2652_reset packages: device_base: !include esp32-poe.yaml @@ -38,38 +38,38 @@ external_components: - source: github://oxan/esphome-stream-server script: -- id: fw_update_mode - then: - - switch.turn_on: zBSL - - delay: 1s - - switch.turn_on: zRST_gpio - - delay: 1s - - switch.turn_off: zRST_gpio - - logger.log: "Delaying ~10 seconds for cc2652 to settle" - - delay: 11s - - switch.turn_off: zBSL - - 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" + - id: fw_update_mode + then: + - switch.turn_on: zBSL + - delay: 1s + - switch.turn_on: zRST_gpio + - delay: 1s + - switch.turn_off: zRST_gpio + - logger.log: "Delaying ~10 seconds for cc2652 to settle" + - delay: 11s + - switch.turn_off: zBSL + - 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" -- id: cc2652_reset - then: - - switch.turn_on: zRST_gpio - - delay: 15ms - - switch.turn_off: zRST_gpio + - id: cc2652_reset + then: + - switch.turn_on: zRST_gpio + - delay: 15ms + - switch.turn_off: zRST_gpio button: -- platform: template - name: "ZigBee Module reset" - disabled_by_default: true - id: zRST - on_press: - - script.execute: cc2652_reset + - platform: template + name: "ZigBee Module reset" + disabled_by_default: true + id: zRST + on_press: + - script.execute: cc2652_reset -- platform: template - name: "ZigBee Module enter programming mode" - disabled_by_default: true - on_press: - - script.execute: fw_update_mode + - platform: template + name: "ZigBee Module enter programming mode" + disabled_by_default: true + on_press: + - script.execute: fw_update_mode stream_server: uart_id: uart_bus @@ -78,6 +78,6 @@ stream_server: buffer_size: 4096 binary_sensor: -- platform: stream_server - connected: - name: "ZigBee Module serial stream connected" + - platform: stream_server + connected: + name: "ZigBee Module serial stream connected" diff --git a/templates/uptime.yaml b/templates/uptime.yaml index ab276e8..c0a5fa3 100644 --- a/templates/uptime.yaml +++ b/templates/uptime.yaml @@ -1,3 +1,3 @@ sensor: -- platform: uptime - name: "ESP chip uptime" + - platform: uptime + name: "ESP chip uptime" diff --git a/templates/wifi.yaml b/templates/wifi.yaml index 84b390e..e57b4da 100644 --- a/templates/wifi.yaml +++ b/templates/wifi.yaml @@ -1,21 +1,21 @@ sensor: -- platform: wifi_signal - name: "ESP chip WiFi signal" + - platform: wifi_signal + name: "ESP chip WiFi signal" text_sensor: -- platform: wifi_info - mac_address: - name: "ESP chip MAC" - icon: mdi:wifi-cog - ip_address: - name: "ESP chip IP" - icon: mdi:wifi-cog - bssid: - name: "ESP chip connected to BSSID" - icon: mdi:wifi-settings - filters: - - to_upper - - substitute: !secret wifi_ap_lookup + - platform: wifi_info + mac_address: + name: "ESP chip MAC" + icon: mdi:wifi-cog + ip_address: + name: "ESP chip IP" + icon: mdi:wifi-cog + bssid: + name: "ESP chip connected to BSSID" + icon: mdi:wifi-settings + filters: + - to_upper + - substitute: !secret wifi_ap_lookup wifi: ssid: !secret wifi_ssid diff --git a/watermeter.yaml b/watermeter.yaml index 0a470aa..04b1f90 100644 --- a/watermeter.yaml +++ b/watermeter.yaml @@ -12,33 +12,33 @@ packages: sensor: !include templates/esp8266-vcc.yaml sensor: -- platform: pulse_meter - # Our meter (USLC/20) is rated Qmax=5.0 (m3/h). - # The official one is rated 5.0. - # - # 3600 / 5000 = 0.72 seconds per liter. That's the shortest - # pulse length at a maximum flow rate for the meter. - internal_filter: 720ms - # Assuming Qmin=50 (l/h), a flow with >72 seconds between pulses - # is beyond the measuring capabilities. However, one of the - # irrigation valves only consumes 0.73 L/min, so we push it a bit. - timeout: 90s + - platform: pulse_meter + # Our meter (USLC/20) is rated Qmax=5.0 (m3/h). + # The official one is rated 5.0. + # + # 3600 / 5000 = 0.72 seconds per liter. That's the shortest + # pulse length at a maximum flow rate for the meter. + internal_filter: 720ms + # Assuming Qmin=50 (l/h), a flow with >72 seconds between pulses + # is beyond the measuring capabilities. However, one of the + # irrigation valves only consumes 0.73 L/min, so we push it a bit. + timeout: 90s - pin: - number: D2 - inverted: true - mode: - input: true - pullup: true + pin: + number: D2 + inverted: true + mode: + input: true + pullup: true - name: "Incoming water flow" - unit_of_measurement: "L/min" - device_class: volume_flow_rate - state_class: "measurement" - icon: mdi:pipe-valve # device_class? + name: "Incoming water flow" + unit_of_measurement: "L/min" + device_class: volume_flow_rate + state_class: "measurement" + icon: mdi:pipe-valve # device_class? - total: - name: "Incoming water total" - unit_of_measurement: "L" - device_class: "water" - state_class: "total_increasing" + total: + name: "Incoming water total" + unit_of_measurement: "L" + device_class: "water" + state_class: "total_increasing" diff --git a/zigbee-coordinator-ch24.yaml b/zigbee-coordinator-ch24.yaml index ec71146..c593edf 100644 --- a/zigbee-coordinator-ch24.yaml +++ b/zigbee-coordinator-ch24.yaml @@ -13,17 +13,17 @@ packages: device_base: !include templates/tubeszb-coordinator.yaml switch: -- platform: gpio - pin: 33 - id: zRST_gpio - inverted: yes - restore_mode: ALWAYS_OFF + - platform: gpio + pin: 33 + id: zRST_gpio + inverted: yes + restore_mode: ALWAYS_OFF -- platform: gpio - pin: 32 - id: zBSL - inverted: yes - restore_mode: ALWAYS_OFF + - platform: gpio + pin: 32 + id: zBSL + inverted: yes + restore_mode: ALWAYS_OFF uart: id: uart_bus diff --git a/zigbee-coordinator-ch25.yaml b/zigbee-coordinator-ch25.yaml index 8db8aa2..27335e7 100644 --- a/zigbee-coordinator-ch25.yaml +++ b/zigbee-coordinator-ch25.yaml @@ -16,17 +16,17 @@ packages: device_base: !include templates/tubeszb-coordinator.yaml switch: -- platform: gpio - pin: 5 - id: zRST_gpio - inverted: yes - restore_mode: ALWAYS_OFF + - platform: gpio + pin: 5 + id: zRST_gpio + inverted: yes + restore_mode: ALWAYS_OFF -- platform: gpio - pin: 16 - id: zBSL - inverted: yes - restore_mode: ALWAYS_OFF + - platform: gpio + pin: 16 + id: zBSL + inverted: yes + restore_mode: ALWAYS_OFF uart: id: uart_bus