Improve code reability.

This commit is contained in:
Your Name 2023-03-22 20:04:39 +01:00
parent 85d1afe874
commit 0956a94c38
28 changed files with 716 additions and 647 deletions

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "esphome/core/helpers.h"
namespace esphome { namespace esphome {
namespace truma_inetbox { namespace truma_inetbox {

View File

@ -1,92 +0,0 @@
#include "TrumaStatusFrame.h"
#include "esphome/core/helpers.h"
#include "TrumaiNetBoxApp.h"
#include "helpers.h"
namespace esphome {
namespace truma_inetbox {
void status_frame_create_empty(StatusFrame *response, u_int8_t message_type, u_int8_t message_length,
u_int8_t command_counter) {
response->inner.genericHeader.service_identifier = LIN_SID_READ_STATE_BUFFER | LIN_SID_RESPONSE;
// Copy header over for this message.
for (size_t i = 1; i < truma_message_header.size(); i++) {
response->raw[i] = truma_message_header[i];
}
response->inner.genericHeader.header_2 = 'T';
response->inner.genericHeader.header_3 = 0x01;
response->inner.genericHeader.message_type = message_type;
response->inner.genericHeader.message_length = message_length;
response->inner.genericHeader.command_counter = command_counter;
}
void status_frame_calculate_checksum(StatusFrame *response) {
response->inner.genericHeader.checksum = 0x0;
response->inner.genericHeader.checksum = data_checksum(&response->raw[10], sizeof(StatusFrame) - 10, 0);
}
void status_frame_create_init(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter) {
status_frame_create_empty(response, STATUS_FRAME_RESPONSE_INIT_REQUEST, 0, command_counter);
// Init frame is empty.
status_frame_calculate_checksum(response);
(*response_len) = sizeof(StatusFrameHeader) + 0;
}
void status_frame_create_update_clock(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter,
u_int8_t hour, u_int8_t minute, u_int8_t second, ClockMode clockMode) {
status_frame_create_empty(response, STATUS_FRAME_CLOCK_RESPONSE, sizeof(StatusFrameClock), command_counter);
response->inner.clock.clock_hour = hour;
response->inner.clock.clock_minute = minute;
response->inner.clock.clock_second = second;
response->inner.clock.display_1 = 0x1;
response->inner.clock.display_2 = 0x1;
response->inner.clock.clock_mode = clockMode;
status_frame_calculate_checksum(response);
(*response_len) = sizeof(StatusFrameHeader) + sizeof(StatusFrameClock);
}
void status_frame_create_update_timer(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter,
TimerActive active, u_int8_t start_hour, u_int8_t start_minute,
u_int8_t stop_hour, u_int8_t stop_minute, TargetTemp room, TargetTemp water,
HeatingMode mode, EnergyMix energy, ElectricPowerLevel elPower) {
status_frame_create_empty(response, STATUS_FRAME_TIMER_RESPONSE, sizeof(StatusFrameTimerResponse), command_counter);
response->inner.timerResponse.timer_target_temp_room = room;
response->inner.timerResponse.timer_heating_mode = mode;
response->inner.timerResponse.timer_target_temp_water = water;
response->inner.timerResponse.timer_energy_mix_a = energy;
response->inner.timerResponse.timer_energy_mix_b = energy;
response->inner.timerResponse.timer_el_power_level_a = elPower;
response->inner.timerResponse.timer_el_power_level_b = elPower;
response->inner.timerResponse.timer_resp_active = active;
response->inner.timerResponse.timer_resp_start_hours = start_hour;
response->inner.timerResponse.timer_resp_start_minutes = start_minute;
response->inner.timerResponse.timer_resp_stop_hours = stop_hour;
response->inner.timerResponse.timer_resp_stop_minutes = stop_minute;
status_frame_calculate_checksum(response);
(*response_len) = sizeof(StatusFrameHeader) + sizeof(StatusFrameTimerResponse);
}
void status_frame_create_update_heater(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter,
TargetTemp room, TargetTemp water, HeatingMode mode, EnergyMix energy,
ElectricPowerLevel elPower) {
status_frame_create_empty(response, STATUS_FRAME_HEATER_RESPONSE, sizeof(StatusFrameHeaterResponse), command_counter);
response->inner.heaterResponse.target_temp_room = room;
response->inner.heaterResponse.heating_mode = mode;
response->inner.heaterResponse.target_temp_water = water;
response->inner.heaterResponse.energy_mix_a = energy;
response->inner.heaterResponse.energy_mix_b = energy;
response->inner.heaterResponse.el_power_level_a = elPower;
response->inner.heaterResponse.el_power_level_b = elPower;
status_frame_calculate_checksum(response);
(*response_len) = sizeof(StatusFrameHeader) + sizeof(StatusFrameHeaterResponse);
}
} // namespace truma_inetbox
} // namespace esphome

View File

@ -1,28 +0,0 @@
#pragma once
#include "TrumaiNetBoxApp.h"
namespace esphome {
namespace truma_inetbox {
void status_frame_create_empty(StatusFrame *response, u_int8_t message_type, u_int8_t message_length,
u_int8_t command_counter);
void status_frame_calculate_checksum(StatusFrame *response);
void status_frame_create_init(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter);
void status_frame_create_update_clock(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter,
u_int8_t hour, u_int8_t minute, u_int8_t second, ClockMode clockMode);
void status_frame_create_update_timer(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter,
TimerActive active, u_int8_t start_hour, u_int8_t start_minute,
u_int8_t stop_hour, u_int8_t stop_minute, TargetTemp room, TargetTemp water,
HeatingMode mode, EnergyMix energy, ElectricPowerLevel elPower);
void status_frame_create_update_heater(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter,
TargetTemp room, TargetTemp water, HeatingMode mode, EnergyMix energy,
ElectricPowerLevel elPower);
} // namespace truma_inetbox
} // namespace esphome

View File

@ -0,0 +1,38 @@
#pragma once
#include "TrumaStructs.h"
#include "helpers.h"
namespace esphome {
namespace truma_inetbox {
inline void status_frame_create_empty(StatusFrame *response, u_int8_t message_type, u_int8_t message_length,
u_int8_t command_counter) {
response->inner.genericHeader.service_identifier = LIN_SID_READ_STATE_BUFFER | LIN_SID_RESPONSE;
// Copy header over for this message.
for (size_t i = 1; i < truma_message_header.size(); i++) {
response->raw[i] = truma_message_header[i];
}
response->inner.genericHeader.header_2 = 'T';
response->inner.genericHeader.header_3 = 0x01;
response->inner.genericHeader.message_type = message_type;
response->inner.genericHeader.message_length = message_length;
response->inner.genericHeader.command_counter = command_counter;
}
inline void status_frame_calculate_checksum(StatusFrame *response) {
response->inner.genericHeader.checksum = 0x0;
response->inner.genericHeader.checksum = data_checksum(&response->raw[10], sizeof(StatusFrame) - 10, 0);
}
inline void status_frame_create_init(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter) {
status_frame_create_empty(response, STATUS_FRAME_RESPONSE_INIT_REQUEST, 0, command_counter);
// Init frame is empty.
status_frame_calculate_checksum(response);
(*response_len) = sizeof(StatusFrameHeader) + 0;
}
} // namespace truma_inetbox
} // namespace esphome

View File

@ -1,16 +1,39 @@
#pragma once #pragma once
#include "TrumaStausFrameStorage.h" #include "TrumaStausFrameStorage.h"
#include "TrumaStructs.h"
#include "esphome/core/helpers.h"
namespace esphome { namespace esphome {
namespace truma_inetbox { namespace truma_inetbox {
template<typename T, typename TResponse> class TrumaStausFrameResponseStorage : public TrumaStausFrameStorage<T> { class TrumaiNetBoxApp;
template<typename T, typename TResponse>
class TrumaStausFrameResponseStorage : public TrumaStausFrameStorage<T>, public Parented<TrumaiNetBoxApp> {
public: public:
void reset(); void reset() override {
TrumaStausFrameStorage<T>::reset();
this->update_status_prepared_ = false;
this->update_status_unsubmitted_ = false;
this->update_status_stale_ = false;
}
bool can_update() { return this->data_valid_; } bool can_update() { return this->data_valid_; }
virtual TResponse *update_prepare() = 0; virtual TResponse *update_prepare() = 0;
void update_submit() { this->update_status_unsubmitted_ = true; } void update_submit() { this->update_status_unsubmitted_ = true; }
const bool has_update() const { return this->update_status_unsubmitted_; }
void set_status(T val) override {
TrumaStausFrameStorage<T>::set_status(val);
this->update_status_stale_ = false;
};
virtual void create_update_data(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter) = 0;
protected:
inline void update_submitted() {
this->update_status_prepared_ = false;
this->update_status_unsubmitted_ = false;
this->update_status_stale_ = true;
}
// Prepared means `update_status_` was copied from `data_`. // Prepared means `update_status_` was copied from `data_`.
bool update_status_prepared_ = false; bool update_status_prepared_ = false;

View File

@ -7,19 +7,35 @@ namespace truma_inetbox {
template<typename T> class TrumaStausFrameStorage { template<typename T> class TrumaStausFrameStorage {
public: public:
bool get_status_valid() { return this->data_valid_; };
const T *get_status() { return &this->data_; };
virtual void set_status(T val) {
this->data_ = val;
this->data_valid_ = true;
this->data_updated_ = true;
this->dump_data();
};
void update() {
if (this->data_updated_) {
this->state_callback_.call(&this->data_);
}
this->data_updated_ = false;
};
virtual void reset() {
this->data_valid_ = false;
this->data_updated_ = false;
};
void add_on_message_callback(std::function<void(const T *)> callback) {
this->state_callback_.add(std::move(callback));
};
virtual void dump_data() const = 0;
protected:
CallbackManager<void(const T *)> state_callback_{}; CallbackManager<void(const T *)> state_callback_{};
T data_; T data_;
bool data_valid_ = false; bool data_valid_ = false;
// Value has changed notify listeners. // Value has changed notify listeners.
bool data_updated_ = false; bool data_updated_ = false;
void add_on_message_callback(std::function<void(const T *)> callback) {
this->state_callback_.add(std::move(callback));
}
bool get_status_valid() { return this->data_valid_; }
const T *get_status() { return &this->data_; }
void update();
void reset();
}; };
} // namespace truma_inetbox } // namespace truma_inetbox

View File

@ -5,6 +5,37 @@
namespace esphome { namespace esphome {
namespace truma_inetbox { namespace truma_inetbox {
#define LIN_SID_RESPONSE 0x40
#define LIN_SID_READ_STATE_BUFFER 0xBA
#define LIN_SID_FIll_STATE_BUFFFER 0xBB
// Response to init are the following frames:
// - 2 * STATUS_FRAME_DEVICES
// - STATUS_FRAME_HEATER
// - STATUS_FRAME_TIMER
// - STAUTS_FRAME_CONFIG
// - STATUS_FRAME_CLOCK
#define STATUS_FRAME_RESPONSE_INIT_REQUEST 0x0A
#define STATUS_FRAME_DEVICES 0x0B
#define STATUS_FRAME_RESPONSE_ACK 0x0D
#define STATUS_FRAME_CLOCK_RESPONSE (STATUS_FRAME_CLOCK - 1)
#define STATUS_FRAME_CLOCK 0x15
// TODO: Documentation and testing of config response.
#define STAUTS_FRAME_CONFIG_RESPONSE (STAUTS_FRAME_CONFIG - 1)
#define STAUTS_FRAME_CONFIG 0x17
#define STATUS_FRAME_HEATER_RESPONSE (STATUS_FRAME_HEATER - 1)
#define STATUS_FRAME_HEATER 0x33
#define STATUS_FRAME_AIRCON_MANUAL_RESPONSE (STATUS_FRAME_AIRCON_MANUAL - 1)
#define STATUS_FRAME_AIRCON_MANUAL 0x35
#define STATUS_FRAME_AIRCON_AUTO_RESPONSE (STATUS_FRAME_AIRCON_AUTO - 1)
#define STATUS_FRAME_AIRCON_AUTO 0x37
#define STATUS_FRAME_TIMER_RESPONSE (STATUS_FRAME_TIMER - 1)
#define STATUS_FRAME_TIMER 0x3D
#define STATUS_FRAME_AIRCON_MANUAL_INIT_RESPONSE (STATUS_FRAME_AIRCON_MANUAL_INIT - 1)
#define STATUS_FRAME_AIRCON_MANUAL_INIT 0x3F
#define STATUS_FRAME_AIRCON_AUTO_INIT_RESPONSE (STATUS_FRAME_AIRCON_AUTO_INIT - 1)
#define STATUS_FRAME_AIRCON_AUTO_INIT 0x41
struct StatusFrameHeader { // NOLINT(altera-struct-pack-align) struct StatusFrameHeader { // NOLINT(altera-struct-pack-align)
// sid // sid
u_int8_t service_identifier; u_int8_t service_identifier;
@ -269,5 +300,28 @@ struct StatusFrameAirconAutoInit { // NOLINT(altera-struct-pack-align)
u_int8_t unknown_20; // 0x00 u_int8_t unknown_20; // 0x00
} __attribute__((packed)); } __attribute__((packed));
union StatusFrame { // NOLINT(altera-struct-pack-align)
u_int8_t raw[41];
struct inner { // NOLINT(altera-struct-pack-align)
StatusFrameHeader genericHeader;
union { // NOLINT(altera-struct-pack-align)
StatusFrameHeater heater;
StatusFrameHeaterResponse heaterResponse;
StatusFrameTimer timer;
StatusFrameTimerResponse timerResponse;
StatusFrameResponseAck responseAck;
StatusFrameClock clock;
StatusFrameConfig config;
StatusFrameDevice device;
StatusFrameAirconManual airconManual;
StatusFrameAirconManualResponse airconManualResponse;
StatusFrameAirconManualInit airconManualInit;
StatusFrameAirconAuto airconAuto;
StatusFrameAirconAutoResponse airconAutoResponse;
StatusFrameAirconAutoInit airconAutoInit;
} __attribute__((packed));
} inner;
} __attribute__((packed));
} // namespace truma_inetbox } // namespace truma_inetbox
} // namespace esphome } // namespace esphome

View File

@ -1,5 +1,5 @@
#include "TrumaiNetBoxApp.h" #include "TrumaiNetBoxApp.h"
#include "TrumaStatusFrame.h" #include "TrumaStatusFrameBuilder.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "helpers.h" #include "helpers.h"
@ -9,15 +9,26 @@ namespace truma_inetbox {
static const char *const TAG = "truma_inetbox.TrumaiNetBoxApp"; static const char *const TAG = "truma_inetbox.TrumaiNetBoxApp";
TrumaiNetBoxApp::TrumaiNetBoxApp() {
this->airconAuto_.set_parent(this);
this->airconManual_.set_parent(this);
this->clock_.set_parent(this);
// this->config_.set_parent(this);
this->heater_.set_parent(this);
this->timer_.set_parent(this);
}
void TrumaiNetBoxApp::update() { void TrumaiNetBoxApp::update() {
// Call listeners in after method 'lin_multiframe_recieved' call. // Call listeners in after method 'lin_multiframe_recieved' call.
// Because 'lin_multiframe_recieved' is time critical an all these sensors can take some time. // Because 'lin_multiframe_recieved' is time critical an all these sensors can take some time.
// Run through callbacks // Run through callbacks
heater_.update(); this->airconAuto_.update();
timer_.update(); this->airconManual_.update();
clock_.update(); this->clock_.update();
config_.update(); this->config_.update();
this->heater_.update();
this->timer_.update();
LinBusProtocol::update(); LinBusProtocol::update();
@ -29,19 +40,12 @@ void TrumaiNetBoxApp::update() {
if (this->time_ != nullptr && !this->update_status_clock_done && this->init_recieved_ > 0) { if (this->time_ != nullptr && !this->update_status_clock_done && this->init_recieved_ > 0) {
if (micros() > ((30 * 1000 * 1000) + this->init_recieved_ /* 30 seconds after init recieved */)) { if (micros() > ((30 * 1000 * 1000) + this->init_recieved_ /* 30 seconds after init recieved */)) {
this->update_status_clock_done = true; this->update_status_clock_done = true;
this->action_write_time(); this->clock_.action_write_time();
} }
} }
#endif // USE_TIME #endif // USE_TIME
} }
template<typename T> void TrumaStausFrameStorage<T>::update() {
if (this->data_updated_) {
this->state_callback_.call(&this->data_);
}
this->data_updated_ = false;
}
const std::array<uint8_t, 4> TrumaiNetBoxApp::lin_identifier() { const std::array<uint8_t, 4> TrumaiNetBoxApp::lin_identifier() {
// Supplier Id: 0x4617 - Truma (Phone: +49 (0)89 4617-0) // Supplier Id: 0x4617 - Truma (Phone: +49 (0)89 4617-0)
// Unknown: // Unknown:
@ -71,27 +75,16 @@ void TrumaiNetBoxApp::lin_reset_device() {
this->device_registered_ = micros(); this->device_registered_ = micros();
this->init_recieved_ = 0; this->init_recieved_ = 0;
this->heater_.reset(); this->airconAuto_.reset();
this->timer_.reset();
this->airconManual_.reset(); this->airconManual_.reset();
this->clock_.reset(); this->clock_.reset();
this->config_.reset(); this->config_.reset();
this->heater_.reset();
this->timer_.reset();
this->update_time_ = 0; this->update_time_ = 0;
} }
template<typename T> void TrumaStausFrameStorage<T>::reset() {
this->data_valid_ = false;
this->data_updated_ = false;
}
template<typename T, typename TResponse> void TrumaStausFrameResponseStorage<T, TResponse>::reset() {
TrumaStausFrameStorage<T>::reset();
this->update_status_prepared_ = false;
this->update_status_unsubmitted_ = false;
this->update_status_stale_ = false;
}
bool TrumaiNetBoxApp::answer_lin_order_(const u_int8_t pid) { bool TrumaiNetBoxApp::answer_lin_order_(const u_int8_t pid) {
// Alive message // Alive message
if (pid == LIN_PID_TRUMA_INET_BOX) { if (pid == LIN_PID_TRUMA_INET_BOX) {
@ -155,64 +148,31 @@ const u_int8_t *TrumaiNetBoxApp::lin_multiframe_recieved(const u_int8_t *message
ESP_LOGD(TAG, "Requested read: Sending init"); ESP_LOGD(TAG, "Requested read: Sending init");
status_frame_create_init(response_frame, return_len, this->message_counter++); status_frame_create_init(response_frame, return_len, this->message_counter++);
return response; return response;
} else if (this->heater_.update_status_unsubmitted_) { } else if (this->heater_.has_update()) {
ESP_LOGD(TAG, "Requested read: Sending heater update"); ESP_LOGD(TAG, "Requested read: Sending heater update");
status_frame_create_update_heater( this->heater_.create_update_data(response_frame, return_len, this->message_counter++);
response_frame, return_len, this->message_counter++, this->heater_.update_status_.target_temp_room,
this->heater_.update_status_.target_temp_water, this->heater_.update_status_.heating_mode,
this->heater_.update_status_.energy_mix_a, this->heater_.update_status_.el_power_level_a);
this->update_time_ = 0; this->update_time_ = 0;
this->heater_.update_status_prepared_ = false;
this->heater_.update_status_unsubmitted_ = false;
this->heater_.update_status_stale_ = true;
return response; return response;
} else if (this->timer_.update_status_unsubmitted_) { } else if (this->timer_.has_update()) {
ESP_LOGD(TAG, "Requested read: Sending timer update"); ESP_LOGD(TAG, "Requested read: Sending timer update");
status_frame_create_update_timer( this->timer_.create_update_data(response_frame, return_len, this->message_counter++);
response_frame, return_len, this->message_counter++, this->timer_.update_status_.timer_resp_active,
this->timer_.update_status_.timer_resp_start_hours, this->timer_.update_status_.timer_resp_start_minutes,
this->timer_.update_status_.timer_resp_stop_hours, this->timer_.update_status_.timer_resp_stop_minutes,
this->timer_.update_status_.timer_target_temp_room, this->timer_.update_status_.timer_target_temp_water,
this->timer_.update_status_.timer_heating_mode, this->timer_.update_status_.timer_energy_mix_a,
this->timer_.update_status_.timer_el_power_level_a);
this->update_time_ = 0; this->update_time_ = 0;
this->timer_.update_status_prepared_ = false;
this->timer_.update_status_unsubmitted_ = false;
this->timer_.update_status_stale_ = true;
return response; return response;
} else if (this->airconManual_.update_status_unsubmitted_) { } else if (this->airconManual_.has_update()) {
ESP_LOGD(TAG, "Requested read: Sending aircon update"); ESP_LOGD(TAG, "Requested read: Sending aircon manual update");
this->airconManual_.create_update_data(response_frame, return_len, this->message_counter++);
status_frame_create_empty(response_frame, STATUS_FRAME_AIRCON_MANUAL_RESPONSE, sizeof(StatusFrameAirconManualResponse), this->update_time_ = 0;
this->message_counter++); return response;
} else if (this->airconAuto_.has_update()) {
response_frame->inner.airconManualResponse.mode = this->airconManual_.update_status_.mode; ESP_LOGD(TAG, "Requested read: Sending aircon auto update");
response_frame->inner.airconManualResponse.unknown_02 = this->airconManual_.update_status_.unknown_02; this->airconAuto_.create_update_data(response_frame, return_len, this->message_counter++);
response_frame->inner.airconManualResponse.operation = this->airconManual_.update_status_.operation;
response_frame->inner.airconManualResponse.energy_mix = this->airconManual_.update_status_.energy_mix;
response_frame->inner.airconManualResponse.target_temp_aircon = this->airconManual_.update_status_.target_temp_aircon;
status_frame_calculate_checksum(response_frame);
(*return_len) = sizeof(StatusFrameHeader) + sizeof(StatusFrameAirconManualResponse);
this->update_time_ = 0; this->update_time_ = 0;
this->airconManual_.update_status_prepared_ = false;
this->airconManual_.update_status_unsubmitted_ = false;
this->airconManual_.update_status_stale_ = true;
return response; return response;
#ifdef USE_TIME #ifdef USE_TIME
} else if (this->update_status_clock_unsubmitted_) { } else if (this->clock_.has_update()) {
if (this->time_ != nullptr) {
ESP_LOGD(TAG, "Requested read: Sending clock update"); ESP_LOGD(TAG, "Requested read: Sending clock update");
// read time live this->clock_.create_update_data(response_frame, return_len, this->message_counter++);
auto now = this->time_->now(); this->update_time_ = 0;
status_frame_create_update_clock(response_frame, return_len, this->message_counter++, now.hour, now.minute,
now.second, this->clock_.data_.clock_mode);
}
this->update_status_clock_unsubmitted_ = false;
return response; return response;
#endif // USE_TIME #endif // USE_TIME
} else { } else {
@ -242,13 +202,10 @@ const u_int8_t *TrumaiNetBoxApp::lin_multiframe_recieved(const u_int8_t *message
// Example: // Example:
// SID<---------PREAMBLE---------->|<---MSG_HEAD---->|tRoom|mo| |elecA|tWate|elecB|mi|mi|cWate|cRoom|st|err | | // SID<---------PREAMBLE---------->|<---MSG_HEAD---->|tRoom|mo| |elecA|tWate|elecB|mi|mi|cWate|cRoom|st|err | |
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.14.33.00.12.00.00.00.00.00.00.00.00.00.00.01.01.CC.0B.6C.0B.00.00.00.00 // BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.14.33.00.12.00.00.00.00.00.00.00.00.00.00.01.01.CC.0B.6C.0B.00.00.00.00
this->heater_.data_ = statusFrame->inner.heater; this->heater_.set_status(statusFrame->inner.heater);
this->heater_.data_valid_ = true;
this->heater_.data_updated_ = true;
this->heater_.update_status_stale_ = false;
return response; return response;
} else if (header->message_type == STATUS_FRAME_AIRCON_MANUAL && header->message_length == sizeof(StatusFrameAirconManual)) { } else if (header->message_type == STATUS_FRAME_AIRCON_MANUAL &&
header->message_length == sizeof(StatusFrameAirconManual)) {
ESP_LOGI(TAG, "StatusFrameAirconManual"); ESP_LOGI(TAG, "StatusFrameAirconManual");
// Example: // Example:
// SID<---------PREAMBLE---------->|<---MSG_HEAD---->| // SID<---------PREAMBLE---------->|<---MSG_HEAD---->|
@ -269,11 +226,7 @@ const u_int8_t *TrumaiNetBoxApp::lin_multiframe_recieved(const u_int8_t *message
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.12.35.00.C2.04.00.71.01.D6.0B.00.00.88.0B.00.00.00.00.00.00.AA.0A // BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.12.35.00.C2.04.00.71.01.D6.0B.00.00.88.0B.00.00.00.00.00.00.AA.0A
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.12.35.00.13.04.00.71.01.86.0B.00.00.88.0B.00.00.00.00.00.00.AA.0A // BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.12.35.00.13.04.00.71.01.86.0B.00.00.88.0B.00.00.00.00.00.00.AA.0A
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.12.35.00.A8.00.00.71.01.00.00.00.00.88.0B.00.00.00.00.00.00.AA.0A // BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.12.35.00.A8.00.00.71.01.00.00.00.00.88.0B.00.00.00.00.00.00.AA.0A
this->airconManual_.data_ = statusFrame->inner.airconManual; this->airconManual_.set_status(statusFrame->inner.airconManual);
this->airconManual_.data_valid_ = true;
this->airconManual_.data_updated_ = true;
this->airconManual_.update_status_stale_ = false;
return response; return response;
} else if (header->message_type == STATUS_FRAME_AIRCON_MANUAL_INIT && } else if (header->message_type == STATUS_FRAME_AIRCON_MANUAL_INIT &&
header->message_length == sizeof(StatusFrameAirconManualInit)) { header->message_length == sizeof(StatusFrameAirconManualInit)) {
@ -282,11 +235,13 @@ const u_int8_t *TrumaiNetBoxApp::lin_multiframe_recieved(const u_int8_t *message
// SID<---------PREAMBLE---------->|<---MSG_HEAD---->| // SID<---------PREAMBLE---------->|<---MSG_HEAD---->|
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.16.3F.00.E2.00.00.71.01.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00 // BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.16.3F.00.E2.00.00.71.01.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00
return response; return response;
} else if (header->message_type == STATUS_FRAME_AIRCON_AUTO && header->message_length == sizeof(StatusFrameAirconAuto)) { } else if (header->message_type == STATUS_FRAME_AIRCON_AUTO &&
header->message_length == sizeof(StatusFrameAirconAuto)) {
ESP_LOGI(TAG, "StatusFrameAirconAuto"); ESP_LOGI(TAG, "StatusFrameAirconAuto");
// Example: // Example:
// SID<---------PREAMBLE---------->|<---MSG_HEAD---->| // SID<---------PREAMBLE---------->|<---MSG_HEAD---->|
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.12.37.00.BF.01.00.01.00.00.00.00.00.00.00.00.00.00.00.49.0B.40.0B // BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.12.37.00.BF.01.00.01.00.00.00.00.00.00.00.00.00.00.00.49.0B.40.0B
this->airconAuto_.set_status(statusFrame->inner.airconAuto);
return response; return response;
} else if (header->message_type == STATUS_FRAME_AIRCON_AUTO_INIT && } else if (header->message_type == STATUS_FRAME_AIRCON_AUTO_INIT &&
header->message_length == sizeof(StatusFrameAirconAutoInit)) { header->message_length == sizeof(StatusFrameAirconAutoInit)) {
@ -301,18 +256,26 @@ const u_int8_t *TrumaiNetBoxApp::lin_multiframe_recieved(const u_int8_t *message
// SID<---------PREAMBLE---------->|<---MSG_HEAD---->|tRoom|mo|??|elecA|tWate|elecB|mi|mi|<--response-->|??|??|on|start|stop-| // SID<---------PREAMBLE---------->|<---MSG_HEAD---->|tRoom|mo|??|elecA|tWate|elecB|mi|mi|<--response-->|??|??|on|start|stop-|
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.18.3D.00.1D.18.0B.01.00.00.00.00.00.00.00.01.01.00.00.00.00.00.00.00.01.00.08.00.09 // BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.18.3D.00.1D.18.0B.01.00.00.00.00.00.00.00.01.01.00.00.00.00.00.00.00.01.00.08.00.09
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.18.3D.00.13.18.0B.0B.00.00.00.00.00.00.00.01.01.00.00.00.00.00.00.00.01.00.08.00.09 // BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.18.3D.00.13.18.0B.0B.00.00.00.00.00.00.00.01.01.00.00.00.00.00.00.00.01.00.08.00.09
this->timer_.data_ = statusFrame->inner.timer; this->timer_.set_status(statusFrame->inner.timer);
this->timer_.data_valid_ = true; return response;
this->timer_.data_updated_ = true;
this->timer_.update_status_stale_ = false;
ESP_LOGD(TAG, "StatusFrameTimer target_temp_room: %f target_temp_water: %f %02u:%02u -> %02u:%02u %s",
temp_code_to_decimal(this->timer_.data_.timer_target_temp_room),
temp_code_to_decimal(this->timer_.data_.timer_target_temp_water), this->timer_.data_.timer_start_hours,
this->timer_.data_.timer_start_minutes, this->timer_.data_.timer_stop_hours,
this->timer_.data_.timer_stop_minutes, ((u_int8_t) this->timer_.data_.timer_active ? " ON" : " OFF"));
} else if (header->message_type == STATUS_FRAME_CLOCK && header->message_length == sizeof(StatusFrameClock)) {
ESP_LOGI(TAG, "StatusFrameClock");
// Example:
// SID<---------PREAMBLE---------->|<---MSG_HEAD---->|
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0A.15.00.5B.0D.20.00.01.01.00.00.01.00.00
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0A.15.00.71.16.00.00.01.01.00.00.02.00.00
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0A.15.00.2B.16.1F.28.01.01.00.00.01.00.00
this->clock_.set_status(statusFrame->inner.clock);
return response;
} else if (header->message_type == STAUTS_FRAME_CONFIG && header->message_length == sizeof(StatusFrameConfig)) {
ESP_LOGI(TAG, "StatusFrameConfig");
// Example:
// SID<---------PREAMBLE---------->|<---MSG_HEAD---->|
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0A.17.00.0F.06.01.B4.0A.AA.0A.00.00.00.00
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0A.17.00.41.06.01.B4.0A.78.0A.00.00.00.00
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0A.17.00.0F.06.01.B4.0A.AA.0A.00.00.00.00
this->config_.set_status(statusFrame->inner.config);
return response; return response;
} else if (header->message_type == STATUS_FRAME_RESPONSE_ACK && } else if (header->message_type == STATUS_FRAME_RESPONSE_ACK &&
header->message_length == sizeof(StatusFrameResponseAck)) { header->message_length == sizeof(StatusFrameResponseAck)) {
@ -335,35 +298,6 @@ const u_int8_t *TrumaiNetBoxApp::lin_multiframe_recieved(const u_int8_t *message
this->lin_reset_device(); this->lin_reset_device();
} }
return response;
} else if (header->message_type == STATUS_FRAME_CLOCK && header->message_length == sizeof(StatusFrameClock)) {
ESP_LOGI(TAG, "StatusFrameClock");
// Example:
// SID<---------PREAMBLE---------->|<---MSG_HEAD---->|
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0A.15.00.5B.0D.20.00.01.01.00.00.01.00.00
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0A.15.00.71.16.00.00.01.01.00.00.02.00.00
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0A.15.00.2B.16.1F.28.01.01.00.00.01.00.00
this->clock_.data_ = statusFrame->inner.clock;
this->clock_.data_valid_ = true;
this->clock_.data_updated_ = true;
ESP_LOGD(TAG, "StatusFrameClock %02d:%02d:%02d", this->clock_.data_.clock_hour, this->clock_.data_.clock_minute,
this->clock_.data_.clock_second);
return response;
} else if (header->message_type == STAUTS_FRAME_CONFIG && header->message_length == sizeof(StatusFrameConfig)) {
ESP_LOGI(TAG, "StatusFrameConfig");
// Example:
// SID<---------PREAMBLE---------->|<---MSG_HEAD---->|
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0A.17.00.0F.06.01.B4.0A.AA.0A.00.00.00.00
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0A.17.00.41.06.01.B4.0A.78.0A.00.00.00.00
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0A.17.00.0F.06.01.B4.0A.AA.0A.00.00.00.00
this->config_.data_ = statusFrame->inner.config;
this->config_.data_valid_ = true;
this->config_.data_updated_ = true;
ESP_LOGD(TAG, "StatusFrameConfig Offset: %.1f", temp_code_to_decimal(this->config_.data_.temp_offset));
return response; return response;
} else if (header->message_type == STATUS_FRAME_DEVICES && header->message_length == sizeof(StatusFrameDevice)) { } else if (header->message_type == STATUS_FRAME_DEVICES && header->message_length == sizeof(StatusFrameDevice)) {
ESP_LOGI(TAG, "StatusFrameDevice"); ESP_LOGI(TAG, "StatusFrameDevice");
@ -380,7 +314,6 @@ const u_int8_t *TrumaiNetBoxApp::lin_multiframe_recieved(const u_int8_t *message
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0C.0B.00.C7.03.00.01.00.50.00.00.04.03.00.60.10 // BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0C.0B.00.C7.03.00.01.00.50.00.00.04.03.00.60.10
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0C.0B.00.71.03.01.01.00.10.03.02.06.00.02.00.00 // BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0C.0B.00.71.03.01.01.00.10.03.02.06.00.02.00.00
// BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0C.0B.00.7C.03.02.01.00.01.0C.00.01.02.01.00.00 // BB.00.1F.00.1E.00.00.22.FF.FF.FF.54.01.0C.0B.00.7C.03.02.01.00.01.0C.00.01.02.01.00.00
auto device = statusFrame->inner.device; auto device = statusFrame->inner.device;
this->init_recieved_ = micros(); this->init_recieved_ = micros();
@ -439,8 +372,8 @@ bool TrumaiNetBoxApp::has_update_to_submit_() {
this->init_requested_ = micros(); this->init_requested_ = micros();
return true; return true;
} }
} else if (this->heater_.update_status_unsubmitted_ || this->timer_.update_status_unsubmitted_ || } else if (this->airconAuto_.has_update() || this->airconManual_.has_update() || this->clock_.has_update() ||
this->update_status_clock_unsubmitted_ || this->airconManual_.update_status_unsubmitted_) { this->heater_.has_update() || this->timer_.has_update()) {
if (this->update_time_ == 0) { if (this->update_time_ == 0) {
// ESP_LOGD(TAG, "Notify CP Plus I got updates."); // ESP_LOGD(TAG, "Notify CP Plus I got updates.");
this->update_time_ = micros(); this->update_time_ = micros();

View File

@ -1,18 +1,13 @@
#pragma once #pragma once
#include <vector>
#include "LinBusProtocol.h" #include "LinBusProtocol.h"
#include "esphome/core/automation.h" #include "TrumaStructs.h"
#include "TrumaEnums.h"
#include "TrumaiNetBoxAppAirconAuto.h" #include "TrumaiNetBoxAppAirconAuto.h"
#include "TrumaiNetBoxAppAirconManual.h" #include "TrumaiNetBoxAppAirconManual.h"
#include "TrumaiNetBoxAppClock.h" #include "TrumaiNetBoxAppClock.h"
#include "TrumaiNetBoxAppConfig.h" #include "TrumaiNetBoxAppConfig.h"
#include "TrumaiNetBoxAppHeater.h" #include "TrumaiNetBoxAppHeater.h"
#include "TrumaiNetBoxAppTimer.h" #include "TrumaiNetBoxAppTimer.h"
#include "TrumaStausFrameResponseStorage.h"
#include "TrumaStausFrameStorage.h"
#include "TrumaStructs.h"
#ifdef USE_TIME #ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h" #include "esphome/components/time/real_time_clock.h"
@ -22,67 +17,19 @@ namespace esphome {
namespace truma_inetbox { namespace truma_inetbox {
#define LIN_PID_TRUMA_INET_BOX 0x18 #define LIN_PID_TRUMA_INET_BOX 0x18
#define LIN_SID_RESPONSE 0x40
#define LIN_SID_READ_STATE_BUFFER 0xBA
#define LIN_SID_FIll_STATE_BUFFFER 0xBB
// Response to init are the following frames:
// - 2 * STATUS_FRAME_DEVICES
// - STATUS_FRAME_HEATER
// - STATUS_FRAME_TIMER
// - STAUTS_FRAME_CONFIG
// - STATUS_FRAME_CLOCK
#define STATUS_FRAME_RESPONSE_INIT_REQUEST 0x0A
#define STATUS_FRAME_DEVICES 0x0B
#define STATUS_FRAME_RESPONSE_ACK 0x0D
#define STATUS_FRAME_CLOCK_RESPONSE (STATUS_FRAME_CLOCK - 1)
#define STATUS_FRAME_CLOCK 0x15
// TODO: Documentation and testing of config response.
#define STAUTS_FRAME_CONFIG_RESPONSE (STAUTS_FRAME_CONFIG - 1)
#define STAUTS_FRAME_CONFIG 0x17
#define STATUS_FRAME_HEATER_RESPONSE (STATUS_FRAME_HEATER - 1)
#define STATUS_FRAME_HEATER 0x33
#define STATUS_FRAME_AIRCON_MANUAL_RESPONSE (STATUS_FRAME_AIRCON_MANUAL - 1)
#define STATUS_FRAME_AIRCON_MANUAL 0x35
#define STATUS_FRAME_AIRCON_AUTO_RESPONSE (STATUS_FRAME_AIRCON_AUTO - 1)
#define STATUS_FRAME_AIRCON_AUTO 0x37
#define STATUS_FRAME_TIMER_RESPONSE (STATUS_FRAME_TIMER - 1)
#define STATUS_FRAME_TIMER 0x3D
#define STATUS_FRAME_AIRCON_MANUAL_INIT_RESPONSE (STATUS_FRAME_AIRCON_MANUAL_INIT - 1)
#define STATUS_FRAME_AIRCON_MANUAL_INIT 0x3F
#define STATUS_FRAME_AIRCON_AUTO_INIT_RESPONSE (STATUS_FRAME_AIRCON_AUTO_INIT - 1)
#define STATUS_FRAME_AIRCON_AUTO_INIT 0x41
union StatusFrame { // NOLINT(altera-struct-pack-align)
u_int8_t raw[41];
struct inner { // NOLINT(altera-struct-pack-align)
StatusFrameHeader genericHeader;
union { // NOLINT(altera-struct-pack-align)
StatusFrameHeater heater;
StatusFrameHeaterResponse heaterResponse;
StatusFrameTimer timer;
StatusFrameTimerResponse timerResponse;
StatusFrameResponseAck responseAck;
StatusFrameClock clock;
StatusFrameConfig config;
StatusFrameDevice device;
StatusFrameAirconManual airconManual;
StatusFrameAirconManualResponse airconManualResponse;
StatusFrameAirconManualInit airconManualInit;
StatusFrameAirconAuto airconAuto;
StatusFrameAirconAutoInit airconAutoInit;
} __attribute__((packed));
} inner;
} __attribute__((packed));
class TrumaiNetBoxApp : public LinBusProtocol { class TrumaiNetBoxApp : public LinBusProtocol {
public: public:
TrumaiNetBoxApp();
void update() override; void update() override;
const std::array<u_int8_t, 4> lin_identifier() override; const std::array<u_int8_t, 4> lin_identifier() override;
void lin_heartbeat() override; void lin_heartbeat() override;
void lin_reset_device() override; void lin_reset_device() override;
const TRUMA_DEVICE get_heater_device() const { return this->heater_device_; }
const TRUMA_DEVICE get_aircon_device() const { return this->aircon_device_; }
TrumaiNetBoxAppAirconAuto *get_aircon_auto() { return &this->airconAuto_; } TrumaiNetBoxAppAirconAuto *get_aircon_auto() { return &this->airconAuto_; }
TrumaiNetBoxAppAirconManual *get_aircon_manual() { return &this->airconManual_; } TrumaiNetBoxAppAirconManual *get_aircon_manual() { return &this->airconManual_; }
TrumaiNetBoxAppClock *get_clock() { return &this->clock_; } TrumaiNetBoxAppClock *get_clock() { return &this->clock_; }
@ -92,23 +39,9 @@ class TrumaiNetBoxApp : public LinBusProtocol {
int64_t get_last_cp_plus_request() { return this->device_registered_; } int64_t get_last_cp_plus_request() { return this->device_registered_; }
bool action_heater_room(u_int8_t temperature, HeatingMode mode = HeatingMode::HEATING_MODE_OFF);
bool action_heater_water(u_int8_t temperature);
bool action_heater_water(TargetTemp temperature);
bool action_heater_electric_power_level(u_int16_t value);
bool action_heater_energy_mix(EnergyMix energy_mix,
ElectricPowerLevel el_power_level = ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0);
bool action_timer_disable();
bool action_timer_activate(u_int16_t start, u_int16_t stop, u_int8_t room_temperature,
HeatingMode mode = HeatingMode::HEATING_MODE_OFF, u_int8_t water_temperature = 0,
EnergyMix energy_mix = EnergyMix::ENERGY_MIX_NONE,
ElectricPowerLevel el_power_level = ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0);
#ifdef USE_TIME #ifdef USE_TIME
void set_time(time::RealTimeClock *time) { time_ = time; } void set_time(time::RealTimeClock *time) { time_ = time; }
bool truma_clock_can_update() { return this->clock_.data_valid_; } time::RealTimeClock *get_time() const { return time_; }
void update_clock_submit() { this->update_status_clock_unsubmitted_ = true; }
bool action_write_time();
#endif // USE_TIME #endif // USE_TIME
protected: protected:
@ -122,12 +55,12 @@ class TrumaiNetBoxApp : public LinBusProtocol {
TRUMA_DEVICE heater_device_ = TRUMA_DEVICE::HEATER_COMBI4; TRUMA_DEVICE heater_device_ = TRUMA_DEVICE::HEATER_COMBI4;
TRUMA_DEVICE aircon_device_ = TRUMA_DEVICE::UNKNOWN; TRUMA_DEVICE aircon_device_ = TRUMA_DEVICE::UNKNOWN;
TrumaiNetBoxAppAirconAuto airconAuto_{}; TrumaiNetBoxAppAirconAuto airconAuto_;
TrumaiNetBoxAppAirconManual airconManual_{}; TrumaiNetBoxAppAirconManual airconManual_;
TrumaiNetBoxAppClock clock_{}; TrumaiNetBoxAppClock clock_;
TrumaiNetBoxAppConfig config_{}; TrumaiNetBoxAppConfig config_;
TrumaiNetBoxAppHeater heater_{}; TrumaiNetBoxAppHeater heater_;
TrumaiNetBoxAppTimer timer_{}; TrumaiNetBoxAppTimer timer_;
// last time CP plus was informed I got an update msg. // last time CP plus was informed I got an update msg.
uint32_t update_time_ = 0; uint32_t update_time_ = 0;
@ -135,15 +68,8 @@ class TrumaiNetBoxApp : public LinBusProtocol {
#ifdef USE_TIME #ifdef USE_TIME
time::RealTimeClock *time_ = nullptr; time::RealTimeClock *time_ = nullptr;
// The behaviour of `update_status_clock_unsubmitted_` is special.
// Just an update is marked. The actual package is prepared when CP Plus asks for the data in the
// `lin_multiframe_recieved` method.
bool update_status_clock_unsubmitted_ = false;
// Mark if the initial clock sync was done. // Mark if the initial clock sync was done.
bool update_status_clock_done = false; bool update_status_clock_done = false;
#else
const bool update_status_clock_unsubmitted_ = false;
#endif // USE_TIME #endif // USE_TIME
bool answer_lin_order_(const u_int8_t pid) override; bool answer_lin_order_(const u_int8_t pid) override;

View File

@ -1,8 +1,13 @@
#include "TrumaiNetBoxAppAirconAuto.h" #include "TrumaiNetBoxAppAirconAuto.h"
#include "TrumaStatusFrameBuilder.h"
#include "esphome/core/log.h"
#include "helpers.h"
namespace esphome { namespace esphome {
namespace truma_inetbox { namespace truma_inetbox {
static const char *const TAG = "truma_inetbox.TrumaiNetBoxAppAirconAuto";
StatusFrameAirconAutoResponse *TrumaiNetBoxAppAirconAuto::update_prepare() { StatusFrameAirconAutoResponse *TrumaiNetBoxAppAirconAuto::update_prepare() {
// An update is currently going on. // An update is currently going on.
if (this->update_status_prepared_ || this->update_status_stale_) { if (this->update_status_prepared_ || this->update_status_stale_) {
@ -20,5 +25,23 @@ StatusFrameAirconAutoResponse *TrumaiNetBoxAppAirconAuto::update_prepare() {
return &this->update_status_; return &this->update_status_;
} }
void TrumaiNetBoxAppAirconAuto::create_update_data(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter){
status_frame_create_empty(response, STATUS_FRAME_AIRCON_AUTO_RESPONSE, sizeof(StatusFrameAirconAutoResponse),
command_counter);
// response->inner.airconAutoResponse.mode = this->update_status_.mode;
// response->inner.airconAutoResponse.unknown_02 = this->update_status_.unknown_02;
// response->inner.airconAutoResponse.operation = this->update_status_.operation;
// response->inner.airconAutoResponse.energy_mix = this->update_status_.energy_mix;
// response->inner.airconAutoResponse.target_temp_aircon = this->update_status_.target_temp_aircon;
status_frame_calculate_checksum(response);
(*response_len) = sizeof(StatusFrameHeader) + sizeof(StatusFrameAirconAutoResponse);
TrumaStausFrameResponseStorage<StatusFrameAirconAuto, StatusFrameAirconAutoResponse>::update_submitted();
}
void TrumaiNetBoxAppAirconAuto::dump_data() const {}
} // namespace truma_inetbox } // namespace truma_inetbox
} // namespace esphome } // namespace esphome

View File

@ -10,6 +10,8 @@ class TrumaiNetBoxAppAirconAuto
: public TrumaStausFrameResponseStorage<StatusFrameAirconAuto, StatusFrameAirconAutoResponse> { : public TrumaStausFrameResponseStorage<StatusFrameAirconAuto, StatusFrameAirconAutoResponse> {
public: public:
StatusFrameAirconAutoResponse *update_prepare() override; StatusFrameAirconAutoResponse *update_prepare() override;
void create_update_data(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter) override;
void dump_data() const override;
}; };
} // namespace truma_inetbox } // namespace truma_inetbox

View File

@ -1,8 +1,13 @@
#include "TrumaiNetBoxAppAirconManual.h" #include "TrumaiNetBoxAppAirconManual.h"
#include "TrumaStatusFrameBuilder.h"
#include "esphome/core/log.h"
#include "helpers.h"
namespace esphome { namespace esphome {
namespace truma_inetbox { namespace truma_inetbox {
static const char *const TAG = "truma_inetbox.TrumaiNetBoxAppAirconManual";
StatusFrameAirconManualResponse *TrumaiNetBoxAppAirconManual::update_prepare() { StatusFrameAirconManualResponse *TrumaiNetBoxAppAirconManual::update_prepare() {
// An update is currently going on. // An update is currently going on.
if (this->update_status_prepared_ || this->update_status_stale_) { if (this->update_status_prepared_ || this->update_status_stale_) {
@ -20,5 +25,24 @@ StatusFrameAirconManualResponse *TrumaiNetBoxAppAirconManual::update_prepare() {
return &this->update_status_; return &this->update_status_;
} }
void TrumaiNetBoxAppAirconManual::create_update_data(StatusFrame *response, u_int8_t *response_len,
u_int8_t command_counter) {
status_frame_create_empty(response, STATUS_FRAME_AIRCON_MANUAL_RESPONSE, sizeof(StatusFrameAirconManualResponse),
command_counter);
response->inner.airconManualResponse.mode = this->update_status_.mode;
response->inner.airconManualResponse.unknown_02 = this->update_status_.unknown_02;
response->inner.airconManualResponse.operation = this->update_status_.operation;
response->inner.airconManualResponse.energy_mix = this->update_status_.energy_mix;
response->inner.airconManualResponse.target_temp_aircon = this->update_status_.target_temp_aircon;
status_frame_calculate_checksum(response);
(*response_len) = sizeof(StatusFrameHeader) + sizeof(StatusFrameAirconManualResponse);
TrumaStausFrameResponseStorage<StatusFrameAirconManual, StatusFrameAirconManualResponse>::update_submitted();
}
void TrumaiNetBoxAppAirconManual::dump_data() const {}
} // namespace truma_inetbox } // namespace truma_inetbox
} // namespace esphome } // namespace esphome

View File

@ -10,6 +10,8 @@ class TrumaiNetBoxAppAirconManual
: public TrumaStausFrameResponseStorage<StatusFrameAirconManual, StatusFrameAirconManualResponse> { : public TrumaStausFrameResponseStorage<StatusFrameAirconManual, StatusFrameAirconManualResponse> {
public: public:
StatusFrameAirconManualResponse *update_prepare() override; StatusFrameAirconManualResponse *update_prepare() override;
void create_update_data(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter) override;
void dump_data() const override;
}; };
} // namespace truma_inetbox } // namespace truma_inetbox

View File

@ -0,0 +1,66 @@
#include "TrumaiNetBoxAppClock.h"
#include "TrumaStatusFrameBuilder.h"
#include "esphome/core/log.h"
#include "helpers.h"
#include "TrumaiNetBoxApp.h"
namespace esphome {
namespace truma_inetbox {
static const char *const TAG = "truma_inetbox.TrumaiNetBoxAppClock";
void TrumaiNetBoxAppClock::dump_data() const {
ESP_LOGD(TAG, "StatusFrameClock %02d:%02d:%02d", this->data_.clock_hour, this->data_.clock_minute,
this->data_.clock_second);
}
#ifdef USE_TIME
bool TrumaiNetBoxAppClock::action_write_time() {
if (!this->can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
if (this->parent_->get_time() == nullptr) {
ESP_LOGW(TAG, "Missing system time component.");
return false;
}
auto now = this->parent_->get_time()->now();
if (!now.is_valid()) {
ESP_LOGW(TAG, "Invalid system time, not syncing to CP Plus.");
return false;
}
// The behaviour of this method is special.
// Just an update is marked. The actual package is prepared when CP Plus asks for the data in the
// `lin_multiframe_recieved` method.
this->update_submit();
return true;
}
void TrumaiNetBoxAppClock::create_update_data(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter) {
if (this->parent_->get_time() != nullptr) {
ESP_LOGD(TAG, "Requested read: Sending clock update");
// read time live
auto now = this->parent_->get_time()->now();
status_frame_create_empty(response, STATUS_FRAME_CLOCK_RESPONSE, sizeof(StatusFrameClock), command_counter);
response->inner.clock.clock_hour = now.hour;
response->inner.clock.clock_minute = now.minute;
response->inner.clock.clock_second = now.second;
response->inner.clock.display_1 = 0x1;
response->inner.clock.display_2 = 0x1;
response->inner.clock.clock_mode = this->data_.clock_mode;
status_frame_calculate_checksum(response);
(*response_len) = sizeof(StatusFrameHeader) + sizeof(StatusFrameClock);
}
this->update_status_unsubmitted_ = false;
}
#endif // USE_TIME
} // namespace truma_inetbox
} // namespace esphome

View File

@ -6,7 +6,27 @@
namespace esphome { namespace esphome {
namespace truma_inetbox { namespace truma_inetbox {
class TrumaiNetBoxAppClock : public TrumaStausFrameStorage<StatusFrameClock> {}; class TrumaiNetBoxApp;
class TrumaiNetBoxAppClock : public TrumaStausFrameStorage<StatusFrameClock>, public Parented<TrumaiNetBoxApp> {
public:
void dump_data() const override;
#ifdef USE_TIME
bool can_update() { return this->data_valid_; }
void update_submit() { this->update_status_unsubmitted_ = true; }
const bool has_update() const { return this->update_status_unsubmitted_; }
bool action_write_time();
void create_update_data(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter);
protected:
// The behaviour of `update_status_clock_unsubmitted_` is special.
// Just an update is marked. The actual package is prepared when CP Plus asks for the data in the
// `lin_multiframe_recieved` method.
bool update_status_unsubmitted_ = false;
#else
constexpr bool has_update() const { return false; }
#endif // USE_TIME
};
} // namespace truma_inetbox } // namespace truma_inetbox
} // namespace esphome } // namespace esphome

View File

@ -0,0 +1,15 @@
#include "TrumaiNetBoxAppConfig.h"
#include "esphome/core/log.h"
#include "helpers.h"
namespace esphome {
namespace truma_inetbox {
static const char *const TAG = "truma_inetbox.TrumaiNetBoxAppConfig";
void TrumaiNetBoxAppConfig::dump_data() const {
ESP_LOGD(TAG, "StatusFrameConfig Offset: %.1f", temp_code_to_decimal(this->config_.data_.temp_offset));
}
} // namespace truma_inetbox
} // namespace esphome

View File

@ -6,7 +6,9 @@
namespace esphome { namespace esphome {
namespace truma_inetbox { namespace truma_inetbox {
class TrumaiNetBoxAppConfig : public TrumaStausFrameStorage<StatusFrameConfig> {}; class TrumaiNetBoxAppConfig : public TrumaStausFrameStorage<StatusFrameConfig> {
void dump_data() const override;
};
} // namespace truma_inetbox } // namespace truma_inetbox
} // namespace esphome } // namespace esphome

View File

@ -1,8 +1,14 @@
#include "TrumaiNetBoxAppHeater.h" #include "TrumaiNetBoxAppHeater.h"
#include "TrumaStatusFrameBuilder.h"
#include "esphome/core/log.h"
#include "helpers.h"
#include "TrumaiNetBoxApp.h"
namespace esphome { namespace esphome {
namespace truma_inetbox { namespace truma_inetbox {
static const char *const TAG = "truma_inetbox.TrumaiNetBoxAppHeater";
StatusFrameHeaterResponse *TrumaiNetBoxAppHeater::update_prepare() { StatusFrameHeaterResponse *TrumaiNetBoxAppHeater::update_prepare() {
// An update is currently going on. // An update is currently going on.
if (this->update_status_prepared_ || this->update_status_stale_) { if (this->update_status_prepared_ || this->update_status_stale_) {
@ -23,5 +29,167 @@ StatusFrameHeaterResponse *TrumaiNetBoxAppHeater::update_prepare() {
return &this->update_status_; return &this->update_status_;
} }
void TrumaiNetBoxAppHeater::create_update_data(StatusFrame *response, u_int8_t *response_len,
u_int8_t command_counter) {
status_frame_create_empty(response, STATUS_FRAME_HEATER_RESPONSE, sizeof(StatusFrameHeaterResponse), command_counter);
response->inner.heaterResponse.target_temp_room = this->update_status_.target_temp_room;
response->inner.heaterResponse.heating_mode = this->update_status_.heating_mode;
response->inner.heaterResponse.target_temp_water = this->update_status_.target_temp_water;
response->inner.heaterResponse.energy_mix_a = this->update_status_.energy_mix_a;
response->inner.heaterResponse.energy_mix_b = this->update_status_.energy_mix_a;
response->inner.heaterResponse.el_power_level_a = this->update_status_.el_power_level_a;
response->inner.heaterResponse.el_power_level_b = this->update_status_.el_power_level_a;
status_frame_calculate_checksum(response);
(*response_len) = sizeof(StatusFrameHeader) + sizeof(StatusFrameHeaterResponse);
TrumaStausFrameResponseStorage<StatusFrameHeater, StatusFrameHeaterResponse>::update_submitted();
}
void TrumaiNetBoxAppHeater::dump_data() const {}
bool TrumaiNetBoxAppHeater::action_heater_room(u_int8_t temperature, HeatingMode mode) {
if (!this->can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
auto heater = this->update_prepare();
heater->target_temp_room = decimal_to_room_temp(temperature);
// Ensure `heating_mode` and `energy_mix_a` is set.
if (heater->target_temp_room == TargetTemp::TARGET_TEMP_OFF) {
heater->heating_mode = HeatingMode::HEATING_MODE_OFF;
} else {
if (this->parent_->get_heater_device() == TRUMA_DEVICE::HEATER_VARIO) {
// If parameter `mode` contains a valid Heating mode use it or else use `AUTO`.
if (mode == HeatingMode::HEATING_MODE_VARIO_HEAT_NIGHT || mode == HeatingMode::HEATING_MODE_VARIO_HEAT_AUTO ||
mode == HeatingMode::HEATING_MODE_BOOST) {
heater->heating_mode = mode;
} else if (heater->heating_mode == HeatingMode::HEATING_MODE_OFF) {
heater->heating_mode = HeatingMode::HEATING_MODE_VARIO_HEAT_AUTO;
}
} else {
// HEATER_COMBI
// If parameter `mode` contains a valid Heating mode use it or else use `ECO`.
if (mode == HeatingMode::HEATING_MODE_ECO || mode == HeatingMode::HEATING_MODE_HIGH ||
mode == HeatingMode::HEATING_MODE_BOOST) {
heater->heating_mode = mode;
} else if (heater->heating_mode == HeatingMode::HEATING_MODE_OFF) {
heater->heating_mode = HeatingMode::HEATING_MODE_ECO;
}
}
}
if (heater->energy_mix_a == EnergyMix::ENERGY_MIX_NONE) {
heater->energy_mix_a = EnergyMix::ENERGY_MIX_GAS;
}
this->update_submit();
return true;
}
bool TrumaiNetBoxAppHeater::action_heater_water(u_int8_t temperature) {
if (!this->can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
auto heater = this->update_prepare();
heater->target_temp_water = decimal_to_water_temp(temperature);
// Ensure `energy_mix_a` is set.
if (heater->target_temp_water != TargetTemp::TARGET_TEMP_OFF && heater->energy_mix_a == EnergyMix::ENERGY_MIX_NONE) {
heater->energy_mix_a = EnergyMix::ENERGY_MIX_GAS;
}
this->update_submit();
return true;
}
bool TrumaiNetBoxAppHeater::action_heater_water(TargetTemp temperature) {
if (!this->can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
auto heater = this->update_prepare();
// If parameter `temperature` contains a valid mode use it or else use `OFF`.
if (temperature == TargetTemp::TARGET_TEMP_WATER_ECO || temperature == TargetTemp::TARGET_TEMP_WATER_HIGH ||
temperature == TargetTemp::TARGET_TEMP_WATER_BOOST) {
heater->target_temp_water = temperature;
} else {
heater->target_temp_water = TargetTemp::TARGET_TEMP_OFF;
}
// Ensure `energy_mix_a` is set.
if (heater->target_temp_water != TargetTemp::TARGET_TEMP_OFF && heater->energy_mix_a == EnergyMix::ENERGY_MIX_NONE) {
heater->energy_mix_a = EnergyMix::ENERGY_MIX_GAS;
}
this->update_submit();
return true;
}
bool TrumaiNetBoxAppHeater::action_heater_electric_power_level(u_int16_t value) {
if (!this->can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
auto heater = this->update_prepare();
heater->el_power_level_a = decimal_to_el_power_level(value);
if (heater->el_power_level_a != ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0) {
if (heater->energy_mix_a != EnergyMix::ENERGY_MIX_MIX &&
heater->energy_mix_a != EnergyMix::ENERGY_MIX_ELECTRICITY) {
heater->energy_mix_a = EnergyMix::ENERGY_MIX_MIX;
}
} else {
heater->energy_mix_a = EnergyMix::ENERGY_MIX_GAS;
}
this->update_submit();
return true;
}
bool TrumaiNetBoxAppHeater::action_heater_energy_mix(EnergyMix energy_mix, ElectricPowerLevel el_power_level) {
if (!this->can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
auto heater = this->update_prepare();
// If parameter `el_power_level` contains a valid mode use it.
if (el_power_level == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0 ||
el_power_level == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_900 ||
el_power_level == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_1800) {
heater->el_power_level_a = el_power_level;
}
if (energy_mix == EnergyMix::ENERGY_MIX_GAS) {
heater->energy_mix_a = energy_mix;
heater->el_power_level_a = ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0;
} else if (energy_mix == EnergyMix::ENERGY_MIX_MIX || energy_mix == EnergyMix::ENERGY_MIX_ELECTRICITY) {
heater->energy_mix_a = energy_mix;
// Electric energy is requested by user without a power level. Set it to minimum.
if (heater->el_power_level_a == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0) {
heater->el_power_level_a = ElectricPowerLevel::ELECTRIC_POWER_LEVEL_900;
}
}
// This last check is reached if invalid `energy_mix` parameter was submitted.
if (heater->el_power_level_a != ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0) {
if (heater->energy_mix_a != EnergyMix::ENERGY_MIX_MIX &&
heater->energy_mix_a != EnergyMix::ENERGY_MIX_ELECTRICITY) {
heater->energy_mix_a = EnergyMix::ENERGY_MIX_MIX;
}
} else {
heater->energy_mix_a = EnergyMix::ENERGY_MIX_GAS;
}
this->update_submit();
return true;
}
} // namespace truma_inetbox } // namespace truma_inetbox
} // namespace esphome } // namespace esphome

View File

@ -9,6 +9,15 @@ namespace truma_inetbox {
class TrumaiNetBoxAppHeater : public TrumaStausFrameResponseStorage<StatusFrameHeater, StatusFrameHeaterResponse> { class TrumaiNetBoxAppHeater : public TrumaStausFrameResponseStorage<StatusFrameHeater, StatusFrameHeaterResponse> {
public: public:
StatusFrameHeaterResponse *update_prepare() override; StatusFrameHeaterResponse *update_prepare() override;
void create_update_data(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter) override;
void dump_data() const override;
bool action_heater_room(u_int8_t temperature, HeatingMode mode = HeatingMode::HEATING_MODE_OFF);
bool action_heater_water(u_int8_t temperature);
bool action_heater_water(TargetTemp temperature);
bool action_heater_electric_power_level(u_int16_t value);
bool action_heater_energy_mix(EnergyMix energy_mix,
ElectricPowerLevel el_power_level = ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0);
}; };
} // namespace truma_inetbox } // namespace truma_inetbox

View File

@ -1,8 +1,14 @@
#include "TrumaiNetBoxAppTimer.h" #include "TrumaiNetBoxAppTimer.h"
#include "TrumaStatusFrameBuilder.h"
#include "esphome/core/log.h"
#include "helpers.h"
#include "TrumaiNetBoxApp.h"
namespace esphome { namespace esphome {
namespace truma_inetbox { namespace truma_inetbox {
static const char *const TAG = "truma_inetbox.TrumaiNetBoxAppTimer";
StatusFrameTimerResponse *TrumaiNetBoxAppTimer::update_prepare() { StatusFrameTimerResponse *TrumaiNetBoxAppTimer::update_prepare() {
// An update is currently going on. // An update is currently going on.
if (this->update_status_prepared_ || this->update_status_stale_) { if (this->update_status_prepared_ || this->update_status_stale_) {
@ -28,5 +34,123 @@ StatusFrameTimerResponse *TrumaiNetBoxAppTimer::update_prepare() {
return &this->update_status_; return &this->update_status_;
} }
void TrumaiNetBoxAppTimer::create_update_data(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter) {
status_frame_create_empty(response, STATUS_FRAME_TIMER_RESPONSE, sizeof(StatusFrameTimerResponse), command_counter);
response->inner.timerResponse.timer_target_temp_room = this->update_status_.timer_target_temp_room;
response->inner.timerResponse.timer_heating_mode = this->update_status_.timer_heating_mode;
response->inner.timerResponse.timer_target_temp_water = this->update_status_.timer_target_temp_water;
response->inner.timerResponse.timer_energy_mix_a = this->update_status_.timer_energy_mix_a;
response->inner.timerResponse.timer_energy_mix_b = this->update_status_.timer_energy_mix_a;
response->inner.timerResponse.timer_el_power_level_a = this->update_status_.timer_el_power_level_a;
response->inner.timerResponse.timer_el_power_level_b = this->update_status_.timer_el_power_level_a;
response->inner.timerResponse.timer_resp_active = this->update_status_.timer_resp_active;
response->inner.timerResponse.timer_resp_start_hours = this->update_status_.timer_resp_start_hours;
response->inner.timerResponse.timer_resp_start_minutes = this->update_status_.timer_resp_start_minutes;
response->inner.timerResponse.timer_resp_stop_hours = this->update_status_.timer_resp_stop_hours;
response->inner.timerResponse.timer_resp_stop_minutes = this->update_status_.timer_resp_stop_minutes;
status_frame_calculate_checksum(response);
(*response_len) = sizeof(StatusFrameHeader) + sizeof(StatusFrameTimerResponse);
TrumaStausFrameResponseStorage<StatusFrameTimer, StatusFrameTimerResponse>::update_submitted();
}
void TrumaiNetBoxAppTimer::dump_data() const {
ESP_LOGD(TAG, "StatusFrameTimer target_temp_room: %f target_temp_water: %f %02u:%02u -> %02u:%02u %s",
temp_code_to_decimal(this->data_.timer_target_temp_room),
temp_code_to_decimal(this->data_.timer_target_temp_water), this->data_.timer_start_hours,
this->data_.timer_start_minutes, this->data_.timer_stop_hours, this->data_.timer_stop_minutes,
((u_int8_t) this->data_.timer_active ? " ON" : " OFF"));
}
bool TrumaiNetBoxAppTimer::action_timer_disable() {
if (!this->can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
auto timer = this->update_prepare();
timer->timer_resp_active = TimerActive::TIMER_ACTIVE_OFF;
this->update_submit();
return true;
}
bool TrumaiNetBoxAppTimer::action_timer_activate(u_int16_t start, u_int16_t stop, u_int8_t room_temperature,
HeatingMode mode, u_int8_t water_temperature, EnergyMix energy_mix,
ElectricPowerLevel el_power_level) {
if (!this->can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
if (start > 1440 || stop > 1440) {
ESP_LOGW(TAG, "Invalid values start/stop submitted.");
return false;
}
auto timer = this->update_prepare();
timer->timer_resp_active = TimerActive::TIMER_ACTIVE_ON;
timer->timer_resp_start_hours = start / 60;
timer->timer_resp_start_minutes = start % 60;
timer->timer_resp_stop_hours = stop / 60;
timer->timer_resp_stop_minutes = stop % 60;
timer->timer_target_temp_room = decimal_to_room_temp(room_temperature);
// Ensure `timer_heating_mode` and `timer_energy_mix_a` is set.
if (timer->timer_target_temp_room == TargetTemp::TARGET_TEMP_OFF) {
timer->timer_heating_mode = HeatingMode::HEATING_MODE_OFF;
} else {
if (this->parent_->get_heater_device() == TRUMA_DEVICE::HEATER_VARIO) {
// If parameter `mode` contains a valid Heating mode use it or else use `AUTO`.
if (mode == HeatingMode::HEATING_MODE_VARIO_HEAT_NIGHT || mode == HeatingMode::HEATING_MODE_VARIO_HEAT_AUTO ||
mode == HeatingMode::HEATING_MODE_BOOST) {
timer->timer_heating_mode = mode;
} else if (timer->timer_heating_mode == HeatingMode::HEATING_MODE_OFF) {
timer->timer_heating_mode = HeatingMode::HEATING_MODE_VARIO_HEAT_AUTO;
}
} else {
// HEATER_COMBI
// If parameter `mode` contains a valid Heating mode use it or else use `ECO`.
if (mode == HeatingMode::HEATING_MODE_ECO || mode == HeatingMode::HEATING_MODE_HIGH ||
mode == HeatingMode::HEATING_MODE_BOOST) {
timer->timer_heating_mode = mode;
} else if (timer->timer_heating_mode == HeatingMode::HEATING_MODE_OFF) {
timer->timer_heating_mode = HeatingMode::HEATING_MODE_ECO;
}
}
}
timer->timer_target_temp_water = decimal_to_water_temp(water_temperature);
// If parameter `el_power_level` contains a valid mode use it.
if (el_power_level == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0 ||
el_power_level == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_900 ||
el_power_level == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_1800) {
timer->timer_el_power_level_a = el_power_level;
}
// Ensure `timer_energy_mix_a` is set
if (timer->timer_energy_mix_a == EnergyMix::ENERGY_MIX_NONE) {
timer->timer_energy_mix_a = EnergyMix::ENERGY_MIX_GAS;
}
// User has supplied a `energy_mix`
if (energy_mix == EnergyMix::ENERGY_MIX_GAS) {
timer->timer_energy_mix_a = energy_mix;
timer->timer_el_power_level_a = ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0;
} else if (energy_mix == EnergyMix::ENERGY_MIX_MIX || energy_mix == EnergyMix::ENERGY_MIX_ELECTRICITY) {
timer->timer_energy_mix_a = energy_mix;
// Electric energy is requested by user without a power level. Set it to minimum.
if (timer->timer_el_power_level_a == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0) {
timer->timer_el_power_level_a = ElectricPowerLevel::ELECTRIC_POWER_LEVEL_900;
}
}
this->update_submit();
return true;
}
} // namespace truma_inetbox } // namespace truma_inetbox
} // namespace esphome } // namespace esphome

View File

@ -9,6 +9,14 @@ namespace truma_inetbox {
class TrumaiNetBoxAppTimer : public TrumaStausFrameResponseStorage<StatusFrameTimer, StatusFrameTimerResponse> { class TrumaiNetBoxAppTimer : public TrumaStausFrameResponseStorage<StatusFrameTimer, StatusFrameTimerResponse> {
public: public:
StatusFrameTimerResponse *update_prepare() override; StatusFrameTimerResponse *update_prepare() override;
void create_update_data(StatusFrame *response, u_int8_t *response_len, u_int8_t command_counter) override;
void dump_data() const override;
bool action_timer_disable();
bool action_timer_activate(u_int16_t start, u_int16_t stop, u_int8_t room_temperature,
HeatingMode mode = HeatingMode::HEATING_MODE_OFF, u_int8_t water_temperature = 0,
EnergyMix energy_mix = EnergyMix::ENERGY_MIX_NONE,
ElectricPowerLevel el_power_level = ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0);
}; };
} // namespace truma_inetbox } // namespace truma_inetbox

View File

@ -1,270 +0,0 @@
#include "TrumaiNetBoxApp.h"
#include "TrumaStatusFrame.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "helpers.h"
namespace esphome {
namespace truma_inetbox {
static const char *const TAG = "truma_inetbox.TrumaiNetBoxApp";
bool TrumaiNetBoxApp::action_heater_room(u_int8_t temperature, HeatingMode mode) {
if (!this->heater_.can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
auto heater = this->heater_.update_prepare();
heater->target_temp_room = decimal_to_room_temp(temperature);
// Ensure `heating_mode` and `energy_mix_a` is set.
if (heater->target_temp_room == TargetTemp::TARGET_TEMP_OFF) {
heater->heating_mode = HeatingMode::HEATING_MODE_OFF;
} else {
if (this->heater_device_ == TRUMA_DEVICE::HEATER_VARIO) {
// If parameter `mode` contains a valid Heating mode use it or else use `AUTO`.
if (mode == HeatingMode::HEATING_MODE_VARIO_HEAT_NIGHT || mode == HeatingMode::HEATING_MODE_VARIO_HEAT_AUTO ||
mode == HeatingMode::HEATING_MODE_BOOST) {
heater->heating_mode = mode;
} else if (heater->heating_mode == HeatingMode::HEATING_MODE_OFF) {
heater->heating_mode = HeatingMode::HEATING_MODE_VARIO_HEAT_AUTO;
}
} else {
// HEATER_COMBI
// If parameter `mode` contains a valid Heating mode use it or else use `ECO`.
if (mode == HeatingMode::HEATING_MODE_ECO || mode == HeatingMode::HEATING_MODE_HIGH ||
mode == HeatingMode::HEATING_MODE_BOOST) {
heater->heating_mode = mode;
} else if (heater->heating_mode == HeatingMode::HEATING_MODE_OFF) {
heater->heating_mode = HeatingMode::HEATING_MODE_ECO;
}
}
}
if (heater->energy_mix_a == EnergyMix::ENERGY_MIX_NONE) {
heater->energy_mix_a = EnergyMix::ENERGY_MIX_GAS;
}
this->heater_.update_submit();
return true;
}
bool TrumaiNetBoxApp::action_heater_water(u_int8_t temperature) {
if (!this->heater_.can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
auto heater = this->heater_.update_prepare();
heater->target_temp_water = decimal_to_water_temp(temperature);
// Ensure `energy_mix_a` is set.
if (heater->target_temp_water != TargetTemp::TARGET_TEMP_OFF && heater->energy_mix_a == EnergyMix::ENERGY_MIX_NONE) {
heater->energy_mix_a = EnergyMix::ENERGY_MIX_GAS;
}
this->heater_.update_submit();
return true;
}
bool TrumaiNetBoxApp::action_heater_water(TargetTemp temperature) {
if (!this->heater_.can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
auto heater = this->heater_.update_prepare();
// If parameter `temperature` contains a valid mode use it or else use `OFF`.
if (temperature == TargetTemp::TARGET_TEMP_WATER_ECO || temperature == TargetTemp::TARGET_TEMP_WATER_HIGH ||
temperature == TargetTemp::TARGET_TEMP_WATER_BOOST) {
heater->target_temp_water = temperature;
} else {
heater->target_temp_water = TargetTemp::TARGET_TEMP_OFF;
}
// Ensure `energy_mix_a` is set.
if (heater->target_temp_water != TargetTemp::TARGET_TEMP_OFF && heater->energy_mix_a == EnergyMix::ENERGY_MIX_NONE) {
heater->energy_mix_a = EnergyMix::ENERGY_MIX_GAS;
}
this->heater_.update_submit();
return true;
}
bool TrumaiNetBoxApp::action_heater_electric_power_level(u_int16_t value) {
if (!this->heater_.can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
auto heater = this->heater_.update_prepare();
heater->el_power_level_a = decimal_to_el_power_level(value);
if (heater->el_power_level_a != ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0) {
if (heater->energy_mix_a != EnergyMix::ENERGY_MIX_MIX &&
heater->energy_mix_a != EnergyMix::ENERGY_MIX_ELECTRICITY) {
heater->energy_mix_a = EnergyMix::ENERGY_MIX_MIX;
}
} else {
heater->energy_mix_a = EnergyMix::ENERGY_MIX_GAS;
}
this->heater_.update_submit();
return true;
}
bool TrumaiNetBoxApp::action_heater_energy_mix(EnergyMix energy_mix, ElectricPowerLevel el_power_level) {
if (!this->heater_.can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
auto heater = this->heater_.update_prepare();
// If parameter `el_power_level` contains a valid mode use it.
if (el_power_level == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0 ||
el_power_level == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_900 ||
el_power_level == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_1800) {
heater->el_power_level_a = el_power_level;
}
if (energy_mix == EnergyMix::ENERGY_MIX_GAS) {
heater->energy_mix_a = energy_mix;
heater->el_power_level_a = ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0;
} else if (energy_mix == EnergyMix::ENERGY_MIX_MIX || energy_mix == EnergyMix::ENERGY_MIX_ELECTRICITY) {
heater->energy_mix_a = energy_mix;
// Electric energy is requested by user without a power level. Set it to minimum.
if (heater->el_power_level_a == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0) {
heater->el_power_level_a = ElectricPowerLevel::ELECTRIC_POWER_LEVEL_900;
}
}
// This last check is reached if invalid `energy_mix` parameter was submitted.
if (heater->el_power_level_a != ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0) {
if (heater->energy_mix_a != EnergyMix::ENERGY_MIX_MIX &&
heater->energy_mix_a != EnergyMix::ENERGY_MIX_ELECTRICITY) {
heater->energy_mix_a = EnergyMix::ENERGY_MIX_MIX;
}
} else {
heater->energy_mix_a = EnergyMix::ENERGY_MIX_GAS;
}
this->heater_.update_submit();
return true;
}
bool TrumaiNetBoxApp::action_timer_disable() {
if (!this->timer_.can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
auto timer = this->timer_.update_prepare();
timer->timer_resp_active = TimerActive::TIMER_ACTIVE_OFF;
this->timer_.update_submit();
return true;
}
bool TrumaiNetBoxApp::action_timer_activate(u_int16_t start, u_int16_t stop, u_int8_t room_temperature,
HeatingMode mode, u_int8_t water_temperature, EnergyMix energy_mix,
ElectricPowerLevel el_power_level) {
if (!this->timer_.can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
if (start > 1440 || stop > 1440) {
ESP_LOGW(TAG, "Invalid values start/stop submitted.");
return false;
}
auto timer = this->timer_.update_prepare();
timer->timer_resp_active = TimerActive::TIMER_ACTIVE_ON;
timer->timer_resp_start_hours = start / 60;
timer->timer_resp_start_minutes = start % 60;
timer->timer_resp_stop_hours = stop / 60;
timer->timer_resp_stop_minutes = stop % 60;
timer->timer_target_temp_room = decimal_to_room_temp(room_temperature);
// Ensure `timer_heating_mode` and `timer_energy_mix_a` is set.
if (timer->timer_target_temp_room == TargetTemp::TARGET_TEMP_OFF) {
timer->timer_heating_mode = HeatingMode::HEATING_MODE_OFF;
} else {
if (this->heater_device_ == TRUMA_DEVICE::HEATER_VARIO) {
// If parameter `mode` contains a valid Heating mode use it or else use `AUTO`.
if (mode == HeatingMode::HEATING_MODE_VARIO_HEAT_NIGHT || mode == HeatingMode::HEATING_MODE_VARIO_HEAT_AUTO ||
mode == HeatingMode::HEATING_MODE_BOOST) {
timer->timer_heating_mode = mode;
} else if (timer->timer_heating_mode == HeatingMode::HEATING_MODE_OFF) {
timer->timer_heating_mode = HeatingMode::HEATING_MODE_VARIO_HEAT_AUTO;
}
} else {
// HEATER_COMBI
// If parameter `mode` contains a valid Heating mode use it or else use `ECO`.
if (mode == HeatingMode::HEATING_MODE_ECO || mode == HeatingMode::HEATING_MODE_HIGH ||
mode == HeatingMode::HEATING_MODE_BOOST) {
timer->timer_heating_mode = mode;
} else if (timer->timer_heating_mode == HeatingMode::HEATING_MODE_OFF) {
timer->timer_heating_mode = HeatingMode::HEATING_MODE_ECO;
}
}
}
timer->timer_target_temp_water = decimal_to_water_temp(water_temperature);
// If parameter `el_power_level` contains a valid mode use it.
if (el_power_level == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0 ||
el_power_level == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_900 ||
el_power_level == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_1800) {
timer->timer_el_power_level_a = el_power_level;
}
// Ensure `timer_energy_mix_a` is set
if (timer->timer_energy_mix_a == EnergyMix::ENERGY_MIX_NONE) {
timer->timer_energy_mix_a = EnergyMix::ENERGY_MIX_GAS;
}
// User has supplied a `energy_mix`
if (energy_mix == EnergyMix::ENERGY_MIX_GAS) {
timer->timer_energy_mix_a = energy_mix;
timer->timer_el_power_level_a = ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0;
} else if (energy_mix == EnergyMix::ENERGY_MIX_MIX || energy_mix == EnergyMix::ENERGY_MIX_ELECTRICITY) {
timer->timer_energy_mix_a = energy_mix;
// Electric energy is requested by user without a power level. Set it to minimum.
if (timer->timer_el_power_level_a == ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0) {
timer->timer_el_power_level_a = ElectricPowerLevel::ELECTRIC_POWER_LEVEL_900;
}
}
this->timer_.update_submit();
return true;
}
#ifdef USE_TIME
bool TrumaiNetBoxApp::action_write_time() {
if (!this->truma_clock_can_update()) {
ESP_LOGW(TAG, "Cannot update Truma.");
return false;
}
if (this->time_ == nullptr) {
ESP_LOGW(TAG, "Missing system time component.");
return false;
}
auto now = this->time_->now();
if (!now.is_valid()) {
ESP_LOGW(TAG, "Invalid system time, not syncing to CP Plus.");
return false;
}
// The behaviour of this method is special.
// Just an update is marked. The actual package is prepared when CP Plus asks for the data in the
// `lin_multiframe_recieved` method.
this->update_clock_submit();
return true;
}
#endif // USE_TIME
} // namespace truma_inetbox
} // namespace esphome

View File

@ -12,7 +12,7 @@ template<typename... Ts> class HeaterRoomTempAction : public Action<Ts...>, publ
TEMPLATABLE_VALUE(HeatingMode, heating_mode) TEMPLATABLE_VALUE(HeatingMode, heating_mode)
void play(Ts... x) override { void play(Ts... x) override {
this->parent_->action_heater_room(this->temperature_.value_or(x..., 0), this->parent_->get_heater()->action_heater_room(this->temperature_.value_or(x..., 0),
this->heating_mode_.value_or(x..., HeatingMode::HEATING_MODE_OFF)); this->heating_mode_.value_or(x..., HeatingMode::HEATING_MODE_OFF));
} }
}; };
@ -21,7 +21,9 @@ template<typename... Ts> class HeaterWaterTempAction : public Action<Ts...>, pub
public: public:
TEMPLATABLE_VALUE(u_int8_t, temperature) TEMPLATABLE_VALUE(u_int8_t, temperature)
void play(Ts... x) override { this->parent_->action_heater_water(this->temperature_.value_or(x..., 0)); } void play(Ts... x) override {
this->parent_->get_heater()->action_heater_water(this->temperature_.value_or(x..., 0));
}
}; };
template<typename... Ts> class HeaterWaterTempEnumAction : public Action<Ts...>, public Parented<TrumaiNetBoxApp> { template<typename... Ts> class HeaterWaterTempEnumAction : public Action<Ts...>, public Parented<TrumaiNetBoxApp> {
@ -29,7 +31,7 @@ template<typename... Ts> class HeaterWaterTempEnumAction : public Action<Ts...>,
TEMPLATABLE_VALUE(TargetTemp, temperature) TEMPLATABLE_VALUE(TargetTemp, temperature)
void play(Ts... x) override { void play(Ts... x) override {
this->parent_->action_heater_water(this->temperature_.value_or(x..., TargetTemp::TARGET_TEMP_OFF)); this->parent_->get_heater()->action_heater_water(this->temperature_.value_or(x..., TargetTemp::TARGET_TEMP_OFF));
} }
}; };
@ -37,7 +39,9 @@ template<typename... Ts> class HeaterElecPowerLevelAction : public Action<Ts...>
public: public:
TEMPLATABLE_VALUE(u_int16_t, watt) TEMPLATABLE_VALUE(u_int16_t, watt)
void play(Ts... x) override { this->parent_->action_heater_electric_power_level(this->watt_.value_or(x..., 0)); } void play(Ts... x) override {
this->parent_->get_heater()->action_heater_electric_power_level(this->watt_.value_or(x..., 0));
}
}; };
template<typename... Ts> class HeaterEnergyMixAction : public Action<Ts...>, public Parented<TrumaiNetBoxApp> { template<typename... Ts> class HeaterEnergyMixAction : public Action<Ts...>, public Parented<TrumaiNetBoxApp> {
@ -46,14 +50,15 @@ template<typename... Ts> class HeaterEnergyMixAction : public Action<Ts...>, pub
TEMPLATABLE_VALUE(ElectricPowerLevel, watt) TEMPLATABLE_VALUE(ElectricPowerLevel, watt)
void play(Ts... x) override { void play(Ts... x) override {
this->parent_->action_heater_energy_mix(this->energy_mix_.value_or(x..., EnergyMix::ENERGY_MIX_GAS), this->parent_->get_heater()->action_heater_energy_mix(
this->energy_mix_.value_or(x..., EnergyMix::ENERGY_MIX_GAS),
this->watt_.value_or(x..., ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0)); this->watt_.value_or(x..., ElectricPowerLevel::ELECTRIC_POWER_LEVEL_0));
} }
}; };
template<typename... Ts> class TimerDisableAction : public Action<Ts...>, public Parented<TrumaiNetBoxApp> { template<typename... Ts> class TimerDisableAction : public Action<Ts...>, public Parented<TrumaiNetBoxApp> {
public: public:
void play(Ts... x) override { this->parent_->action_timer_disable(); } void play(Ts... x) override { this->parent_->get_timer()->action_timer_disable(); }
}; };
template<typename... Ts> class TimerActivateAction : public Action<Ts...>, public Parented<TrumaiNetBoxApp> { template<typename... Ts> class TimerActivateAction : public Action<Ts...>, public Parented<TrumaiNetBoxApp> {
@ -67,7 +72,7 @@ template<typename... Ts> class TimerActivateAction : public Action<Ts...>, publi
TEMPLATABLE_VALUE(ElectricPowerLevel, watt) TEMPLATABLE_VALUE(ElectricPowerLevel, watt)
void play(Ts... x) override { void play(Ts... x) override {
this->parent_->action_timer_activate( this->parent_->get_timer()->action_timer_activate(
this->start_.value(x...), this->stop_.value(x...), this->room_temperature_.value(x...), this->start_.value(x...), this->stop_.value(x...), this->room_temperature_.value(x...),
this->heating_mode_.value_or(x..., HeatingMode::HEATING_MODE_OFF), this->water_temperature_.value_or(x..., 0), this->heating_mode_.value_or(x..., HeatingMode::HEATING_MODE_OFF), this->water_temperature_.value_or(x..., 0),
this->energy_mix_.value_or(x..., EnergyMix::ENERGY_MIX_NONE), this->energy_mix_.value_or(x..., EnergyMix::ENERGY_MIX_NONE),
@ -78,7 +83,7 @@ template<typename... Ts> class TimerActivateAction : public Action<Ts...>, publi
#ifdef USE_TIME #ifdef USE_TIME
template<typename... Ts> class WriteTimeAction : public Action<Ts...>, public Parented<TrumaiNetBoxApp> { template<typename... Ts> class WriteTimeAction : public Action<Ts...>, public Parented<TrumaiNetBoxApp> {
public: public:
void play(Ts... x) override { this->parent_->action_write_time(); } void play(Ts... x) override { this->parent_->get_clock()->action_write_time(); }
}; };
#endif // USE_TIME #endif // USE_TIME

View File

@ -37,7 +37,7 @@ void TrumaRoomClimate::dump_config() { LOG_CLIMATE(TAG, "Truma Room Climate", th
void TrumaRoomClimate::control(const climate::ClimateCall &call) { void TrumaRoomClimate::control(const climate::ClimateCall &call) {
if (call.get_target_temperature().has_value()) { if (call.get_target_temperature().has_value()) {
float temp = *call.get_target_temperature(); float temp = *call.get_target_temperature();
this->parent_->action_heater_room(static_cast<u_int8_t>(temp)); this->parent_->get_heater()->action_heater_room(static_cast<u_int8_t>(temp));
} }
if (call.get_mode().has_value()) { if (call.get_mode().has_value()) {
@ -47,11 +47,11 @@ void TrumaRoomClimate::control(const climate::ClimateCall &call) {
switch (mode) { switch (mode) {
case climate::CLIMATE_MODE_HEAT: case climate::CLIMATE_MODE_HEAT:
if (status_heater->target_temp_room == TargetTemp::TARGET_TEMP_OFF) { if (status_heater->target_temp_room == TargetTemp::TARGET_TEMP_OFF) {
this->parent_->action_heater_room(5); this->parent_->get_heater()->action_heater_room(5);
} }
break; break;
default: default:
this->parent_->action_heater_room(0); this->parent_->get_heater()->action_heater_room(0);
break; break;
} }
} }
@ -65,16 +65,16 @@ void TrumaRoomClimate::control(const climate::ClimateCall &call) {
} }
switch (pres) { switch (pres) {
case climate::CLIMATE_PRESET_ECO: case climate::CLIMATE_PRESET_ECO:
this->parent_->action_heater_room(current_target_temp, HeatingMode::HEATING_MODE_ECO); this->parent_->get_heater()->action_heater_room(current_target_temp, HeatingMode::HEATING_MODE_ECO);
break; break;
case climate::CLIMATE_PRESET_COMFORT: case climate::CLIMATE_PRESET_COMFORT:
this->parent_->action_heater_room(current_target_temp, HeatingMode::HEATING_MODE_HIGH); this->parent_->get_heater()->action_heater_room(current_target_temp, HeatingMode::HEATING_MODE_HIGH);
break; break;
case climate::CLIMATE_PRESET_BOOST: case climate::CLIMATE_PRESET_BOOST:
this->parent_->action_heater_room(current_target_temp, HeatingMode::HEATING_MODE_BOOST); this->parent_->get_heater()->action_heater_room(current_target_temp, HeatingMode::HEATING_MODE_BOOST);
break; break;
default: default:
this->parent_->action_heater_room(0); this->parent_->get_heater()->action_heater_room(0);
break; break;
} }
} }

View File

@ -21,7 +21,7 @@ void TrumaWaterClimate::dump_config() { LOG_CLIMATE(TAG, "Truma Climate", this);
void TrumaWaterClimate::control(const climate::ClimateCall &call) { void TrumaWaterClimate::control(const climate::ClimateCall &call) {
if (call.get_target_temperature().has_value()) { if (call.get_target_temperature().has_value()) {
float temp = *call.get_target_temperature(); float temp = *call.get_target_temperature();
this->parent_->action_heater_water(static_cast<u_int8_t>(temp)); this->parent_->get_heater()->action_heater_water(static_cast<u_int8_t>(temp));
} }
if (call.get_mode().has_value()) { if (call.get_mode().has_value()) {
@ -30,11 +30,11 @@ void TrumaWaterClimate::control(const climate::ClimateCall &call) {
switch (mode) { switch (mode) {
case climate::CLIMATE_MODE_HEAT: case climate::CLIMATE_MODE_HEAT:
if (status_heater->target_temp_water == TargetTemp::TARGET_TEMP_OFF) { if (status_heater->target_temp_water == TargetTemp::TARGET_TEMP_OFF) {
this->parent_->action_heater_water(40); this->parent_->get_heater()->action_heater_water(40);
} }
break; break;
default: default:
this->parent_->action_heater_water(0); this->parent_->get_heater()->action_heater_water(0);
break; break;
} }
} }

View File

@ -1,6 +1,4 @@
#include "helpers.h" #include "helpers.h"
#include "esphome/core/helpers.h"
#include "TrumaiNetBoxApp.h"
namespace esphome { namespace esphome {
namespace truma_inetbox { namespace truma_inetbox {

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "TrumaiNetBoxApp.h" #include "TrumaEnums.h"
#include "esphome/core/helpers.h"
namespace esphome { namespace esphome {
namespace truma_inetbox { namespace truma_inetbox {

View File

@ -26,13 +26,13 @@ void TrumaHeaterNumber::setup() {
void TrumaHeaterNumber::control(float value) { void TrumaHeaterNumber::control(float value) {
switch (this->type_) { switch (this->type_) {
case TRUMA_NUMBER_TYPE::TARGET_ROOM_TEMPERATURE: case TRUMA_NUMBER_TYPE::TARGET_ROOM_TEMPERATURE:
this->parent_->action_heater_room(static_cast<u_int8_t>(value)); this->parent_->get_heater()->action_heater_room(static_cast<u_int8_t>(value));
break; break;
case TRUMA_NUMBER_TYPE::TARGET_WATER_TEMPERATURE: case TRUMA_NUMBER_TYPE::TARGET_WATER_TEMPERATURE:
this->parent_->action_heater_water(static_cast<u_int8_t>(value)); this->parent_->get_heater()->action_heater_water(static_cast<u_int8_t>(value));
break; break;
case TRUMA_NUMBER_TYPE::ELECTRIC_POWER_LEVEL: case TRUMA_NUMBER_TYPE::ELECTRIC_POWER_LEVEL:
this->parent_->action_heater_electric_power_level(static_cast<u_int16_t>(value)); this->parent_->get_heater()->action_heater_electric_power_level(static_cast<u_int16_t>(value));
break; break;
} }
} }