1
0
mirror of https://github.com/esphome/esphome.git synced 2025-03-13 14:18:14 +00:00

Implement button controls and refactor action methods in diesel heater BLE component

This commit is contained in:
Mateusz Wójcik 2025-01-07 12:57:26 +01:00
parent 850e6e0830
commit 1517148f37
8 changed files with 312 additions and 30 deletions

View File

@ -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<DieselHeaterBLE> {
public:
LevelUpButton() = default;
protected:
void press_action() override {
this->parent_->on_level_up_button_press();
}
};
class LevelDownButton : public button::Button, public Parented<DieselHeaterBLE> {
public:
LevelDownButton() = default;
protected:
void press_action() override {
this->parent_->on_level_down_button_press();
}
};
class TempUpButton : public button::Button, public Parented<DieselHeaterBLE> {
public:
TempUpButton() = default;
protected:
void press_action() override {
this->parent_->on_temp_up_button_press();
}
};
class TempDownButton : public button::Button, public Parented<DieselHeaterBLE> {
public:
TempDownButton() = default;
protected:
void press_action() override {
this->parent_->on_temp_down_button_press();
}
};
} // namespace diesel_heater
} // namespace esphome

View File

@ -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))

View File

@ -0,0 +1,130 @@
#pragma once
#include "esphome/core/log.h"
#include <cinttypes>
#include "state.h"
#include "messages.h"
namespace esphome {
namespace diesel_heater_ble {
class HeaterController {
public:
virtual std::vector<Request> gen_power_command(HeaterState state, bool power) = 0;
virtual std::vector<Request> gen_level_command(HeaterState state, uint8_t value) = 0;
virtual std::vector<Request> gen_level_up_command(HeaterState state) = 0;
virtual std::vector<Request> gen_level_down_command(HeaterState state) = 0;
virtual std::vector<Request> gen_temp_command(HeaterState state, uint8_t value) = 0;
virtual std::vector<Request> gen_temp_up_command(HeaterState state) = 0;
virtual std::vector<Request> gen_temp_down_command(HeaterState state) = 0;
virtual std::vector<Request> get_manual_mode_command(HeaterState state) = 0;
virtual std::vector<Request> get_auto_mode_command(HeaterState state) = 0;
};
class HeaterController_AA55_E : public HeaterController {
private:
std::vector<Request> change_mode_command(HeaterState state, uint8_t target_mode) {
std::vector<Request> requests;
if (state.runningmode != target_mode) {
requests.push_back(Request(0x02, target_mode, 0x00));
}
return requests;
}
void debug_request(std::vector<Request> requests) {
for (const auto &request : requests) {
ESP_LOGI("", "Request: %s", format_hex_pretty(request.toBytes()).c_str());
}
}
public:
std::vector<Request> gen_power_command(HeaterState state, bool power) override {
std::vector<Request> requests;
if (state.runningstate != power) {
requests.push_back(Request(0x03, power ? 0x01 : 0x00, 0x00));
}
this->debug_request(requests);
return requests;
}
std::vector<Request> 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<Request> 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<Request> 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<Request> 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<Request> 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<Request> 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<Request> get_manual_mode_command(HeaterState state) override {
return change_mode_command(state, 0x01);
}
std::vector<Request> 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

View File

@ -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<uint8_t> &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

View File

@ -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<Request> &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_{};

View File

@ -33,19 +33,15 @@ public:
}
static HeaterClass detect_heater_class(const std::vector<uint8_t> &raw) {
if(raw[0] == 0xAA) {
return raw[1] == 0x55 ? HeaterClass::HEATER_AA_55 : HeaterClass::HEATER_AA_66;
} else if (raw[0] == 0xDA) {
std::vector<uint8_t> 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;
}
}

View File

@ -14,7 +14,7 @@ class PowerLevelNumber : public number::Number, public Parented<DieselHeaterBLE>
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<DieselHeaterBLE> {
protected:
void control(float value) override {
this->parent_->set_temp_number_action(value);
this->parent_->on_temp_number(value);
}
};

View File

@ -14,7 +14,7 @@ class PowerSwitch : public switch_::Switch, public Parented<DieselHeaterBLE> {
protected:
void write_state(bool state) override {
this->parent_->set_power_switch_action(state);
this->parent_->on_power_switch(state);
}
};