Compare commits
56 Commits
2025.6.0b2
...
ble_events
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14efa3efe0 | ||
|
|
a8eb3f7961 | ||
|
|
b16b371f85 | ||
|
|
0877b3e2af | ||
|
|
deee67b331 | ||
|
|
15a8a164cb | ||
|
|
dac738a916 | ||
|
|
99a54369bf | ||
|
|
261b561bb2 | ||
|
|
f7533dfc5c | ||
|
|
0228379a2e | ||
|
|
da79215bc3 | ||
|
|
ee7d95272d | ||
|
|
2b9b1d12e6 | ||
|
|
2cbb5c7d8e | ||
|
|
9686c7babe | ||
|
|
a59e1c7011 | ||
|
|
66bd4c96c4 | ||
|
|
dc47faa4b6 | ||
|
|
55ee0b116d | ||
|
|
c6957c08bc | ||
|
|
8fe6a323d8 | ||
|
|
8e51590c32 | ||
|
|
ae066d5627 | ||
|
|
6760279916 | ||
|
|
3c208050b0 | ||
|
|
bbc7c9fb37 | ||
|
|
e1c3862586 | ||
|
|
c24b7cb7bd | ||
|
|
c91e16549d | ||
|
|
6e70aca458 | ||
|
|
d9ffd0ac8e | ||
|
|
4641f73d19 | ||
|
|
9f0051c21f | ||
|
|
0331cb09e8 | ||
|
|
2f8946f86c | ||
|
|
88a3df4008 | ||
|
|
0adf514bd6 | ||
|
|
a1b5a2abcb | ||
|
|
068c62c6fe | ||
|
|
0e9f14f969 | ||
|
|
78315fd388 | ||
|
|
0ab69002df | ||
|
|
1eec1239ec | ||
|
|
f467c79a20 | ||
|
|
98a2f23024 | ||
|
|
c955897d1b | ||
|
|
cfdb0925ce | ||
|
|
83db3eddd9 | ||
|
|
cc2c5a544e | ||
|
|
8fba8c2800 | ||
|
|
51d1da8460 | ||
|
|
2f1257056d | ||
|
|
2f8f6967bf | ||
|
|
246527e618 | ||
|
|
3857cc9c83 |
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.6.0b2
|
||||
PROJECT_NUMBER = 2025.7.0-dev
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -266,7 +266,6 @@ enum EntityCategory {
|
||||
// ==================== BINARY SENSOR ====================
|
||||
message ListEntitiesBinarySensorResponse {
|
||||
option (id) = 12;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BINARY_SENSOR";
|
||||
|
||||
@@ -283,7 +282,6 @@ message ListEntitiesBinarySensorResponse {
|
||||
}
|
||||
message BinarySensorStateResponse {
|
||||
option (id) = 21;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BINARY_SENSOR";
|
||||
option (no_delay) = true;
|
||||
@@ -298,7 +296,6 @@ message BinarySensorStateResponse {
|
||||
// ==================== COVER ====================
|
||||
message ListEntitiesCoverResponse {
|
||||
option (id) = 13;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_COVER";
|
||||
|
||||
@@ -328,7 +325,6 @@ enum CoverOperation {
|
||||
}
|
||||
message CoverStateResponse {
|
||||
option (id) = 22;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_COVER";
|
||||
option (no_delay) = true;
|
||||
@@ -371,7 +367,6 @@ message CoverCommandRequest {
|
||||
// ==================== FAN ====================
|
||||
message ListEntitiesFanResponse {
|
||||
option (id) = 14;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_FAN";
|
||||
|
||||
@@ -400,7 +395,6 @@ enum FanDirection {
|
||||
}
|
||||
message FanStateResponse {
|
||||
option (id) = 23;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_FAN";
|
||||
option (no_delay) = true;
|
||||
@@ -450,7 +444,6 @@ enum ColorMode {
|
||||
}
|
||||
message ListEntitiesLightResponse {
|
||||
option (id) = 15;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
|
||||
@@ -474,7 +467,6 @@ message ListEntitiesLightResponse {
|
||||
}
|
||||
message LightStateResponse {
|
||||
option (id) = 24;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LIGHT";
|
||||
option (no_delay) = true;
|
||||
@@ -544,7 +536,6 @@ enum SensorLastResetType {
|
||||
|
||||
message ListEntitiesSensorResponse {
|
||||
option (id) = 16;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SENSOR";
|
||||
|
||||
@@ -566,7 +557,6 @@ message ListEntitiesSensorResponse {
|
||||
}
|
||||
message SensorStateResponse {
|
||||
option (id) = 25;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SENSOR";
|
||||
option (no_delay) = true;
|
||||
@@ -581,7 +571,6 @@ message SensorStateResponse {
|
||||
// ==================== SWITCH ====================
|
||||
message ListEntitiesSwitchResponse {
|
||||
option (id) = 17;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
|
||||
@@ -598,7 +587,6 @@ message ListEntitiesSwitchResponse {
|
||||
}
|
||||
message SwitchStateResponse {
|
||||
option (id) = 26;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SWITCH";
|
||||
option (no_delay) = true;
|
||||
@@ -619,7 +607,6 @@ message SwitchCommandRequest {
|
||||
// ==================== TEXT SENSOR ====================
|
||||
message ListEntitiesTextSensorResponse {
|
||||
option (id) = 18;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_TEXT_SENSOR";
|
||||
|
||||
@@ -635,7 +622,6 @@ message ListEntitiesTextSensorResponse {
|
||||
}
|
||||
message TextSensorStateResponse {
|
||||
option (id) = 27;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_TEXT_SENSOR";
|
||||
option (no_delay) = true;
|
||||
@@ -803,7 +789,6 @@ message ExecuteServiceRequest {
|
||||
// ==================== CAMERA ====================
|
||||
message ListEntitiesCameraResponse {
|
||||
option (id) = 43;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_ESP32_CAMERA";
|
||||
|
||||
@@ -884,7 +869,6 @@ enum ClimatePreset {
|
||||
}
|
||||
message ListEntitiesClimateResponse {
|
||||
option (id) = 46;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
|
||||
@@ -919,7 +903,6 @@ message ListEntitiesClimateResponse {
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_CLIMATE";
|
||||
option (no_delay) = true;
|
||||
@@ -981,7 +964,6 @@ enum NumberMode {
|
||||
}
|
||||
message ListEntitiesNumberResponse {
|
||||
option (id) = 49;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_NUMBER";
|
||||
|
||||
@@ -1002,7 +984,6 @@ message ListEntitiesNumberResponse {
|
||||
}
|
||||
message NumberStateResponse {
|
||||
option (id) = 50;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_NUMBER";
|
||||
option (no_delay) = true;
|
||||
@@ -1026,7 +1007,6 @@ message NumberCommandRequest {
|
||||
// ==================== SELECT ====================
|
||||
message ListEntitiesSelectResponse {
|
||||
option (id) = 52;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SELECT";
|
||||
|
||||
@@ -1042,7 +1022,6 @@ message ListEntitiesSelectResponse {
|
||||
}
|
||||
message SelectStateResponse {
|
||||
option (id) = 53;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SELECT";
|
||||
option (no_delay) = true;
|
||||
@@ -1066,7 +1045,6 @@ message SelectCommandRequest {
|
||||
// ==================== SIREN ====================
|
||||
message ListEntitiesSirenResponse {
|
||||
option (id) = 55;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SIREN";
|
||||
|
||||
@@ -1084,7 +1062,6 @@ message ListEntitiesSirenResponse {
|
||||
}
|
||||
message SirenStateResponse {
|
||||
option (id) = 56;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_SIREN";
|
||||
option (no_delay) = true;
|
||||
@@ -1125,7 +1102,6 @@ enum LockCommand {
|
||||
}
|
||||
message ListEntitiesLockResponse {
|
||||
option (id) = 58;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LOCK";
|
||||
|
||||
@@ -1147,7 +1123,6 @@ message ListEntitiesLockResponse {
|
||||
}
|
||||
message LockStateResponse {
|
||||
option (id) = 59;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LOCK";
|
||||
option (no_delay) = true;
|
||||
@@ -1170,7 +1145,6 @@ message LockCommandRequest {
|
||||
// ==================== BUTTON ====================
|
||||
message ListEntitiesButtonResponse {
|
||||
option (id) = 61;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BUTTON";
|
||||
|
||||
@@ -1222,7 +1196,6 @@ message MediaPlayerSupportedFormat {
|
||||
}
|
||||
message ListEntitiesMediaPlayerResponse {
|
||||
option (id) = 63;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_MEDIA_PLAYER";
|
||||
|
||||
@@ -1241,7 +1214,6 @@ message ListEntitiesMediaPlayerResponse {
|
||||
}
|
||||
message MediaPlayerStateResponse {
|
||||
option (id) = 64;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_MEDIA_PLAYER";
|
||||
option (no_delay) = true;
|
||||
@@ -1763,7 +1735,6 @@ enum AlarmControlPanelStateCommand {
|
||||
|
||||
message ListEntitiesAlarmControlPanelResponse {
|
||||
option (id) = 94;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
|
||||
|
||||
@@ -1781,7 +1752,6 @@ message ListEntitiesAlarmControlPanelResponse {
|
||||
|
||||
message AlarmControlPanelStateResponse {
|
||||
option (id) = 95;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
|
||||
option (no_delay) = true;
|
||||
@@ -1806,7 +1776,6 @@ enum TextMode {
|
||||
}
|
||||
message ListEntitiesTextResponse {
|
||||
option (id) = 97;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_TEXT";
|
||||
|
||||
@@ -1825,7 +1794,6 @@ message ListEntitiesTextResponse {
|
||||
}
|
||||
message TextStateResponse {
|
||||
option (id) = 98;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_TEXT";
|
||||
option (no_delay) = true;
|
||||
@@ -1850,7 +1818,6 @@ message TextCommandRequest {
|
||||
// ==================== DATETIME DATE ====================
|
||||
message ListEntitiesDateResponse {
|
||||
option (id) = 100;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_DATETIME_DATE";
|
||||
|
||||
@@ -1865,7 +1832,6 @@ message ListEntitiesDateResponse {
|
||||
}
|
||||
message DateStateResponse {
|
||||
option (id) = 101;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_DATETIME_DATE";
|
||||
option (no_delay) = true;
|
||||
@@ -1893,7 +1859,6 @@ message DateCommandRequest {
|
||||
// ==================== DATETIME TIME ====================
|
||||
message ListEntitiesTimeResponse {
|
||||
option (id) = 103;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_DATETIME_TIME";
|
||||
|
||||
@@ -1908,7 +1873,6 @@ message ListEntitiesTimeResponse {
|
||||
}
|
||||
message TimeStateResponse {
|
||||
option (id) = 104;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_DATETIME_TIME";
|
||||
option (no_delay) = true;
|
||||
@@ -1936,7 +1900,6 @@ message TimeCommandRequest {
|
||||
// ==================== EVENT ====================
|
||||
message ListEntitiesEventResponse {
|
||||
option (id) = 107;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_EVENT";
|
||||
|
||||
@@ -1954,7 +1917,6 @@ message ListEntitiesEventResponse {
|
||||
}
|
||||
message EventResponse {
|
||||
option (id) = 108;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_EVENT";
|
||||
|
||||
@@ -1965,7 +1927,6 @@ message EventResponse {
|
||||
// ==================== VALVE ====================
|
||||
message ListEntitiesValveResponse {
|
||||
option (id) = 109;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_VALVE";
|
||||
|
||||
@@ -1991,7 +1952,6 @@ enum ValveOperation {
|
||||
}
|
||||
message ValveStateResponse {
|
||||
option (id) = 110;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_VALVE";
|
||||
option (no_delay) = true;
|
||||
@@ -2016,7 +1976,6 @@ message ValveCommandRequest {
|
||||
// ==================== DATETIME DATETIME ====================
|
||||
message ListEntitiesDateTimeResponse {
|
||||
option (id) = 112;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_DATETIME_DATETIME";
|
||||
|
||||
@@ -2031,7 +1990,6 @@ message ListEntitiesDateTimeResponse {
|
||||
}
|
||||
message DateTimeStateResponse {
|
||||
option (id) = 113;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_DATETIME_DATETIME";
|
||||
option (no_delay) = true;
|
||||
@@ -2055,7 +2013,6 @@ message DateTimeCommandRequest {
|
||||
// ==================== UPDATE ====================
|
||||
message ListEntitiesUpdateResponse {
|
||||
option (id) = 116;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_UPDATE";
|
||||
|
||||
@@ -2071,7 +2028,6 @@ message ListEntitiesUpdateResponse {
|
||||
}
|
||||
message UpdateStateResponse {
|
||||
option (id) = 117;
|
||||
option (base_class) = "StateResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_UPDATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
@@ -248,41 +248,25 @@ void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
|
||||
uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn,
|
||||
uint32_t remaining_size, bool is_single) {
|
||||
// Calculate size
|
||||
uint32_t calculated_size = 0;
|
||||
msg.calculate_size(calculated_size);
|
||||
|
||||
// Cache frame sizes to avoid repeated virtual calls
|
||||
const uint8_t header_padding = conn->helper_->frame_header_padding();
|
||||
const uint8_t footer_size = conn->helper_->frame_footer_size();
|
||||
uint32_t size = 0;
|
||||
msg.calculate_size(size);
|
||||
|
||||
// Calculate total size with padding for buffer allocation
|
||||
size_t total_calculated_size = calculated_size + header_padding + footer_size;
|
||||
uint16_t total_size =
|
||||
static_cast<uint16_t>(size) + conn->helper_->frame_header_padding() + conn->helper_->frame_footer_size();
|
||||
|
||||
// Check if it fits
|
||||
if (total_calculated_size > remaining_size) {
|
||||
if (total_size > remaining_size) {
|
||||
return 0; // Doesn't fit
|
||||
}
|
||||
|
||||
// Allocate buffer space - pass payload size, allocation functions add header/footer space
|
||||
ProtoWriteBuffer buffer = is_single ? conn->allocate_single_message_buffer(calculated_size)
|
||||
: conn->allocate_batch_message_buffer(calculated_size);
|
||||
|
||||
// Get buffer size after allocation (which includes header padding)
|
||||
std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
|
||||
size_t size_before_encode = shared_buf.size();
|
||||
// Allocate exact buffer space needed (just the payload, not the overhead)
|
||||
ProtoWriteBuffer buffer =
|
||||
is_single ? conn->allocate_single_message_buffer(size) : conn->allocate_batch_message_buffer(size);
|
||||
|
||||
// Encode directly into buffer
|
||||
msg.encode(buffer);
|
||||
|
||||
// Calculate actual encoded size (not including header that was already added)
|
||||
size_t actual_payload_size = shared_buf.size() - size_before_encode;
|
||||
|
||||
// Return actual total size (header + actual payload + footer)
|
||||
size_t actual_total_size = header_padding + actual_payload_size + footer_size;
|
||||
|
||||
// Verify that calculate_size() returned the correct value
|
||||
assert(calculated_size == actual_payload_size);
|
||||
return static_cast<uint16_t>(actual_total_size);
|
||||
return total_size;
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
@@ -301,7 +285,7 @@ uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConn
|
||||
BinarySensorStateResponse resp;
|
||||
resp.state = binary_sensor->state;
|
||||
resp.missing_state = !binary_sensor->has_state();
|
||||
fill_entity_state_base(binary_sensor, resp);
|
||||
resp.key = binary_sensor->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, BinarySensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
|
||||
@@ -335,7 +319,7 @@ uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *
|
||||
if (traits.get_supports_tilt())
|
||||
msg.tilt = cover->tilt;
|
||||
msg.current_operation = static_cast<enums::CoverOperation>(cover->current_operation);
|
||||
fill_entity_state_base(cover, msg);
|
||||
msg.key = cover->get_object_id_hash();
|
||||
return encode_message_to_buffer(msg, CoverStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
@@ -403,7 +387,7 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co
|
||||
msg.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||
if (traits.supports_preset_modes())
|
||||
msg.preset_mode = fan->preset_mode;
|
||||
fill_entity_state_base(fan, msg);
|
||||
msg.key = fan->get_object_id_hash();
|
||||
return encode_message_to_buffer(msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
@@ -470,7 +454,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
|
||||
resp.warm_white = values.get_warm_white();
|
||||
if (light->supports_effects())
|
||||
resp.effect = light->get_effect_name();
|
||||
fill_entity_state_base(light, resp);
|
||||
resp.key = light->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
@@ -552,7 +536,7 @@ uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection
|
||||
SensorStateResponse resp;
|
||||
resp.state = sensor->state;
|
||||
resp.missing_state = !sensor->has_state();
|
||||
fill_entity_state_base(sensor, resp);
|
||||
resp.key = sensor->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, SensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
|
||||
@@ -586,7 +570,7 @@ uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection
|
||||
auto *a_switch = static_cast<switch_::Switch *>(entity);
|
||||
SwitchStateResponse resp;
|
||||
resp.state = a_switch->state;
|
||||
fill_entity_state_base(a_switch, resp);
|
||||
resp.key = a_switch->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, SwitchStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
|
||||
@@ -629,7 +613,7 @@ uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnec
|
||||
TextSensorStateResponse resp;
|
||||
resp.state = text_sensor->state;
|
||||
resp.missing_state = !text_sensor->has_state();
|
||||
fill_entity_state_base(text_sensor, resp);
|
||||
resp.key = text_sensor->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
@@ -653,7 +637,7 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
|
||||
bool is_single) {
|
||||
auto *climate = static_cast<climate::Climate *>(entity);
|
||||
ClimateStateResponse resp;
|
||||
fill_entity_state_base(climate, resp);
|
||||
resp.key = climate->get_object_id_hash();
|
||||
auto traits = climate->get_traits();
|
||||
resp.mode = static_cast<enums::ClimateMode>(climate->mode);
|
||||
resp.action = static_cast<enums::ClimateAction>(climate->action);
|
||||
@@ -762,7 +746,7 @@ uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection
|
||||
NumberStateResponse resp;
|
||||
resp.state = number->state;
|
||||
resp.missing_state = !number->has_state();
|
||||
fill_entity_state_base(number, resp);
|
||||
resp.key = number->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, NumberStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
|
||||
@@ -803,7 +787,7 @@ uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *c
|
||||
resp.year = date->year;
|
||||
resp.month = date->month;
|
||||
resp.day = date->day;
|
||||
fill_entity_state_base(date, resp);
|
||||
resp.key = date->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, DateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
void APIConnection::send_date_info(datetime::DateEntity *date) {
|
||||
@@ -840,7 +824,7 @@ uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *c
|
||||
resp.hour = time->hour;
|
||||
resp.minute = time->minute;
|
||||
resp.second = time->second;
|
||||
fill_entity_state_base(time, resp);
|
||||
resp.key = time->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, TimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
void APIConnection::send_time_info(datetime::TimeEntity *time) {
|
||||
@@ -879,7 +863,7 @@ uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnectio
|
||||
ESPTime state = datetime->state_as_esptime();
|
||||
resp.epoch_seconds = state.timestamp;
|
||||
}
|
||||
fill_entity_state_base(datetime, resp);
|
||||
resp.key = datetime->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, DateTimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
void APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) {
|
||||
@@ -918,7 +902,7 @@ uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *c
|
||||
TextStateResponse resp;
|
||||
resp.state = text->state;
|
||||
resp.missing_state = !text->has_state();
|
||||
fill_entity_state_base(text, resp);
|
||||
resp.key = text->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
|
||||
@@ -959,7 +943,7 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection
|
||||
SelectStateResponse resp;
|
||||
resp.state = select->state;
|
||||
resp.missing_state = !select->has_state();
|
||||
fill_entity_state_base(select, resp);
|
||||
resp.key = select->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
|
||||
@@ -1019,7 +1003,7 @@ uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *c
|
||||
auto *a_lock = static_cast<lock::Lock *>(entity);
|
||||
LockStateResponse resp;
|
||||
resp.state = static_cast<enums::LockState>(a_lock->state);
|
||||
fill_entity_state_base(a_lock, resp);
|
||||
resp.key = a_lock->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, LockStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
|
||||
@@ -1063,7 +1047,7 @@ uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *
|
||||
ValveStateResponse resp;
|
||||
resp.position = valve->position;
|
||||
resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation);
|
||||
fill_entity_state_base(valve, resp);
|
||||
resp.key = valve->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, ValveStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
void APIConnection::send_valve_info(valve::Valve *valve) {
|
||||
@@ -1111,7 +1095,7 @@ uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConne
|
||||
resp.state = static_cast<enums::MediaPlayerState>(report_state);
|
||||
resp.volume = media_player->volume;
|
||||
resp.muted = media_player->is_muted();
|
||||
fill_entity_state_base(media_player, resp);
|
||||
resp.key = media_player->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, MediaPlayerStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
void APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) {
|
||||
@@ -1375,7 +1359,7 @@ uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, A
|
||||
auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity);
|
||||
AlarmControlPanelStateResponse resp;
|
||||
resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state());
|
||||
fill_entity_state_base(a_alarm_control_panel, resp);
|
||||
resp.key = a_alarm_control_panel->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, AlarmControlPanelStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
void APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
|
||||
@@ -1439,7 +1423,7 @@ uint16_t APIConnection::try_send_event_response(event::Event *event, const std::
|
||||
uint32_t remaining_size, bool is_single) {
|
||||
EventResponse resp;
|
||||
resp.event_type = event_type;
|
||||
fill_entity_state_base(event, resp);
|
||||
resp.key = event->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
|
||||
@@ -1477,7 +1461,7 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection
|
||||
resp.release_summary = update->update_info.summary;
|
||||
resp.release_url = update->update_info.release_url;
|
||||
}
|
||||
fill_entity_state_base(update, resp);
|
||||
resp.key = update->get_object_id_hash();
|
||||
return encode_message_to_buffer(resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
void APIConnection::send_update_info(update::UpdateEntity *update) {
|
||||
@@ -1538,7 +1522,7 @@ bool APIConnection::try_send_log_message(int level, const char *tag, const char
|
||||
buffer.encode_string(3, line, line_length); // string message = 3
|
||||
|
||||
// SubscribeLogsResponse - 29
|
||||
return this->send_buffer(buffer, SubscribeLogsResponse::MESSAGE_TYPE);
|
||||
return this->send_buffer(buffer, 29);
|
||||
}
|
||||
|
||||
HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
@@ -1685,7 +1669,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
|
||||
return false;
|
||||
}
|
||||
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) {
|
||||
if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse
|
||||
if (!this->try_to_clear_buffer(message_type != 29)) { // SubscribeLogsResponse
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1807,7 +1791,7 @@ void APIConnection::process_batch_() {
|
||||
this->batch_first_message_ = true;
|
||||
|
||||
size_t items_processed = 0;
|
||||
uint16_t remaining_size = std::numeric_limits<uint16_t>::max();
|
||||
uint32_t remaining_size = MAX_PACKET_SIZE;
|
||||
|
||||
// Track where each message's header padding begins in the buffer
|
||||
// For plaintext: this is where the 6-byte header padding starts
|
||||
@@ -1832,15 +1816,11 @@ void APIConnection::process_batch_() {
|
||||
packet_info.emplace_back(item.message_type, current_offset, proto_payload_size);
|
||||
|
||||
// Update tracking variables
|
||||
items_processed++;
|
||||
// After first message, set remaining size to MAX_PACKET_SIZE to avoid fragmentation
|
||||
if (items_processed == 1) {
|
||||
remaining_size = MAX_PACKET_SIZE;
|
||||
}
|
||||
remaining_size -= payload_size;
|
||||
// Calculate where the next message's header padding will start
|
||||
// Current buffer size + footer space (that prepare_message_buffer will add for this message)
|
||||
current_offset = this->parent_->get_shared_buffer_ref().size() + footer_size;
|
||||
items_processed++;
|
||||
}
|
||||
|
||||
if (items_processed == 0) {
|
||||
|
||||
@@ -240,8 +240,8 @@ class APIConnection : public APIServerConnection {
|
||||
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
|
||||
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
|
||||
shared_buf.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
|
||||
// Resize to add header padding so message encoding starts at the correct position
|
||||
shared_buf.resize(header_padding);
|
||||
// Insert header padding bytes so message encoding starts at the correct position
|
||||
shared_buf.insert(shared_buf.begin(), header_padding, 0);
|
||||
return {&shared_buf};
|
||||
}
|
||||
|
||||
@@ -249,25 +249,31 @@ class APIConnection : public APIServerConnection {
|
||||
ProtoWriteBuffer prepare_message_buffer(uint16_t message_size, bool is_first_message) {
|
||||
// Get reference to shared buffer (it maintains state between batch messages)
|
||||
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
|
||||
|
||||
if (is_first_message) {
|
||||
shared_buf.clear();
|
||||
}
|
||||
|
||||
size_t current_size = shared_buf.size();
|
||||
|
||||
// Calculate padding to add:
|
||||
// - First message: just header padding
|
||||
// - Subsequent messages: footer for previous message + header padding for this message
|
||||
size_t padding_to_add = is_first_message
|
||||
? this->helper_->frame_header_padding()
|
||||
: this->helper_->frame_header_padding() + this->helper_->frame_footer_size();
|
||||
if (is_first_message) {
|
||||
// For first message, initialize buffer with header padding
|
||||
uint8_t header_padding = this->helper_->frame_header_padding();
|
||||
shared_buf.clear();
|
||||
shared_buf.reserve(message_size + header_padding);
|
||||
shared_buf.resize(header_padding);
|
||||
// Fill header padding with zeros
|
||||
std::fill(shared_buf.begin(), shared_buf.end(), 0);
|
||||
} else {
|
||||
// For subsequent messages, add footer space for previous message and header for this message
|
||||
uint8_t footer_size = this->helper_->frame_footer_size();
|
||||
uint8_t header_padding = this->helper_->frame_header_padding();
|
||||
|
||||
// Reserve space for padding + message
|
||||
shared_buf.reserve(current_size + padding_to_add + message_size);
|
||||
// Reserve additional space for everything
|
||||
shared_buf.reserve(current_size + footer_size + header_padding + message_size);
|
||||
|
||||
// Resize to add the padding bytes
|
||||
shared_buf.resize(current_size + padding_to_add);
|
||||
// Single resize to add both footer and header padding
|
||||
size_t new_size = current_size + footer_size + header_padding;
|
||||
shared_buf.resize(new_size);
|
||||
|
||||
// Fill the newly added bytes with zeros (footer + header padding)
|
||||
std::fill(shared_buf.begin() + current_size, shared_buf.end(), 0);
|
||||
}
|
||||
|
||||
return {&shared_buf};
|
||||
}
|
||||
@@ -282,8 +288,8 @@ class APIConnection : public APIServerConnection {
|
||||
ProtoWriteBuffer allocate_batch_message_buffer(uint16_t size);
|
||||
|
||||
protected:
|
||||
// Helper function to fill common entity info fields
|
||||
static void fill_entity_info_base(esphome::EntityBase *entity, InfoResponseProtoMessage &response) {
|
||||
// Helper function to fill common entity fields
|
||||
template<typename ResponseT> static void fill_entity_info_base(esphome::EntityBase *entity, ResponseT &response) {
|
||||
// Set common fields that are shared by all entity types
|
||||
response.key = entity->get_object_id_hash();
|
||||
response.object_id = entity->get_object_id();
|
||||
@@ -297,11 +303,6 @@ class APIConnection : public APIServerConnection {
|
||||
response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());
|
||||
}
|
||||
|
||||
// Helper function to fill common entity state fields
|
||||
static void fill_entity_state_base(esphome::EntityBase *entity, StateResponseProtoMessage &response) {
|
||||
response.key = entity->get_object_id_hash();
|
||||
}
|
||||
|
||||
// Non-template helper to encode any ProtoMessage
|
||||
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn,
|
||||
uint32_t remaining_size, bool is_single);
|
||||
|
||||
@@ -21,5 +21,4 @@ extend google.protobuf.MessageOptions {
|
||||
optional string ifdef = 1038;
|
||||
optional bool log = 1039 [default=true];
|
||||
optional bool no_delay = 1040 [default=false];
|
||||
optional string base_class = 1041;
|
||||
}
|
||||
|
||||
@@ -628,7 +628,6 @@ template<> const char *proto_enum_to_string<enums::UpdateCommand>(enums::UpdateC
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
|
||||
@@ -253,27 +253,6 @@ enum UpdateCommand : uint32_t {
|
||||
|
||||
} // namespace enums
|
||||
|
||||
class InfoResponseProtoMessage : public ProtoMessage {
|
||||
public:
|
||||
~InfoResponseProtoMessage() override = default;
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
class StateResponseProtoMessage : public ProtoMessage {
|
||||
public:
|
||||
~StateResponseProtoMessage() override = default;
|
||||
uint32_t key{0};
|
||||
|
||||
protected:
|
||||
};
|
||||
class HelloRequest : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 1;
|
||||
@@ -505,15 +484,22 @@ class SubscribeStatesRequest : public ProtoMessage {
|
||||
|
||||
protected:
|
||||
};
|
||||
class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesBinarySensorResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 12;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 56;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_binary_sensor_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string device_class{};
|
||||
bool is_status_binary_sensor{false};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -525,13 +511,14 @@ class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class BinarySensorStateResponse : public StateResponseProtoMessage {
|
||||
class BinarySensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 21;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 9;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "binary_sensor_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
@@ -544,17 +531,24 @@ class BinarySensorStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesCoverResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesCoverResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 13;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 62;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_cover_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
bool assumed_state{false};
|
||||
bool supports_position{false};
|
||||
bool supports_tilt{false};
|
||||
std::string device_class{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
bool supports_stop{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@@ -567,13 +561,14 @@ class ListEntitiesCoverResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class CoverStateResponse : public StateResponseProtoMessage {
|
||||
class CoverStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 22;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 19;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "cover_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
enums::LegacyCoverState legacy_state{};
|
||||
float position{0.0f};
|
||||
float tilt{0.0f};
|
||||
@@ -613,17 +608,24 @@ class CoverCommandRequest : public ProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesFanResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesFanResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 14;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 73;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_fan_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
bool supports_oscillation{false};
|
||||
bool supports_speed{false};
|
||||
bool supports_direction{false};
|
||||
int32_t supported_speed_count{0};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
std::vector<std::string> supported_preset_modes{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@@ -636,13 +638,14 @@ class ListEntitiesFanResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class FanStateResponse : public StateResponseProtoMessage {
|
||||
class FanStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 23;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 26;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "fan_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
bool oscillating{false};
|
||||
enums::FanSpeed speed{};
|
||||
@@ -691,13 +694,17 @@ class FanCommandRequest : public ProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesLightResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesLightResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 15;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 85;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_light_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::vector<enums::ColorMode> supported_color_modes{};
|
||||
bool legacy_supports_brightness{false};
|
||||
bool legacy_supports_rgb{false};
|
||||
@@ -706,6 +713,9 @@ class ListEntitiesLightResponse : public InfoResponseProtoMessage {
|
||||
float min_mireds{0.0f};
|
||||
float max_mireds{0.0f};
|
||||
std::vector<std::string> effects{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -717,13 +727,14 @@ class ListEntitiesLightResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LightStateResponse : public StateResponseProtoMessage {
|
||||
class LightStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 24;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 63;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "light_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
float brightness{0.0f};
|
||||
enums::ColorMode color_mode{};
|
||||
@@ -792,19 +803,26 @@ class LightCommandRequest : public ProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesSensorResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 16;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 73;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_sensor_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
std::string unit_of_measurement{};
|
||||
int32_t accuracy_decimals{0};
|
||||
bool force_update{false};
|
||||
std::string device_class{};
|
||||
enums::SensorStateClass state_class{};
|
||||
enums::SensorLastResetType legacy_last_reset_type{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -816,13 +834,14 @@ class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SensorStateResponse : public StateResponseProtoMessage {
|
||||
class SensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 25;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 12;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "sensor_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
float state{0.0f};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
@@ -835,14 +854,21 @@ class SensorStateResponse : public StateResponseProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesSwitchResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesSwitchResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 17;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 56;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_switch_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool assumed_state{false};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
std::string device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@@ -855,13 +881,14 @@ class ListEntitiesSwitchResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SwitchStateResponse : public StateResponseProtoMessage {
|
||||
class SwitchStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 26;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 7;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "switch_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@@ -892,13 +919,20 @@ class SwitchCommandRequest : public ProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesTextSensorResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 18;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 54;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_text_sensor_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
std::string device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@@ -911,13 +945,14 @@ class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class TextSensorStateResponse : public StateResponseProtoMessage {
|
||||
class TextSensorStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 27;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 16;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "text_sensor_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
std::string state{};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
@@ -1214,13 +1249,20 @@ class ExecuteServiceRequest : public ProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ListEntitiesCameraResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesCameraResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 43;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 45;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_camera_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1271,13 +1313,17 @@ class CameraImageRequest : public ProtoMessage {
|
||||
protected:
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesClimateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 46;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 151;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_climate_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
bool supports_current_temperature{false};
|
||||
bool supports_two_point_target_temperature{false};
|
||||
std::vector<enums::ClimateMode> supported_modes{};
|
||||
@@ -1291,6 +1337,9 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
|
||||
std::vector<std::string> supported_custom_fan_modes{};
|
||||
std::vector<enums::ClimatePreset> supported_presets{};
|
||||
std::vector<std::string> supported_custom_presets{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
float visual_current_temperature_step{0.0f};
|
||||
bool supports_current_humidity{false};
|
||||
bool supports_target_humidity{false};
|
||||
@@ -1307,13 +1356,14 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ClimateStateResponse : public StateResponseProtoMessage {
|
||||
class ClimateStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 47;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 65;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "climate_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
enums::ClimateMode mode{};
|
||||
float current_temperature{0.0f};
|
||||
float target_temperature{0.0f};
|
||||
@@ -1380,16 +1430,23 @@ class ClimateCommandRequest : public ProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesNumberResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesNumberResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 49;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 80;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_number_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
float min_value{0.0f};
|
||||
float max_value{0.0f};
|
||||
float step{0.0f};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
std::string unit_of_measurement{};
|
||||
enums::NumberMode mode{};
|
||||
std::string device_class{};
|
||||
@@ -1404,13 +1461,14 @@ class ListEntitiesNumberResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class NumberStateResponse : public StateResponseProtoMessage {
|
||||
class NumberStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 50;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 12;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "number_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
float state{0.0f};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
@@ -1441,14 +1499,21 @@ class NumberCommandRequest : public ProtoMessage {
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
};
|
||||
class ListEntitiesSelectResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesSelectResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 52;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 63;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_select_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
std::vector<std::string> options{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1460,13 +1525,14 @@ class ListEntitiesSelectResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SelectStateResponse : public StateResponseProtoMessage {
|
||||
class SelectStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 53;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 16;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "select_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
std::string state{};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
@@ -1499,16 +1565,23 @@ class SelectCommandRequest : public ProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ListEntitiesSirenResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesSirenResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 55;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 67;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_siren_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
std::vector<std::string> tones{};
|
||||
bool supports_duration{false};
|
||||
bool supports_volume{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1520,13 +1593,14 @@ class ListEntitiesSirenResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SirenStateResponse : public StateResponseProtoMessage {
|
||||
class SirenStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 56;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 7;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "siren_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@@ -1565,13 +1639,20 @@ class SirenCommandRequest : public ProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesLockResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesLockResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 58;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 60;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_lock_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
bool assumed_state{false};
|
||||
bool supports_open{false};
|
||||
bool requires_code{false};
|
||||
@@ -1587,13 +1668,14 @@ class ListEntitiesLockResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LockStateResponse : public StateResponseProtoMessage {
|
||||
class LockStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 59;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 7;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "lock_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
enums::LockState state{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@@ -1627,13 +1709,20 @@ class LockCommandRequest : public ProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesButtonResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesButtonResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 61;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 54;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_button_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
std::string device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@@ -1680,13 +1769,20 @@ class MediaPlayerSupportedFormat : public ProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesMediaPlayerResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 63;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 81;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_media_player_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
bool supports_pause{false};
|
||||
std::vector<MediaPlayerSupportedFormat> supported_formats{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
@@ -1700,13 +1796,14 @@ class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class MediaPlayerStateResponse : public StateResponseProtoMessage {
|
||||
class MediaPlayerStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 64;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 14;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "media_player_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
enums::MediaPlayerState state{};
|
||||
float volume{0.0f};
|
||||
bool muted{false};
|
||||
@@ -2556,13 +2653,20 @@ class VoiceAssistantSetConfiguration : public ProtoMessage {
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesAlarmControlPanelResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 94;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 53;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_alarm_control_panel_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
uint32_t supported_features{0};
|
||||
bool requires_code{false};
|
||||
bool requires_code_to_arm{false};
|
||||
@@ -2577,13 +2681,14 @@ class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class AlarmControlPanelStateResponse : public StateResponseProtoMessage {
|
||||
class AlarmControlPanelStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 95;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 7;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "alarm_control_panel_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
enums::AlarmControlPanelState state{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@@ -2616,13 +2721,20 @@ class AlarmControlPanelCommandRequest : public ProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesTextResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesTextResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 97;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 64;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_text_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
uint32_t min_length{0};
|
||||
uint32_t max_length{0};
|
||||
std::string pattern{};
|
||||
@@ -2638,13 +2750,14 @@ class ListEntitiesTextResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class TextStateResponse : public StateResponseProtoMessage {
|
||||
class TextStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 98;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 16;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "text_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
std::string state{};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
@@ -2677,13 +2790,20 @@ class TextCommandRequest : public ProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ListEntitiesDateResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesDateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 100;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 45;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_date_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -2695,13 +2815,14 @@ class ListEntitiesDateResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class DateStateResponse : public StateResponseProtoMessage {
|
||||
class DateStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 101;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 19;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "date_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool missing_state{false};
|
||||
uint32_t year{0};
|
||||
uint32_t month{0};
|
||||
@@ -2737,13 +2858,20 @@ class DateCommandRequest : public ProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesTimeResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesTimeResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 103;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 45;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_time_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -2755,13 +2883,14 @@ class ListEntitiesTimeResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class TimeStateResponse : public StateResponseProtoMessage {
|
||||
class TimeStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 104;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 19;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "time_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool missing_state{false};
|
||||
uint32_t hour{0};
|
||||
uint32_t minute{0};
|
||||
@@ -2797,13 +2926,20 @@ class TimeCommandRequest : public ProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesEventResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesEventResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 107;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 72;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_event_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
std::string device_class{};
|
||||
std::vector<std::string> event_types{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
@@ -2817,13 +2953,14 @@ class ListEntitiesEventResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class EventResponse : public StateResponseProtoMessage {
|
||||
class EventResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 108;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 14;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "event_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
std::string event_type{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@@ -2835,13 +2972,20 @@ class EventResponse : public StateResponseProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ListEntitiesValveResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesValveResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 109;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 60;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_valve_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
std::string device_class{};
|
||||
bool assumed_state{false};
|
||||
bool supports_position{false};
|
||||
@@ -2857,13 +3001,14 @@ class ListEntitiesValveResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ValveStateResponse : public StateResponseProtoMessage {
|
||||
class ValveStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 110;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 12;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "valve_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
float position{0.0f};
|
||||
enums::ValveOperation current_operation{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
@@ -2897,13 +3042,20 @@ class ValveCommandRequest : public ProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesDateTimeResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 112;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 45;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_date_time_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -2915,13 +3067,14 @@ class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class DateTimeStateResponse : public StateResponseProtoMessage {
|
||||
class DateTimeStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 113;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 12;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "date_time_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool missing_state{false};
|
||||
uint32_t epoch_seconds{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
@@ -2952,13 +3105,20 @@ class DateTimeCommandRequest : public ProtoMessage {
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
};
|
||||
class ListEntitiesUpdateResponse : public InfoResponseProtoMessage {
|
||||
class ListEntitiesUpdateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 116;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 54;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "list_entities_update_response"; }
|
||||
#endif
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
std::string device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(uint32_t &total_size) const override;
|
||||
@@ -2971,13 +3131,14 @@ class ListEntitiesUpdateResponse : public InfoResponseProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class UpdateStateResponse : public StateResponseProtoMessage {
|
||||
class UpdateStateResponse : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint16_t MESSAGE_TYPE = 117;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 61;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
static constexpr const char *message_name() { return "update_state_response"; }
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
bool missing_state{false};
|
||||
bool in_progress{false};
|
||||
bool has_progress{false};
|
||||
|
||||
@@ -227,7 +227,7 @@ bool APIServer::check_password(const std::string &password) const {
|
||||
void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
|
||||
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
|
||||
@@ -54,7 +54,7 @@ class APIServer : public Component, public Controller {
|
||||
|
||||
void handle_disconnect(APIConnection *conn);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override;
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
void on_cover_update(cover::Cover *obj) override;
|
||||
|
||||
@@ -216,7 +216,7 @@ class ProtoWriteBuffer {
|
||||
this->buffer_->insert(this->buffer_->end(), data, data + len);
|
||||
}
|
||||
void encode_string(uint32_t field_id, const std::string &value, bool force = false) {
|
||||
this->encode_string(field_id, value.data(), value.size(), force);
|
||||
this->encode_string(field_id, value.data(), value.size());
|
||||
}
|
||||
void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
|
||||
this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
from logging import getLogger
|
||||
|
||||
from esphome import automation, core
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server
|
||||
from esphome.components.const import CONF_ON_STATE_CHANGE
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DELAY,
|
||||
@@ -98,6 +101,7 @@ IS_PLATFORM_COMPONENT = True
|
||||
|
||||
CONF_TIME_OFF = "time_off"
|
||||
CONF_TIME_ON = "time_on"
|
||||
CONF_TRIGGER_ON_INITIAL_STATE = "trigger_on_initial_state"
|
||||
|
||||
DEFAULT_DELAY = "1s"
|
||||
DEFAULT_TIME_OFF = "100ms"
|
||||
@@ -127,9 +131,17 @@ MultiClickTriggerEvent = binary_sensor_ns.struct("MultiClickTriggerEvent")
|
||||
StateTrigger = binary_sensor_ns.class_(
|
||||
"StateTrigger", automation.Trigger.template(bool)
|
||||
)
|
||||
StateChangeTrigger = binary_sensor_ns.class_(
|
||||
"StateChangeTrigger",
|
||||
automation.Trigger.template(cg.optional.template(bool), cg.optional.template(bool)),
|
||||
)
|
||||
|
||||
BinarySensorPublishAction = binary_sensor_ns.class_(
|
||||
"BinarySensorPublishAction", automation.Action
|
||||
)
|
||||
BinarySensorInvalidateAction = binary_sensor_ns.class_(
|
||||
"BinarySensorInvalidateAction", automation.Action
|
||||
)
|
||||
|
||||
# Condition
|
||||
BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Condition)
|
||||
@@ -144,6 +156,8 @@ AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Compon
|
||||
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
|
||||
SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component)
|
||||
|
||||
_LOGGER = getLogger(__name__)
|
||||
|
||||
FILTER_REGISTRY = Registry()
|
||||
validate_filters = cv.validate_registry("filter", FILTER_REGISTRY)
|
||||
|
||||
@@ -386,6 +400,14 @@ def validate_click_timing(value):
|
||||
return value
|
||||
|
||||
|
||||
def validate_publish_initial_state(value):
|
||||
value = cv.boolean(value)
|
||||
_LOGGER.warning(
|
||||
"The 'publish_initial_state' option has been replaced by 'trigger_on_initial_state' and will be removed in a future release"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
_BINARY_SENSOR_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMPONENT_SCHEMA)
|
||||
@@ -395,7 +417,12 @@ _BINARY_SENSOR_SCHEMA = (
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt.MQTTBinarySensorComponent
|
||||
),
|
||||
cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean,
|
||||
cv.Exclusive(
|
||||
CONF_PUBLISH_INITIAL_STATE, CONF_TRIGGER_ON_INITIAL_STATE
|
||||
): validate_publish_initial_state,
|
||||
cv.Exclusive(
|
||||
CONF_TRIGGER_ON_INITIAL_STATE, CONF_TRIGGER_ON_INITIAL_STATE
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(CONF_FILTERS): validate_filters,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
|
||||
@@ -454,6 +481,11 @@ _BINARY_SENSOR_SCHEMA = (
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE_CHANGE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateChangeTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -493,8 +525,10 @@ async def setup_binary_sensor_core_(var, config):
|
||||
|
||||
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
|
||||
cg.add(var.set_device_class(device_class))
|
||||
if publish_initial_state := config.get(CONF_PUBLISH_INITIAL_STATE):
|
||||
cg.add(var.set_publish_initial_state(publish_initial_state))
|
||||
trigger = config.get(CONF_TRIGGER_ON_INITIAL_STATE, False) or config.get(
|
||||
CONF_PUBLISH_INITIAL_STATE, False
|
||||
)
|
||||
cg.add(var.set_trigger_on_initial_state(trigger))
|
||||
if inverted := config.get(CONF_INVERTED):
|
||||
cg.add(var.set_inverted(inverted))
|
||||
if filters_config := config.get(CONF_FILTERS):
|
||||
@@ -542,6 +576,17 @@ async def setup_binary_sensor_core_(var, config):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(bool, "x")], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_STATE_CHANGE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger,
|
||||
[
|
||||
(cg.optional.template(bool), "x_previous"),
|
||||
(cg.optional.template(bool), "x"),
|
||||
],
|
||||
conf,
|
||||
)
|
||||
|
||||
if mqtt_id := config.get(CONF_MQTT_ID):
|
||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
@@ -591,3 +636,18 @@ async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args)
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_BINARY_SENSOR")
|
||||
cg.add_global(binary_sensor_ns.using)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"binary_sensor.invalidate_state",
|
||||
BinarySensorInvalidateAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(BinarySensor),
|
||||
},
|
||||
key=CONF_ID,
|
||||
),
|
||||
)
|
||||
async def binary_sensor_invalidate_state_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
@@ -96,7 +96,7 @@ class MultiClickTrigger : public Trigger<>, public Component {
|
||||
: parent_(parent), timing_(std::move(timing)) {}
|
||||
|
||||
void setup() override {
|
||||
this->last_state_ = this->parent_->state;
|
||||
this->last_state_ = this->parent_->get_state_default(false);
|
||||
auto f = std::bind(&MultiClickTrigger::on_state_, this, std::placeholders::_1);
|
||||
this->parent_->add_on_state_callback(f);
|
||||
}
|
||||
@@ -130,6 +130,14 @@ class StateTrigger : public Trigger<bool> {
|
||||
}
|
||||
};
|
||||
|
||||
class StateChangeTrigger : public Trigger<optional<bool>, optional<bool> > {
|
||||
public:
|
||||
explicit StateChangeTrigger(BinarySensor *parent) {
|
||||
parent->add_full_state_callback(
|
||||
[this](optional<bool> old_state, optional<bool> state) { this->trigger(old_state, state); });
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class BinarySensorCondition : public Condition<Ts...> {
|
||||
public:
|
||||
BinarySensorCondition(BinarySensor *parent, bool state) : parent_(parent), state_(state) {}
|
||||
@@ -154,5 +162,15 @@ template<typename... Ts> class BinarySensorPublishAction : public Action<Ts...>
|
||||
BinarySensor *sensor_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class BinarySensorInvalidateAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit BinarySensorInvalidateAction(BinarySensor *sensor) : sensor_(sensor) {}
|
||||
|
||||
void play(Ts... x) override { this->sensor_->invalidate_state(); }
|
||||
|
||||
protected:
|
||||
BinarySensor *sensor_;
|
||||
};
|
||||
|
||||
} // namespace binary_sensor
|
||||
} // namespace esphome
|
||||
|
||||
@@ -7,42 +7,25 @@ namespace binary_sensor {
|
||||
|
||||
static const char *const TAG = "binary_sensor";
|
||||
|
||||
void BinarySensor::add_on_state_callback(std::function<void(bool)> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void BinarySensor::publish_state(bool state) {
|
||||
if (!this->publish_dedup_.next(state))
|
||||
return;
|
||||
void BinarySensor::publish_state(bool new_state) {
|
||||
if (this->filter_list_ == nullptr) {
|
||||
this->send_state_internal(state, false);
|
||||
this->send_state_internal(new_state);
|
||||
} else {
|
||||
this->filter_list_->input(state, false);
|
||||
this->filter_list_->input(new_state);
|
||||
}
|
||||
}
|
||||
void BinarySensor::publish_initial_state(bool state) {
|
||||
if (!this->publish_dedup_.next(state))
|
||||
return;
|
||||
if (this->filter_list_ == nullptr) {
|
||||
this->send_state_internal(state, true);
|
||||
} else {
|
||||
this->filter_list_->input(state, true);
|
||||
void BinarySensor::publish_initial_state(bool new_state) {
|
||||
this->invalidate_state();
|
||||
this->publish_state(new_state);
|
||||
}
|
||||
void BinarySensor::send_state_internal(bool new_state) {
|
||||
// copy the new state to the visible property for backwards compatibility, before any callbacks
|
||||
this->state = new_state;
|
||||
// Note that set_state_ de-dups and will only trigger callbacks if the state has actually changed
|
||||
if (this->set_state_(new_state)) {
|
||||
ESP_LOGD(TAG, "'%s': New state is %s", this->get_name().c_str(), ONOFF(new_state));
|
||||
}
|
||||
}
|
||||
void BinarySensor::send_state_internal(bool state, bool is_initial) {
|
||||
if (is_initial) {
|
||||
ESP_LOGD(TAG, "'%s': Sending initial state %s", this->get_name().c_str(), ONOFF(state));
|
||||
} else {
|
||||
ESP_LOGD(TAG, "'%s': Sending state %s", this->get_name().c_str(), ONOFF(state));
|
||||
}
|
||||
this->has_state_ = true;
|
||||
this->state = state;
|
||||
if (!is_initial || this->publish_initial_state_) {
|
||||
this->state_callback_.call(state);
|
||||
}
|
||||
}
|
||||
|
||||
BinarySensor::BinarySensor() : state(false) {}
|
||||
|
||||
void BinarySensor::add_filter(Filter *filter) {
|
||||
filter->parent_ = this;
|
||||
@@ -60,7 +43,6 @@ void BinarySensor::add_filters(const std::vector<Filter *> &filters) {
|
||||
this->add_filter(filter);
|
||||
}
|
||||
}
|
||||
bool BinarySensor::has_state() const { return this->has_state_; }
|
||||
bool BinarySensor::is_status_binary_sensor() const { return false; }
|
||||
|
||||
} // namespace binary_sensor
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/binary_sensor/filter.h"
|
||||
@@ -34,52 +33,39 @@ namespace binary_sensor {
|
||||
* The sub classes should notify the front-end of new states via the publish_state() method which
|
||||
* handles inverted inputs for you.
|
||||
*/
|
||||
class BinarySensor : public EntityBase, public EntityBase_DeviceClass {
|
||||
class BinarySensor : public StatefulEntityBase<bool>, public EntityBase_DeviceClass {
|
||||
public:
|
||||
explicit BinarySensor();
|
||||
|
||||
/** Add a callback to be notified of state changes.
|
||||
*
|
||||
* @param callback The void(bool) callback.
|
||||
*/
|
||||
void add_on_state_callback(std::function<void(bool)> &&callback);
|
||||
explicit BinarySensor(){};
|
||||
|
||||
/** Publish a new state to the front-end.
|
||||
*
|
||||
* @param state The new state.
|
||||
* @param new_state The new state.
|
||||
*/
|
||||
void publish_state(bool state);
|
||||
void publish_state(bool new_state);
|
||||
|
||||
/** Publish the initial state, this will not make the callback manager send callbacks
|
||||
* and is meant only for the initial state on boot.
|
||||
*
|
||||
* @param state The new state.
|
||||
* @param new_state The new state.
|
||||
*/
|
||||
void publish_initial_state(bool state);
|
||||
|
||||
/// The current reported state of the binary sensor.
|
||||
bool state{false};
|
||||
void publish_initial_state(bool new_state);
|
||||
|
||||
void add_filter(Filter *filter);
|
||||
void add_filters(const std::vector<Filter *> &filters);
|
||||
|
||||
void set_publish_initial_state(bool publish_initial_state) { this->publish_initial_state_ = publish_initial_state; }
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void send_state_internal(bool state, bool is_initial);
|
||||
void send_state_internal(bool new_state);
|
||||
|
||||
/// Return whether this binary sensor has outputted a state.
|
||||
virtual bool has_state() const;
|
||||
|
||||
virtual bool is_status_binary_sensor() const;
|
||||
|
||||
// For backward compatibility, provide an accessible property
|
||||
|
||||
bool state{};
|
||||
|
||||
protected:
|
||||
CallbackManager<void(bool)> state_callback_{};
|
||||
Filter *filter_list_{nullptr};
|
||||
bool has_state_{false};
|
||||
bool publish_initial_state_{false};
|
||||
Deduplicator<bool> publish_dedup_;
|
||||
};
|
||||
|
||||
class BinarySensorInitiallyOff : public BinarySensor {
|
||||
|
||||
@@ -9,37 +9,36 @@ namespace binary_sensor {
|
||||
|
||||
static const char *const TAG = "sensor.filter";
|
||||
|
||||
void Filter::output(bool value, bool is_initial) {
|
||||
void Filter::output(bool value) {
|
||||
if (this->next_ == nullptr) {
|
||||
this->parent_->send_state_internal(value);
|
||||
} else {
|
||||
this->next_->input(value);
|
||||
}
|
||||
}
|
||||
void Filter::input(bool value) {
|
||||
if (!this->dedup_.next(value))
|
||||
return;
|
||||
|
||||
if (this->next_ == nullptr) {
|
||||
this->parent_->send_state_internal(value, is_initial);
|
||||
} else {
|
||||
this->next_->input(value, is_initial);
|
||||
}
|
||||
}
|
||||
void Filter::input(bool value, bool is_initial) {
|
||||
auto b = this->new_value(value, is_initial);
|
||||
auto b = this->new_value(value);
|
||||
if (b.has_value()) {
|
||||
this->output(*b, is_initial);
|
||||
this->output(*b);
|
||||
}
|
||||
}
|
||||
|
||||
optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> DelayedOnOffFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
|
||||
this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });
|
||||
} else {
|
||||
this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
|
||||
this->set_timeout("ON_OFF", this->off_delay_.value(), [this]() { this->output(false); });
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> DelayedOnFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
|
||||
this->set_timeout("ON", this->delay_.value(), [this]() { this->output(true); });
|
||||
return {};
|
||||
} else {
|
||||
this->cancel_timeout("ON");
|
||||
@@ -49,9 +48,9 @@ optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
|
||||
|
||||
float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> DelayedOffFilter::new_value(bool value) {
|
||||
if (!value) {
|
||||
this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
|
||||
this->set_timeout("OFF", this->delay_.value(), [this]() { this->output(false); });
|
||||
return {};
|
||||
} else {
|
||||
this->cancel_timeout("OFF");
|
||||
@@ -61,11 +60,11 @@ optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
|
||||
|
||||
float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; }
|
||||
optional<bool> InvertFilter::new_value(bool value) { return !value; }
|
||||
|
||||
AutorepeatFilter::AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings) : timings_(std::move(timings)) {}
|
||||
|
||||
optional<bool> AutorepeatFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> AutorepeatFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
// Ignore if already running
|
||||
if (this->active_timing_ != 0)
|
||||
@@ -101,7 +100,7 @@ void AutorepeatFilter::next_timing_() {
|
||||
|
||||
void AutorepeatFilter::next_value_(bool val) {
|
||||
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
|
||||
this->output(val, false); // This is at least the second one so not initial
|
||||
this->output(val); // This is at least the second one so not initial
|
||||
this->set_timeout("ON_OFF", val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); });
|
||||
}
|
||||
|
||||
@@ -109,18 +108,18 @@ float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARD
|
||||
|
||||
LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move(f)) {}
|
||||
|
||||
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
|
||||
optional<bool> LambdaFilter::new_value(bool value) { return this->f_(value); }
|
||||
|
||||
optional<bool> SettleFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> SettleFilter::new_value(bool value) {
|
||||
if (!this->steady_) {
|
||||
this->set_timeout("SETTLE", this->delay_.value(), [this, value, is_initial]() {
|
||||
this->set_timeout("SETTLE", this->delay_.value(), [this, value]() {
|
||||
this->steady_ = true;
|
||||
this->output(value, is_initial);
|
||||
this->output(value);
|
||||
});
|
||||
return {};
|
||||
} else {
|
||||
this->steady_ = false;
|
||||
this->output(value, is_initial);
|
||||
this->output(value);
|
||||
this->set_timeout("SETTLE", this->delay_.value(), [this]() { this->steady_ = true; });
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ class BinarySensor;
|
||||
|
||||
class Filter {
|
||||
public:
|
||||
virtual optional<bool> new_value(bool value, bool is_initial) = 0;
|
||||
virtual optional<bool> new_value(bool value) = 0;
|
||||
|
||||
void input(bool value, bool is_initial);
|
||||
void input(bool value);
|
||||
|
||||
void output(bool value, bool is_initial);
|
||||
void output(bool value);
|
||||
|
||||
protected:
|
||||
friend BinarySensor;
|
||||
@@ -30,7 +30,7 @@ class Filter {
|
||||
|
||||
class DelayedOnOffFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -44,7 +44,7 @@ class DelayedOnOffFilter : public Filter, public Component {
|
||||
|
||||
class DelayedOnFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -56,7 +56,7 @@ class DelayedOnFilter : public Filter, public Component {
|
||||
|
||||
class DelayedOffFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -68,7 +68,7 @@ class DelayedOffFilter : public Filter, public Component {
|
||||
|
||||
class InvertFilter : public Filter {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
};
|
||||
|
||||
struct AutorepeatFilterTiming {
|
||||
@@ -86,7 +86,7 @@ class AutorepeatFilter : public Filter, public Component {
|
||||
public:
|
||||
explicit AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings);
|
||||
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -102,7 +102,7 @@ class LambdaFilter : public Filter {
|
||||
public:
|
||||
explicit LambdaFilter(std::function<optional<bool>(bool)> f);
|
||||
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
protected:
|
||||
std::function<optional<bool>(bool)> f_;
|
||||
@@ -110,7 +110,7 @@ class LambdaFilter : public Filter {
|
||||
|
||||
class SettleFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
|
||||
@@ -93,8 +93,9 @@ void BME280Component::setup() {
|
||||
|
||||
// Mark as not failed before initializing. Some devices will turn off sensors to save on batteries
|
||||
// and when they come back on, the COMPONENT_STATE_FAILED bit must be unset on the component.
|
||||
if (this->is_failed()) {
|
||||
this->reset_to_construction_state();
|
||||
if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) {
|
||||
this->component_state_ &= ~COMPONENT_STATE_MASK;
|
||||
this->component_state_ |= COMPONENT_STATE_CONSTRUCTION;
|
||||
}
|
||||
|
||||
if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
|
||||
|
||||
@@ -3,4 +3,5 @@
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
|
||||
CONF_DRAW_ROUNDING = "draw_rounding"
|
||||
CONF_ON_STATE_CHANGE = "on_state_change"
|
||||
CONF_REQUEST_HEADERS = "request_headers"
|
||||
|
||||
@@ -11,25 +11,25 @@ static const char *const TAG = "datetime.date_entity";
|
||||
|
||||
void DateEntity::publish_state() {
|
||||
if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) {
|
||||
this->set_has_state(false);
|
||||
this->has_state_ = false;
|
||||
return;
|
||||
}
|
||||
if (this->year_ < 1970 || this->year_ > 3000) {
|
||||
this->set_has_state(false);
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Year must be between 1970 and 3000");
|
||||
return;
|
||||
}
|
||||
if (this->month_ < 1 || this->month_ > 12) {
|
||||
this->set_has_state(false);
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Month must be between 1 and 12");
|
||||
return;
|
||||
}
|
||||
if (this->day_ > days_in_month(this->month_, this->year_)) {
|
||||
this->set_has_state(false);
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_);
|
||||
return;
|
||||
}
|
||||
this->set_has_state(true);
|
||||
this->has_state_ = true;
|
||||
ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
|
||||
this->state_callback_.call();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ namespace datetime {
|
||||
|
||||
class DateTimeBase : public EntityBase {
|
||||
public:
|
||||
/// Return whether this Datetime has gotten a full state yet.
|
||||
bool has_state() const { return this->has_state_; }
|
||||
|
||||
virtual ESPTime state_as_esptime() const = 0;
|
||||
|
||||
void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
|
||||
@@ -28,6 +31,8 @@ class DateTimeBase : public EntityBase {
|
||||
#ifdef USE_TIME
|
||||
time::RealTimeClock *rtc_;
|
||||
#endif
|
||||
|
||||
bool has_state_{false};
|
||||
};
|
||||
|
||||
#ifdef USE_TIME
|
||||
|
||||
@@ -11,40 +11,40 @@ static const char *const TAG = "datetime.datetime_entity";
|
||||
|
||||
void DateTimeEntity::publish_state() {
|
||||
if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) {
|
||||
this->set_has_state(false);
|
||||
this->has_state_ = false;
|
||||
return;
|
||||
}
|
||||
if (this->year_ < 1970 || this->year_ > 3000) {
|
||||
this->set_has_state(false);
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Year must be between 1970 and 3000");
|
||||
return;
|
||||
}
|
||||
if (this->month_ < 1 || this->month_ > 12) {
|
||||
this->set_has_state(false);
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Month must be between 1 and 12");
|
||||
return;
|
||||
}
|
||||
if (this->day_ > days_in_month(this->month_, this->year_)) {
|
||||
this->set_has_state(false);
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_);
|
||||
return;
|
||||
}
|
||||
if (this->hour_ > 23) {
|
||||
this->set_has_state(false);
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Hour must be between 0 and 23");
|
||||
return;
|
||||
}
|
||||
if (this->minute_ > 59) {
|
||||
this->set_has_state(false);
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Minute must be between 0 and 59");
|
||||
return;
|
||||
}
|
||||
if (this->second_ > 59) {
|
||||
this->set_has_state(false);
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Second must be between 0 and 59");
|
||||
return;
|
||||
}
|
||||
this->set_has_state(true);
|
||||
this->has_state_ = true;
|
||||
ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_,
|
||||
this->month_, this->day_, this->hour_, this->minute_, this->second_);
|
||||
this->state_callback_.call();
|
||||
|
||||
@@ -11,21 +11,21 @@ static const char *const TAG = "datetime.time_entity";
|
||||
|
||||
void TimeEntity::publish_state() {
|
||||
if (this->hour_ > 23) {
|
||||
this->set_has_state(false);
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Hour must be between 0 and 23");
|
||||
return;
|
||||
}
|
||||
if (this->minute_ > 59) {
|
||||
this->set_has_state(false);
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Minute must be between 0 and 59");
|
||||
return;
|
||||
}
|
||||
if (this->second_ > 59) {
|
||||
this->set_has_state(false);
|
||||
this->has_state_ = false;
|
||||
ESP_LOGE(TAG, "Second must be between 0 and 59");
|
||||
return;
|
||||
}
|
||||
this->set_has_state(true);
|
||||
this->has_state_ = true;
|
||||
ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_,
|
||||
this->second_);
|
||||
this->state_callback_.call();
|
||||
|
||||
@@ -94,13 +94,6 @@ COMPILER_OPTIMIZATIONS = {
|
||||
"SIZE": "CONFIG_COMPILER_OPTIMIZATION_SIZE",
|
||||
}
|
||||
|
||||
ARDUINO_ALLOWED_VARIANTS = [
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
]
|
||||
|
||||
|
||||
def get_cpu_frequencies(*frequencies):
|
||||
return [str(x) + "MHZ" for x in frequencies]
|
||||
@@ -150,17 +143,12 @@ def set_core_data(config):
|
||||
CORE.data[KEY_ESP32][KEY_COMPONENTS] = {}
|
||||
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
|
||||
if variant not in ARDUINO_ALLOWED_VARIANTS:
|
||||
raise cv.Invalid(
|
||||
f"ESPHome does not support using the Arduino framework for the {variant}. Please use the ESP-IDF framework instead.",
|
||||
path=[CONF_FRAMEWORK, CONF_TYPE],
|
||||
)
|
||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
|
||||
config[CONF_FRAMEWORK][CONF_VERSION]
|
||||
)
|
||||
|
||||
CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD]
|
||||
CORE.data[KEY_ESP32][KEY_VARIANT] = variant
|
||||
CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT]
|
||||
CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES] = {}
|
||||
|
||||
return config
|
||||
@@ -630,21 +618,6 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def _set_default_framework(config):
|
||||
if CONF_FRAMEWORK not in config:
|
||||
config = config.copy()
|
||||
|
||||
variant = config[CONF_VARIANT]
|
||||
if variant in ARDUINO_ALLOWED_VARIANTS:
|
||||
config[CONF_FRAMEWORK] = ARDUINO_FRAMEWORK_SCHEMA({})
|
||||
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO
|
||||
else:
|
||||
config[CONF_FRAMEWORK] = ESP_IDF_FRAMEWORK_SCHEMA({})
|
||||
config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF
|
||||
|
||||
return config
|
||||
|
||||
|
||||
FRAMEWORK_ESP_IDF = "esp-idf"
|
||||
FRAMEWORK_ARDUINO = "arduino"
|
||||
FRAMEWORK_SCHEMA = cv.typed_schema(
|
||||
@@ -654,6 +627,7 @@ FRAMEWORK_SCHEMA = cv.typed_schema(
|
||||
},
|
||||
lower=True,
|
||||
space="-",
|
||||
default_type=FRAMEWORK_ARDUINO,
|
||||
)
|
||||
|
||||
|
||||
@@ -680,11 +654,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
cv.Optional(CONF_PARTITIONS): cv.file_,
|
||||
cv.Optional(CONF_VARIANT): cv.one_of(*VARIANTS, upper=True),
|
||||
cv.Optional(CONF_FRAMEWORK): FRAMEWORK_SCHEMA,
|
||||
cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA,
|
||||
}
|
||||
),
|
||||
_detect_variant,
|
||||
_set_default_framework,
|
||||
set_core_data,
|
||||
)
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace esp32_ble {
|
||||
|
||||
static const char *const TAG = "esp32_ble";
|
||||
|
||||
// Maximum size of the BLE event queue
|
||||
static constexpr size_t MAX_BLE_QUEUE_SIZE = SCAN_RESULT_BUFFER_SIZE * 2;
|
||||
|
||||
static RAMAllocator<BLEEvent> EVENT_ALLOCATOR( // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
RAMAllocator<BLEEvent>::ALLOW_FAILURE | RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
|
||||
|
||||
@@ -357,38 +360,21 @@ void ESP32BLE::loop() {
|
||||
if (this->advertising_ != nullptr) {
|
||||
this->advertising_->loop();
|
||||
}
|
||||
|
||||
// Log dropped events periodically
|
||||
size_t dropped = this->ble_events_.get_and_reset_dropped_count();
|
||||
if (dropped > 0) {
|
||||
ESP_LOGW(TAG, "Dropped %zu BLE events due to buffer overflow", dropped);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args> void enqueue_ble_event(Args... args) {
|
||||
// Check if queue is full before allocating
|
||||
if (global_ble->ble_events_.full()) {
|
||||
// Queue is full, drop the event
|
||||
global_ble->ble_events_.increment_dropped_count();
|
||||
if (global_ble->ble_events_.size() >= MAX_BLE_QUEUE_SIZE) {
|
||||
ESP_LOGD(TAG, "BLE event queue full (%zu), dropping event", MAX_BLE_QUEUE_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
|
||||
if (new_event == nullptr) {
|
||||
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
|
||||
global_ble->ble_events_.increment_dropped_count();
|
||||
return;
|
||||
}
|
||||
new (new_event) BLEEvent(args...);
|
||||
|
||||
// Push the event - since we're the only producer and we checked full() above,
|
||||
// this should always succeed unless we have a bug
|
||||
if (!global_ble->ble_events_.push(new_event)) {
|
||||
// This should not happen in SPSC queue with single producer
|
||||
ESP_LOGE(TAG, "BLE queue push failed unexpectedly");
|
||||
new_event->~BLEEvent();
|
||||
EVENT_ALLOCATOR.deallocate(new_event, 1);
|
||||
}
|
||||
global_ble->ble_events_.push(new_event);
|
||||
} // NOLINT(clang-analyzer-unix.Malloc)
|
||||
|
||||
// Explicit template instantiations for the friend function
|
||||
|
||||
@@ -30,9 +30,6 @@ static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 32;
|
||||
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 20;
|
||||
#endif
|
||||
|
||||
// Maximum size of the BLE event queue - must be power of 2 for lock-free queue
|
||||
static constexpr size_t MAX_BLE_QUEUE_SIZE = 64;
|
||||
|
||||
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address);
|
||||
|
||||
// NOLINTNEXTLINE(modernize-use-using)
|
||||
@@ -147,7 +144,7 @@ class ESP32BLE : public Component {
|
||||
std::vector<BLEStatusEventHandler *> ble_status_event_handlers_;
|
||||
BLEComponentState state_{BLE_COMPONENT_STATE_OFF};
|
||||
|
||||
LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_;
|
||||
Queue<BLEEvent> ble_events_;
|
||||
BLEAdvertising *advertising_{};
|
||||
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
|
||||
uint32_t advertising_cycle_time_{};
|
||||
|
||||
@@ -2,78 +2,63 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
/*
|
||||
* BLE events come in from a separate Task (thread) in the ESP32 stack. Rather
|
||||
* than using mutex-based locking, this lock-free queue allows the BLE
|
||||
* task to enqueue events without blocking. The main loop() then processes
|
||||
* these events at a safer time.
|
||||
*
|
||||
* This is a Single-Producer Single-Consumer (SPSC) lock-free ring buffer.
|
||||
* The BLE task is the only producer, and the main loop() is the only consumer.
|
||||
* than trying to deal with various locking strategies, all incoming GAP and GATT
|
||||
* events will simply be placed on a semaphore guarded queue. The next time the
|
||||
* component runs loop(), these events are popped off the queue and handed at
|
||||
* this safer time.
|
||||
*/
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
|
||||
template<class T, size_t SIZE> class LockFreeQueue {
|
||||
template<class T> class Queue {
|
||||
public:
|
||||
LockFreeQueue() : head_(0), tail_(0), dropped_count_(0) {}
|
||||
Queue() { m_ = xSemaphoreCreateMutex(); }
|
||||
|
||||
bool push(T *element) {
|
||||
void push(T *element) {
|
||||
if (element == nullptr)
|
||||
return false;
|
||||
|
||||
size_t current_tail = tail_.load(std::memory_order_relaxed);
|
||||
size_t next_tail = (current_tail + 1) % SIZE;
|
||||
|
||||
if (next_tail == head_.load(std::memory_order_acquire)) {
|
||||
// Buffer full
|
||||
dropped_count_.fetch_add(1, std::memory_order_relaxed);
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer_[current_tail] = element;
|
||||
tail_.store(next_tail, std::memory_order_release);
|
||||
return true;
|
||||
return;
|
||||
// It is not called from main loop. Thus it won't block main thread.
|
||||
xSemaphoreTake(m_, portMAX_DELAY);
|
||||
q_.push(element);
|
||||
xSemaphoreGive(m_);
|
||||
}
|
||||
|
||||
T *pop() {
|
||||
size_t current_head = head_.load(std::memory_order_relaxed);
|
||||
T *element = nullptr;
|
||||
|
||||
if (current_head == tail_.load(std::memory_order_acquire)) {
|
||||
return nullptr; // Empty
|
||||
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) {
|
||||
if (!q_.empty()) {
|
||||
element = q_.front();
|
||||
q_.pop();
|
||||
}
|
||||
xSemaphoreGive(m_);
|
||||
}
|
||||
|
||||
T *element = buffer_[current_head];
|
||||
head_.store((current_head + 1) % SIZE, std::memory_order_release);
|
||||
return element;
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
size_t tail = tail_.load(std::memory_order_acquire);
|
||||
size_t head = head_.load(std::memory_order_acquire);
|
||||
return (tail - head + SIZE) % SIZE;
|
||||
}
|
||||
|
||||
size_t get_and_reset_dropped_count() { return dropped_count_.exchange(0, std::memory_order_relaxed); }
|
||||
|
||||
void increment_dropped_count() { dropped_count_.fetch_add(1, std::memory_order_relaxed); }
|
||||
|
||||
bool empty() const { return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire); }
|
||||
|
||||
bool full() const {
|
||||
size_t next_tail = (tail_.load(std::memory_order_relaxed) + 1) % SIZE;
|
||||
return next_tail == head_.load(std::memory_order_acquire);
|
||||
// Lock-free size check. While std::queue::size() is not thread-safe, we intentionally
|
||||
// avoid locking here to prevent blocking the BLE callback thread. The size is only
|
||||
// used to decide whether to drop incoming events when the queue is near capacity.
|
||||
// With a queue limit of 40-64 events and normal processing, dropping events should
|
||||
// be extremely rare. When it does approach capacity, being off by 1-2 events is
|
||||
// acceptable to avoid blocking the BLE stack's time-sensitive callbacks.
|
||||
// Trade-off: We prefer occasional dropped events over potential BLE stack delays.
|
||||
return q_.size();
|
||||
}
|
||||
|
||||
protected:
|
||||
T *buffer_[SIZE];
|
||||
std::atomic<size_t> head_;
|
||||
std::atomic<size_t> tail_;
|
||||
std::atomic<size_t> dropped_count_;
|
||||
std::queue<T *> q_;
|
||||
SemaphoreHandle_t m_;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble
|
||||
|
||||
@@ -50,15 +50,16 @@ void ESP32BLETracker::setup() {
|
||||
ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE");
|
||||
return;
|
||||
}
|
||||
RAMAllocator<BLEScanResult> allocator;
|
||||
this->scan_ring_buffer_ = allocator.allocate(SCAN_RESULT_BUFFER_SIZE);
|
||||
ExternalRAMAllocator<BLEScanResult> allocator(ExternalRAMAllocator<BLEScanResult>::ALLOW_FAILURE);
|
||||
this->scan_result_buffer_ = allocator.allocate(SCAN_RESULT_BUFFER_SIZE);
|
||||
|
||||
if (this->scan_ring_buffer_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate ring buffer for BLE Tracker!");
|
||||
if (this->scan_result_buffer_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate buffer for BLE Tracker!");
|
||||
this->mark_failed();
|
||||
}
|
||||
|
||||
global_esp32_ble_tracker = this;
|
||||
this->scan_result_lock_ = xSemaphoreCreateMutex();
|
||||
|
||||
#ifdef USE_OTA
|
||||
ota::get_global_ota_callback()->add_on_state_callback(
|
||||
@@ -118,31 +119,27 @@ void ESP32BLETracker::loop() {
|
||||
}
|
||||
bool promote_to_connecting = discovered && !searching && !connecting;
|
||||
|
||||
// Process scan results from lock-free SPSC ring buffer
|
||||
// Consumer side: This runs in the main loop thread
|
||||
if (this->scanner_state_ == ScannerState::RUNNING) {
|
||||
// Load our own index with relaxed ordering (we're the only writer)
|
||||
size_t read_idx = this->ring_read_index_.load(std::memory_order_relaxed);
|
||||
if (this->scanner_state_ == ScannerState::RUNNING &&
|
||||
this->scan_result_index_ && // if it looks like we have a scan result we will take the lock
|
||||
xSemaphoreTake(this->scan_result_lock_, 0)) {
|
||||
uint32_t index = this->scan_result_index_;
|
||||
if (index >= SCAN_RESULT_BUFFER_SIZE) {
|
||||
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
|
||||
}
|
||||
|
||||
// Load producer's index with acquire to see their latest writes
|
||||
size_t write_idx = this->ring_write_index_.load(std::memory_order_acquire);
|
||||
|
||||
while (read_idx != write_idx) {
|
||||
// Process one result at a time directly from ring buffer
|
||||
BLEScanResult &scan_result = this->scan_ring_buffer_[read_idx];
|
||||
|
||||
if (this->raw_advertisements_) {
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->parse_devices(&scan_result, 1);
|
||||
}
|
||||
for (auto *client : this->clients_) {
|
||||
client->parse_devices(&scan_result, 1);
|
||||
}
|
||||
if (this->raw_advertisements_) {
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
|
||||
}
|
||||
for (auto *client : this->clients_) {
|
||||
client->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->parse_advertisements_) {
|
||||
if (this->parse_advertisements_) {
|
||||
for (size_t i = 0; i < index; i++) {
|
||||
ESPBTDevice device;
|
||||
device.parse_scan_rst(scan_result);
|
||||
device.parse_scan_rst(this->scan_result_buffer_[i]);
|
||||
|
||||
bool found = false;
|
||||
for (auto *listener : this->listeners_) {
|
||||
@@ -163,19 +160,9 @@ void ESP32BLETracker::loop() {
|
||||
this->print_bt_device_info(device);
|
||||
}
|
||||
}
|
||||
|
||||
// Move to next entry in ring buffer
|
||||
read_idx = (read_idx + 1) % SCAN_RESULT_BUFFER_SIZE;
|
||||
|
||||
// Store with release to ensure reads complete before index update
|
||||
this->ring_read_index_.store(read_idx, std::memory_order_release);
|
||||
}
|
||||
|
||||
// Log dropped results periodically
|
||||
size_t dropped = this->scan_results_dropped_.exchange(0, std::memory_order_relaxed);
|
||||
if (dropped > 0) {
|
||||
ESP_LOGW(TAG, "Dropped %zu BLE scan results due to buffer overflow", dropped);
|
||||
}
|
||||
this->scan_result_index_ = 0;
|
||||
xSemaphoreGive(this->scan_result_lock_);
|
||||
}
|
||||
if (this->scanner_state_ == ScannerState::STOPPED) {
|
||||
this->end_of_scan_(); // Change state to IDLE
|
||||
@@ -404,27 +391,12 @@ void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) {
|
||||
ESP_LOGV(TAG, "gap_scan_result - event %d", scan_result.search_evt);
|
||||
|
||||
if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
|
||||
// Lock-free SPSC ring buffer write (Producer side)
|
||||
// This runs in the ESP-IDF Bluetooth stack callback thread
|
||||
// IMPORTANT: Only this thread writes to ring_write_index_
|
||||
|
||||
// Load our own index with relaxed ordering (we're the only writer)
|
||||
size_t write_idx = this->ring_write_index_.load(std::memory_order_relaxed);
|
||||
size_t next_write_idx = (write_idx + 1) % SCAN_RESULT_BUFFER_SIZE;
|
||||
|
||||
// Load consumer's index with acquire to see their latest updates
|
||||
size_t read_idx = this->ring_read_index_.load(std::memory_order_acquire);
|
||||
|
||||
// Check if buffer is full
|
||||
if (next_write_idx != read_idx) {
|
||||
// Write to ring buffer
|
||||
this->scan_ring_buffer_[write_idx] = scan_result;
|
||||
|
||||
// Store with release to ensure the write is visible before index update
|
||||
this->ring_write_index_.store(next_write_idx, std::memory_order_release);
|
||||
} else {
|
||||
// Buffer full, track dropped results
|
||||
this->scan_results_dropped_.fetch_add(1, std::memory_order_relaxed);
|
||||
if (xSemaphoreTake(this->scan_result_lock_, 0)) {
|
||||
if (this->scan_result_index_ < SCAN_RESULT_BUFFER_SIZE) {
|
||||
// Store BLEScanResult directly in our buffer
|
||||
this->scan_result_buffer_[this->scan_result_index_++] = scan_result;
|
||||
}
|
||||
xSemaphoreGive(this->scan_result_lock_);
|
||||
}
|
||||
} else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
|
||||
// Scan finished on its own
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -283,16 +282,9 @@ class ESP32BLETracker : public Component,
|
||||
bool ble_was_disabled_{true};
|
||||
bool raw_advertisements_{false};
|
||||
bool parse_advertisements_{false};
|
||||
|
||||
// Lock-free Single-Producer Single-Consumer (SPSC) ring buffer for scan results
|
||||
// Producer: ESP-IDF Bluetooth stack callback (gap_scan_event_handler)
|
||||
// Consumer: ESPHome main loop (loop() method)
|
||||
// This design ensures zero blocking in the BT callback and prevents scan result loss
|
||||
BLEScanResult *scan_ring_buffer_;
|
||||
std::atomic<size_t> ring_write_index_{0}; // Written only by BT callback (producer)
|
||||
std::atomic<size_t> ring_read_index_{0}; // Written only by main loop (consumer)
|
||||
std::atomic<size_t> scan_results_dropped_{0}; // Tracks buffer overflow events
|
||||
|
||||
SemaphoreHandle_t scan_result_lock_;
|
||||
size_t scan_result_index_{0};
|
||||
BLEScanResult *scan_result_buffer_;
|
||||
esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
|
||||
esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS};
|
||||
int connecting_{0};
|
||||
|
||||
@@ -57,7 +57,7 @@ void ESP32Camera::dump_config() {
|
||||
" External Clock: Pin:%d Frequency:%u\n"
|
||||
" I2C Pins: SDA:%d SCL:%d\n"
|
||||
" Reset Pin: %d",
|
||||
this->name_.c_str(), YESNO(this->is_internal()), conf.pin_d0, conf.pin_d1, conf.pin_d2, conf.pin_d3,
|
||||
this->name_.c_str(), YESNO(this->internal_), conf.pin_d0, conf.pin_d1, conf.pin_d2, conf.pin_d3,
|
||||
conf.pin_d4, conf.pin_d5, conf.pin_d6, conf.pin_d7, conf.pin_vsync, conf.pin_href, conf.pin_pclk,
|
||||
conf.pin_xclk, conf.xclk_freq_hz, conf.pin_sccb_sda, conf.pin_sccb_scl, conf.pin_reset);
|
||||
switch (this->config_.frame_size) {
|
||||
|
||||
@@ -41,48 +41,39 @@ void FanCall::perform() {
|
||||
void FanCall::validate_() {
|
||||
auto traits = this->parent_.get_traits();
|
||||
|
||||
if (this->speed_.has_value()) {
|
||||
if (this->speed_.has_value())
|
||||
this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count());
|
||||
|
||||
// https://developers.home-assistant.io/docs/core/entity/fan/#preset-modes
|
||||
// "Manually setting a speed must disable any set preset mode"
|
||||
this->preset_mode_.clear();
|
||||
if (this->binary_state_.has_value() && *this->binary_state_) {
|
||||
// when turning on, if neither current nor new speed available, set speed to 100%
|
||||
if (traits.supports_speed() && !this->parent_.state && this->parent_.speed == 0 && !this->speed_.has_value()) {
|
||||
this->speed_ = traits.supported_speed_count();
|
||||
}
|
||||
}
|
||||
|
||||
if (this->oscillating_.has_value() && !traits.supports_oscillation()) {
|
||||
ESP_LOGW(TAG, "'%s' - This fan does not support oscillation!", this->parent_.get_name().c_str());
|
||||
this->oscillating_.reset();
|
||||
}
|
||||
|
||||
if (this->speed_.has_value() && !traits.supports_speed()) {
|
||||
ESP_LOGW(TAG, "'%s' - This fan does not support speeds!", this->parent_.get_name().c_str());
|
||||
this->speed_.reset();
|
||||
}
|
||||
|
||||
if (this->direction_.has_value() && !traits.supports_direction()) {
|
||||
ESP_LOGW(TAG, "'%s' - This fan does not support directions!", this->parent_.get_name().c_str());
|
||||
this->direction_.reset();
|
||||
}
|
||||
|
||||
if (!this->preset_mode_.empty()) {
|
||||
const auto &preset_modes = traits.supported_preset_modes();
|
||||
if (preset_modes.find(this->preset_mode_) == preset_modes.end()) {
|
||||
ESP_LOGW(TAG, "%s: Preset mode '%s' not supported", this->parent_.get_name().c_str(), this->preset_mode_.c_str());
|
||||
ESP_LOGW(TAG, "'%s' - This fan does not support preset mode '%s'!", this->parent_.get_name().c_str(),
|
||||
this->preset_mode_.c_str());
|
||||
this->preset_mode_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// when turning on...
|
||||
if (!this->parent_.state && this->binary_state_.has_value() &&
|
||||
*this->binary_state_
|
||||
// ..,and no preset mode will be active...
|
||||
&& this->preset_mode_.empty() &&
|
||||
this->parent_.preset_mode.empty()
|
||||
// ...and neither current nor new speed is available...
|
||||
&& traits.supports_speed() && this->parent_.speed == 0 && !this->speed_.has_value()) {
|
||||
// ...set speed to 100%
|
||||
this->speed_ = traits.supported_speed_count();
|
||||
}
|
||||
|
||||
if (this->oscillating_.has_value() && !traits.supports_oscillation()) {
|
||||
ESP_LOGW(TAG, "%s: Oscillation not supported", this->parent_.get_name().c_str());
|
||||
this->oscillating_.reset();
|
||||
}
|
||||
|
||||
if (this->speed_.has_value() && !traits.supports_speed()) {
|
||||
ESP_LOGW(TAG, "%s: Speed control not supported", this->parent_.get_name().c_str());
|
||||
this->speed_.reset();
|
||||
}
|
||||
|
||||
if (this->direction_.has_value() && !traits.supports_direction()) {
|
||||
ESP_LOGW(TAG, "%s: Direction control not supported", this->parent_.get_name().c_str());
|
||||
this->direction_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
FanCall FanRestoreState::to_call(Fan &fan) {
|
||||
|
||||
@@ -317,18 +317,15 @@ void I2SAudioMicrophone::stop_driver_() {
|
||||
ESP_LOGW(TAG, "Error uninstalling I2S driver - it may not have started: %s", esp_err_to_name(err));
|
||||
}
|
||||
#else
|
||||
if (this->rx_handle_ != nullptr) {
|
||||
/* Have to stop the channel before deleting it */
|
||||
err = i2s_channel_disable(this->rx_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error stopping I2S microphone - it may not have started: %s", esp_err_to_name(err));
|
||||
}
|
||||
/* If the handle is not needed any more, delete it to release the channel resources */
|
||||
err = i2s_del_channel(this->rx_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error deleting I2S channel - it may not have started: %s", esp_err_to_name(err));
|
||||
}
|
||||
this->rx_handle_ = nullptr;
|
||||
/* Have to stop the channel before deleting it */
|
||||
err = i2s_channel_disable(this->rx_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error stopping I2S microphone - it may not have started: %s", esp_err_to_name(err));
|
||||
}
|
||||
/* If the handle is not needed any more, delete it to release the channel resources */
|
||||
err = i2s_del_channel(this->rx_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error deleting I2S channel - it may not have started: %s", esp_err_to_name(err));
|
||||
}
|
||||
#endif
|
||||
this->parent_->unlock();
|
||||
|
||||
@@ -19,8 +19,9 @@ void KMeterISOComponent::setup() {
|
||||
|
||||
// Mark as not failed before initializing. Some devices will turn off sensors to save on batteries
|
||||
// and when they come back on, the COMPONENT_STATE_FAILED bit must be unset on the component.
|
||||
if (this->is_failed()) {
|
||||
this->reset_to_construction_state();
|
||||
if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) {
|
||||
this->component_state_ &= ~COMPONENT_STATE_MASK;
|
||||
this->component_state_ |= COMPONENT_STATE_CONSTRUCTION;
|
||||
}
|
||||
|
||||
auto err = this->bus_->writev(this->address_, nullptr, 0);
|
||||
|
||||
@@ -50,7 +50,7 @@ MCP23016_PIN_SCHEMA = pins.gpio_base_schema(
|
||||
cv.int_range(min=0, max=15),
|
||||
modes=[CONF_INPUT, CONF_OUTPUT],
|
||||
mode_validator=validate_mode,
|
||||
invertable=True,
|
||||
invertible=True,
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_MCP23016): cv.use_id(MCP23016),
|
||||
|
||||
@@ -60,7 +60,7 @@ MCP23XXX_PIN_SCHEMA = pins.gpio_base_schema(
|
||||
cv.int_range(min=0, max=15),
|
||||
modes=[CONF_INPUT, CONF_OUTPUT, CONF_PULLUP],
|
||||
mode_validator=validate_mode,
|
||||
invertable=True,
|
||||
invertible=True,
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_MCP23XXX): cv.use_id(MCP23XXXBase),
|
||||
|
||||
@@ -153,7 +153,7 @@ bool MQTTComponent::send_discovery_() {
|
||||
if (node_friendly_name.empty()) {
|
||||
node_friendly_name = node_name;
|
||||
}
|
||||
std::string node_area = App.get_area();
|
||||
const std::string &node_area = App.get_area();
|
||||
|
||||
JsonObject device_info = root.createNestedObject(MQTT_DEVICE);
|
||||
const auto mac = get_mac_address();
|
||||
|
||||
@@ -56,7 +56,7 @@ void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nexti
|
||||
this->publish_state(state);
|
||||
} else {
|
||||
this->state = state;
|
||||
this->set_has_state(true);
|
||||
this->has_state_ = true;
|
||||
}
|
||||
|
||||
this->update_component_settings();
|
||||
|
||||
@@ -337,26 +337,23 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
|
||||
|
||||
bool Nextion::upload_end_(bool successful) {
|
||||
ESP_LOGD(TAG, "TFT upload done: %s", YESNO(successful));
|
||||
this->is_updating_ = false;
|
||||
this->ignore_is_setup_ = false;
|
||||
|
||||
uint32_t baud_rate = this->parent_->get_baud_rate();
|
||||
if (baud_rate != this->original_baud_rate_) {
|
||||
ESP_LOGD(TAG, "Baud back: %" PRIu32 "->%" PRIu32, baud_rate, this->original_baud_rate_);
|
||||
this->parent_->set_baud_rate(this->original_baud_rate_);
|
||||
this->parent_->load_settings();
|
||||
}
|
||||
|
||||
if (successful) {
|
||||
ESP_LOGD(TAG, "Restart");
|
||||
delay(1500); // NOLINT
|
||||
App.safe_reboot();
|
||||
delay(1500); // NOLINT
|
||||
} else {
|
||||
ESP_LOGE(TAG, "TFT upload failed");
|
||||
|
||||
this->is_updating_ = false;
|
||||
this->ignore_is_setup_ = false;
|
||||
|
||||
uint32_t baud_rate = this->parent_->get_baud_rate();
|
||||
if (baud_rate != this->original_baud_rate_) {
|
||||
ESP_LOGD(TAG, "Baud back: %" PRIu32 "->%" PRIu32, baud_rate, this->original_baud_rate_);
|
||||
this->parent_->set_baud_rate(this->original_baud_rate_);
|
||||
this->parent_->load_settings();
|
||||
}
|
||||
}
|
||||
|
||||
return successful;
|
||||
}
|
||||
|
||||
|
||||
@@ -337,6 +337,15 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
|
||||
|
||||
bool Nextion::upload_end_(bool successful) {
|
||||
ESP_LOGD(TAG, "TFT upload done: %s", YESNO(successful));
|
||||
this->is_updating_ = false;
|
||||
this->ignore_is_setup_ = false;
|
||||
|
||||
uint32_t baud_rate = this->parent_->get_baud_rate();
|
||||
if (baud_rate != this->original_baud_rate_) {
|
||||
ESP_LOGD(TAG, "Baud back: %" PRIu32 "->%" PRIu32, baud_rate, this->original_baud_rate_);
|
||||
this->parent_->set_baud_rate(this->original_baud_rate_);
|
||||
this->parent_->load_settings();
|
||||
}
|
||||
|
||||
if (successful) {
|
||||
ESP_LOGD(TAG, "Restart");
|
||||
@@ -344,18 +353,7 @@ bool Nextion::upload_end_(bool successful) {
|
||||
App.safe_reboot();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "TFT upload failed");
|
||||
|
||||
this->is_updating_ = false;
|
||||
this->ignore_is_setup_ = false;
|
||||
|
||||
uint32_t baud_rate = this->parent_->get_baud_rate();
|
||||
if (baud_rate != this->original_baud_rate_) {
|
||||
ESP_LOGD(TAG, "Baud back: %" PRIu32 "->%" PRIu32, baud_rate, this->original_baud_rate_);
|
||||
this->parent_->set_baud_rate(this->original_baud_rate_);
|
||||
this->parent_->load_settings();
|
||||
}
|
||||
}
|
||||
|
||||
return successful;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) {
|
||||
} else {
|
||||
this->raw_state = state;
|
||||
this->state = state;
|
||||
this->set_has_state(true);
|
||||
this->has_state_ = true;
|
||||
}
|
||||
}
|
||||
this->update_component_settings();
|
||||
|
||||
@@ -37,7 +37,7 @@ void NextionTextSensor::set_state(const std::string &state, bool publish, bool s
|
||||
this->publish_state(state);
|
||||
} else {
|
||||
this->state = state;
|
||||
this->set_has_state(true);
|
||||
this->has_state_ = true;
|
||||
}
|
||||
|
||||
this->update_component_settings();
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace number {
|
||||
static const char *const TAG = "number";
|
||||
|
||||
void Number::publish_state(float state) {
|
||||
this->set_has_state(true);
|
||||
this->has_state_ = true;
|
||||
this->state = state;
|
||||
ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state);
|
||||
this->state_callback_.call(state);
|
||||
|
||||
@@ -48,6 +48,9 @@ class Number : public EntityBase {
|
||||
|
||||
NumberTraits traits;
|
||||
|
||||
/// Return whether this number has gotten a full state yet.
|
||||
bool has_state() const { return has_state_; }
|
||||
|
||||
protected:
|
||||
friend class NumberCall;
|
||||
|
||||
@@ -60,6 +63,7 @@ class Number : public EntityBase {
|
||||
virtual void control(float value) = 0;
|
||||
|
||||
CallbackManager<void(float)> state_callback_;
|
||||
bool has_state_{false};
|
||||
};
|
||||
|
||||
} // namespace number
|
||||
|
||||
@@ -53,7 +53,7 @@ PCF8574_PIN_SCHEMA = pins.gpio_base_schema(
|
||||
cv.int_range(min=0, max=17),
|
||||
modes=[CONF_INPUT, CONF_OUTPUT],
|
||||
mode_validator=validate_mode,
|
||||
invertable=True,
|
||||
invertible=True,
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_PCF8574): cv.use_id(PCF8574Component),
|
||||
|
||||
@@ -31,6 +31,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
}
|
||||
),
|
||||
},
|
||||
cv.only_with_arduino,
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
|
||||
26
esphome/components/runtime_stats/__init__.py
Normal file
26
esphome/components/runtime_stats/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Runtime statistics component for ESPHome.
|
||||
"""
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
DEPENDENCIES = []
|
||||
|
||||
CONF_ENABLED = "enabled"
|
||||
CONF_LOG_INTERVAL = "log_interval"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ENABLED, default=True): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_LOG_INTERVAL, default=60000
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
"""Generate code for the runtime statistics component."""
|
||||
cg.add(cg.App.set_runtime_stats_enabled(config[CONF_ENABLED]))
|
||||
cg.add(cg.App.set_runtime_stats_log_interval(config[CONF_LOG_INTERVAL]))
|
||||
@@ -10,7 +10,7 @@ void Select::publish_state(const std::string &state) {
|
||||
auto index = this->index_of(state);
|
||||
const auto *name = this->get_name().c_str();
|
||||
if (index.has_value()) {
|
||||
this->set_has_state(true);
|
||||
this->has_state_ = true;
|
||||
this->state = state;
|
||||
ESP_LOGD(TAG, "'%s': Sending state %s (index %zu)", name, state.c_str(), index.value());
|
||||
this->state_callback_.call(state, index.value());
|
||||
|
||||
@@ -35,6 +35,9 @@ class Select : public EntityBase {
|
||||
|
||||
void publish_state(const std::string &state);
|
||||
|
||||
/// Return whether this select component has gotten a full state yet.
|
||||
bool has_state() const { return has_state_; }
|
||||
|
||||
/// Instantiate a SelectCall object to modify this select component's state.
|
||||
SelectCall make_call() { return SelectCall(this); }
|
||||
|
||||
@@ -70,6 +73,7 @@ class Select : public EntityBase {
|
||||
virtual void control(const std::string &value) = 0;
|
||||
|
||||
CallbackManager<void(std::string, size_t)> state_callback_;
|
||||
bool has_state_{false};
|
||||
};
|
||||
|
||||
} // namespace select
|
||||
|
||||
@@ -38,9 +38,7 @@ StateClass Sensor::get_state_class() {
|
||||
|
||||
void Sensor::publish_state(float state) {
|
||||
this->raw_state = state;
|
||||
if (this->raw_callback_) {
|
||||
this->raw_callback_->call(state);
|
||||
}
|
||||
this->raw_callback_.call(state);
|
||||
|
||||
ESP_LOGV(TAG, "'%s': Received new state %f", this->name_.c_str(), state);
|
||||
|
||||
@@ -53,10 +51,7 @@ void Sensor::publish_state(float state) {
|
||||
|
||||
void Sensor::add_on_state_callback(std::function<void(float)> &&callback) { this->callback_.add(std::move(callback)); }
|
||||
void Sensor::add_on_raw_state_callback(std::function<void(float)> &&callback) {
|
||||
if (!this->raw_callback_) {
|
||||
this->raw_callback_ = make_unique<CallbackManager<void(float)>>();
|
||||
}
|
||||
this->raw_callback_->add(std::move(callback));
|
||||
this->raw_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void Sensor::add_filter(Filter *filter) {
|
||||
@@ -93,12 +88,13 @@ float Sensor::get_raw_state() const { return this->raw_state; }
|
||||
std::string Sensor::unique_id() { return ""; }
|
||||
|
||||
void Sensor::internal_send_state_to_frontend(float state) {
|
||||
this->set_has_state(true);
|
||||
this->has_state_ = true;
|
||||
this->state = state;
|
||||
ESP_LOGD(TAG, "'%s': Sending state %.5f %s with %d decimals of accuracy", this->get_name().c_str(), state,
|
||||
this->get_unit_of_measurement().c_str(), this->get_accuracy_decimals());
|
||||
this->callback_.call(state);
|
||||
}
|
||||
bool Sensor::has_state() const { return this->has_state_; }
|
||||
|
||||
} // namespace sensor
|
||||
} // namespace esphome
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "esphome/components/sensor/filter.h"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace esphome {
|
||||
namespace sensor {
|
||||
@@ -141,6 +140,9 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
|
||||
*/
|
||||
float raw_state;
|
||||
|
||||
/// Return whether this sensor has gotten a full state (that passed through all filters) yet.
|
||||
bool has_state() const;
|
||||
|
||||
/** Override this method to set the unique ID of this sensor.
|
||||
*
|
||||
* @deprecated Do not use for new sensors, a suitable unique ID is automatically generated (2023.4).
|
||||
@@ -150,14 +152,15 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
|
||||
void internal_send_state_to_frontend(float state);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<CallbackManager<void(float)>> raw_callback_; ///< Storage for raw state callbacks (lazy allocated).
|
||||
CallbackManager<void(float)> callback_; ///< Storage for filtered state callbacks.
|
||||
CallbackManager<void(float)> raw_callback_; ///< Storage for raw state callbacks.
|
||||
CallbackManager<void(float)> callback_; ///< Storage for filtered state callbacks.
|
||||
|
||||
Filter *filter_list_{nullptr}; ///< Store all active filters.
|
||||
|
||||
optional<int8_t> accuracy_decimals_; ///< Accuracy in decimals override
|
||||
optional<StateClass> state_class_{STATE_CLASS_NONE}; ///< State class override
|
||||
bool force_update_{false}; ///< Force update mode
|
||||
bool has_state_{false};
|
||||
};
|
||||
|
||||
} // namespace sensor
|
||||
|
||||
@@ -95,7 +95,7 @@ SN74HC595_PIN_SCHEMA = pins.gpio_base_schema(
|
||||
cv.int_range(min=0, max=2047),
|
||||
modes=[CONF_OUTPUT],
|
||||
mode_validator=_validate_output_mode,
|
||||
invertable=True,
|
||||
invertible=True,
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_SN74HC595): cv.use_id(SN74HC595Component),
|
||||
|
||||
@@ -9,10 +9,10 @@ namespace status_led {
|
||||
static const char *const TAG = "status_led";
|
||||
|
||||
void StatusLEDLightOutput::loop() {
|
||||
uint8_t new_state = App.get_app_state() & STATUS_LED_MASK;
|
||||
uint32_t new_state = App.get_app_state() & STATUS_LED_MASK;
|
||||
|
||||
if (new_state != this->last_app_state_) {
|
||||
ESP_LOGV(TAG, "New app state 0x%02X", new_state);
|
||||
ESP_LOGV(TAG, "New app state 0x%08" PRIX32, new_state);
|
||||
}
|
||||
|
||||
if ((new_state & STATUS_LED_ERROR) != 0u) {
|
||||
|
||||
@@ -36,7 +36,7 @@ class StatusLEDLightOutput : public light::LightOutput, public Component {
|
||||
GPIOPin *pin_{nullptr};
|
||||
output::BinaryOutput *output_{nullptr};
|
||||
light::LightState *lightstate_{};
|
||||
uint8_t last_app_state_{0xFF};
|
||||
uint32_t last_app_state_{0xFFFF};
|
||||
void output_state_(bool state);
|
||||
};
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ TCA9555_PIN_SCHEMA = pins.gpio_base_schema(
|
||||
cv.int_range(min=0, max=15),
|
||||
modes=[CONF_INPUT, CONF_OUTPUT],
|
||||
mode_validator=validate_mode,
|
||||
invertable=True,
|
||||
invertible=True,
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_TCA9555): cv.use_id(TCA9555Component),
|
||||
|
||||
@@ -110,7 +110,15 @@ void TemplateAlarmControlPanel::loop() {
|
||||
delay = this->arming_night_time_;
|
||||
}
|
||||
if ((millis() - this->last_update_) > delay) {
|
||||
this->bypass_before_arming();
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto sensor_info : this->sensor_map_) {
|
||||
// Check for sensors left on and set to bypass automatically and remove them from monitoring
|
||||
if ((sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (sensor_info.first->state)) {
|
||||
ESP_LOGW(TAG, "%s is left on and will be automatically bypassed", sensor_info.first->get_name().c_str());
|
||||
this->bypassed_sensor_indicies_.push_back(sensor_info.second.store_index);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
this->publish_state(this->desired_state_);
|
||||
}
|
||||
return;
|
||||
@@ -251,23 +259,10 @@ void TemplateAlarmControlPanel::arm_(optional<std::string> code, alarm_control_p
|
||||
if (delay > 0) {
|
||||
this->publish_state(ACP_STATE_ARMING);
|
||||
} else {
|
||||
this->bypass_before_arming();
|
||||
this->publish_state(state);
|
||||
}
|
||||
}
|
||||
|
||||
void TemplateAlarmControlPanel::bypass_before_arming() {
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto sensor_info : this->sensor_map_) {
|
||||
// Check for sensors left on and set to bypass automatically and remove them from monitoring
|
||||
if ((sensor_info.second.flags & BINARY_SENSOR_MODE_BYPASS_AUTO) && (sensor_info.first->state)) {
|
||||
ESP_LOGW(TAG, "'%s' is left on and will be automatically bypassed", sensor_info.first->get_name().c_str());
|
||||
this->bypassed_sensor_indicies_.push_back(sensor_info.second.store_index);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void TemplateAlarmControlPanel::control(const AlarmControlPanelCall &call) {
|
||||
if (call.get_state()) {
|
||||
if (call.get_state() == ACP_STATE_ARMED_AWAY) {
|
||||
|
||||
@@ -60,7 +60,6 @@ class TemplateAlarmControlPanel : public alarm_control_panel::AlarmControlPanel,
|
||||
bool get_requires_code_to_arm() const override { return this->requires_code_to_arm_; }
|
||||
bool get_all_sensors_ready() { return this->sensors_ready_; };
|
||||
void set_restore_mode(TemplateAlarmControlPanelRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
|
||||
void bypass_before_arming();
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
/** Add a binary_sensor to the alarm_panel.
|
||||
|
||||
@@ -6,16 +6,8 @@ namespace template_ {
|
||||
|
||||
static const char *const TAG = "template.binary_sensor";
|
||||
|
||||
void TemplateBinarySensor::setup() {
|
||||
if (!this->publish_initial_state_)
|
||||
return;
|
||||
void TemplateBinarySensor::setup() { this->loop(); }
|
||||
|
||||
if (this->f_ != nullptr) {
|
||||
this->publish_initial_state(this->f_().value_or(false));
|
||||
} else {
|
||||
this->publish_initial_state(false);
|
||||
}
|
||||
}
|
||||
void TemplateBinarySensor::loop() {
|
||||
if (this->f_ == nullptr)
|
||||
return;
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace text {
|
||||
static const char *const TAG = "text";
|
||||
|
||||
void Text::publish_state(const std::string &state) {
|
||||
this->set_has_state(true);
|
||||
this->has_state_ = true;
|
||||
this->state = state;
|
||||
if (this->traits.get_mode() == TEXT_MODE_PASSWORD) {
|
||||
ESP_LOGD(TAG, "'%s': Sending state " LOG_SECRET("'%s'"), this->get_name().c_str(), state.c_str());
|
||||
|
||||
@@ -28,6 +28,9 @@ class Text : public EntityBase {
|
||||
|
||||
void publish_state(const std::string &state);
|
||||
|
||||
/// Return whether this text input has gotten a full state yet.
|
||||
bool has_state() const { return has_state_; }
|
||||
|
||||
/// Instantiate a TextCall object to modify this text component's state.
|
||||
TextCall make_call() { return TextCall(this); }
|
||||
|
||||
@@ -45,6 +48,7 @@ class Text : public EntityBase {
|
||||
virtual void control(const std::string &value) = 0;
|
||||
|
||||
CallbackManager<void(std::string)> state_callback_;
|
||||
bool has_state_{false};
|
||||
};
|
||||
|
||||
} // namespace text
|
||||
|
||||
@@ -8,9 +8,7 @@ static const char *const TAG = "text_sensor";
|
||||
|
||||
void TextSensor::publish_state(const std::string &state) {
|
||||
this->raw_state = state;
|
||||
if (this->raw_callback_) {
|
||||
this->raw_callback_->call(state);
|
||||
}
|
||||
this->raw_callback_.call(state);
|
||||
|
||||
ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), state.c_str());
|
||||
|
||||
@@ -55,22 +53,20 @@ void TextSensor::add_on_state_callback(std::function<void(std::string)> callback
|
||||
this->callback_.add(std::move(callback));
|
||||
}
|
||||
void TextSensor::add_on_raw_state_callback(std::function<void(std::string)> callback) {
|
||||
if (!this->raw_callback_) {
|
||||
this->raw_callback_ = make_unique<CallbackManager<void(std::string)>>();
|
||||
}
|
||||
this->raw_callback_->add(std::move(callback));
|
||||
this->raw_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
std::string TextSensor::get_state() const { return this->state; }
|
||||
std::string TextSensor::get_raw_state() const { return this->raw_state; }
|
||||
void TextSensor::internal_send_state_to_frontend(const std::string &state) {
|
||||
this->state = state;
|
||||
this->set_has_state(true);
|
||||
this->has_state_ = true;
|
||||
ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str());
|
||||
this->callback_.call(state);
|
||||
}
|
||||
|
||||
std::string TextSensor::unique_id() { return ""; }
|
||||
bool TextSensor::has_state() { return this->has_state_; }
|
||||
|
||||
} // namespace text_sensor
|
||||
} // namespace esphome
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "esphome/components/text_sensor/filter.h"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace esphome {
|
||||
namespace text_sensor {
|
||||
@@ -34,8 +33,6 @@ namespace text_sensor {
|
||||
|
||||
class TextSensor : public EntityBase, public EntityBase_DeviceClass {
|
||||
public:
|
||||
TextSensor() = default;
|
||||
|
||||
/// Getter-syntax for .state.
|
||||
std::string get_state() const;
|
||||
/// Getter-syntax for .raw_state
|
||||
@@ -70,14 +67,17 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass {
|
||||
*/
|
||||
virtual std::string unique_id();
|
||||
|
||||
bool has_state();
|
||||
|
||||
void internal_send_state_to_frontend(const std::string &state);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<CallbackManager<void(std::string)>>
|
||||
raw_callback_; ///< Storage for raw state callbacks (lazy allocated).
|
||||
CallbackManager<void(std::string)> callback_; ///< Storage for filtered state callbacks.
|
||||
CallbackManager<void(std::string)> raw_callback_; ///< Storage for raw state callbacks.
|
||||
CallbackManager<void(std::string)> callback_; ///< Storage for filtered state callbacks.
|
||||
|
||||
Filter *filter_list_{nullptr}; ///< Store all active filters.
|
||||
|
||||
bool has_state_{false};
|
||||
};
|
||||
|
||||
} // namespace text_sensor
|
||||
|
||||
@@ -30,7 +30,7 @@ void UpdateEntity::publish_state() {
|
||||
ESP_LOGD(TAG, " Progress: %.0f%%", this->update_info_.progress);
|
||||
}
|
||||
|
||||
this->set_has_state(true);
|
||||
this->has_state_ = true;
|
||||
this->state_callback_.call();
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ enum UpdateState : uint8_t {
|
||||
|
||||
class UpdateEntity : public EntityBase, public EntityBase_DeviceClass {
|
||||
public:
|
||||
bool has_state() const { return this->has_state_; }
|
||||
|
||||
void publish_state();
|
||||
|
||||
void perform() { this->perform(false); }
|
||||
@@ -42,6 +44,7 @@ class UpdateEntity : public EntityBase, public EntityBase_DeviceClass {
|
||||
protected:
|
||||
UpdateState state_{UPDATE_STATE_UNKNOWN};
|
||||
UpdateInfo update_info_;
|
||||
bool has_state_{false};
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ static const char *const TAG = "uptime.sensor";
|
||||
|
||||
void UptimeTimestampSensor::setup() {
|
||||
this->time_->add_on_time_sync_callback([this]() {
|
||||
if (this->has_state())
|
||||
if (this->has_state_)
|
||||
return; // No need to update the timestamp if it's already set
|
||||
|
||||
auto now = this->time_->now();
|
||||
|
||||
@@ -555,7 +555,7 @@ std::string WebServer::button_json(button::Button *obj, JsonDetail start_config)
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
|
||||
void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) {
|
||||
if (this->events_.empty())
|
||||
return;
|
||||
this->events_.deferrable_send_state(obj, "state", binary_sensor_state_json_generator);
|
||||
|
||||
@@ -269,7 +269,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override;
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override;
|
||||
|
||||
/// Handle a binary sensor request under '/binary_sensor/<id>'.
|
||||
void handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
@@ -8,6 +8,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.only_with_esp_idf,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["web_server"]
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
# Increase the maximum supported size of headers section in HTTP request packet to be processed by the server
|
||||
|
||||
@@ -9,12 +9,10 @@
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include "web_server_idf.h"
|
||||
|
||||
#ifdef USE_WEBSERVER
|
||||
#include "esphome/components/web_server/web_server.h"
|
||||
#include "esphome/components/web_server/list_entities.h"
|
||||
#endif // USE_WEBSERVER
|
||||
|
||||
#include "web_server_idf.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server_idf {
|
||||
@@ -275,7 +273,6 @@ void AsyncResponseStream::printf(const char *fmt, ...) {
|
||||
this->print(str);
|
||||
}
|
||||
|
||||
#ifdef USE_WEBSERVER
|
||||
AsyncEventSource::~AsyncEventSource() {
|
||||
for (auto *ses : this->sessions_) {
|
||||
delete ses; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
@@ -514,7 +511,6 @@ void AsyncEventSourceResponse::deferrable_send_state(void *source, const char *e
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace web_server_idf
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include <esp_http_server.h>
|
||||
|
||||
#include <functional>
|
||||
@@ -13,12 +12,10 @@
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
#ifdef USE_WEBSERVER
|
||||
namespace web_server {
|
||||
class WebServer;
|
||||
class ListEntitiesIterator;
|
||||
}; // namespace web_server
|
||||
#endif
|
||||
namespace web_server_idf {
|
||||
|
||||
#define F(string_literal) (string_literal)
|
||||
@@ -223,7 +220,6 @@ class AsyncWebHandler {
|
||||
virtual bool isRequestHandlerTrivial() { return true; }
|
||||
};
|
||||
|
||||
#ifdef USE_WEBSERVER
|
||||
class AsyncEventSource;
|
||||
class AsyncEventSourceResponse;
|
||||
|
||||
@@ -311,13 +307,10 @@ class AsyncEventSource : public AsyncWebHandler {
|
||||
connect_handler_t on_connect_{};
|
||||
esphome::web_server::WebServer *web_server_;
|
||||
};
|
||||
#endif // USE_WEBSERVER
|
||||
|
||||
class DefaultHeaders {
|
||||
friend class AsyncWebServerRequest;
|
||||
#ifdef USE_WEBSERVER
|
||||
friend class AsyncEventSourceResponse;
|
||||
#endif
|
||||
|
||||
public:
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
|
||||
@@ -102,7 +102,7 @@ WeikaiRegister &WeikaiRegister::operator|=(uint8_t value) {
|
||||
// The WeikaiComponent methods
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void WeikaiComponent::loop() {
|
||||
if (!this->is_in_loop_state())
|
||||
if ((this->component_state_ & COMPONENT_STATE_MASK) != COMPONENT_STATE_LOOP)
|
||||
return;
|
||||
|
||||
// If there are some bytes in the receive FIFO we transfers them to the ring buffers
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2025.6.0b2"
|
||||
__version__ = "2025.7.0-dev"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
||||
@@ -66,7 +66,7 @@ void Application::setup() {
|
||||
[](Component *a, Component *b) { return a->get_loop_priority() > b->get_loop_priority(); });
|
||||
|
||||
do {
|
||||
uint8_t new_app_state = STATUS_LED_WARNING;
|
||||
uint32_t new_app_state = STATUS_LED_WARNING;
|
||||
this->scheduler.call();
|
||||
this->feed_wdt();
|
||||
for (uint32_t j = 0; j <= i; j++) {
|
||||
@@ -87,7 +87,7 @@ void Application::setup() {
|
||||
this->calculate_looping_components_();
|
||||
}
|
||||
void Application::loop() {
|
||||
uint8_t new_app_state = 0;
|
||||
uint32_t new_app_state = 0;
|
||||
|
||||
this->scheduler.call();
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/runtime_stats.h"
|
||||
#include "esphome/core/scheduler.h"
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
@@ -87,8 +88,8 @@ static const uint32_t TEARDOWN_TIMEOUT_REBOOT_MS = 1000; // 1 second for quick
|
||||
|
||||
class Application {
|
||||
public:
|
||||
void pre_setup(const std::string &name, const std::string &friendly_name, const char *area, const char *comment,
|
||||
const char *compilation_time, bool name_add_mac_suffix) {
|
||||
void pre_setup(const std::string &name, const std::string &friendly_name, const std::string &area,
|
||||
const char *comment, const char *compilation_time, bool name_add_mac_suffix) {
|
||||
arch_init();
|
||||
this->name_add_mac_suffix_ = name_add_mac_suffix;
|
||||
if (name_add_mac_suffix) {
|
||||
@@ -285,7 +286,7 @@ class Application {
|
||||
const std::string &get_friendly_name() const { return this->friendly_name_; }
|
||||
|
||||
/// Get the area of this Application set by pre_setup().
|
||||
std::string get_area() const { return this->area_ == nullptr ? "" : this->area_; }
|
||||
const std::string &get_area() const { return this->area_; }
|
||||
|
||||
/// Get the comment of this Application set by pre_setup().
|
||||
std::string get_comment() const { return this->comment_; }
|
||||
@@ -314,6 +315,18 @@ class Application {
|
||||
|
||||
uint32_t get_loop_interval() const { return this->loop_interval_; }
|
||||
|
||||
/** Enable or disable runtime statistics collection.
|
||||
*
|
||||
* @param enable Whether to enable runtime statistics collection.
|
||||
*/
|
||||
void set_runtime_stats_enabled(bool enable) { runtime_stats.set_enabled(enable); }
|
||||
|
||||
/** Set the interval at which runtime statistics are logged.
|
||||
*
|
||||
* @param interval The interval in milliseconds between logging of runtime statistics.
|
||||
*/
|
||||
void set_runtime_stats_log_interval(uint32_t interval) { runtime_stats.set_log_interval(interval); }
|
||||
|
||||
void schedule_dump_config() { this->dump_config_at_ = 0; }
|
||||
|
||||
void feed_wdt(uint32_t time = 0);
|
||||
@@ -332,7 +345,7 @@ class Application {
|
||||
*/
|
||||
void teardown_components(uint32_t timeout_ms);
|
||||
|
||||
uint8_t get_app_state() const { return this->app_state_; }
|
||||
uint32_t get_app_state() const { return this->app_state_; }
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
const std::vector<binary_sensor::BinarySensor *> &get_binary_sensors() { return this->binary_sensors_; }
|
||||
@@ -646,14 +659,14 @@ class Application {
|
||||
|
||||
std::string name_;
|
||||
std::string friendly_name_;
|
||||
const char *area_{nullptr};
|
||||
std::string area_;
|
||||
const char *comment_{nullptr};
|
||||
const char *compilation_time_{nullptr};
|
||||
bool name_add_mac_suffix_;
|
||||
uint32_t last_loop_{0};
|
||||
uint32_t loop_interval_{16};
|
||||
size_t dump_config_at_{SIZE_MAX};
|
||||
uint8_t app_state_{0};
|
||||
uint32_t app_state_{0};
|
||||
Component *current_component_{nullptr};
|
||||
uint32_t loop_component_start_time_{0};
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
@@ -30,20 +29,18 @@ const float LATE = -100.0f;
|
||||
|
||||
} // namespace setup_priority
|
||||
|
||||
// Component state uses bits 0-1 (4 states)
|
||||
const uint8_t COMPONENT_STATE_MASK = 0x03;
|
||||
const uint8_t COMPONENT_STATE_CONSTRUCTION = 0x00;
|
||||
const uint8_t COMPONENT_STATE_SETUP = 0x01;
|
||||
const uint8_t COMPONENT_STATE_LOOP = 0x02;
|
||||
const uint8_t COMPONENT_STATE_FAILED = 0x03;
|
||||
// Status LED uses bits 2-3
|
||||
const uint8_t STATUS_LED_MASK = 0x0C;
|
||||
const uint8_t STATUS_LED_OK = 0x00;
|
||||
const uint8_t STATUS_LED_WARNING = 0x04; // Bit 2
|
||||
const uint8_t STATUS_LED_ERROR = 0x08; // Bit 3
|
||||
const uint32_t COMPONENT_STATE_MASK = 0xFF;
|
||||
const uint32_t COMPONENT_STATE_CONSTRUCTION = 0x00;
|
||||
const uint32_t COMPONENT_STATE_SETUP = 0x01;
|
||||
const uint32_t COMPONENT_STATE_LOOP = 0x02;
|
||||
const uint32_t COMPONENT_STATE_FAILED = 0x03;
|
||||
const uint32_t STATUS_LED_MASK = 0xFF00;
|
||||
const uint32_t STATUS_LED_OK = 0x0000;
|
||||
const uint32_t STATUS_LED_WARNING = 0x0100;
|
||||
const uint32_t STATUS_LED_ERROR = 0x0200;
|
||||
|
||||
const uint16_t WARN_IF_BLOCKING_OVER_MS = 50U; ///< Initial blocking time allowed without warning
|
||||
const uint16_t WARN_IF_BLOCKING_INCREMENT_MS = 10U; ///< How long the blocking time must be larger to warn again
|
||||
const uint32_t WARN_IF_BLOCKING_OVER_MS = 50U; ///< Initial blocking time allowed without warning
|
||||
const uint32_t WARN_IF_BLOCKING_INCREMENT_MS = 10U; ///< How long the blocking time must be larger to warn again
|
||||
|
||||
uint32_t global_state = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -89,9 +86,9 @@ void Component::call_dump_config() {
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Component::get_component_state() const { return this->component_state_; }
|
||||
uint32_t Component::get_component_state() const { return this->component_state_; }
|
||||
void Component::call() {
|
||||
uint8_t state = this->component_state_ & COMPONENT_STATE_MASK;
|
||||
uint32_t state = this->component_state_ & COMPONENT_STATE_MASK;
|
||||
switch (state) {
|
||||
case COMPONENT_STATE_CONSTRUCTION:
|
||||
// State Construction: Call setup and set state to setup
|
||||
@@ -123,13 +120,7 @@ const char *Component::get_component_source() const {
|
||||
}
|
||||
bool Component::should_warn_of_blocking(uint32_t blocking_time) {
|
||||
if (blocking_time > this->warn_if_blocking_over_) {
|
||||
// Prevent overflow when adding increment - if we're about to overflow, just max out
|
||||
if (blocking_time + WARN_IF_BLOCKING_INCREMENT_MS < blocking_time ||
|
||||
blocking_time + WARN_IF_BLOCKING_INCREMENT_MS > std::numeric_limits<uint16_t>::max()) {
|
||||
this->warn_if_blocking_over_ = std::numeric_limits<uint16_t>::max();
|
||||
} else {
|
||||
this->warn_if_blocking_over_ = static_cast<uint16_t>(blocking_time + WARN_IF_BLOCKING_INCREMENT_MS);
|
||||
}
|
||||
this->warn_if_blocking_over_ = blocking_time + WARN_IF_BLOCKING_INCREMENT_MS;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -140,18 +131,6 @@ void Component::mark_failed() {
|
||||
this->component_state_ |= COMPONENT_STATE_FAILED;
|
||||
this->status_set_error();
|
||||
}
|
||||
void Component::reset_to_construction_state() {
|
||||
if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) {
|
||||
ESP_LOGI(TAG, "Component %s is being reset to construction state.", this->get_component_source());
|
||||
this->component_state_ &= ~COMPONENT_STATE_MASK;
|
||||
this->component_state_ |= COMPONENT_STATE_CONSTRUCTION;
|
||||
// Clear error status when resetting
|
||||
this->status_clear_error();
|
||||
}
|
||||
}
|
||||
bool Component::is_in_loop_state() const {
|
||||
return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP;
|
||||
}
|
||||
void Component::defer(std::function<void()> &&f) { // NOLINT
|
||||
App.scheduler.set_timeout(this, "", 0, std::move(f));
|
||||
}
|
||||
@@ -267,6 +246,9 @@ uint32_t WarnIfComponentBlockingGuard::finish() {
|
||||
uint32_t curr_time = millis();
|
||||
|
||||
uint32_t blocking_time = curr_time - this->started_;
|
||||
|
||||
// Record component runtime stats
|
||||
runtime_stats.record_component_time(this->component_, blocking_time, curr_time);
|
||||
bool should_warn;
|
||||
if (this->component_ != nullptr) {
|
||||
should_warn = this->component_->should_warn_of_blocking(blocking_time);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/optional.h"
|
||||
#include "esphome/core/runtime_stats.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -53,19 +54,19 @@ static const uint32_t SCHEDULER_DONT_RUN = 4294967295UL;
|
||||
ESP_LOGCONFIG(TAG, " Update Interval: %.1fs", this->get_update_interval() / 1000.0f); \
|
||||
}
|
||||
|
||||
extern const uint8_t COMPONENT_STATE_MASK;
|
||||
extern const uint8_t COMPONENT_STATE_CONSTRUCTION;
|
||||
extern const uint8_t COMPONENT_STATE_SETUP;
|
||||
extern const uint8_t COMPONENT_STATE_LOOP;
|
||||
extern const uint8_t COMPONENT_STATE_FAILED;
|
||||
extern const uint8_t STATUS_LED_MASK;
|
||||
extern const uint8_t STATUS_LED_OK;
|
||||
extern const uint8_t STATUS_LED_WARNING;
|
||||
extern const uint8_t STATUS_LED_ERROR;
|
||||
extern const uint32_t COMPONENT_STATE_MASK;
|
||||
extern const uint32_t COMPONENT_STATE_CONSTRUCTION;
|
||||
extern const uint32_t COMPONENT_STATE_SETUP;
|
||||
extern const uint32_t COMPONENT_STATE_LOOP;
|
||||
extern const uint32_t COMPONENT_STATE_FAILED;
|
||||
extern const uint32_t STATUS_LED_MASK;
|
||||
extern const uint32_t STATUS_LED_OK;
|
||||
extern const uint32_t STATUS_LED_WARNING;
|
||||
extern const uint32_t STATUS_LED_ERROR;
|
||||
|
||||
enum class RetryResult { DONE, RETRY };
|
||||
|
||||
extern const uint16_t WARN_IF_BLOCKING_OVER_MS;
|
||||
extern const uint32_t WARN_IF_BLOCKING_OVER_MS;
|
||||
|
||||
class Component {
|
||||
public:
|
||||
@@ -123,19 +124,7 @@ class Component {
|
||||
*/
|
||||
virtual void on_powerdown() {}
|
||||
|
||||
uint8_t get_component_state() const;
|
||||
|
||||
/** Reset this component back to the construction state to allow setup to run again.
|
||||
*
|
||||
* This can be used by components that have recoverable failures to attempt setup again.
|
||||
*/
|
||||
void reset_to_construction_state();
|
||||
|
||||
/** Check if this component has completed setup and is in the loop state.
|
||||
*
|
||||
* @return True if in loop state, false otherwise.
|
||||
*/
|
||||
bool is_in_loop_state() const;
|
||||
uint32_t get_component_state() const;
|
||||
|
||||
/** Mark this component as failed. Any future timeouts/intervals/setup/loop will no longer be called.
|
||||
*
|
||||
@@ -310,15 +299,10 @@ class Component {
|
||||
/// Cancel a defer callback using the specified name, name must not be empty.
|
||||
bool cancel_defer(const std::string &name); // NOLINT
|
||||
|
||||
/// State of this component - each bit has a purpose:
|
||||
/// Bits 0-1: Component state (0x00=CONSTRUCTION, 0x01=SETUP, 0x02=LOOP, 0x03=FAILED)
|
||||
/// Bit 2: STATUS_LED_WARNING
|
||||
/// Bit 3: STATUS_LED_ERROR
|
||||
/// Bits 4-7: Unused - reserved for future expansion (50% of the bits are free)
|
||||
uint8_t component_state_{0x00};
|
||||
uint32_t component_state_{0x0000}; ///< State of this component.
|
||||
float setup_priority_override_{NAN};
|
||||
const char *component_source_{nullptr};
|
||||
uint16_t warn_if_blocking_over_{WARN_IF_BLOCKING_OVER_MS}; ///< Warn if blocked for this many ms (max 65.5s)
|
||||
uint32_t warn_if_blocking_over_{WARN_IF_BLOCKING_OVER_MS};
|
||||
std::string error_message_{};
|
||||
};
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@ namespace esphome {
|
||||
void Controller::setup_controller(bool include_internal) {
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto *obj : App.get_binary_sensors()) {
|
||||
if (include_internal || !obj->is_internal())
|
||||
obj->add_on_state_callback([this, obj](bool state) { this->on_binary_sensor_update(obj, state); });
|
||||
if (include_internal || !obj->is_internal()) {
|
||||
obj->add_full_state_callback(
|
||||
[this, obj](optional<bool> previous, optional<bool> state) { this->on_binary_sensor_update(obj); });
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
|
||||
@@ -71,7 +71,7 @@ class Controller {
|
||||
public:
|
||||
void setup_controller(bool include_internal = false);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state){};
|
||||
virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj){};
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
virtual void on_fan_update(fan::Fan *obj){};
|
||||
|
||||
@@ -12,12 +12,20 @@ void EntityBase::set_name(const char *name) {
|
||||
this->name_ = StringRef(name);
|
||||
if (this->name_.empty()) {
|
||||
this->name_ = StringRef(App.get_friendly_name());
|
||||
this->flags_.has_own_name = false;
|
||||
this->has_own_name_ = false;
|
||||
} else {
|
||||
this->flags_.has_own_name = true;
|
||||
this->has_own_name_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Entity Internal
|
||||
bool EntityBase::is_internal() const { return this->internal_; }
|
||||
void EntityBase::set_internal(bool internal) { this->internal_ = internal; }
|
||||
|
||||
// Entity Disabled by Default
|
||||
bool EntityBase::is_disabled_by_default() const { return this->disabled_by_default_; }
|
||||
void EntityBase::set_disabled_by_default(bool disabled_by_default) { this->disabled_by_default_ = disabled_by_default; }
|
||||
|
||||
// Entity Icon
|
||||
std::string EntityBase::get_icon() const {
|
||||
if (this->icon_c_str_ == nullptr) {
|
||||
@@ -27,10 +35,14 @@ std::string EntityBase::get_icon() const {
|
||||
}
|
||||
void EntityBase::set_icon(const char *icon) { this->icon_c_str_ = icon; }
|
||||
|
||||
// Entity Category
|
||||
EntityCategory EntityBase::get_entity_category() const { return this->entity_category_; }
|
||||
void EntityBase::set_entity_category(EntityCategory entity_category) { this->entity_category_ = entity_category; }
|
||||
|
||||
// Entity Object ID
|
||||
std::string EntityBase::get_object_id() const {
|
||||
// Check if `App.get_friendly_name()` is constant or dynamic.
|
||||
if (!this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled()) {
|
||||
if (!this->has_own_name_ && App.is_name_add_mac_suffix_enabled()) {
|
||||
// `App.get_friendly_name()` is dynamic.
|
||||
return str_sanitize(str_snake_case(App.get_friendly_name()));
|
||||
} else {
|
||||
@@ -49,7 +61,7 @@ void EntityBase::set_object_id(const char *object_id) {
|
||||
// Calculate Object ID Hash from Entity Name
|
||||
void EntityBase::calc_object_id_() {
|
||||
// Check if `App.get_friendly_name()` is constant or dynamic.
|
||||
if (!this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled()) {
|
||||
if (!this->has_own_name_ && App.is_name_add_mac_suffix_enabled()) {
|
||||
// `App.get_friendly_name()` is dynamic.
|
||||
const auto object_id = str_sanitize(str_snake_case(App.get_friendly_name()));
|
||||
// FNV-1 hash
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include "string_ref.h"
|
||||
#include "helpers.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -20,7 +22,7 @@ class EntityBase {
|
||||
void set_name(const char *name);
|
||||
|
||||
// Get whether this Entity has its own name or it should use the device friendly_name.
|
||||
bool has_own_name() const { return this->flags_.has_own_name; }
|
||||
bool has_own_name() const { return this->has_own_name_; }
|
||||
|
||||
// Get the sanitized name of this Entity as an ID.
|
||||
std::string get_object_id() const;
|
||||
@@ -30,31 +32,23 @@ class EntityBase {
|
||||
uint32_t get_object_id_hash();
|
||||
|
||||
// Get/set whether this Entity should be hidden outside ESPHome
|
||||
bool is_internal() const { return this->flags_.internal; }
|
||||
void set_internal(bool internal) { this->flags_.internal = internal; }
|
||||
bool is_internal() const;
|
||||
void set_internal(bool internal);
|
||||
|
||||
// Check if this object is declared to be disabled by default.
|
||||
// That means that when the device gets added to Home Assistant (or other clients) it should
|
||||
// not be added to the default view by default, and a user action is necessary to manually add it.
|
||||
bool is_disabled_by_default() const { return this->flags_.disabled_by_default; }
|
||||
void set_disabled_by_default(bool disabled_by_default) { this->flags_.disabled_by_default = disabled_by_default; }
|
||||
bool is_disabled_by_default() const;
|
||||
void set_disabled_by_default(bool disabled_by_default);
|
||||
|
||||
// Get/set the entity category.
|
||||
EntityCategory get_entity_category() const { return static_cast<EntityCategory>(this->flags_.entity_category); }
|
||||
void set_entity_category(EntityCategory entity_category) {
|
||||
this->flags_.entity_category = static_cast<uint8_t>(entity_category);
|
||||
}
|
||||
EntityCategory get_entity_category() const;
|
||||
void set_entity_category(EntityCategory entity_category);
|
||||
|
||||
// Get/set this entity's icon
|
||||
std::string get_icon() const;
|
||||
void set_icon(const char *icon);
|
||||
|
||||
// Check if this entity has state
|
||||
bool has_state() const { return this->flags_.has_state; }
|
||||
|
||||
// Set has_state - for components that need to manually set this
|
||||
void set_has_state(bool state) { this->flags_.has_state = state; }
|
||||
|
||||
protected:
|
||||
/// The hash_base() function has been deprecated. It is kept in this
|
||||
/// class for now, to prevent external components from not compiling.
|
||||
@@ -65,16 +59,11 @@ class EntityBase {
|
||||
const char *object_id_c_str_{nullptr};
|
||||
const char *icon_c_str_{nullptr};
|
||||
uint32_t object_id_hash_{};
|
||||
|
||||
// Bit-packed flags to save memory (1 byte instead of 5)
|
||||
struct EntityFlags {
|
||||
uint8_t has_own_name : 1;
|
||||
uint8_t internal : 1;
|
||||
uint8_t disabled_by_default : 1;
|
||||
uint8_t has_state : 1;
|
||||
uint8_t entity_category : 2; // Supports up to 4 categories
|
||||
uint8_t reserved : 2; // Reserved for future use
|
||||
} flags_{};
|
||||
bool has_own_name_{false};
|
||||
bool internal_{false};
|
||||
bool disabled_by_default_{false};
|
||||
EntityCategory entity_category_{ENTITY_CATEGORY_NONE};
|
||||
bool has_state_{};
|
||||
};
|
||||
|
||||
class EntityBase_DeviceClass { // NOLINT(readability-identifier-naming)
|
||||
@@ -99,4 +88,58 @@ class EntityBase_UnitOfMeasurement { // NOLINT(readability-identifier-naming)
|
||||
const char *unit_of_measurement_{nullptr}; ///< Unit of measurement override
|
||||
};
|
||||
|
||||
/**
|
||||
* An entity that has a state.
|
||||
* @tparam T The type of the state
|
||||
*/
|
||||
template<typename T> class StatefulEntityBase : public EntityBase {
|
||||
public:
|
||||
virtual bool has_state() const { return this->state_.has_value(); }
|
||||
virtual const T &get_state() const { return this->state_.value(); }
|
||||
virtual T get_state_default(T default_value) const { return this->state_.value_or(default_value); }
|
||||
void invalidate_state() { this->set_state_({}); }
|
||||
|
||||
void add_full_state_callback(std::function<void(optional<T> previous, optional<T> current)> &&callback) {
|
||||
if (this->full_state_callbacks_ == nullptr)
|
||||
this->full_state_callbacks_ = new CallbackManager<void(optional<T> previous, optional<T> current)>(); // NOLINT
|
||||
this->full_state_callbacks_->add(std::move(callback));
|
||||
}
|
||||
void add_on_state_callback(std::function<void(T)> &&callback) {
|
||||
if (this->state_callbacks_ == nullptr)
|
||||
this->state_callbacks_ = new CallbackManager<void(T)>(); // NOLINT
|
||||
this->state_callbacks_->add(std::move(callback));
|
||||
}
|
||||
|
||||
void set_trigger_on_initial_state(bool trigger_on_initial_state) {
|
||||
this->trigger_on_initial_state_ = trigger_on_initial_state;
|
||||
}
|
||||
|
||||
protected:
|
||||
optional<T> state_{};
|
||||
/**
|
||||
* Set a new state for this entity. This will trigger callbacks only if the new state is different from the previous.
|
||||
*
|
||||
* @param state The new state.
|
||||
* @return True if the state was changed, false if it was the same as before.
|
||||
*/
|
||||
bool set_state_(const optional<T> &state) {
|
||||
if (this->state_ != state) {
|
||||
// call the full state callbacks with the previous and new state
|
||||
if (this->full_state_callbacks_ != nullptr)
|
||||
this->full_state_callbacks_->call(this->state_, state);
|
||||
// trigger legacy callbacks only if the new state is valid and either the trigger on initial state is enabled or
|
||||
// the previous state was valid
|
||||
auto had_state = this->has_state();
|
||||
this->state_ = state;
|
||||
if (this->state_callbacks_ != nullptr && state.has_value() && (this->trigger_on_initial_state_ || had_state))
|
||||
this->state_callbacks_->call(state.value());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool trigger_on_initial_state_{true};
|
||||
// callbacks with full state and previous state
|
||||
CallbackManager<void(optional<T> previous, optional<T> current)> *full_state_callbacks_{};
|
||||
CallbackManager<void(T)> *state_callbacks_{};
|
||||
};
|
||||
} // namespace esphome
|
||||
|
||||
@@ -438,7 +438,7 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::stri
|
||||
}
|
||||
|
||||
/// Return values for parse_on_off().
|
||||
enum ParseOnOffState : uint8_t {
|
||||
enum ParseOnOffState {
|
||||
PARSE_NONE = 0,
|
||||
PARSE_ON,
|
||||
PARSE_OFF,
|
||||
|
||||
@@ -165,6 +165,8 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
|
||||
#define YESNO(b) ((b) ? "YES" : "NO")
|
||||
#define ONOFF(b) ((b) ? "ON" : "OFF")
|
||||
#define TRUEFALSE(b) ((b) ? "TRUE" : "FALSE")
|
||||
// for use with optional values
|
||||
#define ONOFFMAYBE(b) (((b).has_value()) ? ONOFF((b).value()) : "UNKNOWN")
|
||||
|
||||
// Helper class that identifies strings that may be stored in flash storage (similar to Arduino's __FlashStringHelper)
|
||||
struct LogString;
|
||||
|
||||
@@ -52,6 +52,11 @@ template<typename T> class optional { // NOLINT
|
||||
reset();
|
||||
return *this;
|
||||
}
|
||||
bool operator==(optional<T> const &rhs) const {
|
||||
if (has_value() && rhs.has_value())
|
||||
return value() == rhs.value();
|
||||
return !has_value() && !rhs.has_value();
|
||||
}
|
||||
|
||||
template<class U> optional &operator=(optional<U> const &other) {
|
||||
has_value_ = other.has_value();
|
||||
|
||||
28
esphome/core/runtime_stats.cpp
Normal file
28
esphome/core/runtime_stats.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "esphome/core/runtime_stats.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
RuntimeStatsCollector runtime_stats;
|
||||
|
||||
void RuntimeStatsCollector::record_component_time(Component *component, uint32_t duration_ms, uint32_t current_time) {
|
||||
if (!this->enabled_ || component == nullptr)
|
||||
return;
|
||||
|
||||
const char *component_source = component->get_component_source();
|
||||
this->component_stats_[component_source].record_time(duration_ms);
|
||||
|
||||
// If next_log_time_ is 0, initialize it
|
||||
if (this->next_log_time_ == 0) {
|
||||
this->next_log_time_ = current_time + this->log_interval_;
|
||||
return;
|
||||
}
|
||||
|
||||
if (current_time >= this->next_log_time_) {
|
||||
this->log_stats_();
|
||||
this->reset_stats_();
|
||||
this->next_log_time_ = current_time + this->log_interval_;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
161
esphome/core/runtime_stats.h
Normal file
161
esphome/core/runtime_stats.h
Normal file
@@ -0,0 +1,161 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
static const char *const RUNTIME_TAG = "runtime";
|
||||
|
||||
class Component; // Forward declaration
|
||||
|
||||
class ComponentRuntimeStats {
|
||||
public:
|
||||
ComponentRuntimeStats()
|
||||
: period_count_(0),
|
||||
total_count_(0),
|
||||
period_time_ms_(0),
|
||||
total_time_ms_(0),
|
||||
period_max_time_ms_(0),
|
||||
total_max_time_ms_(0) {}
|
||||
|
||||
void record_time(uint32_t duration_ms) {
|
||||
// Update period counters
|
||||
this->period_count_++;
|
||||
this->period_time_ms_ += duration_ms;
|
||||
if (duration_ms > this->period_max_time_ms_)
|
||||
this->period_max_time_ms_ = duration_ms;
|
||||
|
||||
// Update total counters
|
||||
this->total_count_++;
|
||||
this->total_time_ms_ += duration_ms;
|
||||
if (duration_ms > this->total_max_time_ms_)
|
||||
this->total_max_time_ms_ = duration_ms;
|
||||
}
|
||||
|
||||
void reset_period_stats() {
|
||||
this->period_count_ = 0;
|
||||
this->period_time_ms_ = 0;
|
||||
this->period_max_time_ms_ = 0;
|
||||
}
|
||||
|
||||
// Period stats (reset each logging interval)
|
||||
uint32_t get_period_count() const { return this->period_count_; }
|
||||
uint32_t get_period_time_ms() const { return this->period_time_ms_; }
|
||||
uint32_t get_period_max_time_ms() const { return this->period_max_time_ms_; }
|
||||
float get_period_avg_time_ms() const {
|
||||
return this->period_count_ > 0 ? this->period_time_ms_ / static_cast<float>(this->period_count_) : 0.0f;
|
||||
}
|
||||
|
||||
// Total stats (persistent until reboot)
|
||||
uint32_t get_total_count() const { return this->total_count_; }
|
||||
uint32_t get_total_time_ms() const { return this->total_time_ms_; }
|
||||
uint32_t get_total_max_time_ms() const { return this->total_max_time_ms_; }
|
||||
float get_total_avg_time_ms() const {
|
||||
return this->total_count_ > 0 ? this->total_time_ms_ / static_cast<float>(this->total_count_) : 0.0f;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Period stats (reset each logging interval)
|
||||
uint32_t period_count_;
|
||||
uint32_t period_time_ms_;
|
||||
uint32_t period_max_time_ms_;
|
||||
|
||||
// Total stats (persistent until reboot)
|
||||
uint32_t total_count_;
|
||||
uint32_t total_time_ms_;
|
||||
uint32_t total_max_time_ms_;
|
||||
};
|
||||
|
||||
// For sorting components by run time
|
||||
struct ComponentStatPair {
|
||||
std::string name;
|
||||
const ComponentRuntimeStats *stats;
|
||||
|
||||
bool operator>(const ComponentStatPair &other) const {
|
||||
// Sort by period time as that's what we're displaying in the logs
|
||||
return stats->get_period_time_ms() > other.stats->get_period_time_ms();
|
||||
}
|
||||
};
|
||||
|
||||
class RuntimeStatsCollector {
|
||||
public:
|
||||
RuntimeStatsCollector() : log_interval_(60000), next_log_time_(0), enabled_(true) {}
|
||||
|
||||
void set_log_interval(uint32_t log_interval) { this->log_interval_ = log_interval; }
|
||||
uint32_t get_log_interval() const { return this->log_interval_; }
|
||||
|
||||
void set_enabled(bool enabled) { this->enabled_ = enabled; }
|
||||
bool is_enabled() const { return this->enabled_; }
|
||||
|
||||
void record_component_time(Component *component, uint32_t duration_ms, uint32_t current_time);
|
||||
|
||||
protected:
|
||||
void log_stats_() {
|
||||
ESP_LOGI(RUNTIME_TAG, "Component Runtime Statistics");
|
||||
ESP_LOGI(RUNTIME_TAG, "Period stats (last %" PRIu32 "ms):", this->log_interval_);
|
||||
|
||||
// First collect stats we want to display
|
||||
std::vector<ComponentStatPair> stats_to_display;
|
||||
|
||||
for (const auto &it : this->component_stats_) {
|
||||
const ComponentRuntimeStats &stats = it.second;
|
||||
if (stats.get_period_count() > 0) {
|
||||
ComponentStatPair pair = {it.first, &stats};
|
||||
stats_to_display.push_back(pair);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by period runtime (descending)
|
||||
std::sort(stats_to_display.begin(), stats_to_display.end(), std::greater<ComponentStatPair>());
|
||||
|
||||
// Log top components by period runtime
|
||||
for (const auto &it : stats_to_display) {
|
||||
const std::string &source = it.name;
|
||||
const ComponentRuntimeStats *stats = it.stats;
|
||||
|
||||
ESP_LOGI(RUNTIME_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms",
|
||||
source.c_str(), stats->get_period_count(), stats->get_period_avg_time_ms(),
|
||||
stats->get_period_max_time_ms(), stats->get_period_time_ms());
|
||||
}
|
||||
|
||||
// Log total stats since boot
|
||||
ESP_LOGI(RUNTIME_TAG, "Total stats (since boot):");
|
||||
|
||||
// Re-sort by total runtime for all-time stats
|
||||
std::sort(stats_to_display.begin(), stats_to_display.end(),
|
||||
[](const ComponentStatPair &a, const ComponentStatPair &b) {
|
||||
return a.stats->get_total_time_ms() > b.stats->get_total_time_ms();
|
||||
});
|
||||
|
||||
for (const auto &it : stats_to_display) {
|
||||
const std::string &source = it.name;
|
||||
const ComponentRuntimeStats *stats = it.stats;
|
||||
|
||||
ESP_LOGI(RUNTIME_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms",
|
||||
source.c_str(), stats->get_total_count(), stats->get_total_avg_time_ms(), stats->get_total_max_time_ms(),
|
||||
stats->get_total_time_ms());
|
||||
}
|
||||
}
|
||||
|
||||
void reset_stats_() {
|
||||
for (auto &it : this->component_stats_) {
|
||||
it.second.reset_period_stats();
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, ComponentRuntimeStats> component_stats_;
|
||||
uint32_t log_interval_;
|
||||
uint32_t next_log_time_;
|
||||
bool enabled_;
|
||||
};
|
||||
|
||||
// Global instance for runtime stats collection
|
||||
extern RuntimeStatsCollector runtime_stats;
|
||||
|
||||
} // namespace esphome
|
||||
@@ -1,5 +1,8 @@
|
||||
from collections.abc import Callable
|
||||
from functools import reduce
|
||||
from logging import Logger
|
||||
import operator
|
||||
from typing import Any
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -15,6 +18,7 @@ from esphome.const import (
|
||||
CONF_PULLUP,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
|
||||
|
||||
class PinRegistry(dict):
|
||||
@@ -262,7 +266,7 @@ internal_gpio_input_pullup_pin_number = _internal_number_creator(
|
||||
)
|
||||
|
||||
|
||||
def check_strapping_pin(conf, strapping_pin_list, logger):
|
||||
def check_strapping_pin(conf, strapping_pin_list: set[int], logger: Logger):
|
||||
num = conf[CONF_NUMBER]
|
||||
if num in strapping_pin_list and not conf.get(CONF_IGNORE_STRAPPING_WARNING):
|
||||
logger.warning(
|
||||
@@ -291,11 +295,11 @@ def gpio_validate_modes(value):
|
||||
|
||||
|
||||
def gpio_base_schema(
|
||||
pin_type,
|
||||
number_validator,
|
||||
pin_type: MockObjClass,
|
||||
number_validator: Callable[[Any], Any],
|
||||
modes=GPIO_STANDARD_MODES,
|
||||
mode_validator=gpio_validate_modes,
|
||||
invertable=True,
|
||||
mode_validator: Callable[[Any], Any] = gpio_validate_modes,
|
||||
invertible: bool = True,
|
||||
):
|
||||
"""
|
||||
Generate a base gpio pin schema
|
||||
@@ -303,7 +307,7 @@ def gpio_base_schema(
|
||||
:param number_validator: A validator for the pin number
|
||||
:param modes: The available modes, default is all standard modes
|
||||
:param mode_validator: A validator function for the pin mode
|
||||
:param invertable: If the pin supports hardware inversion
|
||||
:param invertible: If the pin supports hardware inversion
|
||||
:return: A schema for the pin
|
||||
"""
|
||||
mode_default = len(modes) == 1
|
||||
@@ -328,7 +332,7 @@ def gpio_base_schema(
|
||||
}
|
||||
)
|
||||
|
||||
if invertable:
|
||||
if invertible:
|
||||
return schema.extend({cv.Optional(CONF_INVERTED, default=False): cv.boolean})
|
||||
|
||||
return schema
|
||||
|
||||
@@ -67,6 +67,20 @@ esp8266:
|
||||
"""
|
||||
|
||||
ESP32_CONFIG = """
|
||||
esp32:
|
||||
board: {board}
|
||||
framework:
|
||||
type: arduino
|
||||
"""
|
||||
|
||||
ESP32S2_CONFIG = """
|
||||
esp32:
|
||||
board: {board}
|
||||
framework:
|
||||
type: esp-idf
|
||||
"""
|
||||
|
||||
ESP32C3_CONFIG = """
|
||||
esp32:
|
||||
board: {board}
|
||||
framework:
|
||||
@@ -91,6 +105,8 @@ rtl87xx:
|
||||
HARDWARE_BASE_CONFIGS = {
|
||||
"ESP8266": ESP8266_CONFIG,
|
||||
"ESP32": ESP32_CONFIG,
|
||||
"ESP32S2": ESP32S2_CONFIG,
|
||||
"ESP32C3": ESP32C3_CONFIG,
|
||||
"RP2040": RP2040_CONFIG,
|
||||
"BK72XX": BK72XX_CONFIG,
|
||||
"RTL87XX": RTL87XX_CONFIG,
|
||||
|
||||
@@ -13,7 +13,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
|
||||
esptool==4.8.1
|
||||
click==8.1.7
|
||||
esphome-dashboard==20250514.0
|
||||
aioesphomeapi==32.2.3
|
||||
aioesphomeapi==32.2.1
|
||||
zeroconf==0.147.0
|
||||
puremagic==1.29
|
||||
ruamel.yaml==0.18.14 # dashboard_import
|
||||
|
||||
@@ -848,10 +848,7 @@ def calculate_message_estimated_size(desc: descriptor.DescriptorProto) -> int:
|
||||
return total_size
|
||||
|
||||
|
||||
def build_message_type(
|
||||
desc: descriptor.DescriptorProto,
|
||||
base_class_fields: dict[str, list[descriptor.FieldDescriptorProto]] = None,
|
||||
) -> tuple[str, str]:
|
||||
def build_message_type(desc: descriptor.DescriptorProto) -> tuple[str, str]:
|
||||
public_content: list[str] = []
|
||||
protected_content: list[str] = []
|
||||
decode_varint: list[str] = []
|
||||
@@ -862,12 +859,6 @@ def build_message_type(
|
||||
dump: list[str] = []
|
||||
size_calc: list[str] = []
|
||||
|
||||
# Check if this message has a base class
|
||||
base_class = get_base_class(desc)
|
||||
common_field_names = set()
|
||||
if base_class and base_class_fields and base_class in base_class_fields:
|
||||
common_field_names = {f.name for f in base_class_fields[base_class]}
|
||||
|
||||
# Get message ID if it's a service message
|
||||
message_id: int | None = get_opt(desc, pb.id)
|
||||
|
||||
@@ -895,14 +886,8 @@ def build_message_type(
|
||||
ti = RepeatedTypeInfo(field)
|
||||
else:
|
||||
ti = TYPE_INFO[field.type](field)
|
||||
|
||||
# Skip field declarations for fields that are in the base class
|
||||
# but include their encode/decode logic
|
||||
if field.name not in common_field_names:
|
||||
protected_content.extend(ti.protected_content)
|
||||
public_content.extend(ti.public_content)
|
||||
|
||||
# Always include encode/decode logic for all fields
|
||||
protected_content.extend(ti.protected_content)
|
||||
public_content.extend(ti.public_content)
|
||||
encode.append(ti.encode_content)
|
||||
size_calc.append(ti.get_size_calculation(f"this->{ti.field_name}"))
|
||||
|
||||
@@ -1016,10 +1001,7 @@ def build_message_type(
|
||||
prot += "#endif\n"
|
||||
public_content.append(prot)
|
||||
|
||||
if base_class:
|
||||
out = f"class {desc.name} : public {base_class} {{\n"
|
||||
else:
|
||||
out = f"class {desc.name} : public ProtoMessage {{\n"
|
||||
out = f"class {desc.name} : public ProtoMessage {{\n"
|
||||
out += " public:\n"
|
||||
out += indent("\n".join(public_content)) + "\n"
|
||||
out += "\n"
|
||||
@@ -1051,132 +1033,6 @@ def get_opt(
|
||||
return desc.options.Extensions[opt]
|
||||
|
||||
|
||||
def get_base_class(desc: descriptor.DescriptorProto) -> str | None:
|
||||
"""Get the base_class option from a message descriptor."""
|
||||
if not desc.options.HasExtension(pb.base_class):
|
||||
return None
|
||||
return desc.options.Extensions[pb.base_class]
|
||||
|
||||
|
||||
def collect_messages_by_base_class(
|
||||
messages: list[descriptor.DescriptorProto],
|
||||
) -> dict[str, list[descriptor.DescriptorProto]]:
|
||||
"""Group messages by their base_class option."""
|
||||
base_class_groups = {}
|
||||
|
||||
for msg in messages:
|
||||
base_class = get_base_class(msg)
|
||||
if base_class:
|
||||
if base_class not in base_class_groups:
|
||||
base_class_groups[base_class] = []
|
||||
base_class_groups[base_class].append(msg)
|
||||
|
||||
return base_class_groups
|
||||
|
||||
|
||||
def find_common_fields(
|
||||
messages: list[descriptor.DescriptorProto],
|
||||
) -> list[descriptor.FieldDescriptorProto]:
|
||||
"""Find fields that are common to all messages in the list."""
|
||||
if not messages:
|
||||
return []
|
||||
|
||||
# Start with fields from the first message
|
||||
first_msg_fields = {field.name: field for field in messages[0].field}
|
||||
common_fields = []
|
||||
|
||||
# Check each field to see if it exists in all messages with same type
|
||||
# Field numbers can vary between messages - derived classes handle the mapping
|
||||
for field_name, field in first_msg_fields.items():
|
||||
is_common = True
|
||||
|
||||
for msg in messages[1:]:
|
||||
found = False
|
||||
for other_field in msg.field:
|
||||
if (
|
||||
other_field.name == field_name
|
||||
and other_field.type == field.type
|
||||
and other_field.label == field.label
|
||||
):
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
is_common = False
|
||||
break
|
||||
|
||||
if is_common:
|
||||
common_fields.append(field)
|
||||
|
||||
# Sort by field number to maintain order
|
||||
common_fields.sort(key=lambda f: f.number)
|
||||
return common_fields
|
||||
|
||||
|
||||
def build_base_class(
|
||||
base_class_name: str,
|
||||
common_fields: list[descriptor.FieldDescriptorProto],
|
||||
) -> tuple[str, str]:
|
||||
"""Build the base class definition and implementation."""
|
||||
public_content = []
|
||||
protected_content = []
|
||||
|
||||
# For base classes, we only declare the fields but don't handle encode/decode
|
||||
# The derived classes will handle encoding/decoding with their specific field numbers
|
||||
for field in common_fields:
|
||||
if field.label == 3: # repeated
|
||||
ti = RepeatedTypeInfo(field)
|
||||
else:
|
||||
ti = TYPE_INFO[field.type](field)
|
||||
|
||||
# Only add field declarations, not encode/decode logic
|
||||
protected_content.extend(ti.protected_content)
|
||||
public_content.extend(ti.public_content)
|
||||
|
||||
# Build header
|
||||
out = f"class {base_class_name} : public ProtoMessage {{\n"
|
||||
out += " public:\n"
|
||||
|
||||
# Add destructor with override
|
||||
public_content.insert(0, f"~{base_class_name}() override = default;")
|
||||
|
||||
# Base classes don't implement encode/decode/calculate_size
|
||||
# Derived classes handle these with their specific field numbers
|
||||
cpp = ""
|
||||
|
||||
out += indent("\n".join(public_content)) + "\n"
|
||||
out += "\n"
|
||||
out += " protected:\n"
|
||||
out += indent("\n".join(protected_content))
|
||||
if protected_content:
|
||||
out += "\n"
|
||||
out += "};\n"
|
||||
|
||||
# No implementation needed for base classes
|
||||
|
||||
return out, cpp
|
||||
|
||||
|
||||
def generate_base_classes(
|
||||
base_class_groups: dict[str, list[descriptor.DescriptorProto]],
|
||||
) -> tuple[str, str]:
|
||||
"""Generate all base classes."""
|
||||
all_headers = []
|
||||
all_cpp = []
|
||||
|
||||
for base_class_name, messages in base_class_groups.items():
|
||||
# Find common fields
|
||||
common_fields = find_common_fields(messages)
|
||||
|
||||
if common_fields:
|
||||
# Generate base class
|
||||
header, cpp = build_base_class(base_class_name, common_fields)
|
||||
all_headers.append(header)
|
||||
all_cpp.append(cpp)
|
||||
|
||||
return "\n".join(all_headers), "\n".join(all_cpp)
|
||||
|
||||
|
||||
def build_service_message_type(
|
||||
mt: descriptor.DescriptorProto,
|
||||
) -> tuple[str, str] | None:
|
||||
@@ -1278,25 +1134,8 @@ def main() -> None:
|
||||
|
||||
mt = file.message_type
|
||||
|
||||
# Collect messages by base class
|
||||
base_class_groups = collect_messages_by_base_class(mt)
|
||||
|
||||
# Find common fields for each base class
|
||||
base_class_fields = {}
|
||||
for base_class_name, messages in base_class_groups.items():
|
||||
common_fields = find_common_fields(messages)
|
||||
if common_fields:
|
||||
base_class_fields[base_class_name] = common_fields
|
||||
|
||||
# Generate base classes
|
||||
if base_class_fields:
|
||||
base_headers, base_cpp = generate_base_classes(base_class_groups)
|
||||
content += base_headers
|
||||
cpp += base_cpp
|
||||
|
||||
# Generate message types with base class information
|
||||
for m in mt:
|
||||
s, c = build_message_type(m, base_class_fields)
|
||||
s, c = build_message_type(m)
|
||||
content += s
|
||||
cpp += c
|
||||
|
||||
|
||||
15
tests/components/binary_sensor/common.yaml
Normal file
15
tests/components/binary_sensor/common.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
trigger_on_initial_state: true
|
||||
id: some_binary_sensor
|
||||
name: "Random binary"
|
||||
lambda: return (random_uint32() & 1) == 0;
|
||||
on_state_change:
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Old state was %s"
|
||||
args: ['x_previous.has_value() ? ONOFF(x_previous) : "Unknown"']
|
||||
- logger.log:
|
||||
format: "New state is %s"
|
||||
args: ['x.has_value() ? ONOFF(x) : "Unknown"']
|
||||
- binary_sensor.invalidate_state: some_binary_sensor
|
||||
2
tests/components/binary_sensor/test.bk72xx-ard.yaml
Normal file
2
tests/components/binary_sensor/test.bk72xx-ard.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
2
tests/components/binary_sensor/test.esp32-ard.yaml
Normal file
2
tests/components/binary_sensor/test.esp32-ard.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
2
tests/components/binary_sensor/test.esp32-c3-ard.yaml
Normal file
2
tests/components/binary_sensor/test.esp32-c3-ard.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
2
tests/components/binary_sensor/test.esp32-c3-idf.yaml
Normal file
2
tests/components/binary_sensor/test.esp32-c3-idf.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
2
tests/components/binary_sensor/test.esp32-idf.yaml
Normal file
2
tests/components/binary_sensor/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
2
tests/components/binary_sensor/test.esp32-s3-idf.yaml
Normal file
2
tests/components/binary_sensor/test.esp32-s3-idf.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
2
tests/components/binary_sensor/test.esp8266-ard.yaml
Normal file
2
tests/components/binary_sensor/test.esp8266-ard.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
2
tests/components/binary_sensor/test.rp2040-ard.yaml
Normal file
2
tests/components/binary_sensor/test.rp2040-ard.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
@@ -63,7 +63,7 @@ binary_sensor:
|
||||
id: lvgl_pressbutton
|
||||
name: Pressbutton
|
||||
widget: spin_up
|
||||
publish_initial_state: true
|
||||
trigger_on_initial_state: true
|
||||
- platform: lvgl
|
||||
name: ButtonMatrix button
|
||||
widget: button_a
|
||||
|
||||
@@ -15,7 +15,7 @@ import sys
|
||||
import tempfile
|
||||
from typing import TextIO
|
||||
|
||||
from aioesphomeapi import APIClient, APIConnectionError, LogParser, ReconnectLogic
|
||||
from aioesphomeapi import APIClient, APIConnectionError, ReconnectLogic
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
@@ -119,21 +119,6 @@ async def yaml_config(request: pytest.FixtureRequest, unused_tcp_port: int) -> s
|
||||
# Add port configuration after api:
|
||||
content = content.replace("api:", f"api:\n port: {unused_tcp_port}")
|
||||
|
||||
# Add debug build flags for integration tests to enable assertions
|
||||
if "esphome:" in content:
|
||||
# Check if platformio_options already exists
|
||||
if "platformio_options:" not in content:
|
||||
# Add platformio_options with debug flags after esphome:
|
||||
content = content.replace(
|
||||
"esphome:",
|
||||
"esphome:\n"
|
||||
" # Enable assertions for integration tests\n"
|
||||
" platformio_options:\n"
|
||||
" build_flags:\n"
|
||||
' - "-DDEBUG" # Enable assert() statements\n'
|
||||
' - "-g" # Add debug symbols',
|
||||
)
|
||||
|
||||
return content
|
||||
|
||||
|
||||
@@ -365,21 +350,11 @@ async def _read_stream_lines(
|
||||
stream: asyncio.StreamReader, lines: list[str], output_stream: TextIO
|
||||
) -> None:
|
||||
"""Read lines from a stream, append to list, and echo to output stream."""
|
||||
log_parser = LogParser()
|
||||
while line := await stream.readline():
|
||||
decoded_line = (
|
||||
line.replace(b"\r", b"")
|
||||
.replace(b"\n", b"")
|
||||
.decode("utf8", "backslashreplace")
|
||||
)
|
||||
decoded_line = line.decode("utf-8", errors="replace")
|
||||
lines.append(decoded_line.rstrip())
|
||||
# Echo to stdout/stderr in real-time
|
||||
# Print without newline to avoid double newlines
|
||||
print(
|
||||
log_parser.parse_line(decoded_line, timestamp=""),
|
||||
file=output_stream,
|
||||
flush=True,
|
||||
)
|
||||
print(decoded_line.rstrip(), file=output_stream, flush=True)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
esphome:
|
||||
name: message-size-batching-test
|
||||
host:
|
||||
api:
|
||||
# Default batch_delay to test batching
|
||||
logger:
|
||||
|
||||
# Create entities that will produce different protobuf header sizes
|
||||
# Header size depends on: 1 byte indicator + varint(payload_size) + varint(message_type)
|
||||
# 4-byte header: type < 128, payload < 128
|
||||
# 5-byte header: type < 128, payload 128-16383 OR type 128+, payload < 128
|
||||
# 6-byte header: type 128+, payload 128-16383
|
||||
|
||||
# Small select with few options - produces small message
|
||||
select:
|
||||
- platform: template
|
||||
name: "Small Select"
|
||||
id: small_select
|
||||
optimistic: true
|
||||
options:
|
||||
- "Option A"
|
||||
- "Option B"
|
||||
initial_option: "Option A"
|
||||
update_interval: 5.0s
|
||||
|
||||
# Medium select with more options - produces medium message
|
||||
- platform: template
|
||||
name: "Medium Select"
|
||||
id: medium_select
|
||||
optimistic: true
|
||||
options:
|
||||
- "Option 001"
|
||||
- "Option 002"
|
||||
- "Option 003"
|
||||
- "Option 004"
|
||||
- "Option 005"
|
||||
- "Option 006"
|
||||
- "Option 007"
|
||||
- "Option 008"
|
||||
- "Option 009"
|
||||
- "Option 010"
|
||||
- "Option 011"
|
||||
- "Option 012"
|
||||
- "Option 013"
|
||||
- "Option 014"
|
||||
- "Option 015"
|
||||
- "Option 016"
|
||||
- "Option 017"
|
||||
- "Option 018"
|
||||
- "Option 019"
|
||||
- "Option 020"
|
||||
initial_option: "Option 001"
|
||||
update_interval: 5.0s
|
||||
|
||||
# Large select with many options - produces larger message
|
||||
- platform: template
|
||||
name: "Large Select with Many Options to Create Larger Payload"
|
||||
id: large_select
|
||||
optimistic: true
|
||||
options:
|
||||
- "Long Option Name 001 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 002 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 003 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 004 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 005 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 006 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 007 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 008 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 009 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 010 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 011 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 012 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 013 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 014 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 015 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 016 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 017 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 018 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 019 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 020 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 021 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 022 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 023 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 024 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 025 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 026 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 027 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 028 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 029 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 030 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 031 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 032 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 033 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 034 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 035 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 036 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 037 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 038 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 039 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 040 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 041 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 042 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 043 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 044 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 045 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 046 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 047 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 048 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 049 - This is a longer option name to increase message size"
|
||||
- "Long Option Name 050 - This is a longer option name to increase message size"
|
||||
initial_option: "Long Option Name 001 - This is a longer option name to increase message size"
|
||||
update_interval: 5.0s
|
||||
|
||||
# Text sensors with different value lengths
|
||||
text_sensor:
|
||||
- platform: template
|
||||
name: "Short Text Sensor"
|
||||
id: short_text_sensor
|
||||
lambda: |-
|
||||
return {"OK"};
|
||||
update_interval: 5.0s
|
||||
|
||||
- platform: template
|
||||
name: "Medium Text Sensor"
|
||||
id: medium_text_sensor
|
||||
lambda: |-
|
||||
return {"This is a medium length text sensor value that should produce a medium sized message"};
|
||||
update_interval: 5.0s
|
||||
|
||||
- platform: template
|
||||
name: "Long Text Sensor with Very Long Value"
|
||||
id: long_text_sensor
|
||||
lambda: |-
|
||||
return {"This is a very long text sensor value that contains a lot of text to ensure we get a larger protobuf message. The message should be long enough to require a 2-byte varint for the payload size, which happens when the payload exceeds 127 bytes. Let's add even more text here to make sure we exceed that threshold and test the batching of messages with different header sizes properly."};
|
||||
update_interval: 5.0s
|
||||
|
||||
# Text input which can have various lengths
|
||||
text:
|
||||
- platform: template
|
||||
name: "Test Text Input"
|
||||
id: test_text_input
|
||||
optimistic: true
|
||||
mode: text
|
||||
min_length: 0
|
||||
max_length: 255
|
||||
initial_value: "Initial value"
|
||||
update_interval: 5.0s
|
||||
|
||||
# Number entity to add variety (different message type number)
|
||||
# The ListEntitiesNumberResponse has message type 49
|
||||
# The NumberStateResponse has message type 50
|
||||
number:
|
||||
- platform: template
|
||||
name: "Test Number with Long Name to Increase Message Size"
|
||||
id: test_number
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
max_value: 1000
|
||||
step: 0.1
|
||||
initial_value: 42.0
|
||||
update_interval: 5.0s
|
||||
@@ -1,58 +0,0 @@
|
||||
esphome:
|
||||
name: host-empty-string-test
|
||||
|
||||
host:
|
||||
|
||||
api:
|
||||
batch_delay: 50ms
|
||||
|
||||
select:
|
||||
- platform: template
|
||||
name: "Select Empty First"
|
||||
id: select_empty_first
|
||||
optimistic: true
|
||||
options:
|
||||
- "" # Empty string at the beginning
|
||||
- "Option A"
|
||||
- "Option B"
|
||||
- "Option C"
|
||||
initial_option: "Option A"
|
||||
|
||||
- platform: template
|
||||
name: "Select Empty Middle"
|
||||
id: select_empty_middle
|
||||
optimistic: true
|
||||
options:
|
||||
- "Option 1"
|
||||
- "Option 2"
|
||||
- "" # Empty string in the middle
|
||||
- "Option 3"
|
||||
- "Option 4"
|
||||
initial_option: "Option 1"
|
||||
|
||||
- platform: template
|
||||
name: "Select Empty Last"
|
||||
id: select_empty_last
|
||||
optimistic: true
|
||||
options:
|
||||
- "Choice X"
|
||||
- "Choice Y"
|
||||
- "Choice Z"
|
||||
- "" # Empty string at the end
|
||||
initial_option: "Choice X"
|
||||
|
||||
# Add a sensor to ensure we have other entities in the list
|
||||
sensor:
|
||||
- platform: template
|
||||
name: "Test Sensor"
|
||||
id: test_sensor
|
||||
lambda: |-
|
||||
return 42.0;
|
||||
update_interval: 60s
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
name: "Test Binary Sensor"
|
||||
id: test_binary_sensor
|
||||
lambda: |-
|
||||
return true;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user