Compare commits

..

22 Commits

Author SHA1 Message Date
J. Nick Koston
dd345106f7 lock 2025-06-12 22:37:26 -05:00
J. Nick Koston
9763821d68 lock 2025-06-12 22:34:03 -05:00
J. Nick Koston
3d358cf431 lock 2025-06-12 22:21:15 -05:00
J. Nick Koston
1cebeb53d3 lock 2025-06-12 22:10:40 -05:00
J. Nick Koston
09bfa7f527 lock 2025-06-12 22:09:53 -05:00
J. Nick Koston
a61138c4f7 lock 2025-06-12 22:05:31 -05:00
J. Nick Koston
b23445e1c3 lock 2025-06-12 22:00:28 -05:00
J. Nick Koston
b887c1bf08 lock 2025-06-12 21:58:52 -05:00
J. Nick Koston
9206888966 label 2025-06-12 21:46:38 -05:00
J. Nick Koston
567cba4510 speed up 2025-06-12 21:37:22 -05:00
J. Nick Koston
7da5e02388 tweak 2025-06-12 21:34:03 -05:00
J. Nick Koston
1dd189cf36 tweak 2025-06-12 21:33:53 -05:00
J. Nick Koston
9e5dc01fd4 tweak 2025-06-12 21:33:29 -05:00
Edward Firmo
2a629cae93 [nextion] Remove upload flags reset from success path to prevent TFT corruption (#9064) 2025-06-13 13:39:32 +12:00
dependabot[bot]
1f14c316a3 Bump pytest-cov from 6.1.1 to 6.2.1 (#9063)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-12 18:16:37 -05:00
J. Nick Koston
dac738a916 Always perform select() when loop duration exceeds interval (#9058) 2025-06-12 03:27:10 +00:00
Clyde Stubbs
261b561bb2 [binary_sensor] Add action to invalidate state and pass to HA (#8961)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-06-12 09:16:20 +10:00
J. Nick Koston
0228379a2e Fix dashboard logging being escaped before parser (#9054) 2025-06-11 16:17:47 -05:00
Jesse Hills
da79215bc3 Merge branch 'beta' into dev 2025-06-12 07:56:24 +12:00
Jesse Hills
44323dc285 Merge pull request #9049 from esphome/bump-2025.6.0b1
2025.6.0b1
2025-06-12 07:55:49 +12:00
Thomas Rupprecht
a59e1c7011 [core/pins] improve pins types (#8848)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-06-11 18:06:41 +00:00
Jesse Hills
abb4d991ad Bump version to 2025.6.0b1 2025-06-11 23:16:56 +12:00
71 changed files with 1091 additions and 240 deletions

View File

@@ -377,7 +377,15 @@ jobs:
id: list-components
run: |
. venv/bin/activate
components=$(script/list-components.py --changed --branch ${{ steps.target-branch.outputs.branch }})
# Check if we should test all components (via label)
if [[ "${{ contains(github.event.pull_request.labels.*.name, 'test-all-components') }}" == "true" ]]; then
echo "Label 'test-all-components' found - testing ALL components"
components=$(script/list-components.py)
else
components=$(script/list-components.py --changed --branch ${{ steps.target-branch.outputs.branch }})
fi
output_components=$(echo "$components" | jq -R -s -c 'split("\n")[:-1] | map(select(length > 0))')
count=$(echo "$output_components" | jq length)
@@ -415,11 +423,15 @@ jobs:
- name: test_build_components -e config -c ${{ matrix.file }}
run: |
. venv/bin/activate
./script/test_build_components -e config -c ${{ matrix.file }}
# Use 4 parallel jobs for config validation
./script/test_build_components -e config -c ${{ matrix.file }} -j 4 -f
- name: test_build_components -e compile -c ${{ matrix.file }}
run: |
. venv/bin/activate
./script/test_build_components -e compile -c ${{ matrix.file }}
mkdir -p build_cache
export PLATFORMIO_BUILD_CACHE_DIR=$PWD/build_cache
# Use 2 parallel jobs for compilation (resource intensive)
./script/test_build_components -e compile -c ${{ matrix.file }} -j 2 -f -b $PWD/build_cache
test-build-components-splitter:
name: Split components for testing into 20 groups maximum
@@ -471,17 +483,28 @@ jobs:
- name: Validate config
run: |
. venv/bin/activate
for component in ${{ matrix.components }}; do
./script/test_build_components -e config -c $component
# Process all components in parallel for config validation
components="${{ matrix.components }}"
# Convert space-separated list to multiple -c flags
component_args=""
for component in $components; do
component_args="$component_args -c $component"
done
# Use 8 parallel jobs for lightweight config validation
./script/test_build_components -e config $component_args -j 8 -f
- name: Compile config
run: |
. venv/bin/activate
mkdir build_cache
mkdir -p build_cache
export PLATFORMIO_BUILD_CACHE_DIR=$PWD/build_cache
for component in ${{ matrix.components }}; do
./script/test_build_components -e compile -c $component
# Process all components in parallel for compilation
components="${{ matrix.components }}"
component_args=""
for component in $components; do
component_args="$component_args -c $component"
done
# Use 4 parallel jobs for resource-intensive compilation
./script/test_build_components -e compile $component_args -j 4 -f -b $PWD/build_cache
ci-status:
name: CI Status

View File

@@ -72,6 +72,10 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
void set_sampling_mode(SamplingMode sampling_mode);
float sample() override;
#ifdef USE_ESP8266
std::string unique_id() override;
#endif // USE_ESP8266
#ifdef USE_RP2040
void set_is_temperature() { this->is_temperature_ = true; }
#endif // USE_RP2040

View File

@@ -56,6 +56,8 @@ float ADCSensor::sample() {
return aggr.aggregate() / 1024.0f;
}
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
} // namespace adc
} // namespace esphome

View File

@@ -272,6 +272,7 @@ message ListEntitiesBinarySensorResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string device_class = 5;
bool is_status_binary_sensor = 6;
@@ -301,6 +302,7 @@ message ListEntitiesCoverResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
bool assumed_state = 5;
bool supports_position = 6;
@@ -371,6 +373,7 @@ message ListEntitiesFanResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
bool supports_oscillation = 5;
bool supports_speed = 6;
@@ -447,6 +450,7 @@ message ListEntitiesLightResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
repeated ColorMode supported_color_modes = 12;
// next four supports_* are for legacy clients, newer clients should use color modes
@@ -538,6 +542,7 @@ message ListEntitiesSensorResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
string unit_of_measurement = 6;
@@ -572,6 +577,7 @@ message ListEntitiesSwitchResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool assumed_state = 6;
@@ -607,6 +613,7 @@ message ListEntitiesTextSensorResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
@@ -788,6 +795,7 @@ message ListEntitiesCameraResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
bool disabled_by_default = 5;
string icon = 6;
EntityCategory entity_category = 7;
@@ -867,6 +875,7 @@ message ListEntitiesClimateResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
bool supports_current_temperature = 5;
bool supports_two_point_target_temperature = 6;
@@ -961,6 +970,7 @@ message ListEntitiesNumberResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
float min_value = 6;
@@ -1003,6 +1013,7 @@ message ListEntitiesSelectResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
repeated string options = 6;
@@ -1040,6 +1051,7 @@ message ListEntitiesSirenResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
@@ -1096,6 +1108,7 @@ message ListEntitiesLockResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
@@ -1138,6 +1151,7 @@ message ListEntitiesButtonResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
@@ -1188,6 +1202,7 @@ message ListEntitiesMediaPlayerResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
@@ -1726,6 +1741,7 @@ message ListEntitiesAlarmControlPanelResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
@@ -1766,6 +1782,7 @@ message ListEntitiesTextResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
@@ -1807,6 +1824,7 @@ message ListEntitiesDateResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
@@ -1847,6 +1865,7 @@ message ListEntitiesTimeResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
@@ -1887,6 +1906,7 @@ message ListEntitiesEventResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
@@ -1913,6 +1933,7 @@ message ListEntitiesValveResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
@@ -1961,6 +1982,7 @@ message ListEntitiesDateTimeResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
@@ -1997,6 +2019,7 @@ message ListEntitiesUpdateResponse {
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;

View File

@@ -225,6 +225,10 @@ void APIConnection::loop() {
}
}
std::string get_default_unique_id(const std::string &component_type, EntityBase *entity) {
return App.get_name() + component_type + entity->get_object_id();
}
DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
// remote initiated disconnect_client
// don't close yet, we still need to send the disconnect response
@@ -291,6 +295,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
ListEntitiesBinarySensorResponse msg;
msg.device_class = binary_sensor->get_device_class();
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
msg.unique_id = get_default_unique_id("binary_sensor", binary_sensor);
fill_entity_info_base(binary_sensor, msg);
return encode_message_to_buffer(msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -327,6 +332,7 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c
msg.supports_tilt = traits.get_supports_tilt();
msg.supports_stop = traits.get_supports_stop();
msg.device_class = cover->get_device_class();
msg.unique_id = get_default_unique_id("cover", cover);
fill_entity_info_base(cover, msg);
return encode_message_to_buffer(msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -395,6 +401,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con
msg.supported_speed_count = traits.supported_speed_count();
for (auto const &preset : traits.supported_preset_modes())
msg.supported_preset_modes.push_back(preset);
msg.unique_id = get_default_unique_id("fan", fan);
fill_entity_info_base(fan, msg);
return encode_message_to_buffer(msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -474,6 +481,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
msg.effects.push_back(effect->get_name());
}
}
msg.unique_id = get_default_unique_id("light", light);
fill_entity_info_base(light, msg);
return encode_message_to_buffer(msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -541,6 +549,9 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
msg.force_update = sensor->get_force_update();
msg.device_class = sensor->get_device_class();
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
msg.unique_id = sensor->unique_id();
if (msg.unique_id.empty())
msg.unique_id = get_default_unique_id("sensor", sensor);
fill_entity_info_base(sensor, msg);
return encode_message_to_buffer(msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -569,6 +580,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *
ListEntitiesSwitchResponse msg;
msg.assumed_state = a_switch->assumed_state();
msg.device_class = a_switch->get_device_class();
msg.unique_id = get_default_unique_id("switch", a_switch);
fill_entity_info_base(a_switch, msg);
return encode_message_to_buffer(msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -609,6 +621,9 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
ListEntitiesTextSensorResponse msg;
msg.device_class = text_sensor->get_device_class();
msg.unique_id = text_sensor->unique_id();
if (msg.unique_id.empty())
msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
fill_entity_info_base(text_sensor, msg);
return encode_message_to_buffer(msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -683,6 +698,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
msg.supported_custom_presets.push_back(custom_preset);
for (auto swing_mode : traits.get_supported_swing_modes())
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
msg.unique_id = get_default_unique_id("climate", climate);
fill_entity_info_base(climate, msg);
return encode_message_to_buffer(msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -744,6 +760,7 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *
msg.min_value = number->traits.get_min_value();
msg.max_value = number->traits.get_max_value();
msg.step = number->traits.get_step();
msg.unique_id = get_default_unique_id("number", number);
fill_entity_info_base(number, msg);
return encode_message_to_buffer(msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -780,6 +797,7 @@ uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *co
bool is_single) {
auto *date = static_cast<datetime::DateEntity *>(entity);
ListEntitiesDateResponse msg;
msg.unique_id = get_default_unique_id("date", date);
fill_entity_info_base(date, msg);
return encode_message_to_buffer(msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -816,6 +834,7 @@ uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *co
bool is_single) {
auto *time = static_cast<datetime::TimeEntity *>(entity);
ListEntitiesTimeResponse msg;
msg.unique_id = get_default_unique_id("time", time);
fill_entity_info_base(time, msg);
return encode_message_to_buffer(msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -854,6 +873,7 @@ uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection
bool is_single) {
auto *datetime = static_cast<datetime::DateTimeEntity *>(entity);
ListEntitiesDateTimeResponse msg;
msg.unique_id = get_default_unique_id("datetime", datetime);
fill_entity_info_base(datetime, msg);
return encode_message_to_buffer(msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -894,6 +914,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co
msg.min_length = text->traits.get_min_length();
msg.max_length = text->traits.get_max_length();
msg.pattern = text->traits.get_pattern();
msg.unique_id = get_default_unique_id("text", text);
fill_entity_info_base(text, msg);
return encode_message_to_buffer(msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -932,6 +953,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *
ListEntitiesSelectResponse msg;
for (const auto &option : select->traits.get_options())
msg.options.push_back(option);
msg.unique_id = get_default_unique_id("select", select);
fill_entity_info_base(select, msg);
return encode_message_to_buffer(msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -955,6 +977,7 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *
auto *button = static_cast<button::Button *>(entity);
ListEntitiesButtonResponse msg;
msg.device_class = button->get_device_class();
msg.unique_id = get_default_unique_id("button", button);
fill_entity_info_base(button, msg);
return encode_message_to_buffer(msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -991,6 +1014,7 @@ uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *co
msg.assumed_state = a_lock->traits.get_assumed_state();
msg.supports_open = a_lock->traits.get_supports_open();
msg.requires_code = a_lock->traits.get_requires_code();
msg.unique_id = get_default_unique_id("lock", a_lock);
fill_entity_info_base(a_lock, msg);
return encode_message_to_buffer(msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -1038,6 +1062,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c
msg.assumed_state = traits.get_is_assumed_state();
msg.supports_position = traits.get_supports_position();
msg.supports_stop = traits.get_supports_stop();
msg.unique_id = get_default_unique_id("valve", valve);
fill_entity_info_base(valve, msg);
return encode_message_to_buffer(msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -1092,6 +1117,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec
media_format.sample_bytes = supported_format.sample_bytes;
msg.supported_formats.push_back(media_format);
}
msg.unique_id = get_default_unique_id("media_player", media_player);
fill_entity_info_base(media_player, msg);
return encode_message_to_buffer(msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -1134,6 +1160,7 @@ uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *
bool is_single) {
auto *camera = static_cast<esp32_camera::ESP32Camera *>(entity);
ListEntitiesCameraResponse msg;
msg.unique_id = get_default_unique_id("camera", camera);
fill_entity_info_base(camera, msg);
return encode_message_to_buffer(msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -1346,6 +1373,7 @@ uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, AP
msg.supported_features = a_alarm_control_panel->get_supported_features();
msg.requires_code = a_alarm_control_panel->get_requires_code();
msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm();
msg.unique_id = get_default_unique_id("alarm_control_panel", a_alarm_control_panel);
fill_entity_info_base(a_alarm_control_panel, msg);
return encode_message_to_buffer(msg, ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
@@ -1406,6 +1434,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
msg.device_class = event->get_device_class();
for (const auto &event_type : event->get_event_types())
msg.event_types.push_back(event_type);
msg.unique_id = get_default_unique_id("event", event);
fill_entity_info_base(event, msg);
return encode_message_to_buffer(msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -1443,6 +1472,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *
auto *update = static_cast<update::UpdateEntity *>(entity);
ListEntitiesUpdateResponse msg;
msg.device_class = update->get_device_class();
msg.unique_id = get_default_unique_id("update", update);
fill_entity_info_base(update, msg);
return encode_message_to_buffer(msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}

View File

@@ -1079,6 +1079,10 @@ bool ListEntitiesBinarySensorResponse::decode_length(uint32_t field_id, ProtoLen
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->device_class = value.as_string();
return true;
@@ -1105,6 +1109,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->device_class);
buffer.encode_bool(6, this->is_status_binary_sensor);
buffer.encode_bool(7, this->disabled_by_default);
@@ -1115,6 +1120,7 @@ void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) cons
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->device_class, false);
ProtoSize::add_bool_field(total_size, 1, this->is_status_binary_sensor, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
@@ -1138,6 +1144,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
@@ -1253,6 +1263,10 @@ bool ListEntitiesCoverResponse::decode_length(uint32_t field_id, ProtoLengthDeli
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 8: {
this->device_class = value.as_string();
return true;
@@ -1279,6 +1293,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_bool(5, this->assumed_state);
buffer.encode_bool(6, this->supports_position);
buffer.encode_bool(7, this->supports_tilt);
@@ -1292,6 +1307,7 @@ void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_bool_field(total_size, 1, this->assumed_state, false);
ProtoSize::add_bool_field(total_size, 1, this->supports_position, false);
ProtoSize::add_bool_field(total_size, 1, this->supports_tilt, false);
@@ -1318,6 +1334,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" assumed_state: ");
out.append(YESNO(this->assumed_state));
out.append("\n");
@@ -1572,6 +1592,10 @@ bool ListEntitiesFanResponse::decode_length(uint32_t field_id, ProtoLengthDelimi
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 10: {
this->icon = value.as_string();
return true;
@@ -1598,6 +1622,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_bool(5, this->supports_oscillation);
buffer.encode_bool(6, this->supports_speed);
buffer.encode_bool(7, this->supports_direction);
@@ -1613,6 +1638,7 @@ void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_bool_field(total_size, 1, this->supports_oscillation, false);
ProtoSize::add_bool_field(total_size, 1, this->supports_speed, false);
ProtoSize::add_bool_field(total_size, 1, this->supports_direction, false);
@@ -1643,6 +1669,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" supports_oscillation: ");
out.append(YESNO(this->supports_oscillation));
out.append("\n");
@@ -1984,6 +2014,10 @@ bool ListEntitiesLightResponse::decode_length(uint32_t field_id, ProtoLengthDeli
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 11: {
this->effects.push_back(value.as_string());
return true;
@@ -2018,6 +2052,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
for (auto &it : this->supported_color_modes) {
buffer.encode_enum<enums::ColorMode>(12, it, true);
}
@@ -2038,6 +2073,7 @@ void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
if (!this->supported_color_modes.empty()) {
for (const auto &it : this->supported_color_modes) {
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(it), true);
@@ -2075,6 +2111,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
for (const auto &it : this->supported_color_modes) {
out.append(" supported_color_modes: ");
out.append(proto_enum_to_string<enums::ColorMode>(it));
@@ -2645,6 +2685,10 @@ bool ListEntitiesSensorResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -2675,6 +2719,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_string(6, this->unit_of_measurement);
buffer.encode_int32(7, this->accuracy_decimals);
@@ -2689,6 +2734,7 @@ void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement, false);
ProtoSize::add_int32_field(total_size, 1, this->accuracy_decimals, false);
@@ -2716,6 +2762,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -2837,6 +2887,10 @@ bool ListEntitiesSwitchResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -2863,6 +2917,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->assumed_state);
buffer.encode_bool(7, this->disabled_by_default);
@@ -2873,6 +2928,7 @@ void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->assumed_state, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
@@ -2896,6 +2952,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -3028,6 +3088,10 @@ bool ListEntitiesTextSensorResponse::decode_length(uint32_t field_id, ProtoLengt
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -3054,6 +3118,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
@@ -3063,6 +3128,7 @@ void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
@@ -3085,6 +3151,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -3885,6 +3955,10 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 6: {
this->icon = value.as_string();
return true;
@@ -3907,6 +3981,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_bool(5, this->disabled_by_default);
buffer.encode_string(6, this->icon);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
@@ -3915,6 +3990,7 @@ void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
@@ -3936,6 +4012,10 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
@@ -4109,6 +4189,10 @@ bool ListEntitiesClimateResponse::decode_length(uint32_t field_id, ProtoLengthDe
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 15: {
this->supported_custom_fan_modes.push_back(value.as_string());
return true;
@@ -4163,6 +4247,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_bool(5, this->supports_current_temperature);
buffer.encode_bool(6, this->supports_two_point_target_temperature);
for (auto &it : this->supported_modes) {
@@ -4201,6 +4286,7 @@ void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_bool_field(total_size, 1, this->supports_current_temperature, false);
ProtoSize::add_bool_field(total_size, 1, this->supports_two_point_target_temperature, false);
if (!this->supported_modes.empty()) {
@@ -4264,6 +4350,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" supports_current_temperature: ");
out.append(YESNO(this->supports_current_temperature));
out.append("\n");
@@ -4844,6 +4934,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -4886,6 +4980,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_float(6, this->min_value);
buffer.encode_float(7, this->max_value);
@@ -4900,6 +4995,7 @@ void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->min_value != 0.0f, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->max_value != 0.0f, false);
@@ -4927,6 +5023,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -5084,6 +5184,10 @@ bool ListEntitiesSelectResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -5110,6 +5214,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
for (auto &it : this->options) {
buffer.encode_string(6, it, true);
@@ -5121,6 +5226,7 @@ void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
if (!this->options.empty()) {
for (const auto &it : this->options) {
@@ -5147,6 +5253,10 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -5301,6 +5411,10 @@ bool ListEntitiesSirenResponse::decode_length(uint32_t field_id, ProtoLengthDeli
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -5327,6 +5441,7 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
for (auto &it : this->tones) {
@@ -5340,6 +5455,7 @@ void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
if (!this->tones.empty()) {
@@ -5368,6 +5484,10 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -5596,6 +5716,10 @@ bool ListEntitiesLockResponse::decode_length(uint32_t field_id, ProtoLengthDelim
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -5622,6 +5746,7 @@ void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
@@ -5634,6 +5759,7 @@ void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
@@ -5659,6 +5785,10 @@ void ListEntitiesLockResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -5825,6 +5955,10 @@ bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -5851,6 +5985,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
@@ -5860,6 +5995,7 @@ void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
@@ -5882,6 +6018,10 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -6028,6 +6168,10 @@ bool ListEntitiesMediaPlayerResponse::decode_length(uint32_t field_id, ProtoLeng
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -6054,6 +6198,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
@@ -6066,6 +6211,7 @@ void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
@@ -6089,6 +6235,10 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -8440,6 +8590,10 @@ bool ListEntitiesAlarmControlPanelResponse::decode_length(uint32_t field_id, Pro
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -8462,6 +8616,7 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) cons
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
@@ -8473,6 +8628,7 @@ void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size)
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
@@ -8497,6 +8653,10 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -8662,6 +8822,10 @@ bool ListEntitiesTextResponse::decode_length(uint32_t field_id, ProtoLengthDelim
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -8688,6 +8852,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
@@ -8700,6 +8865,7 @@ void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
@@ -8725,6 +8891,10 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -8883,6 +9053,10 @@ bool ListEntitiesDateResponse::decode_length(uint32_t field_id, ProtoLengthDelim
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -8905,6 +9079,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
@@ -8913,6 +9088,7 @@ void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
@@ -8934,6 +9110,10 @@ void ListEntitiesDateResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -9114,6 +9294,10 @@ bool ListEntitiesTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelim
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -9136,6 +9320,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
@@ -9144,6 +9329,7 @@ void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
@@ -9165,6 +9351,10 @@ void ListEntitiesTimeResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -9345,6 +9535,10 @@ bool ListEntitiesEventResponse::decode_length(uint32_t field_id, ProtoLengthDeli
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -9375,6 +9569,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
@@ -9387,6 +9582,7 @@ void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
@@ -9414,6 +9610,10 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -9517,6 +9717,10 @@ bool ListEntitiesValveResponse::decode_length(uint32_t field_id, ProtoLengthDeli
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -9543,6 +9747,7 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
@@ -9555,6 +9760,7 @@ void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
@@ -9580,6 +9786,10 @@ void ListEntitiesValveResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -9752,6 +9962,10 @@ bool ListEntitiesDateTimeResponse::decode_length(uint32_t field_id, ProtoLengthD
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -9774,6 +9988,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
@@ -9782,6 +9997,7 @@ void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
@@ -9803,6 +10019,10 @@ void ListEntitiesDateTimeResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
@@ -9933,6 +10153,10 @@ bool ListEntitiesUpdateResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
@@ -9959,6 +10183,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
@@ -9968,6 +10193,7 @@ void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
@@ -9990,6 +10216,10 @@ void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");

View File

@@ -494,6 +494,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
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};
@@ -540,6 +541,7 @@ class ListEntitiesCoverResponse : public ProtoMessage {
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};
@@ -616,6 +618,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
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};
@@ -701,6 +704,7 @@ class ListEntitiesLightResponse : public ProtoMessage {
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};
@@ -809,6 +813,7 @@ class ListEntitiesSensorResponse : public ProtoMessage {
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};
@@ -859,6 +864,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
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};
@@ -923,6 +929,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
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{};
@@ -1252,6 +1259,7 @@ class ListEntitiesCameraResponse : public ProtoMessage {
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{};
@@ -1315,6 +1323,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
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{};
@@ -1431,6 +1440,7 @@ class ListEntitiesNumberResponse : public ProtoMessage {
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};
@@ -1499,6 +1509,7 @@ class ListEntitiesSelectResponse : public ProtoMessage {
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};
@@ -1564,6 +1575,7 @@ class ListEntitiesSirenResponse : public ProtoMessage {
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{};
@@ -1637,6 +1649,7 @@ class ListEntitiesLockResponse : public ProtoMessage {
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{};
@@ -1706,6 +1719,7 @@ class ListEntitiesButtonResponse : public ProtoMessage {
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{};
@@ -1765,6 +1779,7 @@ class ListEntitiesMediaPlayerResponse : public ProtoMessage {
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{};
@@ -2648,6 +2663,7 @@ class ListEntitiesAlarmControlPanelResponse : public ProtoMessage {
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{};
@@ -2715,6 +2731,7 @@ class ListEntitiesTextResponse : public ProtoMessage {
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{};
@@ -2783,6 +2800,7 @@ class ListEntitiesDateResponse : public ProtoMessage {
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{};
@@ -2850,6 +2868,7 @@ class ListEntitiesTimeResponse : public ProtoMessage {
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{};
@@ -2917,6 +2936,7 @@ class ListEntitiesEventResponse : public ProtoMessage {
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{};
@@ -2962,6 +2982,7 @@ class ListEntitiesValveResponse : public ProtoMessage {
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{};
@@ -3031,6 +3052,7 @@ class ListEntitiesDateTimeResponse : public ProtoMessage {
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{};
@@ -3093,6 +3115,7 @@ class ListEntitiesUpdateResponse : public ProtoMessage {
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{};

View File

@@ -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_)

View File

@@ -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;

View File

@@ -46,12 +46,10 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None:
time_ = datetime.now()
message: bytes = msg.message
text = message.decode("utf8", "backslashreplace")
if dashboard:
text = text.replace("\033", "\\033")
for parsed_msg in parse_log_message(
text, f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}]"
):
print(parsed_msg)
print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg)
stop = await async_run(cli, on_log, name=name)
try:

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -3,4 +3,5 @@
CODEOWNERS = ["@esphome/core"]
CONF_DRAW_ROUNDING = "draw_rounding"
CONF_ON_STATE_CHANGE = "on_state_change"
CONF_REQUEST_HEADERS = "request_headers"

View File

@@ -16,6 +16,7 @@ void ESP32HallSensor::update() {
ESP_LOGD(TAG, "'%s': Got reading %.0f µT", this->name_.c_str(), value);
this->publish_state(value);
}
std::string ESP32HallSensor::unique_id() { return get_mac_address() + "-hall"; }
void ESP32HallSensor::dump_config() { LOG_SENSOR("", "ESP32 Hall Sensor", this); }
} // namespace esp32_hall

View File

@@ -13,6 +13,8 @@ class ESP32HallSensor : public sensor::Sensor, public PollingComponent {
void dump_config() override;
void update() override;
std::string unique_id() override;
};
} // namespace esp32_hall

View File

@@ -29,6 +29,7 @@ class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextS
}
float get_setup_priority() const override { return setup_priority::ETHERNET; }
std::string unique_id() override { return get_mac_address() + "-ethernetinfo"; }
void dump_config() override;
void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; }
@@ -51,6 +52,7 @@ class DNSAddressEthernetInfo : public PollingComponent, public text_sensor::Text
}
}
float get_setup_priority() const override { return setup_priority::ETHERNET; }
std::string unique_id() override { return get_mac_address() + "-ethernetinfo-dns"; }
void dump_config() override;
protected:
@@ -61,6 +63,7 @@ class MACAddressEthernetInfo : public Component, public text_sensor::TextSensor
public:
void setup() override { this->publish_state(ethernet::global_eth_component->get_eth_mac_address_pretty()); }
float get_setup_priority() const override { return setup_priority::ETHERNET; }
std::string unique_id() override { return get_mac_address() + "-ethernetinfo-mac"; }
void dump_config() override;
};

View File

@@ -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),

View File

@@ -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),

View File

@@ -128,16 +128,21 @@ bool MQTTComponent::send_discovery_() {
root[MQTT_PAYLOAD_NOT_AVAILABLE] = this->availability_->payload_not_available;
}
std::string unique_id = this->unique_id();
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
char friendly_name_hash[9];
sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name()));
friendly_name_hash[8] = 0; // ensure the hash-string ends with null
root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash;
if (!unique_id.empty()) {
root[MQTT_UNIQUE_ID] = unique_id;
} else {
// default to almost-unique ID. It's a hack but the only way to get that
// gorgeous device registry view.
root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_();
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
char friendly_name_hash[9];
sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name()));
friendly_name_hash[8] = 0; // ensure the hash-string ends with null
root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash;
} else {
// default to almost-unique ID. It's a hack but the only way to get that
// gorgeous device registry view.
root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_();
}
}
const std::string &node_name = App.get_name();
@@ -279,6 +284,7 @@ void MQTTComponent::call_dump_config() {
this->dump_config();
}
void MQTTComponent::schedule_resend_state() { this->resend_state_ = true; }
std::string MQTTComponent::unique_id() { return ""; }
bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connected(); }
// Pull these properties from EntityBase if not overridden

View File

@@ -164,6 +164,13 @@ class MQTTComponent : public Component {
*/
virtual const EntityBase *get_entity() const = 0;
/** A unique ID for this MQTT component, empty for no unique id. See unique ID requirements:
* https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements
*
* @return The unique id as a string.
*/
virtual std::string unique_id();
/// Get the friendly name of this MQTT component.
virtual std::string friendly_name() const;

View File

@@ -74,6 +74,7 @@ bool MQTTSensorComponent::publish_state(float value) {
int8_t accuracy = this->sensor_->get_accuracy_decimals();
return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy));
}
std::string MQTTSensorComponent::unique_id() { return this->sensor_->unique_id(); }
} // namespace mqtt
} // namespace esphome

View File

@@ -46,6 +46,7 @@ class MQTTSensorComponent : public mqtt::MQTTComponent {
/// Override for MQTTComponent, returns "sensor".
std::string component_type() const override;
const EntityBase *get_entity() const override;
std::string unique_id() override;
sensor::Sensor *sensor_;
optional<uint32_t> expire_after_; // Override the expire after advertised to Home Assistant

View File

@@ -38,6 +38,7 @@ bool MQTTTextSensor::send_initial_state() {
}
std::string MQTTTextSensor::component_type() const { return "sensor"; }
const EntityBase *MQTTTextSensor::get_entity() const { return this->sensor_; }
std::string MQTTTextSensor::unique_id() { return this->sensor_->unique_id(); }
} // namespace mqtt
} // namespace esphome

View File

@@ -28,6 +28,7 @@ class MQTTTextSensor : public mqtt::MQTTComponent {
protected:
std::string component_type() const override;
const EntityBase *get_entity() const override;
std::string unique_id() override;
text_sensor::TextSensor *sensor_;
};

View File

@@ -337,23 +337,26 @@ 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;
}

View File

@@ -337,15 +337,6 @@ 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");
@@ -353,7 +344,18 @@ 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;
}

View File

@@ -11,6 +11,8 @@ const std::string &OneWireDevice::get_address_name() {
return this->address_name_;
}
std::string OneWireDevice::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); }
bool OneWireDevice::send_command_(uint8_t cmd) {
if (!this->bus_->select(this->address_))
return false;

View File

@@ -24,6 +24,8 @@ class OneWireDevice {
/// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
const std::string &get_address_name();
std::string unique_id();
protected:
uint64_t address_{0};
OneWireBus *bus_{nullptr}; ///< pointer to OneWireBus instance

View File

@@ -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),

View File

@@ -85,6 +85,7 @@ void Sensor::clear_filters() {
}
float Sensor::get_state() const { return this->state; }
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->has_state_ = true;

View File

@@ -27,6 +27,9 @@ namespace sensor {
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
if (!(obj)->unique_id().empty()) { \
ESP_LOGV(TAG, "%s Unique ID: '%s'", prefix, (obj)->unique_id().c_str()); \
} \
if ((obj)->get_force_update()) { \
ESP_LOGV(TAG, "%s Force Update: YES", prefix); \
} \
@@ -140,6 +143,12 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
/// 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).
*/
virtual std::string unique_id();
void internal_send_state_to_frontend(float state);
protected:

View File

@@ -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),

View File

@@ -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),

View File

@@ -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;

View File

@@ -65,6 +65,7 @@ void TextSensor::internal_send_state_to_frontend(const std::string &state) {
this->callback_.call(state);
}
std::string TextSensor::unique_id() { return ""; }
bool TextSensor::has_state() { return this->has_state_; }
} // namespace text_sensor

View File

@@ -19,6 +19,9 @@ namespace text_sensor {
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
if (!(obj)->unique_id().empty()) { \
ESP_LOGV(TAG, "%s Unique ID: '%s'", prefix, (obj)->unique_id().c_str()); \
} \
}
#define SUB_TEXT_SENSOR(name) \
@@ -58,6 +61,11 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass {
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
/** 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).
*/
virtual std::string unique_id();
bool has_state();

View File

@@ -27,6 +27,7 @@ void UptimeSecondsSensor::update() {
const float seconds = float(seconds_int) + (this->uptime_ % 1000ULL) / 1000.0f;
this->publish_state(seconds);
}
std::string UptimeSecondsSensor::unique_id() { return get_mac_address() + "-uptime"; }
float UptimeSecondsSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
void UptimeSecondsSensor::dump_config() {
LOG_SENSOR("", "Uptime Sensor", this);

View File

@@ -13,6 +13,8 @@ class UptimeSecondsSensor : public sensor::Sensor, public PollingComponent {
float get_setup_priority() const override;
std::string unique_id() override;
protected:
uint64_t uptime_{0};
};

View File

@@ -17,6 +17,7 @@ void VersionTextSensor::setup() {
}
float VersionTextSensor::get_setup_priority() const { return setup_priority::DATA; }
void VersionTextSensor::set_hide_timestamp(bool hide_timestamp) { this->hide_timestamp_ = hide_timestamp; }
std::string VersionTextSensor::unique_id() { return get_mac_address() + "-version"; }
void VersionTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Version Text Sensor", this); }
} // namespace version

View File

@@ -12,6 +12,7 @@ class VersionTextSensor : public text_sensor::TextSensor, public Component {
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
std::string unique_id() override;
protected:
bool hide_timestamp_{false};

View File

@@ -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);

View File

@@ -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);

View File

@@ -28,6 +28,7 @@ class IPAddressWiFiInfo : public PollingComponent, public text_sensor::TextSenso
}
}
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
std::string unique_id() override { return get_mac_address() + "-wifiinfo-ip"; }
void dump_config() override;
void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; }
@@ -50,6 +51,7 @@ class DNSAddressWifiInfo : public PollingComponent, public text_sensor::TextSens
}
}
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
std::string unique_id() override { return get_mac_address() + "-wifiinfo-dns"; }
void dump_config() override;
protected:
@@ -78,6 +80,7 @@ class ScanResultsWiFiInfo : public PollingComponent, public text_sensor::TextSen
}
}
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
std::string unique_id() override { return get_mac_address() + "-wifiinfo-scanresults"; }
void dump_config() override;
protected:
@@ -94,6 +97,7 @@ class SSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
}
}
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
std::string unique_id() override { return get_mac_address() + "-wifiinfo-ssid"; }
void dump_config() override;
protected:
@@ -112,6 +116,7 @@ class BSSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
}
}
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
std::string unique_id() override { return get_mac_address() + "-wifiinfo-bssid"; }
void dump_config() override;
protected:
@@ -121,6 +126,7 @@ class BSSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
class MacAddressWifiInfo : public Component, public text_sensor::TextSensor {
public:
void setup() override { this->publish_state(get_mac_address_pretty()); }
std::string unique_id() override { return get_mac_address() + "-wifiinfo-macadr"; }
void dump_config() override;
};

View File

@@ -13,6 +13,7 @@ class WiFiSignalSensor : public sensor::Sensor, public PollingComponent {
void update() override { this->publish_state(wifi::global_wifi_component->wifi_rssi()); }
void dump_config() override;
std::string unique_id() override { return get_mac_address() + "-wifisignal"; }
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
};

View File

@@ -117,7 +117,9 @@ void Application::loop() {
// Use the last component's end time instead of calling millis() again
auto elapsed = last_op_end_time - this->last_loop_;
if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {
yield();
// Even if we overran the loop interval, we still need to select()
// to know if any sockets have data ready
this->yield_with_select_(0);
} else {
uint32_t delay_time = this->loop_interval_ - elapsed;
uint32_t next_schedule = this->scheduler.next_schedule_in().value_or(delay_time);
@@ -126,7 +128,7 @@ void Application::loop() {
next_schedule = std::max(next_schedule, delay_time / 2);
delay_time = std::min(next_schedule, delay_time);
this->delay_with_select_(delay_time);
this->yield_with_select_(delay_time);
}
this->last_loop_ = last_op_end_time;
@@ -215,7 +217,7 @@ void Application::teardown_components(uint32_t timeout_ms) {
// Give some time for I/O operations if components are still pending
if (!pending_components.empty()) {
this->delay_with_select_(1);
this->yield_with_select_(1);
}
// Update time for next iteration
@@ -293,8 +295,6 @@ bool Application::is_socket_ready(int fd) const {
// This function is thread-safe for reading the result of select()
// However, it should only be called after select() has been executed in the main loop
// The read_fds_ is only modified by select() in the main loop
if (HighFrequencyLoopRequester::is_high_frequency())
return true; // fd sets via select are not updated in high frequency looping - so force true fallback behavior
if (fd < 0 || fd >= FD_SETSIZE)
return false;
@@ -302,7 +302,9 @@ bool Application::is_socket_ready(int fd) const {
}
#endif
void Application::delay_with_select_(uint32_t delay_ms) {
void Application::yield_with_select_(uint32_t delay_ms) {
// Delay while monitoring sockets. When delay_ms is 0, always yield() to ensure other tasks run
// since select() with 0 timeout only polls without yielding.
#ifdef USE_SOCKET_SELECT_SUPPORT
if (!this->socket_fds_.empty()) {
// Update fd_set if socket list has changed
@@ -340,6 +342,10 @@ void Application::delay_with_select_(uint32_t delay_ms) {
ESP_LOGW(TAG, "select() failed with errno %d", errno);
delay(delay_ms);
}
// When delay_ms is 0, we need to yield since select(0) doesn't yield
if (delay_ms == 0) {
yield();
}
} else {
// No sockets registered, use regular delay
delay(delay_ms);

View File

@@ -575,7 +575,7 @@ class Application {
void feed_wdt_arch_();
/// Perform a delay while also monitoring socket file descriptors for readiness
void delay_with_select_(uint32_t delay_ms);
void yield_with_select_(uint32_t delay_ms);
std::vector<Component *> components_{};
std::vector<Component *> looping_components_{};

View File

@@ -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

View File

@@ -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){};

View File

@@ -3,6 +3,8 @@
#include <string>
#include <cstdint>
#include "string_ref.h"
#include "helpers.h"
#include "log.h"
namespace esphome {
@@ -29,7 +31,7 @@ class EntityBase {
// Get the unique Object ID of this Entity
uint32_t get_object_id_hash();
// Get/set whether this Entity should be hidden from outside of ESPHome
// Get/set whether this Entity should be hidden outside ESPHome
bool is_internal() const;
void set_internal(bool internal);
@@ -56,11 +58,12 @@ class EntityBase {
StringRef name_;
const char *object_id_c_str_{nullptr};
const char *icon_c_str_{nullptr};
uint32_t object_id_hash_;
uint32_t object_id_hash_{};
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)
@@ -85,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

View File

@@ -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;

View File

@@ -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();

View File

@@ -10,6 +10,7 @@ import urllib.parse
import esphome.config_validation as cv
from esphome.core import CORE, TimePeriodSeconds
from esphome.git_lock import git_operation_lock
_LOGGER = logging.getLogger(__name__)
@@ -59,66 +60,72 @@ def clone_or_update(
)
repo_dir = _compute_destination_path(key, domain)
if not repo_dir.is_dir():
_LOGGER.info("Cloning %s", key)
_LOGGER.debug("Location: %s", repo_dir)
cmd = ["git", "clone", "--depth=1"]
cmd += ["--", url, str(repo_dir)]
run_git_command(cmd)
if ref is not None:
# We need to fetch the PR branch first, otherwise git will complain
# about missing objects
_LOGGER.info("Fetching %s", ref)
run_git_command(["git", "fetch", "--", "origin", ref], str(repo_dir))
run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir))
if submodules is not None:
_LOGGER.info(
"Initialising submodules (%s) for %s", ", ".join(submodules), key
)
run_git_command(
["git", "submodule", "update", "--init"] + submodules, str(repo_dir)
)
else:
# Check refresh needed
file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD")
# On first clone, FETCH_HEAD does not exists
if not file_timestamp.exists():
file_timestamp = Path(repo_dir / ".git" / "HEAD")
age = datetime.now() - datetime.fromtimestamp(file_timestamp.stat().st_mtime)
if refresh is None or age.total_seconds() > refresh.total_seconds:
old_sha = run_git_command(["git", "rev-parse", "HEAD"], str(repo_dir))
_LOGGER.info("Updating %s", key)
# Use lock to prevent concurrent access to the same repository
with git_operation_lock(key):
if not repo_dir.is_dir():
_LOGGER.info("Cloning %s", key)
_LOGGER.debug("Location: %s", repo_dir)
# Stash local changes (if any)
run_git_command(
["git", "stash", "push", "--include-untracked"], str(repo_dir)
)
# Fetch remote ref
cmd = ["git", "fetch", "--", "origin"]
cmd = ["git", "clone", "--depth=1"]
cmd += ["--", url, str(repo_dir)]
run_git_command(cmd)
if ref is not None:
cmd.append(ref)
run_git_command(cmd, str(repo_dir))
# Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch)
run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir))
# We need to fetch the PR branch first, otherwise git will complain
# about missing objects
_LOGGER.info("Fetching %s", ref)
run_git_command(["git", "fetch", "--", "origin", ref], str(repo_dir))
run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir))
if submodules is not None:
_LOGGER.info(
"Updating submodules (%s) for %s", ", ".join(submodules), key
"Initialising submodules (%s) for %s", ", ".join(submodules), key
)
run_git_command(
["git", "submodule", "update", "--init"] + submodules, str(repo_dir)
)
def revert():
_LOGGER.info("Reverting changes to %s -> %s", key, old_sha)
run_git_command(["git", "reset", "--hard", old_sha], str(repo_dir))
else:
# Check refresh needed
file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD")
# On first clone, FETCH_HEAD does not exists
if not file_timestamp.exists():
file_timestamp = Path(repo_dir / ".git" / "HEAD")
age = datetime.now() - datetime.fromtimestamp(
file_timestamp.stat().st_mtime
)
if refresh is None or age.total_seconds() > refresh.total_seconds:
old_sha = run_git_command(["git", "rev-parse", "HEAD"], str(repo_dir))
_LOGGER.info("Updating %s", key)
_LOGGER.debug("Location: %s", repo_dir)
# Stash local changes (if any)
run_git_command(
["git", "stash", "push", "--include-untracked"], str(repo_dir)
)
# Fetch remote ref
cmd = ["git", "fetch", "--", "origin"]
if ref is not None:
cmd.append(ref)
run_git_command(cmd, str(repo_dir))
# Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch)
run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir))
return repo_dir, revert
if submodules is not None:
_LOGGER.info(
"Updating submodules (%s) for %s", ", ".join(submodules), key
)
run_git_command(
["git", "submodule", "update", "--init"] + submodules,
str(repo_dir),
)
return repo_dir, None
def revert():
_LOGGER.info("Reverting changes to %s -> %s", key, old_sha)
run_git_command(["git", "reset", "--hard", old_sha], str(repo_dir))
return repo_dir, revert
return repo_dir, None
GIT_DOMAINS = {

141
esphome/git_lock.py Normal file
View File

@@ -0,0 +1,141 @@
"""File locking for git operations to prevent race conditions."""
from contextlib import contextmanager
import hashlib
import logging
from pathlib import Path
import sys
import tempfile
import time
# Platform-specific imports
if sys.platform == "win32":
import msvcrt
else:
import fcntl
_LOGGER = logging.getLogger(__name__)
# Global lock directory
LOCK_DIR = Path(tempfile.gettempdir()) / "esphome_git_locks"
LOCK_DIR.mkdir(exist_ok=True)
def _acquire_lock_unix(lock_file, timeout, identifier):
"""Acquire lock on Unix systems using fcntl."""
start_time = time.time()
last_log_time = start_time
while True:
try:
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
return True
except OSError:
elapsed = time.time() - start_time
if elapsed > timeout:
raise TimeoutError(
f"Could not acquire lock for {identifier} within {timeout}s"
)
# Log progress every 10 seconds
if time.time() - last_log_time > 10:
_LOGGER.info(
f"Still waiting for lock {identifier} ({elapsed:.1f}s elapsed)..."
)
last_log_time = time.time()
time.sleep(0.1)
def _release_lock_unix(lock_file):
"""Release lock on Unix systems."""
try:
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
except Exception:
pass
def _acquire_lock_windows(lock_file, timeout, identifier):
"""Acquire lock on Windows systems using msvcrt."""
start_time = time.time()
while True:
try:
msvcrt.locking(lock_file.fileno(), msvcrt.LK_NBLCK, 1)
return True
except OSError:
if time.time() - start_time > timeout:
raise TimeoutError(
f"Could not acquire lock for {identifier} within {timeout}s"
)
time.sleep(0.1)
def _release_lock_windows(lock_file):
"""Release lock on Windows systems."""
try:
msvcrt.locking(lock_file.fileno(), msvcrt.LK_UNLCK, 1)
except Exception:
pass
@contextmanager
def git_operation_lock(identifier: str, timeout: float = 30.0):
"""
Acquire a file lock for a git operation.
:param identifier: Unique identifier for the operation (e.g., repo URL or path)
:param timeout: Maximum time to wait for the lock in seconds
"""
# Create a safe filename from the identifier
lock_name = hashlib.sha256(identifier.encode()).hexdigest()[:16]
lock_path = LOCK_DIR / f"{lock_name}.lock"
# Ensure lock file exists
lock_path.touch(exist_ok=True)
lock_file = None
acquired = False
try:
# Open in binary mode for Windows compatibility
lock_file = open(lock_path, "r+b")
# Platform-specific lock acquisition
if sys.platform == "win32":
acquired = _acquire_lock_windows(lock_file, timeout, identifier)
else:
acquired = _acquire_lock_unix(lock_file, timeout, identifier)
if acquired:
_LOGGER.debug(f"Acquired lock for {identifier}")
yield
finally:
if lock_file:
if acquired:
# Platform-specific lock release
if sys.platform == "win32":
_release_lock_windows(lock_file)
else:
_release_lock_unix(lock_file)
_LOGGER.debug(f"Released lock for {identifier}")
lock_file.close()
@contextmanager
def platformio_init_lock(timeout: float = 30.0):
"""Lock for PlatformIO initialization to prevent race conditions."""
with git_operation_lock("platformio_init", timeout=timeout):
yield
@contextmanager
def platformio_install_lock(package_name: str, timeout: float = 300.0):
"""Lock for PlatformIO package installation to prevent race conditions."""
_LOGGER.info(
f"Waiting for PlatformIO package installation lock ({package_name})..."
)
with git_operation_lock(f"platformio_install_{package_name}", timeout=timeout):
_LOGGER.info(f"Acquired PlatformIO package installation lock ({package_name})")
yield
_LOGGER.info(f"Released PlatformIO package installation lock ({package_name})")

View File

@@ -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

View File

@@ -86,9 +86,28 @@ def run_platformio_cli(*args, **kwargs) -> str | int:
if os.environ.get("ESPHOME_USE_SUBPROCESS") is not None:
return run_external_process(*cmd, **kwargs)
import platformio.__main__
# Import with minimal locking to prevent initialization race conditions
from esphome.git_lock import platformio_init_lock
with platformio_init_lock():
import platformio.__main__
patch_structhash()
# For first-time PlatformIO runs, use a lock to prevent directory creation conflicts
home_pio = Path.home() / ".platformio"
if not home_pio.exists() and len(args) > 0 and args[0] == "run":
from esphome.git_lock import platformio_install_lock
_LOGGER.info("First PlatformIO run detected, using initialization lock...")
with platformio_install_lock("first_run", timeout=120.0):
# Create the directory if it still doesn't exist
home_pio.mkdir(exist_ok=True)
result = run_external_command(platformio.__main__.main, *cmd, **kwargs)
_LOGGER.info("First PlatformIO run completed")
return result
# Normal execution without locking
return run_external_command(platformio.__main__.main, *cmd, **kwargs)

View File

@@ -6,7 +6,7 @@ pre-commit
# Unit tests
pytest==8.4.0
pytest-cov==6.1.1
pytest-cov==6.2.1
pytest-mock==3.14.1
pytest-asyncio==0.26.0
pytest-xdist==3.7.0

View File

@@ -3,25 +3,37 @@
set -e
help() {
echo "Usage: $0 [-e <config|compile|clean>] [-c <string>] [-t <string>]" 1>&2
echo "Usage: $0 [-e <config|compile|clean>] [-c <string>] [-t <string>] [-j <number>] [-p <string>] [-f]" 1>&2
echo 1>&2
echo " - e - Parameter for esphome command. Default compile. Common alternative is config." 1>&2
echo " - c - Component folder name to test. Default *. E.g. '-c logger'." 1>&2
echo " - t - Target name to test. Put '-t list' to display all possibilities. E.g. '-t esp32-s2-idf-51'." 1>&2
echo " - j - Number of parallel jobs. Default is number of CPU cores." 1>&2
echo " - p - Platform filter. E.g. '-p esp32' to test only ESP32 platforms." 1>&2
echo " - f - Fail fast. Exit on first failure." 1>&2
echo " - b - Build cache directory. E.g. '-b /tmp/esphome_cache'." 1>&2
exit 1
}
# Parse parameter:
# - `e` - Parameter for `esphome` command. Default `compile`. Common alternative is `config`.
# - `c` - Component folder name to test. Default `*`.
esphome_command="compile"
target_component="*"
while getopts e:c:t: flag
num_jobs=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
platform_filter=""
fail_fast=false
build_cache_dir=""
while getopts e:c:t:j:p:b:fh flag
do
case $flag in
e) esphome_command=${OPTARG};;
c) target_component=${OPTARG};;
t) requested_target_platform=${OPTARG};;
j) num_jobs=${OPTARG};;
p) platform_filter=${OPTARG};;
f) fail_fast=true;;
b) build_cache_dir=${OPTARG};;
h) help;;
\?) help;;
esac
done
@@ -29,16 +41,66 @@ done
cd "$(dirname "$0")/.."
if ! [ -d "./tests/test_build_components/build" ]; then
mkdir ./tests/test_build_components/build
mkdir -p ./tests/test_build_components/build
fi
# Export build cache directory if specified
if [ -n "$build_cache_dir" ]; then
export PLATFORMIO_BUILD_CACHE_DIR="$build_cache_dir"
mkdir -p "$build_cache_dir"
echo "Using build cache directory: $build_cache_dir"
fi
# Track PIDs for parallel execution
pids=()
failed_builds=()
build_count=0
total_builds=0
# Function to wait for jobs and handle failures
wait_for_jobs() {
local max_jobs=$1
while [ ${#pids[@]} -ge $max_jobs ]; do
for i in "${!pids[@]}"; do
if ! kill -0 "${pids[$i]}" 2>/dev/null; then
wait "${pids[$i]}"
exit_code=$?
if [ $exit_code -ne 0 ]; then
failed_builds+=("${build_info[$i]}")
if [ "$fail_fast" = true ]; then
echo "Build failed, exiting due to fail-fast mode"
# Kill remaining jobs
for pid in "${pids[@]}"; do
kill -TERM "$pid" 2>/dev/null || true
done
exit 1
fi
fi
unset pids[$i]
unset build_info[$i]
# Reindex arrays
pids=("${pids[@]}")
build_info=("${build_info[@]}")
break
fi
done
sleep 0.1
done
}
start_esphome() {
if [ -n "$requested_target_platform" ] && [ "$requested_target_platform" != "$target_platform_with_version" ]; then
echo "Skipping $target_platform_with_version"
return
fi
# Apply platform filter if specified
if [ -n "$platform_filter" ] && [[ ! "$target_platform_with_version" =~ ^$platform_filter ]]; then
echo "Skipping $target_platform_with_version (filtered)"
return
fi
# create dynamic yaml file in `build` folder.
# `./tests/test_build_components/build/[target_component].[test_name].[target_platform_with_version].yaml`
component_test_file="./tests/test_build_components/build/$target_component.$test_name.$target_platform_with_version.yaml"
cp $target_platform_file $component_test_file
@@ -49,17 +111,79 @@ start_esphome() {
sed -i "s!\$component_test_file!../../.$f!g" $component_test_file
fi
# Start esphome process
echo "> [$target_component] [$test_name] [$target_platform_with_version]"
set -x
# TODO: Validate escape of Command line substitution value
python3 -m esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file
{ set +x; } 2>/dev/null
# Start esphome process in background
build_count=$((build_count + 1))
echo "> [$build_count/$total_builds] [$target_component] [$test_name] [$target_platform_with_version]"
(
# Add compile process limit for ESPHome internal parallelization
export ESPHOME_COMPILE_PROCESS_LIMIT=2
# For compilation, add a small random delay to reduce thundering herd effect
# This helps stagger the package installation requests
if [ "$esphome_command" = "compile" ]; then
sleep $((RANDOM % 5))
fi
python3 -m esphome -s component_name $target_component -s component_dir ../../components/$target_component -s test_name $test_name -s target_platform $target_platform $esphome_command $component_test_file
) &
local pid=$!
pids+=($pid)
build_info+=("$target_component/$test_name/$target_platform_with_version")
# Wait if we've reached the job limit
wait_for_jobs $num_jobs
}
# Find all test yaml files.
# - `./tests/components/[target_component]/[test_name].[target_platform].yaml`
# - `./tests/components/[target_component]/[test_name].all.yaml`
# First pass: count total builds
echo "Calculating total number of builds..."
for f in ./tests/components/$target_component/*.*.yaml; do
[ -f "$f" ] || continue
IFS='/' read -r -a folder_name <<< "$f"
IFS='.' read -r -a file_name <<< "${folder_name[4]}"
target_platform="${file_name[1]}"
file_name_parts=${#file_name[@]}
if [ "$target_platform" = "all" ] || [ $file_name_parts = 2 ]; then
for target_platform_file in ./tests/test_build_components/build_components_base.*.yaml; do
IFS='/' read -r -a folder_name <<< "$target_platform_file"
IFS='.' read -r -a file_name <<< "${folder_name[3]}"
target_platform="${file_name[1]}"
target_platform_with_version=${target_platform_file:52}
target_platform_with_version=${target_platform_with_version%.*}
if [ -n "$platform_filter" ] && [[ ! "$target_platform_with_version" =~ ^$platform_filter ]]; then
continue
fi
if [ -n "$requested_target_platform" ] && [ "$requested_target_platform" != "$target_platform_with_version" ]; then
continue
fi
total_builds=$((total_builds + 1))
done
else
target_platform_file="./tests/test_build_components/build_components_base.$target_platform.yaml"
if [ -f "$target_platform_file" ]; then
for target_platform_file in ./tests/test_build_components/build_components_base.$target_platform*.yaml; do
target_platform_with_version=${target_platform_file:52}
target_platform_with_version=${target_platform_with_version%.*}
if [ -n "$platform_filter" ] && [[ ! "$target_platform_with_version" =~ ^$platform_filter ]]; then
continue
fi
if [ -n "$requested_target_platform" ] && [ "$requested_target_platform" != "$target_platform_with_version" ]; then
continue
fi
total_builds=$((total_builds + 1))
done
fi
fi
done
echo "Total builds to execute: $total_builds with $num_jobs parallel jobs"
echo
# Second pass: execute builds
for f in ./tests/components/$target_component/*.*.yaml; do
[ -f "$f" ] || continue
IFS='/' read -r -a folder_name <<< "$f"
@@ -72,22 +196,21 @@ for f in ./tests/components/$target_component/*.*.yaml; do
if [ "$target_platform" = "all" ] || [ $file_name_parts = 2 ]; then
# Test has *not* defined a specific target platform. Need to run tests for all possible target platforms.
for target_platform_file in ./tests/test_build_components/build_components_base.*.yaml; do
IFS='/' read -r -a folder_name <<< "$target_platform_file"
IFS='.' read -r -a file_name <<< "${folder_name[3]}"
target_platform="${file_name[1]}"
target_platform_with_version=${target_platform_file:52}
target_platform_with_version=${target_platform_with_version%.*}
start_esphome
done
else
# Test has defined a specific target platform.
# Validate we have a base test yaml for selected platform.
# The target_platform is sourced from the following location.
# 1. `./tests/test_build_components/build_components_base.[target_platform].yaml`
# 2. `./tests/test_build_components/build_components_base.[target_platform]-ard.yaml`
target_platform_file="./tests/test_build_components/build_components_base.$target_platform.yaml"
if ! [ -f "$target_platform_file" ]; then
echo "No base test file [./tests/test_build_components/build_components_base.$target_platform.yaml] for component test [$f] found."
@@ -104,3 +227,23 @@ for f in ./tests/components/$target_component/*.*.yaml; do
done
fi
done
# Wait for all remaining jobs
wait_for_jobs 1
echo
echo "============================================"
echo "Build Summary:"
echo "Total builds: $total_builds"
echo "Failed builds: ${#failed_builds[@]}"
if [ ${#failed_builds[@]} -gt 0 ]; then
echo
echo "Failed builds:"
for build in "${failed_builds[@]}"; do
echo " - $build"
done
exit 1
else
echo "All builds completed successfully!"
fi

View 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

View File

@@ -0,0 +1,2 @@
packages:
common: !include common.yaml

View File

@@ -0,0 +1,2 @@
packages:
common: !include common.yaml

View File

@@ -0,0 +1,2 @@
packages:
common: !include common.yaml

View File

@@ -0,0 +1,2 @@
packages:
common: !include common.yaml

View File

@@ -0,0 +1,2 @@
packages:
common: !include common.yaml

View File

@@ -0,0 +1,2 @@
packages:
common: !include common.yaml

View File

@@ -0,0 +1,2 @@
packages:
common: !include common.yaml

View File

@@ -0,0 +1,2 @@
packages:
common: !include common.yaml

View File

@@ -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