Merge branch 'integration' into memory_api

This commit is contained in:
J. Nick Koston 2025-07-27 18:50:05 -10:00
commit 226d465f6a
No known key found for this signature in database
14 changed files with 1445 additions and 874 deletions

View File

@ -2,6 +2,7 @@
import argparse
from datetime import datetime
import functools
import getpass
import importlib
import logging
import os
@ -335,7 +336,7 @@ def check_permissions(port):
raise EsphomeError(
"You do not have read or write permission on the selected serial port. "
"To resolve this issue, you can add your user to the dialout group "
f"by running the following command: sudo usermod -a -G dialout {os.getlogin()}. "
f"by running the following command: sudo usermod -a -G dialout {getpass.getuser()}. "
"You will need to log out & back in or reboot to activate the new group access."
)

View File

@ -53,7 +53,6 @@ SERVICE_ARG_NATIVE_TYPES = {
CONF_ENCRYPTION = "encryption"
CONF_BATCH_DELAY = "batch_delay"
CONF_CUSTOM_SERVICES = "custom_services"
CONF_HOMEASSISTANT_STATES = "homeassistant_states"
CONF_HOMEASSISTANT_SERVICES = "homeassistant_services"
CONF_HOMEASSISTANT_STATES = "homeassistant_states"
@ -121,6 +120,7 @@ CONFIG_SCHEMA = cv.All(
cv.Range(max=cv.TimePeriod(milliseconds=65535)),
),
cv.Optional(CONF_CUSTOM_SERVICES, default=False): cv.boolean,
cv.Optional(CONF_HOMEASSISTANT_SERVICES, default=False): cv.boolean,
cv.Optional(CONF_HOMEASSISTANT_STATES, default=False): cv.boolean,
cv.Optional(CONF_HOMEASSISTANT_SERVICES, default=False): cv.boolean,
cv.Optional(CONF_HOMEASSISTANT_STATES, default=False): cv.boolean,
@ -152,10 +152,6 @@ async def to_code(config):
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
cg.add_define("USE_API_SERVICES")
# Set USE_API_HOMEASSISTANT_STATES if enabled
if config[CONF_HOMEASSISTANT_STATES]:
cg.add_define("USE_API_HOMEASSISTANT_STATES")
if config[CONF_HOMEASSISTANT_SERVICES]:
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")

View File

@ -276,8 +276,9 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
#endif
// Calculate size
uint32_t calculated_size = 0;
msg.calculate_size(calculated_size);
ProtoSize size_calc;
msg.calculate_size(size_calc);
uint32_t calculated_size = size_calc.get_size();
// Cache frame sizes to avoid repeated virtual calls
const uint8_t header_padding = conn->helper_->frame_header_padding();

File diff suppressed because it is too large Load Diff

View File

@ -340,7 +340,7 @@ class HelloResponse : public ProtoMessage {
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -371,7 +371,7 @@ class ConnectResponse : public ProtoMessage {
#endif
bool invalid_password{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -450,7 +450,7 @@ class AreaInfo : public ProtoMessage {
StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -466,7 +466,7 @@ class DeviceInfo : public ProtoMessage {
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
uint32_t area_id{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -539,7 +539,7 @@ class DeviceInfoResponse : public ProtoMessage {
AreaInfo area{};
#endif
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -597,7 +597,7 @@ class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage {
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
bool is_status_binary_sensor{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -614,7 +614,7 @@ class BinarySensorStateResponse : public StateResponseProtoMessage {
bool state{false};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -637,7 +637,7 @@ class ListEntitiesCoverResponse : public InfoResponseProtoMessage {
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
bool supports_stop{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -655,7 +655,7 @@ class CoverStateResponse : public StateResponseProtoMessage {
float tilt{0.0f};
enums::CoverOperation current_operation{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -697,7 +697,7 @@ class ListEntitiesFanResponse : public InfoResponseProtoMessage {
int32_t supported_speed_count{0};
std::vector<std::string> supported_preset_modes{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -718,7 +718,7 @@ class FanStateResponse : public StateResponseProtoMessage {
StringRef preset_mode_ref_{};
void set_preset_mode(const StringRef &ref) { this->preset_mode_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -765,7 +765,7 @@ class ListEntitiesLightResponse : public InfoResponseProtoMessage {
float max_mireds{0.0f};
std::vector<std::string> effects{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -793,7 +793,7 @@ class LightStateResponse : public StateResponseProtoMessage {
StringRef effect_ref_{};
void set_effect(const StringRef &ref) { this->effect_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -859,7 +859,7 @@ class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
enums::SensorStateClass state_class{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -876,7 +876,7 @@ class SensorStateResponse : public StateResponseProtoMessage {
float state{0.0f};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -896,7 +896,7 @@ class ListEntitiesSwitchResponse : public InfoResponseProtoMessage {
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -912,7 +912,7 @@ class SwitchStateResponse : public StateResponseProtoMessage {
#endif
bool state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -947,7 +947,7 @@ class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage {
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -965,7 +965,7 @@ class TextSensorStateResponse : public StateResponseProtoMessage {
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1004,7 +1004,7 @@ class SubscribeLogsResponse : public ProtoMessage {
this->message_len_ = len;
}
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1036,7 +1036,7 @@ class NoiseEncryptionSetKeyResponse : public ProtoMessage {
#endif
bool success{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1064,7 +1064,7 @@ class HomeassistantServiceMap : public ProtoMessage {
void set_key(const StringRef &ref) { this->key_ref_ = ref; }
std::string value{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1085,7 +1085,7 @@ class HomeassistantServiceResponse : public ProtoMessage {
std::vector<HomeassistantServiceMap> variables{};
bool is_event{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1120,7 +1120,7 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
void set_attribute(const StringRef &ref) { this->attribute_ref_ = ref; }
bool once{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1167,7 +1167,7 @@ class GetTimeResponse : public ProtoDecodableMessage {
#endif
uint32_t epoch_seconds{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1182,7 +1182,7 @@ class ListEntitiesServicesArgument : public ProtoMessage {
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
enums::ServiceArgType type{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1201,7 +1201,7 @@ class ListEntitiesServicesResponse : public ProtoMessage {
uint32_t key{0};
std::vector<ListEntitiesServicesArgument> args{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1255,7 +1255,7 @@ class ListEntitiesCameraResponse : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_camera_response"; }
#endif
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1277,7 +1277,7 @@ class CameraImageResponse : public StateResponseProtoMessage {
}
bool done{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1327,7 +1327,7 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
float visual_min_humidity{0.0f};
float visual_max_humidity{0.0f};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1357,7 +1357,7 @@ class ClimateStateResponse : public StateResponseProtoMessage {
float current_humidity{0.0f};
float target_humidity{0.0f};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1418,7 +1418,7 @@ class ListEntitiesNumberResponse : public InfoResponseProtoMessage {
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1435,7 +1435,7 @@ class NumberStateResponse : public StateResponseProtoMessage {
float state{0.0f};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1469,7 +1469,7 @@ class ListEntitiesSelectResponse : public InfoResponseProtoMessage {
#endif
std::vector<std::string> options{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1487,7 +1487,7 @@ class SelectStateResponse : public StateResponseProtoMessage {
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1524,7 +1524,7 @@ class ListEntitiesSirenResponse : public InfoResponseProtoMessage {
bool supports_duration{false};
bool supports_volume{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1540,7 +1540,7 @@ class SirenStateResponse : public StateResponseProtoMessage {
#endif
bool state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1586,7 +1586,7 @@ class ListEntitiesLockResponse : public InfoResponseProtoMessage {
StringRef code_format_ref_{};
void set_code_format(const StringRef &ref) { this->code_format_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1602,7 +1602,7 @@ class LockStateResponse : public StateResponseProtoMessage {
#endif
enums::LockState state{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1640,7 +1640,7 @@ class ListEntitiesButtonResponse : public InfoResponseProtoMessage {
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1673,7 +1673,7 @@ class MediaPlayerSupportedFormat : public ProtoMessage {
enums::MediaPlayerFormatPurpose purpose{};
uint32_t sample_bytes{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1690,7 +1690,7 @@ class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage {
bool supports_pause{false};
std::vector<MediaPlayerSupportedFormat> supported_formats{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1708,7 +1708,7 @@ class MediaPlayerStateResponse : public StateResponseProtoMessage {
float volume{0.0f};
bool muted{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1764,7 +1764,7 @@ class BluetoothLERawAdvertisement : public ProtoMessage {
uint8_t data[62]{};
uint8_t data_len{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1780,7 +1780,7 @@ class BluetoothLERawAdvertisementsResponse : public ProtoMessage {
#endif
std::vector<BluetoothLERawAdvertisement> advertisements{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1817,7 +1817,7 @@ class BluetoothDeviceConnectionResponse : public ProtoMessage {
uint32_t mtu{0};
int32_t error{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1844,7 +1844,7 @@ class BluetoothGATTDescriptor : public ProtoMessage {
std::array<uint64_t, 2> uuid{};
uint32_t handle{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1858,7 +1858,7 @@ class BluetoothGATTCharacteristic : public ProtoMessage {
uint32_t properties{0};
std::vector<BluetoothGATTDescriptor> descriptors{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1871,7 +1871,7 @@ class BluetoothGATTService : public ProtoMessage {
uint32_t handle{0};
std::vector<BluetoothGATTCharacteristic> characteristics{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1888,7 +1888,7 @@ class BluetoothGATTGetServicesResponse : public ProtoMessage {
uint64_t address{0};
std::array<BluetoothGATTService, 1> services{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1904,7 +1904,7 @@ class BluetoothGATTGetServicesDoneResponse : public ProtoMessage {
#endif
uint64_t address{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -1943,7 +1943,7 @@ class BluetoothGATTReadResponse : public ProtoMessage {
this->data_len_ = len;
}
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2036,7 +2036,7 @@ class BluetoothGATTNotifyDataResponse : public ProtoMessage {
this->data_len_ = len;
}
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2067,7 +2067,7 @@ class BluetoothConnectionsFreeResponse : public ProtoMessage {
uint32_t limit{0};
std::vector<uint64_t> allocated{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2085,7 +2085,7 @@ class BluetoothGATTErrorResponse : public ProtoMessage {
uint32_t handle{0};
int32_t error{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2102,7 +2102,7 @@ class BluetoothGATTWriteResponse : public ProtoMessage {
uint64_t address{0};
uint32_t handle{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2119,7 +2119,7 @@ class BluetoothGATTNotifyResponse : public ProtoMessage {
uint64_t address{0};
uint32_t handle{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2137,7 +2137,7 @@ class BluetoothDevicePairingResponse : public ProtoMessage {
bool paired{false};
int32_t error{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2155,7 +2155,7 @@ class BluetoothDeviceUnpairingResponse : public ProtoMessage {
bool success{false};
int32_t error{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2186,7 +2186,7 @@ class BluetoothDeviceClearCacheResponse : public ProtoMessage {
bool success{false};
int32_t error{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2203,7 +2203,7 @@ class BluetoothScannerStateResponse : public ProtoMessage {
enums::BluetoothScannerState state{};
enums::BluetoothScannerMode mode{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2249,7 +2249,7 @@ class VoiceAssistantAudioSettings : public ProtoMessage {
uint32_t auto_gain{0};
float volume_multiplier{0.0f};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2271,7 +2271,7 @@ class VoiceAssistantRequest : public ProtoMessage {
StringRef wake_word_phrase_ref_{};
void set_wake_word_phrase(const StringRef &ref) { this->wake_word_phrase_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2338,7 +2338,7 @@ class VoiceAssistantAudio : public ProtoDecodableMessage {
}
bool end{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2396,7 +2396,7 @@ class VoiceAssistantAnnounceFinished : public ProtoMessage {
#endif
bool success{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2411,7 +2411,7 @@ class VoiceAssistantWakeWord : public ProtoMessage {
void set_wake_word(const StringRef &ref) { this->wake_word_ref_ = ref; }
std::vector<std::string> trained_languages{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2442,7 +2442,7 @@ class VoiceAssistantConfigurationResponse : public ProtoMessage {
std::vector<std::string> active_wake_words{};
uint32_t max_active_wake_words{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2477,7 +2477,7 @@ class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage {
bool requires_code{false};
bool requires_code_to_arm{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2493,7 +2493,7 @@ class AlarmControlPanelStateResponse : public StateResponseProtoMessage {
#endif
enums::AlarmControlPanelState state{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2533,7 +2533,7 @@ class ListEntitiesTextResponse : public InfoResponseProtoMessage {
void set_pattern(const StringRef &ref) { this->pattern_ref_ = ref; }
enums::TextMode mode{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2551,7 +2551,7 @@ class TextStateResponse : public StateResponseProtoMessage {
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2585,7 +2585,7 @@ class ListEntitiesDateResponse : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_date_response"; }
#endif
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2604,7 +2604,7 @@ class DateStateResponse : public StateResponseProtoMessage {
uint32_t month{0};
uint32_t day{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2639,7 +2639,7 @@ class ListEntitiesTimeResponse : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_time_response"; }
#endif
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2658,7 +2658,7 @@ class TimeStateResponse : public StateResponseProtoMessage {
uint32_t minute{0};
uint32_t second{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2696,7 +2696,7 @@ class ListEntitiesEventResponse : public InfoResponseProtoMessage {
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
std::vector<std::string> event_types{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2713,7 +2713,7 @@ class EventResponse : public StateResponseProtoMessage {
StringRef event_type_ref_{};
void set_event_type(const StringRef &ref) { this->event_type_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2735,7 +2735,7 @@ class ListEntitiesValveResponse : public InfoResponseProtoMessage {
bool supports_position{false};
bool supports_stop{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2752,7 +2752,7 @@ class ValveStateResponse : public StateResponseProtoMessage {
float position{0.0f};
enums::ValveOperation current_operation{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2787,7 +2787,7 @@ class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_date_time_response"; }
#endif
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2804,7 +2804,7 @@ class DateTimeStateResponse : public StateResponseProtoMessage {
bool missing_state{false};
uint32_t epoch_seconds{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2839,7 +2839,7 @@ class ListEntitiesUpdateResponse : public InfoResponseProtoMessage {
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
@ -2868,7 +2868,7 @@ class UpdateStateResponse : public StateResponseProtoMessage {
StringRef release_url_ref_{};
void set_release_url(const StringRef &ref) { this->release_url_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif

View File

@ -333,13 +333,16 @@ class ProtoWriteBuffer {
std::vector<uint8_t> *buffer_;
};
// Forward declaration
class ProtoSize;
class ProtoMessage {
public:
virtual ~ProtoMessage() = default;
// Default implementation for messages with no fields
virtual void encode(ProtoWriteBuffer buffer) const {}
// Default implementation for messages with no fields
virtual void calculate_size(uint32_t &total_size) const {}
virtual void calculate_size(ProtoSize &size) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
std::string dump() const;
virtual void dump_to(std::string &out) const = 0;
@ -360,24 +363,32 @@ class ProtoDecodableMessage : public ProtoMessage {
};
class ProtoSize {
private:
uint32_t total_size_ = 0;
public:
/**
* @brief ProtoSize class for Protocol Buffer serialization size calculation
*
* This class provides static methods to calculate the exact byte counts needed
* for encoding various Protocol Buffer field types. All methods are designed to be
* efficient for the common case where many fields have default values.
* This class provides methods to calculate the exact byte counts needed
* for encoding various Protocol Buffer field types. The class now uses an
* object-based approach to reduce parameter passing overhead while keeping
* varint calculation methods static for external use.
*
* Implements Protocol Buffer encoding size calculation according to:
* https://protobuf.dev/programming-guides/encoding/
*
* Key features:
* - Object-based approach reduces flash usage by eliminating parameter passing
* - Early-return optimization for zero/default values
* - Direct total_size updates to avoid unnecessary additions
* - Static varint methods for external callers
* - Specialized handling for different field types according to protobuf spec
* - Templated helpers for repeated fields and messages
*/
ProtoSize() = default;
uint32_t get_size() const { return total_size_; }
/**
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
*
@ -478,9 +489,7 @@ class ProtoSize {
* @brief Common parameters for all add_*_field methods
*
* All add_*_field methods follow these common patterns:
*
* @param total_size Reference to the total message size to update
* @param field_id_size Pre-calculated size of the field ID in bytes
* * @param field_id_size Pre-calculated size of the field ID in bytes
* @param value The value to calculate size for (type varies)
* @param force Whether to calculate size even if the value is default/zero/empty
*
@ -493,85 +502,63 @@ class ProtoSize {
/**
* @brief Calculates and adds the size of an int32 field to the total message size
*/
static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
if (value < 0) {
// Negative values are encoded as 10-byte varints in protobuf
total_size += field_id_size + 10;
} else {
// For non-negative values, use the standard varint size
total_size += field_id_size + varint(static_cast<uint32_t>(value));
inline void add_int32(uint32_t field_id_size, int32_t value) {
if (value != 0) {
add_int32_force(field_id_size, value);
}
}
/**
* @brief Calculates and adds the size of an int32 field to the total message size (repeated field version)
* @brief Calculates and adds the size of an int32 field to the total message size (force version)
*/
static inline void add_int32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Always calculate size for repeated fields
if (value < 0) {
// Negative values are encoded as 10-byte varints in protobuf
total_size += field_id_size + 10;
} else {
// For non-negative values, use the standard varint size
total_size += field_id_size + varint(static_cast<uint32_t>(value));
}
inline void add_int32_force(uint32_t field_id_size, int32_t value) {
// Always calculate size when forced
// Negative values are encoded as 10-byte varints in protobuf
total_size_ += field_id_size + (value < 0 ? 10 : varint(static_cast<uint32_t>(value)));
}
/**
* @brief Calculates and adds the size of a uint32 field to the total message size
*/
static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
inline void add_uint32(uint32_t field_id_size, uint32_t value) {
if (value != 0) {
add_uint32_force(field_id_size, value);
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint32 field to the total message size (repeated field version)
* @brief Calculates and adds the size of a uint32 field to the total message size (force version)
*/
static inline void add_uint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Always calculate size for repeated fields
total_size += field_id_size + varint(value);
inline void add_uint32_force(uint32_t field_id_size, uint32_t value) {
// Always calculate size when force is true
total_size_ += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a boolean field to the total message size
*/
static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value) {
// Skip calculation if value is false
if (!value) {
return; // No need to update total_size
inline void add_bool(uint32_t field_id_size, bool value) {
if (value) {
// Boolean fields always use 1 byte when true
total_size_ += field_id_size + 1;
}
// Boolean fields always use 1 byte when true
total_size += field_id_size + 1;
}
/**
* @brief Calculates and adds the size of a boolean field to the total message size (repeated field version)
* @brief Calculates and adds the size of a boolean field to the total message size (force version)
*/
static inline void add_bool_field_repeated(uint32_t &total_size, uint32_t field_id_size, bool value) {
// Always calculate size for repeated fields
inline void add_bool_force(uint32_t field_id_size, bool value) {
// Always calculate size when force is true
// Boolean fields always use 1 byte
total_size += field_id_size + 1;
total_size_ += field_id_size + 1;
}
/**
* @brief Calculates and adds the size of a float field to the total message size
*/
static inline void add_float_field(uint32_t &total_size, uint32_t field_id_size, float value) {
inline void add_float(uint32_t field_id_size, float value) {
if (value != 0.0f) {
total_size += field_id_size + 4;
total_size_ += field_id_size + 4;
}
}
@ -581,9 +568,9 @@ class ProtoSize {
/**
* @brief Calculates and adds the size of a fixed32 field to the total message size
*/
static inline void add_fixed32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
inline void add_fixed32(uint32_t field_id_size, uint32_t value) {
if (value != 0) {
total_size += field_id_size + 4;
total_size_ += field_id_size + 4;
}
}
@ -593,149 +580,104 @@ class ProtoSize {
/**
* @brief Calculates and adds the size of a sfixed32 field to the total message size
*/
static inline void add_sfixed32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
inline void add_sfixed32(uint32_t field_id_size, int32_t value) {
if (value != 0) {
total_size += field_id_size + 4;
total_size_ += field_id_size + 4;
}
}
// NOTE: add_sfixed64_field removed - wire type 1 (64-bit: sfixed64) not supported
// to reduce overhead on embedded systems
/**
* @brief Calculates and adds the size of an enum field to the total message size
*
* Enum fields are encoded as uint32 varints.
*/
static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Enums are encoded as uint32
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of an enum field to the total message size (repeated field version)
*
* Enum fields are encoded as uint32 varints.
*/
static inline void add_enum_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Always calculate size for repeated fields
// Enums are encoded as uint32
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a sint32 field to the total message size
*
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
inline void add_sint32(uint32_t field_id_size, int32_t value) {
if (value != 0) {
add_sint32_force(field_id_size, value);
}
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
total_size += field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of a sint32 field to the total message size (repeated field version)
* @brief Calculates and adds the size of a sint32 field to the total message size (force version)
*
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Always calculate size for repeated fields
inline void add_sint32_force(uint32_t field_id_size, int32_t value) {
// Always calculate size when force is true
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
total_size += field_id_size + varint(zigzag);
total_size_ += field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of an int64 field to the total message size
*/
static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
inline void add_int64(uint32_t field_id_size, int64_t value) {
if (value != 0) {
add_int64_force(field_id_size, value);
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of an int64 field to the total message size (repeated field version)
* @brief Calculates and adds the size of an int64 field to the total message size (force version)
*/
static inline void add_int64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
// Always calculate size for repeated fields
total_size += field_id_size + varint(value);
inline void add_int64_force(uint32_t field_id_size, int64_t value) {
// Always calculate size when force is true
total_size_ += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint64 field to the total message size
*/
static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
inline void add_uint64(uint32_t field_id_size, uint64_t value) {
if (value != 0) {
add_uint64_force(field_id_size, value);
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint64 field to the total message size (repeated field version)
* @brief Calculates and adds the size of a uint64 field to the total message size (force version)
*/
static inline void add_uint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint64_t value) {
// Always calculate size for repeated fields
total_size += field_id_size + varint(value);
inline void add_uint64_force(uint32_t field_id_size, uint64_t value) {
// Always calculate size when force is true
total_size_ += field_id_size + varint(value);
}
// NOTE: sint64 support functions (add_sint64_field, add_sint64_field_repeated) removed
// NOTE: sint64 support functions (add_sint64_field, add_sint64_field_force) removed
// sint64 type is not supported by ESPHome API to reduce overhead on embedded systems
/**
* @brief Calculates and adds the size of a string field using length
* @brief Calculates and adds the size of a length-delimited field (string/bytes) to the total message size
*/
static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, size_t len) {
// Skip calculation if string is empty
if (len == 0) {
return; // No need to update total_size
inline void add_length(uint32_t field_id_size, size_t len) {
if (len != 0) {
add_length_force(field_id_size, len);
}
// Field ID + length varint + string bytes
total_size += field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
}
/**
* @brief Calculates and adds the size of a string/bytes field to the total message size (repeated field version)
* @brief Calculates and adds the size of a length-delimited field (string/bytes) to the total message size (repeated
* field version)
*/
static inline void add_string_field_repeated(uint32_t &total_size, uint32_t field_id_size, const std::string &str) {
// Always calculate size for repeated fields
const uint32_t str_size = static_cast<uint32_t>(str.size());
total_size += field_id_size + varint(str_size) + str_size;
}
/**
* @brief Calculates and adds the size of a bytes field to the total message size
*/
static inline void add_bytes_field(uint32_t &total_size, uint32_t field_id_size, size_t len) {
// Skip calculation if bytes is empty
if (len == 0) {
return; // No need to update total_size
}
inline void add_length_force(uint32_t field_id_size, size_t len) {
// Always calculate size when force is true
// Field ID + length varint + data bytes
total_size += field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
total_size_ += field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
}
/**
* @brief Adds a pre-calculated size directly to the total
*
* This is used when we can calculate the total size by multiplying the number
* of elements by the bytes per element (for repeated fixed-size types like float, fixed32, etc.)
*
* @param size The pre-calculated total size to add
*/
inline void add_precalculated_size(uint32_t size) { total_size_ += size; }
/**
* @brief Calculates and adds the size of a nested message field to the total message size
*
@ -744,26 +686,21 @@ class ProtoSize {
*
* @param nested_size The pre-calculated size of the nested message
*/
static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) {
// Skip calculation if nested message is empty
if (nested_size == 0) {
return; // No need to update total_size
inline void add_message_field(uint32_t field_id_size, uint32_t nested_size) {
if (nested_size != 0) {
add_message_field_force(field_id_size, nested_size);
}
// Calculate and directly add to total_size
// Field ID + length varint + nested message content
total_size += field_id_size + varint(nested_size) + nested_size;
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version)
* @brief Calculates and adds the size of a nested message field to the total message size (force version)
*
* @param nested_size The pre-calculated size of the nested message
*/
static inline void add_message_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) {
// Always calculate size for repeated fields
inline void add_message_field_force(uint32_t field_id_size, uint32_t nested_size) {
// Always calculate size when force is true
// Field ID + length varint + nested message content
total_size += field_id_size + varint(nested_size) + nested_size;
total_size_ += field_id_size + varint(nested_size) + nested_size;
}
/**
@ -775,26 +712,29 @@ class ProtoSize {
*
* @param message The nested message object
*/
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message) {
uint32_t nested_size = 0;
message.calculate_size(nested_size);
inline void add_message_object(uint32_t field_id_size, const ProtoMessage &message) {
// Calculate nested message size by creating a temporary ProtoSize
ProtoSize nested_calc;
message.calculate_size(nested_calc);
uint32_t nested_size = nested_calc.get_size();
// Use the base implementation with the calculated nested_size
add_message_field(total_size, field_id_size, nested_size);
add_message_field(field_id_size, nested_size);
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version)
* @brief Calculates and adds the size of a nested message field to the total message size (force version)
*
* @param message The nested message object
*/
static inline void add_message_object_repeated(uint32_t &total_size, uint32_t field_id_size,
const ProtoMessage &message) {
uint32_t nested_size = 0;
message.calculate_size(nested_size);
inline void add_message_object_force(uint32_t field_id_size, const ProtoMessage &message) {
// Calculate nested message size by creating a temporary ProtoSize
ProtoSize nested_calc;
message.calculate_size(nested_calc);
uint32_t nested_size = nested_calc.get_size();
// Use the base implementation with the calculated nested_size
add_message_field_repeated(total_size, field_id_size, nested_size);
add_message_field_force(field_id_size, nested_size);
}
/**
@ -807,16 +747,15 @@ class ProtoSize {
* @param messages Vector of message objects
*/
template<typename MessageType>
static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size,
const std::vector<MessageType> &messages) {
inline void add_repeated_message(uint32_t field_id_size, const std::vector<MessageType> &messages) {
// Skip if the vector is empty
if (messages.empty()) {
return;
}
// Use the repeated field version for all messages
// Use the force version for all messages in the repeated field
for (const auto &message : messages) {
add_message_object_repeated(total_size, field_id_size, message);
add_message_object_force(field_id_size, message);
}
}
};
@ -826,8 +765,9 @@ inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessa
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
// Calculate the message size first
uint32_t msg_length_bytes = 0;
value.calculate_size(msg_length_bytes);
ProtoSize msg_size;
value.calculate_size(msg_size);
uint32_t msg_length_bytes = msg_size.get_size();
// Calculate how many bytes the length varint needs
uint32_t varint_length_bytes = ProtoSize::varint(msg_length_bytes);
@ -876,8 +816,9 @@ class ProtoService {
// Optimized method that pre-allocates buffer based on message size
bool send_message_(const ProtoMessage &msg, uint8_t message_type) {
uint32_t msg_size = 0;
msg.calculate_size(msg_size);
ProtoSize size;
msg.calculate_size(size);
uint32_t msg_size = size.get_size();
// Create a pre-sized buffer
auto buffer = this->create_buffer(msg_size);

View File

@ -6,6 +6,7 @@ from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_
from esphome.components.network import IPAddress
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.config_validation import only_with_esp_idf
from esphome.const import (
CONF_AP,
CONF_BSSID,
@ -336,7 +337,7 @@ CONFIG_SCHEMA = cv.All(
single=True
),
cv.Optional(CONF_USE_PSRAM): cv.All(
cv.requires_component("psram"), cv.boolean
only_with_esp_idf, cv.requires_component("psram"), cv.boolean
),
}
),

View File

@ -68,7 +68,10 @@ To bit_cast(const From &src) {
return dst;
}
#endif
using std::lerp;
// clang-format off
inline float lerp(float completion, float start, float end) = delete; // Please use std::lerp. Notice that it has different order on arguments!
// clang-format on
// std::byteswap from C++23
template<typename T> constexpr T byteswap(T n) {

View File

@ -275,13 +275,13 @@ class TypeInfo(ABC):
Args:
name: Field name
force: Whether this is for a repeated field
base_method: Base method name (e.g., "add_int32_field")
base_method: Base method name (e.g., "add_int32")
value_expr: Optional value expression (defaults to name)
"""
field_id_size = self.calculate_field_id_size()
method = f"{base_method}_repeated" if force else base_method
method = f"{base_method}_force" if force else base_method
value = value_expr if value_expr else name
return f"ProtoSize::{method}(total_size, {field_id_size}, {value});"
return f"size.{method}({field_id_size}, {value});"
@abstractmethod
def get_size_calculation(self, name: str, force: bool = False) -> str:
@ -389,7 +389,7 @@ class DoubleType(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_double_field(total_size, {field_id_size}, {name});"
return f"size.add_double({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 8
@ -413,7 +413,7 @@ class FloatType(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_float_field(total_size, {field_id_size}, {name});"
return f"size.add_float({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 4
@ -436,7 +436,7 @@ class Int64Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_int64_field")
return self._get_simple_size_calculation(name, force, "add_int64")
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@ -456,7 +456,7 @@ class UInt64Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_uint64_field")
return self._get_simple_size_calculation(name, force, "add_uint64")
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@ -476,7 +476,7 @@ class Int32Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_int32_field")
return self._get_simple_size_calculation(name, force, "add_int32")
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@ -497,7 +497,7 @@ class Fixed64Type(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_fixed64_field(total_size, {field_id_size}, {name});"
return f"size.add_fixed64({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 8
@ -521,7 +521,7 @@ class Fixed32Type(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_fixed32_field(total_size, {field_id_size}, {name});"
return f"size.add_fixed32({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 4
@ -543,7 +543,7 @@ class BoolType(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_bool_field")
return self._get_simple_size_calculation(name, force, "add_bool")
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 1 # field ID + 1 byte
@ -657,19 +657,21 @@ class StringType(TypeInfo):
# For no_zero_copy, we need to use .size() on the string
if no_zero_copy and name != "it":
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_string_field(total_size, {field_id_size}, this->{self.field_name}.size());"
return self._get_simple_size_calculation(name, force, "add_string_field")
return (
f"size.add_length({field_id_size}, this->{self.field_name}.size());"
)
return self._get_simple_size_calculation(name, force, "add_length")
# Check if this is being called from a repeated field context
# In that case, 'name' will be 'it' and we need to use the repeated version
if name == "it":
# For repeated fields, we need to use add_string_field_repeated which includes field ID
# For repeated fields, we need to use add_length_force which includes field ID
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_string_field_repeated(total_size, {field_id_size}, it);"
return f"size.add_length_force({field_id_size}, it.size());"
# For messages that need encoding, use the StringRef size
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_string_field(total_size, {field_id_size}, this->{self.field_name}_ref_.size());"
return f"size.add_length({field_id_size}, this->{self.field_name}_ref_.size());"
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string
@ -804,7 +806,7 @@ class BytesType(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return f"ProtoSize::add_bytes_field(total_size, {self.calculate_field_id_size()}, this->{self.field_name}_len_);"
return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}_len_);"
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes
@ -879,15 +881,11 @@ class FixedArrayBytesType(TypeInfo):
field_id_size = self.calculate_field_id_size()
if force:
# For repeated fields, always calculate size
return f"total_size += {field_id_size} + ProtoSize::varint(static_cast<uint32_t>({length_field})) + {length_field};"
# For repeated fields, always calculate size (no zero check)
return f"size.add_length_force({field_id_size}, {length_field});"
else:
# For non-repeated fields, skip if length is 0 (matching encode_string behavior)
return (
f"if ({length_field} != 0) {{\n"
f" total_size += {field_id_size} + ProtoSize::varint(static_cast<uint32_t>({length_field})) + {length_field};\n"
f"}}"
)
# For non-repeated fields, add_length already checks for zero
return f"size.add_length({field_id_size}, {length_field});"
def get_estimated_size(self) -> int:
# Estimate based on typical BLE advertisement size
@ -914,7 +912,7 @@ class UInt32Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_uint32_field")
return self._get_simple_size_calculation(name, force, "add_uint32")
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@ -951,7 +949,7 @@ class EnumType(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(
name, force, "add_enum_field", f"static_cast<uint32_t>({name})"
name, force, "add_uint32", f"static_cast<uint32_t>({name})"
)
def get_estimated_size(self) -> int:
@ -973,7 +971,7 @@ class SFixed32Type(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_sfixed32_field(total_size, {field_id_size}, {name});"
return f"size.add_sfixed32({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 4
@ -997,7 +995,7 @@ class SFixed64Type(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_sfixed64_field(total_size, {field_id_size}, {name});"
return f"size.add_sfixed64({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 8
@ -1020,7 +1018,7 @@ class SInt32Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_sint32_field")
return self._get_simple_size_calculation(name, force, "add_sint32")
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@ -1040,7 +1038,7 @@ class SInt64Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_sint64_field")
return self._get_simple_size_calculation(name, force, "add_sint64")
def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@ -1274,7 +1272,7 @@ class RepeatedTypeInfo(TypeInfo):
if isinstance(self._ti, MessageType):
# For repeated messages, use the dedicated helper that handles iteration internally
field_id_size = self._ti.calculate_field_id_size()
o = f"ProtoSize::add_repeated_message(total_size, {field_id_size}, {name});"
o = f"size.add_repeated_message({field_id_size}, {name});"
return o
# For other repeated types, use the underlying type's size calculation with force=True
@ -1287,7 +1285,9 @@ class RepeatedTypeInfo(TypeInfo):
field_id_size = self._ti.calculate_field_id_size()
# Pre-calculate the total bytes per element
bytes_per_element = field_id_size + num_bytes
o += f" total_size += {name}.size() * {bytes_per_element};\n"
o += (
f" size.add_precalculated_size({name}.size() * {bytes_per_element});\n"
)
else:
# Other types need the actual value
o += f" for (const auto {'' if self._ti_is_bool else '&'}it : {name}) {{\n"
@ -1722,11 +1722,11 @@ def build_message_type(
if needs_encode and encode:
o = f"void {desc.name}::encode(ProtoWriteBuffer buffer) const {{"
if len(encode) == 1 and len(encode[0]) + len(o) + 3 < 120:
o += f" {encode[0]} "
o += f" {encode[0]} }}\n"
else:
o += "\n"
o += indent("\n".join(encode)) + "\n"
o += "}\n"
o += "}\n"
cpp += o
prot = "void encode(ProtoWriteBuffer buffer) const override;"
public_content.append(prot)
@ -1734,17 +1734,17 @@ def build_message_type(
# Add calculate_size method only if this message needs encoding and has fields
if needs_encode and size_calc:
o = f"void {desc.name}::calculate_size(uint32_t &total_size) const {{"
o = f"void {desc.name}::calculate_size(ProtoSize &size) const {{"
# For a single field, just inline it for simplicity
if len(size_calc) == 1 and len(size_calc[0]) + len(o) + 3 < 120:
o += f" {size_calc[0]} "
o += f" {size_calc[0]} }}\n"
else:
# For multiple fields
o += "\n"
o += indent("\n".join(size_calc)) + "\n"
o += "}\n"
o += "}\n"
cpp += o
prot = "void calculate_size(uint32_t &total_size) const override;"
prot = "void calculate_size(ProtoSize &size) const override;"
public_content.append(prot)
# If no fields to calculate size for or message doesn't need encoding, the default implementation in ProtoMessage will be used

View File

@ -6,7 +6,7 @@ set -e
cd "$(dirname "$0")/.."
if [ ! -n "$VIRTUAL_ENV" ]; then
if [ -x "$(command -v uv)" ]; then
uv venv venv
uv venv --seed venv
else
python3 -m venv venv
fi

View File

@ -0,0 +1,5 @@
switch:
- name: ${door_name} Garage Door Switch
platform: gpio
pin: ${door_pin}
id: ${door_id}

View File

@ -0,0 +1,19 @@
packages:
left_garage_door: !include
file: garage-door.yaml
vars:
door_name: Left
door_pin: 1
door_id: left_garage_door
middle_garage_door: !include
file: garage-door.yaml
vars:
door_name: Middle
door_pin: 2
door_id: middle_garage_door
right_garage_door: !include
file: garage-door.yaml
vars:
door_name: Right
door_pin: 3
door_id: right_garage_door

View File

@ -0,0 +1,311 @@
esphome:
name: test-ha-api
friendly_name: Home Assistant API Test
host:
api:
services:
- service: trigger_all_tests
then:
- logger.log: "=== Starting Home Assistant API Tests ==="
- button.press: test_basic_service
- button.press: test_templated_service
- button.press: test_empty_string_service
- button.press: test_multiple_fields_service
- button.press: test_complex_lambda_service
- button.press: test_all_empty_service
- button.press: test_rapid_service_calls
- button.press: test_read_ha_states
- number.set:
id: ha_number
value: 42.5
- switch.turn_on: ha_switch
- switch.turn_off: ha_switch
- logger.log: "=== All tests completed ==="
logger:
level: DEBUG
# Time component for templated values
time:
- platform: homeassistant
id: homeassistant_time
# Global variables for testing
globals:
- id: test_brightness
type: int
initial_value: '75'
- id: test_string
type: std::string
initial_value: '"test_value"'
# Sensors for testing state reading
sensor:
- platform: template
name: "Test Sensor"
id: test_sensor
lambda: return 42.0;
update_interval: 0.1s
# Home Assistant sensor that reads external state
- platform: homeassistant
name: "HA Temperature"
entity_id: sensor.external_temperature
id: ha_temperature
on_value:
then:
- logger.log:
format: "HA Temperature state updated: %.1f"
args: ['x']
# Test multiple HA state sensors
- platform: homeassistant
name: "HA Humidity"
entity_id: sensor.external_humidity
id: ha_humidity
on_value:
then:
- logger.log:
format: "HA Humidity state updated: %.1f"
args: ['x']
# Binary sensor from Home Assistant
binary_sensor:
- platform: homeassistant
name: "HA Motion"
entity_id: binary_sensor.external_motion
id: ha_motion
on_state:
then:
- logger.log:
format: "HA Motion state changed: %s"
args: ['x ? "ON" : "OFF"']
# Text sensor from Home Assistant
text_sensor:
- platform: homeassistant
name: "HA Weather"
entity_id: weather.home
attribute: condition
id: ha_weather
on_value:
then:
- logger.log:
format: "HA Weather condition updated: %s"
args: ['x.c_str()']
# Test empty state handling
- platform: homeassistant
name: "HA Empty State"
entity_id: sensor.nonexistent_sensor
id: ha_empty_state
on_value:
then:
- logger.log:
format: "HA Empty state updated: %s"
args: ['x.c_str()']
# Number component for testing HA number control
number:
- platform: template
name: "HA Controlled Number"
id: ha_number
min_value: 0
max_value: 100
step: 1
optimistic: true
set_action:
- logger.log:
format: "Setting HA number to: %.1f"
args: ['x']
- homeassistant.action:
action: input_number.set_value
data:
entity_id: input_number.test_number
value: !lambda 'return to_string(x);'
# Switch component for testing HA switch control
switch:
- platform: template
name: "HA Controlled Switch"
id: ha_switch
optimistic: true
turn_on_action:
- logger.log: "Toggling HA switch: switch.test_switch ON"
- homeassistant.action:
action: switch.turn_on
data:
entity_id: switch.test_switch
turn_off_action:
- logger.log: "Toggling HA switch: switch.test_switch OFF"
- homeassistant.action:
action: switch.turn_off
data:
entity_id: switch.test_switch
# Buttons for testing various service call scenarios
button:
# Test 1: Basic service call with static values
- platform: template
name: "Test Basic Service"
id: test_basic_service
on_press:
- logger.log: "Sending HomeAssistant service call: light.turn_off"
- homeassistant.action:
action: light.turn_off
data:
entity_id: light.test_light
- logger.log: "Service data: entity_id=light.test_light"
# Test 2: Service call with templated/lambda values (main bug fix test)
- platform: template
name: "Test Templated Service"
id: test_templated_service
on_press:
- logger.log: "Testing templated service call"
- lambda: |-
int brightness_percent = id(test_brightness);
std::string computed = to_string(brightness_percent * 255 / 100);
ESP_LOGI("test", "Lambda computed value: %s", computed.c_str());
- homeassistant.action:
action: light.turn_on
data:
entity_id: light.test_light
# This creates a temporary string - the main test case
brightness: !lambda 'return to_string(id(test_brightness) * 255 / 100);'
data_template:
color_name: !lambda 'return id(test_string);'
variables:
transition: !lambda 'return "2.5";'
# Test 3: Service call with empty string values
- platform: template
name: "Test Empty String Service"
id: test_empty_string_service
on_press:
- logger.log: "Testing empty string values"
- homeassistant.action:
action: notify.test
data:
message: "Test message"
title: ""
data_template:
target: !lambda 'return "";'
variables:
sound: !lambda 'return "";'
- logger.log: "Empty value for key: title"
- logger.log: "Empty value for key: target"
- logger.log: "Empty value for key: sound"
# Test 4: Service call with multiple data fields
- platform: template
name: "Test Multiple Fields Service"
id: test_multiple_fields_service
on_press:
- logger.log: "Testing multiple data fields"
- homeassistant.action:
action: climate.set_temperature
data:
entity_id: climate.test_climate
temperature: "22"
hvac_mode: "heat"
data_template:
target_temp_high: !lambda 'return "24";'
target_temp_low: !lambda 'return "20";'
variables:
preset_mode: !lambda 'return "comfort";'
# Test 5: Complex lambda with string operations
- platform: template
name: "Test Complex Lambda Service"
id: test_complex_lambda_service
on_press:
- logger.log: "Testing complex lambda expressions"
- homeassistant.action:
action: script.test_script
data:
entity_id: !lambda |-
std::string base = "light.";
std::string room = "living_room";
return base + room;
brightness_pct: !lambda |-
float sensor_val = id(test_sensor).state;
int pct = (int)(sensor_val * 2.38); // 42 * 2.38 ≈ 100
return to_string(pct);
data_template:
message: !lambda |-
char buffer[50];
snprintf(buffer, sizeof(buffer), "Sensor: %.1f, Time: %02d:%02d",
id(test_sensor).state,
id(homeassistant_time).now().hour,
id(homeassistant_time).now().minute);
return std::string(buffer);
# Test 6: Service with only empty strings to verify size calculation
- platform: template
name: "Test All Empty Service"
id: test_all_empty_service
on_press:
- logger.log: "Testing all empty string values"
- homeassistant.action:
action: test.empty
data:
field1: ""
field2: ""
data_template:
field3: !lambda 'return "";'
variables:
field4: !lambda 'return "";'
- logger.log: "All empty service call completed"
# Test 7: Rapid successive service calls
- platform: template
name: "Test Rapid Service Calls"
id: test_rapid_service_calls
on_press:
- logger.log: "Testing rapid service calls"
- repeat:
count: 5
then:
- homeassistant.action:
action: counter.increment
data:
entity_id: counter.test_counter
- delay: 10ms
- logger.log: "Rapid service calls completed"
# Test 8: Log current HA states
- platform: template
name: "Test Read HA States"
id: test_read_ha_states
on_press:
- logger.log: "Reading current HA states"
- lambda: |-
if (id(ha_temperature).has_state()) {
ESP_LOGI("test", "Current HA Temperature: %.1f", id(ha_temperature).state);
} else {
ESP_LOGI("test", "HA Temperature has no state");
}
if (id(ha_humidity).has_state()) {
ESP_LOGI("test", "Current HA Humidity: %.1f", id(ha_humidity).state);
} else {
ESP_LOGI("test", "HA Humidity has no state");
}
ESP_LOGI("test", "Current HA Motion: %s", id(ha_motion).state ? "ON" : "OFF");
if (id(ha_weather).has_state()) {
ESP_LOGI("test", "Current HA Weather: %s", id(ha_weather).state.c_str());
} else {
ESP_LOGI("test", "HA Weather has no state");
}
if (id(ha_empty_state).has_state()) {
ESP_LOGI("test", "HA Empty State value: %s", id(ha_empty_state).state.c_str());
} else {
ESP_LOGI("test", "HA Empty State has no value (expected)");
}

View File

@ -0,0 +1,305 @@
"""Integration test for Home Assistant API functionality.
Tests:
- Home Assistant service calls with templated values (main bug fix)
- Service calls with empty string values
- Home Assistant state reading (sensors, binary sensors, text sensors)
- Home Assistant number and switch component control
- Complex lambda expressions and string handling
"""
from __future__ import annotations
import asyncio
import re
from aioesphomeapi import HomeassistantServiceCall
import pytest
from .types import APIClientConnectedFactory, RunCompiledFunction
@pytest.mark.asyncio
async def test_api_homeassistant(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Comprehensive test for Home Assistant API functionality."""
loop = asyncio.get_running_loop()
# Create futures for patterns that capture values
lambda_computed_future = loop.create_future()
ha_temp_state_future = loop.create_future()
ha_humidity_state_future = loop.create_future()
ha_motion_state_future = loop.create_future()
ha_weather_state_future = loop.create_future()
# State update futures
temp_update_future = loop.create_future()
humidity_update_future = loop.create_future()
motion_update_future = loop.create_future()
weather_update_future = loop.create_future()
# Number future
ha_number_future = loop.create_future()
tests_complete_future = loop.create_future()
# Patterns to match in logs - only keeping patterns that capture values
lambda_computed_pattern = re.compile(r"Lambda computed value: (\d+)")
ha_temp_state_pattern = re.compile(r"Current HA Temperature: ([\d.]+)")
ha_humidity_state_pattern = re.compile(r"Current HA Humidity: ([\d.]+)")
ha_motion_state_pattern = re.compile(r"Current HA Motion: (ON|OFF)")
ha_weather_state_pattern = re.compile(r"Current HA Weather: (\w+)")
# State update patterns
temp_update_pattern = re.compile(r"HA Temperature state updated: ([\d.]+)")
humidity_update_pattern = re.compile(r"HA Humidity state updated: ([\d.]+)")
motion_update_pattern = re.compile(r"HA Motion state changed: (ON|OFF)")
weather_update_pattern = re.compile(r"HA Weather condition updated: (\w+)")
# Number pattern
ha_number_pattern = re.compile(r"Setting HA number to: ([\d.]+)")
tests_complete_pattern = re.compile(r"=== All tests completed ===")
# Track all log lines for debugging
log_lines: list[str] = []
# Track HomeAssistant service calls
ha_service_calls: list[HomeassistantServiceCall] = []
# Service call futures organized by service name
service_call_futures = {
"light.turn_off": loop.create_future(), # basic_service_call
"light.turn_on": loop.create_future(), # templated_service_call
"notify.test": loop.create_future(), # empty_string_service_call
"climate.set_temperature": loop.create_future(), # multiple_fields_service_call
"script.test_script": loop.create_future(), # complex_lambda_service_call
"test.empty": loop.create_future(), # all_empty_service_call
"input_number.set_value": loop.create_future(), # ha_number_service_call
"switch.turn_on": loop.create_future(), # ha_switch_on_service_call
"switch.turn_off": loop.create_future(), # ha_switch_off_service_call
}
def on_service_call(service_call: HomeassistantServiceCall) -> None:
"""Capture HomeAssistant service calls."""
ha_service_calls.append(service_call)
# Check if this service call is one we're waiting for
if service_call.service in service_call_futures:
future = service_call_futures[service_call.service]
if not future.done():
future.set_result(service_call)
def check_output(line: str) -> None:
"""Check log output for expected messages."""
log_lines.append(line)
# Check for patterns that capture values
if not lambda_computed_future.done():
match = lambda_computed_pattern.search(line)
if match:
lambda_computed_future.set_result(match.group(1))
elif not ha_temp_state_future.done() and ha_temp_state_pattern.search(line):
ha_temp_state_future.set_result(line)
elif not ha_humidity_state_future.done() and ha_humidity_state_pattern.search(
line
):
ha_humidity_state_future.set_result(line)
elif not ha_motion_state_future.done() and ha_motion_state_pattern.search(line):
ha_motion_state_future.set_result(line)
elif not ha_weather_state_future.done() and ha_weather_state_pattern.search(
line
):
ha_weather_state_future.set_result(line)
# Check state update patterns
elif not temp_update_future.done() and temp_update_pattern.search(line):
temp_update_future.set_result(line)
elif not humidity_update_future.done() and humidity_update_pattern.search(line):
humidity_update_future.set_result(line)
elif not motion_update_future.done() and motion_update_pattern.search(line):
motion_update_future.set_result(line)
elif not weather_update_future.done() and weather_update_pattern.search(line):
weather_update_future.set_result(line)
# Check number pattern
elif not ha_number_future.done() and ha_number_pattern.search(line):
match = ha_number_pattern.search(line)
if match:
ha_number_future.set_result(match.group(1))
elif not tests_complete_future.done() and tests_complete_pattern.search(line):
tests_complete_future.set_result(True)
# Run with log monitoring
async with (
run_compiled(yaml_config, line_callback=check_output),
api_client_connected() as client,
):
# Verify device info
device_info = await client.device_info()
assert device_info is not None
assert device_info.name == "test-ha-api"
# Subscribe to HomeAssistant service calls
client.subscribe_service_calls(on_service_call)
# Send some Home Assistant states for our sensors to read
client.send_home_assistant_state("sensor.external_temperature", "", "22.5")
client.send_home_assistant_state("sensor.external_humidity", "", "65.0")
client.send_home_assistant_state("binary_sensor.external_motion", "", "ON")
client.send_home_assistant_state("weather.home", "condition", "sunny")
# List entities and services
_, services = await client.list_entities_services()
# Find the trigger service
trigger_service = next(
(s for s in services if s.name == "trigger_all_tests"), None
)
assert trigger_service is not None, "trigger_all_tests service not found"
# Execute all tests
client.execute_service(trigger_service, {})
# Wait for all tests to complete with appropriate timeouts
try:
# Templated service test - the main bug fix
computed_value = await asyncio.wait_for(lambda_computed_future, timeout=5.0)
# Verify the computed value is reasonable (75 * 255 / 100 = 191.25 -> 191)
assert computed_value in ["191", "192"], (
f"Unexpected computed value: {computed_value}"
)
# Check state reads - verify we received the mocked values
temp_line = await asyncio.wait_for(ha_temp_state_future, timeout=5.0)
assert "Current HA Temperature: 22.5" in temp_line
humidity_line = await asyncio.wait_for(
ha_humidity_state_future, timeout=5.0
)
assert "Current HA Humidity: 65.0" in humidity_line
motion_line = await asyncio.wait_for(ha_motion_state_future, timeout=5.0)
assert "Current HA Motion: ON" in motion_line
weather_line = await asyncio.wait_for(ha_weather_state_future, timeout=5.0)
assert "Current HA Weather: sunny" in weather_line
# Number test
number_value = await asyncio.wait_for(ha_number_future, timeout=5.0)
assert number_value == "42.5", f"Unexpected number value: {number_value}"
# Wait for completion
await asyncio.wait_for(tests_complete_future, timeout=5.0)
# Now verify the protobuf messages
# 1. Basic service call
basic_call = await asyncio.wait_for(
service_call_futures["light.turn_off"], timeout=2.0
)
assert basic_call.service == "light.turn_off"
assert "entity_id" in basic_call.data, (
f"entity_id not found in data: {basic_call.data}"
)
assert basic_call.data["entity_id"] == "light.test_light", (
f"Wrong entity_id: {basic_call.data['entity_id']}"
)
# 2. Templated service call - verify the temporary string issue is fixed
templated_call = await asyncio.wait_for(
service_call_futures["light.turn_on"], timeout=2.0
)
assert templated_call.service == "light.turn_on"
# Check the computed brightness value
assert "brightness" in templated_call.data
assert templated_call.data["brightness"] in ["191", "192"] # 75 * 255 / 100
# Check data_template
assert "color_name" in templated_call.data_template
assert templated_call.data_template["color_name"] == "test_value"
# Check variables
assert "transition" in templated_call.variables
assert templated_call.variables["transition"] == "2.5"
# 3. Empty string service call
empty_call = await asyncio.wait_for(
service_call_futures["notify.test"], timeout=2.0
)
assert empty_call.service == "notify.test"
# Verify empty strings are properly handled
assert "title" in empty_call.data and empty_call.data["title"] == ""
assert (
"target" in empty_call.data_template
and empty_call.data_template["target"] == ""
)
assert (
"sound" in empty_call.variables and empty_call.variables["sound"] == ""
)
# 4. Multiple fields service call
multi_call = await asyncio.wait_for(
service_call_futures["climate.set_temperature"], timeout=2.0
)
assert multi_call.service == "climate.set_temperature"
assert multi_call.data["temperature"] == "22"
assert multi_call.data["hvac_mode"] == "heat"
assert multi_call.data_template["target_temp_high"] == "24"
assert multi_call.variables["preset_mode"] == "comfort"
# 5. Complex lambda service call
complex_call = await asyncio.wait_for(
service_call_futures["script.test_script"], timeout=2.0
)
assert complex_call.service == "script.test_script"
assert complex_call.data["entity_id"] == "light.living_room"
assert complex_call.data["brightness_pct"] == "99" # 42 * 2.38 ≈ 99
# Check message includes sensor value
assert "message" in complex_call.data_template
assert "Sensor: 42.0" in complex_call.data_template["message"]
# 6. All empty service call
all_empty_call = await asyncio.wait_for(
service_call_futures["test.empty"], timeout=2.0
)
assert all_empty_call.service == "test.empty"
# All fields should be empty strings
assert all(v == "" for v in all_empty_call.data.values())
assert all(v == "" for v in all_empty_call.data_template.values())
assert all(v == "" for v in all_empty_call.variables.values())
# 7. HA Number service call
number_call = await asyncio.wait_for(
service_call_futures["input_number.set_value"], timeout=2.0
)
assert number_call.service == "input_number.set_value"
assert number_call.data["entity_id"] == "input_number.test_number"
# The value might be formatted with trailing zeros
assert float(number_call.data["value"]) == 42.5
# 8. HA Switch service calls
switch_on_call = await asyncio.wait_for(
service_call_futures["switch.turn_on"], timeout=2.0
)
assert switch_on_call.service == "switch.turn_on"
assert switch_on_call.data["entity_id"] == "switch.test_switch"
switch_off_call = await asyncio.wait_for(
service_call_futures["switch.turn_off"], timeout=2.0
)
assert switch_off_call.service == "switch.turn_off"
assert switch_off_call.data["entity_id"] == "switch.test_switch"
except TimeoutError as e:
# Show recent log lines for debugging
recent_logs = "\n".join(log_lines[-20:])
service_calls_summary = "\n".join(
f"- {call.service}" for call in ha_service_calls
)
pytest.fail(
f"Test timed out waiting for expected log pattern or service call. Error: {e}\n\n"
f"Recent log lines:\n{recent_logs}\n\n"
f"Received service calls:\n{service_calls_summary}"
)