mirror of
https://github.com/esphome/esphome.git
synced 2026-05-22 01:42:49 +08:00
Replace API deferred queue with efficient message batching system (#9012)
This commit is contained in:
@@ -49,6 +49,7 @@ SERVICE_ARG_NATIVE_TYPES = {
|
||||
"string[]": cg.std_vector.template(cg.std_string),
|
||||
}
|
||||
CONF_ENCRYPTION = "encryption"
|
||||
CONF_BATCH_DELAY = "batch_delay"
|
||||
|
||||
|
||||
def validate_encryption_key(value):
|
||||
@@ -109,6 +110,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
): ACTIONS_SCHEMA,
|
||||
cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
|
||||
cv.Optional(CONF_ENCRYPTION): _encryption_schema,
|
||||
cv.Optional(
|
||||
CONF_BATCH_DELAY, default="100ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
@@ -129,6 +133,7 @@ async def to_code(config):
|
||||
cg.add(var.set_port(config[CONF_PORT]))
|
||||
cg.add(var.set_password(config[CONF_PASSWORD]))
|
||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
|
||||
|
||||
for conf in config.get(CONF_ACTIONS, []):
|
||||
template_args = []
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -605,9 +605,21 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
|
||||
int err;
|
||||
APIError aerr;
|
||||
aerr = state_action_();
|
||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||
uint16_t payload_len = static_cast<uint16_t>(raw_buffer->size() - frame_header_padding_);
|
||||
|
||||
// Resize to include MAC space (required for Noise encryption)
|
||||
raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
|
||||
|
||||
// Use write_protobuf_packets with a single packet
|
||||
std::vector<PacketInfo> packets;
|
||||
packets.emplace_back(type, 0, payload_len);
|
||||
|
||||
return write_protobuf_packets(buffer, packets);
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) {
|
||||
APIError aerr = state_action_();
|
||||
if (aerr != APIError::OK) {
|
||||
return aerr;
|
||||
}
|
||||
@@ -616,56 +628,66 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||
// Message data starts after padding
|
||||
uint16_t payload_len = raw_buffer->size() - frame_header_padding_;
|
||||
uint16_t padding = 0;
|
||||
uint16_t msg_len = 4 + payload_len + padding;
|
||||
|
||||
// We need to resize to include MAC space, but we already reserved it in create_buffer
|
||||
raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
|
||||
|
||||
// Write the noise header in the padded area
|
||||
// Buffer layout:
|
||||
// [0] - 0x01 indicator byte
|
||||
// [1-2] - Size of encrypted payload (filled after encryption)
|
||||
// [3-4] - Message type (encrypted)
|
||||
// [5-6] - Payload length (encrypted)
|
||||
// [7...] - Actual payload data (encrypted)
|
||||
uint8_t *buf_start = raw_buffer->data();
|
||||
buf_start[0] = 0x01; // indicator
|
||||
// buf_start[1], buf_start[2] to be set later after encryption
|
||||
const uint8_t msg_offset = 3;
|
||||
buf_start[msg_offset + 0] = (uint8_t) (type >> 8); // type high byte
|
||||
buf_start[msg_offset + 1] = (uint8_t) type; // type low byte
|
||||
buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len high byte
|
||||
buf_start[msg_offset + 3] = (uint8_t) payload_len; // data_len low byte
|
||||
// payload data is already in the buffer starting at position 7
|
||||
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
// The capacity parameter should be msg_len + frame_footer_size_ (MAC length) to allow space for encryption
|
||||
noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
|
||||
err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_cipherstate_encrypt failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::CIPHERSTATE_ENCRYPT_FAILED;
|
||||
if (packets.empty()) {
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
uint16_t total_len = 3 + mbuf.size;
|
||||
buf_start[1] = (uint8_t) (mbuf.size >> 8);
|
||||
buf_start[2] = (uint8_t) mbuf.size;
|
||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||
this->reusable_iovs_.clear();
|
||||
this->reusable_iovs_.reserve(packets.size());
|
||||
|
||||
struct iovec iov;
|
||||
// Point iov_base to the beginning of the buffer (no unused padding in Noise)
|
||||
// We send the entire frame: indicator + size + encrypted(type + data_len + payload + MAC)
|
||||
iov.iov_base = buf_start;
|
||||
iov.iov_len = total_len;
|
||||
// We need to encrypt each packet in place
|
||||
for (const auto &packet : packets) {
|
||||
uint16_t type = packet.message_type;
|
||||
uint16_t offset = packet.offset;
|
||||
uint16_t payload_len = packet.payload_size;
|
||||
uint16_t msg_len = 4 + payload_len; // type(2) + data_len(2) + payload
|
||||
|
||||
// write raw to not have two packets sent if NAGLE disabled
|
||||
return this->write_raw_(&iov, 1);
|
||||
// The buffer already has padding at offset
|
||||
uint8_t *buf_start = raw_buffer->data() + offset;
|
||||
|
||||
// Write noise header
|
||||
buf_start[0] = 0x01; // indicator
|
||||
// buf_start[1], buf_start[2] to be set after encryption
|
||||
|
||||
// Write message header (to be encrypted)
|
||||
const uint8_t msg_offset = 3;
|
||||
buf_start[msg_offset + 0] = (uint8_t) (type >> 8); // type high byte
|
||||
buf_start[msg_offset + 1] = (uint8_t) type; // type low byte
|
||||
buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len high byte
|
||||
buf_start[msg_offset + 3] = (uint8_t) payload_len; // data_len low byte
|
||||
// payload data is already in the buffer starting at offset + 7
|
||||
|
||||
// Make sure we have space for MAC
|
||||
// The buffer should already have been sized appropriately
|
||||
|
||||
// Encrypt the message in place
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
|
||||
|
||||
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("noise_cipherstate_encrypt failed: %s", noise_err_to_str(err).c_str());
|
||||
return APIError::CIPHERSTATE_ENCRYPT_FAILED;
|
||||
}
|
||||
|
||||
// Fill in the encrypted size
|
||||
buf_start[1] = (uint8_t) (mbuf.size >> 8);
|
||||
buf_start[2] = (uint8_t) mbuf.size;
|
||||
|
||||
// Add iovec for this encrypted packet
|
||||
struct iovec iov;
|
||||
iov.iov_base = buf_start;
|
||||
iov.iov_len = 3 + mbuf.size; // indicator + size + encrypted data
|
||||
this->reusable_iovs_.push_back(iov);
|
||||
}
|
||||
|
||||
// Send all encrypted packets in one writev call
|
||||
return this->write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size());
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
|
||||
uint8_t header[3];
|
||||
header[0] = 0x01; // indicator
|
||||
@@ -1004,65 +1026,86 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
|
||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||
uint16_t payload_len = static_cast<uint16_t>(raw_buffer->size() - frame_header_padding_);
|
||||
|
||||
// Use write_protobuf_packets with a single packet
|
||||
std::vector<PacketInfo> packets;
|
||||
packets.emplace_back(type, 0, payload_len);
|
||||
|
||||
return write_protobuf_packets(buffer, packets);
|
||||
}
|
||||
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer,
|
||||
const std::vector<PacketInfo> &packets) {
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||
// Message data starts after padding (frame_header_padding_ = 6)
|
||||
uint16_t payload_len = static_cast<uint16_t>(raw_buffer->size() - frame_header_padding_);
|
||||
|
||||
// Calculate varint sizes for header components
|
||||
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
|
||||
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
|
||||
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
||||
|
||||
if (total_header_len > frame_header_padding_) {
|
||||
// Header is too large to fit in the padding
|
||||
return APIError::BAD_ARG;
|
||||
if (packets.empty()) {
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
// Calculate where to start writing the header
|
||||
// The header starts at the latest possible position to minimize unused padding
|
||||
//
|
||||
// Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
|
||||
// [0-2] - Unused padding
|
||||
// [3] - 0x00 indicator byte
|
||||
// [4] - Payload size varint (1 byte, for sizes 0-127)
|
||||
// [5] - Message type varint (1 byte, for types 0-127)
|
||||
// [6...] - Actual payload data
|
||||
//
|
||||
// Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
|
||||
// [0-1] - Unused padding
|
||||
// [2] - 0x00 indicator byte
|
||||
// [3-4] - Payload size varint (2 bytes, for sizes 128-16383)
|
||||
// [5] - Message type varint (1 byte, for types 0-127)
|
||||
// [6...] - Actual payload data
|
||||
//
|
||||
// Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
|
||||
// [0] - 0x00 indicator byte
|
||||
// [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
|
||||
// [4-5] - Message type varint (2 bytes, for types 128-32767)
|
||||
// [6...] - Actual payload data
|
||||
uint8_t *buf_start = raw_buffer->data();
|
||||
uint8_t header_offset = frame_header_padding_ - total_header_len;
|
||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||
this->reusable_iovs_.clear();
|
||||
this->reusable_iovs_.reserve(packets.size());
|
||||
|
||||
// Write the plaintext header
|
||||
buf_start[header_offset] = 0x00; // indicator
|
||||
for (const auto &packet : packets) {
|
||||
uint16_t type = packet.message_type;
|
||||
uint16_t offset = packet.offset;
|
||||
uint16_t payload_len = packet.payload_size;
|
||||
|
||||
// Encode size varint directly into buffer
|
||||
ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
|
||||
// Calculate varint sizes for header layout
|
||||
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
|
||||
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
|
||||
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
||||
|
||||
// Encode type varint directly into buffer
|
||||
ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
|
||||
// Calculate where to start writing the header
|
||||
// The header starts at the latest possible position to minimize unused padding
|
||||
//
|
||||
// Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
|
||||
// [0-2] - Unused padding
|
||||
// [3] - 0x00 indicator byte
|
||||
// [4] - Payload size varint (1 byte, for sizes 0-127)
|
||||
// [5] - Message type varint (1 byte, for types 0-127)
|
||||
// [6...] - Actual payload data
|
||||
//
|
||||
// Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
|
||||
// [0-1] - Unused padding
|
||||
// [2] - 0x00 indicator byte
|
||||
// [3-4] - Payload size varint (2 bytes, for sizes 128-16383)
|
||||
// [5] - Message type varint (1 byte, for types 0-127)
|
||||
// [6...] - Actual payload data
|
||||
//
|
||||
// Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
|
||||
// [0] - 0x00 indicator byte
|
||||
// [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
|
||||
// [4-5] - Message type varint (2 bytes, for types 128-32767)
|
||||
// [6...] - Actual payload data
|
||||
//
|
||||
// The message starts at offset + frame_header_padding_
|
||||
// So we write the header starting at offset + frame_header_padding_ - total_header_len
|
||||
uint8_t *buf_start = raw_buffer->data() + offset;
|
||||
uint32_t header_offset = frame_header_padding_ - total_header_len;
|
||||
|
||||
struct iovec iov;
|
||||
// Point iov_base to the beginning of our header (skip unused padding)
|
||||
// This ensures we only send the actual header and payload, not the empty padding bytes
|
||||
iov.iov_base = buf_start + header_offset;
|
||||
iov.iov_len = total_header_len + payload_len;
|
||||
// Write the plaintext header
|
||||
buf_start[header_offset] = 0x00; // indicator
|
||||
|
||||
return write_raw_(&iov, 1);
|
||||
// Encode size varint directly into buffer
|
||||
ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
|
||||
|
||||
// Encode type varint directly into buffer
|
||||
ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
|
||||
|
||||
// Add iovec for this packet (header + payload)
|
||||
struct iovec iov;
|
||||
iov.iov_base = buf_start + header_offset;
|
||||
iov.iov_len = total_header_len + payload_len;
|
||||
this->reusable_iovs_.push_back(iov);
|
||||
}
|
||||
|
||||
// Send all packets in one writev call
|
||||
return write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size());
|
||||
}
|
||||
|
||||
#endif // USE_API_PLAINTEXT
|
||||
|
||||
@@ -27,6 +27,17 @@ struct ReadPacketBuffer {
|
||||
uint16_t data_len;
|
||||
};
|
||||
|
||||
// Packed packet info structure to minimize memory usage
|
||||
struct PacketInfo {
|
||||
uint16_t message_type; // 2 bytes
|
||||
uint16_t offset; // 2 bytes (sufficient for packet size ~1460 bytes)
|
||||
uint16_t payload_size; // 2 bytes (up to 65535 bytes)
|
||||
uint16_t padding; // 2 byte (for alignment)
|
||||
|
||||
PacketInfo(uint16_t type, uint16_t off, uint16_t size)
|
||||
: message_type(type), offset(off), payload_size(size), padding(0) {}
|
||||
};
|
||||
|
||||
enum class APIError : int {
|
||||
OK = 0,
|
||||
WOULD_BLOCK = 1001,
|
||||
@@ -87,6 +98,10 @@ class APIFrameHelper {
|
||||
// Give this helper a name for logging
|
||||
void set_log_info(std::string info) { info_ = std::move(info); }
|
||||
virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) = 0;
|
||||
// Write multiple protobuf packets in a single operation
|
||||
// packets contains (message_type, offset, length) for each message in the buffer
|
||||
// The buffer contains all messages with appropriate padding before each
|
||||
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) = 0;
|
||||
// Get the frame header padding required by this protocol
|
||||
virtual uint8_t frame_header_padding() = 0;
|
||||
// Get the frame footer size required by this protocol
|
||||
@@ -157,6 +172,9 @@ class APIFrameHelper {
|
||||
uint8_t frame_header_padding_{0};
|
||||
uint8_t frame_footer_size_{0};
|
||||
|
||||
// Reusable IOV array for write_protobuf_packets to avoid repeated allocations
|
||||
std::vector<struct iovec> reusable_iovs_;
|
||||
|
||||
// Receive buffer for reading frame data
|
||||
std::vector<uint8_t> rx_buf_;
|
||||
uint16_t rx_buf_len_ = 0;
|
||||
@@ -182,6 +200,7 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) override;
|
||||
// Get the frame header padding required by this protocol
|
||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||
// Get the frame footer size required by this protocol
|
||||
@@ -226,6 +245,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) override;
|
||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||
// Get the frame footer size required by this protocol
|
||||
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,162 +10,94 @@ namespace api {
|
||||
|
||||
class APIServerConnectionBase : public ProtoService {
|
||||
public:
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
protected:
|
||||
void log_send_message_(const char *name, const std::string &dump);
|
||||
|
||||
public:
|
||||
#endif
|
||||
|
||||
template<typename T> bool send_message(const T &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
this->log_send_message_(T::message_name(), msg.dump());
|
||||
#endif
|
||||
return this->send_message_(msg, T::MESSAGE_TYPE);
|
||||
}
|
||||
|
||||
virtual void on_hello_request(const HelloRequest &value){};
|
||||
bool send_hello_response(const HelloResponse &msg);
|
||||
|
||||
virtual void on_connect_request(const ConnectRequest &value){};
|
||||
bool send_connect_response(const ConnectResponse &msg);
|
||||
bool send_disconnect_request(const DisconnectRequest &msg);
|
||||
|
||||
virtual void on_disconnect_request(const DisconnectRequest &value){};
|
||||
bool send_disconnect_response(const DisconnectResponse &msg);
|
||||
virtual void on_disconnect_response(const DisconnectResponse &value){};
|
||||
bool send_ping_request(const PingRequest &msg);
|
||||
virtual void on_ping_request(const PingRequest &value){};
|
||||
bool send_ping_response(const PingResponse &msg);
|
||||
virtual void on_ping_response(const PingResponse &value){};
|
||||
virtual void on_device_info_request(const DeviceInfoRequest &value){};
|
||||
bool send_device_info_response(const DeviceInfoResponse &msg);
|
||||
|
||||
virtual void on_list_entities_request(const ListEntitiesRequest &value){};
|
||||
bool send_list_entities_done_response(const ListEntitiesDoneResponse &msg);
|
||||
|
||||
virtual void on_subscribe_states_request(const SubscribeStatesRequest &value){};
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_binary_sensor_state_response(const BinarySensorStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_list_entities_cover_response(const ListEntitiesCoverResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_cover_state_response(const CoverStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
virtual void on_cover_command_request(const CoverCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_list_entities_fan_response(const ListEntitiesFanResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_fan_state_response(const FanStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
virtual void on_fan_command_request(const FanCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_list_entities_light_response(const ListEntitiesLightResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_light_state_response(const LightStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
virtual void on_light_command_request(const LightCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_sensor_state_response(const SensorStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_switch_state_response(const SwitchStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
virtual void on_switch_command_request(const SwitchCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_text_sensor_state_response(const TextSensorStateResponse &msg);
|
||||
#endif
|
||||
|
||||
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
|
||||
bool send_subscribe_logs_response(const SubscribeLogsResponse &msg);
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyResponse &msg);
|
||||
#endif
|
||||
|
||||
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
||||
bool send_homeassistant_service_response(const HomeassistantServiceResponse &msg);
|
||||
|
||||
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
||||
bool send_subscribe_home_assistant_state_response(const SubscribeHomeAssistantStateResponse &msg);
|
||||
|
||||
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
|
||||
bool send_get_time_request(const GetTimeRequest &msg);
|
||||
virtual void on_get_time_request(const GetTimeRequest &value){};
|
||||
bool send_get_time_response(const GetTimeResponse &msg);
|
||||
virtual void on_get_time_response(const GetTimeResponse &value){};
|
||||
bool send_list_entities_services_response(const ListEntitiesServicesResponse &msg);
|
||||
|
||||
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool send_list_entities_camera_response(const ListEntitiesCameraResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool send_camera_image_response(const CameraImageResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
virtual void on_camera_image_request(const CameraImageRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_list_entities_climate_response(const ListEntitiesClimateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_climate_state_response(const ClimateStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool send_list_entities_number_response(const ListEntitiesNumberResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool send_number_state_response(const NumberStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
virtual void on_number_command_request(const NumberCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool send_list_entities_select_response(const ListEntitiesSelectResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool send_select_state_response(const SelectStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
virtual void on_select_command_request(const SelectCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_SIREN
|
||||
bool send_list_entities_siren_response(const ListEntitiesSirenResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_SIREN
|
||||
bool send_siren_state_response(const SirenStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SIREN
|
||||
virtual void on_siren_command_request(const SirenCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool send_list_entities_lock_response(const ListEntitiesLockResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool send_lock_state_response(const LockStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
virtual void on_lock_command_request(const LockCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_BUTTON
|
||||
virtual void on_button_command_request(const ButtonCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
bool send_list_entities_media_player_response(const ListEntitiesMediaPlayerResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
bool send_media_player_state_response(const MediaPlayerStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){};
|
||||
#endif
|
||||
@@ -173,33 +105,19 @@ class APIServerConnectionBase : public ProtoService {
|
||||
virtual void on_subscribe_bluetooth_le_advertisements_request(
|
||||
const SubscribeBluetoothLEAdvertisementsRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_le_raw_advertisements_response(const BluetoothLERawAdvertisementsResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_get_services_done_response(const BluetoothGATTGetServicesDoneResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
|
||||
#endif
|
||||
@@ -212,49 +130,23 @@ class APIServerConnectionBase : public ProtoService {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_error_response(const BluetoothGATTErrorResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_device_pairing_response(const BluetoothDevicePairingResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_unsubscribe_bluetooth_le_advertisements_request(
|
||||
const UnsubscribeBluetoothLEAdvertisementsRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_scanner_state_response(const BluetoothScannerStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool send_voice_assistant_request(const VoiceAssistantRequest &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_response(const VoiceAssistantResponse &value){};
|
||||
#endif
|
||||
@@ -262,7 +154,6 @@ class APIServerConnectionBase : public ProtoService {
|
||||
virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool send_voice_assistant_audio(const VoiceAssistantAudio &msg);
|
||||
virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
@@ -271,84 +162,39 @@ class APIServerConnectionBase : public ProtoService {
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool send_voice_assistant_configuration_response(const VoiceAssistantConfigurationResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){};
|
||||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool send_alarm_control_panel_state_response(const AlarmControlPanelStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool send_list_entities_text_response(const ListEntitiesTextResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool send_text_state_response(const TextStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
virtual void on_text_command_request(const TextCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool send_list_entities_date_response(const ListEntitiesDateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool send_date_state_response(const DateStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
virtual void on_date_command_request(const DateCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
bool send_list_entities_time_response(const ListEntitiesTimeResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
bool send_time_state_response(const TimeStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_TIME
|
||||
virtual void on_time_command_request(const TimeCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool send_list_entities_event_response(const ListEntitiesEventResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool send_event_response(const EventResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
bool send_list_entities_valve_response(const ListEntitiesValveResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
bool send_valve_state_response(const ValveStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_VALVE
|
||||
virtual void on_valve_command_request(const ValveCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
bool send_list_entities_date_time_response(const ListEntitiesDateTimeResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
bool send_date_time_state_response(const DateTimeStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool send_list_entities_update_response(const ListEntitiesUpdateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool send_update_state_response(const UpdateStateResponse &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
virtual void on_update_command_request(const UpdateCommandRequest &value){};
|
||||
#endif
|
||||
|
||||
@@ -24,7 +24,11 @@ static const char *const TAG = "api";
|
||||
// APIServer
|
||||
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
APIServer::APIServer() { global_api_server = this; }
|
||||
APIServer::APIServer() {
|
||||
global_api_server = this;
|
||||
// Pre-allocate shared write buffer
|
||||
shared_write_buffer_.reserve(64);
|
||||
}
|
||||
|
||||
void APIServer::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Running setup");
|
||||
@@ -227,7 +231,7 @@ void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool s
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_binary_sensor_state(obj, state);
|
||||
c->send_binary_sensor_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -263,7 +267,7 @@ void APIServer::on_sensor_update(sensor::Sensor *obj, float state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_sensor_state(obj, state);
|
||||
c->send_sensor_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -272,7 +276,7 @@ void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_switch_state(obj, state);
|
||||
c->send_switch_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -281,7 +285,7 @@ void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::s
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_text_sensor_state(obj, state);
|
||||
c->send_text_sensor_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -299,7 +303,7 @@ void APIServer::on_number_update(number::Number *obj, float state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_number_state(obj, state);
|
||||
c->send_number_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -335,7 +339,7 @@ void APIServer::on_text_update(text::Text *obj, const std::string &state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_text_state(obj, state);
|
||||
c->send_text_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -344,7 +348,7 @@ void APIServer::on_select_update(select::Select *obj, const std::string &state,
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_select_state(obj, state);
|
||||
c->send_select_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -353,7 +357,7 @@ void APIServer::on_lock_update(lock::Lock *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_lock_state(obj, obj->state);
|
||||
c->send_lock_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -404,6 +408,8 @@ void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||
|
||||
void APIServer::set_password(const std::string &password) { this->password_ = password; }
|
||||
|
||||
void APIServer::set_batch_delay(uint32_t batch_delay) { this->batch_delay_ = batch_delay; }
|
||||
|
||||
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_homeassistant_service_call(call);
|
||||
@@ -462,7 +468,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
||||
ESP_LOGW(TAG, "Disconnecting all clients to reset connections");
|
||||
this->set_noise_psk(psk);
|
||||
for (auto &c : this->clients_) {
|
||||
c->send_disconnect_request(DisconnectRequest());
|
||||
c->send_message(DisconnectRequest());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -492,7 +498,7 @@ void APIServer::on_shutdown() {
|
||||
|
||||
// Send disconnect requests to all connected clients
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->send_disconnect_request(DisconnectRequest())) {
|
||||
if (!c->send_message(DisconnectRequest())) {
|
||||
// If we can't send the disconnect request, mark for immediate closure
|
||||
c->next_close_ = true;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,11 @@ class APIServer : public Component, public Controller {
|
||||
void set_port(uint16_t port);
|
||||
void set_password(const std::string &password);
|
||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||
void set_batch_delay(uint32_t batch_delay);
|
||||
uint32_t get_batch_delay() const { return batch_delay_; }
|
||||
|
||||
// Get reference to shared buffer for API connections
|
||||
std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; }
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
bool save_noise_psk(psk_t psk, bool make_active = true);
|
||||
@@ -141,9 +146,11 @@ class APIServer : public Component, public Controller {
|
||||
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
||||
uint16_t port_{6053};
|
||||
uint32_t reboot_timeout_{300000};
|
||||
uint32_t batch_delay_{100};
|
||||
uint32_t last_connected_{0};
|
||||
std::vector<std::unique_ptr<APIConnection>> clients_;
|
||||
std::string password_;
|
||||
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
|
||||
std::vector<HomeAssistantStateSubscription> state_subs_;
|
||||
std::vector<UserServiceDescriptor *> user_services_;
|
||||
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
|
||||
|
||||
@@ -73,7 +73,7 @@ bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(
|
||||
ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {}
|
||||
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
|
||||
auto resp = service->encode_list_service_response();
|
||||
return this->client_->send_list_entities_services_response(resp);
|
||||
return this->client_->send_message(resp);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
|
||||
@@ -360,11 +360,11 @@ class ProtoService {
|
||||
* @return A ProtoWriteBuffer object with the reserved size.
|
||||
*/
|
||||
virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0;
|
||||
virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0;
|
||||
virtual bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) = 0;
|
||||
virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
|
||||
|
||||
// Optimized method that pre-allocates buffer based on message size
|
||||
template<class C> bool send_message_(const C &msg, uint32_t message_type) {
|
||||
bool send_message_(const ProtoMessage &msg, uint16_t message_type) {
|
||||
uint32_t msg_size = 0;
|
||||
msg.calculate_size(msg_size);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace api {
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool InitialStateIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
|
||||
return this->client_->send_binary_sensor_state(binary_sensor, binary_sensor->state);
|
||||
return this->client_->send_binary_sensor_state(binary_sensor);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
@@ -21,27 +21,21 @@ bool InitialStateIterator::on_fan(fan::Fan *fan) { return this->client_->send_fa
|
||||
bool InitialStateIterator::on_light(light::LightState *light) { return this->client_->send_light_state(light); }
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool InitialStateIterator::on_sensor(sensor::Sensor *sensor) {
|
||||
return this->client_->send_sensor_state(sensor, sensor->state);
|
||||
}
|
||||
bool InitialStateIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_state(sensor); }
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool InitialStateIterator::on_switch(switch_::Switch *a_switch) {
|
||||
return this->client_->send_switch_state(a_switch, a_switch->state);
|
||||
}
|
||||
bool InitialStateIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_state(a_switch); }
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool InitialStateIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
|
||||
return this->client_->send_text_sensor_state(text_sensor, text_sensor->state);
|
||||
return this->client_->send_text_sensor_state(text_sensor);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); }
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool InitialStateIterator::on_number(number::Number *number) {
|
||||
return this->client_->send_number_state(number, number->state);
|
||||
}
|
||||
bool InitialStateIterator::on_number(number::Number *number) { return this->client_->send_number_state(number); }
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool InitialStateIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_state(date); }
|
||||
@@ -55,15 +49,13 @@ bool InitialStateIterator::on_datetime(datetime::DateTimeEntity *datetime) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); }
|
||||
bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text); }
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool InitialStateIterator::on_select(select::Select *select) {
|
||||
return this->client_->send_select_state(select, select->state);
|
||||
}
|
||||
bool InitialStateIterator::on_select(select::Select *select) { return this->client_->send_select_state(select); }
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); }
|
||||
bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock); }
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
bool InitialStateIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_state(valve); }
|
||||
|
||||
@@ -75,7 +75,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||
resp.data.reserve(param->read.value_len);
|
||||
// Use bulk insert instead of individual push_backs
|
||||
resp.data.insert(resp.data.end(), param->read.value, param->read.value + param->read.value_len);
|
||||
this->proxy_->get_api_connection()->send_bluetooth_gatt_read_response(resp);
|
||||
this->proxy_->get_api_connection()->send_message(resp);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_WRITE_CHAR_EVT:
|
||||
@@ -89,7 +89,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||
api::BluetoothGATTWriteResponse resp;
|
||||
resp.address = this->address_;
|
||||
resp.handle = param->write.handle;
|
||||
this->proxy_->get_api_connection()->send_bluetooth_gatt_write_response(resp);
|
||||
this->proxy_->get_api_connection()->send_message(resp);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
|
||||
@@ -103,7 +103,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||
api::BluetoothGATTNotifyResponse resp;
|
||||
resp.address = this->address_;
|
||||
resp.handle = param->unreg_for_notify.handle;
|
||||
this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_response(resp);
|
||||
this->proxy_->get_api_connection()->send_message(resp);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
@@ -116,7 +116,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||
api::BluetoothGATTNotifyResponse resp;
|
||||
resp.address = this->address_;
|
||||
resp.handle = param->reg_for_notify.handle;
|
||||
this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_response(resp);
|
||||
this->proxy_->get_api_connection()->send_message(resp);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
@@ -128,7 +128,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
||||
resp.data.reserve(param->notify.value_len);
|
||||
// Use bulk insert instead of individual push_backs
|
||||
resp.data.insert(resp.data.end(), param->notify.value, param->notify.value + param->notify.value_len);
|
||||
this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_data_response(resp);
|
||||
this->proxy_->get_api_connection()->send_message(resp);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -39,7 +39,7 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
|
||||
resp.state = static_cast<api::enums::BluetoothScannerState>(state);
|
||||
resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
|
||||
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
|
||||
this->api_connection_->send_bluetooth_scanner_state_response(resp);
|
||||
this->api_connection_->send_message(resp);
|
||||
}
|
||||
|
||||
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
@@ -103,7 +103,7 @@ void BluetoothProxy::flush_pending_advertisements() {
|
||||
|
||||
api::BluetoothLERawAdvertisementsResponse resp;
|
||||
resp.advertisements.swap(batch_buffer);
|
||||
this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
|
||||
this->api_connection_->send_message(resp);
|
||||
}
|
||||
|
||||
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
@@ -141,7 +141,7 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
|
||||
manufacturer_data.data.assign(data.data.begin(), data.data.end());
|
||||
}
|
||||
|
||||
this->api_connection_->send_bluetooth_le_advertisement(resp);
|
||||
this->api_connection_->send_message(resp);
|
||||
}
|
||||
|
||||
void BluetoothProxy::dump_config() {
|
||||
@@ -302,7 +302,7 @@ void BluetoothProxy::loop() {
|
||||
service_resp.characteristics.push_back(std::move(characteristic_resp));
|
||||
}
|
||||
resp.services.push_back(std::move(service_resp));
|
||||
this->api_connection_->send_bluetooth_gatt_get_services_response(resp);
|
||||
this->api_connection_->send_message(resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,7 +455,7 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
||||
call.success = ret == ESP_OK;
|
||||
call.error = ret;
|
||||
|
||||
this->api_connection_->send_bluetooth_device_clear_cache_response(call);
|
||||
this->api_connection_->send_message(call);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -579,7 +579,7 @@ void BluetoothProxy::send_device_connection(uint64_t address, bool connected, ui
|
||||
call.connected = connected;
|
||||
call.mtu = mtu;
|
||||
call.error = error;
|
||||
this->api_connection_->send_bluetooth_device_connection_response(call);
|
||||
this->api_connection_->send_message(call);
|
||||
}
|
||||
void BluetoothProxy::send_connections_free() {
|
||||
if (this->api_connection_ == nullptr)
|
||||
@@ -592,7 +592,7 @@ void BluetoothProxy::send_connections_free() {
|
||||
call.allocated.push_back(connection->address_);
|
||||
}
|
||||
}
|
||||
this->api_connection_->send_bluetooth_connections_free_response(call);
|
||||
this->api_connection_->send_message(call);
|
||||
}
|
||||
|
||||
void BluetoothProxy::send_gatt_services_done(uint64_t address) {
|
||||
@@ -600,7 +600,7 @@ void BluetoothProxy::send_gatt_services_done(uint64_t address) {
|
||||
return;
|
||||
api::BluetoothGATTGetServicesDoneResponse call;
|
||||
call.address = address;
|
||||
this->api_connection_->send_bluetooth_gatt_get_services_done_response(call);
|
||||
this->api_connection_->send_message(call);
|
||||
}
|
||||
|
||||
void BluetoothProxy::send_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
|
||||
@@ -610,7 +610,7 @@ void BluetoothProxy::send_gatt_error(uint64_t address, uint16_t handle, esp_err_
|
||||
call.address = address;
|
||||
call.handle = handle;
|
||||
call.error = error;
|
||||
this->api_connection_->send_bluetooth_gatt_error_response(call);
|
||||
this->api_connection_->send_message(call);
|
||||
}
|
||||
|
||||
void BluetoothProxy::send_device_pairing(uint64_t address, bool paired, esp_err_t error) {
|
||||
@@ -619,7 +619,7 @@ void BluetoothProxy::send_device_pairing(uint64_t address, bool paired, esp_err_
|
||||
call.paired = paired;
|
||||
call.error = error;
|
||||
|
||||
this->api_connection_->send_bluetooth_device_pairing_response(call);
|
||||
this->api_connection_->send_message(call);
|
||||
}
|
||||
|
||||
void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_err_t error) {
|
||||
@@ -628,7 +628,7 @@ void BluetoothProxy::send_device_unpairing(uint64_t address, bool success, esp_e
|
||||
call.success = success;
|
||||
call.error = error;
|
||||
|
||||
this->api_connection_->send_bluetooth_device_unpairing_response(call);
|
||||
this->api_connection_->send_message(call);
|
||||
}
|
||||
|
||||
void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
|
||||
|
||||
@@ -223,7 +223,7 @@ void VoiceAssistant::loop() {
|
||||
msg.wake_word_phrase = this->wake_word_;
|
||||
this->wake_word_ = "";
|
||||
|
||||
if (this->api_client_ == nullptr || !this->api_client_->send_voice_assistant_request(msg)) {
|
||||
if (this->api_client_ == nullptr || !this->api_client_->send_message(msg)) {
|
||||
ESP_LOGW(TAG, "Could not request start");
|
||||
this->error_trigger_->trigger("not-connected", "Could not request start");
|
||||
this->continuous_ = false;
|
||||
@@ -245,7 +245,7 @@ void VoiceAssistant::loop() {
|
||||
if (this->audio_mode_ == AUDIO_MODE_API) {
|
||||
api::VoiceAssistantAudio msg;
|
||||
msg.data.assign((char *) this->send_buffer_, read_bytes);
|
||||
this->api_client_->send_voice_assistant_audio(msg);
|
||||
this->api_client_->send_message(msg);
|
||||
} else {
|
||||
if (!this->udp_socket_running_) {
|
||||
if (!this->start_udp_socket_()) {
|
||||
@@ -331,7 +331,7 @@ void VoiceAssistant::loop() {
|
||||
|
||||
api::VoiceAssistantAnnounceFinished msg;
|
||||
msg.success = true;
|
||||
this->api_client_->send_voice_assistant_announce_finished(msg);
|
||||
this->api_client_->send_message(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -580,7 +580,7 @@ void VoiceAssistant::signal_stop_() {
|
||||
ESP_LOGD(TAG, "Signaling stop");
|
||||
api::VoiceAssistantRequest msg;
|
||||
msg.start = false;
|
||||
this->api_client_->send_voice_assistant_request(msg);
|
||||
this->api_client_->send_message(msg);
|
||||
}
|
||||
|
||||
void VoiceAssistant::start_playback_timeout_() {
|
||||
@@ -590,7 +590,7 @@ void VoiceAssistant::start_playback_timeout_() {
|
||||
|
||||
api::VoiceAssistantAnnounceFinished msg;
|
||||
msg.success = true;
|
||||
this->api_client_->send_voice_assistant_announce_finished(msg);
|
||||
this->api_client_->send_message(msg);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -258,6 +258,14 @@ class TypeInfo(ABC):
|
||||
force: Whether to force encoding the field even if it has a default value
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_estimated_size(self) -> int:
|
||||
"""Get estimated size in bytes for this field with typical values.
|
||||
|
||||
Returns:
|
||||
Estimated size in bytes including field ID and typical data
|
||||
"""
|
||||
|
||||
|
||||
TYPE_INFO: dict[int, TypeInfo] = {}
|
||||
|
||||
@@ -291,6 +299,9 @@ class DoubleType(TypeInfo):
|
||||
o = f"ProtoSize::add_fixed_field<8>(total_size, {field_id_size}, {name} != 0.0, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 8 # field ID + 8 bytes for double
|
||||
|
||||
|
||||
@register_type(2)
|
||||
class FloatType(TypeInfo):
|
||||
@@ -310,6 +321,9 @@ class FloatType(TypeInfo):
|
||||
o = f"ProtoSize::add_fixed_field<4>(total_size, {field_id_size}, {name} != 0.0f, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 4 # field ID + 4 bytes for float
|
||||
|
||||
|
||||
@register_type(3)
|
||||
class Int64Type(TypeInfo):
|
||||
@@ -329,6 +343,9 @@ class Int64Type(TypeInfo):
|
||||
o = f"ProtoSize::add_int64_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
||||
|
||||
|
||||
@register_type(4)
|
||||
class UInt64Type(TypeInfo):
|
||||
@@ -348,6 +365,9 @@ class UInt64Type(TypeInfo):
|
||||
o = f"ProtoSize::add_uint64_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
||||
|
||||
|
||||
@register_type(5)
|
||||
class Int32Type(TypeInfo):
|
||||
@@ -367,6 +387,9 @@ class Int32Type(TypeInfo):
|
||||
o = f"ProtoSize::add_int32_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
||||
|
||||
|
||||
@register_type(6)
|
||||
class Fixed64Type(TypeInfo):
|
||||
@@ -386,6 +409,9 @@ class Fixed64Type(TypeInfo):
|
||||
o = f"ProtoSize::add_fixed_field<8>(total_size, {field_id_size}, {name} != 0, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 8 # field ID + 8 bytes fixed
|
||||
|
||||
|
||||
@register_type(7)
|
||||
class Fixed32Type(TypeInfo):
|
||||
@@ -405,6 +431,9 @@ class Fixed32Type(TypeInfo):
|
||||
o = f"ProtoSize::add_fixed_field<4>(total_size, {field_id_size}, {name} != 0, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 4 # field ID + 4 bytes fixed
|
||||
|
||||
|
||||
@register_type(8)
|
||||
class BoolType(TypeInfo):
|
||||
@@ -423,6 +452,9 @@ class BoolType(TypeInfo):
|
||||
o = f"ProtoSize::add_bool_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 1 # field ID + 1 byte
|
||||
|
||||
|
||||
@register_type(9)
|
||||
class StringType(TypeInfo):
|
||||
@@ -443,6 +475,9 @@ class StringType(TypeInfo):
|
||||
o = f"ProtoSize::add_string_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string
|
||||
|
||||
|
||||
@register_type(11)
|
||||
class MessageType(TypeInfo):
|
||||
@@ -478,6 +513,11 @@ class MessageType(TypeInfo):
|
||||
o = f"ProtoSize::add_message_object(total_size, {field_id_size}, {name}, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return (
|
||||
self.calculate_field_id_size() + 16
|
||||
) # field ID + 16 bytes estimated submessage
|
||||
|
||||
|
||||
@register_type(12)
|
||||
class BytesType(TypeInfo):
|
||||
@@ -498,6 +538,9 @@ class BytesType(TypeInfo):
|
||||
o = f"ProtoSize::add_string_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes
|
||||
|
||||
|
||||
@register_type(13)
|
||||
class UInt32Type(TypeInfo):
|
||||
@@ -517,6 +560,9 @@ class UInt32Type(TypeInfo):
|
||||
o = f"ProtoSize::add_uint32_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
||||
|
||||
|
||||
@register_type(14)
|
||||
class EnumType(TypeInfo):
|
||||
@@ -544,6 +590,9 @@ class EnumType(TypeInfo):
|
||||
o = f"ProtoSize::add_enum_field(total_size, {field_id_size}, static_cast<uint32_t>({name}), {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 1 # field ID + 1 byte typical enum
|
||||
|
||||
|
||||
@register_type(15)
|
||||
class SFixed32Type(TypeInfo):
|
||||
@@ -563,6 +612,9 @@ class SFixed32Type(TypeInfo):
|
||||
o = f"ProtoSize::add_fixed_field<4>(total_size, {field_id_size}, {name} != 0, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 4 # field ID + 4 bytes fixed
|
||||
|
||||
|
||||
@register_type(16)
|
||||
class SFixed64Type(TypeInfo):
|
||||
@@ -582,6 +634,9 @@ class SFixed64Type(TypeInfo):
|
||||
o = f"ProtoSize::add_fixed_field<8>(total_size, {field_id_size}, {name} != 0, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 8 # field ID + 8 bytes fixed
|
||||
|
||||
|
||||
@register_type(17)
|
||||
class SInt32Type(TypeInfo):
|
||||
@@ -601,6 +656,9 @@ class SInt32Type(TypeInfo):
|
||||
o = f"ProtoSize::add_sint32_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
||||
|
||||
|
||||
@register_type(18)
|
||||
class SInt64Type(TypeInfo):
|
||||
@@ -620,6 +678,9 @@ class SInt64Type(TypeInfo):
|
||||
o = f"ProtoSize::add_sint64_field(total_size, {field_id_size}, {name}, {force_str(force)});"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
|
||||
|
||||
|
||||
class RepeatedTypeInfo(TypeInfo):
|
||||
def __init__(self, field: descriptor.FieldDescriptorProto) -> None:
|
||||
@@ -738,6 +799,15 @@ class RepeatedTypeInfo(TypeInfo):
|
||||
o += "}"
|
||||
return o
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
# For repeated fields, estimate underlying type size * 2 (assume 2 items typically)
|
||||
underlying_size = (
|
||||
self._ti.get_estimated_size()
|
||||
if hasattr(self._ti, "get_estimated_size")
|
||||
else 8
|
||||
)
|
||||
return underlying_size * 2
|
||||
|
||||
|
||||
def build_enum_type(desc) -> tuple[str, str]:
|
||||
"""Builds the enum type."""
|
||||
@@ -762,6 +832,22 @@ def build_enum_type(desc) -> tuple[str, str]:
|
||||
return out, cpp
|
||||
|
||||
|
||||
def calculate_message_estimated_size(desc: descriptor.DescriptorProto) -> int:
|
||||
"""Calculate estimated size for a complete message based on typical values."""
|
||||
total_size = 0
|
||||
|
||||
for field in desc.field:
|
||||
if field.label == 3: # repeated
|
||||
ti = RepeatedTypeInfo(field)
|
||||
else:
|
||||
ti = TYPE_INFO[field.type](field)
|
||||
|
||||
# Add estimated size for this field
|
||||
total_size += ti.get_estimated_size()
|
||||
|
||||
return total_size
|
||||
|
||||
|
||||
def build_message_type(desc: descriptor.DescriptorProto) -> tuple[str, str]:
|
||||
public_content: list[str] = []
|
||||
protected_content: list[str] = []
|
||||
@@ -773,6 +859,28 @@ def build_message_type(desc: descriptor.DescriptorProto) -> tuple[str, str]:
|
||||
dump: list[str] = []
|
||||
size_calc: list[str] = []
|
||||
|
||||
# Get message ID if it's a service message
|
||||
message_id: int | None = get_opt(desc, pb.id)
|
||||
|
||||
# Add MESSAGE_TYPE method if this is a service message
|
||||
if message_id is not None:
|
||||
# Add static constexpr for message type
|
||||
public_content.append(f"static constexpr uint16_t MESSAGE_TYPE = {message_id};")
|
||||
|
||||
# Add estimated size constant
|
||||
estimated_size = calculate_message_estimated_size(desc)
|
||||
public_content.append(
|
||||
f"static constexpr uint16_t ESTIMATED_SIZE = {estimated_size};"
|
||||
)
|
||||
|
||||
# Add message_name method for debugging
|
||||
public_content.append("#ifdef HAS_PROTO_MESSAGE_DUMP")
|
||||
snake_name = camel_to_snake(desc.name)
|
||||
public_content.append(
|
||||
f'static constexpr const char *message_name() {{ return "{snake_name}"; }}'
|
||||
)
|
||||
public_content.append("#endif")
|
||||
|
||||
for field in desc.field:
|
||||
if field.label == 3:
|
||||
ti = RepeatedTypeInfo(field)
|
||||
@@ -941,24 +1049,18 @@ def build_service_message_type(
|
||||
hout = ""
|
||||
cout = ""
|
||||
|
||||
# Store ifdef for later use
|
||||
if ifdef is not None:
|
||||
ifdefs[str(mt.name)] = ifdef
|
||||
hout += f"#ifdef {ifdef}\n"
|
||||
cout += f"#ifdef {ifdef}\n"
|
||||
|
||||
if source in (SOURCE_BOTH, SOURCE_SERVER):
|
||||
# Generate send
|
||||
func = f"send_{snake}"
|
||||
hout += f"bool {func}(const {mt.name} &msg);\n"
|
||||
cout += f"bool APIServerConnectionBase::{func}(const {mt.name} &msg) {{\n"
|
||||
if log:
|
||||
cout += "#ifdef HAS_PROTO_MESSAGE_DUMP\n"
|
||||
cout += f' ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n'
|
||||
cout += "#endif\n"
|
||||
# cout += f' this->set_nodelay({str(nodelay).lower()});\n'
|
||||
cout += f" return this->send_message_<{mt.name}>(msg, {id_});\n"
|
||||
cout += "}\n"
|
||||
# Don't generate individual send methods anymore
|
||||
# The generic send_message method will be used instead
|
||||
pass
|
||||
if source in (SOURCE_BOTH, SOURCE_CLIENT):
|
||||
# Only add ifdef when we're actually generating content
|
||||
if ifdef is not None:
|
||||
hout += f"#ifdef {ifdef}\n"
|
||||
# Generate receive
|
||||
func = f"on_{snake}"
|
||||
hout += f"virtual void {func}(const {mt.name} &value){{}};\n"
|
||||
@@ -977,9 +1079,9 @@ def build_service_message_type(
|
||||
case += "break;"
|
||||
RECEIVE_CASES[id_] = case
|
||||
|
||||
if ifdef is not None:
|
||||
hout += "#endif\n"
|
||||
cout += "#endif\n"
|
||||
# Only close ifdef if we opened it
|
||||
if ifdef is not None:
|
||||
hout += "#endif\n"
|
||||
|
||||
return hout, cout
|
||||
|
||||
@@ -1083,6 +1185,29 @@ def main() -> None:
|
||||
hpp += f"class {class_name} : public ProtoService {{\n"
|
||||
hpp += " public:\n"
|
||||
|
||||
# Add logging helper method declaration
|
||||
hpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n"
|
||||
hpp += " protected:\n"
|
||||
hpp += " void log_send_message_(const char *name, const std::string &dump);\n"
|
||||
hpp += " public:\n"
|
||||
hpp += "#endif\n\n"
|
||||
|
||||
# Add generic send_message method
|
||||
hpp += " template<typename T>\n"
|
||||
hpp += " bool send_message(const T &msg) {\n"
|
||||
hpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n"
|
||||
hpp += " this->log_send_message_(T::message_name(), msg.dump());\n"
|
||||
hpp += "#endif\n"
|
||||
hpp += " return this->send_message_(msg, T::MESSAGE_TYPE);\n"
|
||||
hpp += " }\n\n"
|
||||
|
||||
# Add logging helper method implementation to cpp
|
||||
cpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n"
|
||||
cpp += f"void {class_name}::log_send_message_(const char *name, const std::string &dump) {{\n"
|
||||
cpp += ' ESP_LOGVV(TAG, "send_message %s: %s", name, dump.c_str());\n'
|
||||
cpp += "}\n"
|
||||
cpp += "#endif\n\n"
|
||||
|
||||
for mt in file.message_type:
|
||||
obj = build_service_message_type(mt)
|
||||
if obj is None:
|
||||
@@ -1155,8 +1280,7 @@ def main() -> None:
|
||||
body += f"this->{func}(msg);\n"
|
||||
else:
|
||||
body += f"{ret} ret = this->{func}(msg);\n"
|
||||
ret_snake = camel_to_snake(ret)
|
||||
body += f"if (!this->send_{ret_snake}(ret)) {{\n"
|
||||
body += "if (!this->send_message(ret)) {\n"
|
||||
body += " this->on_fatal_error();\n"
|
||||
body += "}\n"
|
||||
cpp += indent(body) + "\n" + "}\n"
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
esphome:
|
||||
name: host-batch-delay-test
|
||||
host:
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
logger:
|
||||
|
||||
# Add multiple sensors to test batching
|
||||
sensor:
|
||||
- platform: template
|
||||
name: "Test Sensor 1"
|
||||
id: test_sensor1
|
||||
lambda: |-
|
||||
return 1.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 2"
|
||||
id: test_sensor2
|
||||
lambda: |-
|
||||
return 2.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 3"
|
||||
id: test_sensor3
|
||||
lambda: |-
|
||||
return 3.0;
|
||||
update_interval: 0.1s
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
name: "Test Binary Sensor 1"
|
||||
id: test_binary_sensor1
|
||||
lambda: |-
|
||||
return millis() % 1000 < 500;
|
||||
- platform: template
|
||||
name: "Test Binary Sensor 2"
|
||||
id: test_binary_sensor2
|
||||
lambda: |-
|
||||
return millis() % 2000 < 1000;
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
name: "Test Switch 1"
|
||||
id: test_switch1
|
||||
turn_on_action:
|
||||
- logger.log: "Switch 1 turned on"
|
||||
turn_off_action:
|
||||
- logger.log: "Switch 1 turned off"
|
||||
- platform: template
|
||||
name: "Test Switch 2"
|
||||
id: test_switch2
|
||||
turn_on_action:
|
||||
- logger.log: "Switch 2 turned on"
|
||||
turn_off_action:
|
||||
- logger.log: "Switch 2 turned off"
|
||||
@@ -0,0 +1,322 @@
|
||||
esphome:
|
||||
name: host-mode-many-entities
|
||||
friendly_name: "Host Mode Many Entities Test"
|
||||
|
||||
logger:
|
||||
|
||||
host:
|
||||
|
||||
api:
|
||||
|
||||
sensor:
|
||||
# 50 test sensors with predictable values for batching test
|
||||
- platform: template
|
||||
name: "Test Sensor 1"
|
||||
lambda: return 1.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 2"
|
||||
lambda: return 2.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 3"
|
||||
lambda: return 3.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 4"
|
||||
lambda: return 4.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 5"
|
||||
lambda: return 5.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 6"
|
||||
lambda: return 6.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 7"
|
||||
lambda: return 7.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 8"
|
||||
lambda: return 8.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 9"
|
||||
lambda: return 9.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 10"
|
||||
lambda: return 10.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 11"
|
||||
lambda: return 11.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 12"
|
||||
lambda: return 12.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 13"
|
||||
lambda: return 13.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 14"
|
||||
lambda: return 14.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 15"
|
||||
lambda: return 15.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 16"
|
||||
lambda: return 16.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 17"
|
||||
lambda: return 17.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 18"
|
||||
lambda: return 18.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 19"
|
||||
lambda: return 19.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 20"
|
||||
lambda: return 20.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 21"
|
||||
lambda: return 21.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 22"
|
||||
lambda: return 22.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 23"
|
||||
lambda: return 23.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 24"
|
||||
lambda: return 24.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 25"
|
||||
lambda: return 25.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 26"
|
||||
lambda: return 26.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 27"
|
||||
lambda: return 27.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 28"
|
||||
lambda: return 28.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 29"
|
||||
lambda: return 29.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 30"
|
||||
lambda: return 30.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 31"
|
||||
lambda: return 31.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 32"
|
||||
lambda: return 32.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 33"
|
||||
lambda: return 33.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 34"
|
||||
lambda: return 34.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 35"
|
||||
lambda: return 35.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 36"
|
||||
lambda: return 36.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 37"
|
||||
lambda: return 37.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 38"
|
||||
lambda: return 38.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 39"
|
||||
lambda: return 39.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 40"
|
||||
lambda: return 40.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 41"
|
||||
lambda: return 41.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 42"
|
||||
lambda: return 42.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 43"
|
||||
lambda: return 43.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 44"
|
||||
lambda: return 44.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 45"
|
||||
lambda: return 45.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 46"
|
||||
lambda: return 46.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 47"
|
||||
lambda: return 47.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 48"
|
||||
lambda: return 48.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 49"
|
||||
lambda: return 49.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 50"
|
||||
lambda: return 50.0;
|
||||
update_interval: 0.1s
|
||||
|
||||
# Mixed entity types for comprehensive batching test
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
name: "Test Binary Sensor 1"
|
||||
lambda: return millis() % 1000 < 500;
|
||||
- platform: template
|
||||
name: "Test Binary Sensor 2"
|
||||
lambda: return millis() % 2000 < 1000;
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
name: "Test Switch 1"
|
||||
lambda: return true;
|
||||
turn_on_action:
|
||||
- logger.log: "Switch 1 ON"
|
||||
turn_off_action:
|
||||
- logger.log: "Switch 1 OFF"
|
||||
- platform: template
|
||||
name: "Test Switch 2"
|
||||
lambda: return false;
|
||||
turn_on_action:
|
||||
- logger.log: "Switch 2 ON"
|
||||
turn_off_action:
|
||||
- logger.log: "Switch 2 OFF"
|
||||
|
||||
text_sensor:
|
||||
- platform: template
|
||||
name: "Test Text Sensor 1"
|
||||
lambda: return std::string("Test Value 1");
|
||||
- platform: template
|
||||
name: "Test Text Sensor 2"
|
||||
lambda: return std::string("Test Value 2");
|
||||
- platform: version
|
||||
name: "ESPHome Version"
|
||||
|
||||
number:
|
||||
- platform: template
|
||||
name: "Test Number"
|
||||
min_value: 0
|
||||
max_value: 100
|
||||
step: 1
|
||||
lambda: return 50.0;
|
||||
set_action:
|
||||
- logger.log: "Number set"
|
||||
|
||||
select:
|
||||
- platform: template
|
||||
name: "Test Select"
|
||||
options:
|
||||
- "Option 1"
|
||||
- "Option 2"
|
||||
initial_option: "Option 1"
|
||||
optimistic: true
|
||||
set_action:
|
||||
- logger.log: "Select changed"
|
||||
|
||||
text:
|
||||
- platform: template
|
||||
name: "Test Text"
|
||||
mode: text
|
||||
initial_value: "Hello"
|
||||
set_action:
|
||||
- logger.log: "Text changed"
|
||||
|
||||
valve:
|
||||
- platform: template
|
||||
name: "Test Valve"
|
||||
open_action:
|
||||
- logger.log: "Valve opening"
|
||||
close_action:
|
||||
- logger.log: "Valve closing"
|
||||
stop_action:
|
||||
- logger.log: "Valve stopping"
|
||||
|
||||
alarm_control_panel:
|
||||
- platform: template
|
||||
name: "Test Alarm"
|
||||
codes:
|
||||
- "1234"
|
||||
arming_away_time: 0s
|
||||
arming_home_time: 0s
|
||||
pending_time: 0s
|
||||
trigger_time: 300s
|
||||
restore_mode: ALWAYS_DISARMED
|
||||
on_disarmed:
|
||||
- logger.log: "Alarm disarmed"
|
||||
on_arming:
|
||||
- logger.log: "Alarm arming"
|
||||
on_armed_away:
|
||||
- logger.log: "Alarm armed away"
|
||||
on_armed_home:
|
||||
- logger.log: "Alarm armed home"
|
||||
on_pending:
|
||||
- logger.log: "Alarm pending"
|
||||
on_triggered:
|
||||
- logger.log: "Alarm triggered"
|
||||
|
||||
event:
|
||||
- platform: template
|
||||
name: "Test Event"
|
||||
event_types:
|
||||
- first_event
|
||||
- second_event
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: "Test Button"
|
||||
on_press:
|
||||
- logger.log: "Button pressed"
|
||||
@@ -0,0 +1,136 @@
|
||||
esphome:
|
||||
name: host-mode-many-entities-multi
|
||||
friendly_name: "Host Mode Many Entities Multiple Connections Test"
|
||||
|
||||
logger:
|
||||
|
||||
host:
|
||||
|
||||
api:
|
||||
|
||||
sensor:
|
||||
# 20 test sensors for faster testing with multiple connections
|
||||
- platform: template
|
||||
name: "Test Sensor 1"
|
||||
lambda: return 1.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 2"
|
||||
lambda: return 2.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 3"
|
||||
lambda: return 3.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 4"
|
||||
lambda: return 4.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 5"
|
||||
lambda: return 5.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 6"
|
||||
lambda: return 6.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 7"
|
||||
lambda: return 7.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 8"
|
||||
lambda: return 8.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 9"
|
||||
lambda: return 9.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 10"
|
||||
lambda: return 10.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 11"
|
||||
lambda: return 11.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 12"
|
||||
lambda: return 12.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 13"
|
||||
lambda: return 13.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 14"
|
||||
lambda: return 14.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 15"
|
||||
lambda: return 15.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 16"
|
||||
lambda: return 16.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 17"
|
||||
lambda: return 17.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 18"
|
||||
lambda: return 18.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 19"
|
||||
lambda: return 19.0;
|
||||
update_interval: 0.1s
|
||||
- platform: template
|
||||
name: "Test Sensor 20"
|
||||
lambda: return 20.0;
|
||||
update_interval: 0.1s
|
||||
|
||||
# Mixed entity types for comprehensive batching test
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
name: "Test Binary Sensor 1"
|
||||
lambda: return millis() % 1000 < 500;
|
||||
- platform: template
|
||||
name: "Test Binary Sensor 2"
|
||||
lambda: return millis() % 2000 < 1000;
|
||||
|
||||
text_sensor:
|
||||
- platform: template
|
||||
name: "Test Text Sensor 1"
|
||||
lambda: return std::string("Test Value 1");
|
||||
- platform: template
|
||||
name: "Test Text Sensor 2"
|
||||
lambda: return std::string("Test Value 2");
|
||||
- platform: version
|
||||
name: "ESPHome Version"
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
name: "Test Switch 1"
|
||||
lambda: return true;
|
||||
turn_on_action:
|
||||
- logger.log: "Switch 1 ON"
|
||||
turn_off_action:
|
||||
- logger.log: "Switch 1 OFF"
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: "Test Button"
|
||||
on_press:
|
||||
- logger.log: "Button pressed"
|
||||
|
||||
number:
|
||||
- platform: template
|
||||
name: "Test Number"
|
||||
min_value: 0
|
||||
max_value: 100
|
||||
step: 1
|
||||
lambda: return 50.0;
|
||||
set_action:
|
||||
- logger.log: "Number set"
|
||||
@@ -5,3 +5,46 @@ api:
|
||||
encryption:
|
||||
key: N4Yle5YirwZhPiHHsdZLdOA73ndj/84veVaLhTvxCuU=
|
||||
logger:
|
||||
|
||||
# Test sensors to verify batching works with noise encryption
|
||||
sensor:
|
||||
- platform: template
|
||||
name: "Noise Test Sensor 1"
|
||||
lambda: return 1.0;
|
||||
update_interval: 2s
|
||||
- platform: template
|
||||
name: "Noise Test Sensor 2"
|
||||
lambda: return 2.0;
|
||||
update_interval: 2s
|
||||
- platform: template
|
||||
name: "Noise Test Sensor 3"
|
||||
lambda: return 3.0;
|
||||
update_interval: 2s
|
||||
- platform: template
|
||||
name: "Noise Test Sensor 4"
|
||||
lambda: return 4.0;
|
||||
update_interval: 2s
|
||||
- platform: template
|
||||
name: "Noise Test Sensor 5"
|
||||
lambda: return 5.0;
|
||||
update_interval: 2s
|
||||
- platform: template
|
||||
name: "Noise Test Sensor 6"
|
||||
lambda: return 6.0;
|
||||
update_interval: 2s
|
||||
- platform: template
|
||||
name: "Noise Test Sensor 7"
|
||||
lambda: return 7.0;
|
||||
update_interval: 2s
|
||||
- platform: template
|
||||
name: "Noise Test Sensor 8"
|
||||
lambda: return 8.0;
|
||||
update_interval: 2s
|
||||
- platform: template
|
||||
name: "Noise Test Sensor 9"
|
||||
lambda: return 9.0;
|
||||
update_interval: 2s
|
||||
- platform: template
|
||||
name: "Noise Test Sensor 10"
|
||||
lambda: return 10.0;
|
||||
update_interval: 2s
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Integration test for API batch_delay setting."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
from aioesphomeapi import EntityState
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_host_mode_batch_delay(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test API with batch_delay set to 0ms - messages should be sent immediately without batching."""
|
||||
# Write, compile and run the ESPHome device, then connect to API
|
||||
loop = asyncio.get_running_loop()
|
||||
async with run_compiled(yaml_config), api_client_connected() as client:
|
||||
# Verify we can get device info
|
||||
device_info = await client.device_info()
|
||||
assert device_info is not None
|
||||
assert device_info.name == "host-batch-delay-test"
|
||||
|
||||
# Subscribe to state changes
|
||||
states: dict[int, EntityState] = {}
|
||||
state_timestamps: dict[int, float] = {}
|
||||
entity_count_future: asyncio.Future[int] = loop.create_future()
|
||||
|
||||
def on_state(state: EntityState) -> None:
|
||||
"""Track when states are received."""
|
||||
states[state.key] = state
|
||||
state_timestamps[state.key] = time.monotonic()
|
||||
# When we have received all expected entities, resolve the future
|
||||
if len(states) >= 7 and not entity_count_future.done():
|
||||
entity_count_future.set_result(len(states))
|
||||
|
||||
client.subscribe_states(on_state)
|
||||
|
||||
# Wait for states from all entities with timeout
|
||||
try:
|
||||
entity_count = await asyncio.wait_for(entity_count_future, timeout=5.0)
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail(
|
||||
f"Did not receive states from at least 7 entities within 5 seconds. "
|
||||
f"Received {len(states)} states"
|
||||
)
|
||||
|
||||
# Verify we received all states
|
||||
assert entity_count >= 7, f"Expected at least 7 entities, got {entity_count}"
|
||||
assert len(states) >= 7 # 3 sensors + 2 binary sensors + 2 switches
|
||||
|
||||
# When batch_delay is 0ms, states are sent immediately without batching
|
||||
# This means each state arrives in its own packet, which may actually be slower
|
||||
# than batching due to network overhead
|
||||
if state_timestamps:
|
||||
first_timestamp = min(state_timestamps.values())
|
||||
last_timestamp = max(state_timestamps.values())
|
||||
time_spread = last_timestamp - first_timestamp
|
||||
|
||||
# With batch_delay=0ms, states arrive individually which may take longer
|
||||
# We just verify they all arrive within a reasonable time
|
||||
assert time_spread < 1.0, f"States took {time_spread:.3f}s to arrive"
|
||||
|
||||
# Also test list_entities - with batch_delay=0ms each entity is sent separately
|
||||
start_time = time.monotonic()
|
||||
entity_info, services = await client.list_entities_services()
|
||||
end_time = time.monotonic()
|
||||
|
||||
list_time = end_time - start_time
|
||||
|
||||
# Verify we got all expected entities
|
||||
assert len(entity_info) >= 7 # 3 sensors + 2 binary sensors + 2 switches
|
||||
|
||||
# With batch_delay=0ms, listing sends each entity separately which may be slower
|
||||
assert list_time < 1.0, f"list_entities took {list_time:.3f}s"
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Integration test for many entities to test API batching."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from aioesphomeapi import EntityState
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_host_mode_many_entities(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test API batching with many entities of different types."""
|
||||
# Write, compile and run the ESPHome device, then connect to API
|
||||
loop = asyncio.get_running_loop()
|
||||
async with run_compiled(yaml_config), api_client_connected() as client:
|
||||
# Subscribe to state changes
|
||||
states: dict[int, EntityState] = {}
|
||||
entity_count_future: asyncio.Future[int] = loop.create_future()
|
||||
|
||||
def on_state(state: EntityState) -> None:
|
||||
states[state.key] = state
|
||||
# When we have received states from a good number of entities, resolve the future
|
||||
if len(states) >= 50 and not entity_count_future.done():
|
||||
entity_count_future.set_result(len(states))
|
||||
|
||||
client.subscribe_states(on_state)
|
||||
|
||||
# Wait for states from at least 50 entities with timeout
|
||||
try:
|
||||
entity_count = await asyncio.wait_for(entity_count_future, timeout=10.0)
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail(
|
||||
f"Did not receive states from at least 50 entities within 10 seconds. "
|
||||
f"Received {len(states)} states: {list(states.keys())}"
|
||||
)
|
||||
|
||||
# Verify we received a good number of entity states
|
||||
assert entity_count >= 50, f"Expected at least 50 entities, got {entity_count}"
|
||||
assert len(states) >= 50, f"Expected at least 50 states, got {len(states)}"
|
||||
|
||||
# Verify we have different entity types by checking some expected values
|
||||
sensor_states = [
|
||||
s
|
||||
for s in states.values()
|
||||
if hasattr(s, "state") and isinstance(s.state, float)
|
||||
]
|
||||
|
||||
assert len(sensor_states) >= 50, (
|
||||
f"Expected at least 50 sensor states, got {len(sensor_states)}"
|
||||
)
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Integration test for shared buffer optimization with multiple API connections."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from aioesphomeapi import EntityState
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_host_mode_many_entities_multiple_connections(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test shared buffer optimization with multiple API connections."""
|
||||
# Write, compile and run the ESPHome device
|
||||
loop = asyncio.get_running_loop()
|
||||
async with (
|
||||
run_compiled(yaml_config),
|
||||
api_client_connected() as client1,
|
||||
api_client_connected() as client2,
|
||||
):
|
||||
# Subscribe both clients to state changes
|
||||
states1: dict[int, EntityState] = {}
|
||||
states2: dict[int, EntityState] = {}
|
||||
|
||||
client1_ready = loop.create_future()
|
||||
client2_ready = loop.create_future()
|
||||
|
||||
def on_state1(state: EntityState) -> None:
|
||||
states1[state.key] = state
|
||||
if len(states1) >= 20 and not client1_ready.done():
|
||||
client1_ready.set_result(len(states1))
|
||||
|
||||
def on_state2(state: EntityState) -> None:
|
||||
states2[state.key] = state
|
||||
if len(states2) >= 20 and not client2_ready.done():
|
||||
client2_ready.set_result(len(states2))
|
||||
|
||||
client1.subscribe_states(on_state1)
|
||||
client2.subscribe_states(on_state2)
|
||||
|
||||
# Wait for both clients to receive states
|
||||
try:
|
||||
count1, count2 = await asyncio.gather(
|
||||
asyncio.wait_for(client1_ready, timeout=10.0),
|
||||
asyncio.wait_for(client2_ready, timeout=10.0),
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail(
|
||||
f"One or both clients did not receive enough states within 10 seconds. "
|
||||
f"Client1: {len(states1)}, Client2: {len(states2)}"
|
||||
)
|
||||
|
||||
# Verify both clients received states successfully
|
||||
assert count1 >= 20, (
|
||||
f"Client 1 should have received at least 20 states, got {count1}"
|
||||
)
|
||||
assert count2 >= 20, (
|
||||
f"Client 2 should have received at least 20 states, got {count2}"
|
||||
)
|
||||
|
||||
# Verify both clients received the same entity keys (same device state)
|
||||
common_keys = set(states1.keys()) & set(states2.keys())
|
||||
assert len(common_keys) >= 20, (
|
||||
f"Expected at least 20 common entity keys, got {len(common_keys)}"
|
||||
)
|
||||
Reference in New Issue
Block a user