mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'dev' into message_creator_ram
This commit is contained in:
		| @@ -14,8 +14,8 @@ from esphome.const import ( | |||||||
|     CONF_WEB_SERVER, |     CONF_WEB_SERVER, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@grahambrown11", "@hwstar"] | CODEOWNERS = ["@grahambrown11", "@hwstar"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
| @@ -149,6 +149,9 @@ _ALARM_CONTROL_PANEL_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _ALARM_CONTROL_PANEL_SCHEMA.add_extra(entity_duplicate_validator("alarm_control_panel")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def alarm_control_panel_schema( | def alarm_control_panel_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
|     *, |     *, | ||||||
| @@ -190,7 +193,7 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id( | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_alarm_control_panel_core_(var, config): | async def setup_alarm_control_panel_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "alarm_control_panel") | ||||||
|     for conf in config.get(CONF_ON_STATE, []): |     for conf in config.get(CONF_ON_STATE, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|         await automation.build_automation(trigger, [], conf) |         await automation.build_automation(trigger, [], conf) | ||||||
|   | |||||||
| @@ -188,6 +188,17 @@ message DeviceInfoRequest { | |||||||
|   // Empty |   // Empty | ||||||
| } | } | ||||||
|  |  | ||||||
|  | message AreaInfo { | ||||||
|  |   uint32 area_id = 1; | ||||||
|  |   string name = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message DeviceInfo { | ||||||
|  |   uint32 device_id = 1; | ||||||
|  |   string name = 2; | ||||||
|  |   uint32 area_id = 3; | ||||||
|  | } | ||||||
|  |  | ||||||
| message DeviceInfoResponse { | message DeviceInfoResponse { | ||||||
|   option (id) = 10; |   option (id) = 10; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
| @@ -236,6 +247,12 @@ message DeviceInfoResponse { | |||||||
|  |  | ||||||
|   // Supports receiving and saving api encryption key |   // Supports receiving and saving api encryption key | ||||||
|   bool api_encryption_supported = 19; |   bool api_encryption_supported = 19; | ||||||
|  |  | ||||||
|  |   repeated DeviceInfo devices = 20; | ||||||
|  |   repeated AreaInfo areas = 21; | ||||||
|  |  | ||||||
|  |   // Top-level area info to phase out suggested_area | ||||||
|  |   AreaInfo area = 22; | ||||||
| } | } | ||||||
|  |  | ||||||
| message ListEntitiesRequest { | message ListEntitiesRequest { | ||||||
| @@ -280,6 +297,7 @@ message ListEntitiesBinarySensorResponse { | |||||||
|   bool disabled_by_default = 7; |   bool disabled_by_default = 7; | ||||||
|   string icon = 8; |   string icon = 8; | ||||||
|   EntityCategory entity_category = 9; |   EntityCategory entity_category = 9; | ||||||
|  |   uint32 device_id = 10; | ||||||
| } | } | ||||||
| message BinarySensorStateResponse { | message BinarySensorStateResponse { | ||||||
|   option (id) = 21; |   option (id) = 21; | ||||||
| @@ -315,6 +333,7 @@ message ListEntitiesCoverResponse { | |||||||
|   string icon = 10; |   string icon = 10; | ||||||
|   EntityCategory entity_category = 11; |   EntityCategory entity_category = 11; | ||||||
|   bool supports_stop = 12; |   bool supports_stop = 12; | ||||||
|  |   uint32 device_id = 13; | ||||||
| } | } | ||||||
|  |  | ||||||
| enum LegacyCoverState { | enum LegacyCoverState { | ||||||
| @@ -388,6 +407,7 @@ message ListEntitiesFanResponse { | |||||||
|   string icon = 10; |   string icon = 10; | ||||||
|   EntityCategory entity_category = 11; |   EntityCategory entity_category = 11; | ||||||
|   repeated string supported_preset_modes = 12; |   repeated string supported_preset_modes = 12; | ||||||
|  |   uint32 device_id = 13; | ||||||
| } | } | ||||||
| enum FanSpeed { | enum FanSpeed { | ||||||
|   FAN_SPEED_LOW = 0; |   FAN_SPEED_LOW = 0; | ||||||
| @@ -471,6 +491,7 @@ message ListEntitiesLightResponse { | |||||||
|   bool disabled_by_default = 13; |   bool disabled_by_default = 13; | ||||||
|   string icon = 14; |   string icon = 14; | ||||||
|   EntityCategory entity_category = 15; |   EntityCategory entity_category = 15; | ||||||
|  |   uint32 device_id = 16; | ||||||
| } | } | ||||||
| message LightStateResponse { | message LightStateResponse { | ||||||
|   option (id) = 24; |   option (id) = 24; | ||||||
| @@ -563,6 +584,7 @@ message ListEntitiesSensorResponse { | |||||||
|   SensorLastResetType legacy_last_reset_type = 11; |   SensorLastResetType legacy_last_reset_type = 11; | ||||||
|   bool disabled_by_default = 12; |   bool disabled_by_default = 12; | ||||||
|   EntityCategory entity_category = 13; |   EntityCategory entity_category = 13; | ||||||
|  |   uint32 device_id = 14; | ||||||
| } | } | ||||||
| message SensorStateResponse { | message SensorStateResponse { | ||||||
|   option (id) = 25; |   option (id) = 25; | ||||||
| @@ -595,6 +617,7 @@ message ListEntitiesSwitchResponse { | |||||||
|   bool disabled_by_default = 7; |   bool disabled_by_default = 7; | ||||||
|   EntityCategory entity_category = 8; |   EntityCategory entity_category = 8; | ||||||
|   string device_class = 9; |   string device_class = 9; | ||||||
|  |   uint32 device_id = 10; | ||||||
| } | } | ||||||
| message SwitchStateResponse { | message SwitchStateResponse { | ||||||
|   option (id) = 26; |   option (id) = 26; | ||||||
| @@ -632,6 +655,7 @@ message ListEntitiesTextSensorResponse { | |||||||
|   bool disabled_by_default = 6; |   bool disabled_by_default = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|   string device_class = 8; |   string device_class = 8; | ||||||
|  |   uint32 device_id = 9; | ||||||
| } | } | ||||||
| message TextSensorStateResponse { | message TextSensorStateResponse { | ||||||
|   option (id) = 27; |   option (id) = 27; | ||||||
| @@ -814,6 +838,7 @@ message ListEntitiesCameraResponse { | |||||||
|   bool disabled_by_default = 5; |   bool disabled_by_default = 5; | ||||||
|   string icon = 6; |   string icon = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|  |   uint32 device_id = 8; | ||||||
| } | } | ||||||
|  |  | ||||||
| message CameraImageResponse { | message CameraImageResponse { | ||||||
| @@ -916,6 +941,7 @@ message ListEntitiesClimateResponse { | |||||||
|   bool supports_target_humidity = 23; |   bool supports_target_humidity = 23; | ||||||
|   float visual_min_humidity = 24; |   float visual_min_humidity = 24; | ||||||
|   float visual_max_humidity = 25; |   float visual_max_humidity = 25; | ||||||
|  |   uint32 device_id = 26; | ||||||
| } | } | ||||||
| message ClimateStateResponse { | message ClimateStateResponse { | ||||||
|   option (id) = 47; |   option (id) = 47; | ||||||
| @@ -999,6 +1025,7 @@ message ListEntitiesNumberResponse { | |||||||
|   string unit_of_measurement = 11; |   string unit_of_measurement = 11; | ||||||
|   NumberMode mode = 12; |   NumberMode mode = 12; | ||||||
|   string device_class = 13; |   string device_class = 13; | ||||||
|  |   uint32 device_id = 14; | ||||||
| } | } | ||||||
| message NumberStateResponse { | message NumberStateResponse { | ||||||
|   option (id) = 50; |   option (id) = 50; | ||||||
| @@ -1039,6 +1066,7 @@ message ListEntitiesSelectResponse { | |||||||
|   repeated string options = 6; |   repeated string options = 6; | ||||||
|   bool disabled_by_default = 7; |   bool disabled_by_default = 7; | ||||||
|   EntityCategory entity_category = 8; |   EntityCategory entity_category = 8; | ||||||
|  |   uint32 device_id = 9; | ||||||
| } | } | ||||||
| message SelectStateResponse { | message SelectStateResponse { | ||||||
|   option (id) = 53; |   option (id) = 53; | ||||||
| @@ -1081,6 +1109,7 @@ message ListEntitiesSirenResponse { | |||||||
|   bool supports_duration = 8; |   bool supports_duration = 8; | ||||||
|   bool supports_volume = 9; |   bool supports_volume = 9; | ||||||
|   EntityCategory entity_category = 10; |   EntityCategory entity_category = 10; | ||||||
|  |   uint32 device_id = 11; | ||||||
| } | } | ||||||
| message SirenStateResponse { | message SirenStateResponse { | ||||||
|   option (id) = 56; |   option (id) = 56; | ||||||
| @@ -1144,6 +1173,7 @@ message ListEntitiesLockResponse { | |||||||
|  |  | ||||||
|   // Not yet implemented: |   // Not yet implemented: | ||||||
|   string code_format = 11; |   string code_format = 11; | ||||||
|  |   uint32 device_id = 12; | ||||||
| } | } | ||||||
| message LockStateResponse { | message LockStateResponse { | ||||||
|   option (id) = 59; |   option (id) = 59; | ||||||
| @@ -1183,6 +1213,7 @@ message ListEntitiesButtonResponse { | |||||||
|   bool disabled_by_default = 6; |   bool disabled_by_default = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|   string device_class = 8; |   string device_class = 8; | ||||||
|  |   uint32 device_id = 9; | ||||||
| } | } | ||||||
| message ButtonCommandRequest { | message ButtonCommandRequest { | ||||||
|   option (id) = 62; |   option (id) = 62; | ||||||
| @@ -1238,6 +1269,8 @@ message ListEntitiesMediaPlayerResponse { | |||||||
|   bool supports_pause = 8; |   bool supports_pause = 8; | ||||||
|  |  | ||||||
|   repeated MediaPlayerSupportedFormat supported_formats = 9; |   repeated MediaPlayerSupportedFormat supported_formats = 9; | ||||||
|  |  | ||||||
|  |   uint32 device_id = 10; | ||||||
| } | } | ||||||
| message MediaPlayerStateResponse { | message MediaPlayerStateResponse { | ||||||
|   option (id) = 64; |   option (id) = 64; | ||||||
| @@ -1778,6 +1811,7 @@ message ListEntitiesAlarmControlPanelResponse { | |||||||
|   uint32 supported_features = 8; |   uint32 supported_features = 8; | ||||||
|   bool requires_code = 9; |   bool requires_code = 9; | ||||||
|   bool requires_code_to_arm = 10; |   bool requires_code_to_arm = 10; | ||||||
|  |   uint32 device_id = 11; | ||||||
| } | } | ||||||
|  |  | ||||||
| message AlarmControlPanelStateResponse { | message AlarmControlPanelStateResponse { | ||||||
| @@ -1823,6 +1857,7 @@ message ListEntitiesTextResponse { | |||||||
|   uint32 max_length = 9; |   uint32 max_length = 9; | ||||||
|   string pattern = 10; |   string pattern = 10; | ||||||
|   TextMode mode = 11; |   TextMode mode = 11; | ||||||
|  |   uint32 device_id = 12; | ||||||
| } | } | ||||||
| message TextStateResponse { | message TextStateResponse { | ||||||
|   option (id) = 98; |   option (id) = 98; | ||||||
| @@ -1863,6 +1898,7 @@ message ListEntitiesDateResponse { | |||||||
|   string icon = 5; |   string icon = 5; | ||||||
|   bool disabled_by_default = 6; |   bool disabled_by_default = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|  |   uint32 device_id = 8; | ||||||
| } | } | ||||||
| message DateStateResponse { | message DateStateResponse { | ||||||
|   option (id) = 101; |   option (id) = 101; | ||||||
| @@ -1906,6 +1942,7 @@ message ListEntitiesTimeResponse { | |||||||
|   string icon = 5; |   string icon = 5; | ||||||
|   bool disabled_by_default = 6; |   bool disabled_by_default = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|  |   uint32 device_id = 8; | ||||||
| } | } | ||||||
| message TimeStateResponse { | message TimeStateResponse { | ||||||
|   option (id) = 104; |   option (id) = 104; | ||||||
| @@ -1952,6 +1989,7 @@ message ListEntitiesEventResponse { | |||||||
|   string device_class = 8; |   string device_class = 8; | ||||||
|  |  | ||||||
|   repeated string event_types = 9; |   repeated string event_types = 9; | ||||||
|  |   uint32 device_id = 10; | ||||||
| } | } | ||||||
| message EventResponse { | message EventResponse { | ||||||
|   option (id) = 108; |   option (id) = 108; | ||||||
| @@ -1983,6 +2021,7 @@ message ListEntitiesValveResponse { | |||||||
|   bool assumed_state = 9; |   bool assumed_state = 9; | ||||||
|   bool supports_position = 10; |   bool supports_position = 10; | ||||||
|   bool supports_stop = 11; |   bool supports_stop = 11; | ||||||
|  |   uint32 device_id = 12; | ||||||
| } | } | ||||||
|  |  | ||||||
| enum ValveOperation { | enum ValveOperation { | ||||||
| @@ -2029,6 +2068,7 @@ message ListEntitiesDateTimeResponse { | |||||||
|   string icon = 5; |   string icon = 5; | ||||||
|   bool disabled_by_default = 6; |   bool disabled_by_default = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|  |   uint32 device_id = 8; | ||||||
| } | } | ||||||
| message DateTimeStateResponse { | message DateTimeStateResponse { | ||||||
|   option (id) = 113; |   option (id) = 113; | ||||||
| @@ -2069,6 +2109,7 @@ message ListEntitiesUpdateResponse { | |||||||
|   bool disabled_by_default = 6; |   bool disabled_by_default = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|   string device_class = 8; |   string device_class = 8; | ||||||
|  |   uint32 device_id = 9; | ||||||
| } | } | ||||||
| message UpdateStateResponse { | message UpdateStateResponse { | ||||||
|   option (id) = 117; |   option (id) = 117; | ||||||
|   | |||||||
| @@ -1629,6 +1629,23 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { | |||||||
| #endif | #endif | ||||||
| #ifdef USE_API_NOISE | #ifdef USE_API_NOISE | ||||||
|   resp.api_encryption_supported = true; |   resp.api_encryption_supported = true; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_DEVICES | ||||||
|  |   for (auto const &device : App.get_devices()) { | ||||||
|  |     DeviceInfo device_info; | ||||||
|  |     device_info.device_id = device->get_device_id(); | ||||||
|  |     device_info.name = device->get_name(); | ||||||
|  |     device_info.area_id = device->get_area_id(); | ||||||
|  |     resp.devices.push_back(device_info); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_AREAS | ||||||
|  |   for (auto const &area : App.get_areas()) { | ||||||
|  |     AreaInfo area_info; | ||||||
|  |     area_info.area_id = area->get_area_id(); | ||||||
|  |     area_info.name = area->get_name(); | ||||||
|  |     resp.areas.push_back(area_info); | ||||||
|  |   } | ||||||
| #endif | #endif | ||||||
|   return resp; |   return resp; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -301,6 +301,9 @@ class APIConnection : public APIServerConnection { | |||||||
|     response.icon = entity->get_icon(); |     response.icon = entity->get_icon(); | ||||||
|     response.disabled_by_default = entity->is_disabled_by_default(); |     response.disabled_by_default = entity->is_disabled_by_default(); | ||||||
|     response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category()); |     response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category()); | ||||||
|  | #ifdef USE_DEVICES | ||||||
|  |     response.device_id = entity->get_device_id(); | ||||||
|  | #endif | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Helper function to fill common entity state fields |   // Helper function to fill common entity state fields | ||||||
|   | |||||||
| @@ -812,6 +812,103 @@ void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {} | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); } | void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); } | ||||||
| #endif | #endif | ||||||
|  | bool AreaInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->area_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool AreaInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 2: { | ||||||
|  |       this->name = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void AreaInfo::encode(ProtoWriteBuffer buffer) const { | ||||||
|  |   buffer.encode_uint32(1, this->area_id); | ||||||
|  |   buffer.encode_string(2, this->name); | ||||||
|  | } | ||||||
|  | void AreaInfo::calculate_size(uint32_t &total_size) const { | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->area_id, false); | ||||||
|  |   ProtoSize::add_string_field(total_size, 1, this->name, false); | ||||||
|  | } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void AreaInfo::dump_to(std::string &out) const { | ||||||
|  |   __attribute__((unused)) char buffer[64]; | ||||||
|  |   out.append("AreaInfo {\n"); | ||||||
|  |   out.append("  area_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->area_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  name: "); | ||||||
|  |   out.append("'").append(this->name).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | bool DeviceInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 3: { | ||||||
|  |       this->area_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool DeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 2: { | ||||||
|  |       this->name = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void DeviceInfo::encode(ProtoWriteBuffer buffer) const { | ||||||
|  |   buffer.encode_uint32(1, this->device_id); | ||||||
|  |   buffer.encode_string(2, this->name); | ||||||
|  |   buffer.encode_uint32(3, this->area_id); | ||||||
|  | } | ||||||
|  | void DeviceInfo::calculate_size(uint32_t &total_size) const { | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
|  |   ProtoSize::add_string_field(total_size, 1, this->name, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->area_id, false); | ||||||
|  | } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void DeviceInfo::dump_to(std::string &out) const { | ||||||
|  |   __attribute__((unused)) char buffer[64]; | ||||||
|  |   out.append("DeviceInfo {\n"); | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  name: "); | ||||||
|  |   out.append("'").append(this->name).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  area_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->area_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
| bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|   switch (field_id) { |   switch (field_id) { | ||||||
|     case 1: { |     case 1: { | ||||||
| @@ -896,6 +993,18 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v | |||||||
|       this->bluetooth_mac_address = value.as_string(); |       this->bluetooth_mac_address = value.as_string(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 20: { | ||||||
|  |       this->devices.push_back(value.as_message<DeviceInfo>()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 21: { | ||||||
|  |       this->areas.push_back(value.as_message<AreaInfo>()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 22: { | ||||||
|  |       this->area = value.as_message<AreaInfo>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -920,6 +1029,13 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(16, this->suggested_area); |   buffer.encode_string(16, this->suggested_area); | ||||||
|   buffer.encode_string(18, this->bluetooth_mac_address); |   buffer.encode_string(18, this->bluetooth_mac_address); | ||||||
|   buffer.encode_bool(19, this->api_encryption_supported); |   buffer.encode_bool(19, this->api_encryption_supported); | ||||||
|  |   for (auto &it : this->devices) { | ||||||
|  |     buffer.encode_message<DeviceInfo>(20, it, true); | ||||||
|  |   } | ||||||
|  |   for (auto &it : this->areas) { | ||||||
|  |     buffer.encode_message<AreaInfo>(21, it, true); | ||||||
|  |   } | ||||||
|  |   buffer.encode_message<AreaInfo>(22, this->area); | ||||||
| } | } | ||||||
| void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { | void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->uses_password, false); |   ProtoSize::add_bool_field(total_size, 1, this->uses_password, false); | ||||||
| @@ -941,6 +1057,9 @@ void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_string_field(total_size, 2, this->suggested_area, false); |   ProtoSize::add_string_field(total_size, 2, this->suggested_area, false); | ||||||
|   ProtoSize::add_string_field(total_size, 2, this->bluetooth_mac_address, false); |   ProtoSize::add_string_field(total_size, 2, this->bluetooth_mac_address, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 2, this->api_encryption_supported, false); |   ProtoSize::add_bool_field(total_size, 2, this->api_encryption_supported, false); | ||||||
|  |   ProtoSize::add_repeated_message(total_size, 2, this->devices); | ||||||
|  |   ProtoSize::add_repeated_message(total_size, 2, this->areas); | ||||||
|  |   ProtoSize::add_message_object(total_size, 2, this->area, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void DeviceInfoResponse::dump_to(std::string &out) const { | void DeviceInfoResponse::dump_to(std::string &out) const { | ||||||
| @@ -1026,6 +1145,22 @@ void DeviceInfoResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  api_encryption_supported: "); |   out.append("  api_encryption_supported: "); | ||||||
|   out.append(YESNO(this->api_encryption_supported)); |   out.append(YESNO(this->api_encryption_supported)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   for (const auto &it : this->devices) { | ||||||
|  |     out.append("  devices: "); | ||||||
|  |     it.dump_to(out); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (const auto &it : this->areas) { | ||||||
|  |     out.append("  areas: "); | ||||||
|  |     it.dump_to(out); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   out.append("  area: "); | ||||||
|  |   this->area.dump_to(out); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -1052,6 +1187,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 10: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -1102,6 +1241,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(7, this->disabled_by_default); |   buffer.encode_bool(7, this->disabled_by_default); | ||||||
|   buffer.encode_string(8, this->icon); |   buffer.encode_string(8, this->icon); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(9, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(9, this->entity_category); | ||||||
|  |   buffer.encode_uint32(10, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -1113,6 +1253,7 @@ void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) cons | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, 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_string_field(total_size, 1, this->icon, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | ||||||
| @@ -1154,6 +1295,11 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -1236,6 +1382,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val | |||||||
|       this->supports_stop = value.as_bool(); |       this->supports_stop = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 13: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -1289,6 +1439,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(10, this->icon); |   buffer.encode_string(10, this->icon); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(11, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(11, this->entity_category); | ||||||
|   buffer.encode_bool(12, this->supports_stop); |   buffer.encode_bool(12, this->supports_stop); | ||||||
|  |   buffer.encode_uint32(13, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -1303,6 +1454,7 @@ void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, 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); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); |   ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesCoverResponse::dump_to(std::string &out) const { | void ListEntitiesCoverResponse::dump_to(std::string &out) const { | ||||||
| @@ -1356,6 +1508,11 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  supports_stop: "); |   out.append("  supports_stop: "); | ||||||
|   out.append(YESNO(this->supports_stop)); |   out.append(YESNO(this->supports_stop)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -1565,6 +1722,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 13: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -1620,6 +1781,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   for (auto &it : this->supported_preset_modes) { |   for (auto &it : this->supported_preset_modes) { | ||||||
|     buffer.encode_string(12, it, true); |     buffer.encode_string(12, it, true); | ||||||
|   } |   } | ||||||
|  |   buffer.encode_uint32(13, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -1638,6 +1800,7 @@ void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const { | |||||||
|       ProtoSize::add_string_field(total_size, 1, it, true); |       ProtoSize::add_string_field(total_size, 1, it, true); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesFanResponse::dump_to(std::string &out) const { | void ListEntitiesFanResponse::dump_to(std::string &out) const { | ||||||
| @@ -1694,6 +1857,11 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { | |||||||
|     out.append("'").append(it).append("'"); |     out.append("'").append(it).append("'"); | ||||||
|     out.append("\n"); |     out.append("\n"); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -1987,6 +2155,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 16: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2055,6 +2227,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(13, this->disabled_by_default); |   buffer.encode_bool(13, this->disabled_by_default); | ||||||
|   buffer.encode_string(14, this->icon); |   buffer.encode_string(14, this->icon); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(15, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(15, this->entity_category); | ||||||
|  |   buffer.encode_uint32(16, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -2080,6 +2253,7 @@ void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, 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_string_field(total_size, 1, this->icon, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 2, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesLightResponse::dump_to(std::string &out) const { | void ListEntitiesLightResponse::dump_to(std::string &out) const { | ||||||
| @@ -2151,6 +2325,11 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -2658,6 +2837,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 14: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2716,6 +2899,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type); |   buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type); | ||||||
|   buffer.encode_bool(12, this->disabled_by_default); |   buffer.encode_bool(12, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(13, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(13, this->entity_category); | ||||||
|  |   buffer.encode_uint32(14, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -2731,6 +2915,7 @@ void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->legacy_last_reset_type), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->legacy_last_reset_type), false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, 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); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesSensorResponse::dump_to(std::string &out) const { | void ListEntitiesSensorResponse::dump_to(std::string &out) const { | ||||||
| @@ -2789,6 +2974,11 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -2860,6 +3050,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 10: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2910,6 +3104,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(7, this->disabled_by_default); |   buffer.encode_bool(7, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); | ||||||
|   buffer.encode_string(9, this->device_class); |   buffer.encode_string(9, this->device_class); | ||||||
|  |   buffer.encode_uint32(10, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -2921,6 +3116,7 @@ void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, 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); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); |   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesSwitchResponse::dump_to(std::string &out) const { | void ListEntitiesSwitchResponse::dump_to(std::string &out) const { | ||||||
| @@ -2962,6 +3158,11 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  device_class: "); |   out.append("  device_class: "); | ||||||
|   out.append("'").append(this->device_class).append("'"); |   out.append("'").append(this->device_class).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -3061,6 +3262,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 9: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -3110,6 +3315,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|   buffer.encode_string(8, this->device_class); |   buffer.encode_string(8, this->device_class); | ||||||
|  |   buffer.encode_uint32(9, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -3120,6 +3326,7 @@ void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, 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); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); |   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { | void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { | ||||||
| @@ -3157,6 +3364,11 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  device_class: "); |   out.append("  device_class: "); | ||||||
|   out.append("'").append(this->device_class).append("'"); |   out.append("'").append(this->device_class).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -3922,6 +4134,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -3966,6 +4182,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(5, this->disabled_by_default); |   buffer.encode_bool(5, this->disabled_by_default); | ||||||
|   buffer.encode_string(6, this->icon); |   buffer.encode_string(6, this->icon); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|  |   buffer.encode_uint32(8, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -3975,6 +4192,7 @@ void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, 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_string_field(total_size, 1, this->icon, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesCameraResponse::dump_to(std::string &out) const { | void ListEntitiesCameraResponse::dump_to(std::string &out) const { | ||||||
| @@ -4008,6 +4226,11 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -4156,6 +4379,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v | |||||||
|       this->supports_target_humidity = value.as_bool(); |       this->supports_target_humidity = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 26: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -4262,6 +4489,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(23, this->supports_target_humidity); |   buffer.encode_bool(23, this->supports_target_humidity); | ||||||
|   buffer.encode_float(24, this->visual_min_humidity); |   buffer.encode_float(24, this->visual_min_humidity); | ||||||
|   buffer.encode_float(25, this->visual_max_humidity); |   buffer.encode_float(25, this->visual_max_humidity); | ||||||
|  |   buffer.encode_uint32(26, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -4313,6 +4541,7 @@ void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 2, this->supports_target_humidity, false); |   ProtoSize::add_bool_field(total_size, 2, this->supports_target_humidity, false); | ||||||
|   ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_min_humidity != 0.0f, false); |   ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_min_humidity != 0.0f, false); | ||||||
|   ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_max_humidity != 0.0f, false); |   ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_max_humidity != 0.0f, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 2, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesClimateResponse::dump_to(std::string &out) const { | void ListEntitiesClimateResponse::dump_to(std::string &out) const { | ||||||
| @@ -4436,6 +4665,11 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { | |||||||
|   sprintf(buffer, "%g", this->visual_max_humidity); |   sprintf(buffer, "%g", this->visual_max_humidity); | ||||||
|   out.append(buffer); |   out.append(buffer); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -4901,6 +5135,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->mode = value.as_enum<enums::NumberMode>(); |       this->mode = value.as_enum<enums::NumberMode>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 14: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -4971,6 +5209,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(11, this->unit_of_measurement); |   buffer.encode_string(11, this->unit_of_measurement); | ||||||
|   buffer.encode_enum<enums::NumberMode>(12, this->mode); |   buffer.encode_enum<enums::NumberMode>(12, this->mode); | ||||||
|   buffer.encode_string(13, this->device_class); |   buffer.encode_string(13, this->device_class); | ||||||
|  |   buffer.encode_uint32(14, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -4986,6 +5225,7 @@ void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement, false); |   ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); |   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesNumberResponse::dump_to(std::string &out) const { | void ListEntitiesNumberResponse::dump_to(std::string &out) const { | ||||||
| @@ -5046,6 +5286,11 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  device_class: "); |   out.append("  device_class: "); | ||||||
|   out.append("'").append(this->device_class).append("'"); |   out.append("'").append(this->device_class).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -5151,6 +5396,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 9: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -5202,6 +5451,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   } |   } | ||||||
|   buffer.encode_bool(7, this->disabled_by_default); |   buffer.encode_bool(7, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); | ||||||
|  |   buffer.encode_uint32(9, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -5216,6 +5466,7 @@ void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   } |   } | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, 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); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesSelectResponse::dump_to(std::string &out) const { | void ListEntitiesSelectResponse::dump_to(std::string &out) const { | ||||||
| @@ -5255,6 +5506,11 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -5378,6 +5634,10 @@ bool ListEntitiesSirenResponse::decode_varint(uint32_t field_id, ProtoVarInt val | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 11: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -5431,6 +5691,7 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(8, this->supports_duration); |   buffer.encode_bool(8, this->supports_duration); | ||||||
|   buffer.encode_bool(9, this->supports_volume); |   buffer.encode_bool(9, this->supports_volume); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(10, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(10, this->entity_category); | ||||||
|  |   buffer.encode_uint32(11, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -5447,6 +5708,7 @@ void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_duration, false); |   ProtoSize::add_bool_field(total_size, 1, this->supports_duration, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_volume, false); |   ProtoSize::add_bool_field(total_size, 1, this->supports_volume, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesSirenResponse::dump_to(std::string &out) const { | void ListEntitiesSirenResponse::dump_to(std::string &out) const { | ||||||
| @@ -5494,6 +5756,11 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -5683,6 +5950,10 @@ bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt valu | |||||||
|       this->requires_code = value.as_bool(); |       this->requires_code = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 12: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -5735,6 +6006,7 @@ void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(9, this->supports_open); |   buffer.encode_bool(9, this->supports_open); | ||||||
|   buffer.encode_bool(10, this->requires_code); |   buffer.encode_bool(10, this->requires_code); | ||||||
|   buffer.encode_string(11, this->code_format); |   buffer.encode_string(11, this->code_format); | ||||||
|  |   buffer.encode_uint32(12, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -5748,6 +6020,7 @@ void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_open, false); |   ProtoSize::add_bool_field(total_size, 1, this->supports_open, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->requires_code, false); |   ProtoSize::add_bool_field(total_size, 1, this->requires_code, false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->code_format, false); |   ProtoSize::add_string_field(total_size, 1, this->code_format, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesLockResponse::dump_to(std::string &out) const { | void ListEntitiesLockResponse::dump_to(std::string &out) const { | ||||||
| @@ -5797,6 +6070,11 @@ void ListEntitiesLockResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  code_format: "); |   out.append("  code_format: "); | ||||||
|   out.append("'").append(this->code_format).append("'"); |   out.append("'").append(this->code_format).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -5922,6 +6200,10 @@ bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 9: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -5971,6 +6253,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|   buffer.encode_string(8, this->device_class); |   buffer.encode_string(8, this->device_class); | ||||||
|  |   buffer.encode_uint32(9, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -5981,6 +6264,7 @@ void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, 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); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); |   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesButtonResponse::dump_to(std::string &out) const { | void ListEntitiesButtonResponse::dump_to(std::string &out) const { | ||||||
| @@ -6018,6 +6302,11 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  device_class: "); |   out.append("  device_class: "); | ||||||
|   out.append("'").append(this->device_class).append("'"); |   out.append("'").append(this->device_class).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -6135,6 +6424,10 @@ bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarI | |||||||
|       this->supports_pause = value.as_bool(); |       this->supports_pause = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 10: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -6187,6 +6480,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   for (auto &it : this->supported_formats) { |   for (auto &it : this->supported_formats) { | ||||||
|     buffer.encode_message<MediaPlayerSupportedFormat>(9, it, true); |     buffer.encode_message<MediaPlayerSupportedFormat>(9, it, true); | ||||||
|   } |   } | ||||||
|  |   buffer.encode_uint32(10, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -6198,6 +6492,7 @@ void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const | |||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_pause, false); |   ProtoSize::add_bool_field(total_size, 1, this->supports_pause, false); | ||||||
|   ProtoSize::add_repeated_message(total_size, 1, this->supported_formats); |   ProtoSize::add_repeated_message(total_size, 1, this->supported_formats); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { | void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { | ||||||
| @@ -6241,6 +6536,11 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { | |||||||
|     it.dump_to(out); |     it.dump_to(out); | ||||||
|     out.append("\n"); |     out.append("\n"); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -8551,6 +8851,10 @@ bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, Pro | |||||||
|       this->requires_code_to_arm = value.as_bool(); |       this->requires_code_to_arm = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 11: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -8598,6 +8902,7 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) cons | |||||||
|   buffer.encode_uint32(8, this->supported_features); |   buffer.encode_uint32(8, this->supported_features); | ||||||
|   buffer.encode_bool(9, this->requires_code); |   buffer.encode_bool(9, this->requires_code); | ||||||
|   buffer.encode_bool(10, this->requires_code_to_arm); |   buffer.encode_bool(10, this->requires_code_to_arm); | ||||||
|  |   buffer.encode_uint32(11, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -8610,6 +8915,7 @@ void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) | |||||||
|   ProtoSize::add_uint32_field(total_size, 1, this->supported_features, false); |   ProtoSize::add_uint32_field(total_size, 1, this->supported_features, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->requires_code, false); |   ProtoSize::add_bool_field(total_size, 1, this->requires_code, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->requires_code_to_arm, false); |   ProtoSize::add_bool_field(total_size, 1, this->requires_code_to_arm, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { | void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { | ||||||
| @@ -8656,6 +8962,11 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  requires_code_to_arm: "); |   out.append("  requires_code_to_arm: "); | ||||||
|   out.append(YESNO(this->requires_code_to_arm)); |   out.append(YESNO(this->requires_code_to_arm)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -8783,6 +9094,10 @@ bool ListEntitiesTextResponse::decode_varint(uint32_t field_id, ProtoVarInt valu | |||||||
|       this->mode = value.as_enum<enums::TextMode>(); |       this->mode = value.as_enum<enums::TextMode>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 12: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -8835,6 +9150,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_uint32(9, this->max_length); |   buffer.encode_uint32(9, this->max_length); | ||||||
|   buffer.encode_string(10, this->pattern); |   buffer.encode_string(10, this->pattern); | ||||||
|   buffer.encode_enum<enums::TextMode>(11, this->mode); |   buffer.encode_enum<enums::TextMode>(11, this->mode); | ||||||
|  |   buffer.encode_uint32(12, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -8848,6 +9164,7 @@ void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_uint32_field(total_size, 1, this->max_length, false); |   ProtoSize::add_uint32_field(total_size, 1, this->max_length, false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->pattern, false); |   ProtoSize::add_string_field(total_size, 1, this->pattern, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesTextResponse::dump_to(std::string &out) const { | void ListEntitiesTextResponse::dump_to(std::string &out) const { | ||||||
| @@ -8899,6 +9216,11 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  mode: "); |   out.append("  mode: "); | ||||||
|   out.append(proto_enum_to_string<enums::TextMode>(this->mode)); |   out.append(proto_enum_to_string<enums::TextMode>(this->mode)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -9014,6 +9336,10 @@ bool ListEntitiesDateResponse::decode_varint(uint32_t field_id, ProtoVarInt valu | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -9058,6 +9384,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(5, this->icon); |   buffer.encode_string(5, this->icon); | ||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|  |   buffer.encode_uint32(8, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -9067,6 +9394,7 @@ void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, 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_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); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesDateResponse::dump_to(std::string &out) const { | void ListEntitiesDateResponse::dump_to(std::string &out) const { | ||||||
| @@ -9100,6 +9428,11 @@ void ListEntitiesDateResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -9255,6 +9588,10 @@ bool ListEntitiesTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt valu | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -9299,6 +9636,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(5, this->icon); |   buffer.encode_string(5, this->icon); | ||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|  |   buffer.encode_uint32(8, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -9308,6 +9646,7 @@ void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, 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_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); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesTimeResponse::dump_to(std::string &out) const { | void ListEntitiesTimeResponse::dump_to(std::string &out) const { | ||||||
| @@ -9341,6 +9680,11 @@ void ListEntitiesTimeResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -9496,6 +9840,10 @@ bool ListEntitiesEventResponse::decode_varint(uint32_t field_id, ProtoVarInt val | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 10: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -9552,6 +9900,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   for (auto &it : this->event_types) { |   for (auto &it : this->event_types) { | ||||||
|     buffer.encode_string(9, it, true); |     buffer.encode_string(9, it, true); | ||||||
|   } |   } | ||||||
|  |   buffer.encode_uint32(10, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -9567,6 +9916,7 @@ void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const { | |||||||
|       ProtoSize::add_string_field(total_size, 1, it, true); |       ProtoSize::add_string_field(total_size, 1, it, true); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesEventResponse::dump_to(std::string &out) const { | void ListEntitiesEventResponse::dump_to(std::string &out) const { | ||||||
| @@ -9610,6 +9960,11 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const { | |||||||
|     out.append("'").append(it).append("'"); |     out.append("'").append(it).append("'"); | ||||||
|     out.append("\n"); |     out.append("\n"); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -9678,6 +10033,10 @@ bool ListEntitiesValveResponse::decode_varint(uint32_t field_id, ProtoVarInt val | |||||||
|       this->supports_stop = value.as_bool(); |       this->supports_stop = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 12: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -9730,6 +10089,7 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(9, this->assumed_state); |   buffer.encode_bool(9, this->assumed_state); | ||||||
|   buffer.encode_bool(10, this->supports_position); |   buffer.encode_bool(10, this->supports_position); | ||||||
|   buffer.encode_bool(11, this->supports_stop); |   buffer.encode_bool(11, this->supports_stop); | ||||||
|  |   buffer.encode_uint32(12, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -9743,6 +10103,7 @@ void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->assumed_state, 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_position, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); |   ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesValveResponse::dump_to(std::string &out) const { | void ListEntitiesValveResponse::dump_to(std::string &out) const { | ||||||
| @@ -9792,6 +10153,11 @@ void ListEntitiesValveResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  supports_stop: "); |   out.append("  supports_stop: "); | ||||||
|   out.append(YESNO(this->supports_stop)); |   out.append(YESNO(this->supports_stop)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -9923,6 +10289,10 @@ bool ListEntitiesDateTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -9967,6 +10337,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(5, this->icon); |   buffer.encode_string(5, this->icon); | ||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|  |   buffer.encode_uint32(8, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -9976,6 +10347,7 @@ void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, 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_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); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { | void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { | ||||||
| @@ -10009,6 +10381,11 @@ void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -10114,6 +10491,10 @@ bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 9: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -10163,6 +10544,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|   buffer.encode_string(8, this->device_class); |   buffer.encode_string(8, this->device_class); | ||||||
|  |   buffer.encode_uint32(9, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -10173,6 +10555,7 @@ void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, 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); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); |   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesUpdateResponse::dump_to(std::string &out) const { | void ListEntitiesUpdateResponse::dump_to(std::string &out) const { | ||||||
| @@ -10210,6 +10593,11 @@ void ListEntitiesUpdateResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  device_class: "); |   out.append("  device_class: "); | ||||||
|   out.append("'").append(this->device_class).append("'"); |   out.append("'").append(this->device_class).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -264,6 +264,7 @@ class InfoResponseProtoMessage : public ProtoMessage { | |||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|   std::string icon{}; |   std::string icon{}; | ||||||
|   enums::EntityCategory entity_category{}; |   enums::EntityCategory entity_category{}; | ||||||
|  |   uint32_t device_id{0}; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
| }; | }; | ||||||
| @@ -415,10 +416,39 @@ class DeviceInfoRequest : public ProtoMessage { | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
| }; | }; | ||||||
|  | class AreaInfo : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   uint32_t area_id{0}; | ||||||
|  |   std::string name{}; | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  |   void calculate_size(uint32_t &total_size) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|  |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
|  | }; | ||||||
|  | class DeviceInfo : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   uint32_t device_id{0}; | ||||||
|  |   std::string name{}; | ||||||
|  |   uint32_t area_id{0}; | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  |   void calculate_size(uint32_t &total_size) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|  |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
|  | }; | ||||||
| class DeviceInfoResponse : public ProtoMessage { | class DeviceInfoResponse : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 10; |   static constexpr uint16_t MESSAGE_TYPE = 10; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 129; |   static constexpr uint16_t ESTIMATED_SIZE = 219; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "device_info_response"; } |   static constexpr const char *message_name() { return "device_info_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -441,6 +471,9 @@ class DeviceInfoResponse : public ProtoMessage { | |||||||
|   std::string suggested_area{}; |   std::string suggested_area{}; | ||||||
|   std::string bluetooth_mac_address{}; |   std::string bluetooth_mac_address{}; | ||||||
|   bool api_encryption_supported{false}; |   bool api_encryption_supported{false}; | ||||||
|  |   std::vector<DeviceInfo> devices{}; | ||||||
|  |   std::vector<AreaInfo> areas{}; | ||||||
|  |   AreaInfo area{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| @@ -493,7 +526,7 @@ class SubscribeStatesRequest : public ProtoMessage { | |||||||
| class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage { | class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 12; |   static constexpr uint16_t MESSAGE_TYPE = 12; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 56; |   static constexpr uint16_t ESTIMATED_SIZE = 60; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_binary_sensor_response"; } |   static constexpr const char *message_name() { return "list_entities_binary_sensor_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -532,7 +565,7 @@ class BinarySensorStateResponse : public StateResponseProtoMessage { | |||||||
| class ListEntitiesCoverResponse : public InfoResponseProtoMessage { | class ListEntitiesCoverResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 13; |   static constexpr uint16_t MESSAGE_TYPE = 13; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 62; |   static constexpr uint16_t ESTIMATED_SIZE = 66; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_cover_response"; } |   static constexpr const char *message_name() { return "list_entities_cover_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -601,7 +634,7 @@ class CoverCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesFanResponse : public InfoResponseProtoMessage { | class ListEntitiesFanResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 14; |   static constexpr uint16_t MESSAGE_TYPE = 14; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 73; |   static constexpr uint16_t ESTIMATED_SIZE = 77; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_fan_response"; } |   static constexpr const char *message_name() { return "list_entities_fan_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -679,7 +712,7 @@ class FanCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesLightResponse : public InfoResponseProtoMessage { | class ListEntitiesLightResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 15; |   static constexpr uint16_t MESSAGE_TYPE = 15; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 85; |   static constexpr uint16_t ESTIMATED_SIZE = 90; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_light_response"; } |   static constexpr const char *message_name() { return "list_entities_light_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -780,7 +813,7 @@ class LightCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesSensorResponse : public InfoResponseProtoMessage { | class ListEntitiesSensorResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 16; |   static constexpr uint16_t MESSAGE_TYPE = 16; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 73; |   static constexpr uint16_t ESTIMATED_SIZE = 77; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_sensor_response"; } |   static constexpr const char *message_name() { return "list_entities_sensor_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -823,7 +856,7 @@ class SensorStateResponse : public StateResponseProtoMessage { | |||||||
| class ListEntitiesSwitchResponse : public InfoResponseProtoMessage { | class ListEntitiesSwitchResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 17; |   static constexpr uint16_t MESSAGE_TYPE = 17; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 56; |   static constexpr uint16_t ESTIMATED_SIZE = 60; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_switch_response"; } |   static constexpr const char *message_name() { return "list_entities_switch_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -880,7 +913,7 @@ class SwitchCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage { | class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 18; |   static constexpr uint16_t MESSAGE_TYPE = 18; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 54; |   static constexpr uint16_t ESTIMATED_SIZE = 58; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_text_sensor_response"; } |   static constexpr const char *message_name() { return "list_entities_text_sensor_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -1196,7 +1229,7 @@ class ExecuteServiceRequest : public ProtoMessage { | |||||||
| class ListEntitiesCameraResponse : public InfoResponseProtoMessage { | class ListEntitiesCameraResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 43; |   static constexpr uint16_t MESSAGE_TYPE = 43; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 45; |   static constexpr uint16_t ESTIMATED_SIZE = 49; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_camera_response"; } |   static constexpr const char *message_name() { return "list_entities_camera_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -1253,7 +1286,7 @@ class CameraImageRequest : public ProtoMessage { | |||||||
| class ListEntitiesClimateResponse : public InfoResponseProtoMessage { | class ListEntitiesClimateResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 46; |   static constexpr uint16_t MESSAGE_TYPE = 46; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 151; |   static constexpr uint16_t ESTIMATED_SIZE = 156; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_climate_response"; } |   static constexpr const char *message_name() { return "list_entities_climate_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -1362,7 +1395,7 @@ class ClimateCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesNumberResponse : public InfoResponseProtoMessage { | class ListEntitiesNumberResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 49; |   static constexpr uint16_t MESSAGE_TYPE = 49; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 80; |   static constexpr uint16_t ESTIMATED_SIZE = 84; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_number_response"; } |   static constexpr const char *message_name() { return "list_entities_number_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -1423,7 +1456,7 @@ class NumberCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesSelectResponse : public InfoResponseProtoMessage { | class ListEntitiesSelectResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 52; |   static constexpr uint16_t MESSAGE_TYPE = 52; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 63; |   static constexpr uint16_t ESTIMATED_SIZE = 67; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_select_response"; } |   static constexpr const char *message_name() { return "list_entities_select_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -1481,7 +1514,7 @@ class SelectCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesSirenResponse : public InfoResponseProtoMessage { | class ListEntitiesSirenResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 55; |   static constexpr uint16_t MESSAGE_TYPE = 55; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 67; |   static constexpr uint16_t ESTIMATED_SIZE = 71; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_siren_response"; } |   static constexpr const char *message_name() { return "list_entities_siren_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -1547,7 +1580,7 @@ class SirenCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesLockResponse : public InfoResponseProtoMessage { | class ListEntitiesLockResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 58; |   static constexpr uint16_t MESSAGE_TYPE = 58; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 60; |   static constexpr uint16_t ESTIMATED_SIZE = 64; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_lock_response"; } |   static constexpr const char *message_name() { return "list_entities_lock_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -1609,7 +1642,7 @@ class LockCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesButtonResponse : public InfoResponseProtoMessage { | class ListEntitiesButtonResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 61; |   static constexpr uint16_t MESSAGE_TYPE = 61; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 54; |   static constexpr uint16_t ESTIMATED_SIZE = 58; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_button_response"; } |   static constexpr const char *message_name() { return "list_entities_button_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -1662,7 +1695,7 @@ class MediaPlayerSupportedFormat : public ProtoMessage { | |||||||
| class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage { | class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 63; |   static constexpr uint16_t MESSAGE_TYPE = 63; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 81; |   static constexpr uint16_t ESTIMATED_SIZE = 85; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_media_player_response"; } |   static constexpr const char *message_name() { return "list_entities_media_player_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2532,7 +2565,7 @@ class VoiceAssistantSetConfiguration : public ProtoMessage { | |||||||
| class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage { | class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 94; |   static constexpr uint16_t MESSAGE_TYPE = 94; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 53; |   static constexpr uint16_t ESTIMATED_SIZE = 57; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_alarm_control_panel_response"; } |   static constexpr const char *message_name() { return "list_entities_alarm_control_panel_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2592,7 +2625,7 @@ class AlarmControlPanelCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesTextResponse : public InfoResponseProtoMessage { | class ListEntitiesTextResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 97; |   static constexpr uint16_t MESSAGE_TYPE = 97; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 64; |   static constexpr uint16_t ESTIMATED_SIZE = 68; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_text_response"; } |   static constexpr const char *message_name() { return "list_entities_text_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2653,7 +2686,7 @@ class TextCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesDateResponse : public InfoResponseProtoMessage { | class ListEntitiesDateResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 100; |   static constexpr uint16_t MESSAGE_TYPE = 100; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 45; |   static constexpr uint16_t ESTIMATED_SIZE = 49; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_date_response"; } |   static constexpr const char *message_name() { return "list_entities_date_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2713,7 +2746,7 @@ class DateCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesTimeResponse : public InfoResponseProtoMessage { | class ListEntitiesTimeResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 103; |   static constexpr uint16_t MESSAGE_TYPE = 103; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 45; |   static constexpr uint16_t ESTIMATED_SIZE = 49; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_time_response"; } |   static constexpr const char *message_name() { return "list_entities_time_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2773,7 +2806,7 @@ class TimeCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesEventResponse : public InfoResponseProtoMessage { | class ListEntitiesEventResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 107; |   static constexpr uint16_t MESSAGE_TYPE = 107; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 72; |   static constexpr uint16_t ESTIMATED_SIZE = 76; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_event_response"; } |   static constexpr const char *message_name() { return "list_entities_event_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2811,7 +2844,7 @@ class EventResponse : public StateResponseProtoMessage { | |||||||
| class ListEntitiesValveResponse : public InfoResponseProtoMessage { | class ListEntitiesValveResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 109; |   static constexpr uint16_t MESSAGE_TYPE = 109; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 60; |   static constexpr uint16_t ESTIMATED_SIZE = 64; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_valve_response"; } |   static constexpr const char *message_name() { return "list_entities_valve_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2873,7 +2906,7 @@ class ValveCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage { | class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 112; |   static constexpr uint16_t MESSAGE_TYPE = 112; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 45; |   static constexpr uint16_t ESTIMATED_SIZE = 49; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_date_time_response"; } |   static constexpr const char *message_name() { return "list_entities_date_time_response"; } | ||||||
| #endif | #endif | ||||||
| @@ -2928,7 +2961,7 @@ class DateTimeCommandRequest : public ProtoMessage { | |||||||
| class ListEntitiesUpdateResponse : public InfoResponseProtoMessage { | class ListEntitiesUpdateResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 116; |   static constexpr uint16_t MESSAGE_TYPE = 116; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 54; |   static constexpr uint16_t ESTIMATED_SIZE = 58; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_update_response"; } |   static constexpr const char *message_name() { return "list_entities_update_response"; } | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -60,8 +60,8 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_WINDOW, |     DEVICE_CLASS_WINDOW, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
| from esphome.util import Registry | from esphome.util import Registry | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| @@ -148,6 +148,7 @@ BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Conditi | |||||||
|  |  | ||||||
| # Filters | # Filters | ||||||
| Filter = binary_sensor_ns.class_("Filter") | Filter = binary_sensor_ns.class_("Filter") | ||||||
|  | TimeoutFilter = binary_sensor_ns.class_("TimeoutFilter", Filter, cg.Component) | ||||||
| DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component) | DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component) | ||||||
| DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component) | DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component) | ||||||
| DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component) | DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component) | ||||||
| @@ -171,6 +172,19 @@ async def invert_filter_to_code(config, filter_id): | |||||||
|     return cg.new_Pvariable(filter_id) |     return cg.new_Pvariable(filter_id) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @register_filter( | ||||||
|  |     "timeout", | ||||||
|  |     TimeoutFilter, | ||||||
|  |     cv.templatable(cv.positive_time_period_milliseconds), | ||||||
|  | ) | ||||||
|  | async def timeout_filter_to_code(config, filter_id): | ||||||
|  |     var = cg.new_Pvariable(filter_id) | ||||||
|  |     await cg.register_component(var, {}) | ||||||
|  |     template_ = await cg.templatable(config, [], cg.uint32) | ||||||
|  |     cg.add(var.set_timeout_value(template_)) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_filter( | @register_filter( | ||||||
|     "delayed_on_off", |     "delayed_on_off", | ||||||
|     DelayedOnOffFilter, |     DelayedOnOffFilter, | ||||||
| @@ -491,6 +505,9 @@ _BINARY_SENSOR_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _BINARY_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("binary_sensor")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def binary_sensor_schema( | def binary_sensor_schema( | ||||||
|     class_: MockObjClass = cv.UNDEFINED, |     class_: MockObjClass = cv.UNDEFINED, | ||||||
|     *, |     *, | ||||||
| @@ -521,7 +538,7 @@ BINARY_SENSOR_SCHEMA.add_extra(cv.deprecated_schema_constant("binary_sensor")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_binary_sensor_core_(var, config): | async def setup_binary_sensor_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "binary_sensor") | ||||||
|  |  | ||||||
|     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: |     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: | ||||||
|         cg.add(var.set_device_class(device_class)) |         cg.add(var.set_device_class(device_class)) | ||||||
|   | |||||||
| @@ -25,6 +25,12 @@ void Filter::input(bool value) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void TimeoutFilter::input(bool value) { | ||||||
|  |   this->set_timeout("timeout", this->timeout_delay_.value(), [this]() { this->parent_->invalidate_state(); }); | ||||||
|  |   // we do not de-dup here otherwise changes from invalid to valid state will not be output | ||||||
|  |   this->output(value); | ||||||
|  | } | ||||||
|  |  | ||||||
| optional<bool> DelayedOnOffFilter::new_value(bool value) { | optional<bool> DelayedOnOffFilter::new_value(bool value) { | ||||||
|   if (value) { |   if (value) { | ||||||
|     this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); }); |     this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); }); | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ class Filter { | |||||||
|  public: |  public: | ||||||
|   virtual optional<bool> new_value(bool value) = 0; |   virtual optional<bool> new_value(bool value) = 0; | ||||||
|  |  | ||||||
|   void input(bool value); |   virtual void input(bool value); | ||||||
|  |  | ||||||
|   void output(bool value); |   void output(bool value); | ||||||
|  |  | ||||||
| @@ -28,6 +28,16 @@ class Filter { | |||||||
|   Deduplicator<bool> dedup_; |   Deduplicator<bool> dedup_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class TimeoutFilter : public Filter, public Component { | ||||||
|  |  public: | ||||||
|  |   optional<bool> new_value(bool value) override { return value; } | ||||||
|  |   void input(bool value) override; | ||||||
|  |   template<typename T> void set_timeout_value(T timeout) { this->timeout_delay_ = timeout; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   TemplatableValue<uint32_t> timeout_delay_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
| class DelayedOnOffFilter : public Filter, public Component { | class DelayedOnOffFilter : public Filter, public Component { | ||||||
|  public: |  public: | ||||||
|   optional<bool> new_value(bool value) override; |   optional<bool> new_value(bool value) override; | ||||||
|   | |||||||
| @@ -18,8 +18,8 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_UPDATE, |     DEVICE_CLASS_UPDATE, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
| @@ -61,6 +61,9 @@ _BUTTON_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _BUTTON_SCHEMA.add_extra(entity_duplicate_validator("button")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def button_schema( | def button_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
|     *, |     *, | ||||||
| @@ -87,7 +90,7 @@ BUTTON_SCHEMA.add_extra(cv.deprecated_schema_constant("button")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_button_core_(var, config): | async def setup_button_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "button") | ||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_PRESS, []): |     for conf in config.get(CONF_ON_PRESS, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|   | |||||||
| @@ -48,8 +48,8 @@ from esphome.const import ( | |||||||
|     CONF_WEB_SERVER, |     CONF_WEB_SERVER, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
| @@ -247,6 +247,9 @@ _CLIMATE_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _CLIMATE_SCHEMA.add_extra(entity_duplicate_validator("climate")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def climate_schema( | def climate_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
|     *, |     *, | ||||||
| @@ -273,7 +276,7 @@ CLIMATE_SCHEMA.add_extra(cv.deprecated_schema_constant("climate")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_climate_core_(var, config): | async def setup_climate_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "climate") | ||||||
|  |  | ||||||
|     visual = config[CONF_VISUAL] |     visual = config[CONF_VISUAL] | ||||||
|     if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None: |     if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None: | ||||||
|   | |||||||
| @@ -33,8 +33,8 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_WINDOW, |     DEVICE_CLASS_WINDOW, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
| @@ -126,6 +126,9 @@ _COVER_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _COVER_SCHEMA.add_extra(entity_duplicate_validator("cover")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def cover_schema( | def cover_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
|     *, |     *, | ||||||
| @@ -154,7 +157,7 @@ COVER_SCHEMA.add_extra(cv.deprecated_schema_constant("cover")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_cover_core_(var, config): | async def setup_cover_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "cover") | ||||||
|  |  | ||||||
|     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: |     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: | ||||||
|         cg.add(var.set_device_class(device_class)) |         cg.add(var.set_device_class(device_class)) | ||||||
|   | |||||||
| @@ -22,8 +22,8 @@ from esphome.const import ( | |||||||
|     CONF_YEAR, |     CONF_YEAR, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@rfdarter", "@jesserockz"] | CODEOWNERS = ["@rfdarter", "@jesserockz"] | ||||||
|  |  | ||||||
| @@ -84,6 +84,8 @@ _DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( | |||||||
|     .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) |     .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) | ||||||
| ).add_extra(_validate_time_present) | ).add_extra(_validate_time_present) | ||||||
|  |  | ||||||
|  | _DATETIME_SCHEMA.add_extra(entity_duplicate_validator("datetime")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def date_schema(class_: MockObjClass) -> cv.Schema: | def date_schema(class_: MockObjClass) -> cv.Schema: | ||||||
|     schema = cv.Schema( |     schema = cv.Schema( | ||||||
| @@ -133,7 +135,7 @@ def datetime_schema(class_: MockObjClass) -> cv.Schema: | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_datetime_core_(var, config): | async def setup_datetime_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "datetime") | ||||||
|  |  | ||||||
|     if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: |     if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: | ||||||
|         mqtt_ = cg.new_Pvariable(mqtt_id, var) |         mqtt_ = cg.new_Pvariable(mqtt_id, var) | ||||||
|   | |||||||
| @@ -455,7 +455,7 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|                     CONF_NAME: "Demo Plain Sensor", |                     CONF_NAME: "Demo Plain Sensor", | ||||||
|                 }, |                 }, | ||||||
|                 { |                 { | ||||||
|                     CONF_NAME: "Demo Temperature Sensor", |                     CONF_NAME: "Demo Temperature Sensor 1", | ||||||
|                     CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, |                     CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, | ||||||
|                     CONF_ICON: ICON_THERMOMETER, |                     CONF_ICON: ICON_THERMOMETER, | ||||||
|                     CONF_ACCURACY_DECIMALS: 1, |                     CONF_ACCURACY_DECIMALS: 1, | ||||||
| @@ -463,7 +463,7 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|                     CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, |                     CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, | ||||||
|                 }, |                 }, | ||||||
|                 { |                 { | ||||||
|                     CONF_NAME: "Demo Temperature Sensor", |                     CONF_NAME: "Demo Temperature Sensor 2", | ||||||
|                     CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, |                     CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, | ||||||
|                     CONF_ICON: ICON_THERMOMETER, |                     CONF_ICON: ICON_THERMOMETER, | ||||||
|                     CONF_ACCURACY_DECIMALS: 1, |                     CONF_ACCURACY_DECIMALS: 1, | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
| #include "ble_event_pool.h" | #include "ble_event_pool.h" | ||||||
|  |  | ||||||
| #include "esphome/core/application.h" | #include "esphome/core/application.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| #include <esp_bt.h> | #include <esp_bt.h> | ||||||
| @@ -516,13 +517,12 @@ void ESP32BLE::dump_config() { | |||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|     ESP_LOGCONFIG(TAG, |     ESP_LOGCONFIG(TAG, | ||||||
|                   "ESP32 BLE:\n" |                   "BLE:\n" | ||||||
|                   "  MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n" |                   "  MAC address: %s\n" | ||||||
|                   "  IO Capability: %s", |                   "  IO Capability: %s", | ||||||
|                   mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5], |                   format_mac_address_pretty(mac_address).c_str(), io_capability_s); | ||||||
|                   io_capability_s); |  | ||||||
|   } else { |   } else { | ||||||
|     ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled"); |     ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled"); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ from esphome.const import ( | |||||||
|     CONF_VSYNC_PIN, |     CONF_VSYNC_PIN, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE | from esphome.core import CORE | ||||||
| from esphome.cpp_helpers import setup_entity | from esphome.core.entity_helpers import setup_entity | ||||||
|  |  | ||||||
| DEPENDENCIES = ["esp32"] | DEPENDENCIES = ["esp32"] | ||||||
|  |  | ||||||
| @@ -284,7 +284,7 @@ SETTERS = { | |||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "camera") | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|     for key, setter in SETTERS.items(): |     for key, setter in SETTERS.items(): | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								esphome/components/esp32_hall/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/esp32_hall/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										5
									
								
								esphome/components/esp32_hall/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								esphome/components/esp32_hall/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | import esphome.config_validation as cv | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.invalid( | ||||||
|  |     "The esp32_hall component has been removed as of ESPHome 2025.7.0. See https://github.com/esphome/esphome/pull/9117 for details." | ||||||
|  | ) | ||||||
| @@ -18,8 +18,8 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_MOTION, |     DEVICE_CLASS_MOTION, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@nohat"] | CODEOWNERS = ["@nohat"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
| @@ -59,6 +59,9 @@ _EVENT_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _EVENT_SCHEMA.add_extra(entity_duplicate_validator("event")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def event_schema( | def event_schema( | ||||||
|     class_: MockObjClass = cv.UNDEFINED, |     class_: MockObjClass = cv.UNDEFINED, | ||||||
|     *, |     *, | ||||||
| @@ -88,7 +91,7 @@ EVENT_SCHEMA.add_extra(cv.deprecated_schema_constant("event")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_event_core_(var, config, *, event_types: list[str]): | async def setup_event_core_(var, config, *, event_types: list[str]): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "event") | ||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_EVENT, []): |     for conf in config.get(CONF_ON_EVENT, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ from esphome.const import ( | |||||||
|     CONF_WEB_SERVER, |     CONF_WEB_SERVER, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
| from esphome.cpp_helpers import setup_entity | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
|  |  | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
| @@ -161,6 +161,9 @@ _FAN_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _FAN_SCHEMA.add_extra(entity_duplicate_validator("fan")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def fan_schema( | def fan_schema( | ||||||
|     class_: cg.Pvariable, |     class_: cg.Pvariable, | ||||||
|     *, |     *, | ||||||
| @@ -225,7 +228,7 @@ def validate_preset_modes(value): | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_fan_core_(var, config): | async def setup_fan_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "fan") | ||||||
|  |  | ||||||
|     cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) |     cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #include "esphome/core/application.h" | ||||||
|  |  | ||||||
| #define highbyte(val) (uint8_t)((val) >> 8) | #define highbyte(val) (uint8_t)((val) >> 8) | ||||||
| #define lowbyte(val) (uint8_t)((val) &0xff) | #define lowbyte(val) (uint8_t)((val) &0xff) | ||||||
|  |  | ||||||
| @@ -73,9 +75,9 @@ void LD2410Component::dump_config() { | |||||||
| #endif | #endif | ||||||
|   this->read_all_info(); |   this->read_all_info(); | ||||||
|   ESP_LOGCONFIG(TAG, |   ESP_LOGCONFIG(TAG, | ||||||
|                 "  Throttle_ : %ums\n" |                 "  Throttle: %ums\n" | ||||||
|                 "  MAC Address : %s\n" |                 "  MAC address: %s\n" | ||||||
|                 "  Firmware Version : %s", |                 "  Firmware version: %s", | ||||||
|                 this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str())); |                 this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str())); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -153,7 +155,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { | |||||||
|   /* |   /* | ||||||
|     Reduce data update rate to prevent home assistant database size grow fast |     Reduce data update rate to prevent home assistant database size grow fast | ||||||
|   */ |   */ | ||||||
|   int32_t current_millis = millis(); |   int32_t current_millis = App.get_loop_component_start_time(); | ||||||
|   if (current_millis - last_periodic_millis_ < this->throttle_) |   if (current_millis - last_periodic_millis_ < this->throttle_) | ||||||
|     return; |     return; | ||||||
|   last_periodic_millis_ = current_millis; |   last_periodic_millis_ = current_millis; | ||||||
| @@ -299,21 +301,6 @@ const char MAC_FMT[] = "%02X:%02X:%02X:%02X:%02X:%02X"; | |||||||
| const std::string UNKNOWN_MAC("unknown"); | const std::string UNKNOWN_MAC("unknown"); | ||||||
| const std::string NO_MAC("08:05:04:03:02:01"); | const std::string NO_MAC("08:05:04:03:02:01"); | ||||||
|  |  | ||||||
| std::string format_mac(uint8_t *buffer) { |  | ||||||
|   std::string::size_type mac_size = 256; |  | ||||||
|   std::string mac; |  | ||||||
|   do { |  | ||||||
|     mac.resize(mac_size + 1); |  | ||||||
|     mac_size = std::snprintf(&mac[0], mac.size(), MAC_FMT, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], |  | ||||||
|                              buffer[15]); |  | ||||||
|   } while (mac_size + 1 > mac.size()); |  | ||||||
|   mac.resize(mac_size); |  | ||||||
|   if (mac == NO_MAC) { |  | ||||||
|     return UNKNOWN_MAC; |  | ||||||
|   } |  | ||||||
|   return mac; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
| std::function<void(void)> set_number_value(number::Number *n, float value) { | std::function<void(void)> set_number_value(number::Number *n, float value) { | ||||||
|   float normalized_value = value * 1.0; |   float normalized_value = value * 1.0; | ||||||
| @@ -328,40 +315,40 @@ std::function<void(void)> set_number_value(number::Number *n, float value) { | |||||||
| bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { | bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { | ||||||
|   ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", buffer[COMMAND]); |   ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", buffer[COMMAND]); | ||||||
|   if (len < 10) { |   if (len < 10) { | ||||||
|     ESP_LOGE(TAG, "Error with last command : incorrect length"); |     ESP_LOGE(TAG, "Invalid length"); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) {  // check 4 frame start bytes |   if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) {  // check 4 frame start bytes | ||||||
|     ESP_LOGE(TAG, "Error with last command : incorrect Header"); |     ESP_LOGE(TAG, "Invalid header"); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (buffer[COMMAND_STATUS] != 0x01) { |   if (buffer[COMMAND_STATUS] != 0x01) { | ||||||
|     ESP_LOGE(TAG, "Error with last command : status != 0x01"); |     ESP_LOGE(TAG, "Invalid status"); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (this->two_byte_to_int_(buffer[8], buffer[9]) != 0x00) { |   if (this->two_byte_to_int_(buffer[8], buffer[9]) != 0x00) { | ||||||
|     ESP_LOGE(TAG, "Error with last command , last buffer was: %u , %u", buffer[8], buffer[9]); |     ESP_LOGE(TAG, "Invalid command: %u, %u", buffer[8], buffer[9]); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   switch (buffer[COMMAND]) { |   switch (buffer[COMMAND]) { | ||||||
|     case lowbyte(CMD_ENABLE_CONF): |     case lowbyte(CMD_ENABLE_CONF): | ||||||
|       ESP_LOGV(TAG, "Handled Enable conf command"); |       ESP_LOGV(TAG, "Enable conf"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_DISABLE_CONF): |     case lowbyte(CMD_DISABLE_CONF): | ||||||
|       ESP_LOGV(TAG, "Handled Disabled conf command"); |       ESP_LOGV(TAG, "Disabled conf"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_SET_BAUD_RATE): |     case lowbyte(CMD_SET_BAUD_RATE): | ||||||
|       ESP_LOGV(TAG, "Handled baud rate change command"); |       ESP_LOGV(TAG, "Baud rate change"); | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|       if (this->baud_rate_select_ != nullptr) { |       if (this->baud_rate_select_ != nullptr) { | ||||||
|         ESP_LOGE(TAG, "Change baud rate component config to %s and reinstall", this->baud_rate_select_->state.c_str()); |         ESP_LOGE(TAG, "Configure baud rate to %s and reinstall", this->baud_rate_select_->state.c_str()); | ||||||
|       } |       } | ||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_VERSION): |     case lowbyte(CMD_VERSION): | ||||||
|       this->version_ = format_version(buffer); |       this->version_ = format_version(buffer); | ||||||
|       ESP_LOGV(TAG, "FW Version is: %s", const_cast<char *>(this->version_.c_str())); |       ESP_LOGV(TAG, "Firmware version: %s", const_cast<char *>(this->version_.c_str())); | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|       if (this->version_text_sensor_ != nullptr) { |       if (this->version_text_sensor_ != nullptr) { | ||||||
|         this->version_text_sensor_->publish_state(this->version_); |         this->version_text_sensor_->publish_state(this->version_); | ||||||
| @@ -371,7 +358,7 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
|     case lowbyte(CMD_QUERY_DISTANCE_RESOLUTION): { |     case lowbyte(CMD_QUERY_DISTANCE_RESOLUTION): { | ||||||
|       std::string distance_resolution = |       std::string distance_resolution = | ||||||
|           DISTANCE_RESOLUTION_INT_TO_ENUM.at(this->two_byte_to_int_(buffer[10], buffer[11])); |           DISTANCE_RESOLUTION_INT_TO_ENUM.at(this->two_byte_to_int_(buffer[10], buffer[11])); | ||||||
|       ESP_LOGV(TAG, "Distance resolution is: %s", const_cast<char *>(distance_resolution.c_str())); |       ESP_LOGV(TAG, "Distance resolution: %s", const_cast<char *>(distance_resolution.c_str())); | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|       if (this->distance_resolution_select_ != nullptr && |       if (this->distance_resolution_select_ != nullptr && | ||||||
|           this->distance_resolution_select_->state != distance_resolution) { |           this->distance_resolution_select_->state != distance_resolution) { | ||||||
| @@ -383,9 +370,9 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
|       this->light_function_ = LIGHT_FUNCTION_INT_TO_ENUM.at(buffer[10]); |       this->light_function_ = LIGHT_FUNCTION_INT_TO_ENUM.at(buffer[10]); | ||||||
|       this->light_threshold_ = buffer[11] * 1.0; |       this->light_threshold_ = buffer[11] * 1.0; | ||||||
|       this->out_pin_level_ = OUT_PIN_LEVEL_INT_TO_ENUM.at(buffer[12]); |       this->out_pin_level_ = OUT_PIN_LEVEL_INT_TO_ENUM.at(buffer[12]); | ||||||
|       ESP_LOGV(TAG, "Light function is: %s", const_cast<char *>(this->light_function_.c_str())); |       ESP_LOGV(TAG, "Light function: %s", const_cast<char *>(this->light_function_.c_str())); | ||||||
|       ESP_LOGV(TAG, "Light threshold is: %f", this->light_threshold_); |       ESP_LOGV(TAG, "Light threshold: %f", this->light_threshold_); | ||||||
|       ESP_LOGV(TAG, "Out pin level is: %s", const_cast<char *>(this->out_pin_level_.c_str())); |       ESP_LOGV(TAG, "Out pin level: %s", const_cast<char *>(this->out_pin_level_.c_str())); | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|       if (this->light_function_select_ != nullptr && this->light_function_select_->state != this->light_function_) { |       if (this->light_function_select_ != nullptr && this->light_function_select_->state != this->light_function_) { | ||||||
|         this->light_function_select_->publish_state(this->light_function_); |         this->light_function_select_->publish_state(this->light_function_); | ||||||
| @@ -406,11 +393,11 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
|       if (len < 20) { |       if (len < 20) { | ||||||
|         return false; |         return false; | ||||||
|       } |       } | ||||||
|       this->mac_ = format_mac(buffer); |       this->mac_ = format_mac_address_pretty(&buffer[10]); | ||||||
|       ESP_LOGV(TAG, "MAC Address is: %s", const_cast<char *>(this->mac_.c_str())); |       ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str()); | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|       if (this->mac_text_sensor_ != nullptr) { |       if (this->mac_text_sensor_ != nullptr) { | ||||||
|         this->mac_text_sensor_->publish_state(this->mac_); |         this->mac_text_sensor_->publish_state(this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_); | ||||||
|       } |       } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
| @@ -420,19 +407,19 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_GATE_SENS): |     case lowbyte(CMD_GATE_SENS): | ||||||
|       ESP_LOGV(TAG, "Handled sensitivity command"); |       ESP_LOGV(TAG, "Sensitivity"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_BLUETOOTH): |     case lowbyte(CMD_BLUETOOTH): | ||||||
|       ESP_LOGV(TAG, "Handled bluetooth command"); |       ESP_LOGV(TAG, "Bluetooth"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_SET_DISTANCE_RESOLUTION): |     case lowbyte(CMD_SET_DISTANCE_RESOLUTION): | ||||||
|       ESP_LOGV(TAG, "Handled set distance resolution command"); |       ESP_LOGV(TAG, "Set distance resolution"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_SET_LIGHT_CONTROL): |     case lowbyte(CMD_SET_LIGHT_CONTROL): | ||||||
|       ESP_LOGV(TAG, "Handled set light control command"); |       ESP_LOGV(TAG, "Set light control"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_BT_PASSWORD): |     case lowbyte(CMD_BT_PASSWORD): | ||||||
|       ESP_LOGV(TAG, "Handled set bluetooth password command"); |       ESP_LOGV(TAG, "Set bluetooth password"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_QUERY):  // Query parameters response |     case lowbyte(CMD_QUERY):  // Query parameters response | ||||||
|     { |     { | ||||||
| @@ -532,7 +519,7 @@ void LD2410Component::set_baud_rate(const std::string &state) { | |||||||
|  |  | ||||||
| void LD2410Component::set_bluetooth_password(const std::string &password) { | void LD2410Component::set_bluetooth_password(const std::string &password) { | ||||||
|   if (password.length() != 6) { |   if (password.length() != 6) { | ||||||
|     ESP_LOGE(TAG, "set_bluetooth_password(): invalid password length, must be exactly 6 chars '%s'", password.c_str()); |     ESP_LOGE(TAG, "Password must be exactly 6 chars"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   this->set_config_mode_(true); |   this->set_config_mode_(true); | ||||||
| @@ -544,7 +531,7 @@ void LD2410Component::set_bluetooth_password(const std::string &password) { | |||||||
|  |  | ||||||
| void LD2410Component::set_engineering_mode(bool enable) { | void LD2410Component::set_engineering_mode(bool enable) { | ||||||
|   this->set_config_mode_(true); |   this->set_config_mode_(true); | ||||||
|   last_engineering_mode_change_millis_ = millis(); |   last_engineering_mode_change_millis_ = App.get_loop_component_start_time(); | ||||||
|   uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG; |   uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG; | ||||||
|   this->send_command_(cmd, nullptr, 0); |   this->send_command_(cmd, nullptr, 0); | ||||||
|   this->set_config_mode_(false); |   this->set_config_mode_(false); | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| #include "ld2420.h" | #include "ld2420.h" | ||||||
|  | #include "esphome/core/application.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -40,7 +41,7 @@ There are three documented parameters for modes: | |||||||
|   00 04 = Energy output mode |   00 04 = Energy output mode | ||||||
|     This mode outputs detailed signal energy values for each gate and the target distance. |     This mode outputs detailed signal energy values for each gate and the target distance. | ||||||
|     The data format consist of the following. |     The data format consist of the following. | ||||||
|     Header HH, Length LL, Persence PP, Distance DD, 16 Gate Energies EE, Footer FF |     Header HH, Length LL, Presence PP, Distance DD, 16 Gate Energies EE, Footer FF | ||||||
|     HH HH HH HH LL LL PP DD DD EE EE .. 16x   .. FF FF FF FF |     HH HH HH HH LL LL PP DD DD EE EE .. 16x   .. FF FF FF FF | ||||||
|     F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5 |     F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5 | ||||||
|   00 00 = debug output mode |   00 00 = debug output mode | ||||||
| @@ -67,10 +68,10 @@ float LD2420Component::get_setup_priority() const { return setup_priority::BUS; | |||||||
| void LD2420Component::dump_config() { | void LD2420Component::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, |   ESP_LOGCONFIG(TAG, | ||||||
|                 "LD2420:\n" |                 "LD2420:\n" | ||||||
|                 "  Firmware Version : %7s\n" |                 "  Firmware version: %7s", | ||||||
|                 "LD2420 Number:", |  | ||||||
|                 this->ld2420_firmware_ver_); |                 this->ld2420_firmware_ver_); | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
|  |   ESP_LOGCONFIG(TAG, "Number:"); | ||||||
|   LOG_NUMBER(TAG, "  Gate Timeout:", this->gate_timeout_number_); |   LOG_NUMBER(TAG, "  Gate Timeout:", this->gate_timeout_number_); | ||||||
|   LOG_NUMBER(TAG, "  Gate Max Distance:", this->max_gate_distance_number_); |   LOG_NUMBER(TAG, "  Gate Max Distance:", this->max_gate_distance_number_); | ||||||
|   LOG_NUMBER(TAG, "  Gate Min Distance:", this->min_gate_distance_number_); |   LOG_NUMBER(TAG, "  Gate Min Distance:", this->min_gate_distance_number_); | ||||||
| @@ -86,10 +87,10 @@ void LD2420Component::dump_config() { | |||||||
|   LOG_BUTTON(TAG, "  Factory Reset:", this->factory_reset_button_); |   LOG_BUTTON(TAG, "  Factory Reset:", this->factory_reset_button_); | ||||||
|   LOG_BUTTON(TAG, "  Restart Module:", this->restart_module_button_); |   LOG_BUTTON(TAG, "  Restart Module:", this->restart_module_button_); | ||||||
| #endif | #endif | ||||||
|   ESP_LOGCONFIG(TAG, "LD2420 Select:"); |   ESP_LOGCONFIG(TAG, "Select:"); | ||||||
|   LOG_SELECT(TAG, "  Operating Mode", this->operating_selector_); |   LOG_SELECT(TAG, "  Operating Mode", this->operating_selector_); | ||||||
|   if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { |   if (LD2420Component::get_firmware_int(this->ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { | ||||||
|     ESP_LOGW(TAG, "LD2420 Firmware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_); |     ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->ld2420_firmware_ver_); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -102,7 +103,7 @@ uint8_t LD2420Component::calc_checksum(void *data, size_t size) { | |||||||
|   return checksum; |   return checksum; | ||||||
| } | } | ||||||
|  |  | ||||||
| int LD2420Component::get_firmware_int_(const char *version_string) { | int LD2420Component::get_firmware_int(const char *version_string) { | ||||||
|   std::string version_str = version_string; |   std::string version_str = version_string; | ||||||
|   if (version_str[0] == 'v') { |   if (version_str[0] == 'v') { | ||||||
|     version_str = version_str.substr(1); |     version_str = version_str.substr(1); | ||||||
| @@ -115,7 +116,7 @@ int LD2420Component::get_firmware_int_(const char *version_string) { | |||||||
| void LD2420Component::setup() { | void LD2420Component::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Running setup"); |   ESP_LOGCONFIG(TAG, "Running setup"); | ||||||
|   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { |   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { | ||||||
|     ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); |     ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -127,7 +128,7 @@ void LD2420Component::setup() { | |||||||
|   const char *pfw = this->ld2420_firmware_ver_; |   const char *pfw = this->ld2420_firmware_ver_; | ||||||
|   std::string fw_str(pfw); |   std::string fw_str(pfw); | ||||||
|  |  | ||||||
|   for (auto &listener : listeners_) { |   for (auto &listener : this->listeners_) { | ||||||
|     listener->on_fw_version(fw_str); |     listener->on_fw_version(fw_str); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -137,11 +138,11 @@ void LD2420Component::setup() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); |   memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); | ||||||
|   if (get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { |   if (LD2420Component::get_firmware_int(this->ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { | ||||||
|     this->set_operating_mode(OP_SIMPLE_MODE_STRING); |     this->set_operating_mode(OP_SIMPLE_MODE_STRING); | ||||||
|     this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); |     this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); | ||||||
|     this->set_mode_(CMD_SYSTEM_MODE_SIMPLE); |     this->set_mode_(CMD_SYSTEM_MODE_SIMPLE); | ||||||
|     ESP_LOGW(TAG, "LD2420 Frimware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_); |     ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->ld2420_firmware_ver_); | ||||||
|   } else { |   } else { | ||||||
|     this->set_mode_(CMD_SYSTEM_MODE_ENERGY); |     this->set_mode_(CMD_SYSTEM_MODE_ENERGY); | ||||||
|     this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING); |     this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING); | ||||||
| @@ -151,18 +152,17 @@ void LD2420Component::setup() { | |||||||
| #endif | #endif | ||||||
|   this->set_system_mode(this->system_mode_); |   this->set_system_mode(this->system_mode_); | ||||||
|   this->set_config_mode(false); |   this->set_config_mode(false); | ||||||
|   ESP_LOGCONFIG(TAG, "LD2420 setup complete."); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2420Component::apply_config_action() { | void LD2420Component::apply_config_action() { | ||||||
|   const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config)); |   const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config)); | ||||||
|   if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) { |   if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) { | ||||||
|     ESP_LOGCONFIG(TAG, "No configuration change detected"); |     ESP_LOGD(TAG, "No configuration change detected"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   ESP_LOGCONFIG(TAG, "Reconfiguring LD2420"); |   ESP_LOGD(TAG, "Reconfiguring"); | ||||||
|   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { |   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { | ||||||
|     ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); |     ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -178,13 +178,12 @@ void LD2420Component::apply_config_action() { | |||||||
|   this->set_system_mode(this->system_mode_); |   this->set_system_mode(this->system_mode_); | ||||||
|   this->set_config_mode(false);  // Disable config mode to save new values in LD2420 nvm |   this->set_config_mode(false);  // Disable config mode to save new values in LD2420 nvm | ||||||
|   this->set_operating_mode(OP_NORMAL_MODE_STRING); |   this->set_operating_mode(OP_NORMAL_MODE_STRING); | ||||||
|   ESP_LOGCONFIG(TAG, "LD2420 reconfig complete."); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2420Component::factory_reset_action() { | void LD2420Component::factory_reset_action() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting factory defaults"); |   ESP_LOGD(TAG, "Setting factory defaults"); | ||||||
|   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { |   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { | ||||||
|     ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); |     ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -207,18 +206,16 @@ void LD2420Component::factory_reset_action() { | |||||||
|   this->init_gate_config_numbers(); |   this->init_gate_config_numbers(); | ||||||
|   this->refresh_gate_config_numbers(); |   this->refresh_gate_config_numbers(); | ||||||
| #endif | #endif | ||||||
|   ESP_LOGCONFIG(TAG, "LD2420 factory reset complete."); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2420Component::restart_module_action() { | void LD2420Component::restart_module_action() { | ||||||
|   ESP_LOGCONFIG(TAG, "Restarting LD2420 module"); |   ESP_LOGD(TAG, "Restarting"); | ||||||
|   this->send_module_restart(); |   this->send_module_restart(); | ||||||
|   this->set_timeout(250, [this]() { |   this->set_timeout(250, [this]() { | ||||||
|     this->set_config_mode(true); |     this->set_config_mode(true); | ||||||
|     this->set_system_mode(system_mode_); |     this->set_system_mode(this->system_mode_); | ||||||
|     this->set_config_mode(false); |     this->set_config_mode(false); | ||||||
|   }); |   }); | ||||||
|   ESP_LOGCONFIG(TAG, "LD2420 Restarted."); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2420Component::revert_config_action() { | void LD2420Component::revert_config_action() { | ||||||
| @@ -226,18 +223,18 @@ void LD2420Component::revert_config_action() { | |||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
|   this->init_gate_config_numbers(); |   this->init_gate_config_numbers(); | ||||||
| #endif | #endif | ||||||
|   ESP_LOGCONFIG(TAG, "Reverted config number edits."); |   ESP_LOGD(TAG, "Reverted config number edits"); | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2420Component::loop() { | void LD2420Component::loop() { | ||||||
|   // If there is a active send command do not process it here, the send command call will handle it. |   // If there is a active send command do not process it here, the send command call will handle it. | ||||||
|   if (!get_cmd_active_()) { |   if (!this->get_cmd_active_()) { | ||||||
|     if (!available()) |     if (!this->available()) | ||||||
|       return; |       return; | ||||||
|     static uint8_t buffer[2048]; |     static uint8_t buffer[2048]; | ||||||
|     static uint8_t rx_data; |     static uint8_t rx_data; | ||||||
|     while (available()) { |     while (this->available()) { | ||||||
|       rx_data = read(); |       rx_data = this->read(); | ||||||
|       this->readline_(rx_data, buffer, sizeof(buffer)); |       this->readline_(rx_data, buffer, sizeof(buffer)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -292,7 +289,7 @@ void LD2420Component::report_gate_data() { | |||||||
|  |  | ||||||
| void LD2420Component::set_operating_mode(const std::string &state) { | void LD2420Component::set_operating_mode(const std::string &state) { | ||||||
|   // If unsupported firmware ignore mode select |   // If unsupported firmware ignore mode select | ||||||
|   if (get_firmware_int_(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) { |   if (LD2420Component::get_firmware_int(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) { | ||||||
|     this->current_operating_mode = OP_MODE_TO_UINT.at(state); |     this->current_operating_mode = OP_MODE_TO_UINT.at(state); | ||||||
|     // Entering Auto Calibrate we need to clear the privoiuos data collection |     // Entering Auto Calibrate we need to clear the privoiuos data collection | ||||||
|     this->operating_selector_->publish_state(state); |     this->operating_selector_->publish_state(state); | ||||||
| @@ -365,13 +362,13 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Resonable refresh rate for home assistant database size health |   // Resonable refresh rate for home assistant database size health | ||||||
|   const int32_t current_millis = millis(); |   const int32_t current_millis = App.get_loop_component_start_time(); | ||||||
|   if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) |   if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) | ||||||
|     return; |     return; | ||||||
|   this->last_periodic_millis = current_millis; |   this->last_periodic_millis = current_millis; | ||||||
|   for (auto &listener : this->listeners_) { |   for (auto &listener : this->listeners_) { | ||||||
|     listener->on_distance(get_distance_()); |     listener->on_distance(this->get_distance_()); | ||||||
|     listener->on_presence(get_presence_()); |     listener->on_presence(this->get_presence_()); | ||||||
|     listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0])); |     listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0])); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -392,9 +389,9 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) { | |||||||
|   char outbuf[bufsize]{0}; |   char outbuf[bufsize]{0}; | ||||||
|   while (true) { |   while (true) { | ||||||
|     if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') { |     if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') { | ||||||
|       set_presence_(false); |       this->set_presence_(false); | ||||||
|     } else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') { |     } else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') { | ||||||
|       set_presence_(true); |       this->set_presence_(true); | ||||||
|     } |     } | ||||||
|     if (inbuf[pos] >= '0' && inbuf[pos] <= '9') { |     if (inbuf[pos] >= '0' && inbuf[pos] <= '9') { | ||||||
|       if (index < bufsize - 1) { |       if (index < bufsize - 1) { | ||||||
| @@ -411,18 +408,18 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) { | |||||||
|   } |   } | ||||||
|   outbuf[index] = '\0'; |   outbuf[index] = '\0'; | ||||||
|   if (index > 1) |   if (index > 1) | ||||||
|     set_distance_(strtol(outbuf, &endptr, 10)); |     this->set_distance_(strtol(outbuf, &endptr, 10)); | ||||||
|  |  | ||||||
|   if (get_mode_() == CMD_SYSTEM_MODE_SIMPLE) { |   if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) { | ||||||
|     // Resonable refresh rate for home assistant database size health |     // Resonable refresh rate for home assistant database size health | ||||||
|     const int32_t current_millis = millis(); |     const int32_t current_millis = App.get_loop_component_start_time(); | ||||||
|     if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) |     if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) | ||||||
|       return; |       return; | ||||||
|     this->last_normal_periodic_millis = current_millis; |     this->last_normal_periodic_millis = current_millis; | ||||||
|     for (auto &listener : this->listeners_) |     for (auto &listener : this->listeners_) | ||||||
|       listener->on_distance(get_distance_()); |       listener->on_distance(this->get_distance_()); | ||||||
|     for (auto &listener : this->listeners_) |     for (auto &listener : this->listeners_) | ||||||
|       listener->on_presence(get_presence_()); |       listener->on_presence(this->get_presence_()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -433,10 +430,10 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
|   uint8_t data_element = 0; |   uint8_t data_element = 0; | ||||||
|   uint16_t data_pos = 0; |   uint16_t data_pos = 0; | ||||||
|   if (this->cmd_reply_.length > CMD_MAX_BYTES) { |   if (this->cmd_reply_.length > CMD_MAX_BYTES) { | ||||||
|     ESP_LOGW(TAG, "LD2420 reply - received command reply frame is corrupt, length exceeds %d bytes.", CMD_MAX_BYTES); |     ESP_LOGW(TAG, "Reply frame too long"); | ||||||
|     return; |     return; | ||||||
|   } else if (this->cmd_reply_.length < 2) { |   } else if (this->cmd_reply_.length < 2) { | ||||||
|     ESP_LOGW(TAG, "LD2420 reply - received command frame is corrupt, length is less than 2 bytes."); |     ESP_LOGW(TAG, "Command frame too short"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error)); |   memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error)); | ||||||
| @@ -447,13 +444,13 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
|   this->cmd_reply_.ack = true; |   this->cmd_reply_.ack = true; | ||||||
|   switch ((uint16_t) this->cmd_reply_.command) { |   switch ((uint16_t) this->cmd_reply_.command) { | ||||||
|     case (CMD_ENABLE_CONF): |     case (CMD_ENABLE_CONF): | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result); |       ESP_LOGV(TAG, "Set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result); | ||||||
|       break; |       break; | ||||||
|     case (CMD_DISABLE_CONF): |     case (CMD_DISABLE_CONF): | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result); |       ESP_LOGV(TAG, "Set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result); | ||||||
|       break; |       break; | ||||||
|     case (CMD_READ_REGISTER): |     case (CMD_READ_REGISTER): | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - read register: CMD = %2X %s", CMD_READ_REGISTER, result); |       ESP_LOGV(TAG, "Read register: CMD = %2X %s", CMD_READ_REGISTER, result); | ||||||
|       // TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file |       // TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file | ||||||
|       data_pos = 0x0A; |       data_pos = 0x0A; | ||||||
|       for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE *  // NOLINT |       for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE *  // NOLINT | ||||||
| @@ -465,13 +462,13 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case (CMD_WRITE_REGISTER): |     case (CMD_WRITE_REGISTER): | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - write register: CMD = %2X %s", CMD_WRITE_REGISTER, result); |       ESP_LOGV(TAG, "Write register: CMD = %2X %s", CMD_WRITE_REGISTER, result); | ||||||
|       break; |       break; | ||||||
|     case (CMD_WRITE_ABD_PARAM): |     case (CMD_WRITE_ABD_PARAM): | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result); |       ESP_LOGV(TAG, "Write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result); | ||||||
|       break; |       break; | ||||||
|     case (CMD_READ_ABD_PARAM): |     case (CMD_READ_ABD_PARAM): | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result); |       ESP_LOGV(TAG, "Read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result); | ||||||
|       data_pos = CMD_ABD_DATA_REPLY_START; |       data_pos = CMD_ABD_DATA_REPLY_START; | ||||||
|       for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE *  // NOLINT |       for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE *  // NOLINT | ||||||
|                                         ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE)); |                                         ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE)); | ||||||
| @@ -483,11 +480,11 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case (CMD_WRITE_SYS_PARAM): |     case (CMD_WRITE_SYS_PARAM): | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result); |       ESP_LOGV(TAG, "Set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result); | ||||||
|       break; |       break; | ||||||
|     case (CMD_READ_VERSION): |     case (CMD_READ_VERSION): | ||||||
|       memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]); |       memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]); | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - module firmware version: %7s %s", this->ld2420_firmware_ver_, result); |       ESP_LOGV(TAG, "Firmware version: %7s %s", this->ld2420_firmware_ver_, result); | ||||||
|       break; |       break; | ||||||
|     default: |     default: | ||||||
|       break; |       break; | ||||||
| @@ -533,7 +530,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     while (!this->cmd_reply_.ack) { |     while (!this->cmd_reply_.ack) { | ||||||
|       while (available()) { |       while (this->available()) { | ||||||
|         this->readline_(read(), ack_buffer, sizeof(ack_buffer)); |         this->readline_(read(), ack_buffer, sizeof(ack_buffer)); | ||||||
|       } |       } | ||||||
|       delay_microseconds_safe(1450); |       delay_microseconds_safe(1450); | ||||||
| @@ -548,7 +545,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { | |||||||
|     if (this->cmd_reply_.ack) |     if (this->cmd_reply_.ack) | ||||||
|       retry = 0; |       retry = 0; | ||||||
|     if (this->cmd_reply_.error > 0) |     if (this->cmd_reply_.error > 0) | ||||||
|       handle_cmd_error(error); |       this->handle_cmd_error(error); | ||||||
|   } |   } | ||||||
|   return error; |   return error; | ||||||
| } | } | ||||||
| @@ -563,7 +560,7 @@ uint8_t LD2420Component::set_config_mode(bool enable) { | |||||||
|     cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER); |     cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER); | ||||||
|   } |   } | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command); |   ESP_LOGV(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command); | ||||||
|   return this->send_cmd_from_array(cmd_frame); |   return this->send_cmd_from_array(cmd_frame); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -576,7 +573,7 @@ void LD2420Component::ld2420_restart() { | |||||||
|   cmd_frame.header = CMD_FRAME_HEADER; |   cmd_frame.header = CMD_FRAME_HEADER; | ||||||
|   cmd_frame.command = CMD_RESTART; |   cmd_frame.command = CMD_RESTART; | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending restart command: %2X", cmd_frame.command); |   ESP_LOGV(TAG, "Sending restart command: %2X", cmd_frame.command); | ||||||
|   this->send_cmd_from_array(cmd_frame); |   this->send_cmd_from_array(cmd_frame); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -588,7 +585,7 @@ void LD2420Component::get_reg_value_(uint16_t reg) { | |||||||
|   cmd_frame.data[1] = reg; |   cmd_frame.data[1] = reg; | ||||||
|   cmd_frame.data_length += 2; |   cmd_frame.data_length += 2; | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command); |   ESP_LOGV(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command); | ||||||
|   this->send_cmd_from_array(cmd_frame); |   this->send_cmd_from_array(cmd_frame); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -602,11 +599,11 @@ void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) { | |||||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE)); |   memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE)); | ||||||
|   cmd_frame.data_length += 2; |   cmd_frame.data_length += 2; | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value); |   ESP_LOGV(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value); | ||||||
|   this->send_cmd_from_array(cmd_frame); |   this->send_cmd_from_array(cmd_frame); | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGI(TAG, "Command failed: %s", ERR_MESSAGE[error]); } | void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGE(TAG, "Command failed: %s", ERR_MESSAGE[error]); } | ||||||
|  |  | ||||||
| int LD2420Component::get_gate_threshold_(uint8_t gate) { | int LD2420Component::get_gate_threshold_(uint8_t gate) { | ||||||
|   uint8_t error; |   uint8_t error; | ||||||
| @@ -619,7 +616,7 @@ int LD2420Component::get_gate_threshold_(uint8_t gate) { | |||||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate])); |   memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate])); | ||||||
|   cmd_frame.data_length += 2; |   cmd_frame.data_length += 2; | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending read gate %d high/low theshold command: %2X", gate, cmd_frame.command); |   ESP_LOGV(TAG, "Sending read gate %d high/low threshold command: %2X", gate, cmd_frame.command); | ||||||
|   error = this->send_cmd_from_array(cmd_frame); |   error = this->send_cmd_from_array(cmd_frame); | ||||||
|   if (error == 0) { |   if (error == 0) { | ||||||
|     this->current_config.move_thresh[gate] = cmd_reply_.data[0]; |     this->current_config.move_thresh[gate] = cmd_reply_.data[0]; | ||||||
| @@ -644,7 +641,7 @@ int LD2420Component::get_min_max_distances_timeout_() { | |||||||
|          sizeof(CMD_TIMEOUT_REG));  // Register: global delay time |          sizeof(CMD_TIMEOUT_REG));  // Register: global delay time | ||||||
|   cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG); |   cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG); | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command); |   ESP_LOGV(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command); | ||||||
|   error = this->send_cmd_from_array(cmd_frame); |   error = this->send_cmd_from_array(cmd_frame); | ||||||
|   if (error == 0) { |   if (error == 0) { | ||||||
|     this->current_config.min_gate = (uint16_t) cmd_reply_.data[0]; |     this->current_config.min_gate = (uint16_t) cmd_reply_.data[0]; | ||||||
| @@ -667,9 +664,9 @@ void LD2420Component::set_system_mode(uint16_t mode) { | |||||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm)); |   memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm)); | ||||||
|   cmd_frame.data_length += sizeof(unknown_parm); |   cmd_frame.data_length += sizeof(unknown_parm); | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending write system mode command: %2X", cmd_frame.command); |   ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command); | ||||||
|   if (this->send_cmd_from_array(cmd_frame) == 0) |   if (this->send_cmd_from_array(cmd_frame) == 0) | ||||||
|     set_mode_(mode); |     this->set_mode_(mode); | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2420Component::get_firmware_version_() { | void LD2420Component::get_firmware_version_() { | ||||||
| @@ -679,7 +676,7 @@ void LD2420Component::get_firmware_version_() { | |||||||
|   cmd_frame.command = CMD_READ_VERSION; |   cmd_frame.command = CMD_READ_VERSION; | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "Sending read firmware version command: %2X", cmd_frame.command); |   ESP_LOGV(TAG, "Sending read firmware version command: %2X", cmd_frame.command); | ||||||
|   this->send_cmd_from_array(cmd_frame); |   this->send_cmd_from_array(cmd_frame); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -712,7 +709,7 @@ void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance, | |||||||
|   cmd_frame.data_length += sizeof(timeout); |   cmd_frame.data_length += sizeof(timeout); | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command); |   ESP_LOGV(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command); | ||||||
|   this->send_cmd_from_array(cmd_frame); |   this->send_cmd_from_array(cmd_frame); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -738,7 +735,7 @@ void LD2420Component::set_gate_threshold(uint8_t gate) { | |||||||
|          sizeof(this->new_config.still_thresh[gate])); |          sizeof(this->new_config.still_thresh[gate])); | ||||||
|   cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]); |   cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]); | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command); |   ESP_LOGV(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command); | ||||||
|   this->send_cmd_from_array(cmd_frame); |   this->send_cmd_from_array(cmd_frame); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -179,7 +179,7 @@ class LD2420Component : public Component, public uart::UARTDevice { | |||||||
|   void set_operating_mode(const std::string &state); |   void set_operating_mode(const std::string &state); | ||||||
|   void auto_calibrate_sensitivity(); |   void auto_calibrate_sensitivity(); | ||||||
|   void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number); |   void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number); | ||||||
|   uint8_t calc_checksum(void *data, size_t size); |   static uint8_t calc_checksum(void *data, size_t size); | ||||||
|  |  | ||||||
|   RegConfigT current_config; |   RegConfigT current_config; | ||||||
|   RegConfigT new_config; |   RegConfigT new_config; | ||||||
| @@ -222,7 +222,7 @@ class LD2420Component : public Component, public uart::UARTDevice { | |||||||
|     volatile bool ack; |     volatile bool ack; | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   int get_firmware_int_(const char *version_string); |   static int get_firmware_int(const char *version_string); | ||||||
|   void get_firmware_version_(); |   void get_firmware_version_(); | ||||||
|   int get_gate_threshold_(uint8_t gate); |   int get_gate_threshold_(uint8_t gate); | ||||||
|   void get_reg_value_(uint16_t reg); |   void get_reg_value_(uint16_t reg); | ||||||
|   | |||||||
| @@ -6,7 +6,9 @@ | |||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
| #endif | #endif | ||||||
|  | #include "esphome/core/application.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
| #define highbyte(val) (uint8_t)((val) >> 8) | #define highbyte(val) (uint8_t)((val) >> 8) | ||||||
| #define lowbyte(val) (uint8_t)((val) &0xff) | #define lowbyte(val) (uint8_t)((val) &0xff) | ||||||
| @@ -96,11 +98,6 @@ static inline std::string get_direction(int16_t speed) { | |||||||
|   return STATIONARY; |   return STATIONARY; | ||||||
| } | } | ||||||
|  |  | ||||||
| static inline std::string format_mac(uint8_t *buffer) { |  | ||||||
|   return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], |  | ||||||
|                       buffer[15]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static inline std::string format_version(uint8_t *buffer) { | static inline std::string format_version(uint8_t *buffer) { | ||||||
|   return str_sprintf("%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], |   return str_sprintf("%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], | ||||||
|                      buffer[14]); |                      buffer[14]); | ||||||
| @@ -120,7 +117,7 @@ void LD2450Component::setup() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void LD2450Component::dump_config() { | void LD2450Component::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "HLK-LD2450 Human motion tracking radar module:"); |   ESP_LOGCONFIG(TAG, "LD2450:"); | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
|   LOG_BINARY_SENSOR("  ", "TargetBinarySensor", this->target_binary_sensor_); |   LOG_BINARY_SENSOR("  ", "TargetBinarySensor", this->target_binary_sensor_); | ||||||
|   LOG_BINARY_SENSOR("  ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_); |   LOG_BINARY_SENSOR("  ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_); | ||||||
| @@ -189,9 +186,9 @@ void LD2450Component::dump_config() { | |||||||
|   LOG_NUMBER("  ", "PresenceTimeoutNumber", this->presence_timeout_number_); |   LOG_NUMBER("  ", "PresenceTimeoutNumber", this->presence_timeout_number_); | ||||||
| #endif | #endif | ||||||
|   ESP_LOGCONFIG(TAG, |   ESP_LOGCONFIG(TAG, | ||||||
|                 "  Throttle : %ums\n" |                 "  Throttle: %ums\n" | ||||||
|                 "  MAC Address : %s\n" |                 "  MAC Address: %s\n" | ||||||
|                 "  Firmware version : %s", |                 "  Firmware version: %s", | ||||||
|                 this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str())); |                 this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str())); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -266,8 +263,7 @@ bool LD2450Component::get_timeout_status_(uint32_t check_millis) { | |||||||
|   if (this->timeout_ == 0) { |   if (this->timeout_ == 0) { | ||||||
|     this->timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT); |     this->timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT); | ||||||
|   } |   } | ||||||
|   auto current_millis = millis(); |   return App.get_loop_component_start_time() - check_millis >= this->timeout_; | ||||||
|   return current_millis - check_millis >= this->timeout_; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Extract, store and publish zone details LD2450 buffer | // Extract, store and publish zone details LD2450 buffer | ||||||
| @@ -354,25 +350,24 @@ void LD2450Component::send_command_(uint8_t command, const uint8_t *command_valu | |||||||
| //   Header       Target 1                  Target 2                  Target 3                  End | //   Header       Target 1                  Target 2                  Target 3                  End | ||||||
| void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | ||||||
|   if (len < 29) {  // header (4 bytes) + 8 x 3 target data + footer (2 bytes) |   if (len < 29) {  // header (4 bytes) + 8 x 3 target data + footer (2 bytes) | ||||||
|     ESP_LOGE(TAG, "Periodic data: invalid message length"); |     ESP_LOGE(TAG, "Invalid message length"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) {  // header |   if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) {  // header | ||||||
|     ESP_LOGE(TAG, "Periodic data: invalid message header"); |     ESP_LOGE(TAG, "Invalid message header"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) {  // footer |   if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) {  // footer | ||||||
|     ESP_LOGE(TAG, "Periodic data: invalid message footer"); |     ESP_LOGE(TAG, "Invalid message footer"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   auto current_millis = millis(); |   if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) { | ||||||
|   if (current_millis - this->last_periodic_millis_ < this->throttle_) { |  | ||||||
|     ESP_LOGV(TAG, "Throttling: %d", this->throttle_); |     ESP_LOGV(TAG, "Throttling: %d", this->throttle_); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->last_periodic_millis_ = current_millis; |   this->last_periodic_millis_ = App.get_loop_component_start_time(); | ||||||
|  |  | ||||||
|   int16_t target_count = 0; |   int16_t target_count = 0; | ||||||
|   int16_t still_target_count = 0; |   int16_t still_target_count = 0; | ||||||
| @@ -555,13 +550,13 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | |||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
|   // For presence timeout check |   // For presence timeout check | ||||||
|   if (target_count > 0) { |   if (target_count > 0) { | ||||||
|     this->presence_millis_ = millis(); |     this->presence_millis_ = App.get_loop_component_start_time(); | ||||||
|   } |   } | ||||||
|   if (moving_target_count > 0) { |   if (moving_target_count > 0) { | ||||||
|     this->moving_presence_millis_ = millis(); |     this->moving_presence_millis_ = App.get_loop_component_start_time(); | ||||||
|   } |   } | ||||||
|   if (still_target_count > 0) { |   if (still_target_count > 0) { | ||||||
|     this->still_presence_millis_ = millis(); |     this->still_presence_millis_ = App.get_loop_component_start_time(); | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| @@ -569,31 +564,31 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | |||||||
| bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | ||||||
|   ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]); |   ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]); | ||||||
|   if (len < 10) { |   if (len < 10) { | ||||||
|     ESP_LOGE(TAG, "Ack data: invalid length"); |     ESP_LOGE(TAG, "Invalid ack length"); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) {  // frame header |   if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) {  // frame header | ||||||
|     ESP_LOGE(TAG, "Ack data: invalid header (command %02X)", buffer[COMMAND]); |     ESP_LOGE(TAG, "Invalid ack header (command %02X)", buffer[COMMAND]); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (buffer[COMMAND_STATUS] != 0x01) { |   if (buffer[COMMAND_STATUS] != 0x01) { | ||||||
|     ESP_LOGE(TAG, "Ack data: invalid status"); |     ESP_LOGE(TAG, "Invalid ack status"); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (buffer[8] || buffer[9]) { |   if (buffer[8] || buffer[9]) { | ||||||
|     ESP_LOGE(TAG, "Ack data: last buffer was %u, %u", buffer[8], buffer[9]); |     ESP_LOGE(TAG, "Last buffer was %u, %u", buffer[8], buffer[9]); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   switch (buffer[COMMAND]) { |   switch (buffer[COMMAND]) { | ||||||
|     case lowbyte(CMD_ENABLE_CONF): |     case lowbyte(CMD_ENABLE_CONF): | ||||||
|       ESP_LOGV(TAG, "Got enable conf command"); |       ESP_LOGV(TAG, "Enable conf command"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_DISABLE_CONF): |     case lowbyte(CMD_DISABLE_CONF): | ||||||
|       ESP_LOGV(TAG, "Got disable conf command"); |       ESP_LOGV(TAG, "Disable conf command"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_SET_BAUD_RATE): |     case lowbyte(CMD_SET_BAUD_RATE): | ||||||
|       ESP_LOGV(TAG, "Got baud rate change command"); |       ESP_LOGV(TAG, "Baud rate change command"); | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|       if (this->baud_rate_select_ != nullptr) { |       if (this->baud_rate_select_ != nullptr) { | ||||||
|         ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str()); |         ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str()); | ||||||
| @@ -613,7 +608,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | |||||||
|       if (len < 20) { |       if (len < 20) { | ||||||
|         return false; |         return false; | ||||||
|       } |       } | ||||||
|       this->mac_ = ld2450::format_mac(buffer); |       this->mac_ = format_mac_address_pretty(&buffer[10]); | ||||||
|       ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str()); |       ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str()); | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|       if (this->mac_text_sensor_ != nullptr) { |       if (this->mac_text_sensor_ != nullptr) { | ||||||
| @@ -622,15 +617,15 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | |||||||
| #endif | #endif | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|       if (this->bluetooth_switch_ != nullptr) { |       if (this->bluetooth_switch_ != nullptr) { | ||||||
|         this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC); |         this->bluetooth_switch_->publish_state(this->mac_ != UNKNOWN_MAC); | ||||||
|       } |       } | ||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_BLUETOOTH): |     case lowbyte(CMD_BLUETOOTH): | ||||||
|       ESP_LOGV(TAG, "Got Bluetooth command"); |       ESP_LOGV(TAG, "Bluetooth command"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_SINGLE_TARGET_MODE): |     case lowbyte(CMD_SINGLE_TARGET_MODE): | ||||||
|       ESP_LOGV(TAG, "Got single target conf command"); |       ESP_LOGV(TAG, "Single target conf command"); | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|       if (this->multi_target_switch_ != nullptr) { |       if (this->multi_target_switch_ != nullptr) { | ||||||
|         this->multi_target_switch_->publish_state(false); |         this->multi_target_switch_->publish_state(false); | ||||||
| @@ -638,7 +633,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | |||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_MULTI_TARGET_MODE): |     case lowbyte(CMD_MULTI_TARGET_MODE): | ||||||
|       ESP_LOGV(TAG, "Got multi target conf command"); |       ESP_LOGV(TAG, "Multi target conf command"); | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|       if (this->multi_target_switch_ != nullptr) { |       if (this->multi_target_switch_ != nullptr) { | ||||||
|         this->multi_target_switch_->publish_state(true); |         this->multi_target_switch_->publish_state(true); | ||||||
| @@ -646,7 +641,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | |||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_QUERY_TARGET_MODE): |     case lowbyte(CMD_QUERY_TARGET_MODE): | ||||||
|       ESP_LOGV(TAG, "Got query target tracking mode command"); |       ESP_LOGV(TAG, "Query target tracking mode command"); | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|       if (this->multi_target_switch_ != nullptr) { |       if (this->multi_target_switch_ != nullptr) { | ||||||
|         this->multi_target_switch_->publish_state(buffer[10] == 0x02); |         this->multi_target_switch_->publish_state(buffer[10] == 0x02); | ||||||
| @@ -654,7 +649,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | |||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_QUERY_ZONE): |     case lowbyte(CMD_QUERY_ZONE): | ||||||
|       ESP_LOGV(TAG, "Got query zone conf command"); |       ESP_LOGV(TAG, "Query zone conf command"); | ||||||
|       this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16); |       this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16); | ||||||
|       this->publish_zone_type(); |       this->publish_zone_type(); | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
| @@ -674,7 +669,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | |||||||
|       this->process_zone_(buffer); |       this->process_zone_(buffer); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_SET_ZONE): |     case lowbyte(CMD_SET_ZONE): | ||||||
|       ESP_LOGV(TAG, "Got set zone conf command"); |       ESP_LOGV(TAG, "Set zone conf command"); | ||||||
|       this->query_zone_info(); |       this->query_zone_info(); | ||||||
|       break; |       break; | ||||||
|     default: |     default: | ||||||
|   | |||||||
| @@ -38,8 +38,8 @@ from esphome.const import ( | |||||||
|     CONF_WHITE, |     CONF_WHITE, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| from .automation import LIGHT_STATE_SCHEMA | from .automation import LIGHT_STATE_SCHEMA | ||||||
| from .effects import ( | from .effects import ( | ||||||
| @@ -110,6 +110,8 @@ LIGHT_SCHEMA = ( | |||||||
|     ) |     ) | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | LIGHT_SCHEMA.add_extra(entity_duplicate_validator("light")) | ||||||
|  |  | ||||||
| BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend( | BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend( | ||||||
|     { |     { | ||||||
|         cv.Optional(CONF_EFFECTS): validate_effects(BINARY_EFFECTS), |         cv.Optional(CONF_EFFECTS): validate_effects(BINARY_EFFECTS), | ||||||
| @@ -207,7 +209,7 @@ def validate_color_temperature_channels(value): | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_light_core_(light_var, output_var, config): | async def setup_light_core_(light_var, output_var, config): | ||||||
|     await setup_entity(light_var, config) |     await setup_entity(light_var, config, "light") | ||||||
|  |  | ||||||
|     cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE])) |     cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE])) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,8 +14,8 @@ from esphome.const import ( | |||||||
|     CONF_WEB_SERVER, |     CONF_WEB_SERVER, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
| @@ -67,6 +67,9 @@ _LOCK_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _LOCK_SCHEMA.add_extra(entity_duplicate_validator("lock")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def lock_schema( | def lock_schema( | ||||||
|     class_: MockObjClass = cv.UNDEFINED, |     class_: MockObjClass = cv.UNDEFINED, | ||||||
|     *, |     *, | ||||||
| @@ -94,7 +97,7 @@ LOCK_SCHEMA.add_extra(cv.deprecated_schema_constant("lock")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def _setup_lock_core(var, config): | async def _setup_lock_core(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "lock") | ||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_LOCK, []): |     for conf in config.get(CONF_ON_LOCK, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import esphome.config_validation as cv | |||||||
| from esphome.const import CONF_SIZE, CONF_TEXT | from esphome.const import CONF_SIZE, CONF_TEXT | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
|  |  | ||||||
| from ..defines import CONF_MAIN, literal | from ..defines import CONF_MAIN | ||||||
| from ..lv_validation import color, color_retmapper, lv_text | from ..lv_validation import color, color_retmapper, lv_text | ||||||
| from ..lvcode import LocalVariable, lv, lv_expr | from ..lvcode import LocalVariable, lv, lv_expr | ||||||
| from ..schemas import TEXT_SCHEMA | from ..schemas import TEXT_SCHEMA | ||||||
| @@ -34,7 +34,7 @@ class QrCodeType(WidgetType): | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def get_uses(self): |     def get_uses(self): | ||||||
|         return ("canvas", "img") |         return ("canvas", "img", "label") | ||||||
|  |  | ||||||
|     def obj_creator(self, parent: MockObjClass, config: dict): |     def obj_creator(self, parent: MockObjClass, config: dict): | ||||||
|         dark_color = color_retmapper(config[CONF_DARK_COLOR]) |         dark_color = color_retmapper(config[CONF_DARK_COLOR]) | ||||||
| @@ -45,10 +45,8 @@ class QrCodeType(WidgetType): | |||||||
|     async def to_code(self, w: Widget, config): |     async def to_code(self, w: Widget, config): | ||||||
|         if (value := config.get(CONF_TEXT)) is not None: |         if (value := config.get(CONF_TEXT)) is not None: | ||||||
|             value = await lv_text.process(value) |             value = await lv_text.process(value) | ||||||
|             with LocalVariable( |             with LocalVariable("qr_text", cg.std_string, value, modifier="") as str_obj: | ||||||
|                 "qr_text", cg.const_char_ptr, value, modifier="" |                 lv.qrcode_update(w.obj, str_obj.c_str(), str_obj.size()) | ||||||
|             ) as str_obj: |  | ||||||
|                 lv.qrcode_update(w.obj, str_obj, literal(f"strlen({str_obj})")) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| qr_code_spec = QrCodeType() | qr_code_spec = QrCodeType() | ||||||
|   | |||||||
| @@ -11,9 +11,9 @@ from esphome.const import ( | |||||||
|     CONF_VOLUME, |     CONF_VOLUME, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE | from esphome.core import CORE | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.coroutine import coroutine_with_priority | from esphome.coroutine import coroutine_with_priority | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
|  |  | ||||||
| @@ -81,7 +81,7 @@ IsAnnouncingCondition = media_player_ns.class_( | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_media_player_core_(var, config): | async def setup_media_player_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "media_player") | ||||||
|     for conf in config.get(CONF_ON_STATE, []): |     for conf in config.get(CONF_ON_STATE, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|         await automation.build_automation(trigger, [], conf) |         await automation.build_automation(trigger, [], conf) | ||||||
| @@ -143,6 +143,8 @@ _MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( | |||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | _MEDIA_PLAYER_SCHEMA.add_extra(entity_duplicate_validator("media_player")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def media_player_schema( | def media_player_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
| @@ -166,7 +168,6 @@ def media_player_schema( | |||||||
| MEDIA_PLAYER_SCHEMA = media_player_schema(MediaPlayer) | MEDIA_PLAYER_SCHEMA = media_player_schema(MediaPlayer) | ||||||
| MEDIA_PLAYER_SCHEMA.add_extra(cv.deprecated_schema_constant("media_player")) | MEDIA_PLAYER_SCHEMA.add_extra(cv.deprecated_schema_constant("media_player")) | ||||||
|  |  | ||||||
|  |  | ||||||
| MEDIA_PLAYER_ACTION_SCHEMA = automation.maybe_simple_id( | MEDIA_PLAYER_ACTION_SCHEMA = automation.maybe_simple_id( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -64,6 +64,14 @@ class ModbusDevice { | |||||||
|     this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload); |     this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload); | ||||||
|   } |   } | ||||||
|   void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload); } |   void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload); } | ||||||
|  |   void send_error(uint8_t function_code, uint8_t exception_code) { | ||||||
|  |     std::vector<uint8_t> error_response; | ||||||
|  |     error_response.reserve(3); | ||||||
|  |     error_response.push_back(this->address_); | ||||||
|  |     error_response.push_back(function_code | 0x80); | ||||||
|  |     error_response.push_back(exception_code); | ||||||
|  |     this->send_raw(error_response); | ||||||
|  |   } | ||||||
|   // If more than one device is connected block sending a new command before a response is received |   // If more than one device is connected block sending a new command before a response is received | ||||||
|   bool waiting_for_response() { return parent_->waiting_for_response != 0; } |   bool waiting_for_response() { return parent_->waiting_for_response != 0; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -112,6 +112,22 @@ TYPE_REGISTER_MAP = { | |||||||
|     "FP32_R": 2, |     "FP32_R": 2, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | CPP_TYPE_REGISTER_MAP = { | ||||||
|  |     "RAW": cg.uint16, | ||||||
|  |     "U_WORD": cg.uint16, | ||||||
|  |     "S_WORD": cg.int16, | ||||||
|  |     "U_DWORD": cg.uint32, | ||||||
|  |     "U_DWORD_R": cg.uint32, | ||||||
|  |     "S_DWORD": cg.int32, | ||||||
|  |     "S_DWORD_R": cg.int32, | ||||||
|  |     "U_QWORD": cg.uint64, | ||||||
|  |     "U_QWORD_R": cg.uint64, | ||||||
|  |     "S_QWORD": cg.int64, | ||||||
|  |     "S_QWORD_R": cg.int64, | ||||||
|  |     "FP32": cg.float_, | ||||||
|  |     "FP32_R": cg.float_, | ||||||
|  | } | ||||||
|  |  | ||||||
| ModbusCommandSentTrigger = modbus_controller_ns.class_( | ModbusCommandSentTrigger = modbus_controller_ns.class_( | ||||||
|     "ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_) |     "ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_) | ||||||
| ) | ) | ||||||
| @@ -285,21 +301,24 @@ async def to_code(config): | |||||||
|     cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES])) |     cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES])) | ||||||
|     if CONF_SERVER_REGISTERS in config: |     if CONF_SERVER_REGISTERS in config: | ||||||
|         for server_register in config[CONF_SERVER_REGISTERS]: |         for server_register in config[CONF_SERVER_REGISTERS]: | ||||||
|  |             server_register_var = cg.new_Pvariable( | ||||||
|  |                 server_register[CONF_ID], | ||||||
|  |                 server_register[CONF_ADDRESS], | ||||||
|  |                 server_register[CONF_VALUE_TYPE], | ||||||
|  |                 TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]], | ||||||
|  |             ) | ||||||
|  |             cpp_type = CPP_TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]] | ||||||
|             cg.add( |             cg.add( | ||||||
|                 var.add_server_register( |                 server_register_var.set_read_lambda( | ||||||
|                     cg.new_Pvariable( |                     cg.TemplateArguments(cpp_type), | ||||||
|                         server_register[CONF_ID], |                     await cg.process_lambda( | ||||||
|                         server_register[CONF_ADDRESS], |                         server_register[CONF_READ_LAMBDA], | ||||||
|                         server_register[CONF_VALUE_TYPE], |                         [(cg.uint16, "address")], | ||||||
|                         TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]], |                         return_type=cpp_type, | ||||||
|                         await cg.process_lambda( |                     ), | ||||||
|                             server_register[CONF_READ_LAMBDA], |  | ||||||
|                             [], |  | ||||||
|                             return_type=cg.float_, |  | ||||||
|                         ), |  | ||||||
|                     ) |  | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|  |             cg.add(var.add_server_register(server_register_var)) | ||||||
|     await register_modbus_device(var, config) |     await register_modbus_device(var, config) | ||||||
|     for conf in config.get(CONF_ON_COMMAND_SENT, []): |     for conf in config.get(CONF_ON_COMMAND_SENT, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|   | |||||||
| @@ -117,12 +117,17 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t | |||||||
|     bool found = false; |     bool found = false; | ||||||
|     for (auto *server_register : this->server_registers_) { |     for (auto *server_register : this->server_registers_) { | ||||||
|       if (server_register->address == current_address) { |       if (server_register->address == current_address) { | ||||||
|         float value = server_register->read_lambda(); |         if (!server_register->read_lambda) { | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |         int64_t value = server_register->read_lambda(); | ||||||
|  |         ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %s.", | ||||||
|  |                  server_register->address, static_cast<size_t>(server_register->value_type), | ||||||
|  |                  server_register->register_count, server_register->format_value(value).c_str()); | ||||||
|  |  | ||||||
|         ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %0.1f.", |         std::vector<uint16_t> payload; | ||||||
|                  server_register->address, static_cast<uint8_t>(server_register->value_type), |         payload.reserve(server_register->register_count * 2); | ||||||
|                  server_register->register_count, value); |         number_to_payload(payload, value, server_register->value_type); | ||||||
|         std::vector<uint16_t> payload = float_to_payload(value, server_register->value_type); |  | ||||||
|         sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend()); |         sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend()); | ||||||
|         current_address += server_register->register_count; |         current_address += server_register->register_count; | ||||||
|         found = true; |         found = true; | ||||||
| @@ -132,11 +137,7 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t | |||||||
|  |  | ||||||
|     if (!found) { |     if (!found) { | ||||||
|       ESP_LOGW(TAG, "Could not match any register to address %02X. Sending exception response.", current_address); |       ESP_LOGW(TAG, "Could not match any register to address %02X. Sending exception response.", current_address); | ||||||
|       std::vector<uint8_t> error_response; |       send_error(function_code, 0x02); | ||||||
|       error_response.push_back(this->address_); |  | ||||||
|       error_response.push_back(0x81); |  | ||||||
|       error_response.push_back(0x02); |  | ||||||
|       this->send_raw(error_response); |  | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -63,6 +63,10 @@ enum class SensorValueType : uint8_t { | |||||||
|   FP32_R = 0xD |   FP32_R = 0xD | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | inline bool value_type_is_float(SensorValueType v) { | ||||||
|  |   return v == SensorValueType::FP32 || v == SensorValueType::FP32_R; | ||||||
|  | } | ||||||
|  |  | ||||||
| inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) { | inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) { | ||||||
|   switch (reg_type) { |   switch (reg_type) { | ||||||
|     case ModbusRegisterType::COIL: |     case ModbusRegisterType::COIL: | ||||||
| @@ -253,18 +257,53 @@ class SensorItem { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| class ServerRegister { | class ServerRegister { | ||||||
|  |   using ReadLambda = std::function<int64_t()>; | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count, |   ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count) { | ||||||
|                  std::function<float()> read_lambda) { |  | ||||||
|     this->address = address; |     this->address = address; | ||||||
|     this->value_type = value_type; |     this->value_type = value_type; | ||||||
|     this->register_count = register_count; |     this->register_count = register_count; | ||||||
|     this->read_lambda = std::move(read_lambda); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   template<typename T> void set_read_lambda(const std::function<T(uint16_t address)> &&user_read_lambda) { | ||||||
|  |     this->read_lambda = [this, user_read_lambda]() -> int64_t { | ||||||
|  |       T user_value = user_read_lambda(this->address); | ||||||
|  |       if constexpr (std::is_same_v<T, float>) { | ||||||
|  |         return bit_cast<uint32_t>(user_value); | ||||||
|  |       } else { | ||||||
|  |         return static_cast<int64_t>(user_value); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Formats a raw value into a string representation based on the value type for debugging | ||||||
|  |   std::string format_value(int64_t value) const { | ||||||
|  |     switch (this->value_type) { | ||||||
|  |       case SensorValueType::U_WORD: | ||||||
|  |       case SensorValueType::U_DWORD: | ||||||
|  |       case SensorValueType::U_DWORD_R: | ||||||
|  |       case SensorValueType::U_QWORD: | ||||||
|  |       case SensorValueType::U_QWORD_R: | ||||||
|  |         return std::to_string(static_cast<uint64_t>(value)); | ||||||
|  |       case SensorValueType::S_WORD: | ||||||
|  |       case SensorValueType::S_DWORD: | ||||||
|  |       case SensorValueType::S_DWORD_R: | ||||||
|  |       case SensorValueType::S_QWORD: | ||||||
|  |       case SensorValueType::S_QWORD_R: | ||||||
|  |         return std::to_string(value); | ||||||
|  |       case SensorValueType::FP32_R: | ||||||
|  |       case SensorValueType::FP32: | ||||||
|  |         return str_sprintf("%.1f", bit_cast<float>(static_cast<uint32_t>(value))); | ||||||
|  |       default: | ||||||
|  |         return std::to_string(value); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   uint16_t address{0}; |   uint16_t address{0}; | ||||||
|   SensorValueType value_type{SensorValueType::RAW}; |   SensorValueType value_type{SensorValueType::RAW}; | ||||||
|   uint8_t register_count{0}; |   uint8_t register_count{0}; | ||||||
|   std::function<float()> read_lambda; |   ReadLambda read_lambda; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // ModbusController::create_register_ranges_ tries to optimize register range | // ModbusController::create_register_ranges_ tries to optimize register range | ||||||
| @@ -444,7 +483,7 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { | |||||||
|   void on_modbus_data(const std::vector<uint8_t> &data) override; |   void on_modbus_data(const std::vector<uint8_t> &data) override; | ||||||
|   /// called when a modbus error response was received |   /// called when a modbus error response was received | ||||||
|   void on_modbus_error(uint8_t function_code, uint8_t exception_code) override; |   void on_modbus_error(uint8_t function_code, uint8_t exception_code) override; | ||||||
|   /// called when a modbus request (function code 3 or 4) was parsed without errors |   /// called when a modbus request (function code 0x03 or 0x04) was parsed without errors | ||||||
|   void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final; |   void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final; | ||||||
|   /// default delegate called by process_modbus_data when a response has retrieved from the incoming queue |   /// default delegate called by process_modbus_data when a response has retrieved from the incoming queue | ||||||
|   void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data); |   void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data); | ||||||
| @@ -529,7 +568,7 @@ inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem | |||||||
|   int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask); |   int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask); | ||||||
|  |  | ||||||
|   float float_value; |   float float_value; | ||||||
|   if (item.sensor_value_type == SensorValueType::FP32 || item.sensor_value_type == SensorValueType::FP32_R) { |   if (value_type_is_float(item.sensor_value_type)) { | ||||||
|     float_value = bit_cast<float>(static_cast<uint32_t>(number)); |     float_value = bit_cast<float>(static_cast<uint32_t>(number)); | ||||||
|   } else { |   } else { | ||||||
|     float_value = static_cast<float>(number); |     float_value = static_cast<float>(number); | ||||||
| @@ -541,7 +580,7 @@ inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem | |||||||
| inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) { | inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) { | ||||||
|   int64_t val; |   int64_t val; | ||||||
|  |  | ||||||
|   if (value_type == SensorValueType::FP32 || value_type == SensorValueType::FP32_R) { |   if (value_type_is_float(value_type)) { | ||||||
|     val = bit_cast<uint32_t>(value); |     val = bit_cast<uint32_t>(value); | ||||||
|   } else { |   } else { | ||||||
|     val = llroundf(value); |     val = llroundf(value); | ||||||
|   | |||||||
| @@ -68,6 +68,7 @@ def AUTO_LOAD(): | |||||||
|  |  | ||||||
| CONF_DISCOVER_IP = "discover_ip" | CONF_DISCOVER_IP = "discover_ip" | ||||||
| CONF_IDF_SEND_ASYNC = "idf_send_async" | CONF_IDF_SEND_ASYNC = "idf_send_async" | ||||||
|  | CONF_WAIT_FOR_CONNECTION = "wait_for_connection" | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_message_just_topic(value): | def validate_message_just_topic(value): | ||||||
| @@ -298,6 +299,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                 } |                 } | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_PUBLISH_NAN_AS_NONE, default=False): cv.boolean, |             cv.Optional(CONF_PUBLISH_NAN_AS_NONE, default=False): cv.boolean, | ||||||
|  |             cv.Optional(CONF_WAIT_FOR_CONNECTION, default=False): cv.boolean, | ||||||
|         } |         } | ||||||
|     ), |     ), | ||||||
|     validate_config, |     validate_config, | ||||||
| @@ -453,6 +455,8 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     cg.add(var.set_publish_nan_as_none(config[CONF_PUBLISH_NAN_AS_NONE])) |     cg.add(var.set_publish_nan_as_none(config[CONF_PUBLISH_NAN_AS_NONE])) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_wait_for_connection(config[CONF_WAIT_FOR_CONNECTION])) | ||||||
|  |  | ||||||
|  |  | ||||||
| MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema( | MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -17,7 +17,8 @@ enum class MQTTClientDisconnectReason : int8_t { | |||||||
|   MQTT_MALFORMED_CREDENTIALS = 4, |   MQTT_MALFORMED_CREDENTIALS = 4, | ||||||
|   MQTT_NOT_AUTHORIZED = 5, |   MQTT_NOT_AUTHORIZED = 5, | ||||||
|   ESP8266_NOT_ENOUGH_SPACE = 6, |   ESP8266_NOT_ENOUGH_SPACE = 6, | ||||||
|   TLS_BAD_FINGERPRINT = 7 |   TLS_BAD_FINGERPRINT = 7, | ||||||
|  |   DNS_RESOLVE_ERROR = 8 | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// internal struct for MQTT messages. | /// internal struct for MQTT messages. | ||||||
|   | |||||||
| @@ -176,7 +176,8 @@ void MQTTClientComponent::dump_config() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| bool MQTTClientComponent::can_proceed() { | bool MQTTClientComponent::can_proceed() { | ||||||
|   return network::is_disabled() || this->state_ == MQTT_CLIENT_DISABLED || this->is_connected(); |   return network::is_disabled() || this->state_ == MQTT_CLIENT_DISABLED || this->is_connected() || | ||||||
|  |          !this->wait_for_connection_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void MQTTClientComponent::start_dnslookup_() { | void MQTTClientComponent::start_dnslookup_() { | ||||||
| @@ -228,6 +229,8 @@ void MQTTClientComponent::check_dnslookup_() { | |||||||
|   if (this->dns_resolve_error_) { |   if (this->dns_resolve_error_) { | ||||||
|     ESP_LOGW(TAG, "Couldn't resolve IP address for '%s'", this->credentials_.address.c_str()); |     ESP_LOGW(TAG, "Couldn't resolve IP address for '%s'", this->credentials_.address.c_str()); | ||||||
|     this->state_ = MQTT_CLIENT_DISCONNECTED; |     this->state_ = MQTT_CLIENT_DISCONNECTED; | ||||||
|  |     this->disconnect_reason_ = MQTTClientDisconnectReason::DNS_RESOLVE_ERROR; | ||||||
|  |     this->on_disconnect_.call(MQTTClientDisconnectReason::DNS_RESOLVE_ERROR); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -697,7 +700,9 @@ void MQTTClientComponent::set_on_connect(mqtt_on_connect_callback_t &&callback) | |||||||
| } | } | ||||||
|  |  | ||||||
| void MQTTClientComponent::set_on_disconnect(mqtt_on_disconnect_callback_t &&callback) { | void MQTTClientComponent::set_on_disconnect(mqtt_on_disconnect_callback_t &&callback) { | ||||||
|  |   auto callback_copy = callback; | ||||||
|   this->mqtt_backend_.set_on_disconnect(std::forward<mqtt_on_disconnect_callback_t>(callback)); |   this->mqtt_backend_.set_on_disconnect(std::forward<mqtt_on_disconnect_callback_t>(callback)); | ||||||
|  |   this->on_disconnect_.add(std::move(callback_copy)); | ||||||
| } | } | ||||||
|  |  | ||||||
| #if ASYNC_TCP_SSL_ENABLED | #if ASYNC_TCP_SSL_ENABLED | ||||||
|   | |||||||
| @@ -4,11 +4,12 @@ | |||||||
|  |  | ||||||
| #ifdef USE_MQTT | #ifdef USE_MQTT | ||||||
|  |  | ||||||
| #include "esphome/core/component.h" |  | ||||||
| #include "esphome/core/automation.h" |  | ||||||
| #include "esphome/core/log.h" |  | ||||||
| #include "esphome/components/json/json_util.h" | #include "esphome/components/json/json_util.h" | ||||||
| #include "esphome/components/network/ip_address.h" | #include "esphome/components/network/ip_address.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
| #if defined(USE_ESP32) | #if defined(USE_ESP32) | ||||||
| #include "mqtt_backend_esp32.h" | #include "mqtt_backend_esp32.h" | ||||||
| #elif defined(USE_ESP8266) | #elif defined(USE_ESP8266) | ||||||
| @@ -267,6 +268,8 @@ class MQTTClientComponent : public Component { | |||||||
|   void set_publish_nan_as_none(bool publish_nan_as_none); |   void set_publish_nan_as_none(bool publish_nan_as_none); | ||||||
|   bool is_publish_nan_as_none() const; |   bool is_publish_nan_as_none() const; | ||||||
|  |  | ||||||
|  |   void set_wait_for_connection(bool wait_for_connection) { this->wait_for_connection_ = wait_for_connection; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void send_device_info_(); |   void send_device_info_(); | ||||||
|  |  | ||||||
| @@ -332,8 +335,10 @@ class MQTTClientComponent : public Component { | |||||||
|   uint32_t connect_begin_; |   uint32_t connect_begin_; | ||||||
|   uint32_t last_connected_{0}; |   uint32_t last_connected_{0}; | ||||||
|   optional<MQTTClientDisconnectReason> disconnect_reason_{}; |   optional<MQTTClientDisconnectReason> disconnect_reason_{}; | ||||||
|  |   CallbackManager<MQTTBackend::on_disconnect_callback_t> on_disconnect_; | ||||||
|  |  | ||||||
|   bool publish_nan_as_none_{false}; |   bool publish_nan_as_none_{false}; | ||||||
|  |   bool wait_for_connection_{false}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| extern MQTTClientComponent *global_mqtt_client;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | extern MQTTClientComponent *global_mqtt_client;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
|   | |||||||
| @@ -76,8 +76,8 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_WIND_SPEED, |     DEVICE_CLASS_WIND_SPEED, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| DEVICE_CLASSES = [ | DEVICE_CLASSES = [ | ||||||
| @@ -207,6 +207,9 @@ _NUMBER_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _NUMBER_SCHEMA.add_extra(entity_duplicate_validator("number")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def number_schema( | def number_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
|     *, |     *, | ||||||
| @@ -237,7 +240,7 @@ NUMBER_SCHEMA.add_extra(cv.deprecated_schema_constant("number")) | |||||||
| async def setup_number_core_( | async def setup_number_core_( | ||||||
|     var, config, *, min_value: float, max_value: float, step: float |     var, config, *, min_value: float, max_value: float, step: float | ||||||
| ): | ): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "number") | ||||||
|  |  | ||||||
|     cg.add(var.traits.set_min_value(min_value)) |     cg.add(var.traits.set_min_value(min_value)) | ||||||
|     cg.add(var.traits.set_max_value(max_value)) |     cg.add(var.traits.set_max_value(max_value)) | ||||||
|   | |||||||
| @@ -17,8 +17,8 @@ from esphome.const import ( | |||||||
|     CONF_WEB_SERVER, |     CONF_WEB_SERVER, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
| @@ -65,6 +65,9 @@ _SELECT_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _SELECT_SCHEMA.add_extra(entity_duplicate_validator("select")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def select_schema( | def select_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
|     *, |     *, | ||||||
| @@ -89,7 +92,7 @@ SELECT_SCHEMA.add_extra(cv.deprecated_schema_constant("select")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_select_core_(var, config, *, options: list[str]): | async def setup_select_core_(var, config, *, options: list[str]): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "select") | ||||||
|  |  | ||||||
|     cg.add(var.traits.set_options(options)) |     cg.add(var.traits.set_options(options)) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -101,8 +101,8 @@ from esphome.const import ( | |||||||
|     ENTITY_CATEGORY_CONFIG, |     ENTITY_CATEGORY_CONFIG, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
| from esphome.util import Registry | from esphome.util import Registry | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| @@ -318,6 +318,8 @@ _SENSOR_SCHEMA = ( | |||||||
|     ) |     ) | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | _SENSOR_SCHEMA.add_extra(entity_duplicate_validator("sensor")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def sensor_schema( | def sensor_schema( | ||||||
|     class_: MockObjClass = cv.UNDEFINED, |     class_: MockObjClass = cv.UNDEFINED, | ||||||
| @@ -787,7 +789,7 @@ async def build_filters(config): | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_sensor_core_(var, config): | async def setup_sensor_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "sensor") | ||||||
|  |  | ||||||
|     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: |     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: | ||||||
|         cg.add(var.set_device_class(device_class)) |         cg.add(var.set_device_class(device_class)) | ||||||
|   | |||||||
| @@ -20,8 +20,8 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_SWITCH, |     DEVICE_CLASS_SWITCH, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
| @@ -91,6 +91,9 @@ _SWITCH_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _SWITCH_SCHEMA.add_extra(entity_duplicate_validator("switch")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def switch_schema( | def switch_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
|     *, |     *, | ||||||
| @@ -131,7 +134,7 @@ SWITCH_SCHEMA.add_extra(cv.deprecated_schema_constant("switch")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_switch_core_(var, config): | async def setup_switch_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "switch") | ||||||
|  |  | ||||||
|     if (inverted := config.get(CONF_INVERTED)) is not None: |     if (inverted := config.get(CONF_INVERTED)) is not None: | ||||||
|         cg.add(var.set_inverted(inverted)) |         cg.add(var.set_inverted(inverted)) | ||||||
|   | |||||||
| @@ -14,8 +14,8 @@ from esphome.const import ( | |||||||
|     CONF_WEB_SERVER, |     CONF_WEB_SERVER, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@mauritskorse"] | CODEOWNERS = ["@mauritskorse"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
| @@ -58,6 +58,9 @@ _TEXT_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _TEXT_SCHEMA.add_extra(entity_duplicate_validator("text")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def text_schema( | def text_schema( | ||||||
|     class_: MockObjClass = cv.UNDEFINED, |     class_: MockObjClass = cv.UNDEFINED, | ||||||
|     *, |     *, | ||||||
| @@ -94,7 +97,7 @@ async def setup_text_core_( | |||||||
|     max_length: int | None, |     max_length: int | None, | ||||||
|     pattern: str | None, |     pattern: str | None, | ||||||
| ): | ): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "text") | ||||||
|  |  | ||||||
|     cg.add(var.traits.set_min_length(min_length)) |     cg.add(var.traits.set_min_length(min_length)) | ||||||
|     cg.add(var.traits.set_max_length(max_length)) |     cg.add(var.traits.set_max_length(max_length)) | ||||||
|   | |||||||
| @@ -21,8 +21,8 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_TIMESTAMP, |     DEVICE_CLASS_TIMESTAMP, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
| from esphome.util import Registry | from esphome.util import Registry | ||||||
|  |  | ||||||
| DEVICE_CLASSES = [ | DEVICE_CLASSES = [ | ||||||
| @@ -153,6 +153,9 @@ _TEXT_SENSOR_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _TEXT_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("text_sensor")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def text_sensor_schema( | def text_sensor_schema( | ||||||
|     class_: MockObjClass = cv.UNDEFINED, |     class_: MockObjClass = cv.UNDEFINED, | ||||||
|     *, |     *, | ||||||
| @@ -186,7 +189,7 @@ async def build_filters(config): | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_text_sensor_core_(var, config): | async def setup_text_sensor_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "text_sensor") | ||||||
|  |  | ||||||
|     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: |     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: | ||||||
|         cg.add(var.set_device_class(device_class)) |         cg.add(var.set_device_class(device_class)) | ||||||
|   | |||||||
| @@ -15,8 +15,8 @@ from esphome.const import ( | |||||||
|     ENTITY_CATEGORY_CONFIG, |     ENTITY_CATEGORY_CONFIG, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
| @@ -58,6 +58,9 @@ _UPDATE_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _UPDATE_SCHEMA.add_extra(entity_duplicate_validator("update")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def update_schema( | def update_schema( | ||||||
|     class_: MockObjClass = cv.UNDEFINED, |     class_: MockObjClass = cv.UNDEFINED, | ||||||
|     *, |     *, | ||||||
| @@ -87,7 +90,7 @@ UPDATE_SCHEMA.add_extra(cv.deprecated_schema_constant("update")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_update_core_(var, config): | async def setup_update_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "update") | ||||||
|  |  | ||||||
|     if device_class_config := config.get(CONF_DEVICE_CLASS): |     if device_class_config := config.get(CONF_DEVICE_CLASS): | ||||||
|         cg.add(var.set_device_class(device_class_config)) |         cg.add(var.set_device_class(device_class_config)) | ||||||
|   | |||||||
| @@ -22,8 +22,8 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_WATER, |     DEVICE_CLASS_WATER, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
| @@ -103,6 +103,9 @@ _VALVE_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _VALVE_SCHEMA.add_extra(entity_duplicate_validator("valve")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def valve_schema( | def valve_schema( | ||||||
|     class_: MockObjClass = cv.UNDEFINED, |     class_: MockObjClass = cv.UNDEFINED, | ||||||
|     *, |     *, | ||||||
| @@ -132,7 +135,7 @@ VALVE_SCHEMA.add_extra(cv.deprecated_schema_constant("valve")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def _setup_valve_core(var, config): | async def _setup_valve_core(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "valve") | ||||||
|  |  | ||||||
|     if device_class_config := config.get(CONF_DEVICE_CLASS): |     if device_class_config := config.get(CONF_DEVICE_CLASS): | ||||||
|         cg.add(var.set_device_class(device_class_config)) |         cg.add(var.set_device_class(device_class_config)) | ||||||
|   | |||||||
| @@ -741,11 +741,6 @@ void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->po | |||||||
|  |  | ||||||
| void WiFiComponent::set_passive_scan(bool passive) { this->passive_scan_ = passive; } | void WiFiComponent::set_passive_scan(bool passive) { this->passive_scan_ = passive; } | ||||||
|  |  | ||||||
| std::string WiFiComponent::format_mac_addr(const uint8_t *mac) { |  | ||||||
|   char buf[20]; |  | ||||||
|   sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); |  | ||||||
|   return buf; |  | ||||||
| } |  | ||||||
| bool WiFiComponent::is_captive_portal_active_() { | bool WiFiComponent::is_captive_portal_active_() { | ||||||
| #ifdef USE_CAPTIVE_PORTAL | #ifdef USE_CAPTIVE_PORTAL | ||||||
|   return captive_portal::global_captive_portal != nullptr && captive_portal::global_captive_portal->is_active(); |   return captive_portal::global_captive_portal != nullptr && captive_portal::global_captive_portal->is_active(); | ||||||
|   | |||||||
| @@ -321,8 +321,6 @@ class WiFiComponent : public Component { | |||||||
|   int32_t get_wifi_channel(); |   int32_t get_wifi_channel(); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   static std::string format_mac_addr(const uint8_t mac[6]); |  | ||||||
|  |  | ||||||
| #ifdef USE_WIFI_AP | #ifdef USE_WIFI_AP | ||||||
|   void setup_ap_config_(); |   void setup_ap_config_(); | ||||||
| #endif  // USE_WIFI_AP | #endif  // USE_WIFI_AP | ||||||
|   | |||||||
| @@ -550,7 +550,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ | |||||||
|       memcpy(buf, it.ssid, it.ssid_len); |       memcpy(buf, it.ssid, it.ssid_len); | ||||||
|       buf[it.ssid_len] = '\0'; |       buf[it.ssid_len] = '\0'; | ||||||
|       ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, |       ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, | ||||||
|                format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); |                format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); | ||||||
| #if USE_NETWORK_IPV6 | #if USE_NETWORK_IPV6 | ||||||
|       this->set_timeout(100, [] { WiFi.enableIPv6(); }); |       this->set_timeout(100, [] { WiFi.enableIPv6(); }); | ||||||
| #endif /* USE_NETWORK_IPV6 */ | #endif /* USE_NETWORK_IPV6 */ | ||||||
| @@ -566,7 +566,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ | |||||||
|         ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); |         ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); | ||||||
|       } else { |       } else { | ||||||
|         ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, |         ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, | ||||||
|                  format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); |                  format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       uint8_t reason = it.reason; |       uint8_t reason = it.reason; | ||||||
| @@ -636,13 +636,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ | |||||||
|     case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { |     case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { | ||||||
|       auto it = info.wifi_sta_connected; |       auto it = info.wifi_sta_connected; | ||||||
|       auto &mac = it.bssid; |       auto &mac = it.bssid; | ||||||
|       ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(mac).c_str()); |       ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str()); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { |     case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { | ||||||
|       auto it = info.wifi_sta_disconnected; |       auto it = info.wifi_sta_disconnected; | ||||||
|       auto &mac = it.bssid; |       auto &mac = it.bssid; | ||||||
|       ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(mac).c_str()); |       ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str()); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: { |     case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: { | ||||||
| @@ -651,7 +651,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ | |||||||
|     } |     } | ||||||
|     case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { |     case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { | ||||||
|       auto it = info.wifi_ap_probereqrecved; |       auto it = info.wifi_ap_probereqrecved; | ||||||
|       ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); |       ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     default: |     default: | ||||||
|   | |||||||
| @@ -496,7 +496,8 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { | |||||||
|       char buf[33]; |       char buf[33]; | ||||||
|       memcpy(buf, it.ssid, it.ssid_len); |       memcpy(buf, it.ssid, it.ssid_len); | ||||||
|       buf[it.ssid_len] = '\0'; |       buf[it.ssid_len] = '\0'; | ||||||
|       ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_addr(it.bssid).c_str(), it.channel); |       ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(), | ||||||
|  |                it.channel); | ||||||
|       s_sta_connected = true; |       s_sta_connected = true; | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
| @@ -510,7 +511,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { | |||||||
|         s_sta_connect_not_found = true; |         s_sta_connect_not_found = true; | ||||||
|       } else { |       } else { | ||||||
|         ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, |         ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, | ||||||
|                  format_mac_addr(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason))); |                  format_mac_address_pretty(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason))); | ||||||
|         s_sta_connect_error = true; |         s_sta_connect_error = true; | ||||||
|       } |       } | ||||||
|       s_sta_connected = false; |       s_sta_connected = false; | ||||||
| @@ -545,17 +546,17 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { | |||||||
|     } |     } | ||||||
|     case EVENT_SOFTAPMODE_STACONNECTED: { |     case EVENT_SOFTAPMODE_STACONNECTED: { | ||||||
|       auto it = event->event_info.sta_connected; |       auto it = event->event_info.sta_connected; | ||||||
|       ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_addr(it.mac).c_str(), it.aid); |       ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case EVENT_SOFTAPMODE_STADISCONNECTED: { |     case EVENT_SOFTAPMODE_STADISCONNECTED: { | ||||||
|       auto it = event->event_info.sta_disconnected; |       auto it = event->event_info.sta_disconnected; | ||||||
|       ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_addr(it.mac).c_str(), it.aid); |       ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case EVENT_SOFTAPMODE_PROBEREQRECVED: { |     case EVENT_SOFTAPMODE_PROBEREQRECVED: { | ||||||
|       auto it = event->event_info.ap_probereqrecved; |       auto it = event->event_info.ap_probereqrecved; | ||||||
|       ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); |       ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
| #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) | #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) | ||||||
| @@ -567,7 +568,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { | |||||||
|     } |     } | ||||||
|     case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: { |     case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: { | ||||||
|       auto it = event->event_info.distribute_sta_ip; |       auto it = event->event_info.distribute_sta_ip; | ||||||
|       ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_addr(it.mac).c_str(), |       ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), | ||||||
|                format_ip_addr(it.ip).c_str(), it.aid); |                format_ip_addr(it.ip).c_str(), it.aid); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -691,7 +691,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { | |||||||
|     memcpy(buf, it.ssid, it.ssid_len); |     memcpy(buf, it.ssid, it.ssid_len); | ||||||
|     buf[it.ssid_len] = '\0'; |     buf[it.ssid_len] = '\0'; | ||||||
|     ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, |     ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, | ||||||
|              format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); |              format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); | ||||||
|     s_sta_connected = true; |     s_sta_connected = true; | ||||||
|  |  | ||||||
|   } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) { |   } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) { | ||||||
| @@ -708,7 +708,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { | |||||||
|       return; |       return; | ||||||
|     } else { |     } else { | ||||||
|       ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, |       ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, | ||||||
|                format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); |                format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); | ||||||
|       s_sta_connect_error = true; |       s_sta_connect_error = true; | ||||||
|     } |     } | ||||||
|     s_sta_connected = false; |     s_sta_connected = false; | ||||||
| @@ -780,15 +780,15 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { | |||||||
|  |  | ||||||
|   } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_PROBEREQRECVED) { |   } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_PROBEREQRECVED) { | ||||||
|     const auto &it = data->data.ap_probe_req_rx; |     const auto &it = data->data.ap_probe_req_rx; | ||||||
|     ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); |     ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); | ||||||
|  |  | ||||||
|   } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STACONNECTED) { |   } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STACONNECTED) { | ||||||
|     const auto &it = data->data.ap_staconnected; |     const auto &it = data->data.ap_staconnected; | ||||||
|     ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(it.mac).c_str()); |     ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(it.mac).c_str()); | ||||||
|  |  | ||||||
|   } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STADISCONNECTED) { |   } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STADISCONNECTED) { | ||||||
|     const auto &it = data->data.ap_stadisconnected; |     const auto &it = data->data.ap_stadisconnected; | ||||||
|     ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(it.mac).c_str()); |     ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(it.mac).c_str()); | ||||||
|  |  | ||||||
|   } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_AP_STAIPASSIGNED) { |   } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_AP_STAIPASSIGNED) { | ||||||
|     const auto &it = data->data.ip_ap_staipassigned; |     const auto &it = data->data.ip_ap_staipassigned; | ||||||
|   | |||||||
| @@ -281,7 +281,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ | |||||||
|       memcpy(buf, it.ssid, it.ssid_len); |       memcpy(buf, it.ssid, it.ssid_len); | ||||||
|       buf[it.ssid_len] = '\0'; |       buf[it.ssid_len] = '\0'; | ||||||
|       ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, |       ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, | ||||||
|                format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); |                format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); | ||||||
|  |  | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
| @@ -294,7 +294,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ | |||||||
|         ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); |         ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); | ||||||
|       } else { |       } else { | ||||||
|         ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, |         ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, | ||||||
|                  format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); |                  format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       uint8_t reason = it.reason; |       uint8_t reason = it.reason; | ||||||
| @@ -349,13 +349,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ | |||||||
|     case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { |     case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { | ||||||
|       auto it = info.wifi_sta_connected; |       auto it = info.wifi_sta_connected; | ||||||
|       auto &mac = it.bssid; |       auto &mac = it.bssid; | ||||||
|       ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(mac).c_str()); |       ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str()); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { |     case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { | ||||||
|       auto it = info.wifi_sta_disconnected; |       auto it = info.wifi_sta_disconnected; | ||||||
|       auto &mac = it.bssid; |       auto &mac = it.bssid; | ||||||
|       ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(mac).c_str()); |       ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str()); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: { |     case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: { | ||||||
| @@ -364,7 +364,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ | |||||||
|     } |     } | ||||||
|     case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { |     case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { | ||||||
|       auto it = info.wifi_ap_probereqrecved; |       auto it = info.wifi_ap_probereqrecved; | ||||||
|       ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); |       ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     default: |     default: | ||||||
|   | |||||||
| @@ -320,7 +320,7 @@ bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, c | |||||||
|     memcpy(mac_address + 4, mac_reverse + 1, 1); |     memcpy(mac_address + 4, mac_reverse + 1, 1); | ||||||
|     memcpy(mac_address + 5, mac_reverse, 1); |     memcpy(mac_address + 5, mac_reverse, 1); | ||||||
|     ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed."); |     ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed."); | ||||||
|     ESP_LOGVV(TAG, "  MAC address : %s", format_hex_pretty(mac_address, 6).c_str()); |     ESP_LOGVV(TAG, "  MAC address : %s", format_mac_address_pretty(mac_address).c_str()); | ||||||
|     ESP_LOGVV(TAG, "       Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); |     ESP_LOGVV(TAG, "       Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); | ||||||
|     ESP_LOGVV(TAG, "          Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str()); |     ESP_LOGVV(TAG, "          Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str()); | ||||||
|     ESP_LOGVV(TAG, "           Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str()); |     ESP_LOGVV(TAG, "           Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str()); | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| """Helpers for config validation using voluptuous.""" | """Helpers for config validation using voluptuous.""" | ||||||
|  |  | ||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
| from contextlib import contextmanager | from contextlib import contextmanager | ||||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| @@ -29,6 +31,7 @@ from esphome.const import ( | |||||||
|     CONF_COMMAND_RETAIN, |     CONF_COMMAND_RETAIN, | ||||||
|     CONF_COMMAND_TOPIC, |     CONF_COMMAND_TOPIC, | ||||||
|     CONF_DAY, |     CONF_DAY, | ||||||
|  |     CONF_DEVICE_ID, | ||||||
|     CONF_DISABLED_BY_DEFAULT, |     CONF_DISABLED_BY_DEFAULT, | ||||||
|     CONF_DISCOVERY, |     CONF_DISCOVERY, | ||||||
|     CONF_ENTITY_CATEGORY, |     CONF_ENTITY_CATEGORY, | ||||||
| @@ -355,6 +358,13 @@ def icon(value): | |||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def sub_device_id(value: str | None) -> core.ID: | ||||||
|  |     # Lazy import to avoid circular imports | ||||||
|  |     from esphome.core.config import Device | ||||||
|  |  | ||||||
|  |     return use_id(Device)(value) | ||||||
|  |  | ||||||
|  |  | ||||||
| def boolean(value): | def boolean(value): | ||||||
|     """Validate the given config option to be a boolean. |     """Validate the given config option to be a boolean. | ||||||
|  |  | ||||||
| @@ -1896,6 +1906,7 @@ ENTITY_BASE_SCHEMA = Schema( | |||||||
|         Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean, |         Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean, | ||||||
|         Optional(CONF_ICON): icon, |         Optional(CONF_ICON): icon, | ||||||
|         Optional(CONF_ENTITY_CATEGORY): entity_category, |         Optional(CONF_ENTITY_CATEGORY): entity_category, | ||||||
|  |         Optional(CONF_DEVICE_ID): sub_device_id, | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -1964,7 +1975,7 @@ class Version: | |||||||
|         return f"{self.major}.{self.minor}.{self.patch}" |         return f"{self.major}.{self.minor}.{self.patch}" | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def parse(cls, value: str) -> "Version": |     def parse(cls, value: str) -> Version: | ||||||
|         match = re.match(r"^(\d+).(\d+).(\d+)-?\w*$", value) |         match = re.match(r"^(\d+).(\d+).(\d+)-?\w*$", value) | ||||||
|         if match is None: |         if match is None: | ||||||
|             raise ValueError(f"Not a valid version number {value}") |             raise ValueError(f"Not a valid version number {value}") | ||||||
|   | |||||||
| @@ -56,6 +56,8 @@ CONF_AP = "ap" | |||||||
| CONF_APPARENT_POWER = "apparent_power" | CONF_APPARENT_POWER = "apparent_power" | ||||||
| CONF_ARDUINO_VERSION = "arduino_version" | CONF_ARDUINO_VERSION = "arduino_version" | ||||||
| CONF_AREA = "area" | CONF_AREA = "area" | ||||||
|  | CONF_AREA_ID = "area_id" | ||||||
|  | CONF_AREAS = "areas" | ||||||
| CONF_ARGS = "args" | CONF_ARGS = "args" | ||||||
| CONF_ASSUMED_STATE = "assumed_state" | CONF_ASSUMED_STATE = "assumed_state" | ||||||
| CONF_AT = "at" | CONF_AT = "at" | ||||||
| @@ -217,6 +219,7 @@ CONF_DEST = "dest" | |||||||
| CONF_DEVICE = "device" | CONF_DEVICE = "device" | ||||||
| CONF_DEVICE_CLASS = "device_class" | CONF_DEVICE_CLASS = "device_class" | ||||||
| CONF_DEVICE_FACTOR = "device_factor" | CONF_DEVICE_FACTOR = "device_factor" | ||||||
|  | CONF_DEVICE_ID = "device_id" | ||||||
| CONF_DEVICES = "devices" | CONF_DEVICES = "devices" | ||||||
| CONF_DIELECTRIC_CONSTANT = "dielectric_constant" | CONF_DIELECTRIC_CONSTANT = "dielectric_constant" | ||||||
| CONF_DIMENSIONS = "dimensions" | CONF_DIMENSIONS = "dimensions" | ||||||
| @@ -1096,7 +1099,7 @@ UNIT_KILOMETER_PER_HOUR = "km/h" | |||||||
| UNIT_KILOVOLT_AMPS = "kVA" | UNIT_KILOVOLT_AMPS = "kVA" | ||||||
| UNIT_KILOVOLT_AMPS_HOURS = "kVAh" | UNIT_KILOVOLT_AMPS_HOURS = "kVAh" | ||||||
| UNIT_KILOVOLT_AMPS_REACTIVE = "kVAR" | UNIT_KILOVOLT_AMPS_REACTIVE = "kVAR" | ||||||
| UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVARh" | UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kvarh" | ||||||
| UNIT_KILOWATT = "kW" | UNIT_KILOWATT = "kW" | ||||||
| UNIT_KILOWATT_HOURS = "kWh" | UNIT_KILOWATT_HOURS = "kWh" | ||||||
| UNIT_LITRE = "L" | UNIT_LITRE = "L" | ||||||
| @@ -1132,7 +1135,7 @@ UNIT_VOLT = "V" | |||||||
| UNIT_VOLT_AMPS = "VA" | UNIT_VOLT_AMPS = "VA" | ||||||
| UNIT_VOLT_AMPS_HOURS = "VAh" | UNIT_VOLT_AMPS_HOURS = "VAh" | ||||||
| UNIT_VOLT_AMPS_REACTIVE = "var" | UNIT_VOLT_AMPS_REACTIVE = "var" | ||||||
| UNIT_VOLT_AMPS_REACTIVE_HOURS = "VARh" | UNIT_VOLT_AMPS_REACTIVE_HOURS = "varh" | ||||||
| UNIT_WATT = "W" | UNIT_WATT = "W" | ||||||
| UNIT_WATT_HOURS = "Wh" | UNIT_WATT_HOURS = "Wh" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -522,6 +522,9 @@ class EsphomeCore: | |||||||
|         # Dict to track platform entity counts for pre-allocation |         # Dict to track platform entity counts for pre-allocation | ||||||
|         # Key: platform name (e.g. "sensor", "binary_sensor"), Value: count |         # Key: platform name (e.g. "sensor", "binary_sensor"), Value: count | ||||||
|         self.platform_counts: defaultdict[str, int] = defaultdict(int) |         self.platform_counts: defaultdict[str, int] = defaultdict(int) | ||||||
|  |         # Track entity unique IDs to handle duplicates | ||||||
|  |         # Set of (device_id, platform, sanitized_name) tuples | ||||||
|  |         self.unique_ids: set[tuple[str, str, str]] = set() | ||||||
|         # Whether ESPHome was started in verbose mode |         # Whether ESPHome was started in verbose mode | ||||||
|         self.verbose = False |         self.verbose = False | ||||||
|         # Whether ESPHome was started in quiet mode |         # Whether ESPHome was started in quiet mode | ||||||
| @@ -553,6 +556,7 @@ class EsphomeCore: | |||||||
|         self.loaded_integrations = set() |         self.loaded_integrations = set() | ||||||
|         self.component_ids = set() |         self.component_ids = set() | ||||||
|         self.platform_counts = defaultdict(int) |         self.platform_counts = defaultdict(int) | ||||||
|  |         self.unique_ids = set() | ||||||
|         PIN_SCHEMA_REGISTRY.reset() |         PIN_SCHEMA_REGISTRY.reset() | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|   | |||||||
| @@ -9,6 +9,13 @@ | |||||||
| #include "esphome/core/preferences.h" | #include "esphome/core/preferences.h" | ||||||
| #include "esphome/core/scheduler.h" | #include "esphome/core/scheduler.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_DEVICES | ||||||
|  | #include "esphome/core/device.h" | ||||||
|  | #endif | ||||||
|  | #ifdef USE_AREAS | ||||||
|  | #include "esphome/core/area.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #ifdef USE_SOCKET_SELECT_SUPPORT | #ifdef USE_SOCKET_SELECT_SUPPORT | ||||||
| #include <sys/select.h> | #include <sys/select.h> | ||||||
| #endif | #endif | ||||||
| @@ -87,7 +94,7 @@ static const uint32_t TEARDOWN_TIMEOUT_REBOOT_MS = 1000;  // 1 second for quick | |||||||
|  |  | ||||||
| class Application { | class Application { | ||||||
|  public: |  public: | ||||||
|   void pre_setup(const std::string &name, const std::string &friendly_name, const char *area, const char *comment, |   void pre_setup(const std::string &name, const std::string &friendly_name, const char *comment, | ||||||
|                  const char *compilation_time, bool name_add_mac_suffix) { |                  const char *compilation_time, bool name_add_mac_suffix) { | ||||||
|     arch_init(); |     arch_init(); | ||||||
|     this->name_add_mac_suffix_ = name_add_mac_suffix; |     this->name_add_mac_suffix_ = name_add_mac_suffix; | ||||||
| @@ -102,11 +109,17 @@ class Application { | |||||||
|       this->name_ = name; |       this->name_ = name; | ||||||
|       this->friendly_name_ = friendly_name; |       this->friendly_name_ = friendly_name; | ||||||
|     } |     } | ||||||
|     this->area_ = area; |  | ||||||
|     this->comment_ = comment; |     this->comment_ = comment; | ||||||
|     this->compilation_time_ = compilation_time; |     this->compilation_time_ = compilation_time; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | #ifdef USE_DEVICES | ||||||
|  |   void register_device(Device *device) { this->devices_.push_back(device); } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_AREAS | ||||||
|  |   void register_area(Area *area) { this->areas_.push_back(area); } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   void set_current_component(Component *component) { this->current_component_ = component; } |   void set_current_component(Component *component) { this->current_component_ = component; } | ||||||
|   Component *get_current_component() { return this->current_component_; } |   Component *get_current_component() { return this->current_component_; } | ||||||
|  |  | ||||||
| @@ -264,6 +277,12 @@ class Application { | |||||||
| #ifdef USE_UPDATE | #ifdef USE_UPDATE | ||||||
|   void reserve_update(size_t count) { this->updates_.reserve(count); } |   void reserve_update(size_t count) { this->updates_.reserve(count); } | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_AREAS | ||||||
|  |   void reserve_area(size_t count) { this->areas_.reserve(count); } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_DEVICES | ||||||
|  |   void reserve_device(size_t count) { this->devices_.reserve(count); } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   /// Register the component in this Application instance. |   /// Register the component in this Application instance. | ||||||
|   template<class C> C *register_component(C *c) { |   template<class C> C *register_component(C *c) { | ||||||
| @@ -285,7 +304,15 @@ class Application { | |||||||
|   const std::string &get_friendly_name() const { return this->friendly_name_; } |   const std::string &get_friendly_name() const { return this->friendly_name_; } | ||||||
|  |  | ||||||
|   /// Get the area of this Application set by pre_setup(). |   /// Get the area of this Application set by pre_setup(). | ||||||
|   std::string get_area() const { return this->area_ == nullptr ? "" : this->area_; } |   const char *get_area() const { | ||||||
|  | #ifdef USE_AREAS | ||||||
|  |     // If we have areas registered, return the name of the first one (which is the top-level area) | ||||||
|  |     if (!this->areas_.empty() && this->areas_[0] != nullptr) { | ||||||
|  |       return this->areas_[0]->get_name(); | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  |     return ""; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /// Get the comment of this Application set by pre_setup(). |   /// Get the comment of this Application set by pre_setup(). | ||||||
|   std::string get_comment() const { return this->comment_; } |   std::string get_comment() const { return this->comment_; } | ||||||
| @@ -334,6 +361,12 @@ class Application { | |||||||
|  |  | ||||||
|   uint8_t get_app_state() const { return this->app_state_; } |   uint8_t get_app_state() const { return this->app_state_; } | ||||||
|  |  | ||||||
|  | #ifdef USE_DEVICES | ||||||
|  |   const std::vector<Device *> &get_devices() { return this->devices_; } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_AREAS | ||||||
|  |   const std::vector<Area *> &get_areas() { return this->areas_; } | ||||||
|  | #endif | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
|   const std::vector<binary_sensor::BinarySensor *> &get_binary_sensors() { return this->binary_sensors_; } |   const std::vector<binary_sensor::BinarySensor *> &get_binary_sensors() { return this->binary_sensors_; } | ||||||
|   binary_sensor::BinarySensor *get_binary_sensor_by_key(uint32_t key, bool include_internal = false) { |   binary_sensor::BinarySensor *get_binary_sensor_by_key(uint32_t key, bool include_internal = false) { | ||||||
| @@ -610,6 +643,12 @@ class Application { | |||||||
|   uint16_t current_loop_index_{0}; |   uint16_t current_loop_index_{0}; | ||||||
|   bool in_loop_{false}; |   bool in_loop_{false}; | ||||||
|  |  | ||||||
|  | #ifdef USE_DEVICES | ||||||
|  |   std::vector<Device *> devices_{}; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_AREAS | ||||||
|  |   std::vector<Area *> areas_{}; | ||||||
|  | #endif | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
|   std::vector<binary_sensor::BinarySensor *> binary_sensors_{}; |   std::vector<binary_sensor::BinarySensor *> binary_sensors_{}; | ||||||
| #endif | #endif | ||||||
| @@ -676,7 +715,6 @@ class Application { | |||||||
|  |  | ||||||
|   std::string name_; |   std::string name_; | ||||||
|   std::string friendly_name_; |   std::string friendly_name_; | ||||||
|   const char *area_{nullptr}; |  | ||||||
|   const char *comment_{nullptr}; |   const char *comment_{nullptr}; | ||||||
|   const char *compilation_time_{nullptr}; |   const char *compilation_time_{nullptr}; | ||||||
|   bool name_add_mac_suffix_; |   bool name_add_mac_suffix_; | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								esphome/core/area.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								esphome/core/area.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <cstdint> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  |  | ||||||
|  | class Area { | ||||||
|  |  public: | ||||||
|  |   void set_area_id(uint32_t area_id) { this->area_id_ = area_id; } | ||||||
|  |   uint32_t get_area_id() { return this->area_id_; } | ||||||
|  |   void set_name(const char *name) { this->name_ = name; } | ||||||
|  |   const char *get_name() { return this->name_; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint32_t area_id_{}; | ||||||
|  |   const char *name_ = ""; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace esphome | ||||||
| @@ -1,18 +1,24 @@ | |||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
| import logging | import logging | ||||||
| import os | import os | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
|  |  | ||||||
| from esphome import automation | from esphome import automation, core | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_AREA, |     CONF_AREA, | ||||||
|  |     CONF_AREA_ID, | ||||||
|  |     CONF_AREAS, | ||||||
|     CONF_BUILD_PATH, |     CONF_BUILD_PATH, | ||||||
|     CONF_COMMENT, |     CONF_COMMENT, | ||||||
|     CONF_COMPILE_PROCESS_LIMIT, |     CONF_COMPILE_PROCESS_LIMIT, | ||||||
|     CONF_DEBUG_SCHEDULER, |     CONF_DEBUG_SCHEDULER, | ||||||
|  |     CONF_DEVICES, | ||||||
|     CONF_ESPHOME, |     CONF_ESPHOME, | ||||||
|     CONF_FRIENDLY_NAME, |     CONF_FRIENDLY_NAME, | ||||||
|  |     CONF_ID, | ||||||
|     CONF_INCLUDES, |     CONF_INCLUDES, | ||||||
|     CONF_LIBRARIES, |     CONF_LIBRARIES, | ||||||
|     CONF_MIN_VERSION, |     CONF_MIN_VERSION, | ||||||
| @@ -32,7 +38,13 @@ from esphome.const import ( | |||||||
|     __version__ as ESPHOME_VERSION, |     __version__ as ESPHOME_VERSION, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
| from esphome.helpers import copy_file_if_changed, get_str_env, walk_files | from esphome.helpers import ( | ||||||
|  |     copy_file_if_changed, | ||||||
|  |     fnv1a_32bit_hash, | ||||||
|  |     get_str_env, | ||||||
|  |     walk_files, | ||||||
|  | ) | ||||||
|  | from esphome.types import ConfigType | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| @@ -48,7 +60,8 @@ LoopTrigger = cg.esphome_ns.class_( | |||||||
| ProjectUpdateTrigger = cg.esphome_ns.class_( | ProjectUpdateTrigger = cg.esphome_ns.class_( | ||||||
|     "ProjectUpdateTrigger", cg.Component, automation.Trigger.template(cg.std_string) |     "ProjectUpdateTrigger", cg.Component, automation.Trigger.template(cg.std_string) | ||||||
| ) | ) | ||||||
|  | Device = cg.esphome_ns.class_("Device") | ||||||
|  | Area = cg.esphome_ns.class_("Area") | ||||||
|  |  | ||||||
| VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} | VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} | ||||||
|  |  | ||||||
| @@ -71,6 +84,56 @@ def validate_hostname(config): | |||||||
|     return config |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_ids_and_references(config: ConfigType) -> ConfigType: | ||||||
|  |     """Validate that there are no hash collisions between IDs and that area_id references are valid. | ||||||
|  |  | ||||||
|  |     This validation is critical because we use 32-bit hashes for performance on microcontrollers. | ||||||
|  |     By detecting collisions at compile time, we prevent any runtime issues while maintaining | ||||||
|  |     optimal performance on 32-bit platforms. In practice, with typical deployments having only | ||||||
|  |     a handful of areas and devices, hash collisions are virtually impossible. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     # Helper to check hash collisions | ||||||
|  |     def check_hash_collision( | ||||||
|  |         id_obj: core.ID, | ||||||
|  |         hash_dict: dict[int, str], | ||||||
|  |         item_type: str, | ||||||
|  |         path: list[str | int], | ||||||
|  |     ) -> None: | ||||||
|  |         hash_val: int = fnv1a_32bit_hash(id_obj.id) | ||||||
|  |         if hash_val in hash_dict and hash_dict[hash_val] != id_obj.id: | ||||||
|  |             raise cv.Invalid( | ||||||
|  |                 f"{item_type} ID '{id_obj.id}' with hash {hash_val} collides with " | ||||||
|  |                 f"existing {item_type.lower()} ID '{hash_dict[hash_val]}'", | ||||||
|  |                 path=path, | ||||||
|  |             ) | ||||||
|  |         hash_dict[hash_val] = id_obj.id | ||||||
|  |  | ||||||
|  |     # Collect all areas | ||||||
|  |     all_areas: list[dict[str, str | core.ID]] = [] | ||||||
|  |     if CONF_AREA in config: | ||||||
|  |         all_areas.append(config[CONF_AREA]) | ||||||
|  |     all_areas.extend(config[CONF_AREAS]) | ||||||
|  |  | ||||||
|  |     # Validate area hash collisions and collect IDs | ||||||
|  |     area_hashes: dict[int, str] = {} | ||||||
|  |     area_ids: set[str] = set() | ||||||
|  |     for area in all_areas: | ||||||
|  |         area_id: core.ID = area[CONF_ID] | ||||||
|  |         check_hash_collision(area_id, area_hashes, "Area", [CONF_AREAS, area_id.id]) | ||||||
|  |         area_ids.add(area_id.id) | ||||||
|  |  | ||||||
|  |     # Validate device hash collisions and area references | ||||||
|  |     device_hashes: dict[int, str] = {} | ||||||
|  |     for device in config[CONF_DEVICES]: | ||||||
|  |         device_id: core.ID = device[CONF_ID] | ||||||
|  |         check_hash_collision( | ||||||
|  |             device_id, device_hashes, "Device", [CONF_DEVICES, device_id.id] | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
| def valid_include(value): | def valid_include(value): | ||||||
|     # Look for "<...>" includes |     # Look for "<...>" includes | ||||||
|     if value.startswith("<") and value.endswith(">"): |     if value.startswith("<") and value.endswith(">"): | ||||||
| @@ -111,13 +174,32 @@ if "ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT" in os.environ: | |||||||
| else: | else: | ||||||
|     _compile_process_limit_default = cv.UNDEFINED |     _compile_process_limit_default = cv.UNDEFINED | ||||||
|  |  | ||||||
|  | AREA_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_ID): cv.declare_id(Area), | ||||||
|  |         cv.Required(CONF_NAME): cv.string, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | DEVICE_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_ID): cv.declare_id(Device), | ||||||
|  |         cv.Required(CONF_NAME): cv.string, | ||||||
|  |         cv.Optional(CONF_AREA_ID): cv.use_id(Area), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_area_config(config: dict | str) -> dict[str, str | core.ID]: | ||||||
|  |     return cv.maybe_simple_value(AREA_SCHEMA, key=CONF_NAME)(config) | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
|             cv.Required(CONF_NAME): cv.valid_name, |             cv.Required(CONF_NAME): cv.valid_name, | ||||||
|             cv.Optional(CONF_FRIENDLY_NAME, ""): cv.string, |             cv.Optional(CONF_FRIENDLY_NAME, ""): cv.string, | ||||||
|             cv.Optional(CONF_AREA, ""): cv.string, |             cv.Optional(CONF_AREA): validate_area_config, | ||||||
|             cv.Optional(CONF_COMMENT): cv.string, |             cv.Optional(CONF_COMMENT): cv.string, | ||||||
|             cv.Required(CONF_BUILD_PATH): cv.string, |             cv.Required(CONF_BUILD_PATH): cv.string, | ||||||
|             cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( |             cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( | ||||||
| @@ -167,11 +249,17 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             cv.Optional( |             cv.Optional( | ||||||
|                 CONF_COMPILE_PROCESS_LIMIT, default=_compile_process_limit_default |                 CONF_COMPILE_PROCESS_LIMIT, default=_compile_process_limit_default | ||||||
|             ): cv.int_range(min=1, max=get_usable_cpu_count()), |             ): cv.int_range(min=1, max=get_usable_cpu_count()), | ||||||
|  |             cv.Optional(CONF_AREAS, default=[]): cv.ensure_list(AREA_SCHEMA), | ||||||
|  |             cv.Optional(CONF_DEVICES, default=[]): cv.ensure_list(DEVICE_SCHEMA), | ||||||
|         } |         } | ||||||
|     ), |     ), | ||||||
|     validate_hostname, |     validate_hostname, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FINAL_VALIDATE_SCHEMA = cv.All(validate_ids_and_references) | ||||||
|  |  | ||||||
|  |  | ||||||
| PRELOAD_CONFIG_SCHEMA = cv.Schema( | PRELOAD_CONFIG_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.Required(CONF_NAME): cv.valid_name, |         cv.Required(CONF_NAME): cv.valid_name, | ||||||
| @@ -336,7 +424,7 @@ async def _add_platform_reserves() -> None: | |||||||
|  |  | ||||||
|  |  | ||||||
| @coroutine_with_priority(100.0) | @coroutine_with_priority(100.0) | ||||||
| async def to_code(config): | async def to_code(config: ConfigType) -> None: | ||||||
|     cg.add_global(cg.global_ns.namespace("esphome").using) |     cg.add_global(cg.global_ns.namespace("esphome").using) | ||||||
|     # These can be used by user lambdas, put them to default scope |     # These can be used by user lambdas, put them to default scope | ||||||
|     cg.add_global(cg.RawExpression("using std::isnan")) |     cg.add_global(cg.RawExpression("using std::isnan")) | ||||||
| @@ -347,7 +435,6 @@ async def to_code(config): | |||||||
|         cg.App.pre_setup( |         cg.App.pre_setup( | ||||||
|             config[CONF_NAME], |             config[CONF_NAME], | ||||||
|             config[CONF_FRIENDLY_NAME], |             config[CONF_FRIENDLY_NAME], | ||||||
|             config[CONF_AREA], |  | ||||||
|             config.get(CONF_COMMENT, ""), |             config.get(CONF_COMMENT, ""), | ||||||
|             cg.RawExpression('__DATE__ ", " __TIME__'), |             cg.RawExpression('__DATE__ ", " __TIME__'), | ||||||
|             config[CONF_NAME_ADD_MAC_SUFFIX], |             config[CONF_NAME_ADD_MAC_SUFFIX], | ||||||
| @@ -417,3 +504,50 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     if config[CONF_PLATFORMIO_OPTIONS]: |     if config[CONF_PLATFORMIO_OPTIONS]: | ||||||
|         CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) |         CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS]) | ||||||
|  |  | ||||||
|  |     # Process areas | ||||||
|  |     all_areas: list[dict[str, str | core.ID]] = [] | ||||||
|  |     if CONF_AREA in config: | ||||||
|  |         all_areas.append(config[CONF_AREA]) | ||||||
|  |     all_areas.extend(config[CONF_AREAS]) | ||||||
|  |  | ||||||
|  |     if all_areas: | ||||||
|  |         cg.add(cg.RawStatement(f"App.reserve_area({len(all_areas)});")) | ||||||
|  |         cg.add_define("USE_AREAS") | ||||||
|  |  | ||||||
|  |         for area_conf in all_areas: | ||||||
|  |             area_id: core.ID = area_conf[CONF_ID] | ||||||
|  |             area_id_hash: int = fnv1a_32bit_hash(area_id.id) | ||||||
|  |             area_name: str = area_conf[CONF_NAME] | ||||||
|  |  | ||||||
|  |             area_var = cg.new_Pvariable(area_id) | ||||||
|  |             cg.add(area_var.set_area_id(area_id_hash)) | ||||||
|  |             cg.add(area_var.set_name(area_name)) | ||||||
|  |             cg.add(cg.App.register_area(area_var)) | ||||||
|  |  | ||||||
|  |     # Process devices | ||||||
|  |     devices: list[dict[str, str | core.ID]] = config[CONF_DEVICES] | ||||||
|  |     if not devices: | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     # Reserve space for devices | ||||||
|  |     cg.add(cg.RawStatement(f"App.reserve_device({len(devices)});")) | ||||||
|  |     cg.add_define("USE_DEVICES") | ||||||
|  |  | ||||||
|  |     # Process each device | ||||||
|  |     for dev_conf in devices: | ||||||
|  |         device_id: core.ID = dev_conf[CONF_ID] | ||||||
|  |         device_id_hash = fnv1a_32bit_hash(device_id.id) | ||||||
|  |         device_name: str = dev_conf[CONF_NAME] | ||||||
|  |  | ||||||
|  |         dev = cg.new_Pvariable(device_id) | ||||||
|  |         cg.add(dev.set_device_id(device_id_hash)) | ||||||
|  |         cg.add(dev.set_name(device_name)) | ||||||
|  |  | ||||||
|  |         # Set area if specified | ||||||
|  |         if CONF_AREA_ID in dev_conf: | ||||||
|  |             area_id: core.ID = dev_conf[CONF_AREA_ID] | ||||||
|  |             area_id_hash = fnv1a_32bit_hash(area_id.id) | ||||||
|  |             cg.add(dev.set_area_id(area_id_hash)) | ||||||
|  |  | ||||||
|  |         cg.add(cg.App.register_device(dev)) | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ | |||||||
|  |  | ||||||
| // Feature flags | // Feature flags | ||||||
| #define USE_ALARM_CONTROL_PANEL | #define USE_ALARM_CONTROL_PANEL | ||||||
|  | #define USE_AREAS | ||||||
| #define USE_BINARY_SENSOR | #define USE_BINARY_SENSOR | ||||||
| #define USE_BUTTON | #define USE_BUTTON | ||||||
| #define USE_CLIMATE | #define USE_CLIMATE | ||||||
| @@ -29,6 +30,7 @@ | |||||||
| #define USE_DATETIME_DATETIME | #define USE_DATETIME_DATETIME | ||||||
| #define USE_DATETIME_TIME | #define USE_DATETIME_TIME | ||||||
| #define USE_DEEP_SLEEP | #define USE_DEEP_SLEEP | ||||||
|  | #define USE_DEVICES | ||||||
| #define USE_DISPLAY | #define USE_DISPLAY | ||||||
| #define USE_ESP32_IMPROV_STATE_CALLBACK | #define USE_ESP32_IMPROV_STATE_CALLBACK | ||||||
| #define USE_EVENT | #define USE_EVENT | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								esphome/core/device.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								esphome/core/device.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  |  | ||||||
|  | class Device { | ||||||
|  |  public: | ||||||
|  |   void set_device_id(uint32_t device_id) { this->device_id_ = device_id; } | ||||||
|  |   uint32_t get_device_id() { return this->device_id_; } | ||||||
|  |   void set_name(const char *name) { this->name_ = name; } | ||||||
|  |   const char *get_name() { return this->name_; } | ||||||
|  |   void set_area_id(uint32_t area_id) { this->area_id_ = area_id; } | ||||||
|  |   uint32_t get_area_id() { return this->area_id_; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint32_t device_id_{}; | ||||||
|  |   uint32_t area_id_{}; | ||||||
|  |   const char *name_ = ""; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace esphome | ||||||
| @@ -11,7 +11,14 @@ const StringRef &EntityBase::get_name() const { return this->name_; } | |||||||
| void EntityBase::set_name(const char *name) { | void EntityBase::set_name(const char *name) { | ||||||
|   this->name_ = StringRef(name); |   this->name_ = StringRef(name); | ||||||
|   if (this->name_.empty()) { |   if (this->name_.empty()) { | ||||||
|     this->name_ = StringRef(App.get_friendly_name()); | #ifdef USE_DEVICES | ||||||
|  |     if (this->device_ != nullptr) { | ||||||
|  |       this->name_ = StringRef(this->device_->get_name()); | ||||||
|  |     } else | ||||||
|  | #endif | ||||||
|  |     { | ||||||
|  |       this->name_ = StringRef(App.get_friendly_name()); | ||||||
|  |     } | ||||||
|     this->flags_.has_own_name = false; |     this->flags_.has_own_name = false; | ||||||
|   } else { |   } else { | ||||||
|     this->flags_.has_own_name = true; |     this->flags_.has_own_name = true; | ||||||
| @@ -47,19 +54,7 @@ void EntityBase::set_object_id(const char *object_id) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // Calculate Object ID Hash from Entity Name | // Calculate Object ID Hash from Entity Name | ||||||
| void EntityBase::calc_object_id_() { | void EntityBase::calc_object_id_() { this->object_id_hash_ = fnv1_hash(this->get_object_id()); } | ||||||
|   // Check if `App.get_friendly_name()` is constant or dynamic. |  | ||||||
|   if (!this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled()) { |  | ||||||
|     // `App.get_friendly_name()` is dynamic. |  | ||||||
|     const auto object_id = str_sanitize(str_snake_case(App.get_friendly_name())); |  | ||||||
|     // FNV-1 hash |  | ||||||
|     this->object_id_hash_ = fnv1_hash(object_id); |  | ||||||
|   } else { |  | ||||||
|     // `App.get_friendly_name()` is constant. |  | ||||||
|     // FNV-1 hash |  | ||||||
|     this->object_id_hash_ = fnv1_hash(this->object_id_c_str_); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } | uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,10 @@ | |||||||
| #include "helpers.h" | #include "helpers.h" | ||||||
| #include "log.h" | #include "log.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_DEVICES | ||||||
|  | #include "device.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
|  |  | ||||||
| enum EntityCategory : uint8_t { | enum EntityCategory : uint8_t { | ||||||
| @@ -51,6 +55,17 @@ class EntityBase { | |||||||
|   std::string get_icon() const; |   std::string get_icon() const; | ||||||
|   void set_icon(const char *icon); |   void set_icon(const char *icon); | ||||||
|  |  | ||||||
|  | #ifdef USE_DEVICES | ||||||
|  |   // Get/set this entity's device id | ||||||
|  |   uint32_t get_device_id() const { | ||||||
|  |     if (this->device_ == nullptr) { | ||||||
|  |       return 0;  // No device set, return 0 | ||||||
|  |     } | ||||||
|  |     return this->device_->get_device_id(); | ||||||
|  |   } | ||||||
|  |   void set_device(Device *device) { this->device_ = device; } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   // Check if this entity has state |   // Check if this entity has state | ||||||
|   bool has_state() const { return this->flags_.has_state; } |   bool has_state() const { return this->flags_.has_state; } | ||||||
|  |  | ||||||
| @@ -67,6 +82,9 @@ class EntityBase { | |||||||
|   const char *object_id_c_str_{nullptr}; |   const char *object_id_c_str_{nullptr}; | ||||||
|   const char *icon_c_str_{nullptr}; |   const char *icon_c_str_{nullptr}; | ||||||
|   uint32_t object_id_hash_{}; |   uint32_t object_id_hash_{}; | ||||||
|  | #ifdef USE_DEVICES | ||||||
|  |   Device *device_{}; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   // Bit-packed flags to save memory (1 byte instead of 5) |   // Bit-packed flags to save memory (1 byte instead of 5) | ||||||
|   struct EntityFlags { |   struct EntityFlags { | ||||||
|   | |||||||
| @@ -1,5 +1,116 @@ | |||||||
| from esphome.const import CONF_ID | from collections.abc import Callable | ||||||
|  | import logging | ||||||
|  |  | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_DEVICE_ID, | ||||||
|  |     CONF_DISABLED_BY_DEFAULT, | ||||||
|  |     CONF_ENTITY_CATEGORY, | ||||||
|  |     CONF_ICON, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_INTERNAL, | ||||||
|  |     CONF_NAME, | ||||||
|  | ) | ||||||
|  | from esphome.core import CORE, ID | ||||||
|  | from esphome.cpp_generator import MockObj, add, get_variable | ||||||
| import esphome.final_validate as fv | import esphome.final_validate as fv | ||||||
|  | from esphome.helpers import sanitize, snake_case | ||||||
|  | from esphome.types import ConfigType | ||||||
|  |  | ||||||
|  | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_base_entity_object_id( | ||||||
|  |     name: str, friendly_name: str | None, device_name: str | None = None | ||||||
|  | ) -> str: | ||||||
|  |     """Calculate the base object ID for an entity that will be set via set_object_id(). | ||||||
|  |  | ||||||
|  |     This function calculates what object_id_c_str_ should be set to in C++. | ||||||
|  |  | ||||||
|  |     The C++ EntityBase::get_object_id() (entity_base.cpp lines 38-49) works as: | ||||||
|  |     - If !has_own_name && is_name_add_mac_suffix_enabled(): | ||||||
|  |         return str_sanitize(str_snake_case(App.get_friendly_name()))  // Dynamic | ||||||
|  |     - Else: | ||||||
|  |         return object_id_c_str_ ?? ""  // What we set via set_object_id() | ||||||
|  |  | ||||||
|  |     Since we're calculating what to pass to set_object_id(), we always need to | ||||||
|  |     generate the object_id the same way, regardless of name_add_mac_suffix setting. | ||||||
|  |  | ||||||
|  |     Args: | ||||||
|  |         name: The entity name (empty string if no name) | ||||||
|  |         friendly_name: The friendly name from CORE.friendly_name | ||||||
|  |         device_name: The device name if entity is on a sub-device | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |         The base object ID to use for duplicate checking and to pass to set_object_id() | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     if name: | ||||||
|  |         # Entity has its own name (has_own_name will be true) | ||||||
|  |         base_str = name | ||||||
|  |     elif device_name: | ||||||
|  |         # Entity has empty name and is on a sub-device | ||||||
|  |         # C++ EntityBase::set_name() uses device->get_name() when device is set | ||||||
|  |         base_str = device_name | ||||||
|  |     elif friendly_name: | ||||||
|  |         # Entity has empty name (has_own_name will be false) | ||||||
|  |         # C++ uses App.get_friendly_name() which returns friendly_name or device name | ||||||
|  |         base_str = friendly_name | ||||||
|  |     else: | ||||||
|  |         # Fallback to device name | ||||||
|  |         base_str = CORE.name | ||||||
|  |  | ||||||
|  |     return sanitize(snake_case(base_str)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None: | ||||||
|  |     """Set up generic properties of an Entity. | ||||||
|  |  | ||||||
|  |     This function sets up the common entity properties like name, icon, | ||||||
|  |     entity category, etc. | ||||||
|  |  | ||||||
|  |     Args: | ||||||
|  |         var: The entity variable to set up | ||||||
|  |         config: Configuration dictionary containing entity settings | ||||||
|  |         platform: The platform name (e.g., "sensor", "binary_sensor") | ||||||
|  |     """ | ||||||
|  |     # Get device info | ||||||
|  |     device_name: str | None = None | ||||||
|  |     if CONF_DEVICE_ID in config: | ||||||
|  |         device_id_obj: ID = config[CONF_DEVICE_ID] | ||||||
|  |         device: MockObj = await get_variable(device_id_obj) | ||||||
|  |         add(var.set_device(device)) | ||||||
|  |         # Get device name for object ID calculation | ||||||
|  |         device_name = device_id_obj.id | ||||||
|  |  | ||||||
|  |     add(var.set_name(config[CONF_NAME])) | ||||||
|  |  | ||||||
|  |     # Calculate base object_id using the same logic as C++ | ||||||
|  |     # This must match the C++ behavior in esphome/core/entity_base.cpp | ||||||
|  |     base_object_id = get_base_entity_object_id( | ||||||
|  |         config[CONF_NAME], CORE.friendly_name, device_name | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     if not config[CONF_NAME]: | ||||||
|  |         _LOGGER.debug( | ||||||
|  |             "Entity has empty name, using '%s' as object_id base", base_object_id | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     # Set the object ID | ||||||
|  |     add(var.set_object_id(base_object_id)) | ||||||
|  |     _LOGGER.debug( | ||||||
|  |         "Setting object_id '%s' for entity '%s' on platform '%s'", | ||||||
|  |         base_object_id, | ||||||
|  |         config[CONF_NAME], | ||||||
|  |         platform, | ||||||
|  |     ) | ||||||
|  |     add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) | ||||||
|  |     if CONF_INTERNAL in config: | ||||||
|  |         add(var.set_internal(config[CONF_INTERNAL])) | ||||||
|  |     if CONF_ICON in config: | ||||||
|  |         add(var.set_icon(config[CONF_ICON])) | ||||||
|  |     if CONF_ENTITY_CATEGORY in config: | ||||||
|  |         add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) | ||||||
|  |  | ||||||
|  |  | ||||||
| def inherit_property_from(property_to_inherit, parent_id_property, transform=None): | def inherit_property_from(property_to_inherit, parent_id_property, transform=None): | ||||||
| @@ -54,3 +165,48 @@ def inherit_property_from(property_to_inherit, parent_id_property, transform=Non | |||||||
|         return config |         return config | ||||||
|  |  | ||||||
|     return inherit_property |     return inherit_property | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigType]: | ||||||
|  |     """Create a validator function to check for duplicate entity names. | ||||||
|  |  | ||||||
|  |     This validator is meant to be used with schema.add_extra() for entity base schemas. | ||||||
|  |  | ||||||
|  |     Args: | ||||||
|  |         platform: The platform name (e.g., "sensor", "binary_sensor") | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |         A validator function that checks for duplicate names | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def validator(config: ConfigType) -> ConfigType: | ||||||
|  |         if CONF_NAME not in config: | ||||||
|  |             # No name to validate | ||||||
|  |             return config | ||||||
|  |  | ||||||
|  |         # Get the entity name and device info | ||||||
|  |         entity_name = config[CONF_NAME] | ||||||
|  |         device_id = ""  # Empty string for main device | ||||||
|  |  | ||||||
|  |         if CONF_DEVICE_ID in config: | ||||||
|  |             device_id_obj = config[CONF_DEVICE_ID] | ||||||
|  |             # Use the device ID string directly for uniqueness | ||||||
|  |             device_id = device_id_obj.id | ||||||
|  |  | ||||||
|  |         # For duplicate detection, just use the sanitized name | ||||||
|  |         name_key = sanitize(snake_case(entity_name)) | ||||||
|  |  | ||||||
|  |         # Check for duplicates | ||||||
|  |         unique_key = (device_id, platform, name_key) | ||||||
|  |         if unique_key in CORE.unique_ids: | ||||||
|  |             device_prefix = f" on device '{device_id}'" if device_id else "" | ||||||
|  |             raise cv.Invalid( | ||||||
|  |                 f"Duplicate {platform} entity with name '{entity_name}' found{device_prefix}. " | ||||||
|  |                 f"Each entity on a device must have a unique name within its platform." | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         # Add to tracking set | ||||||
|  |         CORE.unique_ids.add(unique_key) | ||||||
|  |         return config | ||||||
|  |  | ||||||
|  |     return validator | ||||||
|   | |||||||
| @@ -356,6 +356,10 @@ size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) { | |||||||
|   return chars; |   return chars; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | std::string format_mac_address_pretty(const uint8_t *mac) { | ||||||
|  |   return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); | ||||||
|  | } | ||||||
|  |  | ||||||
| static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; } | static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; } | ||||||
| std::string format_hex(const uint8_t *data, size_t length) { | std::string format_hex(const uint8_t *data, size_t length) { | ||||||
|   std::string ret; |   std::string ret; | ||||||
| @@ -732,7 +736,7 @@ std::string get_mac_address() { | |||||||
| std::string get_mac_address_pretty() { | std::string get_mac_address_pretty() { | ||||||
|   uint8_t mac[6]; |   uint8_t mac[6]; | ||||||
|   get_mac_address_raw(mac); |   get_mac_address_raw(mac); | ||||||
|   return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); |   return format_mac_address_pretty(mac); | ||||||
| } | } | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|   | |||||||
| @@ -402,6 +402,8 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> optional< | |||||||
|   return parse_hex<T>(str.c_str(), str.length()); |   return parse_hex<T>(str.c_str(), str.length()); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// Format the six-byte array \p mac into a MAC address. | ||||||
|  | std::string format_mac_address_pretty(const uint8_t mac[6]); | ||||||
| /// Format the byte array \p data of length \p len in lowercased hex. | /// Format the byte array \p data of length \p len in lowercased hex. | ||||||
| std::string format_hex(const uint8_t *data, size_t length); | std::string format_hex(const uint8_t *data, size_t length); | ||||||
| /// Format the vector \p data in lowercased hex. | /// Format the vector \p data in lowercased hex. | ||||||
|   | |||||||
| @@ -1,11 +1,6 @@ | |||||||
| import logging | import logging | ||||||
|  |  | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_DISABLED_BY_DEFAULT, |  | ||||||
|     CONF_ENTITY_CATEGORY, |  | ||||||
|     CONF_ICON, |  | ||||||
|     CONF_INTERNAL, |  | ||||||
|     CONF_NAME, |  | ||||||
|     CONF_SAFE_MODE, |     CONF_SAFE_MODE, | ||||||
|     CONF_SETUP_PRIORITY, |     CONF_SETUP_PRIORITY, | ||||||
|     CONF_TYPE_ID, |     CONF_TYPE_ID, | ||||||
| @@ -16,7 +11,6 @@ from esphome.core import CORE, ID, coroutine | |||||||
| from esphome.coroutine import FakeAwaitable | from esphome.coroutine import FakeAwaitable | ||||||
| from esphome.cpp_generator import add, get_variable | from esphome.cpp_generator import add, get_variable | ||||||
| from esphome.cpp_types import App | from esphome.cpp_types import App | ||||||
| from esphome.helpers import sanitize, snake_case |  | ||||||
| from esphome.types import ConfigFragmentType, ConfigType | from esphome.types import ConfigFragmentType, ConfigType | ||||||
| from esphome.util import Registry, RegistryEntry | from esphome.util import Registry, RegistryEntry | ||||||
|  |  | ||||||
| @@ -96,22 +90,6 @@ async def register_parented(var, value): | |||||||
|     add(var.set_parent(paren)) |     add(var.set_parent(paren)) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_entity(var, config): |  | ||||||
|     """Set up generic properties of an Entity""" |  | ||||||
|     add(var.set_name(config[CONF_NAME])) |  | ||||||
|     if not config[CONF_NAME]: |  | ||||||
|         add(var.set_object_id(sanitize(snake_case(CORE.friendly_name)))) |  | ||||||
|     else: |  | ||||||
|         add(var.set_object_id(sanitize(snake_case(config[CONF_NAME])))) |  | ||||||
|     add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT])) |  | ||||||
|     if CONF_INTERNAL in config: |  | ||||||
|         add(var.set_internal(config[CONF_INTERNAL])) |  | ||||||
|     if CONF_ICON in config: |  | ||||||
|         add(var.set_icon(config[CONF_ICON])) |  | ||||||
|     if CONF_ENTITY_CATEGORY in config: |  | ||||||
|         add(var.set_entity_category(config[CONF_ENTITY_CATEGORY])) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def extract_registry_entry_config( | def extract_registry_entry_config( | ||||||
|     registry: Registry, |     registry: Registry, | ||||||
|     full_config: ConfigType, |     full_config: ConfigType, | ||||||
|   | |||||||
| @@ -1,25 +1,9 @@ | |||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
|  |  | ||||||
| import unicodedata | from esphome.helpers import slugify | ||||||
|  |  | ||||||
| from esphome.const import ALLOWED_NAME_CHARS |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def strip_accents(value): | def friendly_name_slugify(value: str) -> str: | ||||||
|     return "".join( |     """Convert a friendly name to a slug with dashes instead of underscores.""" | ||||||
|         c |     # First use the standard slugify, then convert underscores to dashes | ||||||
|         for c in unicodedata.normalize("NFD", str(value)) |     return slugify(value).replace("_", "-") | ||||||
|         if unicodedata.category(c) != "Mn" |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def friendly_name_slugify(value): |  | ||||||
|     value = ( |  | ||||||
|         strip_accents(value) |  | ||||||
|         .lower() |  | ||||||
|         .replace(" ", "-") |  | ||||||
|         .replace("_", "-") |  | ||||||
|         .replace("--", "-") |  | ||||||
|         .strip("-") |  | ||||||
|     ) |  | ||||||
|     return "".join(c for c in value if c in ALLOWED_NAME_CHARS) |  | ||||||
|   | |||||||
| @@ -29,6 +29,53 @@ def ensure_unique_string(preferred_string, current_strings): | |||||||
|     return test_string |     return test_string | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def fnv1a_32bit_hash(string: str) -> int: | ||||||
|  |     """FNV-1a 32-bit hash function. | ||||||
|  |  | ||||||
|  |     Note: This uses 32-bit hash instead of 64-bit for several reasons: | ||||||
|  |     1. ESPHome targets 32-bit microcontrollers with limited RAM (often <320KB) | ||||||
|  |     2. Using 64-bit hashes would double the RAM usage for storing IDs | ||||||
|  |     3. 64-bit operations are slower on 32-bit processors | ||||||
|  |  | ||||||
|  |     While there's a ~50% collision probability at ~77,000 unique IDs, | ||||||
|  |     ESPHome validates for collisions at compile time, preventing any | ||||||
|  |     runtime issues. In practice, most ESPHome installations only have | ||||||
|  |     a handful of area_ids and device_ids (typically <10 areas and <100 | ||||||
|  |     devices), making collisions virtually impossible. | ||||||
|  |     """ | ||||||
|  |     hash_value = 2166136261 | ||||||
|  |     for char in string: | ||||||
|  |         hash_value ^= ord(char) | ||||||
|  |         hash_value = (hash_value * 16777619) & 0xFFFFFFFF | ||||||
|  |     return hash_value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def strip_accents(value: str) -> str: | ||||||
|  |     """Remove accents from a string.""" | ||||||
|  |     import unicodedata | ||||||
|  |  | ||||||
|  |     return "".join( | ||||||
|  |         c | ||||||
|  |         for c in unicodedata.normalize("NFD", str(value)) | ||||||
|  |         if unicodedata.category(c) != "Mn" | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def slugify(value: str) -> str: | ||||||
|  |     """Convert a string to a valid C++ identifier slug.""" | ||||||
|  |     from esphome.const import ALLOWED_NAME_CHARS | ||||||
|  |  | ||||||
|  |     value = ( | ||||||
|  |         strip_accents(value) | ||||||
|  |         .lower() | ||||||
|  |         .replace(" ", "_") | ||||||
|  |         .replace("-", "_") | ||||||
|  |         .replace("__", "_") | ||||||
|  |         .strip("_") | ||||||
|  |     ) | ||||||
|  |     return "".join(c for c in value if c in ALLOWED_NAME_CHARS) | ||||||
|  |  | ||||||
|  |  | ||||||
| def indent_all_but_first_and_last(text, padding="  "): | def indent_all_but_first_and_last(text, padding="  "): | ||||||
|     lines = text.splitlines(True) |     lines = text.splitlines(True) | ||||||
|     if len(lines) <= 2: |     if len(lines) <= 2: | ||||||
|   | |||||||
| @@ -12,12 +12,12 @@ sensor: | |||||||
|     frequency: 60Hz |     frequency: 60Hz | ||||||
|     phase_a: |     phase_a: | ||||||
|       name: Channel A |       name: Channel A | ||||||
|       voltage: Voltage |       voltage: Channel A Voltage | ||||||
|       current: Current |       current: Channel A Current | ||||||
|       active_power: Active Power |       active_power: Channel A Active Power | ||||||
|       power_factor: Power Factor |       power_factor: Channel A Power Factor | ||||||
|       forward_active_energy: Forward Active Energy |       forward_active_energy: Channel A Forward Active Energy | ||||||
|       reverse_active_energy: Reverse Active Energy |       reverse_active_energy: Channel A Reverse Active Energy | ||||||
|       calibration: |       calibration: | ||||||
|         current_gain: 3116628 |         current_gain: 3116628 | ||||||
|         voltage_gain: -757178 |         voltage_gain: -757178 | ||||||
| @@ -25,12 +25,12 @@ sensor: | |||||||
|         phase_angle: 188 |         phase_angle: 188 | ||||||
|     phase_b: |     phase_b: | ||||||
|       name: Channel B |       name: Channel B | ||||||
|       voltage: Voltage |       voltage: Channel B Voltage | ||||||
|       current: Current |       current: Channel B Current | ||||||
|       active_power: Active Power |       active_power: Channel B Active Power | ||||||
|       power_factor: Power Factor |       power_factor: Channel B Power Factor | ||||||
|       forward_active_energy: Forward Active Energy |       forward_active_energy: Channel B Forward Active Energy | ||||||
|       reverse_active_energy: Reverse Active Energy |       reverse_active_energy: Channel B Reverse Active Energy | ||||||
|       calibration: |       calibration: | ||||||
|         current_gain: 3133655 |         current_gain: 3133655 | ||||||
|         voltage_gain: -755235 |         voltage_gain: -755235 | ||||||
| @@ -38,12 +38,12 @@ sensor: | |||||||
|         phase_angle: 188 |         phase_angle: 188 | ||||||
|     phase_c: |     phase_c: | ||||||
|       name: Channel C |       name: Channel C | ||||||
|       voltage: Voltage |       voltage: Channel C Voltage | ||||||
|       current: Current |       current: Channel C Current | ||||||
|       active_power: Active Power |       active_power: Channel C Active Power | ||||||
|       power_factor: Power Factor |       power_factor: Channel C Power Factor | ||||||
|       forward_active_energy: Forward Active Energy |       forward_active_energy: Channel C Forward Active Energy | ||||||
|       reverse_active_energy: Reverse Active Energy |       reverse_active_energy: Channel C Reverse Active Energy | ||||||
|       calibration: |       calibration: | ||||||
|         current_gain: 3111158 |         current_gain: 3111158 | ||||||
|         voltage_gain: -743813 |         voltage_gain: -743813 | ||||||
| @@ -51,6 +51,6 @@ sensor: | |||||||
|         phase_angle: 180 |         phase_angle: 180 | ||||||
|     neutral: |     neutral: | ||||||
|       name: Neutral |       name: Neutral | ||||||
|       current: Current |       current: Neutral Current | ||||||
|       calibration: |       calibration: | ||||||
|         current_gain: 3189 |         current_gain: 3189 | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ alarm_control_panel: | |||||||
|             ESP_LOGD("TEST", "State change %s", LOG_STR_ARG(alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()))); |             ESP_LOGD("TEST", "State change %s", LOG_STR_ARG(alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state()))); | ||||||
|   - platform: template |   - platform: template | ||||||
|     id: alarmcontrolpanel2 |     id: alarmcontrolpanel2 | ||||||
|     name: Alarm Panel |     name: Alarm Panel 2 | ||||||
|     codes: |     codes: | ||||||
|       - "1234" |       - "1234" | ||||||
|     requires_code_to_arm: true |     requires_code_to_arm: true | ||||||
|   | |||||||
| @@ -4,6 +4,31 @@ binary_sensor: | |||||||
|     id: some_binary_sensor |     id: some_binary_sensor | ||||||
|     name: "Random binary" |     name: "Random binary" | ||||||
|     lambda: return (random_uint32() & 1) == 0; |     lambda: return (random_uint32() & 1) == 0; | ||||||
|  |     filters: | ||||||
|  |       - invert: | ||||||
|  |       - delayed_on: 100ms | ||||||
|  |       - delayed_off: 100ms | ||||||
|  |       # Templated, delays for 1s (1000ms) only if a reed switch is active | ||||||
|  |       - delayed_on_off: !lambda "return 1000;" | ||||||
|  |       - delayed_on_off: | ||||||
|  |           time_on: 10s | ||||||
|  |           time_off: !lambda "return 1000;" | ||||||
|  |       - autorepeat: | ||||||
|  |           - delay: 1s | ||||||
|  |             time_off: 100ms | ||||||
|  |             time_on: 900ms | ||||||
|  |           - delay: 5s | ||||||
|  |             time_off: 100ms | ||||||
|  |             time_on: 400ms | ||||||
|  |       - lambda: |- | ||||||
|  |           if (id(some_binary_sensor).state) { | ||||||
|  |             return x; | ||||||
|  |           } else { | ||||||
|  |             return {}; | ||||||
|  |           } | ||||||
|  |       - settle: 100ms | ||||||
|  |       - timeout: 10s | ||||||
|  |  | ||||||
|     on_state_change: |     on_state_change: | ||||||
|       then: |       then: | ||||||
|         - logger.log: |         - logger.log: | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ binary_sensor: | |||||||
|  |  | ||||||
| sensor: | sensor: | ||||||
|   - platform: binary_sensor_map |   - platform: binary_sensor_map | ||||||
|     name: Binary Sensor Map |     name: Binary Sensor Map Group | ||||||
|     type: group |     type: group | ||||||
|     channels: |     channels: | ||||||
|       - binary_sensor: bin1 |       - binary_sensor: bin1 | ||||||
| @@ -36,7 +36,7 @@ sensor: | |||||||
|       - binary_sensor: bin3 |       - binary_sensor: bin3 | ||||||
|         value: 100.0 |         value: 100.0 | ||||||
|   - platform: binary_sensor_map |   - platform: binary_sensor_map | ||||||
|     name: Binary Sensor Map |     name: Binary Sensor Map Sum | ||||||
|     type: sum |     type: sum | ||||||
|     channels: |     channels: | ||||||
|       - binary_sensor: bin1 |       - binary_sensor: bin1 | ||||||
| @@ -46,7 +46,7 @@ sensor: | |||||||
|       - binary_sensor: bin3 |       - binary_sensor: bin3 | ||||||
|         value: 100.0 |         value: 100.0 | ||||||
|   - platform: binary_sensor_map |   - platform: binary_sensor_map | ||||||
|     name: Binary Sensor Map |     name: Binary Sensor Map Bayesian | ||||||
|     type: bayesian |     type: bayesian | ||||||
|     prior: 0.4 |     prior: 0.4 | ||||||
|     observations: |     observations: | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ one_wire: | |||||||
| sensor: | sensor: | ||||||
|   - platform: dallas_temp |   - platform: dallas_temp | ||||||
|     address: 0x1C0000031EDD2A28 |     address: 0x1C0000031EDD2A28 | ||||||
|     name: Dallas Temperature |     name: Dallas Temperature 1 | ||||||
|     resolution: 9 |     resolution: 9 | ||||||
|   - platform: dallas_temp |   - platform: dallas_temp | ||||||
|     name: Dallas Temperature |     name: Dallas Temperature 2 | ||||||
|   | |||||||
| @@ -2,7 +2,9 @@ esphome: | |||||||
|   debug_scheduler: true |   debug_scheduler: true | ||||||
|   platformio_options: |   platformio_options: | ||||||
|     board_build.flash_mode: dio |     board_build.flash_mode: dio | ||||||
|   area: testing |   area: | ||||||
|  |     id: testing_area | ||||||
|  |     name: Testing Area | ||||||
|   on_boot: |   on_boot: | ||||||
|     logger.log: on_boot |     logger.log: on_boot | ||||||
|   on_shutdown: |   on_shutdown: | ||||||
| @@ -17,4 +19,20 @@ esphome: | |||||||
|     version: "1.1" |     version: "1.1" | ||||||
|     on_update: |     on_update: | ||||||
|       logger.log: on_update |       logger.log: on_update | ||||||
|  |   areas: | ||||||
|  |     - id: another_area | ||||||
|  |       name: Another area | ||||||
|  |   devices: | ||||||
|  |     - id: other_device | ||||||
|  |       name: Another device | ||||||
|  |       area_id: another_area | ||||||
|  |     - id: test_device | ||||||
|  |       name: Test device in main area | ||||||
|  |       area_id: testing_area  # Reference the main area (not in areas) | ||||||
|  |     - id: no_area_device | ||||||
|  |       name: Device without area  # This device has no area_id | ||||||
|  |  | ||||||
|  | binary_sensor: | ||||||
|  |   - platform: template | ||||||
|  |     name: Other device sensor | ||||||
|  |     device_id: other_device | ||||||
|   | |||||||
| @@ -7,20 +7,20 @@ climate: | |||||||
|     protocol: mitsubishi_heavy_zm |     protocol: mitsubishi_heavy_zm | ||||||
|     horizontal_default: left |     horizontal_default: left | ||||||
|     vertical_default: up |     vertical_default: up | ||||||
|     name: HeatpumpIR Climate |     name: HeatpumpIR Climate Mitsubishi | ||||||
|     min_temperature: 18 |     min_temperature: 18 | ||||||
|     max_temperature: 30 |     max_temperature: 30 | ||||||
|   - platform: heatpumpir |   - platform: heatpumpir | ||||||
|     protocol: daikin |     protocol: daikin | ||||||
|     horizontal_default: mleft |     horizontal_default: mleft | ||||||
|     vertical_default: mup |     vertical_default: mup | ||||||
|     name: HeatpumpIR Climate |     name: HeatpumpIR Climate Daikin | ||||||
|     min_temperature: 18 |     min_temperature: 18 | ||||||
|     max_temperature: 30 |     max_temperature: 30 | ||||||
|   - platform: heatpumpir |   - platform: heatpumpir | ||||||
|     protocol: panasonic_altdke |     protocol: panasonic_altdke | ||||||
|     horizontal_default: mright |     horizontal_default: mright | ||||||
|     vertical_default: mdown |     vertical_default: mdown | ||||||
|     name: HeatpumpIR Climate |     name: HeatpumpIR Climate Panasonic | ||||||
|     min_temperature: 18 |     min_temperature: 18 | ||||||
|     max_temperature: 30 |     max_temperature: 30 | ||||||
|   | |||||||
| @@ -114,7 +114,7 @@ light: | |||||||
|     warm_white_color_temperature: 500 mireds |     warm_white_color_temperature: 500 mireds | ||||||
|   - platform: rgb |   - platform: rgb | ||||||
|     id: test_rgb_light_initial_state |     id: test_rgb_light_initial_state | ||||||
|     name: RGB Light |     name: RGB Light Initial State | ||||||
|     red: test_ledc_1 |     red: test_ledc_1 | ||||||
|     green: test_ledc_2 |     green: test_ledc_2 | ||||||
|     blue: test_ledc_3 |     blue: test_ledc_3 | ||||||
|   | |||||||
| @@ -6,13 +6,13 @@ i2c: | |||||||
| sensor: | sensor: | ||||||
|   - platform: ltr390 |   - platform: ltr390 | ||||||
|     uv: |     uv: | ||||||
|       name: LTR390 UV |       name: LTR390 UV 1 | ||||||
|     uv_index: |     uv_index: | ||||||
|       name: LTR390 UVI |       name: LTR390 UVI 1 | ||||||
|     light: |     light: | ||||||
|       name: LTR390 Light |       name: LTR390 Light 1 | ||||||
|     ambient_light: |     ambient_light: | ||||||
|       name: LTR390 ALS |       name: LTR390 ALS 1 | ||||||
|     gain: X3 |     gain: X3 | ||||||
|     resolution: 18 |     resolution: 18 | ||||||
|     window_correction_factor: 1.0 |     window_correction_factor: 1.0 | ||||||
| @@ -20,13 +20,13 @@ sensor: | |||||||
|     update_interval: 60s |     update_interval: 60s | ||||||
|   - platform: ltr390 |   - platform: ltr390 | ||||||
|     uv: |     uv: | ||||||
|       name: LTR390 UV |       name: LTR390 UV 2 | ||||||
|     uv_index: |     uv_index: | ||||||
|       name: LTR390 UVI |       name: LTR390 UVI 2 | ||||||
|     light: |     light: | ||||||
|       name: LTR390 Light |       name: LTR390 Light 2 | ||||||
|     ambient_light: |     ambient_light: | ||||||
|       name: LTR390 ALS |       name: LTR390 ALS 2 | ||||||
|     gain: |     gain: | ||||||
|       ambient_light: X9 |       ambient_light: X9 | ||||||
|       uv: X3 |       uv: X3 | ||||||
|   | |||||||
| @@ -24,33 +24,33 @@ sensor: | |||||||
|     widget: lv_arc |     widget: lv_arc | ||||||
|   - platform: lvgl |   - platform: lvgl | ||||||
|     widget: slider_id |     widget: slider_id | ||||||
|     name: LVGL Slider |     name: LVGL Slider Sensor | ||||||
|   - platform: lvgl |   - platform: lvgl | ||||||
|     widget: bar_id |     widget: bar_id | ||||||
|     id: lvgl_bar_sensor |     id: lvgl_bar_sensor | ||||||
|     name: LVGL Bar |     name: LVGL Bar Sensor | ||||||
|   - platform: lvgl |   - platform: lvgl | ||||||
|     widget: spinbox_id |     widget: spinbox_id | ||||||
|     name: LVGL Spinbox |     name: LVGL Spinbox Sensor | ||||||
|  |  | ||||||
| number: | number: | ||||||
|   - platform: lvgl |   - platform: lvgl | ||||||
|     widget: slider_id |     widget: slider_id | ||||||
|     name: LVGL Slider |     name: LVGL Slider Number | ||||||
|     update_on_release: true |     update_on_release: true | ||||||
|     restore_value: true |     restore_value: true | ||||||
|   - platform: lvgl |   - platform: lvgl | ||||||
|     widget: lv_arc |     widget: lv_arc | ||||||
|     id: lvgl_arc_number |     id: lvgl_arc_number | ||||||
|     name: LVGL Arc |     name: LVGL Arc Number | ||||||
|   - platform: lvgl |   - platform: lvgl | ||||||
|     widget: bar_id |     widget: bar_id | ||||||
|     id: lvgl_bar_number |     id: lvgl_bar_number | ||||||
|     name: LVGL Bar |     name: LVGL Bar Number | ||||||
|   - platform: lvgl |   - platform: lvgl | ||||||
|     widget: spinbox_id |     widget: spinbox_id | ||||||
|     id: lvgl_spinbox_number |     id: lvgl_spinbox_number | ||||||
|     name: LVGL Spinbox |     name: LVGL Spinbox Number | ||||||
|  |  | ||||||
| light: | light: | ||||||
|   - platform: lvgl |   - platform: lvgl | ||||||
|   | |||||||
| @@ -646,7 +646,9 @@ lvgl: | |||||||
|             on_click: |             on_click: | ||||||
|               lvgl.qrcode.update: |               lvgl.qrcode.update: | ||||||
|                 id: lv_qr |                 id: lv_qr | ||||||
|                 text: homeassistant.io |                 text: | ||||||
|  |                   format: "A string with a number %d" | ||||||
|  |                   args: ['(int)(random_uint32() % 1000)'] | ||||||
|  |  | ||||||
|         - slider: |         - slider: | ||||||
|             min_value: 0 |             min_value: 0 | ||||||
|   | |||||||
| @@ -170,4 +170,4 @@ switch: | |||||||
|     otc_active: |     otc_active: | ||||||
|       name: "Boiler Outside temperature compensation active" |       name: "Boiler Outside temperature compensation active" | ||||||
|     ch2_active: |     ch2_active: | ||||||
|       name: "Boiler Central Heating 2 active" |       name: "Boiler Central Heating 2 active status" | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ packages: | |||||||
|   - !include package.yaml |   - !include package.yaml | ||||||
|   - github://esphome/esphome/tests/components/template/common.yaml@dev |   - github://esphome/esphome/tests/components/template/common.yaml@dev | ||||||
|   - url: https://github.com/esphome/esphome |   - url: https://github.com/esphome/esphome | ||||||
|     file: tests/components/binary_sensor_map/common.yaml |     file: tests/components/absolute_humidity/common.yaml | ||||||
|     ref: dev |     ref: dev | ||||||
|     refresh: 1d |     refresh: 1d | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ packages: | |||||||
|   shorthand: github://esphome/esphome/tests/components/template/common.yaml@dev |   shorthand: github://esphome/esphome/tests/components/template/common.yaml@dev | ||||||
|   github: |   github: | ||||||
|     url: https://github.com/esphome/esphome |     url: https://github.com/esphome/esphome | ||||||
|     file: tests/components/binary_sensor_map/common.yaml |     file: tests/components/absolute_humidity/common.yaml | ||||||
|     ref: dev |     ref: dev | ||||||
|     refresh: 1d |     refresh: 1d | ||||||
|  |  | ||||||
|   | |||||||
| @@ -115,7 +115,7 @@ button: | |||||||
|         address: 0x00 |         address: 0x00 | ||||||
|         command: 0x0B |         command: 0x0B | ||||||
|   - platform: template |   - platform: template | ||||||
|     name: RC5 |     name: RC5 Raw | ||||||
|     on_press: |     on_press: | ||||||
|       remote_transmitter.transmit_raw: |       remote_transmitter.transmit_raw: | ||||||
|         code: [1000, -1000] |         code: [1000, -1000] | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ | |||||||
| using namespace esphome; | using namespace esphome; | ||||||
|  |  | ||||||
| void setup() { | void setup() { | ||||||
|   App.pre_setup("livingroom", "LivingRoom", "LivingRoomArea", "comment", __DATE__ ", " __TIME__, false); |   App.pre_setup("livingroom", "LivingRoom", "comment", __DATE__ ", " __TIME__, false); | ||||||
|   auto *log = new logger::Logger(115200, 512);  // NOLINT |   auto *log = new logger::Logger(115200, 512);  // NOLINT | ||||||
|   log->pre_setup(); |   log->pre_setup(); | ||||||
|   log->set_uart_selection(logger::UART_SELECTION_UART0); |   log->set_uart_selection(logger::UART_SELECTION_UART0); | ||||||
|   | |||||||
| @@ -203,6 +203,7 @@ async def compile_esphome( | |||||||
|         loop = asyncio.get_running_loop() |         loop = asyncio.get_running_loop() | ||||||
|  |  | ||||||
|         def _read_config_and_get_binary(): |         def _read_config_and_get_binary(): | ||||||
|  |             CORE.reset()  # Reset CORE state between test runs | ||||||
|             CORE.config_path = str(config_path) |             CORE.config_path = str(config_path) | ||||||
|             config = esphome.config.read_config( |             config = esphome.config.read_config( | ||||||
|                 {"command": "compile", "config": str(config_path)} |                 {"command": "compile", "config": str(config_path)} | ||||||
|   | |||||||
							
								
								
									
										57
									
								
								tests/integration/fixtures/areas_and_devices.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								tests/integration/fixtures/areas_and_devices.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | esphome: | ||||||
|  |   name: areas-devices-test | ||||||
|  |   # Define top-level area | ||||||
|  |   area: | ||||||
|  |     id: living_room_area | ||||||
|  |     name: Living Room | ||||||
|  |   # Define additional areas | ||||||
|  |   areas: | ||||||
|  |     - id: bedroom_area | ||||||
|  |       name: Bedroom | ||||||
|  |     - id: kitchen_area | ||||||
|  |       name: Kitchen | ||||||
|  |   # Define devices with area assignments | ||||||
|  |   devices: | ||||||
|  |     - id: light_controller_device | ||||||
|  |       name: Light Controller | ||||||
|  |       area_id: living_room_area  # Uses top-level area | ||||||
|  |     - id: temp_sensor_device | ||||||
|  |       name: Temperature Sensor | ||||||
|  |       area_id: bedroom_area | ||||||
|  |     - id: motion_detector_device | ||||||
|  |       name: Motion Detector | ||||||
|  |       area_id: living_room_area  # Reuses top-level area | ||||||
|  |     - id: smart_switch_device | ||||||
|  |       name: Smart Switch | ||||||
|  |       area_id: kitchen_area | ||||||
|  |  | ||||||
|  | host: | ||||||
|  | api: | ||||||
|  | logger: | ||||||
|  |  | ||||||
|  | # Sensors assigned to different devices | ||||||
|  | sensor: | ||||||
|  |   - platform: template | ||||||
|  |     name: Light Controller Sensor | ||||||
|  |     device_id: light_controller_device | ||||||
|  |     lambda: return 1.0; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Temperature Sensor Reading | ||||||
|  |     device_id: temp_sensor_device | ||||||
|  |     lambda: return 2.0; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Motion Detector Status | ||||||
|  |     device_id: motion_detector_device | ||||||
|  |     lambda: return 3.0; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Smart Switch Power | ||||||
|  |     device_id: smart_switch_device | ||||||
|  |     lambda: return 4.0; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |  | ||||||
| @@ -0,0 +1,154 @@ | |||||||
|  | esphome: | ||||||
|  |   name: duplicate-entities-test | ||||||
|  |   # Define devices to test multi-device duplicate handling | ||||||
|  |   devices: | ||||||
|  |     - id: controller_1 | ||||||
|  |       name: Controller 1 | ||||||
|  |     - id: controller_2 | ||||||
|  |       name: Controller 2 | ||||||
|  |     - id: controller_3 | ||||||
|  |       name: Controller 3 | ||||||
|  |  | ||||||
|  | host: | ||||||
|  | api:  # Port will be automatically injected | ||||||
|  | logger: | ||||||
|  |  | ||||||
|  | # Test that duplicate entity names are allowed on different devices | ||||||
|  |  | ||||||
|  | # Scenario 1: Same sensor name on different devices (allowed) | ||||||
|  | sensor: | ||||||
|  |   - platform: template | ||||||
|  |     name: Temperature | ||||||
|  |     device_id: controller_1 | ||||||
|  |     lambda: return 21.0; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Temperature | ||||||
|  |     device_id: controller_2 | ||||||
|  |     lambda: return 22.0; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Temperature | ||||||
|  |     device_id: controller_3 | ||||||
|  |     lambda: return 23.0; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |  | ||||||
|  |   # Main device sensor (no device_id) | ||||||
|  |   - platform: template | ||||||
|  |     name: Temperature | ||||||
|  |     lambda: return 20.0; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |  | ||||||
|  |   # Different sensor with unique name | ||||||
|  |   - platform: template | ||||||
|  |     name: Humidity | ||||||
|  |     lambda: return 60.0; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |  | ||||||
|  | # Scenario 2: Same binary sensor name on different devices (allowed) | ||||||
|  | binary_sensor: | ||||||
|  |   - platform: template | ||||||
|  |     name: Status | ||||||
|  |     device_id: controller_1 | ||||||
|  |     lambda: return true; | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Status | ||||||
|  |     device_id: controller_2 | ||||||
|  |     lambda: return false; | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Status | ||||||
|  |     lambda: return true;  # Main device | ||||||
|  |  | ||||||
|  |   # Different platform can have same name as sensor | ||||||
|  |   - platform: template | ||||||
|  |     name: Temperature | ||||||
|  |     lambda: return true; | ||||||
|  |  | ||||||
|  | # Scenario 3: Same text sensor name on different devices | ||||||
|  | text_sensor: | ||||||
|  |   - platform: template | ||||||
|  |     name: Device Info | ||||||
|  |     device_id: controller_1 | ||||||
|  |     lambda: return {"Controller 1 Active"}; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Device Info | ||||||
|  |     device_id: controller_2 | ||||||
|  |     lambda: return {"Controller 2 Active"}; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Device Info | ||||||
|  |     lambda: return {"Main Device Active"}; | ||||||
|  |     update_interval: 0.1s | ||||||
|  |  | ||||||
|  | # Scenario 4: Same switch name on different devices | ||||||
|  | switch: | ||||||
|  |   - platform: template | ||||||
|  |     name: Power | ||||||
|  |     device_id: controller_1 | ||||||
|  |     lambda: return false; | ||||||
|  |     turn_on_action: [] | ||||||
|  |     turn_off_action: [] | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Power | ||||||
|  |     device_id: controller_2 | ||||||
|  |     lambda: return true; | ||||||
|  |     turn_on_action: [] | ||||||
|  |     turn_off_action: [] | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: Power | ||||||
|  |     device_id: controller_3 | ||||||
|  |     lambda: return false; | ||||||
|  |     turn_on_action: [] | ||||||
|  |     turn_off_action: [] | ||||||
|  |  | ||||||
|  |   # Unique switch on main device | ||||||
|  |   - platform: template | ||||||
|  |     name: Main Power | ||||||
|  |     lambda: return true; | ||||||
|  |     turn_on_action: [] | ||||||
|  |     turn_off_action: [] | ||||||
|  |  | ||||||
|  | # Scenario 5: Empty names on different devices (should use device name) | ||||||
|  | button: | ||||||
|  |   - platform: template | ||||||
|  |     name: "" | ||||||
|  |     device_id: controller_1 | ||||||
|  |     on_press: [] | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: "" | ||||||
|  |     device_id: controller_2 | ||||||
|  |     on_press: [] | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: "" | ||||||
|  |     on_press: []  # Main device | ||||||
|  |  | ||||||
|  | # Scenario 6: Special characters in names | ||||||
|  | number: | ||||||
|  |   - platform: template | ||||||
|  |     name: "Temperature Setpoint!" | ||||||
|  |     device_id: controller_1 | ||||||
|  |     min_value: 10.0 | ||||||
|  |     max_value: 30.0 | ||||||
|  |     step: 0.1 | ||||||
|  |     lambda: return 21.0; | ||||||
|  |     set_action: [] | ||||||
|  |  | ||||||
|  |   - platform: template | ||||||
|  |     name: "Temperature Setpoint!" | ||||||
|  |     device_id: controller_2 | ||||||
|  |     min_value: 10.0 | ||||||
|  |     max_value: 30.0 | ||||||
|  |     step: 0.1 | ||||||
|  |     lambda: return 22.0; | ||||||
|  |     set_action: [] | ||||||
							
								
								
									
										15
									
								
								tests/integration/fixtures/legacy_area.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								tests/integration/fixtures/legacy_area.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | esphome: | ||||||
|  |   name: legacy-area-test | ||||||
|  |   # Using legacy string-based area configuration | ||||||
|  |   area: Master Bedroom | ||||||
|  |  | ||||||
|  | host: | ||||||
|  | api: | ||||||
|  | logger: | ||||||
|  |  | ||||||
|  | # Simple sensor to ensure the device compiles and runs | ||||||
|  | sensor: | ||||||
|  |   - platform: template | ||||||
|  |     name: Test Sensor | ||||||
|  |     lambda: return 42.0; | ||||||
|  |     update_interval: 1s | ||||||
							
								
								
									
										121
									
								
								tests/integration/test_areas_and_devices.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								tests/integration/test_areas_and_devices.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | |||||||
|  | """Integration test for areas and devices feature.""" | ||||||
|  |  | ||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | import asyncio | ||||||
|  |  | ||||||
|  | from aioesphomeapi import EntityState | ||||||
|  | import pytest | ||||||
|  |  | ||||||
|  | from .types import APIClientConnectedFactory, RunCompiledFunction | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_areas_and_devices( | ||||||
|  |     yaml_config: str, | ||||||
|  |     run_compiled: RunCompiledFunction, | ||||||
|  |     api_client_connected: APIClientConnectedFactory, | ||||||
|  | ) -> None: | ||||||
|  |     """Test areas and devices configuration with entity mapping.""" | ||||||
|  |     async with run_compiled(yaml_config), api_client_connected() as client: | ||||||
|  |         # Get device info which includes areas and devices | ||||||
|  |         device_info = await client.device_info() | ||||||
|  |         assert device_info is not None | ||||||
|  |  | ||||||
|  |         # Verify areas are reported | ||||||
|  |         areas = device_info.areas | ||||||
|  |         assert len(areas) >= 2, f"Expected at least 2 areas, got {len(areas)}" | ||||||
|  |  | ||||||
|  |         # Find our specific areas | ||||||
|  |         main_area = next((a for a in areas if a.name == "Living Room"), None) | ||||||
|  |         bedroom_area = next((a for a in areas if a.name == "Bedroom"), None) | ||||||
|  |         kitchen_area = next((a for a in areas if a.name == "Kitchen"), None) | ||||||
|  |  | ||||||
|  |         assert main_area is not None, "Living Room area not found" | ||||||
|  |         assert bedroom_area is not None, "Bedroom area not found" | ||||||
|  |         assert kitchen_area is not None, "Kitchen area not found" | ||||||
|  |  | ||||||
|  |         # Verify devices are reported | ||||||
|  |         devices = device_info.devices | ||||||
|  |         assert len(devices) >= 4, f"Expected at least 4 devices, got {len(devices)}" | ||||||
|  |  | ||||||
|  |         # Find our specific devices | ||||||
|  |         light_controller = next( | ||||||
|  |             (d for d in devices if d.name == "Light Controller"), None | ||||||
|  |         ) | ||||||
|  |         temp_sensor = next((d for d in devices if d.name == "Temperature Sensor"), None) | ||||||
|  |         motion_detector = next( | ||||||
|  |             (d for d in devices if d.name == "Motion Detector"), None | ||||||
|  |         ) | ||||||
|  |         smart_switch = next((d for d in devices if d.name == "Smart Switch"), None) | ||||||
|  |  | ||||||
|  |         assert light_controller is not None, "Light Controller device not found" | ||||||
|  |         assert temp_sensor is not None, "Temperature Sensor device not found" | ||||||
|  |         assert motion_detector is not None, "Motion Detector device not found" | ||||||
|  |         assert smart_switch is not None, "Smart Switch device not found" | ||||||
|  |  | ||||||
|  |         # Verify device area assignments | ||||||
|  |         assert light_controller.area_id == main_area.area_id, ( | ||||||
|  |             "Light Controller should be in Living Room" | ||||||
|  |         ) | ||||||
|  |         assert temp_sensor.area_id == bedroom_area.area_id, ( | ||||||
|  |             "Temperature Sensor should be in Bedroom" | ||||||
|  |         ) | ||||||
|  |         assert motion_detector.area_id == main_area.area_id, ( | ||||||
|  |             "Motion Detector should be in Living Room" | ||||||
|  |         ) | ||||||
|  |         assert smart_switch.area_id == kitchen_area.area_id, ( | ||||||
|  |             "Smart Switch should be in Kitchen" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Verify suggested_area is set to the top-level area name | ||||||
|  |         assert device_info.suggested_area == "Living Room", ( | ||||||
|  |             f"Expected suggested_area to be 'Living Room', got '{device_info.suggested_area}'" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Get entity list to verify device_id mapping | ||||||
|  |         entities = await client.list_entities_services() | ||||||
|  |  | ||||||
|  |         # Collect sensor entities | ||||||
|  |         sensor_entities = [e for e in entities[0] if hasattr(e, "device_id")] | ||||||
|  |         assert len(sensor_entities) >= 4, ( | ||||||
|  |             f"Expected at least 4 sensor entities, got {len(sensor_entities)}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Subscribe to states to get sensor values | ||||||
|  |         loop = asyncio.get_running_loop() | ||||||
|  |         states: dict[int, EntityState] = {} | ||||||
|  |         states_future: asyncio.Future[bool] = loop.create_future() | ||||||
|  |  | ||||||
|  |         def on_state(state: EntityState) -> None: | ||||||
|  |             states[state.key] = state | ||||||
|  |             # Check if we have all expected sensor states | ||||||
|  |             if len(states) >= 4 and not states_future.done(): | ||||||
|  |                 states_future.set_result(True) | ||||||
|  |  | ||||||
|  |         client.subscribe_states(on_state) | ||||||
|  |  | ||||||
|  |         # Wait for sensor states | ||||||
|  |         try: | ||||||
|  |             await asyncio.wait_for(states_future, timeout=10.0) | ||||||
|  |         except asyncio.TimeoutError: | ||||||
|  |             pytest.fail( | ||||||
|  |                 f"Did not receive all sensor states within 10 seconds. " | ||||||
|  |                 f"Received {len(states)} states" | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         # Verify we have sensor entities with proper device_id assignments | ||||||
|  |         device_id_mapping = { | ||||||
|  |             "Light Controller Sensor": light_controller.device_id, | ||||||
|  |             "Temperature Sensor Reading": temp_sensor.device_id, | ||||||
|  |             "Motion Detector Status": motion_detector.device_id, | ||||||
|  |             "Smart Switch Power": smart_switch.device_id, | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         for entity in sensor_entities: | ||||||
|  |             if entity.name in device_id_mapping: | ||||||
|  |                 expected_device_id = device_id_mapping[entity.name] | ||||||
|  |                 assert entity.device_id == expected_device_id, ( | ||||||
|  |                     f"{entity.name} has device_id {entity.device_id}, " | ||||||
|  |                     f"expected {expected_device_id}" | ||||||
|  |                 ) | ||||||
							
								
								
									
										184
									
								
								tests/integration/test_duplicate_entities.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								tests/integration/test_duplicate_entities.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,184 @@ | |||||||
|  | """Integration test for duplicate entity handling with new validation.""" | ||||||
|  |  | ||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | import asyncio | ||||||
|  |  | ||||||
|  | from aioesphomeapi import EntityInfo | ||||||
|  | import pytest | ||||||
|  |  | ||||||
|  | from .types import APIClientConnectedFactory, RunCompiledFunction | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_duplicate_entities_on_different_devices( | ||||||
|  |     yaml_config: str, | ||||||
|  |     run_compiled: RunCompiledFunction, | ||||||
|  |     api_client_connected: APIClientConnectedFactory, | ||||||
|  | ) -> None: | ||||||
|  |     """Test that duplicate entity names are allowed on different devices.""" | ||||||
|  |     async with run_compiled(yaml_config), api_client_connected() as client: | ||||||
|  |         # Get device info | ||||||
|  |         device_info = await client.device_info() | ||||||
|  |         assert device_info is not None | ||||||
|  |  | ||||||
|  |         # Get devices | ||||||
|  |         devices = device_info.devices | ||||||
|  |         assert len(devices) >= 3, f"Expected at least 3 devices, got {len(devices)}" | ||||||
|  |  | ||||||
|  |         # Find our test devices | ||||||
|  |         controller_1 = next((d for d in devices if d.name == "Controller 1"), None) | ||||||
|  |         controller_2 = next((d for d in devices if d.name == "Controller 2"), None) | ||||||
|  |         controller_3 = next((d for d in devices if d.name == "Controller 3"), None) | ||||||
|  |  | ||||||
|  |         assert controller_1 is not None, "Controller 1 device not found" | ||||||
|  |         assert controller_2 is not None, "Controller 2 device not found" | ||||||
|  |         assert controller_3 is not None, "Controller 3 device not found" | ||||||
|  |  | ||||||
|  |         # Get entity list | ||||||
|  |         entities = await client.list_entities_services() | ||||||
|  |         all_entities: list[EntityInfo] = [] | ||||||
|  |         for entity_list in entities[0]: | ||||||
|  |             all_entities.append(entity_list) | ||||||
|  |  | ||||||
|  |         # Group entities by type for easier testing | ||||||
|  |         sensors = [e for e in all_entities if e.__class__.__name__ == "SensorInfo"] | ||||||
|  |         binary_sensors = [ | ||||||
|  |             e for e in all_entities if e.__class__.__name__ == "BinarySensorInfo" | ||||||
|  |         ] | ||||||
|  |         text_sensors = [ | ||||||
|  |             e for e in all_entities if e.__class__.__name__ == "TextSensorInfo" | ||||||
|  |         ] | ||||||
|  |         switches = [e for e in all_entities if e.__class__.__name__ == "SwitchInfo"] | ||||||
|  |         buttons = [e for e in all_entities if e.__class__.__name__ == "ButtonInfo"] | ||||||
|  |         numbers = [e for e in all_entities if e.__class__.__name__ == "NumberInfo"] | ||||||
|  |  | ||||||
|  |         # Scenario 1: Check sensors with same "Temperature" name on different devices | ||||||
|  |         temp_sensors = [s for s in sensors if s.name == "Temperature"] | ||||||
|  |         assert len(temp_sensors) == 4, ( | ||||||
|  |             f"Expected exactly 4 temperature sensors, got {len(temp_sensors)}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Verify each sensor is on a different device | ||||||
|  |         temp_device_ids = set() | ||||||
|  |         temp_object_ids = set() | ||||||
|  |  | ||||||
|  |         for sensor in temp_sensors: | ||||||
|  |             temp_device_ids.add(sensor.device_id) | ||||||
|  |             temp_object_ids.add(sensor.object_id) | ||||||
|  |  | ||||||
|  |             # All should have object_id "temperature" (no suffix) | ||||||
|  |             assert sensor.object_id == "temperature", ( | ||||||
|  |                 f"Expected object_id 'temperature', got '{sensor.object_id}'" | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         # Should have 4 different device IDs (including None for main device) | ||||||
|  |         assert len(temp_device_ids) == 4, ( | ||||||
|  |             f"Temperature sensors should be on different devices, got {temp_device_ids}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Scenario 2: Check binary sensors "Status" on different devices | ||||||
|  |         status_binary = [b for b in binary_sensors if b.name == "Status"] | ||||||
|  |         assert len(status_binary) == 3, ( | ||||||
|  |             f"Expected exactly 3 status binary sensors, got {len(status_binary)}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # All should have object_id "status" | ||||||
|  |         for binary in status_binary: | ||||||
|  |             assert binary.object_id == "status", ( | ||||||
|  |                 f"Expected object_id 'status', got '{binary.object_id}'" | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         # Scenario 3: Check that sensor and binary_sensor can have same name | ||||||
|  |         temp_binary = [b for b in binary_sensors if b.name == "Temperature"] | ||||||
|  |         assert len(temp_binary) == 1, ( | ||||||
|  |             f"Expected exactly 1 temperature binary sensor, got {len(temp_binary)}" | ||||||
|  |         ) | ||||||
|  |         assert temp_binary[0].object_id == "temperature" | ||||||
|  |  | ||||||
|  |         # Scenario 4: Check text sensors "Device Info" on different devices | ||||||
|  |         info_text = [t for t in text_sensors if t.name == "Device Info"] | ||||||
|  |         assert len(info_text) == 3, ( | ||||||
|  |             f"Expected exactly 3 device info text sensors, got {len(info_text)}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # All should have object_id "device_info" | ||||||
|  |         for text in info_text: | ||||||
|  |             assert text.object_id == "device_info", ( | ||||||
|  |                 f"Expected object_id 'device_info', got '{text.object_id}'" | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         # Scenario 5: Check switches "Power" on different devices | ||||||
|  |         power_switches = [s for s in switches if s.name == "Power"] | ||||||
|  |         assert len(power_switches) == 3, ( | ||||||
|  |             f"Expected exactly 3 power switches, got {len(power_switches)}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # All should have object_id "power" | ||||||
|  |         for switch in power_switches: | ||||||
|  |             assert switch.object_id == "power", ( | ||||||
|  |                 f"Expected object_id 'power', got '{switch.object_id}'" | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         # Scenario 6: Check empty name buttons (should use device name) | ||||||
|  |         empty_buttons = [b for b in buttons if b.name == ""] | ||||||
|  |         assert len(empty_buttons) == 3, ( | ||||||
|  |             f"Expected exactly 3 empty name buttons, got {len(empty_buttons)}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Group by device | ||||||
|  |         c1_buttons = [b for b in empty_buttons if b.device_id == controller_1.device_id] | ||||||
|  |         c2_buttons = [b for b in empty_buttons if b.device_id == controller_2.device_id] | ||||||
|  |  | ||||||
|  |         # For main device, device_id is 0 | ||||||
|  |         main_buttons = [b for b in empty_buttons if b.device_id == 0] | ||||||
|  |  | ||||||
|  |         # Check object IDs for empty name entities | ||||||
|  |         assert len(c1_buttons) == 1 and c1_buttons[0].object_id == "controller_1" | ||||||
|  |         assert len(c2_buttons) == 1 and c2_buttons[0].object_id == "controller_2" | ||||||
|  |         assert ( | ||||||
|  |             len(main_buttons) == 1 | ||||||
|  |             and main_buttons[0].object_id == "duplicate-entities-test" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Scenario 7: Check special characters in number names | ||||||
|  |         temp_numbers = [n for n in numbers if n.name == "Temperature Setpoint!"] | ||||||
|  |         assert len(temp_numbers) == 2, ( | ||||||
|  |             f"Expected exactly 2 temperature setpoint numbers, got {len(temp_numbers)}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Special characters should be sanitized to _ in object_id | ||||||
|  |         for number in temp_numbers: | ||||||
|  |             assert number.object_id == "temperature_setpoint_", ( | ||||||
|  |                 f"Expected object_id 'temperature_setpoint_', got '{number.object_id}'" | ||||||
|  |             ) | ||||||
|  |  | ||||||
|  |         # Verify we can get states for all entities (ensures they're functional) | ||||||
|  |         loop = asyncio.get_running_loop() | ||||||
|  |         states_future: asyncio.Future[None] = loop.create_future() | ||||||
|  |         state_count = 0 | ||||||
|  |         expected_count = ( | ||||||
|  |             len(sensors) | ||||||
|  |             + len(binary_sensors) | ||||||
|  |             + len(text_sensors) | ||||||
|  |             + len(switches) | ||||||
|  |             + len(buttons) | ||||||
|  |             + len(numbers) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         def on_state(state) -> None: | ||||||
|  |             nonlocal state_count | ||||||
|  |             state_count += 1 | ||||||
|  |             if state_count >= expected_count and not states_future.done(): | ||||||
|  |                 states_future.set_result(None) | ||||||
|  |  | ||||||
|  |         client.subscribe_states(on_state) | ||||||
|  |  | ||||||
|  |         # Wait for all entity states | ||||||
|  |         try: | ||||||
|  |             await asyncio.wait_for(states_future, timeout=10.0) | ||||||
|  |         except asyncio.TimeoutError: | ||||||
|  |             pytest.fail( | ||||||
|  |                 f"Did not receive all entity states within 10 seconds. " | ||||||
|  |                 f"Expected {expected_count}, received {state_count}" | ||||||
|  |             ) | ||||||
							
								
								
									
										41
									
								
								tests/integration/test_legacy_area.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								tests/integration/test_legacy_area.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | """Integration test for legacy string-based area configuration.""" | ||||||
|  |  | ||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | import pytest | ||||||
|  |  | ||||||
|  | from .types import APIClientConnectedFactory, RunCompiledFunction | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_legacy_area( | ||||||
|  |     yaml_config: str, | ||||||
|  |     run_compiled: RunCompiledFunction, | ||||||
|  |     api_client_connected: APIClientConnectedFactory, | ||||||
|  | ) -> None: | ||||||
|  |     """Test legacy string-based area configuration.""" | ||||||
|  |     async with run_compiled(yaml_config), api_client_connected() as client: | ||||||
|  |         # Get device info which includes areas | ||||||
|  |         device_info = await client.device_info() | ||||||
|  |         assert device_info is not None | ||||||
|  |  | ||||||
|  |         # Verify the area is reported (should be converted to structured format) | ||||||
|  |         areas = device_info.areas | ||||||
|  |         assert len(areas) == 1, f"Expected exactly 1 area, got {len(areas)}" | ||||||
|  |  | ||||||
|  |         # Find the area - should be slugified from "Master Bedroom" | ||||||
|  |         area = areas[0] | ||||||
|  |         assert area.name == "Master Bedroom", ( | ||||||
|  |             f"Expected area name 'Master Bedroom', got '{area.name}'" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Verify area.id is set (it should be a hash) | ||||||
|  |         assert area.area_id > 0, "Area ID should be a positive hash value" | ||||||
|  |  | ||||||
|  |         # The suggested_area field should be set for backward compatibility | ||||||
|  |         assert device_info.suggested_area == "Master Bedroom", ( | ||||||
|  |             f"Expected suggested_area to be 'Master Bedroom', got '{device_info.suggested_area}'" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Verify deprecated warning would have been logged during compilation | ||||||
|  |         # (We can't check logs directly in integration tests, but the code should work) | ||||||
| @@ -14,6 +14,8 @@ import sys | |||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
|  | from esphome.core import CORE | ||||||
|  |  | ||||||
| here = Path(__file__).parent | here = Path(__file__).parent | ||||||
|  |  | ||||||
| # Configure location of package root | # Configure location of package root | ||||||
| @@ -21,6 +23,13 @@ package_root = here.parent.parent | |||||||
| sys.path.insert(0, package_root.as_posix()) | sys.path.insert(0, package_root.as_posix()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture(autouse=True) | ||||||
|  | def reset_core(): | ||||||
|  |     """Reset CORE after each test.""" | ||||||
|  |     yield | ||||||
|  |     CORE.reset() | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| def fixture_path() -> Path: | def fixture_path() -> Path: | ||||||
|     """ |     """ | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								tests/unit_tests/core/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/unit_tests/core/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										33
									
								
								tests/unit_tests/core/common.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								tests/unit_tests/core/common.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | """Common test utilities for core unit tests.""" | ||||||
|  |  | ||||||
|  | from collections.abc import Callable | ||||||
|  | from pathlib import Path | ||||||
|  | from unittest.mock import patch | ||||||
|  |  | ||||||
|  | from esphome import config, yaml_util | ||||||
|  | from esphome.config import Config | ||||||
|  | from esphome.core import CORE | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def load_config_from_yaml( | ||||||
|  |     yaml_file: Callable[[str], str], yaml_content: str | ||||||
|  | ) -> Config | None: | ||||||
|  |     """Load configuration from YAML content.""" | ||||||
|  |     yaml_path = yaml_file(yaml_content) | ||||||
|  |     parsed_yaml = yaml_util.load_yaml(yaml_path) | ||||||
|  |  | ||||||
|  |     # Mock yaml_util.load_yaml to return our parsed content | ||||||
|  |     with ( | ||||||
|  |         patch.object(yaml_util, "load_yaml", return_value=parsed_yaml), | ||||||
|  |         patch.object(CORE, "config_path", yaml_path), | ||||||
|  |     ): | ||||||
|  |         return config.read_config({}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def load_config_from_fixture( | ||||||
|  |     yaml_file: Callable[[str], str], fixture_name: str, fixtures_dir: Path | ||||||
|  | ) -> Config | None: | ||||||
|  |     """Load configuration from a fixture file.""" | ||||||
|  |     fixture_path = fixtures_dir / fixture_name | ||||||
|  |     yaml_content = fixture_path.read_text() | ||||||
|  |     return load_config_from_yaml(yaml_file, yaml_content) | ||||||
							
								
								
									
										18
									
								
								tests/unit_tests/core/conftest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								tests/unit_tests/core/conftest.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | """Shared fixtures for core unit tests.""" | ||||||
|  |  | ||||||
|  | from collections.abc import Callable | ||||||
|  | from pathlib import Path | ||||||
|  |  | ||||||
|  | import pytest | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture | ||||||
|  | def yaml_file(tmp_path: Path) -> Callable[[str], str]: | ||||||
|  |     """Create a temporary YAML file for testing.""" | ||||||
|  |  | ||||||
|  |     def _yaml_file(content: str) -> str: | ||||||
|  |         yaml_path = tmp_path / "test.yaml" | ||||||
|  |         yaml_path.write_text(content) | ||||||
|  |         return str(yaml_path) | ||||||
|  |  | ||||||
|  |     return _yaml_file | ||||||
							
								
								
									
										225
									
								
								tests/unit_tests/core/test_config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								tests/unit_tests/core/test_config.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,225 @@ | |||||||
|  | """Unit tests for core config functionality including areas and devices.""" | ||||||
|  |  | ||||||
|  | from collections.abc import Callable | ||||||
|  | from pathlib import Path | ||||||
|  | from typing import Any | ||||||
|  |  | ||||||
|  | import pytest | ||||||
|  |  | ||||||
|  | from esphome import config_validation as cv, core | ||||||
|  | from esphome.const import CONF_AREA, CONF_AREAS, CONF_DEVICES | ||||||
|  | from esphome.core.config import Area, validate_area_config | ||||||
|  |  | ||||||
|  | from .common import load_config_from_fixture | ||||||
|  |  | ||||||
|  | FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "config" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_validate_area_config_with_string() -> None: | ||||||
|  |     """Test that string area config is converted to structured format.""" | ||||||
|  |     result = validate_area_config("Living Room") | ||||||
|  |  | ||||||
|  |     assert isinstance(result, dict) | ||||||
|  |     assert "id" in result | ||||||
|  |     assert "name" in result | ||||||
|  |     assert result["name"] == "Living Room" | ||||||
|  |     assert isinstance(result["id"], core.ID) | ||||||
|  |     assert result["id"].is_declaration | ||||||
|  |     assert not result["id"].is_manual | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_validate_area_config_with_dict() -> None: | ||||||
|  |     """Test that structured area config passes through unchanged.""" | ||||||
|  |     area_id = cv.declare_id(Area)("test_area") | ||||||
|  |     input_config: dict[str, Any] = { | ||||||
|  |         "id": area_id, | ||||||
|  |         "name": "Test Area", | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     result = validate_area_config(input_config) | ||||||
|  |  | ||||||
|  |     assert result == input_config | ||||||
|  |     assert result["id"] == area_id | ||||||
|  |     assert result["name"] == "Test Area" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_device_with_valid_area_id(yaml_file: Callable[[str], str]) -> None: | ||||||
|  |     """Test that device with valid area_id works correctly.""" | ||||||
|  |     result = load_config_from_fixture(yaml_file, "valid_area_device.yaml", FIXTURES_DIR) | ||||||
|  |     assert result is not None | ||||||
|  |  | ||||||
|  |     esphome_config = result["esphome"] | ||||||
|  |  | ||||||
|  |     # Verify areas were parsed correctly | ||||||
|  |     assert CONF_AREAS in esphome_config | ||||||
|  |     areas = esphome_config[CONF_AREAS] | ||||||
|  |     assert len(areas) == 1 | ||||||
|  |     assert areas[0]["id"].id == "bedroom_area" | ||||||
|  |     assert areas[0]["name"] == "Bedroom" | ||||||
|  |  | ||||||
|  |     # Verify devices were parsed correctly | ||||||
|  |     assert CONF_DEVICES in esphome_config | ||||||
|  |     devices = esphome_config[CONF_DEVICES] | ||||||
|  |     assert len(devices) == 1 | ||||||
|  |     assert devices[0]["id"].id == "test_device" | ||||||
|  |     assert devices[0]["name"] == "Test Device" | ||||||
|  |     assert devices[0]["area_id"].id == "bedroom_area" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_multiple_areas_and_devices(yaml_file: Callable[[str], str]) -> None: | ||||||
|  |     """Test multiple areas and devices configuration.""" | ||||||
|  |     result = load_config_from_fixture( | ||||||
|  |         yaml_file, "multiple_areas_devices.yaml", FIXTURES_DIR | ||||||
|  |     ) | ||||||
|  |     assert result is not None | ||||||
|  |  | ||||||
|  |     esphome_config = result["esphome"] | ||||||
|  |  | ||||||
|  |     # Verify main area | ||||||
|  |     assert CONF_AREA in esphome_config | ||||||
|  |     main_area = esphome_config[CONF_AREA] | ||||||
|  |     assert main_area["id"].id == "main_area" | ||||||
|  |     assert main_area["name"] == "Main Area" | ||||||
|  |  | ||||||
|  |     # Verify additional areas | ||||||
|  |     assert CONF_AREAS in esphome_config | ||||||
|  |     areas = esphome_config[CONF_AREAS] | ||||||
|  |     assert len(areas) == 2 | ||||||
|  |     area_ids = {area["id"].id for area in areas} | ||||||
|  |     assert area_ids == {"area1", "area2"} | ||||||
|  |  | ||||||
|  |     # Verify devices | ||||||
|  |     assert CONF_DEVICES in esphome_config | ||||||
|  |     devices = esphome_config[CONF_DEVICES] | ||||||
|  |     assert len(devices) == 3 | ||||||
|  |  | ||||||
|  |     # Check device-area associations | ||||||
|  |     device_area_map = {dev["id"].id: dev["area_id"].id for dev in devices} | ||||||
|  |     assert device_area_map == { | ||||||
|  |         "device1": "main_area", | ||||||
|  |         "device2": "area1", | ||||||
|  |         "device3": "area2", | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_legacy_string_area( | ||||||
|  |     yaml_file: Callable[[str], str], caplog: pytest.LogCaptureFixture | ||||||
|  | ) -> None: | ||||||
|  |     """Test legacy string area configuration with deprecation warning.""" | ||||||
|  |     result = load_config_from_fixture( | ||||||
|  |         yaml_file, "legacy_string_area.yaml", FIXTURES_DIR | ||||||
|  |     ) | ||||||
|  |     assert result is not None | ||||||
|  |  | ||||||
|  |     esphome_config = result["esphome"] | ||||||
|  |  | ||||||
|  |     # Verify the string was converted to structured format | ||||||
|  |     assert CONF_AREA in esphome_config | ||||||
|  |     area = esphome_config[CONF_AREA] | ||||||
|  |     assert isinstance(area, dict) | ||||||
|  |     assert area["name"] == "Living Room" | ||||||
|  |     assert isinstance(area["id"], core.ID) | ||||||
|  |     assert area["id"].is_declaration | ||||||
|  |     assert not area["id"].is_manual | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_area_id_collision( | ||||||
|  |     yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] | ||||||
|  | ) -> None: | ||||||
|  |     """Test that duplicate area IDs are detected.""" | ||||||
|  |     result = load_config_from_fixture(yaml_file, "area_id_collision.yaml", FIXTURES_DIR) | ||||||
|  |     assert result is None | ||||||
|  |  | ||||||
|  |     # Check for the specific error message in stdout | ||||||
|  |     captured = capsys.readouterr() | ||||||
|  |     # Exact duplicates are now caught by IDPassValidationStep | ||||||
|  |     assert "ID duplicate_id redefined! Check esphome->area->id." in captured.out | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_device_without_area(yaml_file: Callable[[str], str]) -> None: | ||||||
|  |     """Test that devices without area_id work correctly.""" | ||||||
|  |     result = load_config_from_fixture( | ||||||
|  |         yaml_file, "device_without_area.yaml", FIXTURES_DIR | ||||||
|  |     ) | ||||||
|  |     assert result is not None | ||||||
|  |  | ||||||
|  |     esphome_config = result["esphome"] | ||||||
|  |  | ||||||
|  |     # Verify device was parsed | ||||||
|  |     assert CONF_DEVICES in esphome_config | ||||||
|  |     devices = esphome_config[CONF_DEVICES] | ||||||
|  |     assert len(devices) == 1 | ||||||
|  |  | ||||||
|  |     device = devices[0] | ||||||
|  |     assert device["id"].id == "test_device" | ||||||
|  |     assert device["name"] == "Test Device" | ||||||
|  |  | ||||||
|  |     # Verify no area_id is present | ||||||
|  |     assert "area_id" not in device | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_device_with_invalid_area_id( | ||||||
|  |     yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] | ||||||
|  | ) -> None: | ||||||
|  |     """Test that device with non-existent area_id fails validation.""" | ||||||
|  |     result = load_config_from_fixture( | ||||||
|  |         yaml_file, "device_invalid_area.yaml", FIXTURES_DIR | ||||||
|  |     ) | ||||||
|  |     assert result is None | ||||||
|  |  | ||||||
|  |     # Check for the specific error message in stdout | ||||||
|  |     captured = capsys.readouterr() | ||||||
|  |     assert ( | ||||||
|  |         "Couldn't find ID 'nonexistent_area'. Please check you have defined an ID with that name in your configuration." | ||||||
|  |         in captured.out | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_device_id_hash_collision( | ||||||
|  |     yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] | ||||||
|  | ) -> None: | ||||||
|  |     """Test that device IDs with hash collisions are detected.""" | ||||||
|  |     result = load_config_from_fixture( | ||||||
|  |         yaml_file, "device_id_collision.yaml", FIXTURES_DIR | ||||||
|  |     ) | ||||||
|  |     assert result is None | ||||||
|  |  | ||||||
|  |     # Check for the specific error message about hash collision | ||||||
|  |     captured = capsys.readouterr() | ||||||
|  |     # The error message shows the ID that collides and includes the hash value | ||||||
|  |     assert ( | ||||||
|  |         "Device ID 'd6ka' with hash 3082558663 collides with existing device ID 'test_2258'" | ||||||
|  |         in captured.out | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_area_id_hash_collision( | ||||||
|  |     yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] | ||||||
|  | ) -> None: | ||||||
|  |     """Test that area IDs with hash collisions are detected.""" | ||||||
|  |     result = load_config_from_fixture( | ||||||
|  |         yaml_file, "area_id_hash_collision.yaml", FIXTURES_DIR | ||||||
|  |     ) | ||||||
|  |     assert result is None | ||||||
|  |  | ||||||
|  |     # Check for the specific error message about hash collision | ||||||
|  |     captured = capsys.readouterr() | ||||||
|  |     # The error message shows the ID that collides and includes the hash value | ||||||
|  |     assert ( | ||||||
|  |         "Area ID 'd6ka' with hash 3082558663 collides with existing area ID 'test_2258'" | ||||||
|  |         in captured.out | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_device_duplicate_id( | ||||||
|  |     yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] | ||||||
|  | ) -> None: | ||||||
|  |     """Test that duplicate device IDs are detected by IDPassValidationStep.""" | ||||||
|  |     result = load_config_from_fixture( | ||||||
|  |         yaml_file, "device_duplicate_id.yaml", FIXTURES_DIR | ||||||
|  |     ) | ||||||
|  |     assert result is None | ||||||
|  |  | ||||||
|  |     # Check for the specific error message from IDPassValidationStep | ||||||
|  |     captured = capsys.readouterr() | ||||||
|  |     assert "ID duplicate_device redefined!" in captured.out | ||||||
							
								
								
									
										595
									
								
								tests/unit_tests/core/test_entity_helpers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										595
									
								
								tests/unit_tests/core/test_entity_helpers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,595 @@ | |||||||
|  | """Test get_base_entity_object_id function matches C++ behavior.""" | ||||||
|  |  | ||||||
|  | from collections.abc import Callable, Generator | ||||||
|  | from pathlib import Path | ||||||
|  | import re | ||||||
|  | from typing import Any | ||||||
|  |  | ||||||
|  | import pytest | ||||||
|  |  | ||||||
|  | from esphome.config_validation import Invalid | ||||||
|  | from esphome.const import CONF_DEVICE_ID, CONF_DISABLED_BY_DEFAULT, CONF_ICON, CONF_NAME | ||||||
|  | from esphome.core import CORE, ID, entity_helpers | ||||||
|  | from esphome.core.entity_helpers import get_base_entity_object_id, setup_entity | ||||||
|  | from esphome.cpp_generator import MockObj | ||||||
|  | from esphome.helpers import sanitize, snake_case | ||||||
|  |  | ||||||
|  | from .common import load_config_from_fixture | ||||||
|  |  | ||||||
|  | # Pre-compiled regex pattern for extracting object IDs from expressions | ||||||
|  | OBJECT_ID_PATTERN = re.compile(r'\.set_object_id\(["\'](.*?)["\']\)') | ||||||
|  |  | ||||||
|  | FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "entity_helpers" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture(autouse=True) | ||||||
|  | def restore_core_state() -> Generator[None, None, None]: | ||||||
|  |     """Save and restore CORE state for tests.""" | ||||||
|  |     original_name = CORE.name | ||||||
|  |     original_friendly_name = CORE.friendly_name | ||||||
|  |     yield | ||||||
|  |     CORE.name = original_name | ||||||
|  |     CORE.friendly_name = original_friendly_name | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_with_entity_name() -> None: | ||||||
|  |     """Test when entity has its own name - should use entity name.""" | ||||||
|  |     # Simple name | ||||||
|  |     assert get_base_entity_object_id("Temperature Sensor", None) == "temperature_sensor" | ||||||
|  |     assert ( | ||||||
|  |         get_base_entity_object_id("Temperature Sensor", "Device Name") | ||||||
|  |         == "temperature_sensor" | ||||||
|  |     ) | ||||||
|  |     # Even with device name, entity name takes precedence | ||||||
|  |     assert ( | ||||||
|  |         get_base_entity_object_id("Temperature Sensor", "Device Name", "Sub Device") | ||||||
|  |         == "temperature_sensor" | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     # Name with special characters | ||||||
|  |     assert ( | ||||||
|  |         get_base_entity_object_id("Temp!@#$%^&*()Sensor", None) | ||||||
|  |         == "temp__________sensor" | ||||||
|  |     ) | ||||||
|  |     assert get_base_entity_object_id("Temp-Sensor_123", None) == "temp-sensor_123" | ||||||
|  |  | ||||||
|  |     # Already snake_case | ||||||
|  |     assert get_base_entity_object_id("temperature_sensor", None) == "temperature_sensor" | ||||||
|  |  | ||||||
|  |     # Mixed case | ||||||
|  |     assert get_base_entity_object_id("TemperatureSensor", None) == "temperaturesensor" | ||||||
|  |     assert get_base_entity_object_id("TEMPERATURE SENSOR", None) == "temperature_sensor" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_empty_name_with_device_name() -> None: | ||||||
|  |     """Test when entity has empty name and is on a sub-device - should use device name.""" | ||||||
|  |     # C++ behavior: when has_own_name is false and device is set, uses device->get_name() | ||||||
|  |     assert ( | ||||||
|  |         get_base_entity_object_id("", "Friendly Device", "Sub Device 1") | ||||||
|  |         == "sub_device_1" | ||||||
|  |     ) | ||||||
|  |     assert ( | ||||||
|  |         get_base_entity_object_id("", "Kitchen Controller", "controller_1") | ||||||
|  |         == "controller_1" | ||||||
|  |     ) | ||||||
|  |     assert get_base_entity_object_id("", None, "Test-Device_123") == "test-device_123" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_empty_name_with_friendly_name() -> None: | ||||||
|  |     """Test when entity has empty name and no device - should use friendly name.""" | ||||||
|  |     # C++ behavior: when has_own_name is false, uses App.get_friendly_name() | ||||||
|  |     assert get_base_entity_object_id("", "Friendly Device") == "friendly_device" | ||||||
|  |     assert get_base_entity_object_id("", "Kitchen Controller") == "kitchen_controller" | ||||||
|  |     assert get_base_entity_object_id("", "Test-Device_123") == "test-device_123" | ||||||
|  |  | ||||||
|  |     # Special characters in friendly name | ||||||
|  |     assert get_base_entity_object_id("", "Device!@#$%") == "device_____" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_empty_name_no_friendly_name() -> None: | ||||||
|  |     """Test when entity has empty name and no friendly name - should use device name.""" | ||||||
|  |     # Test with CORE.name set | ||||||
|  |     CORE.name = "device-name" | ||||||
|  |     assert get_base_entity_object_id("", None) == "device-name" | ||||||
|  |  | ||||||
|  |     CORE.name = "Test Device" | ||||||
|  |     assert get_base_entity_object_id("", None) == "test_device" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_edge_cases() -> None: | ||||||
|  |     """Test edge cases.""" | ||||||
|  |     # Only spaces | ||||||
|  |     assert get_base_entity_object_id("   ", None) == "___" | ||||||
|  |  | ||||||
|  |     # Unicode characters (should be replaced) | ||||||
|  |     assert get_base_entity_object_id("Température", None) == "temp_rature" | ||||||
|  |     assert get_base_entity_object_id("测试", None) == "__" | ||||||
|  |  | ||||||
|  |     # Empty string with empty friendly name (empty friendly name is treated as None) | ||||||
|  |     # Falls back to CORE.name | ||||||
|  |     CORE.name = "device" | ||||||
|  |     assert get_base_entity_object_id("", "") == "device" | ||||||
|  |  | ||||||
|  |     # Very long name (should work fine) | ||||||
|  |     long_name = "a" * 100 + " " + "b" * 100 | ||||||
|  |     expected = "a" * 100 + "_" + "b" * 100 | ||||||
|  |     assert get_base_entity_object_id(long_name, None) == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.parametrize( | ||||||
|  |     ("name", "expected"), | ||||||
|  |     [ | ||||||
|  |         ("Temperature Sensor", "temperature_sensor"), | ||||||
|  |         ("Living Room Light", "living_room_light"), | ||||||
|  |         ("Test-Device_123", "test-device_123"), | ||||||
|  |         ("Special!@#Chars", "special___chars"), | ||||||
|  |         ("UPPERCASE NAME", "uppercase_name"), | ||||||
|  |         ("lowercase name", "lowercase_name"), | ||||||
|  |         ("Mixed Case Name", "mixed_case_name"), | ||||||
|  |         ("   Spaces   ", "___spaces___"), | ||||||
|  |     ], | ||||||
|  | ) | ||||||
|  | def test_matches_cpp_helpers(name: str, expected: str) -> None: | ||||||
|  |     """Test that the logic matches using snake_case and sanitize directly.""" | ||||||
|  |     # For non-empty names, verify our function produces same result as direct snake_case + sanitize | ||||||
|  |     assert get_base_entity_object_id(name, None) == sanitize(snake_case(name)) | ||||||
|  |     assert get_base_entity_object_id(name, None) == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_empty_name_fallback() -> None: | ||||||
|  |     """Test empty name handling which falls back to friendly_name or CORE.name.""" | ||||||
|  |     # Empty name is handled specially - it doesn't just use sanitize(snake_case("")) | ||||||
|  |     # Instead it falls back to friendly_name or CORE.name | ||||||
|  |     assert sanitize(snake_case("")) == ""  # Direct conversion gives empty string | ||||||
|  |     # But our function returns a fallback | ||||||
|  |     CORE.name = "device" | ||||||
|  |     assert get_base_entity_object_id("", None) == "device"  # Uses device name | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_name_add_mac_suffix_behavior() -> None: | ||||||
|  |     """Test behavior related to name_add_mac_suffix. | ||||||
|  |  | ||||||
|  |     In C++, when name_add_mac_suffix is enabled and entity has no name, | ||||||
|  |     get_object_id() returns str_sanitize(str_snake_case(App.get_friendly_name())) | ||||||
|  |     dynamically. Our function always returns the same result since we're | ||||||
|  |     calculating the base for duplicate tracking. | ||||||
|  |     """ | ||||||
|  |     # The function should always return the same result regardless of | ||||||
|  |     # name_add_mac_suffix setting, as we're calculating the base object_id | ||||||
|  |     assert get_base_entity_object_id("", "Test Device") == "test_device" | ||||||
|  |     assert get_base_entity_object_id("Entity Name", "Test Device") == "entity_name" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_priority_order() -> None: | ||||||
|  |     """Test the priority order: entity name > device name > friendly name > CORE.name.""" | ||||||
|  |     CORE.name = "core-device" | ||||||
|  |  | ||||||
|  |     # 1. Entity name has highest priority | ||||||
|  |     assert ( | ||||||
|  |         get_base_entity_object_id("Entity Name", "Friendly Name", "Device Name") | ||||||
|  |         == "entity_name" | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     # 2. Device name is next priority (when entity name is empty) | ||||||
|  |     assert ( | ||||||
|  |         get_base_entity_object_id("", "Friendly Name", "Device Name") == "device_name" | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     # 3. Friendly name is next (when entity and device names are empty) | ||||||
|  |     assert get_base_entity_object_id("", "Friendly Name", None) == "friendly_name" | ||||||
|  |  | ||||||
|  |     # 4. CORE.name is last resort | ||||||
|  |     assert get_base_entity_object_id("", None, None) == "core-device" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.parametrize( | ||||||
|  |     ("name", "friendly_name", "device_name", "expected"), | ||||||
|  |     [ | ||||||
|  |         # name, friendly_name, device_name, expected | ||||||
|  |         ("Living Room Light", None, None, "living_room_light"), | ||||||
|  |         ("", "Kitchen Controller", None, "kitchen_controller"), | ||||||
|  |         ( | ||||||
|  |             "", | ||||||
|  |             "ESP32 Device", | ||||||
|  |             "controller_1", | ||||||
|  |             "controller_1", | ||||||
|  |         ),  # Device name takes precedence | ||||||
|  |         ("GPIO2 Button", None, None, "gpio2_button"), | ||||||
|  |         ("WiFi Signal", "My Device", None, "wifi_signal"), | ||||||
|  |         ("", None, "esp32_node", "esp32_node"), | ||||||
|  |         ("Front Door Sensor", "Home Assistant", "door_controller", "front_door_sensor"), | ||||||
|  |     ], | ||||||
|  | ) | ||||||
|  | def test_real_world_examples( | ||||||
|  |     name: str, friendly_name: str | None, device_name: str | None, expected: str | ||||||
|  | ) -> None: | ||||||
|  |     """Test real-world entity naming scenarios.""" | ||||||
|  |     result = get_base_entity_object_id(name, friendly_name, device_name) | ||||||
|  |     assert result == expected | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_issue_6953_scenarios() -> None: | ||||||
|  |     """Test specific scenarios from issue #6953.""" | ||||||
|  |     # Scenario 1: Multiple empty names on main device with name_add_mac_suffix | ||||||
|  |     # The Python code calculates the base, C++ might append MAC suffix dynamically | ||||||
|  |     CORE.name = "device-name" | ||||||
|  |     CORE.friendly_name = "Friendly Device" | ||||||
|  |  | ||||||
|  |     # All empty names should resolve to same base | ||||||
|  |     assert get_base_entity_object_id("", CORE.friendly_name) == "friendly_device" | ||||||
|  |     assert get_base_entity_object_id("", CORE.friendly_name) == "friendly_device" | ||||||
|  |     assert get_base_entity_object_id("", CORE.friendly_name) == "friendly_device" | ||||||
|  |  | ||||||
|  |     # Scenario 2: Empty names on sub-devices | ||||||
|  |     assert ( | ||||||
|  |         get_base_entity_object_id("", "Main Device", "controller_1") == "controller_1" | ||||||
|  |     ) | ||||||
|  |     assert ( | ||||||
|  |         get_base_entity_object_id("", "Main Device", "controller_2") == "controller_2" | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     # Scenario 3: xyz duplicates | ||||||
|  |     assert get_base_entity_object_id("xyz", None) == "xyz" | ||||||
|  |     assert get_base_entity_object_id("xyz", "Device") == "xyz" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Tests for setup_entity function | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture | ||||||
|  | def setup_test_environment() -> Generator[list[str], None, None]: | ||||||
|  |     """Set up test environment for setup_entity tests.""" | ||||||
|  |     # Set CORE state for tests | ||||||
|  |     CORE.name = "test-device" | ||||||
|  |     CORE.friendly_name = "Test Device" | ||||||
|  |     # Store original add function | ||||||
|  |  | ||||||
|  |     original_add = entity_helpers.add | ||||||
|  |     # Track what gets added | ||||||
|  |     added_expressions: list[str] = [] | ||||||
|  |  | ||||||
|  |     def mock_add(expression: Any) -> Any: | ||||||
|  |         added_expressions.append(str(expression)) | ||||||
|  |         return original_add(expression) | ||||||
|  |  | ||||||
|  |     # Patch add function in entity_helpers module | ||||||
|  |     entity_helpers.add = mock_add | ||||||
|  |     yield added_expressions | ||||||
|  |     # Clean up | ||||||
|  |     entity_helpers.add = original_add | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def extract_object_id_from_expressions(expressions: list[str]) -> str | None: | ||||||
|  |     """Extract the object ID that was set from the generated expressions.""" | ||||||
|  |     for expr in expressions: | ||||||
|  |         # Look for set_object_id calls with regex to handle various formats | ||||||
|  |         # Matches: var.set_object_id("temperature_2") or var.set_object_id('temperature_2') | ||||||
|  |         if match := OBJECT_ID_PATTERN.search(expr): | ||||||
|  |             return match.group(1) | ||||||
|  |     return None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_setup_entity_no_duplicates(setup_test_environment: list[str]) -> None: | ||||||
|  |     """Test setup_entity with unique names.""" | ||||||
|  |  | ||||||
|  |     added_expressions = setup_test_environment | ||||||
|  |  | ||||||
|  |     # Create mock entities | ||||||
|  |     var1 = MockObj("sensor1") | ||||||
|  |     var2 = MockObj("sensor2") | ||||||
|  |  | ||||||
|  |     # Set up first entity | ||||||
|  |     config1 = { | ||||||
|  |         CONF_NAME: "Temperature", | ||||||
|  |         CONF_DISABLED_BY_DEFAULT: False, | ||||||
|  |     } | ||||||
|  |     await setup_entity(var1, config1, "sensor") | ||||||
|  |  | ||||||
|  |     # Get object ID from first entity | ||||||
|  |     object_id1 = extract_object_id_from_expressions(added_expressions) | ||||||
|  |     assert object_id1 == "temperature" | ||||||
|  |  | ||||||
|  |     # Clear for next entity | ||||||
|  |     added_expressions.clear() | ||||||
|  |  | ||||||
|  |     # Set up second entity with different name | ||||||
|  |     config2 = { | ||||||
|  |         CONF_NAME: "Humidity", | ||||||
|  |         CONF_DISABLED_BY_DEFAULT: False, | ||||||
|  |     } | ||||||
|  |     await setup_entity(var2, config2, "sensor") | ||||||
|  |  | ||||||
|  |     # Get object ID from second entity | ||||||
|  |     object_id2 = extract_object_id_from_expressions(added_expressions) | ||||||
|  |     assert object_id2 == "humidity" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_setup_entity_different_platforms( | ||||||
|  |     setup_test_environment: list[str], | ||||||
|  | ) -> None: | ||||||
|  |     """Test that same name on different platforms doesn't conflict.""" | ||||||
|  |  | ||||||
|  |     added_expressions = setup_test_environment | ||||||
|  |  | ||||||
|  |     # Create mock entities | ||||||
|  |     sensor = MockObj("sensor1") | ||||||
|  |     binary_sensor = MockObj("binary_sensor1") | ||||||
|  |     text_sensor = MockObj("text_sensor1") | ||||||
|  |  | ||||||
|  |     config = { | ||||||
|  |         CONF_NAME: "Status", | ||||||
|  |         CONF_DISABLED_BY_DEFAULT: False, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     # Set up entities on different platforms | ||||||
|  |     platforms = [ | ||||||
|  |         (sensor, "sensor"), | ||||||
|  |         (binary_sensor, "binary_sensor"), | ||||||
|  |         (text_sensor, "text_sensor"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     object_ids: list[str] = [] | ||||||
|  |     for var, platform in platforms: | ||||||
|  |         added_expressions.clear() | ||||||
|  |         await setup_entity(var, config, platform) | ||||||
|  |         object_id = extract_object_id_from_expressions(added_expressions) | ||||||
|  |         object_ids.append(object_id) | ||||||
|  |  | ||||||
|  |     # All should get base object ID without suffix | ||||||
|  |     assert all(obj_id == "status" for obj_id in object_ids) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture | ||||||
|  | def mock_get_variable() -> Generator[dict[ID, MockObj], None, None]: | ||||||
|  |     """Mock get_variable to return test devices.""" | ||||||
|  |     devices = {} | ||||||
|  |     original_get_variable = entity_helpers.get_variable | ||||||
|  |  | ||||||
|  |     async def _mock_get_variable(device_id: ID) -> MockObj: | ||||||
|  |         if device_id in devices: | ||||||
|  |             return devices[device_id] | ||||||
|  |         return await original_get_variable(device_id) | ||||||
|  |  | ||||||
|  |     entity_helpers.get_variable = _mock_get_variable | ||||||
|  |     yield devices | ||||||
|  |     # Clean up | ||||||
|  |     entity_helpers.get_variable = original_get_variable | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_setup_entity_with_devices( | ||||||
|  |     setup_test_environment: list[str], mock_get_variable: dict[ID, MockObj] | ||||||
|  | ) -> None: | ||||||
|  |     """Test that same name on different devices doesn't conflict.""" | ||||||
|  |     added_expressions = setup_test_environment | ||||||
|  |  | ||||||
|  |     # Create mock devices | ||||||
|  |     device1_id = ID("device1", type="Device") | ||||||
|  |     device2_id = ID("device2", type="Device") | ||||||
|  |     device1 = MockObj("device1_obj") | ||||||
|  |     device2 = MockObj("device2_obj") | ||||||
|  |  | ||||||
|  |     # Register devices with the mock | ||||||
|  |     mock_get_variable[device1_id] = device1 | ||||||
|  |     mock_get_variable[device2_id] = device2 | ||||||
|  |  | ||||||
|  |     # Create sensors with same name on different devices | ||||||
|  |     sensor1 = MockObj("sensor1") | ||||||
|  |     sensor2 = MockObj("sensor2") | ||||||
|  |  | ||||||
|  |     config1 = { | ||||||
|  |         CONF_NAME: "Temperature", | ||||||
|  |         CONF_DEVICE_ID: device1_id, | ||||||
|  |         CONF_DISABLED_BY_DEFAULT: False, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     config2 = { | ||||||
|  |         CONF_NAME: "Temperature", | ||||||
|  |         CONF_DEVICE_ID: device2_id, | ||||||
|  |         CONF_DISABLED_BY_DEFAULT: False, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     # Get object IDs | ||||||
|  |     object_ids: list[str] = [] | ||||||
|  |     for var, config in [(sensor1, config1), (sensor2, config2)]: | ||||||
|  |         added_expressions.clear() | ||||||
|  |         await setup_entity(var, config, "sensor") | ||||||
|  |         object_id = extract_object_id_from_expressions(added_expressions) | ||||||
|  |         object_ids.append(object_id) | ||||||
|  |  | ||||||
|  |     # Both should get base object ID without suffix (different devices) | ||||||
|  |     assert object_ids[0] == "temperature" | ||||||
|  |     assert object_ids[1] == "temperature" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_setup_entity_empty_name(setup_test_environment: list[str]) -> None: | ||||||
|  |     """Test setup_entity with empty entity name.""" | ||||||
|  |  | ||||||
|  |     added_expressions = setup_test_environment | ||||||
|  |  | ||||||
|  |     var = MockObj("sensor1") | ||||||
|  |  | ||||||
|  |     config = { | ||||||
|  |         CONF_NAME: "", | ||||||
|  |         CONF_DISABLED_BY_DEFAULT: False, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     await setup_entity(var, config, "sensor") | ||||||
|  |  | ||||||
|  |     object_id = extract_object_id_from_expressions(added_expressions) | ||||||
|  |     # Should use friendly name | ||||||
|  |     assert object_id == "test_device" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_setup_entity_special_characters( | ||||||
|  |     setup_test_environment: list[str], | ||||||
|  | ) -> None: | ||||||
|  |     """Test setup_entity with names containing special characters.""" | ||||||
|  |  | ||||||
|  |     added_expressions = setup_test_environment | ||||||
|  |  | ||||||
|  |     var = MockObj("sensor1") | ||||||
|  |  | ||||||
|  |     config = { | ||||||
|  |         CONF_NAME: "Temperature Sensor!", | ||||||
|  |         CONF_DISABLED_BY_DEFAULT: False, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     await setup_entity(var, config, "sensor") | ||||||
|  |     object_id = extract_object_id_from_expressions(added_expressions) | ||||||
|  |  | ||||||
|  |     # Special characters should be sanitized | ||||||
|  |     assert object_id == "temperature_sensor_" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_setup_entity_with_icon(setup_test_environment: list[str]) -> None: | ||||||
|  |     """Test setup_entity sets icon correctly.""" | ||||||
|  |  | ||||||
|  |     added_expressions = setup_test_environment | ||||||
|  |  | ||||||
|  |     var = MockObj("sensor1") | ||||||
|  |  | ||||||
|  |     config = { | ||||||
|  |         CONF_NAME: "Temperature", | ||||||
|  |         CONF_DISABLED_BY_DEFAULT: False, | ||||||
|  |         CONF_ICON: "mdi:thermometer", | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     await setup_entity(var, config, "sensor") | ||||||
|  |  | ||||||
|  |     # Check icon was set | ||||||
|  |     assert any( | ||||||
|  |         'sensor1.set_icon("mdi:thermometer")' in expr for expr in added_expressions | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_setup_entity_disabled_by_default( | ||||||
|  |     setup_test_environment: list[str], | ||||||
|  | ) -> None: | ||||||
|  |     """Test setup_entity sets disabled_by_default correctly.""" | ||||||
|  |  | ||||||
|  |     added_expressions = setup_test_environment | ||||||
|  |  | ||||||
|  |     var = MockObj("sensor1") | ||||||
|  |  | ||||||
|  |     config = { | ||||||
|  |         CONF_NAME: "Temperature", | ||||||
|  |         CONF_DISABLED_BY_DEFAULT: True, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     await setup_entity(var, config, "sensor") | ||||||
|  |  | ||||||
|  |     # Check disabled_by_default was set | ||||||
|  |     assert any( | ||||||
|  |         "sensor1.set_disabled_by_default(true)" in expr for expr in added_expressions | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_entity_duplicate_validator() -> None: | ||||||
|  |     """Test the entity_duplicate_validator function.""" | ||||||
|  |     from esphome.core.entity_helpers import entity_duplicate_validator | ||||||
|  |  | ||||||
|  |     # Reset CORE unique_ids for clean test | ||||||
|  |     CORE.unique_ids.clear() | ||||||
|  |  | ||||||
|  |     # Create validator for sensor platform | ||||||
|  |     validator = entity_duplicate_validator("sensor") | ||||||
|  |  | ||||||
|  |     # First entity should pass | ||||||
|  |     config1 = {CONF_NAME: "Temperature"} | ||||||
|  |     validated1 = validator(config1) | ||||||
|  |     assert validated1 == config1 | ||||||
|  |     assert ("", "sensor", "temperature") in CORE.unique_ids | ||||||
|  |  | ||||||
|  |     # Second entity with different name should pass | ||||||
|  |     config2 = {CONF_NAME: "Humidity"} | ||||||
|  |     validated2 = validator(config2) | ||||||
|  |     assert validated2 == config2 | ||||||
|  |     assert ("", "sensor", "humidity") in CORE.unique_ids | ||||||
|  |  | ||||||
|  |     # Duplicate entity should fail | ||||||
|  |     config3 = {CONF_NAME: "Temperature"} | ||||||
|  |     with pytest.raises( | ||||||
|  |         Invalid, match=r"Duplicate sensor entity with name 'Temperature' found" | ||||||
|  |     ): | ||||||
|  |         validator(config3) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_entity_duplicate_validator_with_devices() -> None: | ||||||
|  |     """Test entity_duplicate_validator with devices.""" | ||||||
|  |     from esphome.core.entity_helpers import entity_duplicate_validator | ||||||
|  |  | ||||||
|  |     # Reset CORE unique_ids for clean test | ||||||
|  |     CORE.unique_ids.clear() | ||||||
|  |  | ||||||
|  |     # Create validator for sensor platform | ||||||
|  |     validator = entity_duplicate_validator("sensor") | ||||||
|  |  | ||||||
|  |     # Create mock device IDs | ||||||
|  |     device1 = ID("device1", type="Device") | ||||||
|  |     device2 = ID("device2", type="Device") | ||||||
|  |  | ||||||
|  |     # Same name on different devices should pass | ||||||
|  |     config1 = {CONF_NAME: "Temperature", CONF_DEVICE_ID: device1} | ||||||
|  |     validated1 = validator(config1) | ||||||
|  |     assert validated1 == config1 | ||||||
|  |     assert ("device1", "sensor", "temperature") in CORE.unique_ids | ||||||
|  |  | ||||||
|  |     config2 = {CONF_NAME: "Temperature", CONF_DEVICE_ID: device2} | ||||||
|  |     validated2 = validator(config2) | ||||||
|  |     assert validated2 == config2 | ||||||
|  |     assert ("device2", "sensor", "temperature") in CORE.unique_ids | ||||||
|  |  | ||||||
|  |     # Duplicate on same device should fail | ||||||
|  |     config3 = {CONF_NAME: "Temperature", CONF_DEVICE_ID: device1} | ||||||
|  |     with pytest.raises( | ||||||
|  |         Invalid, | ||||||
|  |         match=r"Duplicate sensor entity with name 'Temperature' found on device 'device1'", | ||||||
|  |     ): | ||||||
|  |         validator(config3) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_duplicate_entity_yaml_validation( | ||||||
|  |     yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] | ||||||
|  | ) -> None: | ||||||
|  |     """Test that duplicate entity names are caught during YAML config validation.""" | ||||||
|  |     result = load_config_from_fixture(yaml_file, "duplicate_entity.yaml", FIXTURES_DIR) | ||||||
|  |     assert result is None | ||||||
|  |  | ||||||
|  |     # Check for the duplicate entity error message | ||||||
|  |     captured = capsys.readouterr() | ||||||
|  |     assert "Duplicate sensor entity with name 'Temperature' found" in captured.out | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_duplicate_entity_with_devices_yaml_validation( | ||||||
|  |     yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] | ||||||
|  | ) -> None: | ||||||
|  |     """Test duplicate entity validation with devices.""" | ||||||
|  |     result = load_config_from_fixture( | ||||||
|  |         yaml_file, "duplicate_entity_with_devices.yaml", FIXTURES_DIR | ||||||
|  |     ) | ||||||
|  |     assert result is None | ||||||
|  |  | ||||||
|  |     # Check for the duplicate entity error message with device | ||||||
|  |     captured = capsys.readouterr() | ||||||
|  |     assert ( | ||||||
|  |         "Duplicate sensor entity with name 'Temperature' found on device 'device1'" | ||||||
|  |         in captured.out | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_entity_different_platforms_yaml_validation( | ||||||
|  |     yaml_file: Callable[[str], str], | ||||||
|  | ) -> None: | ||||||
|  |     """Test that same entity name on different platforms is allowed.""" | ||||||
|  |     result = load_config_from_fixture( | ||||||
|  |         yaml_file, "entity_different_platforms.yaml", FIXTURES_DIR | ||||||
|  |     ) | ||||||
|  |     # This should succeed | ||||||
|  |     assert result is not None | ||||||
							
								
								
									
										10
									
								
								tests/unit_tests/fixtures/core/config/area_id_collision.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								tests/unit_tests/fixtures/core/config/area_id_collision.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | esphome: | ||||||
|  |   name: test-collision | ||||||
|  |   area: | ||||||
|  |     id: duplicate_id | ||||||
|  |     name: Area 1 | ||||||
|  |   areas: | ||||||
|  |     - id: duplicate_id | ||||||
|  |       name: Area 2 | ||||||
|  |  | ||||||
|  | host: | ||||||
| @@ -0,0 +1,10 @@ | |||||||
|  | esphome: | ||||||
|  |   name: test | ||||||
|  |   areas: | ||||||
|  |     - id: test_2258 | ||||||
|  |       name: "Area 1" | ||||||
|  |     - id: d6ka | ||||||
|  |       name: "Area 2" | ||||||
|  |  | ||||||
|  | esp32: | ||||||
|  |   board: esp32dev | ||||||
| @@ -0,0 +1,10 @@ | |||||||
|  | esphome: | ||||||
|  |   name: test | ||||||
|  |   devices: | ||||||
|  |     - id: duplicate_device | ||||||
|  |       name: "Device 1" | ||||||
|  |     - id: duplicate_device | ||||||
|  |       name: "Device 2" | ||||||
|  |  | ||||||
|  | esp32: | ||||||
|  |   board: esp32dev | ||||||
| @@ -0,0 +1,10 @@ | |||||||
|  | esphome: | ||||||
|  |   name: test | ||||||
|  |   devices: | ||||||
|  |     - id: test_2258 | ||||||
|  |       name: "Device 1" | ||||||
|  |     - id: d6ka | ||||||
|  |       name: "Device 2" | ||||||
|  |  | ||||||
|  | esp32: | ||||||
|  |   board: esp32dev | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user