mirror of
https://github.com/esphome/esphome.git
synced 2026-10-06 10:56:43 +00:00
Merge branch 'dev' into get_peername_stack_save_ram
This commit is contained in:
@@ -477,7 +477,7 @@ message FanCommandRequest {
|
||||
bool has_speed_level = 10;
|
||||
int32 speed_level = 11;
|
||||
bool has_preset_mode = 12;
|
||||
string preset_mode = 13;
|
||||
string preset_mode = 13 [(pointer_to_buffer) = true];
|
||||
uint32 device_id = 14 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
|
||||
@@ -747,7 +747,7 @@ message NoiseEncryptionSetKeyRequest {
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_API_NOISE";
|
||||
|
||||
bytes key = 1;
|
||||
bytes key = 1 [(pointer_to_buffer) = true];
|
||||
}
|
||||
|
||||
message NoiseEncryptionSetKeyResponse {
|
||||
@@ -824,9 +824,9 @@ message HomeAssistantStateResponse {
|
||||
option (no_delay) = true;
|
||||
option (ifdef) = "USE_API_HOMEASSISTANT_STATES";
|
||||
|
||||
string entity_id = 1;
|
||||
string state = 2;
|
||||
string attribute = 3;
|
||||
string entity_id = 1 [(pointer_to_buffer) = true];
|
||||
string state = 2 [(pointer_to_buffer) = true];
|
||||
string attribute = 3 [(pointer_to_buffer) = true];
|
||||
}
|
||||
|
||||
// ==================== IMPORT TIME ====================
|
||||
@@ -1091,16 +1091,95 @@ message ClimateCommandRequest {
|
||||
bool has_swing_mode = 14;
|
||||
ClimateSwingMode swing_mode = 15;
|
||||
bool has_custom_fan_mode = 16;
|
||||
string custom_fan_mode = 17;
|
||||
string custom_fan_mode = 17 [(pointer_to_buffer) = true];
|
||||
bool has_preset = 18;
|
||||
ClimatePreset preset = 19;
|
||||
bool has_custom_preset = 20;
|
||||
string custom_preset = 21;
|
||||
string custom_preset = 21 [(pointer_to_buffer) = true];
|
||||
bool has_target_humidity = 22;
|
||||
float target_humidity = 23;
|
||||
uint32 device_id = 24 [(field_ifdef) = "USE_DEVICES"];
|
||||
}
|
||||
|
||||
// ==================== WATER_HEATER ====================
|
||||
enum WaterHeaterMode {
|
||||
WATER_HEATER_MODE_OFF = 0;
|
||||
WATER_HEATER_MODE_ECO = 1;
|
||||
WATER_HEATER_MODE_ELECTRIC = 2;
|
||||
WATER_HEATER_MODE_PERFORMANCE = 3;
|
||||
WATER_HEATER_MODE_HIGH_DEMAND = 4;
|
||||
WATER_HEATER_MODE_HEAT_PUMP = 5;
|
||||
WATER_HEATER_MODE_GAS = 6;
|
||||
}
|
||||
|
||||
message ListEntitiesWaterHeaterResponse {
|
||||
option (id) = 132;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_WATER_HEATER";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||
bool disabled_by_default = 5;
|
||||
EntityCategory entity_category = 6;
|
||||
uint32 device_id = 7 [(field_ifdef) = "USE_DEVICES"];
|
||||
float min_temperature = 8;
|
||||
float max_temperature = 9;
|
||||
float target_temperature_step = 10;
|
||||
repeated WaterHeaterMode supported_modes = 11 [(container_pointer_no_template) = "water_heater::WaterHeaterModeMask"];
|
||||
// Bitmask of WaterHeaterFeature flags
|
||||
uint32 supported_features = 12;
|
||||
}
|
||||
|
||||
message WaterHeaterStateResponse {
|
||||
option (id) = 133;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_WATER_HEATER";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
float current_temperature = 2;
|
||||
float target_temperature = 3;
|
||||
WaterHeaterMode mode = 4;
|
||||
uint32 device_id = 5 [(field_ifdef) = "USE_DEVICES"];
|
||||
// Bitmask of current state flags (bit 0 = away, bit 1 = on)
|
||||
uint32 state = 6;
|
||||
float target_temperature_low = 7;
|
||||
float target_temperature_high = 8;
|
||||
}
|
||||
|
||||
// Bitmask for WaterHeaterCommandRequest.has_fields
|
||||
enum WaterHeaterCommandHasField {
|
||||
WATER_HEATER_COMMAND_HAS_NONE = 0;
|
||||
WATER_HEATER_COMMAND_HAS_MODE = 1;
|
||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE = 2;
|
||||
WATER_HEATER_COMMAND_HAS_STATE = 4;
|
||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8;
|
||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16;
|
||||
}
|
||||
|
||||
message WaterHeaterCommandRequest {
|
||||
option (id) = 134;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_WATER_HEATER";
|
||||
option (no_delay) = true;
|
||||
option (base_class) = "CommandProtoMessage";
|
||||
|
||||
fixed32 key = 1;
|
||||
// Bitmask of which fields are set (see WaterHeaterCommandHasField)
|
||||
uint32 has_fields = 2;
|
||||
WaterHeaterMode mode = 3;
|
||||
float target_temperature = 4;
|
||||
uint32 device_id = 5 [(field_ifdef) = "USE_DEVICES"];
|
||||
// State flags bitmask (bit 0 = away, bit 1 = on)
|
||||
uint32 state = 6;
|
||||
float target_temperature_low = 7;
|
||||
float target_temperature_high = 8;
|
||||
}
|
||||
|
||||
// ==================== NUMBER ====================
|
||||
enum NumberMode {
|
||||
NUMBER_MODE_AUTO = 0;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <cinttypes>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
#ifdef USE_ESP8266
|
||||
#include <pgmspace.h>
|
||||
@@ -42,6 +43,9 @@
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
#include "esphome/components/zwave_proxy/zwave_proxy.h"
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
#include "esphome/components/water_heater/water_heater.h"
|
||||
#endif
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
@@ -93,8 +97,7 @@ static const int CAMERA_STOP_STREAM = 5000;
|
||||
return;
|
||||
#endif // USE_DEVICES
|
||||
|
||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
|
||||
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
|
||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) : parent_(parent) {
|
||||
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
|
||||
auto &noise_ctx = parent->get_noise_ctx();
|
||||
if (noise_ctx.has_psk()) {
|
||||
@@ -135,6 +138,7 @@ void APIConnection::start() {
|
||||
}
|
||||
|
||||
APIConnection::~APIConnection() {
|
||||
this->destroy_active_iterator_();
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
|
||||
@@ -147,6 +151,32 @@ APIConnection::~APIConnection() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void APIConnection::destroy_active_iterator_() {
|
||||
switch (this->active_iterator_) {
|
||||
case ActiveIterator::LIST_ENTITIES:
|
||||
this->iterator_storage_.list_entities.~ListEntitiesIterator();
|
||||
break;
|
||||
case ActiveIterator::INITIAL_STATE:
|
||||
this->iterator_storage_.initial_state.~InitialStateIterator();
|
||||
break;
|
||||
case ActiveIterator::NONE:
|
||||
break;
|
||||
}
|
||||
this->active_iterator_ = ActiveIterator::NONE;
|
||||
}
|
||||
|
||||
void APIConnection::begin_iterator_(ActiveIterator type) {
|
||||
this->destroy_active_iterator_();
|
||||
this->active_iterator_ = type;
|
||||
if (type == ActiveIterator::LIST_ENTITIES) {
|
||||
new (&this->iterator_storage_.list_entities) ListEntitiesIterator(this);
|
||||
this->iterator_storage_.list_entities.begin();
|
||||
} else {
|
||||
new (&this->iterator_storage_.initial_state) InitialStateIterator(this);
|
||||
this->iterator_storage_.initial_state.begin();
|
||||
}
|
||||
}
|
||||
|
||||
void APIConnection::loop() {
|
||||
if (this->flags_.next_close) {
|
||||
// requested a disconnect
|
||||
@@ -189,23 +219,35 @@ void APIConnection::loop() {
|
||||
this->process_batch_();
|
||||
}
|
||||
|
||||
if (!this->list_entities_iterator_.completed()) {
|
||||
this->process_iterator_batch_(this->list_entities_iterator_);
|
||||
} else if (!this->initial_state_iterator_.completed()) {
|
||||
this->process_iterator_batch_(this->initial_state_iterator_);
|
||||
|
||||
// If we've completed initial states, process any remaining and clear the flag
|
||||
if (this->initial_state_iterator_.completed()) {
|
||||
// Process any remaining batched messages immediately
|
||||
if (!this->deferred_batch_.empty()) {
|
||||
this->process_batch_();
|
||||
switch (this->active_iterator_) {
|
||||
case ActiveIterator::LIST_ENTITIES:
|
||||
if (this->iterator_storage_.list_entities.completed()) {
|
||||
this->destroy_active_iterator_();
|
||||
if (this->flags_.state_subscription) {
|
||||
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
|
||||
}
|
||||
} else {
|
||||
this->process_iterator_batch_(this->iterator_storage_.list_entities);
|
||||
}
|
||||
// Now that everything is sent, enable immediate sending for future state changes
|
||||
this->flags_.should_try_send_immediately = true;
|
||||
// Release excess memory from buffers that grew during initial sync
|
||||
this->deferred_batch_.release_buffer();
|
||||
this->helper_->release_buffers();
|
||||
}
|
||||
break;
|
||||
case ActiveIterator::INITIAL_STATE:
|
||||
if (this->iterator_storage_.initial_state.completed()) {
|
||||
this->destroy_active_iterator_();
|
||||
// Process any remaining batched messages immediately
|
||||
if (!this->deferred_batch_.empty()) {
|
||||
this->process_batch_();
|
||||
}
|
||||
// Now that everything is sent, enable immediate sending for future state changes
|
||||
this->flags_.should_try_send_immediately = true;
|
||||
// Release excess memory from buffers that grew during initial sync
|
||||
this->deferred_batch_.release_buffer();
|
||||
this->helper_->release_buffers();
|
||||
} else {
|
||||
this->process_iterator_batch_(this->iterator_storage_.initial_state);
|
||||
}
|
||||
break;
|
||||
case ActiveIterator::NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->flags_.sent_ping) {
|
||||
@@ -229,33 +271,17 @@ void APIConnection::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
|
||||
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
|
||||
bool done = this->image_reader_->available() == to_send;
|
||||
|
||||
CameraImageResponse msg;
|
||||
msg.key = camera::Camera::instance()->get_object_id_hash();
|
||||
msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
|
||||
msg.done = done;
|
||||
#ifdef USE_DEVICES
|
||||
msg.device_id = camera::Camera::instance()->get_device_id();
|
||||
#endif
|
||||
|
||||
if (this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) {
|
||||
this->image_reader_->consume_data(to_send);
|
||||
if (done) {
|
||||
this->image_reader_->return_image();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
if (state_subs_at_ >= 0) {
|
||||
this->process_state_subscriptions_();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
// Process camera last - state updates are higher priority
|
||||
// (missing a frame is fine, missing a state update is not)
|
||||
this->try_send_camera_image_();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
|
||||
@@ -448,7 +474,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
if (msg.has_direction)
|
||||
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
|
||||
if (msg.has_preset_mode)
|
||||
call.set_preset_mode(msg.preset_mode);
|
||||
call.set_preset_mode(reinterpret_cast<const char *>(msg.preset_mode), msg.preset_mode_len);
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
@@ -713,11 +739,11 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||
if (msg.has_fan_mode)
|
||||
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
|
||||
if (msg.has_custom_fan_mode)
|
||||
call.set_fan_mode(msg.custom_fan_mode);
|
||||
call.set_fan_mode(reinterpret_cast<const char *>(msg.custom_fan_mode), msg.custom_fan_mode_len);
|
||||
if (msg.has_preset)
|
||||
call.set_preset(static_cast<climate::ClimatePreset>(msg.preset));
|
||||
if (msg.has_custom_preset)
|
||||
call.set_preset(msg.custom_preset);
|
||||
call.set_preset(reinterpret_cast<const char *>(msg.custom_preset), msg.custom_preset_len);
|
||||
if (msg.has_swing_mode)
|
||||
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
|
||||
call.perform();
|
||||
@@ -1058,6 +1084,36 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
|
||||
#endif
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
void APIConnection::try_send_camera_image_() {
|
||||
if (!this->image_reader_)
|
||||
return;
|
||||
|
||||
// Send as many chunks as possible without blocking
|
||||
while (this->image_reader_->available()) {
|
||||
if (!this->helper_->can_write_without_blocking())
|
||||
return;
|
||||
|
||||
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
|
||||
bool done = this->image_reader_->available() == to_send;
|
||||
|
||||
CameraImageResponse msg;
|
||||
msg.key = camera::Camera::instance()->get_object_id_hash();
|
||||
msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
|
||||
msg.done = done;
|
||||
#ifdef USE_DEVICES
|
||||
msg.device_id = camera::Camera::instance()->get_device_id();
|
||||
#endif
|
||||
|
||||
if (!this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) {
|
||||
return; // Send failed, try again later
|
||||
}
|
||||
this->image_reader_->consume_data(to_send);
|
||||
if (done) {
|
||||
this->image_reader_->return_image();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image) {
|
||||
if (!this->flags_.state_subscription)
|
||||
return;
|
||||
@@ -1065,8 +1121,11 @@ void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image)
|
||||
return;
|
||||
if (this->image_reader_->available())
|
||||
return;
|
||||
if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE))
|
||||
if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE)) {
|
||||
this->image_reader_->set_image(std::move(image));
|
||||
// Try to send immediately to reduce latency
|
||||
this->try_send_camera_image_();
|
||||
}
|
||||
}
|
||||
uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
@@ -1307,6 +1366,57 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool APIConnection::send_water_heater_state(water_heater::WaterHeater *water_heater) {
|
||||
return this->send_message_smart_(water_heater, &APIConnection::try_send_water_heater_state,
|
||||
WaterHeaterStateResponse::MESSAGE_TYPE, WaterHeaterStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
auto *wh = static_cast<water_heater::WaterHeater *>(entity);
|
||||
WaterHeaterStateResponse resp;
|
||||
resp.mode = static_cast<enums::WaterHeaterMode>(wh->get_mode());
|
||||
resp.current_temperature = wh->get_current_temperature();
|
||||
resp.target_temperature = wh->get_target_temperature();
|
||||
resp.target_temperature_low = wh->get_target_temperature_low();
|
||||
resp.target_temperature_high = wh->get_target_temperature_high();
|
||||
resp.state = wh->get_state();
|
||||
resp.key = wh->get_object_id_hash();
|
||||
|
||||
return encode_message_to_buffer(resp, WaterHeaterStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
auto *wh = static_cast<water_heater::WaterHeater *>(entity);
|
||||
ListEntitiesWaterHeaterResponse msg;
|
||||
auto traits = wh->get_traits();
|
||||
msg.min_temperature = traits.get_min_temperature();
|
||||
msg.max_temperature = traits.get_max_temperature();
|
||||
msg.target_temperature_step = traits.get_target_temperature_step();
|
||||
msg.supported_modes = &traits.get_supported_modes();
|
||||
msg.supported_features = traits.get_feature_flags();
|
||||
return fill_and_encode_entity_info(wh, msg, ListEntitiesWaterHeaterResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||
is_single);
|
||||
}
|
||||
|
||||
void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) {
|
||||
ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater)
|
||||
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE)
|
||||
call.set_mode(static_cast<water_heater::WaterHeaterMode>(msg.mode));
|
||||
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE)
|
||||
call.set_target_temperature(msg.target_temperature);
|
||||
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW)
|
||||
call.set_target_temperature_low(msg.target_temperature_low);
|
||||
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH)
|
||||
call.set_target_temperature_high(msg.target_temperature_high);
|
||||
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_STATE) {
|
||||
call.set_away((msg.state & water_heater::WATER_HEATER_STATE_AWAY) != 0);
|
||||
call.set_on((msg.state & water_heater::WATER_HEATER_STATE_ON) != 0);
|
||||
}
|
||||
call.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void APIConnection::send_event(event::Event *event, const char *event_type) {
|
||||
this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
|
||||
@@ -1585,15 +1695,29 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
// Compare entity_id and attribute with message fields
|
||||
bool entity_match = (strcmp(it.entity_id, msg.entity_id.c_str()) == 0);
|
||||
bool attribute_match = (it.attribute != nullptr && strcmp(it.attribute, msg.attribute.c_str()) == 0) ||
|
||||
(it.attribute == nullptr && msg.attribute.empty());
|
||||
// Skip if entity_id is empty (invalid message)
|
||||
if (msg.entity_id_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity_match && attribute_match) {
|
||||
it.callback(msg.state);
|
||||
for (auto &it : this->parent_->get_state_subs()) {
|
||||
// Compare entity_id: check length matches and content matches
|
||||
size_t entity_id_len = strlen(it.entity_id);
|
||||
if (entity_id_len != msg.entity_id_len || memcmp(it.entity_id, msg.entity_id, msg.entity_id_len) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compare attribute: either both have matching attribute, or both have none
|
||||
size_t sub_attr_len = it.attribute != nullptr ? strlen(it.attribute) : 0;
|
||||
if (sub_attr_len != msg.attribute_len ||
|
||||
(sub_attr_len > 0 && memcmp(it.attribute, msg.attribute, sub_attr_len) != 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create temporary string for callback (callback takes const std::string &)
|
||||
// Handle empty state (nullptr with len=0)
|
||||
std::string state(msg.state_len > 0 ? reinterpret_cast<const char *>(msg.state) : "", msg.state_len);
|
||||
it.callback(state);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1669,13 +1793,13 @@ bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryption
|
||||
resp.success = false;
|
||||
|
||||
psk_t psk{};
|
||||
if (msg.key.empty()) {
|
||||
if (msg.key_len == 0) {
|
||||
if (this->parent_->clear_noise_psk(true)) {
|
||||
resp.success = true;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to clear encryption key");
|
||||
}
|
||||
} else if (base64_decode(msg.key, psk.data(), psk.size()) != psk.size()) {
|
||||
} else if (base64_decode(msg.key, msg.key_len, psk.data(), psk.size()) != psk.size()) {
|
||||
ESP_LOGW(TAG, "Invalid encryption key length");
|
||||
} else if (!this->parent_->save_noise_psk(psk, true)) {
|
||||
ESP_LOGW(TAG, "Failed to save encryption key");
|
||||
|
||||
@@ -177,6 +177,11 @@ class APIConnection final : public APIServerConnection {
|
||||
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool send_water_heater_state(water_heater::WaterHeater *water_heater);
|
||||
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void send_event(event::Event *event, const char *event_type);
|
||||
#endif
|
||||
@@ -204,10 +209,14 @@ class APIConnection final : public APIServerConnection {
|
||||
bool send_disconnect_response(const DisconnectRequest &msg) override;
|
||||
bool send_ping_response(const PingRequest &msg) override;
|
||||
bool send_device_info_response(const DeviceInfoRequest &msg) override;
|
||||
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
|
||||
void list_entities(const ListEntitiesRequest &msg) override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
|
||||
void subscribe_states(const SubscribeStatesRequest &msg) override {
|
||||
this->flags_.state_subscription = true;
|
||||
this->initial_state_iterator_.begin();
|
||||
// Start initial state iterator only if no iterator is active
|
||||
// If list_entities is running, we'll start initial_state when it completes
|
||||
if (this->active_iterator_ == ActiveIterator::NONE) {
|
||||
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
|
||||
}
|
||||
}
|
||||
void subscribe_logs(const SubscribeLogsRequest &msg) override {
|
||||
this->flags_.log_subscription = msg.level;
|
||||
@@ -293,6 +302,10 @@ class APIConnection final : public APIServerConnection {
|
||||
// Helper function to handle authentication completion
|
||||
void complete_authentication_();
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
void try_send_camera_image_();
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void process_state_subscriptions_();
|
||||
#endif
|
||||
@@ -316,17 +329,10 @@ class APIConnection final : public APIServerConnection {
|
||||
APIConnection *conn, uint32_t remaining_size, bool is_single) {
|
||||
// Set common fields that are shared by all entity types
|
||||
msg.key = entity->get_object_id_hash();
|
||||
// Try to use static reference first to avoid allocation
|
||||
StringRef static_ref = entity->get_object_id_ref_for_api_();
|
||||
// Store dynamic string outside the if-else to maintain lifetime
|
||||
std::string object_id;
|
||||
if (!static_ref.empty()) {
|
||||
msg.set_object_id(static_ref);
|
||||
} else {
|
||||
// Dynamic case - need to allocate
|
||||
object_id = entity->get_object_id();
|
||||
msg.set_object_id(StringRef(object_id));
|
||||
}
|
||||
// Get object_id with zero heap allocation
|
||||
// Static case returns direct reference, dynamic case uses buffer
|
||||
char object_id_buf[OBJECT_ID_MAX_LEN];
|
||||
msg.set_object_id(entity->get_object_id_to(object_id_buf));
|
||||
|
||||
if (entity->has_own_name()) {
|
||||
msg.set_name(entity->get_name());
|
||||
@@ -462,6 +468,12 @@ class APIConnection final : public APIServerConnection {
|
||||
static uint16_t try_send_alarm_control_panel_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single);
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
static uint16_t try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single);
|
||||
static uint16_t try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single);
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
|
||||
uint32_t remaining_size, bool is_single);
|
||||
@@ -496,10 +508,22 @@ class APIConnection final : public APIServerConnection {
|
||||
std::unique_ptr<APIFrameHelper> helper_;
|
||||
APIServer *parent_;
|
||||
|
||||
// Group 2: Larger objects (must be 4-byte aligned)
|
||||
// These contain vectors/pointers internally, so putting them early ensures good alignment
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
ListEntitiesIterator list_entities_iterator_;
|
||||
// Group 2: Iterator union (saves ~16 bytes vs separate iterators)
|
||||
// These iterators are never active simultaneously - list_entities runs to completion
|
||||
// before initial_state begins, so we use a union with explicit construction/destruction.
|
||||
enum class ActiveIterator : uint8_t { NONE, LIST_ENTITIES, INITIAL_STATE };
|
||||
|
||||
union IteratorUnion {
|
||||
ListEntitiesIterator list_entities;
|
||||
InitialStateIterator initial_state;
|
||||
// Constructor/destructor do nothing - use placement new/explicit destructor
|
||||
IteratorUnion() {}
|
||||
~IteratorUnion() {}
|
||||
} iterator_storage_;
|
||||
|
||||
// Helper methods for iterator lifecycle management
|
||||
void destroy_active_iterator_();
|
||||
void begin_iterator_(ActiveIterator type);
|
||||
#ifdef USE_CAMERA
|
||||
std::unique_ptr<camera::CameraImageReader> image_reader_;
|
||||
#endif
|
||||
@@ -614,7 +638,9 @@ class APIConnection final : public APIServerConnection {
|
||||
// 2-byte types immediately after flags_ (no padding between them)
|
||||
uint16_t client_api_version_major_{0};
|
||||
uint16_t client_api_version_minor_{0};
|
||||
// Total: 2 (flags) + 2 + 2 = 6 bytes, then 2 bytes padding to next 4-byte boundary
|
||||
// 1-byte type to fill padding
|
||||
ActiveIterator active_iterator_{ActiveIterator::NONE};
|
||||
// Total: 2 (flags) + 2 + 2 + 1 = 7 bytes, then 1 byte padding to next 4-byte boundary
|
||||
|
||||
uint32_t get_batch_delay_ms_() const;
|
||||
// Message will use 8 more bytes than the minimum size, and typical
|
||||
|
||||
@@ -447,9 +447,12 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
}
|
||||
bool FanCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 13:
|
||||
this->preset_mode = value.as_string();
|
||||
case 13: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->preset_mode = value.data();
|
||||
this->preset_mode_len = value.size();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -855,9 +858,12 @@ void SubscribeLogsResponse::calculate_size(ProtoSize &size) const {
|
||||
#ifdef USE_API_NOISE
|
||||
bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
this->key = value.as_string();
|
||||
case 1: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->key = value.data();
|
||||
this->key_len = value.size();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -960,15 +966,24 @@ void SubscribeHomeAssistantStateResponse::calculate_size(ProtoSize &size) const
|
||||
}
|
||||
bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
this->entity_id = value.as_string();
|
||||
case 1: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->entity_id = value.data();
|
||||
this->entity_id_len = value.size();
|
||||
break;
|
||||
case 2:
|
||||
this->state = value.as_string();
|
||||
}
|
||||
case 2: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->state = value.data();
|
||||
this->state_len = value.size();
|
||||
break;
|
||||
case 3:
|
||||
this->attribute = value.as_string();
|
||||
}
|
||||
case 3: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->attribute = value.data();
|
||||
this->attribute_len = value.size();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1392,12 +1407,18 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
|
||||
}
|
||||
bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 17:
|
||||
this->custom_fan_mode = value.as_string();
|
||||
case 17: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->custom_fan_mode = value.data();
|
||||
this->custom_fan_mode_len = value.size();
|
||||
break;
|
||||
case 21:
|
||||
this->custom_preset = value.as_string();
|
||||
}
|
||||
case 21: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->custom_preset = value.data();
|
||||
this->custom_preset_len = value.size();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1426,6 +1447,114 @@ bool ClimateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->object_id_ref_);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name_ref_);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(4, this->icon_ref_);
|
||||
#endif
|
||||
buffer.encode_bool(5, this->disabled_by_default);
|
||||
buffer.encode_uint32(6, static_cast<uint32_t>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(7, this->device_id);
|
||||
#endif
|
||||
buffer.encode_float(8, this->min_temperature);
|
||||
buffer.encode_float(9, this->max_temperature);
|
||||
buffer.encode_float(10, this->target_temperature_step);
|
||||
for (const auto &it : *this->supported_modes) {
|
||||
buffer.encode_uint32(11, static_cast<uint32_t>(it), true);
|
||||
}
|
||||
buffer.encode_uint32(12, this->supported_features);
|
||||
}
|
||||
void ListEntitiesWaterHeaterResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_length(1, this->object_id_ref_.size());
|
||||
size.add_fixed32(1, this->key);
|
||||
size.add_length(1, this->name_ref_.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size.add_length(1, this->icon_ref_.size());
|
||||
#endif
|
||||
size.add_bool(1, this->disabled_by_default);
|
||||
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
size.add_float(1, this->min_temperature);
|
||||
size.add_float(1, this->max_temperature);
|
||||
size.add_float(1, this->target_temperature_step);
|
||||
if (!this->supported_modes->empty()) {
|
||||
for (const auto &it : *this->supported_modes) {
|
||||
size.add_uint32_force(1, static_cast<uint32_t>(it));
|
||||
}
|
||||
}
|
||||
size.add_uint32(1, this->supported_features);
|
||||
}
|
||||
void WaterHeaterStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_float(2, this->current_temperature);
|
||||
buffer.encode_float(3, this->target_temperature);
|
||||
buffer.encode_uint32(4, static_cast<uint32_t>(this->mode));
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(5, this->device_id);
|
||||
#endif
|
||||
buffer.encode_uint32(6, this->state);
|
||||
buffer.encode_float(7, this->target_temperature_low);
|
||||
buffer.encode_float(8, this->target_temperature_high);
|
||||
}
|
||||
void WaterHeaterStateResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_fixed32(1, this->key);
|
||||
size.add_float(1, this->current_temperature);
|
||||
size.add_float(1, this->target_temperature);
|
||||
size.add_uint32(1, static_cast<uint32_t>(this->mode));
|
||||
#ifdef USE_DEVICES
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
size.add_uint32(1, this->state);
|
||||
size.add_float(1, this->target_temperature_low);
|
||||
size.add_float(1, this->target_temperature_high);
|
||||
}
|
||||
bool WaterHeaterCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
this->has_fields = value.as_uint32();
|
||||
break;
|
||||
case 3:
|
||||
this->mode = static_cast<enums::WaterHeaterMode>(value.as_uint32());
|
||||
break;
|
||||
#ifdef USE_DEVICES
|
||||
case 5:
|
||||
this->device_id = value.as_uint32();
|
||||
break;
|
||||
#endif
|
||||
case 6:
|
||||
this->state = value.as_uint32();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool WaterHeaterCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1:
|
||||
this->key = value.as_fixed32();
|
||||
break;
|
||||
case 4:
|
||||
this->target_temperature = value.as_float();
|
||||
break;
|
||||
case 7:
|
||||
this->target_temperature_low = value.as_float();
|
||||
break;
|
||||
case 8:
|
||||
this->target_temperature_high = value.as_float();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->object_id_ref_);
|
||||
|
||||
@@ -129,6 +129,25 @@ enum ClimatePreset : uint32_t {
|
||||
CLIMATE_PRESET_ACTIVITY = 7,
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
enum WaterHeaterMode : uint32_t {
|
||||
WATER_HEATER_MODE_OFF = 0,
|
||||
WATER_HEATER_MODE_ECO = 1,
|
||||
WATER_HEATER_MODE_ELECTRIC = 2,
|
||||
WATER_HEATER_MODE_PERFORMANCE = 3,
|
||||
WATER_HEATER_MODE_HIGH_DEMAND = 4,
|
||||
WATER_HEATER_MODE_HEAT_PUMP = 5,
|
||||
WATER_HEATER_MODE_GAS = 6,
|
||||
};
|
||||
#endif
|
||||
enum WaterHeaterCommandHasField : uint32_t {
|
||||
WATER_HEATER_COMMAND_HAS_NONE = 0,
|
||||
WATER_HEATER_COMMAND_HAS_MODE = 1,
|
||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE = 2,
|
||||
WATER_HEATER_COMMAND_HAS_STATE = 4,
|
||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8,
|
||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16,
|
||||
};
|
||||
#ifdef USE_NUMBER
|
||||
enum NumberMode : uint32_t {
|
||||
NUMBER_MODE_AUTO = 0,
|
||||
@@ -765,7 +784,7 @@ class FanStateResponse final : public StateResponseProtoMessage {
|
||||
class FanCommandRequest final : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 31;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 38;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 48;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "fan_command_request"; }
|
||||
#endif
|
||||
@@ -778,7 +797,8 @@ class FanCommandRequest final : public CommandProtoMessage {
|
||||
bool has_speed_level{false};
|
||||
int32_t speed_level{0};
|
||||
bool has_preset_mode{false};
|
||||
std::string preset_mode{};
|
||||
const uint8_t *preset_mode{nullptr};
|
||||
uint16_t preset_mode_len{0};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -1053,11 +1073,12 @@ class SubscribeLogsResponse final : public ProtoMessage {
|
||||
class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 124;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 9;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 19;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "noise_encryption_set_key_request"; }
|
||||
#endif
|
||||
std::string key{};
|
||||
const uint8_t *key{nullptr};
|
||||
uint16_t key_len{0};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -1201,13 +1222,16 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage {
|
||||
class HomeAssistantStateResponse final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 40;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 27;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 57;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "home_assistant_state_response"; }
|
||||
#endif
|
||||
std::string entity_id{};
|
||||
std::string state{};
|
||||
std::string attribute{};
|
||||
const uint8_t *entity_id{nullptr};
|
||||
uint16_t entity_id_len{0};
|
||||
const uint8_t *state{nullptr};
|
||||
uint16_t state_len{0};
|
||||
const uint8_t *attribute{nullptr};
|
||||
uint16_t attribute_len{0};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -1475,7 +1499,7 @@ class ClimateStateResponse final : public StateResponseProtoMessage {
|
||||
class ClimateCommandRequest final : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 48;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 84;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 104;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "climate_command_request"; }
|
||||
#endif
|
||||
@@ -1492,11 +1516,13 @@ class ClimateCommandRequest final : public CommandProtoMessage {
|
||||
bool has_swing_mode{false};
|
||||
enums::ClimateSwingMode swing_mode{};
|
||||
bool has_custom_fan_mode{false};
|
||||
std::string custom_fan_mode{};
|
||||
const uint8_t *custom_fan_mode{nullptr};
|
||||
uint16_t custom_fan_mode_len{0};
|
||||
bool has_preset{false};
|
||||
enums::ClimatePreset preset{};
|
||||
bool has_custom_preset{false};
|
||||
std::string custom_preset{};
|
||||
const uint8_t *custom_preset{nullptr};
|
||||
uint16_t custom_preset_len{0};
|
||||
bool has_target_humidity{false};
|
||||
float target_humidity{0.0f};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1509,6 +1535,70 @@ class ClimateCommandRequest final : public CommandProtoMessage {
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
class ListEntitiesWaterHeaterResponse final : public InfoResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 132;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 63;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_water_heater_response"; }
|
||||
#endif
|
||||
float min_temperature{0.0f};
|
||||
float max_temperature{0.0f};
|
||||
float target_temperature_step{0.0f};
|
||||
const water_heater::WaterHeaterModeMask *supported_modes{};
|
||||
uint32_t supported_features{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
class WaterHeaterStateResponse final : public StateResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 133;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 35;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "water_heater_state_response"; }
|
||||
#endif
|
||||
float current_temperature{0.0f};
|
||||
float target_temperature{0.0f};
|
||||
enums::WaterHeaterMode mode{};
|
||||
uint32_t state{0};
|
||||
float target_temperature_low{0.0f};
|
||||
float target_temperature_high{0.0f};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
class WaterHeaterCommandRequest final : public CommandProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 134;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 34;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "water_heater_command_request"; }
|
||||
#endif
|
||||
uint32_t has_fields{0};
|
||||
enums::WaterHeaterMode mode{};
|
||||
float target_temperature{0.0f};
|
||||
uint32_t state{0};
|
||||
float target_temperature_low{0.0f};
|
||||
float target_temperature_high{0.0f};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
class ListEntitiesNumberResponse final : public InfoResponseProtoMessage {
|
||||
public:
|
||||
|
||||
@@ -348,6 +348,47 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
template<> const char *proto_enum_to_string<enums::WaterHeaterMode>(enums::WaterHeaterMode value) {
|
||||
switch (value) {
|
||||
case enums::WATER_HEATER_MODE_OFF:
|
||||
return "WATER_HEATER_MODE_OFF";
|
||||
case enums::WATER_HEATER_MODE_ECO:
|
||||
return "WATER_HEATER_MODE_ECO";
|
||||
case enums::WATER_HEATER_MODE_ELECTRIC:
|
||||
return "WATER_HEATER_MODE_ELECTRIC";
|
||||
case enums::WATER_HEATER_MODE_PERFORMANCE:
|
||||
return "WATER_HEATER_MODE_PERFORMANCE";
|
||||
case enums::WATER_HEATER_MODE_HIGH_DEMAND:
|
||||
return "WATER_HEATER_MODE_HIGH_DEMAND";
|
||||
case enums::WATER_HEATER_MODE_HEAT_PUMP:
|
||||
return "WATER_HEATER_MODE_HEAT_PUMP";
|
||||
case enums::WATER_HEATER_MODE_GAS:
|
||||
return "WATER_HEATER_MODE_GAS";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
template<>
|
||||
const char *proto_enum_to_string<enums::WaterHeaterCommandHasField>(enums::WaterHeaterCommandHasField value) {
|
||||
switch (value) {
|
||||
case enums::WATER_HEATER_COMMAND_HAS_NONE:
|
||||
return "WATER_HEATER_COMMAND_HAS_NONE";
|
||||
case enums::WATER_HEATER_COMMAND_HAS_MODE:
|
||||
return "WATER_HEATER_COMMAND_HAS_MODE";
|
||||
case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE:
|
||||
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE";
|
||||
case enums::WATER_HEATER_COMMAND_HAS_STATE:
|
||||
return "WATER_HEATER_COMMAND_HAS_STATE";
|
||||
case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW:
|
||||
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW";
|
||||
case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH:
|
||||
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#ifdef USE_NUMBER
|
||||
template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) {
|
||||
switch (value) {
|
||||
@@ -923,7 +964,9 @@ void FanCommandRequest::dump_to(std::string &out) const {
|
||||
dump_field(out, "has_speed_level", this->has_speed_level);
|
||||
dump_field(out, "speed_level", this->speed_level);
|
||||
dump_field(out, "has_preset_mode", this->has_preset_mode);
|
||||
dump_field(out, "preset_mode", this->preset_mode);
|
||||
out.append(" preset_mode: ");
|
||||
out.append(format_hex_pretty(this->preset_mode, this->preset_mode_len));
|
||||
out.append("\n");
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
@@ -1113,7 +1156,7 @@ void SubscribeLogsResponse::dump_to(std::string &out) const {
|
||||
void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "NoiseEncryptionSetKeyRequest");
|
||||
out.append(" key: ");
|
||||
out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->key.data()), this->key.size()));
|
||||
out.append(format_hex_pretty(this->key, this->key_len));
|
||||
out.append("\n");
|
||||
}
|
||||
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
|
||||
@@ -1182,9 +1225,15 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
|
||||
}
|
||||
void HomeAssistantStateResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "HomeAssistantStateResponse");
|
||||
dump_field(out, "entity_id", this->entity_id);
|
||||
dump_field(out, "state", this->state);
|
||||
dump_field(out, "attribute", this->attribute);
|
||||
out.append(" entity_id: ");
|
||||
out.append(format_hex_pretty(this->entity_id, this->entity_id_len));
|
||||
out.append("\n");
|
||||
out.append(" state: ");
|
||||
out.append(format_hex_pretty(this->state, this->state_len));
|
||||
out.append("\n");
|
||||
out.append(" attribute: ");
|
||||
out.append(format_hex_pretty(this->attribute, this->attribute_len));
|
||||
out.append("\n");
|
||||
}
|
||||
#endif
|
||||
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
|
||||
@@ -1374,11 +1423,15 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||
dump_field(out, "has_swing_mode", this->has_swing_mode);
|
||||
dump_field(out, "swing_mode", static_cast<enums::ClimateSwingMode>(this->swing_mode));
|
||||
dump_field(out, "has_custom_fan_mode", this->has_custom_fan_mode);
|
||||
dump_field(out, "custom_fan_mode", this->custom_fan_mode);
|
||||
out.append(" custom_fan_mode: ");
|
||||
out.append(format_hex_pretty(this->custom_fan_mode, this->custom_fan_mode_len));
|
||||
out.append("\n");
|
||||
dump_field(out, "has_preset", this->has_preset);
|
||||
dump_field(out, "preset", static_cast<enums::ClimatePreset>(this->preset));
|
||||
dump_field(out, "has_custom_preset", this->has_custom_preset);
|
||||
dump_field(out, "custom_preset", this->custom_preset);
|
||||
out.append(" custom_preset: ");
|
||||
out.append(format_hex_pretty(this->custom_preset, this->custom_preset_len));
|
||||
out.append("\n");
|
||||
dump_field(out, "has_target_humidity", this->has_target_humidity);
|
||||
dump_field(out, "target_humidity", this->target_humidity);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1386,6 +1439,55 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
void ListEntitiesWaterHeaterResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesWaterHeaterResponse");
|
||||
dump_field(out, "object_id", this->object_id_ref_);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name_ref_);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon_ref_);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
dump_field(out, "min_temperature", this->min_temperature);
|
||||
dump_field(out, "max_temperature", this->max_temperature);
|
||||
dump_field(out, "target_temperature_step", this->target_temperature_step);
|
||||
for (const auto &it : *this->supported_modes) {
|
||||
dump_field(out, "supported_modes", static_cast<enums::WaterHeaterMode>(it), 4);
|
||||
}
|
||||
dump_field(out, "supported_features", this->supported_features);
|
||||
}
|
||||
void WaterHeaterStateResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "WaterHeaterStateResponse");
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "current_temperature", this->current_temperature);
|
||||
dump_field(out, "target_temperature", this->target_temperature);
|
||||
dump_field(out, "mode", static_cast<enums::WaterHeaterMode>(this->mode));
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
dump_field(out, "state", this->state);
|
||||
dump_field(out, "target_temperature_low", this->target_temperature_low);
|
||||
dump_field(out, "target_temperature_high", this->target_temperature_high);
|
||||
}
|
||||
void WaterHeaterCommandRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "WaterHeaterCommandRequest");
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "has_fields", this->has_fields);
|
||||
dump_field(out, "mode", static_cast<enums::WaterHeaterMode>(this->mode));
|
||||
dump_field(out, "target_temperature", this->target_temperature);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
dump_field(out, "state", this->state);
|
||||
dump_field(out, "target_temperature_low", this->target_temperature_low);
|
||||
dump_field(out, "target_temperature_high", this->target_temperature_high);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesNumberResponse");
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#include "esphome/components/climate/climate_traits.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
#include "esphome/components/water_heater/water_heater.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
#include "esphome/components/light/light_traits.h"
|
||||
#endif
|
||||
|
||||
@@ -621,6 +621,17 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
this->on_homeassistant_action_response(msg);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
case WaterHeaterCommandRequest::MESSAGE_TYPE: {
|
||||
WaterHeaterCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_water_heater_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_water_heater_command_request(msg);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -91,6 +91,10 @@ class APIServerConnectionBase : public ProtoService {
|
||||
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
virtual void on_water_heater_command_request(const WaterHeaterCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
virtual void on_number_command_request(const NumberCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
@@ -338,6 +338,10 @@ API_DISPATCH_UPDATE(valve::Valve, valve)
|
||||
API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player)
|
||||
#endif
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
API_DISPATCH_UPDATE(water_heater::WaterHeater, water_heater)
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
// Event is a special case - unlike other entities with simple state fields,
|
||||
// events store their state in a member accessed via obj->get_last_event_type()
|
||||
|
||||
@@ -133,6 +133,9 @@ class APIServer : public Component,
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
void on_media_player_update(media_player::MediaPlayer *obj) override;
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
void on_water_heater_update(water_heater::WaterHeater *obj) override;
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
void send_homeassistant_action(const HomeassistantActionRequest &call);
|
||||
|
||||
|
||||
@@ -73,6 +73,9 @@ LIST_ENTITIES_HANDLER(media_player, media_player::MediaPlayer, ListEntitiesMedia
|
||||
LIST_ENTITIES_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel,
|
||||
ListEntitiesAlarmControlPanelResponse)
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
LIST_ENTITIES_HANDLER(water_heater, water_heater::WaterHeater, ListEntitiesWaterHeaterResponse)
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
LIST_ENTITIES_HANDLER(event, event::Event, ListEntitiesEventResponse)
|
||||
#endif
|
||||
|
||||
@@ -82,6 +82,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override;
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool on_water_heater(water_heater::WaterHeater *entity) override;
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool on_event(event::Event *entity) override;
|
||||
#endif
|
||||
|
||||
@@ -60,6 +60,9 @@ INITIAL_STATE_HANDLER(media_player, media_player::MediaPlayer)
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
INITIAL_STATE_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel)
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
INITIAL_STATE_HANDLER(water_heater, water_heater::WaterHeater)
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
INITIAL_STATE_HANDLER(update, update::UpdateEntity)
|
||||
#endif
|
||||
|
||||
@@ -76,6 +76,9 @@ class InitialStateIterator : public ComponentIterator {
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override;
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool on_water_heater(water_heater::WaterHeater *entity) override;
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool on_event(event::Event *event) override { return true; };
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user