From 1517148f3760803aa17fafc3246cebf17c2fce9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20W=C3=B3jcik?= Date: Tue, 7 Jan 2025 12:57:26 +0100 Subject: [PATCH] Implement button controls and refactor action methods in diesel heater BLE component --- esphome/components/diesel_heater_ble/button.h | 53 +++++++ .../components/diesel_heater_ble/button.py | 46 +++++++ .../diesel_heater_ble/controllers.h | 130 ++++++++++++++++++ .../components/diesel_heater_ble/heater.cpp | 56 ++++++-- esphome/components/diesel_heater_ble/heater.h | 33 ++++- .../components/diesel_heater_ble/messages.h | 18 +-- esphome/components/diesel_heater_ble/number.h | 4 +- esphome/components/diesel_heater_ble/switch.h | 2 +- 8 files changed, 312 insertions(+), 30 deletions(-) create mode 100644 esphome/components/diesel_heater_ble/button.h create mode 100644 esphome/components/diesel_heater_ble/button.py create mode 100644 esphome/components/diesel_heater_ble/controllers.h diff --git a/esphome/components/diesel_heater_ble/button.h b/esphome/components/diesel_heater_ble/button.h new file mode 100644 index 0000000000..51e8915067 --- /dev/null +++ b/esphome/components/diesel_heater_ble/button.h @@ -0,0 +1,53 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "esphome/core/log.h" +#include "heater.h" +#include "messages.h" + +namespace esphome { +namespace diesel_heater_ble { + +class LevelUpButton : public button::Button, public Parented { + public: + LevelUpButton() = default; + + protected: + void press_action() override { + this->parent_->on_level_up_button_press(); + } +}; + +class LevelDownButton : public button::Button, public Parented { + public: + LevelDownButton() = default; + + protected: + void press_action() override { + this->parent_->on_level_down_button_press(); + } +}; + +class TempUpButton : public button::Button, public Parented { + public: + TempUpButton() = default; + + protected: + void press_action() override { + this->parent_->on_temp_up_button_press(); + } +}; + +class TempDownButton : public button::Button, public Parented { + public: + TempDownButton() = default; + + protected: + void press_action() override { + this->parent_->on_temp_down_button_press(); + } +}; + + +} // namespace diesel_heater +} // namespace esphome \ No newline at end of file diff --git a/esphome/components/diesel_heater_ble/button.py b/esphome/components/diesel_heater_ble/button.py new file mode 100644 index 0000000000..edbafc9994 --- /dev/null +++ b/esphome/components/diesel_heater_ble/button.py @@ -0,0 +1,46 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button + +from . import DieselHeaterBLE, CONF_HEATER_ID, diesel_heater_ble_ns + +CONF_LEVEL_UP_ID = "level_up" +CONF_LEVEL_DOWN_ID = "level_down" +CONF_TEMP_UP_ID = "temp_up" +CONF_TEMP_DOWN_ID = "temp_down" + + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(CONF_HEATER_ID): cv.use_id(DieselHeaterBLE), + cv.Optional(CONF_LEVEL_UP_ID): button.button_schema( + diesel_heater_ble_ns.class_("LevelUpButton", button.Button), + icon="mdi:arrow-up", + ), + cv.Optional(CONF_LEVEL_DOWN_ID): button.button_schema( + diesel_heater_ble_ns.class_("LevelDownButton", button.Button), + icon="mdi:arrow-down", + ), + cv.Optional(CONF_TEMP_UP_ID): button.button_schema( + diesel_heater_ble_ns.class_("TempUpButton", button.Button), + icon="mdi:arrow-up", + ), + cv.Optional(CONF_TEMP_DOWN_ID): button.button_schema( + diesel_heater_ble_ns.class_("TempDownButton", button.Button), + icon="mdi:arrow-down", + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_HEATER_ID]) + + for var in [CONF_LEVEL_UP_ID, CONF_LEVEL_DOWN_ID, CONF_TEMP_UP_ID, CONF_TEMP_DOWN_ID]: + if conf := config.get(var): + sw_var = await button.new_button(conf) + await cg.register_parented(sw_var, parent) + cg.add(getattr(parent, f"set_{var}_button")(sw_var)) \ No newline at end of file diff --git a/esphome/components/diesel_heater_ble/controllers.h b/esphome/components/diesel_heater_ble/controllers.h new file mode 100644 index 0000000000..21ca63a9af --- /dev/null +++ b/esphome/components/diesel_heater_ble/controllers.h @@ -0,0 +1,130 @@ +#pragma once + +#include "esphome/core/log.h" +#include +#include "state.h" +#include "messages.h" + +namespace esphome { +namespace diesel_heater_ble { + +class HeaterController { +public: + virtual std::vector gen_power_command(HeaterState state, bool power) = 0; + virtual std::vector gen_level_command(HeaterState state, uint8_t value) = 0; + virtual std::vector gen_level_up_command(HeaterState state) = 0; + virtual std::vector gen_level_down_command(HeaterState state) = 0; + virtual std::vector gen_temp_command(HeaterState state, uint8_t value) = 0; + virtual std::vector gen_temp_up_command(HeaterState state) = 0; + virtual std::vector gen_temp_down_command(HeaterState state) = 0; + virtual std::vector get_manual_mode_command(HeaterState state) = 0; + virtual std::vector get_auto_mode_command(HeaterState state) = 0; +}; + +class HeaterController_AA55_E : public HeaterController { +private: + std::vector change_mode_command(HeaterState state, uint8_t target_mode) { + std::vector requests; + if (state.runningmode != target_mode) { + requests.push_back(Request(0x02, target_mode, 0x00)); + } + return requests; + } + + void debug_request(std::vector requests) { + for (const auto &request : requests) { + ESP_LOGI("", "Request: %s", format_hex_pretty(request.toBytes()).c_str()); + } + } + +public: + std::vector gen_power_command(HeaterState state, bool power) override { + std::vector requests; + if (state.runningstate != power) { + requests.push_back(Request(0x03, power ? 0x01 : 0x00, 0x00)); + } + this->debug_request(requests); + return requests; + } + + std::vector gen_level_command(HeaterState state, uint8_t value) override { + auto requests = get_manual_mode_command(state); + if (state.setlevel != value) { + requests.push_back(Request(0x04, value, 0x00)); + } + this->debug_request(requests); + return requests; + } + + std::vector gen_level_up_command(HeaterState state) override { + auto requests = get_manual_mode_command(state); + if (state.setlevel <= 9) { + requests.push_back(Request(0x04, state.setlevel + 1, 0x00)); + } + this->debug_request(requests); + return requests; + } + + std::vector gen_level_down_command(HeaterState state) override { + auto requests = get_manual_mode_command(state); + if (state.setlevel > 1) { + requests.push_back(Request(0x04, state.setlevel - 1, 0x00)); + } + this->debug_request(requests); + return requests; + } + + std::vector gen_temp_command(HeaterState state, uint8_t value) override { + auto requests = get_auto_mode_command(state); + if (state.settemp != value) { + requests.push_back(Request(0x04, value, 0x00)); + } + this->debug_request(requests); + return requests; + } + + std::vector gen_temp_up_command(HeaterState state) override { + auto requests = get_auto_mode_command(state); + if (state.tempunit == 0x00 && state.settemp < 36) { + requests.push_back(Request(0x04, state.settemp + 1, 0x00)); + } else if (state.tempunit == 0x01 && state.settemp < 97) { + requests.push_back(Request(0x04, state.settemp + 1, 0x00)); + } + this->debug_request(requests); + return requests; + } + + std::vector gen_temp_down_command(HeaterState state) override { + auto requests = get_auto_mode_command(state); + if (state.tempunit == 0x00 && state.settemp > 8) { + requests.push_back(Request(0x04, state.settemp - 1, 0x00)); + } else if (state.tempunit == 0x01 && state.settemp > 46) { + requests.push_back(Request(0x04, state.settemp - 1, 0x00)); + } + this->debug_request(requests); + return requests; + } + + std::vector get_manual_mode_command(HeaterState state) override { + return change_mode_command(state, 0x01); + } + + std::vector get_auto_mode_command(HeaterState state) override { + return change_mode_command(state, 0x02); + } +}; + +class ControllerSelector { +public: + static HeaterController *get_controller(HeaterClass heater_class) { + switch (heater_class) { + case HeaterClass::HEATER_AA_55_ENCRYPTED: + return new HeaterController_AA55_E(); + default: + return nullptr; + } + } +}; + +} // namespace diesel_heater_ble +} // namespace esphome diff --git a/esphome/components/diesel_heater_ble/heater.cpp b/esphome/components/diesel_heater_ble/heater.cpp index 2b19703621..35aa392dbe 100644 --- a/esphome/components/diesel_heater_ble/heater.cpp +++ b/esphome/components/diesel_heater_ble/heater.cpp @@ -100,6 +100,14 @@ bool DieselHeaterBLE::ble_register_for_notify(esp_gatt_if_t gattc_if, esp_bd_add void DieselHeaterBLE::on_notification_received(const std::vector &data) { // ESP_LOGD(TAG, "Notification received: %s", format_hex_pretty(data).c_str()); + if (this->controller_ == nullptr) { + this->controller_ = ControllerSelector::get_controller(ResponseParser::detect_heater_class(data)); + if (this->controller_ == nullptr) { + ESP_LOGD(TAG, "Failed to get controller."); + return; + } + } + bool ret = ResponseParser::parse(data, this->state_); if (!ret) { ESP_LOGD(TAG, "Failed to parse response."); @@ -212,22 +220,46 @@ void DieselHeaterBLE::update_sensors(const HeaterState &new_state) { } } -void DieselHeaterBLE::set_power_level_action(float value) { - if (this->get_state().runningmode == 2) { - this->sent_request(SetRunningModeRequest(1).toBytes()); - } - this->sent_request(SetLevelRequest(value + 1).toBytes()); +void DieselHeaterBLE::on_power_level_number(float value) { + this->sent_requests( + this->controller_->gen_level_command(this->state_, value) + ); } -void DieselHeaterBLE::set_temp_number_action(float value) { - if (this->get_state().runningmode == 1) { - this->sent_request(SetRunningModeRequest(2).toBytes()); - } - this->sent_request(SetTemperatureRequest(value).toBytes()); +void DieselHeaterBLE::on_temp_number(float value) { + this->sent_requests( + this->controller_->gen_temp_command(this->state_, value) + ); } -void DieselHeaterBLE::set_power_switch_action(bool state) { - this->sent_request(SetPowerRequest(state).toBytes()); +void DieselHeaterBLE::on_power_switch(bool state) { + this->sent_requests( + this->controller_->gen_power_command(this->state_, state) + ); +} + +void DieselHeaterBLE::on_level_up_button_press() { + this->sent_requests( + this->controller_->gen_level_up_command(this->state_) + ); +} + +void DieselHeaterBLE::on_level_down_button_press() { + this->sent_requests( + this->controller_->gen_level_down_command(this->state_) + ); +} + +void DieselHeaterBLE::on_temp_up_button_press() { + this->sent_requests( + this->controller_->gen_temp_up_command(this->state_) + ); +} + +void DieselHeaterBLE::on_temp_down_button_press() { + this->sent_requests( + this->controller_->gen_temp_down_command(this->state_) + ); } } // namespace diesel_heater_ble diff --git a/esphome/components/diesel_heater_ble/heater.h b/esphome/components/diesel_heater_ble/heater.h index b889f6e09e..f08e293e26 100644 --- a/esphome/components/diesel_heater_ble/heater.h +++ b/esphome/components/diesel_heater_ble/heater.h @@ -10,11 +10,13 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/number/number.h" #include "esphome/components/switch/switch.h" +#include "esphome/components/button/button.h" #include "esphome/core/component.h" #include "esphome/core/log.h" #include "messages.h" #include "state.h" +#include "controllers.h" namespace esphome { namespace diesel_heater_ble { @@ -33,6 +35,12 @@ class DieselHeaterBLE : public Component, public ble_client::BLEClientNode { this->ble_write_chr(this->parent()->get_gattc_if(), this->parent()->get_remote_bda(), this->handle_, data_.data(), data.size()); } + void sent_requests(const std::vector &requests) { + for (const auto &request : requests) { + this->sent_request(request.toBytes()); + } + } + bool ble_write_chr(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda, uint16_t handle, uint8_t *data, uint16_t len); bool ble_read_chr(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda, uint16_t handle); bool ble_register_for_notify(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda); @@ -63,17 +71,28 @@ class DieselHeaterBLE : public Component, public ble_client::BLEClientNode { void set_temp_unit(sensor::Sensor *sensor) { temp_unit_ = sensor; } void set_altitude_unit(sensor::Sensor *sensor) { altitude_unit_ = sensor; } void set_automatic_heating(sensor::Sensor *sensor) { automatic_heating_ = sensor; } - + + // Button setters + void set_level_up_button(button::Button *button) { level_up_button_ = button; } + void set_level_down_button(button::Button *button) { level_down_button_ = button; } + void set_temp_up_button(button::Button *button) { temp_up_button_ = button; } + void set_temp_down_button(button::Button *button) { temp_down_button_ = button; } + + void on_level_up_button_press(); + void on_level_down_button_press(); + void on_temp_up_button_press(); + void on_temp_down_button_press(); + // Number setters void set_power_level_number(number::Number *number) { power_level_number_ = number; } - void set_power_level_action(float value); + void on_power_level_number(float value); void set_set_temp_number(number::Number *number) { set_temp_number_ = number; } - void set_temp_number_action(float value); + void on_temp_number(float value); // Switch setter void set_power_switch(switch_::Switch *sw) { power_switch_ = sw; } - void set_power_switch_action(bool state); + void on_power_switch(bool state); HeaterState get_state() { @@ -86,6 +105,7 @@ class DieselHeaterBLE : public Component, public ble_client::BLEClientNode { esp32_ble_tracker::ESPBTUUID characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw("0000ffe1-0000-1000-8000-00805f9b34fb"); HeaterState state_; + HeaterController *controller_{}; bool response_received_{false}; uint32_t last_request_{0}; @@ -115,6 +135,11 @@ class DieselHeaterBLE : public Component, public ble_client::BLEClientNode { sensor::Sensor *altitude_unit_{}; sensor::Sensor *automatic_heating_{}; + button::Button *level_up_button_{}; + button::Button *level_down_button_{}; + button::Button *temp_up_button_{}; + button::Button *temp_down_button_{}; + number::Number *power_level_number_{}; number::Number *set_temp_number_{}; diff --git a/esphome/components/diesel_heater_ble/messages.h b/esphome/components/diesel_heater_ble/messages.h index bc857b51a4..d3e04e3ad0 100644 --- a/esphome/components/diesel_heater_ble/messages.h +++ b/esphome/components/diesel_heater_ble/messages.h @@ -33,19 +33,15 @@ public: } static HeaterClass detect_heater_class(const std::vector &raw) { - if(raw[0] == 0xAA) { - return raw[1] == 0x55 ? HeaterClass::HEATER_AA_55 : HeaterClass::HEATER_AA_66; - } else if (raw[0] == 0xDA) { - std::vector decrypted = ResponseParser::decrypt(raw); - if (decrypted[1] == 0x55) { + switch (raw[1]) { + case 0x55: + return HeaterClass::HEATER_AA_55; + case 0x66: + return HeaterClass::HEATER_AA_66; + case 0x07: return HeaterClass::HEATER_AA_55_ENCRYPTED; - } else if (decrypted[1] == 0x66) { - return HeaterClass::HEATER_AA_66_ENCRYPTED; - } else { + default: return HeaterClass::HEATER_CLASS_UNKNOWN; - } - } else { - return HeaterClass::HEATER_CLASS_UNKNOWN; } } diff --git a/esphome/components/diesel_heater_ble/number.h b/esphome/components/diesel_heater_ble/number.h index ede45a0bc5..b43aff7618 100644 --- a/esphome/components/diesel_heater_ble/number.h +++ b/esphome/components/diesel_heater_ble/number.h @@ -14,7 +14,7 @@ class PowerLevelNumber : public number::Number, public Parented protected: void control(float value) override { - this->parent_->set_power_level_action(value); + this->parent_->on_power_level_number(value); } }; @@ -24,7 +24,7 @@ class SetTempNumber : public number::Number, public Parented { protected: void control(float value) override { - this->parent_->set_temp_number_action(value); + this->parent_->on_temp_number(value); } }; diff --git a/esphome/components/diesel_heater_ble/switch.h b/esphome/components/diesel_heater_ble/switch.h index ffb426a27c..7e0fd8c474 100644 --- a/esphome/components/diesel_heater_ble/switch.h +++ b/esphome/components/diesel_heater_ble/switch.h @@ -14,7 +14,7 @@ class PowerSwitch : public switch_::Switch, public Parented { protected: void write_state(bool state) override { - this->parent_->set_power_switch_action(state); + this->parent_->on_power_switch(state); } };