mirror of
https://github.com/esphome/esphome.git
synced 2026-05-31 17:06:40 +08:00
[climate] Add zero-copy support for API custom fan mode and preset commands (#12402)
This commit is contained in:
@@ -1091,11 +1091,11 @@ message ClimateCommandRequest {
|
|||||||
bool has_swing_mode = 14;
|
bool has_swing_mode = 14;
|
||||||
ClimateSwingMode swing_mode = 15;
|
ClimateSwingMode swing_mode = 15;
|
||||||
bool has_custom_fan_mode = 16;
|
bool has_custom_fan_mode = 16;
|
||||||
string custom_fan_mode = 17;
|
string custom_fan_mode = 17 [(pointer_to_buffer) = true];
|
||||||
bool has_preset = 18;
|
bool has_preset = 18;
|
||||||
ClimatePreset preset = 19;
|
ClimatePreset preset = 19;
|
||||||
bool has_custom_preset = 20;
|
bool has_custom_preset = 20;
|
||||||
string custom_preset = 21;
|
string custom_preset = 21 [(pointer_to_buffer) = true];
|
||||||
bool has_target_humidity = 22;
|
bool has_target_humidity = 22;
|
||||||
float target_humidity = 23;
|
float target_humidity = 23;
|
||||||
uint32 device_id = 24 [(field_ifdef) = "USE_DEVICES"];
|
uint32 device_id = 24 [(field_ifdef) = "USE_DEVICES"];
|
||||||
|
|||||||
@@ -712,11 +712,11 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
|||||||
if (msg.has_fan_mode)
|
if (msg.has_fan_mode)
|
||||||
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
|
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
|
||||||
if (msg.has_custom_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)
|
if (msg.has_preset)
|
||||||
call.set_preset(static_cast<climate::ClimatePreset>(msg.preset));
|
call.set_preset(static_cast<climate::ClimatePreset>(msg.preset));
|
||||||
if (msg.has_custom_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)
|
if (msg.has_swing_mode)
|
||||||
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
|
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
|
||||||
call.perform();
|
call.perform();
|
||||||
|
|||||||
@@ -1392,12 +1392,18 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
|
|||||||
}
|
}
|
||||||
bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
bool ClimateCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
case 17:
|
case 17: {
|
||||||
this->custom_fan_mode = value.as_string();
|
// Use raw data directly to avoid allocation
|
||||||
|
this->custom_fan_mode = value.data();
|
||||||
|
this->custom_fan_mode_len = value.size();
|
||||||
break;
|
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;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1475,7 +1475,7 @@ class ClimateStateResponse final : public StateResponseProtoMessage {
|
|||||||
class ClimateCommandRequest final : public CommandProtoMessage {
|
class ClimateCommandRequest final : public CommandProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 48;
|
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
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "climate_command_request"; }
|
const char *message_name() const override { return "climate_command_request"; }
|
||||||
#endif
|
#endif
|
||||||
@@ -1492,11 +1492,13 @@ class ClimateCommandRequest final : public CommandProtoMessage {
|
|||||||
bool has_swing_mode{false};
|
bool has_swing_mode{false};
|
||||||
enums::ClimateSwingMode swing_mode{};
|
enums::ClimateSwingMode swing_mode{};
|
||||||
bool has_custom_fan_mode{false};
|
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};
|
bool has_preset{false};
|
||||||
enums::ClimatePreset preset{};
|
enums::ClimatePreset preset{};
|
||||||
bool has_custom_preset{false};
|
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};
|
bool has_target_humidity{false};
|
||||||
float target_humidity{0.0f};
|
float target_humidity{0.0f};
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
|
|||||||
@@ -1374,11 +1374,15 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
|||||||
dump_field(out, "has_swing_mode", this->has_swing_mode);
|
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, "swing_mode", static_cast<enums::ClimateSwingMode>(this->swing_mode));
|
||||||
dump_field(out, "has_custom_fan_mode", this->has_custom_fan_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, "has_preset", this->has_preset);
|
||||||
dump_field(out, "preset", static_cast<enums::ClimatePreset>(this->preset));
|
dump_field(out, "preset", static_cast<enums::ClimatePreset>(this->preset));
|
||||||
dump_field(out, "has_custom_preset", this->has_custom_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, "has_target_humidity", this->has_target_humidity);
|
||||||
dump_field(out, "target_humidity", this->target_humidity);
|
dump_field(out, "target_humidity", this->target_humidity);
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/controller_registry.h"
|
#include "esphome/core/controller_registry.h"
|
||||||
#include "esphome/core/macros.h"
|
#include "esphome/core/macros.h"
|
||||||
|
#include <strings.h>
|
||||||
|
|
||||||
namespace esphome::climate {
|
namespace esphome::climate {
|
||||||
|
|
||||||
@@ -190,24 +191,30 @@ ClimateCall &ClimateCall::set_fan_mode(ClimateFanMode fan_mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode) {
|
ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode) {
|
||||||
|
return this->set_fan_mode(custom_fan_mode, strlen(custom_fan_mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
|
||||||
|
return this->set_fan_mode(fan_mode.data(), fan_mode.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
ClimateCall &ClimateCall::set_fan_mode(const char *custom_fan_mode, size_t len) {
|
||||||
// Check if it's a standard enum mode first
|
// Check if it's a standard enum mode first
|
||||||
for (const auto &mode_entry : CLIMATE_FAN_MODES_BY_STR) {
|
for (const auto &mode_entry : CLIMATE_FAN_MODES_BY_STR) {
|
||||||
if (str_equals_case_insensitive(custom_fan_mode, mode_entry.str)) {
|
if (strncasecmp(custom_fan_mode, mode_entry.str, len) == 0 && mode_entry.str[len] == '\0') {
|
||||||
return this->set_fan_mode(static_cast<ClimateFanMode>(mode_entry.value));
|
return this->set_fan_mode(static_cast<ClimateFanMode>(mode_entry.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Find the matching pointer from parent climate device
|
// Find the matching pointer from parent climate device
|
||||||
if (const char *mode_ptr = this->parent_->find_custom_fan_mode_(custom_fan_mode)) {
|
if (const char *mode_ptr = this->parent_->find_custom_fan_mode_(custom_fan_mode, len)) {
|
||||||
this->custom_fan_mode_ = mode_ptr;
|
this->custom_fan_mode_ = mode_ptr;
|
||||||
this->fan_mode_.reset();
|
this->fan_mode_.reset();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), custom_fan_mode);
|
ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %.*s", this->parent_->get_name().c_str(), (int) len, custom_fan_mode);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { return this->set_fan_mode(fan_mode.c_str()); }
|
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_fan_mode(optional<std::string> fan_mode) {
|
ClimateCall &ClimateCall::set_fan_mode(optional<std::string> fan_mode) {
|
||||||
if (fan_mode.has_value()) {
|
if (fan_mode.has_value()) {
|
||||||
this->set_fan_mode(fan_mode.value());
|
this->set_fan_mode(fan_mode.value());
|
||||||
@@ -222,24 +229,30 @@ ClimateCall &ClimateCall::set_preset(ClimatePreset preset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_preset(const char *custom_preset) {
|
ClimateCall &ClimateCall::set_preset(const char *custom_preset) {
|
||||||
|
return this->set_preset(custom_preset, strlen(custom_preset));
|
||||||
|
}
|
||||||
|
|
||||||
|
ClimateCall &ClimateCall::set_preset(const std::string &preset) {
|
||||||
|
return this->set_preset(preset.data(), preset.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
ClimateCall &ClimateCall::set_preset(const char *custom_preset, size_t len) {
|
||||||
// Check if it's a standard enum preset first
|
// Check if it's a standard enum preset first
|
||||||
for (const auto &preset_entry : CLIMATE_PRESETS_BY_STR) {
|
for (const auto &preset_entry : CLIMATE_PRESETS_BY_STR) {
|
||||||
if (str_equals_case_insensitive(custom_preset, preset_entry.str)) {
|
if (strncasecmp(custom_preset, preset_entry.str, len) == 0 && preset_entry.str[len] == '\0') {
|
||||||
return this->set_preset(static_cast<ClimatePreset>(preset_entry.value));
|
return this->set_preset(static_cast<ClimatePreset>(preset_entry.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Find the matching pointer from parent climate device
|
// Find the matching pointer from parent climate device
|
||||||
if (const char *preset_ptr = this->parent_->find_custom_preset_(custom_preset)) {
|
if (const char *preset_ptr = this->parent_->find_custom_preset_(custom_preset, len)) {
|
||||||
this->custom_preset_ = preset_ptr;
|
this->custom_preset_ = preset_ptr;
|
||||||
this->preset_.reset();
|
this->preset_.reset();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
ESP_LOGW(TAG, "'%s' - Unrecognized preset %s", this->parent_->get_name().c_str(), custom_preset);
|
ESP_LOGW(TAG, "'%s' - Unrecognized preset %.*s", this->parent_->get_name().c_str(), (int) len, custom_preset);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_preset(const std::string &preset) { return this->set_preset(preset.c_str()); }
|
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_preset(optional<std::string> preset) {
|
ClimateCall &ClimateCall::set_preset(optional<std::string> preset) {
|
||||||
if (preset.has_value()) {
|
if (preset.has_value()) {
|
||||||
this->set_preset(preset.value());
|
this->set_preset(preset.value());
|
||||||
@@ -688,11 +701,19 @@ bool Climate::set_custom_preset_(const char *preset) {
|
|||||||
void Climate::clear_custom_preset_() { this->custom_preset_ = nullptr; }
|
void Climate::clear_custom_preset_() { this->custom_preset_ = nullptr; }
|
||||||
|
|
||||||
const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode) {
|
const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode) {
|
||||||
return this->get_traits().find_custom_fan_mode_(custom_fan_mode);
|
return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode, size_t len) {
|
||||||
|
return this->get_traits().find_custom_fan_mode_(custom_fan_mode, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *Climate::find_custom_preset_(const char *custom_preset) {
|
const char *Climate::find_custom_preset_(const char *custom_preset) {
|
||||||
return this->get_traits().find_custom_preset_(custom_preset);
|
return this->find_custom_preset_(custom_preset, strlen(custom_preset));
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *Climate::find_custom_preset_(const char *custom_preset, size_t len) {
|
||||||
|
return this->get_traits().find_custom_preset_(custom_preset, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Climate::dump_traits_(const char *tag) {
|
void Climate::dump_traits_(const char *tag) {
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ class ClimateCall {
|
|||||||
ClimateCall &set_fan_mode(optional<std::string> fan_mode);
|
ClimateCall &set_fan_mode(optional<std::string> fan_mode);
|
||||||
/// Set the custom fan mode of the climate device.
|
/// Set the custom fan mode of the climate device.
|
||||||
ClimateCall &set_fan_mode(const char *custom_fan_mode);
|
ClimateCall &set_fan_mode(const char *custom_fan_mode);
|
||||||
|
/// Set the custom fan mode of the climate device (zero-copy API path).
|
||||||
|
ClimateCall &set_fan_mode(const char *custom_fan_mode, size_t len);
|
||||||
/// Set the swing mode of the climate device.
|
/// Set the swing mode of the climate device.
|
||||||
ClimateCall &set_swing_mode(ClimateSwingMode swing_mode);
|
ClimateCall &set_swing_mode(ClimateSwingMode swing_mode);
|
||||||
/// Set the swing mode of the climate device.
|
/// Set the swing mode of the climate device.
|
||||||
@@ -94,6 +96,8 @@ class ClimateCall {
|
|||||||
ClimateCall &set_preset(optional<std::string> preset);
|
ClimateCall &set_preset(optional<std::string> preset);
|
||||||
/// Set the custom preset of the climate device.
|
/// Set the custom preset of the climate device.
|
||||||
ClimateCall &set_preset(const char *custom_preset);
|
ClimateCall &set_preset(const char *custom_preset);
|
||||||
|
/// Set the custom preset of the climate device (zero-copy API path).
|
||||||
|
ClimateCall &set_preset(const char *custom_preset, size_t len);
|
||||||
|
|
||||||
void perform();
|
void perform();
|
||||||
|
|
||||||
@@ -290,9 +294,11 @@ class Climate : public EntityBase {
|
|||||||
|
|
||||||
/// Find and return the matching custom fan mode pointer from traits, or nullptr if not found.
|
/// Find and return the matching custom fan mode pointer from traits, or nullptr if not found.
|
||||||
const char *find_custom_fan_mode_(const char *custom_fan_mode);
|
const char *find_custom_fan_mode_(const char *custom_fan_mode);
|
||||||
|
const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len);
|
||||||
|
|
||||||
/// Find and return the matching custom preset pointer from traits, or nullptr if not found.
|
/// Find and return the matching custom preset pointer from traits, or nullptr if not found.
|
||||||
const char *find_custom_preset_(const char *custom_preset);
|
const char *find_custom_preset_(const char *custom_preset);
|
||||||
|
const char *find_custom_preset_(const char *custom_preset, size_t len);
|
||||||
|
|
||||||
/** Get the default traits of this climate device.
|
/** Get the default traits of this climate device.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -20,18 +20,22 @@ using ClimatePresetMask = FiniteSetMask<ClimatePreset, DefaultBitPolicy<ClimateP
|
|||||||
|
|
||||||
// Lightweight linear search for small vectors (1-20 items) of const char* pointers
|
// Lightweight linear search for small vectors (1-20 items) of const char* pointers
|
||||||
// Avoids std::find template overhead
|
// Avoids std::find template overhead
|
||||||
inline bool vector_contains(const std::vector<const char *> &vec, const char *value) {
|
inline bool vector_contains(const std::vector<const char *> &vec, const char *value, size_t len) {
|
||||||
for (const char *item : vec) {
|
for (const char *item : vec) {
|
||||||
if (strcmp(item, value) == 0)
|
if (strncmp(item, value, len) == 0 && item[len] == '\0')
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool vector_contains(const std::vector<const char *> &vec, const char *value) {
|
||||||
|
return vector_contains(vec, value, strlen(value));
|
||||||
|
}
|
||||||
|
|
||||||
// Find and return matching pointer from vector, or nullptr if not found
|
// Find and return matching pointer from vector, or nullptr if not found
|
||||||
inline const char *vector_find(const std::vector<const char *> &vec, const char *value) {
|
inline const char *vector_find(const std::vector<const char *> &vec, const char *value, size_t len) {
|
||||||
for (const char *item : vec) {
|
for (const char *item : vec) {
|
||||||
if (strcmp(item, value) == 0)
|
if (strncmp(item, value, len) == 0 && item[len] == '\0')
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -257,13 +261,19 @@ class ClimateTraits {
|
|||||||
/// Find and return the matching custom fan mode pointer from supported modes, or nullptr if not found
|
/// Find and return the matching custom fan mode pointer from supported modes, or nullptr if not found
|
||||||
/// This is protected as it's an implementation detail - use Climate::find_custom_fan_mode_() instead
|
/// This is protected as it's an implementation detail - use Climate::find_custom_fan_mode_() instead
|
||||||
const char *find_custom_fan_mode_(const char *custom_fan_mode) const {
|
const char *find_custom_fan_mode_(const char *custom_fan_mode) const {
|
||||||
return vector_find(this->supported_custom_fan_modes_, custom_fan_mode);
|
return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode));
|
||||||
|
}
|
||||||
|
const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len) const {
|
||||||
|
return vector_find(this->supported_custom_fan_modes_, custom_fan_mode, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find and return the matching custom preset pointer from supported presets, or nullptr if not found
|
/// Find and return the matching custom preset pointer from supported presets, or nullptr if not found
|
||||||
/// This is protected as it's an implementation detail - use Climate::find_custom_preset_() instead
|
/// This is protected as it's an implementation detail - use Climate::find_custom_preset_() instead
|
||||||
const char *find_custom_preset_(const char *custom_preset) const {
|
const char *find_custom_preset_(const char *custom_preset) const {
|
||||||
return vector_find(this->supported_custom_presets_, custom_preset);
|
return this->find_custom_preset_(custom_preset, strlen(custom_preset));
|
||||||
|
}
|
||||||
|
const char *find_custom_preset_(const char *custom_preset, size_t len) const {
|
||||||
|
return vector_find(this->supported_custom_presets_, custom_preset, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t feature_flags_{0};
|
uint32_t feature_flags_{0};
|
||||||
|
|||||||
Reference in New Issue
Block a user