Compare commits

..

176 Commits

Author SHA1 Message Date
J. Nick Koston
4bdd08887e use a common that does not have dupes on dev 2025-06-25 00:50:18 +02:00
J. Nick Koston
1fd8ebf386 update tests now that duplicate names are validated 2025-06-25 00:35:38 +02:00
J. Nick Koston
d2fc3e749c update tests now that duplicate names are validated 2025-06-25 00:34:50 +02:00
J. Nick Koston
71fbcbceaf update tests now that duplicate names are validated 2025-06-25 00:34:27 +02:00
J. Nick Koston
27347b2088 update tests now that duplicate names are validated 2025-06-25 00:34:04 +02:00
J. Nick Koston
599993d1a5 update tests now that duplicate names are validated 2025-06-25 00:22:51 +02:00
J. Nick Koston
bf359cb8e3 update tests now that duplicate names are validated 2025-06-25 00:20:51 +02:00
J. Nick Koston
509a704410 update tests now that duplicate names are validated 2025-06-25 00:19:32 +02:00
J. Nick Koston
1f48e2b01f update tests now that duplicate names are validated 2025-06-25 00:18:40 +02:00
J. Nick Koston
83613726d1 fix 2025-06-25 00:04:07 +02:00
J. Nick Koston
796e12bd70 Merge branch 'dev' into multi_device 2025-06-24 23:54:24 +02:00
J. Nick Koston
ddbe17d3f6 fixes 2025-06-24 23:40:16 +02:00
J. Nick Koston
591ec36f4a fixes 2025-06-24 23:37:58 +02:00
J. Nick Koston
41eceb72ef preen 2025-06-24 23:28:06 +02:00
J. Nick Koston
0a5f094025 cleanup 2025-06-24 23:25:46 +02:00
J. Nick Koston
ca0f3ba262 cleanup 2025-06-24 23:23:59 +02:00
J. Nick Koston
30f4e782db cleanup 2025-06-24 23:23:35 +02:00
J. Nick Koston
192158ef1a cleanup 2025-06-24 23:22:18 +02:00
J. Nick Koston
602456db40 cleanup 2025-06-24 23:13:45 +02:00
J. Nick Koston
536e45668f migrate 2025-06-24 23:09:08 +02:00
J. Nick Koston
10bf05ab0d migrate 2025-06-24 22:59:46 +02:00
J. Nick Koston
5ad1af69e4 migrate 2025-06-24 22:57:10 +02:00
J. Nick Koston
48f2911434 raise 2025-06-24 22:18:29 +02:00
J. Nick Koston
ac3598f12a cleanup 2025-06-24 18:07:58 +02:00
J. Nick Koston
66201be5ca preen 2025-06-24 18:00:10 +02:00
J. Nick Koston
ac0b0b652e cleanup 2025-06-24 17:55:58 +02:00
J. Nick Koston
d89ee2df42 Update esphome/core/application.h 2025-06-24 17:52:13 +02:00
J. Nick Koston
418e248e5e cleanup 2025-06-24 17:51:05 +02:00
J. Nick Koston
8c2b141049 cleanup 2025-06-24 17:41:40 +02:00
J. Nick Koston
2f8e07302b Update esphome/core/entity_base.cpp 2025-06-24 17:10:06 +02:00
J. Nick Koston
c3776240b6 fixes 2025-06-24 17:03:23 +02:00
J. Nick Koston
e370872ec1 fix conflicts 2025-06-24 16:13:34 +02:00
Jesse Hills
d4e978369a Store reference to device on EntityBase
This is so we can get the name of the device to use as part of the object id and to internally set the name for logging.
2025-06-24 19:56:30 +12:00
Jesse Hills
8d5d7f5237 Merge branch 'dev' into multi_device 2025-06-24 16:02:03 +12:00
Jesse Hills
7ad6dab383 [mqtt] Don't wait for connection unless configured to (#8933) 2025-06-24 13:31:38 +12:00
Clyde Stubbs
612c8d5841 [lvgl] Fix dangling pointer issue with qrcode (#9190) 2025-06-24 09:43:40 +10:00
Cody Cutrer
a35e476be5 [opt3001] New component (#6625)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-06-23 14:31:20 -05:00
Jesse Hills
87a7157fc4 Merge branch 'release' into dev 2025-06-24 07:28:40 +12:00
Jesse Hills
fa34adbf6c Merge pull request #9185 from esphome/bump-2025.6.1
2025.6.1
2025-06-24 07:27:59 +12:00
dependabot[bot]
ac942e0670 Bump aioesphomeapi from 33.1.0 to 33.1.1 (#9187) 2025-06-23 19:58:32 +02:00
Jesse Hills
22e360d479 Bump version to 2025.6.1 2025-06-23 23:32:22 +12:00
myhomeiot
649936200e Restore access to BLEScanResult as get_scan_result (#9148) 2025-06-23 23:32:22 +12:00
rwrozelle
5d6e690c12 Fixes for setup of OpenThread either using TLV or entering Credentials directly (#9157)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-06-23 23:32:22 +12:00
Jesse Hills
2f2ecadae7 [config validation] Add more ip address / network validators (#9181) 2025-06-23 23:32:22 +12:00
J. Nick Koston
6dfb9eba61 Fix missing BLE GAP events causing RSSI sensor and beacon failures (#9138) 2025-06-23 23:32:22 +12:00
Edward Firmo
24587fe875 [nextion] Fix command spacing double timing and response blocking issues (#9134) 2025-06-23 23:32:22 +12:00
J. Nick Koston
a1aebe6a2c Eliminate memory fragmentation with BLE event pool (#9101) 2025-06-23 23:32:22 +12:00
Gustavo Ambrozio
2ad266582f [online_image] Allow suppressing update on url change (#8885) 2025-06-23 20:40:07 +10:00
JonasB2497
1a47164876 Feature fontmetrics (#8978) 2025-06-23 14:47:47 +10:00
myhomeiot
cd22723623 Restore access to BLEScanResult as get_scan_result (#9148) 2025-06-23 15:42:20 +12:00
rwrozelle
aecaffa2f5 Fixes for setup of OpenThread either using TLV or entering Credentials directly (#9157)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-06-23 15:41:29 +12:00
Jesse Hills
87df3596a2 [config validation] Add more ip address / network validators (#9181) 2025-06-23 15:41:06 +12:00
Clyde Stubbs
41c7852128 [lvgl] Use styles instead of object properties for themes (#9116) 2025-06-23 14:25:26 +12:00
Clyde Stubbs
78ec9856fb [lvgl] Add start_value to bar; make values templatable and updateable (#9056) 2025-06-23 14:23:41 +12:00
J. Nick Koston
2a45467bf6 Pre-reserve looping components vector to reduce memory allocations (#9177) 2025-06-23 14:10:09 +12:00
J. Nick Koston
7fc5bfd787 Reduce RAM usage for scheduled tasks (#9180) 2025-06-23 14:09:34 +12:00
J. Nick Koston
04f592ba6d Fix slow noise handshake by reading multiple messages per loop (#9130) 2025-06-23 14:07:53 +12:00
J. Nick Koston
59889a6286 Reduce Logger memory usage by optimizing variable sizes (#9161) 2025-06-23 14:06:02 +12:00
J. Nick Koston
20f946ccaf Merge branch 'dev' into multi_device 2025-06-23 00:32:09 +02:00
Jesse Hills
e5e972231c Update testing 2025-06-23 10:26:31 +12:00
Jesse Hills
dc5cbd4df8 [const] Move `CONF_DEVICES to const.py` (#9179) 2025-06-23 09:54:49 +12:00
Jesse Hills
754d2874e7 `this->` 2025-06-23 09:21:29 +12:00
Jesse Hills
06de58ff8b Dont need to warning about simple string area
A single device in a single area can have a simple string as the area
2025-06-23 09:20:53 +12:00
Edward Firmo
7ab9083d77 [nextion] Revert to millis() on recv_ret_string_ (#9168) 2025-06-22 20:56:50 +00:00
J. Nick Koston
5857f7b9a7 Merge remote-tracking branch 'dala318/multi_device' into multi_device 2025-06-22 21:55:46 +02:00
J. Nick Koston
a5ea0cd41f remove unreachable code 2025-06-22 21:55:23 +02:00
J. Nick Koston
d677934417 Merge branch 'dev' into multi_device 2025-06-22 21:45:28 +02:00
J. Nick Koston
ba87a0b63c cleanups 2025-06-22 21:32:20 +02:00
J. Nick Koston
b725bb3dd1 lint 2025-06-22 21:28:16 +02:00
J. Nick Koston
c34ba3deb5 lint 2025-06-22 21:25:55 +02:00
J. Nick Koston
68b13340fb lint 2025-06-22 21:24:17 +02:00
J. Nick Koston
8831999ea6 lint 2025-06-22 21:23:41 +02:00
J. Nick Koston
c1853f8b84 document design decisions 2025-06-22 21:21:29 +02:00
J. Nick Koston
2b9b7e2853 validation should happen sooner 2025-06-22 21:18:04 +02:00
J. Nick Koston
d3b18debf9 validate sooner 2025-06-22 21:06:33 +02:00
J. Nick Koston
b01eb28d42 validate sooner 2025-06-22 21:05:15 +02:00
J. Nick Koston
02019dd16c validate sooner 2025-06-22 21:04:42 +02:00
J. Nick Koston
7be12f5ff6 validate sooner 2025-06-22 20:59:54 +02:00
J. Nick Koston
a90d59b6ba validate sooner 2025-06-22 20:59:07 +02:00
J. Nick Koston
25ed7c890b cleanups 2025-06-22 20:03:02 +02:00
J. Nick Koston
85e3b63f05 adjust 2025-06-22 19:49:12 +02:00
J. Nick Koston
a37bac1956 add files 2025-06-22 19:47:19 +02:00
J. Nick Koston
818a978dfc units 2025-06-22 19:40:53 +02:00
J. Nick Koston
180aeb7d8e simplify 2025-06-22 13:50:29 +02:00
J. Nick Koston
0764fa7292 simplify 2025-06-22 13:48:27 +02:00
J. Nick Koston
17bf533ed7 simplify 2025-06-22 13:44:05 +02:00
J. Nick Koston
d7eae1c1a0 simplify 2025-06-22 13:43:52 +02:00
J. Nick Koston
7f2d979255 preen 2025-06-22 13:39:12 +02:00
J. Nick Koston
46b419ea8b preen 2025-06-22 13:38:14 +02:00
J. Nick Koston
b30b527ff9 one more place to check 2025-06-22 13:37:30 +02:00
J. Nick Koston
41b1bfc504 legacy test 2025-06-22 13:37:01 +02:00
J. Nick Koston
f4f14a7507 fixes 2025-06-22 13:29:49 +02:00
J. Nick Koston
61c29213a7 fixes 2025-06-22 13:29:41 +02:00
J. Nick Koston
e6d7639209 Merge branch 'dev' into multi_device 2025-06-22 13:03:16 +02:00
J. Nick Koston
3c07a186b2 Merge remote-tracking branch 'dala318/multi_device' into multi_device 2025-06-22 13:02:48 +02:00
J. Nick Koston
8a725250a9 Merge branch 'dev' into multi_device 2025-06-22 12:32:44 +02:00
J. Nick Koston
502b8a6073 fixes 2025-06-22 12:32:25 +02:00
J. Nick Koston
b03e3b8d4a fixes 2025-06-22 10:07:05 +02:00
J. Nick Koston
a98e34d190 handle collisions 2025-06-22 10:02:59 +02:00
J. Nick Koston
bf8d8b6e63 handle collisions 2025-06-22 10:01:53 +02:00
J. Nick Koston
57599f7a98 handle collisions 2025-06-22 10:00:31 +02:00
J. Nick Koston
ffccce7ffc handle collisions 2025-06-22 09:58:12 +02:00
J. Nick Koston
221e3c6c9c preen 2025-06-21 18:09:16 +02:00
J. Nick Koston
fb1679d572 preen 2025-06-21 18:07:45 +02:00
J. Nick Koston
c19065f112 preen 2025-06-21 18:02:32 +02:00
J. Nick Koston
f2b04a077e preen 2025-06-21 18:01:12 +02:00
J. Nick Koston
8e7841c880 preen 2025-06-21 18:00:17 +02:00
J. Nick Koston
1873490b24 preen 2025-06-21 17:57:36 +02:00
J. Nick Koston
4d231953f4 preen 2025-06-21 17:57:10 +02:00
J. Nick Koston
aa4c399657 reverse space in vectors 2025-06-21 17:36:25 +02:00
J. Nick Koston
1f99d18982 reverse space in vectors 2025-06-21 17:34:08 +02:00
J. Nick Koston
be37178ef8 make areas and devices consistant 2025-06-21 17:32:11 +02:00
J. Nick Koston
fad86c655e make areas and devices consistant 2025-06-21 17:30:17 +02:00
J. Nick Koston
4a7958586e make areas and devices consistant 2025-06-21 17:19:16 +02:00
J. Nick Koston
f44ecd0891 make areas and devices consistant 2025-06-21 17:18:23 +02:00
J. Nick Koston
3d0392d668 make areas and devices consistant 2025-06-21 17:17:29 +02:00
J. Nick Koston
d300d2605b make areas and devices consistant 2025-06-21 17:13:04 +02:00
J. Nick Koston
66cce6a2f2 make areas and devices consistant 2025-06-21 17:12:25 +02:00
J. Nick Koston
65e3c6bfbb make areas and devices consistant 2025-06-21 17:12:00 +02:00
J. Nick Koston
2a39060912 Merge remote-tracking branch 'upstream/dev' into multi_device 2025-06-21 17:06:11 +02:00
J. Nick Koston
8714e80978 make areas and devices consistant 2025-06-21 17:05:46 +02:00
J. Nick Koston
98de53f60b migrate to using same area info for top level and sub devices 2025-06-21 16:47:03 +02:00
J. Nick Koston
41e11e9a0e migrate to using same area info for top level and sub devices 2025-06-21 16:43:48 +02:00
J. Nick Koston
e7a4eac8bd migrate to using same area info for top level and sub devices 2025-06-21 16:42:05 +02:00
J. Nick Koston
1589a131db migrate to using same area info for top level and sub devices 2025-06-21 16:39:07 +02:00
J. Nick Koston
7d84f0e650 migrate to using same area info for top level and sub devices 2025-06-21 16:37:21 +02:00
J. Nick Koston
86fb0e317f fixes 2025-06-21 15:22:35 +02:00
J. Nick Koston
32088d5ef7 revert 2025-06-21 13:35:32 +02:00
J. Nick Koston
63de88dd57 fixes 2025-06-21 13:33:29 +02:00
J. Nick Koston
153a6440dc cleanups to address review comments 2025-06-21 13:20:59 +02:00
J. Nick Koston
8937ed2269 cleanups to address review comments 2025-06-21 13:18:25 +02:00
J. Nick Koston
02e922b56f cleanups to address review comments 2025-06-21 13:16:42 +02:00
J. Nick Koston
bf9e901ab9 cleanups to address review comments 2025-06-21 13:13:44 +02:00
J. Nick Koston
1234ef8de2 Merge remote-tracking branch 'upstream/dev' into multi_device 2025-06-21 12:13:54 +02:00
Daniel Vikstrom
57f4067fbf Move fnv1a_32bit_hash to helpers 2025-06-02 14:42:39 +02:00
Daniel Vikstrom
f4a9221232 Change hash method 2025-06-02 08:31:06 +02:00
J. Nick Koston
3d4a75148d Merge branch 'dev' into multi_device 2025-05-31 10:27:31 -05:00
J. Nick Koston
c2c5bd844d Merge branch 'dev' into multi_device 2025-05-29 13:43:21 -05:00
Daniel Vikstrom
9624efa21e Fix proto generation and clang 2025-05-22 14:18:46 +02:00
DanielV
831638210d Merge branch 'dev' into multi_device 2025-05-22 08:41:54 +02:00
Daniel Vikström
a59a8c563e Attempt fixing circular import by lazy import 2025-05-06 12:30:04 +02:00
Daniel Vikström
856829bcbb More namespace and import fixes 2025-05-06 12:05:45 +02:00
Daniel Vikström
dd2b931f61 Fix namespace error 2025-05-06 11:46:23 +02:00
Daniel Vikström
39beccbbb0 remove from CODEOWNERS 2025-05-06 10:50:09 +02:00
Daniel Vikström
ff626b428f Attempt moving it to esphome config section 2025-05-06 10:48:26 +02:00
Daniel Vikström
3915e1f012 Revert "Improve stability for unrelated test"
This reverts commit 3922950951.
2025-05-06 03:36:03 +02:00
Daniel Vikström
7b460b6224 Restore ci-api-proto.yml 2025-05-06 03:34:33 +02:00
Daniel Vikström
8fb8e79730 Fix clang 2025-05-06 03:20:22 +02:00
Daniel Vikström
79bbc475f4 Fix generated files and revert entity config to device_id 2025-05-06 03:05:00 +02:00
Daniel Vikström
cef023283b Fix generated files 2025-05-06 02:55:44 +02:00
Daniel Vikström
d4fda79ada Attempt to replace device_id:str with device_uid:uint32 2025-05-06 02:07:59 +02:00
DanielV
ff0bdcf4cd Merge branch 'dev' into multi_device 2025-05-06 00:48:23 +02:00
DanielV
bfbc313144 Merge branch 'dev' into multi_device 2025-04-22 14:28:51 +02:00
Daniel Vikström
31f2376f15 Rename ref in codegen 2025-04-22 14:03:07 +02:00
DanielV
f76ecb6604 Merge pull request #10 from dala318/multi_device_2
Sub Devices (all entities)
2025-04-22 08:49:28 +02:00
Daniel Vikström
298cc58433 Activate the rest of entities 2025-04-19 23:18:26 +02:00
Daniel Vikström
825c0593e1 Fix generated code after merge 2025-04-19 19:07:50 +02:00
DanielV
87ed1dc3e3 Merge branch 'dev' into multi_device 2025-04-19 18:58:09 +02:00
DanielV
67e9db021c Merge branch 'dev' into multi_device 2025-04-14 22:21:50 +02:00
Daniel Vikström
3922950951 Improve stability for unrelated test 2025-04-14 21:37:27 +02:00
DanielV
9c4aa0ba53 Merge branch 'dev' into multi_device 2025-04-11 13:19:52 +02:00
Daniel Vikström
f5f1651b31 Fix clang 2025-04-10 09:35:08 +02:00
Daniel Vikström
32f4e4ca13 Cleaning up 2025-04-09 19:20:28 +02:00
Daniel Vikström
962e0c4c33 Make it a Class but only use the id in entities 2025-04-09 19:09:31 +02:00
Daniel Vikström
2c01bc5795 Fix clang-tidy 2025-04-09 15:22:40 +02:00
Daniel Vikström
0651f7cb3c Work on sub-device creation 2025-04-09 01:39:24 +02:00
Daniel Vikström
01ac59ce2a Store proto with all additions but commented out 2025-04-09 01:18:42 +02:00
Daniel Vikström
c1fd597757 Add CODEOWNER 2025-04-09 01:12:14 +02:00
Daniel Vikström
e79e244eee Fix generated proto-files 2025-04-09 01:09:45 +02:00
Daniel Vikström
68ecc08111 Register device_id to entity and separate struct for all device info 2025-04-09 00:11:05 +02:00
Daniel Vikström
3b5fbc359f Formating updates 2025-04-08 22:21:11 +02:00
Daniel Vikström
583e5ea47f Add code-owner tag 2025-04-08 22:21:08 +02:00
Daniel Vikström
7b647c3fae Add a single test 2025-04-08 22:21:07 +02:00
Daniel Vikström
a8b76c617c Some basic chain working 2025-04-08 22:07:09 +02:00
Daniel Vikström
1bd8985dff Add a device component 2025-04-08 22:00:09 +02:00
Daniel Vikström
25b5a6c4ae Add device_id to entity_base 2025-04-08 22:00:06 +02:00
125 changed files with 3448 additions and 464 deletions

View File

@@ -323,6 +323,7 @@ esphome/components/one_wire/* @ssieb
esphome/components/online_image/* @clydebarrow @guillempages
esphome/components/opentherm/* @olegtarasov
esphome/components/openthread/* @mrene
esphome/components/opt3001/* @ccutrer
esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/packet_transport/* @clydebarrow

View File

@@ -14,8 +14,8 @@ from esphome.const import (
CONF_WEB_SERVER,
)
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_helpers import setup_entity
CODEOWNERS = ["@grahambrown11", "@hwstar"]
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(
class_: MockObjClass,
*,
@@ -190,7 +193,7 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id(
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, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)

View File

@@ -188,6 +188,17 @@ message DeviceInfoRequest {
// 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 {
option (id) = 10;
option (source) = SOURCE_SERVER;
@@ -236,6 +247,12 @@ message DeviceInfoResponse {
// Supports receiving and saving api encryption key
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 {
@@ -280,6 +297,7 @@ message ListEntitiesBinarySensorResponse {
bool disabled_by_default = 7;
string icon = 8;
EntityCategory entity_category = 9;
uint32 device_id = 10;
}
message BinarySensorStateResponse {
option (id) = 21;
@@ -315,6 +333,7 @@ message ListEntitiesCoverResponse {
string icon = 10;
EntityCategory entity_category = 11;
bool supports_stop = 12;
uint32 device_id = 13;
}
enum LegacyCoverState {
@@ -388,6 +407,7 @@ message ListEntitiesFanResponse {
string icon = 10;
EntityCategory entity_category = 11;
repeated string supported_preset_modes = 12;
uint32 device_id = 13;
}
enum FanSpeed {
FAN_SPEED_LOW = 0;
@@ -471,6 +491,7 @@ message ListEntitiesLightResponse {
bool disabled_by_default = 13;
string icon = 14;
EntityCategory entity_category = 15;
uint32 device_id = 16;
}
message LightStateResponse {
option (id) = 24;
@@ -563,6 +584,7 @@ message ListEntitiesSensorResponse {
SensorLastResetType legacy_last_reset_type = 11;
bool disabled_by_default = 12;
EntityCategory entity_category = 13;
uint32 device_id = 14;
}
message SensorStateResponse {
option (id) = 25;
@@ -595,6 +617,7 @@ message ListEntitiesSwitchResponse {
bool disabled_by_default = 7;
EntityCategory entity_category = 8;
string device_class = 9;
uint32 device_id = 10;
}
message SwitchStateResponse {
option (id) = 26;
@@ -632,6 +655,7 @@ message ListEntitiesTextSensorResponse {
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
string device_class = 8;
uint32 device_id = 9;
}
message TextSensorStateResponse {
option (id) = 27;
@@ -814,6 +838,7 @@ message ListEntitiesCameraResponse {
bool disabled_by_default = 5;
string icon = 6;
EntityCategory entity_category = 7;
uint32 device_id = 8;
}
message CameraImageResponse {
@@ -916,6 +941,7 @@ message ListEntitiesClimateResponse {
bool supports_target_humidity = 23;
float visual_min_humidity = 24;
float visual_max_humidity = 25;
uint32 device_id = 26;
}
message ClimateStateResponse {
option (id) = 47;
@@ -999,6 +1025,7 @@ message ListEntitiesNumberResponse {
string unit_of_measurement = 11;
NumberMode mode = 12;
string device_class = 13;
uint32 device_id = 14;
}
message NumberStateResponse {
option (id) = 50;
@@ -1039,6 +1066,7 @@ message ListEntitiesSelectResponse {
repeated string options = 6;
bool disabled_by_default = 7;
EntityCategory entity_category = 8;
uint32 device_id = 9;
}
message SelectStateResponse {
option (id) = 53;
@@ -1081,6 +1109,7 @@ message ListEntitiesSirenResponse {
bool supports_duration = 8;
bool supports_volume = 9;
EntityCategory entity_category = 10;
uint32 device_id = 11;
}
message SirenStateResponse {
option (id) = 56;
@@ -1144,6 +1173,7 @@ message ListEntitiesLockResponse {
// Not yet implemented:
string code_format = 11;
uint32 device_id = 12;
}
message LockStateResponse {
option (id) = 59;
@@ -1183,6 +1213,7 @@ message ListEntitiesButtonResponse {
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
string device_class = 8;
uint32 device_id = 9;
}
message ButtonCommandRequest {
option (id) = 62;
@@ -1238,6 +1269,8 @@ message ListEntitiesMediaPlayerResponse {
bool supports_pause = 8;
repeated MediaPlayerSupportedFormat supported_formats = 9;
uint32 device_id = 10;
}
message MediaPlayerStateResponse {
option (id) = 64;
@@ -1778,6 +1811,7 @@ message ListEntitiesAlarmControlPanelResponse {
uint32 supported_features = 8;
bool requires_code = 9;
bool requires_code_to_arm = 10;
uint32 device_id = 11;
}
message AlarmControlPanelStateResponse {
@@ -1823,6 +1857,7 @@ message ListEntitiesTextResponse {
uint32 max_length = 9;
string pattern = 10;
TextMode mode = 11;
uint32 device_id = 12;
}
message TextStateResponse {
option (id) = 98;
@@ -1863,6 +1898,7 @@ message ListEntitiesDateResponse {
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
uint32 device_id = 8;
}
message DateStateResponse {
option (id) = 101;
@@ -1906,6 +1942,7 @@ message ListEntitiesTimeResponse {
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
uint32 device_id = 8;
}
message TimeStateResponse {
option (id) = 104;
@@ -1952,6 +1989,7 @@ message ListEntitiesEventResponse {
string device_class = 8;
repeated string event_types = 9;
uint32 device_id = 10;
}
message EventResponse {
option (id) = 108;
@@ -1983,6 +2021,7 @@ message ListEntitiesValveResponse {
bool assumed_state = 9;
bool supports_position = 10;
bool supports_stop = 11;
uint32 device_id = 12;
}
enum ValveOperation {
@@ -2029,6 +2068,7 @@ message ListEntitiesDateTimeResponse {
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
uint32 device_id = 8;
}
message DateTimeStateResponse {
option (id) = 113;
@@ -2069,6 +2109,7 @@ message ListEntitiesUpdateResponse {
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
string device_class = 8;
uint32 device_id = 9;
}
message UpdateStateResponse {
option (id) = 117;

View File

@@ -28,6 +28,12 @@
namespace esphome {
namespace api {
// Read a maximum of 5 messages per loop iteration to prevent starving other components.
// This is a balance between API responsiveness and allowing other components to run.
// Since each message could contain multiple protobuf messages when using packet batching,
// this limits the number of messages processed, not the number of TCP packets.
static constexpr uint8_t MAX_MESSAGES_PER_LOOP = 5;
static const char *const TAG = "api.connection";
static const int ESP32_CAMERA_STOP_STREAM = 5000;
@@ -109,33 +115,38 @@ void APIConnection::loop() {
return;
}
const uint32_t now = App.get_loop_component_start_time();
// Check if socket has data ready before attempting to read
if (this->helper_->is_socket_ready()) {
ReadPacketBuffer buffer;
err = this->helper_->read_packet(&buffer);
if (err == APIError::WOULD_BLOCK) {
// pass
} else if (err != APIError::OK) {
on_fatal_error();
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str());
} else if (err == APIError::CONNECTION_CLOSED) {
ESP_LOGW(TAG, "%s: Connection closed", this->get_client_combined_info().c_str());
} else {
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->get_client_combined_info().c_str(),
api_error_to_str(err), errno);
}
return;
} else {
this->last_traffic_ = App.get_loop_component_start_time();
// read a packet
if (buffer.data_len > 0) {
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
} else {
this->read_message(0, buffer.type, nullptr);
}
if (this->remove_)
// Read up to MAX_MESSAGES_PER_LOOP messages per loop to improve throughput
for (uint8_t message_count = 0; message_count < MAX_MESSAGES_PER_LOOP; message_count++) {
ReadPacketBuffer buffer;
err = this->helper_->read_packet(&buffer);
if (err == APIError::WOULD_BLOCK) {
// No more data available
break;
} else if (err != APIError::OK) {
on_fatal_error();
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str());
} else if (err == APIError::CONNECTION_CLOSED) {
ESP_LOGW(TAG, "%s: Connection closed", this->get_client_combined_info().c_str());
} else {
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->get_client_combined_info().c_str(),
api_error_to_str(err), errno);
}
return;
} else {
this->last_traffic_ = now;
// read a packet
if (buffer.data_len > 0) {
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
} else {
this->read_message(0, buffer.type, nullptr);
}
if (this->remove_)
return;
}
}
}
@@ -152,7 +163,6 @@ void APIConnection::loop() {
static uint8_t max_ping_retries = 60;
static uint16_t ping_retry_interval = 1000;
const uint32_t now = App.get_loop_component_start_time();
if (this->sent_ping_) {
// Disconnect if not responded within 2.5*keepalive
if (now - this->last_traffic_ > (KEEPALIVE_TIMEOUT_MS * 5) / 2) {
@@ -1619,6 +1629,23 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
#endif
#ifdef USE_API_NOISE
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
return resp;
}

View File

@@ -301,6 +301,9 @@ class APIConnection : public APIServerConnection {
response.icon = entity->get_icon();
response.disabled_by_default = entity->is_disabled_by_default();
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

View File

@@ -274,12 +274,21 @@ APIError APINoiseFrameHelper::init() {
}
/// Run through handshake messages (if in that phase)
APIError APINoiseFrameHelper::loop() {
APIError err = state_action_();
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
return err;
// During handshake phase, process as many actions as possible until we can't progress
// socket_->ready() stays true until next main loop, but state_action() will return
// WOULD_BLOCK when no more data is available to read
while (state_ != State::DATA && this->socket_->ready()) {
APIError err = state_action_();
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
return err;
}
if (err == APIError::WOULD_BLOCK) {
break;
}
}
if (!this->tx_buf_.empty()) {
err = try_send_tx_buf_();
APIError err = try_send_tx_buf_();
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
return err;
}

View File

@@ -812,6 +812,103 @@ void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); }
#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) {
switch (field_id) {
case 1: {
@@ -896,6 +993,18 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
this->bluetooth_mac_address = value.as_string();
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:
return false;
}
@@ -920,6 +1029,13 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(16, this->suggested_area);
buffer.encode_string(18, this->bluetooth_mac_address);
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 {
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->bluetooth_mac_address, 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
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(YESNO(this->api_encryption_supported));
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("}");
}
#endif
@@ -1052,6 +1187,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 10: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -1102,6 +1241,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_string(8, this->icon);
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 {
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_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_uint32_field(total_size, 1, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -1236,6 +1382,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->supports_stop = value.as_bool();
return true;
}
case 13: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -1289,6 +1439,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(10, this->icon);
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
buffer.encode_bool(12, this->supports_stop);
buffer.encode_uint32(13, this->device_id);
}
void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const {
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_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_uint32_field(total_size, 1, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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(YESNO(this->supports_stop));
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -1565,6 +1722,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 13: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -1620,6 +1781,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->supported_preset_modes) {
buffer.encode_string(12, it, true);
}
buffer.encode_uint32(13, this->device_id);
}
void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const {
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_uint32_field(total_size, 1, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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("\n");
}
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -1987,6 +2155,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 16: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -2055,6 +2227,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(13, this->disabled_by_default);
buffer.encode_string(14, this->icon);
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 {
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_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_uint32_field(total_size, 2, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -2658,6 +2837,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 14: {
this->device_id = value.as_uint32();
return true;
}
default:
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_bool(12, this->disabled_by_default);
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 {
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_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_uint32_field(total_size, 1, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -2860,6 +3050,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 10: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -2910,6 +3104,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
buffer.encode_string(9, this->device_class);
buffer.encode_uint32(10, this->device_id);
}
void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const {
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_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_uint32_field(total_size, 1, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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("'").append(this->device_class).append("'");
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -3061,6 +3262,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 9: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -3110,6 +3315,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_string(8, this->device_class);
buffer.encode_uint32(9, this->device_id);
}
void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const {
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_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_uint32_field(total_size, 1, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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("'").append(this->device_class).append("'");
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -3922,6 +4134,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 8: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -3966,6 +4182,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(5, this->disabled_by_default);
buffer.encode_string(6, this->icon);
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 {
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_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_uint32_field(total_size, 1, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -4156,6 +4379,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
this->supports_target_humidity = value.as_bool();
return true;
}
case 26: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -4262,6 +4489,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(23, this->supports_target_humidity);
buffer.encode_float(24, this->visual_min_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 {
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_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_uint32_field(total_size, 2, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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);
out.append(buffer);
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -4901,6 +5135,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->mode = value.as_enum<enums::NumberMode>();
return true;
}
case 14: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -4971,6 +5209,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(11, this->unit_of_measurement);
buffer.encode_enum<enums::NumberMode>(12, this->mode);
buffer.encode_string(13, this->device_class);
buffer.encode_uint32(14, this->device_id);
}
void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const {
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_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_uint32_field(total_size, 1, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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("'").append(this->device_class).append("'");
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -5151,6 +5396,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 9: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -5202,6 +5451,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
}
buffer.encode_bool(7, this->disabled_by_default);
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 {
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_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
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(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -5378,6 +5634,10 @@ bool ListEntitiesSirenResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 11: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -5431,6 +5691,7 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(8, this->supports_duration);
buffer.encode_bool(9, this->supports_volume);
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 {
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_volume, 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
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(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -5683,6 +5950,10 @@ bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
this->requires_code = value.as_bool();
return true;
}
case 12: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -5735,6 +6006,7 @@ void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(9, this->supports_open);
buffer.encode_bool(10, this->requires_code);
buffer.encode_string(11, this->code_format);
buffer.encode_uint32(12, this->device_id);
}
void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const {
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->requires_code, 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
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("'").append(this->code_format).append("'");
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -5922,6 +6200,10 @@ bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 9: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -5971,6 +6253,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_string(8, this->device_class);
buffer.encode_uint32(9, this->device_id);
}
void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const {
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_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_uint32_field(total_size, 1, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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("'").append(this->device_class).append("'");
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -6135,6 +6424,10 @@ bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarI
this->supports_pause = value.as_bool();
return true;
}
case 10: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -6187,6 +6480,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->supported_formats) {
buffer.encode_message<MediaPlayerSupportedFormat>(9, it, true);
}
buffer.encode_uint32(10, this->device_id);
}
void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const {
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_bool_field(total_size, 1, this->supports_pause, false);
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
void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
@@ -6241,6 +6536,11 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
it.dump_to(out);
out.append("\n");
}
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -8551,6 +8851,10 @@ bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, Pro
this->requires_code_to_arm = value.as_bool();
return true;
}
case 11: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -8598,6 +8902,7 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) cons
buffer.encode_uint32(8, this->supported_features);
buffer.encode_bool(9, this->requires_code);
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 {
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_bool_field(total_size, 1, this->requires_code, 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
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(YESNO(this->requires_code_to_arm));
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -8783,6 +9094,10 @@ bool ListEntitiesTextResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
this->mode = value.as_enum<enums::TextMode>();
return true;
}
case 12: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -8835,6 +9150,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(9, this->max_length);
buffer.encode_string(10, this->pattern);
buffer.encode_enum<enums::TextMode>(11, this->mode);
buffer.encode_uint32(12, this->device_id);
}
void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const {
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_string_field(total_size, 1, this->pattern, 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
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(proto_enum_to_string<enums::TextMode>(this->mode));
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -9014,6 +9336,10 @@ bool ListEntitiesDateResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 8: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -9058,6 +9384,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
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 {
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_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_uint32_field(total_size, 1, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -9255,6 +9588,10 @@ bool ListEntitiesTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 8: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -9299,6 +9636,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
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 {
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_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_uint32_field(total_size, 1, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -9496,6 +9840,10 @@ bool ListEntitiesEventResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 10: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -9552,6 +9900,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->event_types) {
buffer.encode_string(9, it, true);
}
buffer.encode_uint32(10, this->device_id);
}
void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const {
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_uint32_field(total_size, 1, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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("\n");
}
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -9678,6 +10033,10 @@ bool ListEntitiesValveResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->supports_stop = value.as_bool();
return true;
}
case 12: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -9730,6 +10089,7 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(9, this->assumed_state);
buffer.encode_bool(10, this->supports_position);
buffer.encode_bool(11, this->supports_stop);
buffer.encode_uint32(12, this->device_id);
}
void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const {
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->supports_position, 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
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(YESNO(this->supports_stop));
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -9923,6 +10289,10 @@ bool ListEntitiesDateTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 8: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -9967,6 +10337,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
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 {
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_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_uint32_field(total_size, 1, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
@@ -10114,6 +10491,10 @@ bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 9: {
this->device_id = value.as_uint32();
return true;
}
default:
return false;
}
@@ -10163,6 +10544,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_string(8, this->device_class);
buffer.encode_uint32(9, this->device_id);
}
void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const {
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_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_uint32_field(total_size, 1, this->device_id, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
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("'").append(this->device_class).append("'");
out.append("\n");
out.append(" device_id: ");
sprintf(buffer, "%" PRIu32, this->device_id);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif

View File

@@ -264,6 +264,7 @@ class InfoResponseProtoMessage : public ProtoMessage {
bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
uint32_t device_id{0};
protected:
};
@@ -415,10 +416,39 @@ class DeviceInfoRequest : public ProtoMessage {
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 {
public:
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
static constexpr const char *message_name() { return "device_info_response"; }
#endif
@@ -441,6 +471,9 @@ class DeviceInfoResponse : public ProtoMessage {
std::string suggested_area{};
std::string bluetooth_mac_address{};
bool api_encryption_supported{false};
std::vector<DeviceInfo> devices{};
std::vector<AreaInfo> areas{};
AreaInfo area{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -493,7 +526,7 @@ class SubscribeStatesRequest : public ProtoMessage {
class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_binary_sensor_response"; }
#endif
@@ -532,7 +565,7 @@ class BinarySensorStateResponse : public StateResponseProtoMessage {
class ListEntitiesCoverResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_cover_response"; }
#endif
@@ -601,7 +634,7 @@ class CoverCommandRequest : public ProtoMessage {
class ListEntitiesFanResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_fan_response"; }
#endif
@@ -679,7 +712,7 @@ class FanCommandRequest : public ProtoMessage {
class ListEntitiesLightResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_light_response"; }
#endif
@@ -780,7 +813,7 @@ class LightCommandRequest : public ProtoMessage {
class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_sensor_response"; }
#endif
@@ -823,7 +856,7 @@ class SensorStateResponse : public StateResponseProtoMessage {
class ListEntitiesSwitchResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_switch_response"; }
#endif
@@ -880,7 +913,7 @@ class SwitchCommandRequest : public ProtoMessage {
class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_text_sensor_response"; }
#endif
@@ -1196,7 +1229,7 @@ class ExecuteServiceRequest : public ProtoMessage {
class ListEntitiesCameraResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_camera_response"; }
#endif
@@ -1253,7 +1286,7 @@ class CameraImageRequest : public ProtoMessage {
class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_climate_response"; }
#endif
@@ -1362,7 +1395,7 @@ class ClimateCommandRequest : public ProtoMessage {
class ListEntitiesNumberResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_number_response"; }
#endif
@@ -1423,7 +1456,7 @@ class NumberCommandRequest : public ProtoMessage {
class ListEntitiesSelectResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_select_response"; }
#endif
@@ -1481,7 +1514,7 @@ class SelectCommandRequest : public ProtoMessage {
class ListEntitiesSirenResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_siren_response"; }
#endif
@@ -1547,7 +1580,7 @@ class SirenCommandRequest : public ProtoMessage {
class ListEntitiesLockResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_lock_response"; }
#endif
@@ -1609,7 +1642,7 @@ class LockCommandRequest : public ProtoMessage {
class ListEntitiesButtonResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_button_response"; }
#endif
@@ -1662,7 +1695,7 @@ class MediaPlayerSupportedFormat : public ProtoMessage {
class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_media_player_response"; }
#endif
@@ -2532,7 +2565,7 @@ class VoiceAssistantSetConfiguration : public ProtoMessage {
class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_alarm_control_panel_response"; }
#endif
@@ -2592,7 +2625,7 @@ class AlarmControlPanelCommandRequest : public ProtoMessage {
class ListEntitiesTextResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_text_response"; }
#endif
@@ -2653,7 +2686,7 @@ class TextCommandRequest : public ProtoMessage {
class ListEntitiesDateResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_date_response"; }
#endif
@@ -2713,7 +2746,7 @@ class DateCommandRequest : public ProtoMessage {
class ListEntitiesTimeResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_time_response"; }
#endif
@@ -2773,7 +2806,7 @@ class TimeCommandRequest : public ProtoMessage {
class ListEntitiesEventResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_event_response"; }
#endif
@@ -2811,7 +2844,7 @@ class EventResponse : public StateResponseProtoMessage {
class ListEntitiesValveResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_valve_response"; }
#endif
@@ -2873,7 +2906,7 @@ class ValveCommandRequest : public ProtoMessage {
class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_date_time_response"; }
#endif
@@ -2928,7 +2961,7 @@ class DateTimeCommandRequest : public ProtoMessage {
class ListEntitiesUpdateResponse : public InfoResponseProtoMessage {
public:
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
static constexpr const char *message_name() { return "list_entities_update_response"; }
#endif

View File

@@ -47,11 +47,6 @@ void APIServer::setup() {
}
#endif
// Schedule reboot if no clients connect within timeout
if (this->reboot_timeout_ != 0) {
this->schedule_reboot_timeout_();
}
this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
if (this->socket_ == nullptr) {
ESP_LOGW(TAG, "Could not create socket");
@@ -111,6 +106,8 @@ void APIServer::setup() {
}
#endif
this->last_connected_ = App.get_loop_component_start_time();
#ifdef USE_ESP32_CAMERA
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
esp32_camera::global_esp32_camera->add_image_callback(
@@ -124,16 +121,6 @@ void APIServer::setup() {
#endif
}
void APIServer::schedule_reboot_timeout_() {
this->status_set_warning();
this->set_timeout("api_reboot", this->reboot_timeout_, []() {
if (!global_api_server->is_connected()) {
ESP_LOGE(TAG, "No client connected; rebooting");
App.reboot();
}
});
}
void APIServer::loop() {
// Accept new clients only if the socket exists and has incoming connections
if (this->socket_ && this->socket_->ready()) {
@@ -148,12 +135,6 @@ void APIServer::loop() {
auto *conn = new APIConnection(std::move(sock), this);
this->clients_.emplace_back(conn);
conn->start();
// Clear warning status and cancel reboot when first client connects
if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) {
this->status_clear_warning();
this->cancel_timeout("api_reboot");
}
}
}
@@ -173,12 +154,6 @@ void APIServer::loop() {
std::swap(this->clients_[client_index], this->clients_.back());
}
this->clients_.pop_back();
// Schedule reboot when last client disconnects
if (this->clients_.empty() && this->reboot_timeout_ != 0) {
this->schedule_reboot_timeout_();
}
// Don't increment client_index since we need to process the swapped element
} else {
// Process active client
@@ -188,7 +163,19 @@ void APIServer::loop() {
}
}
// Reboot timeout is now handled by connection/disconnection events
if (this->reboot_timeout_ != 0) {
const uint32_t now = App.get_loop_component_start_time();
if (!this->is_connected()) {
if (now - this->last_connected_ > this->reboot_timeout_) {
ESP_LOGE(TAG, "No client connected; rebooting");
App.reboot();
}
this->status_set_warning();
} else {
this->last_connected_ = now;
this->status_clear_warning();
}
}
}
void APIServer::dump_config() {

View File

@@ -142,7 +142,6 @@ class APIServer : public Component, public Controller {
}
protected:
void schedule_reboot_timeout_();
// Pointers and pointer-like types first (4 bytes each)
std::unique_ptr<socket::Socket> socket_ = nullptr;
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
@@ -151,6 +150,7 @@ class APIServer : public Component, public Controller {
// 4-byte aligned types
uint32_t reboot_timeout_{300000};
uint32_t batch_delay_{100};
uint32_t last_connected_{0};
// Vectors and strings (12 bytes each on 32-bit)
std::vector<std::unique_ptr<APIConnection>> clients_;

View File

@@ -60,8 +60,8 @@ from esphome.const import (
DEVICE_CLASS_WINDOW,
)
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_helpers import setup_entity
from esphome.util import Registry
CODEOWNERS = ["@esphome/core"]
@@ -491,6 +491,9 @@ _BINARY_SENSOR_SCHEMA = (
)
_BINARY_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("binary_sensor"))
def binary_sensor_schema(
class_: MockObjClass = cv.UNDEFINED,
*,
@@ -521,7 +524,7 @@ BINARY_SENSOR_SCHEMA.add_extra(cv.deprecated_schema_constant("binary_sensor"))
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:
cg.add(var.set_device_class(device_class))

View File

@@ -18,8 +18,8 @@ from esphome.const import (
DEVICE_CLASS_UPDATE,
)
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_helpers import setup_entity
CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True
@@ -61,6 +61,9 @@ _BUTTON_SCHEMA = (
)
_BUTTON_SCHEMA.add_extra(entity_duplicate_validator("button"))
def button_schema(
class_: MockObjClass,
*,
@@ -87,7 +90,7 @@ BUTTON_SCHEMA.add_extra(cv.deprecated_schema_constant("button"))
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, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@@ -48,8 +48,8 @@ from esphome.const import (
CONF_WEB_SERVER,
)
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_helpers import setup_entity
IS_PLATFORM_COMPONENT = True
@@ -247,6 +247,9 @@ _CLIMATE_SCHEMA = (
)
_CLIMATE_SCHEMA.add_extra(entity_duplicate_validator("climate"))
def climate_schema(
class_: MockObjClass,
*,
@@ -273,7 +276,7 @@ CLIMATE_SCHEMA.add_extra(cv.deprecated_schema_constant("climate"))
async def setup_climate_core_(var, config):
await setup_entity(var, config)
await setup_entity(var, config, "climate")
visual = config[CONF_VISUAL]
if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None:

View File

@@ -33,8 +33,8 @@ from esphome.const import (
DEVICE_CLASS_WINDOW,
)
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_helpers import setup_entity
IS_PLATFORM_COMPONENT = True
@@ -126,6 +126,9 @@ _COVER_SCHEMA = (
)
_COVER_SCHEMA.add_extra(entity_duplicate_validator("cover"))
def cover_schema(
class_: MockObjClass,
*,
@@ -154,7 +157,7 @@ COVER_SCHEMA.add_extra(cv.deprecated_schema_constant("cover"))
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:
cg.add(var.set_device_class(device_class))

View File

@@ -22,8 +22,8 @@ from esphome.const import (
CONF_YEAR,
)
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_helpers import setup_entity
CODEOWNERS = ["@rfdarter", "@jesserockz"]
@@ -84,6 +84,8 @@ _DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
).add_extra(_validate_time_present)
_DATETIME_SCHEMA.add_extra(entity_duplicate_validator("datetime"))
def date_schema(class_: MockObjClass) -> cv.Schema:
schema = cv.Schema(
@@ -133,7 +135,7 @@ def datetime_schema(class_: MockObjClass) -> cv.Schema:
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:
mqtt_ = cg.new_Pvariable(mqtt_id, var)

View File

@@ -455,7 +455,7 @@ CONFIG_SCHEMA = cv.Schema(
CONF_NAME: "Demo Plain Sensor",
},
{
CONF_NAME: "Demo Temperature Sensor",
CONF_NAME: "Demo Temperature Sensor 1",
CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS,
CONF_ICON: ICON_THERMOMETER,
CONF_ACCURACY_DECIMALS: 1,
@@ -463,7 +463,7 @@ CONFIG_SCHEMA = cv.Schema(
CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT,
},
{
CONF_NAME: "Demo Temperature Sensor",
CONF_NAME: "Demo Temperature Sensor 2",
CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS,
CONF_ICON: ICON_THERMOMETER,
CONF_ACCURACY_DECIMALS: 1,

View File

@@ -522,6 +522,7 @@ optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData
}
void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) {
this->scan_result_ = &scan_result;
for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++)
this->address_[i] = scan_result.bda[i];
this->address_type_ = static_cast<esp_ble_addr_type_t>(scan_result.ble_addr_type);

View File

@@ -85,6 +85,9 @@ class ESPBTDevice {
const std::vector<ServiceData> &get_service_datas() const { return service_datas_; }
// Exposed through a function for use in lambdas
const BLEScanResult &get_scan_result() const { return *scan_result_; }
bool resolve_irk(const uint8_t *irk) const;
optional<ESPBLEiBeacon> get_ibeacon() const {
@@ -111,6 +114,7 @@ class ESPBTDevice {
std::vector<ESPBTUUID> service_uuids_{};
std::vector<ServiceData> manufacturer_datas_{};
std::vector<ServiceData> service_datas_{};
const BLEScanResult *scan_result_{nullptr};
};
class ESP32BLETracker;

View File

@@ -19,7 +19,7 @@ from esphome.const import (
CONF_VSYNC_PIN,
)
from esphome.core import CORE
from esphome.cpp_helpers import setup_entity
from esphome.core.entity_helpers import setup_entity
DEPENDENCIES = ["esp32"]
@@ -284,7 +284,7 @@ SETTERS = {
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await setup_entity(var, config)
await setup_entity(var, config, "camera")
await cg.register_component(var, config)
for key, setter in SETTERS.items():

View File

@@ -18,8 +18,8 @@ from esphome.const import (
DEVICE_CLASS_MOTION,
)
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_helpers import setup_entity
CODEOWNERS = ["@nohat"]
IS_PLATFORM_COMPONENT = True
@@ -59,6 +59,9 @@ _EVENT_SCHEMA = (
)
_EVENT_SCHEMA.add_extra(entity_duplicate_validator("event"))
def event_schema(
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]):
await setup_entity(var, config)
await setup_entity(var, config, "event")
for conf in config.get(CONF_ON_EVENT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@@ -32,7 +32,7 @@ from esphome.const import (
CONF_WEB_SERVER,
)
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
@@ -161,6 +161,9 @@ _FAN_SCHEMA = (
)
_FAN_SCHEMA.add_extra(entity_duplicate_validator("fan"))
def fan_schema(
class_: cg.Pvariable,
*,
@@ -225,7 +228,7 @@ def validate_preset_modes(value):
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]))

View File

@@ -1,6 +1,7 @@
from collections.abc import MutableMapping
import functools
import hashlib
from itertools import accumulate
import logging
import os
from pathlib import Path
@@ -468,8 +469,9 @@ class EFont:
class GlyphInfo:
def __init__(self, data_len, advance, offset_x, offset_y, width, height):
self.data_len = data_len
def __init__(self, glyph, data, advance, offset_x, offset_y, width, height):
self.glyph = glyph
self.bitmap_data = data
self.advance = advance
self.offset_x = offset_x
self.offset_y = offset_y
@@ -477,6 +479,62 @@ class GlyphInfo:
self.height = height
def glyph_to_glyphinfo(glyph, font, size, bpp):
scale = 256 // (1 << bpp)
if not font.is_scalable:
sizes = [pt_to_px(x.size) for x in font.available_sizes]
if size in sizes:
font.select_size(sizes.index(size))
else:
font.set_pixel_sizes(size, 0)
flags = FT_LOAD_RENDER
if bpp != 1:
flags |= FT_LOAD_NO_BITMAP
else:
flags |= FT_LOAD_TARGET_MONO
font.load_char(glyph, flags)
width = font.glyph.bitmap.width
height = font.glyph.bitmap.rows
buffer = font.glyph.bitmap.buffer
pitch = font.glyph.bitmap.pitch
glyph_data = [0] * ((height * width * bpp + 7) // 8)
src_mode = font.glyph.bitmap.pixel_mode
pos = 0
for y in range(height):
for x in range(width):
if src_mode == ft_pixel_mode_mono:
pixel = (
(1 << bpp) - 1
if buffer[y * pitch + x // 8] & (1 << (7 - x % 8))
else 0
)
else:
pixel = buffer[y * pitch + x] // scale
for bit_num in range(bpp):
if pixel & (1 << (bpp - bit_num - 1)):
glyph_data[pos // 8] |= 0x80 >> (pos % 8)
pos += 1
ascender = pt_to_px(font.size.ascender)
if ascender == 0:
if not font.is_scalable:
ascender = size
else:
_LOGGER.error(
"Unable to determine ascender of font %s %s",
font.family_name,
font.style_name,
)
return GlyphInfo(
glyph,
glyph_data,
pt_to_px(font.glyph.metrics.horiAdvance),
font.glyph.bitmap_left,
ascender - font.glyph.bitmap_top,
width,
height,
)
async def to_code(config):
"""
Collect all glyph codepoints, construct a map from a codepoint to a font file.
@@ -506,98 +564,47 @@ async def to_code(config):
codepoints = list(point_set)
codepoints.sort(key=functools.cmp_to_key(glyph_comparator))
glyph_args = {}
data = []
bpp = config[CONF_BPP]
scale = 256 // (1 << bpp)
size = config[CONF_SIZE]
# create the data array for all glyphs
for codepoint in codepoints:
font = point_font_map[codepoint]
if not font.is_scalable:
sizes = [pt_to_px(x.size) for x in font.available_sizes]
if size in sizes:
font.select_size(sizes.index(size))
else:
font.set_pixel_sizes(size, 0)
flags = FT_LOAD_RENDER
if bpp != 1:
flags |= FT_LOAD_NO_BITMAP
else:
flags |= FT_LOAD_TARGET_MONO
font.load_char(codepoint, flags)
width = font.glyph.bitmap.width
height = font.glyph.bitmap.rows
buffer = font.glyph.bitmap.buffer
pitch = font.glyph.bitmap.pitch
glyph_data = [0] * ((height * width * bpp + 7) // 8)
src_mode = font.glyph.bitmap.pixel_mode
pos = 0
for y in range(height):
for x in range(width):
if src_mode == ft_pixel_mode_mono:
pixel = (
(1 << bpp) - 1
if buffer[y * pitch + x // 8] & (1 << (7 - x % 8))
else 0
)
else:
pixel = buffer[y * pitch + x] // scale
for bit_num in range(bpp):
if pixel & (1 << (bpp - bit_num - 1)):
glyph_data[pos // 8] |= 0x80 >> (pos % 8)
pos += 1
ascender = pt_to_px(font.size.ascender)
if ascender == 0:
if not font.is_scalable:
ascender = size
else:
_LOGGER.error(
"Unable to determine ascender of font %s", config[CONF_FILE]
)
glyph_args[codepoint] = GlyphInfo(
len(data),
pt_to_px(font.glyph.metrics.horiAdvance),
font.glyph.bitmap_left,
ascender - font.glyph.bitmap_top,
width,
height,
)
data += glyph_data
rhs = [HexInt(x) for x in data]
glyph_args = [
glyph_to_glyphinfo(x, point_font_map[x], size, bpp) for x in codepoints
]
rhs = [HexInt(x) for x in flatten([x.bitmap_data for x in glyph_args])]
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
# Create the glyph table that points to data in the above array.
glyph_initializer = []
for codepoint in codepoints:
glyph_initializer.append(
cg.StructInitializer(
GlyphData,
(
"a_char",
cg.RawExpression(
f"(const uint8_t *){cpp_string_escape(codepoint)}"
),
),
(
"data",
cg.RawExpression(
f"{str(prog_arr)} + {str(glyph_args[codepoint].data_len)}"
),
),
("advance", glyph_args[codepoint].advance),
("offset_x", glyph_args[codepoint].offset_x),
("offset_y", glyph_args[codepoint].offset_y),
("width", glyph_args[codepoint].width),
("height", glyph_args[codepoint].height),
)
glyph_initializer = [
cg.StructInitializer(
GlyphData,
(
"a_char",
cg.RawExpression(f"(const uint8_t *){cpp_string_escape(x.glyph)}"),
),
(
"data",
cg.RawExpression(f"{str(prog_arr)} + {str(y - len(x.bitmap_data))}"),
),
("advance", x.advance),
("offset_x", x.offset_x),
("offset_y", x.offset_y),
("width", x.width),
("height", x.height),
)
for (x, y) in zip(
glyph_args, list(accumulate([len(x.bitmap_data) for x in glyph_args]))
)
]
glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer)
font_height = pt_to_px(base_font.size.height)
ascender = pt_to_px(base_font.size.ascender)
descender = abs(pt_to_px(base_font.size.descender))
g = glyph_to_glyphinfo("x", base_font, size, bpp)
xheight = g.height if len(g.bitmap_data) > 1 else 0
g = glyph_to_glyphinfo("X", base_font, size, bpp)
capheight = g.height if len(g.bitmap_data) > 1 else 0
if font_height == 0:
if not base_font.is_scalable:
font_height = size
@@ -610,5 +617,8 @@ async def to_code(config):
len(glyph_initializer),
ascender,
font_height,
descender,
xheight,
capheight,
bpp,
)

View File

@@ -45,8 +45,15 @@ void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
*height = this->glyph_data_->height;
}
Font::Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp)
: baseline_(baseline), height_(height), bpp_(bpp) {
Font::Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
uint8_t bpp)
: baseline_(baseline),
height_(height),
descender_(descender),
linegap_(height - baseline - descender),
xheight_(xheight),
capheight_(capheight),
bpp_(bpp) {
glyphs_.reserve(data_nr);
for (int i = 0; i < data_nr; ++i)
glyphs_.emplace_back(&data[i]);

View File

@@ -50,11 +50,17 @@ class Font
public:
/** Construct the font with the given glyphs.
*
* @param glyphs A vector of glyphs, must be sorted lexicographically.
* @param data A vector of glyphs, must be sorted lexicographically.
* @param data_nr The number of glyphs in data.
* @param baseline The y-offset from the top of the text to the baseline.
* @param bottom The y-offset from the top of the text to the bottom (i.e. height).
* @param height The y-offset from the top of the text to the bottom.
* @param descender The y-offset from the baseline to the lowest stroke in the font (e.g. from letters like g or p).
* @param xheight The height of lowercase letters, usually measured at the "x" glyph.
* @param capheight The height of capital letters, usually measured at the "X" glyph.
* @param bpp The bits per pixel used for this font. Used to read data out of the glyph bitmaps.
*/
Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp = 1);
Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
uint8_t bpp = 1);
int match_next_glyph(const uint8_t *str, int *match_length);
@@ -65,6 +71,11 @@ class Font
#endif
inline int get_baseline() { return this->baseline_; }
inline int get_height() { return this->height_; }
inline int get_ascender() { return this->baseline_; }
inline int get_descender() { return this->descender_; }
inline int get_linegap() { return this->linegap_; }
inline int get_xheight() { return this->xheight_; }
inline int get_capheight() { return this->capheight_; }
inline int get_bpp() { return this->bpp_; }
const std::vector<Glyph, RAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
@@ -73,6 +84,10 @@ class Font
std::vector<Glyph, RAMAllocator<Glyph>> glyphs_;
int baseline_;
int height_;
int descender_;
int linegap_;
int xheight_;
int capheight_;
uint8_t bpp_; // bits per pixel
};

View File

@@ -38,8 +38,8 @@ from esphome.const import (
CONF_WHITE,
)
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_helpers import setup_entity
from .automation import LIGHT_STATE_SCHEMA
from .effects import (
@@ -110,6 +110,8 @@ LIGHT_SCHEMA = (
)
)
LIGHT_SCHEMA.add_extra(entity_duplicate_validator("light"))
BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend(
{
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):
await setup_entity(light_var, config)
await setup_entity(light_var, config, "light")
cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE]))

View File

@@ -14,8 +14,8 @@ from esphome.const import (
CONF_WEB_SERVER,
)
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_helpers import setup_entity
CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True
@@ -67,6 +67,9 @@ _LOCK_SCHEMA = (
)
_LOCK_SCHEMA.add_extra(entity_duplicate_validator("lock"))
def lock_schema(
class_: MockObjClass = cv.UNDEFINED,
*,
@@ -94,7 +97,7 @@ LOCK_SCHEMA.add_extra(cv.deprecated_schema_constant("lock"))
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, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@@ -184,7 +184,9 @@ CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(Logger),
cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes,
cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.All(
cv.validate_bytes, cv.int_range(min=160, max=65535)
),
cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
cv.SplitDefault(
CONF_TASK_LOG_BUFFER_SIZE,

View File

@@ -24,7 +24,7 @@ static const char *const TAG = "logger";
// - Messages are serialized through main loop for proper console output
// - Fallback to emergency console logging only if ring buffer is full
// - WITHOUT task log buffer: Only emergency console output, no callbacks
void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT
void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
if (level > this->level_for(tag))
return;
@@ -46,8 +46,8 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *
bool message_sent = false;
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
// For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered
message_sent = this->log_buffer_->send_message_thread_safe(static_cast<uint8_t>(level), tag,
static_cast<uint16_t>(line), current_task, format, args);
message_sent =
this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), current_task, format, args);
#endif // USE_ESPHOME_TASK_LOG_BUFFER
// Emergency console logging for non-main tasks when ring buffer is full or disabled
@@ -58,7 +58,7 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *
// Maximum size for console log messages (includes null terminator)
static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144;
char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety
int buffer_at = 0; // Initialize buffer position
uint16_t buffer_at = 0; // Initialize buffer position
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at,
MAX_CONSOLE_LOG_MSG_SIZE);
this->write_msg_(console_buffer);
@@ -69,7 +69,7 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *
}
#else
// Implementation for all other platforms
void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT
void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
if (level > this->level_for(tag) || global_recursion_guard_)
return;
@@ -85,7 +85,7 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *
#ifdef USE_STORE_LOG_STR_IN_FLASH
// Implementation for ESP8266 with flash string support.
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format,
void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
va_list args) { // NOLINT
if (level > this->level_for(tag) || global_recursion_guard_)
return;
@@ -122,7 +122,7 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr
}
#endif // USE_STORE_LOG_STR_IN_FLASH
inline int Logger::level_for(const char *tag) {
inline uint8_t Logger::level_for(const char *tag) {
auto it = this->log_levels_.find(tag);
if (it != this->log_levels_.end())
return it->second;
@@ -195,13 +195,13 @@ void Logger::loop() {
#endif
void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
void Logger::set_log_level(const std::string &tag, int log_level) { this->log_levels_[tag] = log_level; }
void Logger::set_log_level(const std::string &tag, uint8_t log_level) { this->log_levels_[tag] = log_level; }
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
UARTSelection Logger::get_uart() const { return this->uart_; }
#endif
void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) {
void Logger::add_on_log_callback(std::function<void(uint8_t, const char *, const char *)> &&callback) {
this->log_callback_.add(std::move(callback));
}
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
@@ -230,7 +230,7 @@ void Logger::dump_config() {
}
}
void Logger::set_log_level(int level) {
void Logger::set_log_level(uint8_t level) {
if (level > ESPHOME_LOG_LEVEL) {
level = ESPHOME_LOG_LEVEL;
ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]);

View File

@@ -61,7 +61,7 @@ static const char *const LOG_LEVEL_LETTERS[] = {
*
* Advanced configuration (pin selection, etc) is not supported.
*/
enum UARTSelection {
enum UARTSelection : uint8_t {
#ifdef USE_LIBRETINY
UART_SELECTION_DEFAULT = 0,
UART_SELECTION_UART0,
@@ -129,10 +129,10 @@ class Logger : public Component {
#endif
/// Set the default log level for this logger.
void set_log_level(int level);
void set_log_level(uint8_t level);
/// Set the log level of the specified tag.
void set_log_level(const std::string &tag, int log_level);
int get_log_level() { return this->current_level_; }
void set_log_level(const std::string &tag, uint8_t log_level);
uint8_t get_log_level() { return this->current_level_; }
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
@@ -140,19 +140,20 @@ class Logger : public Component {
void pre_setup();
void dump_config() override;
inline int level_for(const char *tag);
inline uint8_t level_for(const char *tag);
/// Register a callback that will be called for every log message sent
void add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback);
void add_on_log_callback(std::function<void(uint8_t, const char *, const char *)> &&callback);
// add a listener for log level changes
void add_listener(std::function<void(int)> &&callback) { this->level_callback_.add(std::move(callback)); }
void add_listener(std::function<void(uint8_t)> &&callback) { this->level_callback_.add(std::move(callback)); }
float get_setup_priority() const override;
void log_vprintf_(int level, const char *tag, int line, const char *format, va_list args); // NOLINT
void log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args); // NOLINT
#ifdef USE_STORE_LOG_STR_IN_FLASH
void log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, va_list args); // NOLINT
void log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
va_list args); // NOLINT
#endif
protected:
@@ -160,8 +161,9 @@ class Logger : public Component {
// Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
// It's the caller's responsibility to initialize buffer_at (typically to 0)
inline void HOT format_log_to_buffer_with_terminator_(int level, const char *tag, int line, const char *format,
va_list args, char *buffer, int *buffer_at, int buffer_size) {
inline void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format,
va_list args, char *buffer, uint16_t *buffer_at,
uint16_t buffer_size) {
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(), buffer, buffer_at, buffer_size);
#else
@@ -180,7 +182,7 @@ class Logger : public Component {
}
// Helper to format and send a log message to both console and callbacks
inline void HOT log_message_to_buffer_and_send_(int level, const char *tag, int line, const char *format,
inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format,
va_list args) {
// Format to tx_buffer and prepare for output
this->tx_buffer_at_ = 0; // Initialize buffer position
@@ -194,11 +196,12 @@ class Logger : public Component {
}
// Write the body of the log message to the buffer
inline void write_body_to_buffer_(const char *value, size_t length, char *buffer, int *buffer_at, int buffer_size) {
inline void write_body_to_buffer_(const char *value, size_t length, char *buffer, uint16_t *buffer_at,
uint16_t buffer_size) {
// Calculate available space
const int available = buffer_size - *buffer_at;
if (available <= 0)
if (*buffer_at >= buffer_size)
return;
const uint16_t available = buffer_size - *buffer_at;
// Determine copy length (minimum of remaining capacity and string length)
const size_t copy_len = (length < static_cast<size_t>(available)) ? length : available;
@@ -211,7 +214,7 @@ class Logger : public Component {
}
// Format string to explicit buffer with varargs
inline void printf_to_buffer_(char *buffer, int *buffer_at, int buffer_size, const char *format, ...) {
inline void printf_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, ...) {
va_list arg;
va_start(arg, format);
this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, arg);
@@ -222,41 +225,50 @@ class Logger : public Component {
const char *get_uart_selection_();
#endif
// Group 4-byte aligned members first
uint32_t baud_rate_;
char *tx_buffer_{nullptr};
int tx_buffer_at_{0};
int tx_buffer_size_{0};
#ifdef USE_ARDUINO
Stream *hw_serial_{nullptr};
#endif
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
void *main_task_ = nullptr; // Only used for thread name identification
#endif
#ifdef USE_ESP32
// Task-specific recursion guards:
// - Main task uses a dedicated member variable for efficiency
// - Other tasks use pthread TLS with a dynamically created key via pthread_key_create
pthread_key_t log_recursion_key_; // 4 bytes
#endif
#ifdef USE_ESP_IDF
uart_port_t uart_num_; // 4 bytes (enum defaults to int size)
#endif
// Large objects (internally aligned)
std::map<std::string, uint8_t> log_levels_{};
CallbackManager<void(uint8_t, const char *, const char *)> log_callback_{};
CallbackManager<void(uint8_t)> level_callback_{};
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
#endif
// Group smaller types together at the end
uint16_t tx_buffer_at_{0};
uint16_t tx_buffer_size_{0};
uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
UARTSelection uart_{UART_SELECTION_UART0};
#endif
#ifdef USE_LIBRETINY
UARTSelection uart_{UART_SELECTION_DEFAULT};
#endif
#ifdef USE_ARDUINO
Stream *hw_serial_{nullptr};
#endif
#ifdef USE_ESP_IDF
uart_port_t uart_num_;
#endif
std::map<std::string, int> log_levels_{};
CallbackManager<void(int, const char *, const char *)> log_callback_{};
int current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
#endif
#ifdef USE_ESP32
// Task-specific recursion guards:
// - Main task uses a dedicated member variable for efficiency
// - Other tasks use pthread TLS with a dynamically created key via pthread_key_create
bool main_task_recursion_guard_{false};
pthread_key_t log_recursion_key_;
#else
bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
#endif
CallbackManager<void(int)> level_callback_{};
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
void *main_task_ = nullptr; // Only used for thread name identification
const char *HOT get_thread_name_() {
TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
if (current_task == main_task_) {
@@ -297,11 +309,10 @@ class Logger : public Component {
}
#endif
inline void HOT write_header_to_buffer_(int level, const char *tag, int line, const char *thread_name, char *buffer,
int *buffer_at, int buffer_size) {
inline void HOT write_header_to_buffer_(uint8_t level, const char *tag, int line, const char *thread_name,
char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
// Format header
if (level < 0)
level = 0;
// uint8_t level is already bounded 0-255, just ensure it's <= 7
if (level > 7)
level = 7;
@@ -320,12 +331,12 @@ class Logger : public Component {
this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]: ", color, letter, tag, line);
}
inline void HOT format_body_to_buffer_(char *buffer, int *buffer_at, int buffer_size, const char *format,
inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format,
va_list args) {
// Get remaining capacity in the buffer
const int remaining = buffer_size - *buffer_at;
if (remaining <= 0)
if (*buffer_at >= buffer_size)
return;
const uint16_t remaining = buffer_size - *buffer_at;
const int ret = vsnprintf(buffer + *buffer_at, remaining, format, args);
@@ -334,7 +345,7 @@ class Logger : public Component {
}
// Update buffer_at with the formatted length (handle truncation)
int formatted_len = (ret >= remaining) ? remaining : ret;
uint16_t formatted_len = (ret >= remaining) ? remaining : ret;
*buffer_at += formatted_len;
// Remove all trailing newlines right after formatting
@@ -343,18 +354,18 @@ class Logger : public Component {
}
}
inline void HOT write_footer_to_buffer_(char *buffer, int *buffer_at, int buffer_size) {
static const int RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR);
inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
static const uint16_t RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR);
this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size);
}
};
extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
class LoggerMessageTrigger : public Trigger<int, const char *, const char *> {
class LoggerMessageTrigger : public Trigger<uint8_t, const char *, const char *> {
public:
explicit LoggerMessageTrigger(Logger *parent, int level) {
explicit LoggerMessageTrigger(Logger *parent, uint8_t level) {
this->level_ = level;
parent->add_on_log_callback([this](int level, const char *tag, const char *message) {
parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message) {
if (level <= this->level_) {
this->trigger(level, tag, message);
}
@@ -362,7 +373,7 @@ class LoggerMessageTrigger : public Trigger<int, const char *, const char *> {
}
protected:
int level_;
uint8_t level_;
};
} // namespace logger

View File

@@ -454,9 +454,13 @@ def container_validator(schema, widget_type: WidgetType):
"""
def validator(value):
result = schema
if w_sch := widget_type.schema:
result = result.extend(w_sch)
if isinstance(w_sch, dict):
w_sch = cv.Schema(w_sch)
# order is important here to preserve extras
result = w_sch.extend(schema)
else:
result = schema
ltype = df.TYPE_NONE
if value and (layout := value.get(df.CONF_LAYOUT)):
if not isinstance(layout, dict):

View File

@@ -3,7 +3,6 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.core import ID
from esphome.cpp_generator import MockObj
from .defines import (
CONF_STYLE_DEFINITIONS,
@@ -13,12 +12,13 @@ from .defines import (
literal,
)
from .helpers import add_lv_use
from .lvcode import LambdaContext, LocalVariable, lv, lv_assign, lv_variable
from .lvcode import LambdaContext, LocalVariable, lv
from .schemas import ALL_STYLES, FULL_STYLE_SCHEMA, STYLE_REMAP
from .types import ObjUpdateAction, lv_lambda_t, lv_obj_t, lv_obj_t_ptr, lv_style_t
from .types import ObjUpdateAction, lv_obj_t, lv_style_t
from .widgets import (
Widget,
add_widgets,
collect_parts,
set_obj_properties,
theme_widget_map,
wait_for_widgets,
@@ -37,12 +37,18 @@ async def style_set(svar, style):
lv.call(f"style_set_{remapped_prop}", svar, literal(value))
async def create_style(style, id_name):
style_id = ID(id_name, True, lv_style_t)
svar = cg.new_Pvariable(style_id)
lv.style_init(svar)
await style_set(svar, style)
return svar
async def styles_to_code(config):
"""Convert styles to C__ code."""
for style in config.get(CONF_STYLE_DEFINITIONS, ()):
svar = cg.new_Pvariable(style[CONF_ID])
lv.style_init(svar)
await style_set(svar, style)
await create_style(style, style[CONF_ID].id)
@automation.register_action(
@@ -68,16 +74,18 @@ async def theme_to_code(config):
if theme := config.get(CONF_THEME):
add_lv_use(CONF_THEME)
for w_name, style in theme.items():
if not isinstance(style, dict):
continue
lname = "lv_theme_apply_" + w_name
apply = lv_variable(lv_lambda_t, lname)
theme_widget_map[w_name] = apply
ow = Widget.create("obj", MockObj(ID("obj")), obj_spec)
async with LambdaContext([(lv_obj_t_ptr, "obj")], where=w_name) as context:
await set_obj_properties(ow, style)
lv_assign(apply, await context.get_lambda())
# Work around Python 3.10 bug with nested async comprehensions
# With Python 3.11 this could be simplified
styles = {}
for part, states in collect_parts(style).items():
styles[part] = {
state: await create_style(
props,
"_lv_theme_style_" + w_name + "_" + part + "_" + state,
)
for state, props in states.items()
}
theme_widget_map[w_name] = styles
async def add_top_layer(lv_component, config):

View File

@@ -6,7 +6,7 @@ from esphome.config_validation import Invalid
from esphome.const import CONF_DEFAULT, CONF_GROUP, CONF_ID, CONF_STATE, CONF_TYPE
from esphome.core import ID, TimePeriod
from esphome.coroutine import FakeAwaitable
from esphome.cpp_generator import CallExpression, MockObj
from esphome.cpp_generator import MockObj
from ..defines import (
CONF_FLEX_ALIGN_CROSS,
@@ -453,7 +453,17 @@ async def widget_to_code(w_cnfig, w_type: WidgetType, parent):
w = Widget.create(wid, var, spec, w_cnfig)
if theme := theme_widget_map.get(w_type):
lv_add(CallExpression(theme, w.obj))
for part, states in theme.items():
part = "LV_PART_" + part.upper()
for state, style in states.items():
state = "LV_STATE_" + state.upper()
if state == "LV_STATE_DEFAULT":
lv_state = literal(part)
elif part == "LV_PART_MAIN":
lv_state = literal(state)
else:
lv_state = join_enums((state, part))
lv.obj_add_style(w.obj, style, lv_state)
await set_obj_properties(w, w_cnfig)
await add_widgets(w, w_cnfig)
await spec.to_code(w, w_cnfig)

View File

@@ -1,8 +1,15 @@
import esphome.config_validation as cv
from esphome.const import CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_MODE, CONF_VALUE
from ..defines import BAR_MODES, CONF_ANIMATED, CONF_INDICATOR, CONF_MAIN, literal
from ..lv_validation import animated, get_start_value, lv_float
from ..defines import (
BAR_MODES,
CONF_ANIMATED,
CONF_INDICATOR,
CONF_MAIN,
CONF_START_VALUE,
literal,
)
from ..lv_validation import animated, lv_int
from ..lvcode import lv
from ..types import LvNumber, NumberType
from . import Widget
@@ -10,22 +17,30 @@ from . import Widget
# Note this file cannot be called "bar.py" because that name is disallowed.
CONF_BAR = "bar"
BAR_MODIFY_SCHEMA = cv.Schema(
{
cv.Optional(CONF_VALUE): lv_float,
cv.Optional(CONF_ANIMATED, default=True): animated,
}
)
def validate_bar(config):
if config.get(CONF_MODE) != "LV_BAR_MODE_RANGE" and CONF_START_VALUE in config:
raise cv.Invalid(
f"{CONF_START_VALUE} is only allowed when {CONF_MODE} is set to 'RANGE'"
)
if (CONF_MIN_VALUE in config) != (CONF_MAX_VALUE in config):
raise cv.Invalid(
f"If either {CONF_MIN_VALUE} or {CONF_MAX_VALUE} is set, both must be set"
)
return config
BAR_SCHEMA = cv.Schema(
{
cv.Optional(CONF_VALUE): lv_float,
cv.Optional(CONF_MIN_VALUE, default=0): cv.int_,
cv.Optional(CONF_MAX_VALUE, default=100): cv.int_,
cv.Optional(CONF_MODE, default="NORMAL"): BAR_MODES.one_of,
cv.Optional(CONF_VALUE): lv_int,
cv.Optional(CONF_START_VALUE): lv_int,
cv.Optional(CONF_MIN_VALUE): lv_int,
cv.Optional(CONF_MAX_VALUE): lv_int,
cv.Optional(CONF_MODE): BAR_MODES.one_of,
cv.Optional(CONF_ANIMATED, default=True): animated,
}
)
).add_extra(validate_bar)
class BarType(NumberType):
@@ -35,17 +50,23 @@ class BarType(NumberType):
LvNumber("lv_bar_t"),
parts=(CONF_MAIN, CONF_INDICATOR),
schema=BAR_SCHEMA,
modify_schema=BAR_MODIFY_SCHEMA,
)
async def to_code(self, w: Widget, config):
var = w.obj
if mode := config.get(CONF_MODE):
lv.bar_set_mode(var, literal(mode))
is_animated = literal(config[CONF_ANIMATED])
if CONF_MIN_VALUE in config:
lv.bar_set_range(var, config[CONF_MIN_VALUE], config[CONF_MAX_VALUE])
lv.bar_set_mode(var, literal(config[CONF_MODE]))
value = await get_start_value(config)
if value is not None:
lv.bar_set_value(var, value, literal(config[CONF_ANIMATED]))
lv.bar_set_range(
var,
await lv_int.process(config[CONF_MIN_VALUE]),
await lv_int.process(config[CONF_MAX_VALUE]),
)
if value := await lv_int.process(config.get(CONF_VALUE)):
lv.bar_set_value(var, value, is_animated)
if start_value := await lv_int.process(config.get(CONF_START_VALUE)):
lv.bar_set_start_value(var, start_value, is_animated)
@property
def animated(self):

View File

@@ -3,7 +3,7 @@ import esphome.config_validation as cv
from esphome.const import CONF_SIZE, CONF_TEXT
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 ..lvcode import LocalVariable, lv, lv_expr
from ..schemas import TEXT_SCHEMA
@@ -34,7 +34,7 @@ class QrCodeType(WidgetType):
)
def get_uses(self):
return ("canvas", "img")
return ("canvas", "img", "label")
def obj_creator(self, parent: MockObjClass, config: dict):
dark_color = color_retmapper(config[CONF_DARK_COLOR])
@@ -45,10 +45,8 @@ class QrCodeType(WidgetType):
async def to_code(self, w: Widget, config):
if (value := config.get(CONF_TEXT)) is not None:
value = await lv_text.process(value)
with LocalVariable(
"qr_text", cg.const_char_ptr, value, modifier=""
) as str_obj:
lv.qrcode_update(w.obj, str_obj, literal(f"strlen({str_obj})"))
with LocalVariable("qr_text", cg.std_string, value, modifier="") as str_obj:
lv.qrcode_update(w.obj, str_obj.c_str(), str_obj.size())
qr_code_spec = QrCodeType()

View File

@@ -11,9 +11,9 @@ from esphome.const import (
CONF_VOLUME,
)
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.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@jesserockz"]
@@ -81,7 +81,7 @@ IsAnnouncingCondition = media_player_ns.class_(
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, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
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(
class_: MockObjClass,
@@ -166,7 +168,6 @@ def media_player_schema(
MEDIA_PLAYER_SCHEMA = media_player_schema(MediaPlayer)
MEDIA_PLAYER_SCHEMA.add_extra(cv.deprecated_schema_constant("media_player"))
MEDIA_PLAYER_ACTION_SCHEMA = automation.maybe_simple_id(
cv.Schema(
{

View File

@@ -68,6 +68,7 @@ def AUTO_LOAD():
CONF_DISCOVER_IP = "discover_ip"
CONF_IDF_SEND_ASYNC = "idf_send_async"
CONF_WAIT_FOR_CONNECTION = "wait_for_connection"
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_WAIT_FOR_CONNECTION, default=False): cv.boolean,
}
),
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_wait_for_connection(config[CONF_WAIT_FOR_CONNECTION]))
MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema(
{

View File

@@ -176,7 +176,8 @@ void MQTTClientComponent::dump_config() {
}
}
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_() {

View File

@@ -4,11 +4,11 @@
#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/network/ip_address.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/log.h"
#if defined(USE_ESP32)
#include "mqtt_backend_esp32.h"
#elif defined(USE_ESP8266)
@@ -267,6 +267,8 @@ class MQTTClientComponent : public Component {
void set_publish_nan_as_none(bool publish_nan_as_none);
bool is_publish_nan_as_none() const;
void set_wait_for_connection(bool wait_for_connection) { this->wait_for_connection_ = wait_for_connection; }
protected:
void send_device_info_();
@@ -334,6 +336,7 @@ class MQTTClientComponent : public Component {
optional<MQTTClientDisconnectReason> disconnect_reason_{};
bool publish_nan_as_none_{false};
bool wait_for_connection_{false};
};
extern MQTTClientComponent *global_mqtt_client; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -963,13 +963,12 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool
uint16_t ret = 0;
uint8_t c = 0;
uint8_t nr_of_ff_bytes = 0;
uint64_t start;
bool exit_flag = false;
bool ff_flag = false;
start = App.get_loop_component_start_time();
const uint32_t start = millis();
while ((timeout == 0 && this->available()) || App.get_loop_component_start_time() - start <= timeout) {
while ((timeout == 0 && this->available()) || millis() - start <= timeout) {
if (!this->available()) {
App.feed_wdt();
delay(1);
@@ -1038,7 +1037,7 @@ void Nextion::add_no_result_to_queue_(const std::string &variable_name) {
nextion_queue->component = new nextion::NextionComponentBase;
nextion_queue->component->set_variable_name(variable_name);
nextion_queue->queue_time = App.get_loop_component_start_time();
nextion_queue->queue_time = millis();
this->nextion_queue_.push_back(nextion_queue);

View File

@@ -76,8 +76,8 @@ from esphome.const import (
DEVICE_CLASS_WIND_SPEED,
)
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_helpers import setup_entity
CODEOWNERS = ["@esphome/core"]
DEVICE_CLASSES = [
@@ -207,6 +207,9 @@ _NUMBER_SCHEMA = (
)
_NUMBER_SCHEMA.add_extra(entity_duplicate_validator("number"))
def number_schema(
class_: MockObjClass,
*,
@@ -237,7 +240,7 @@ NUMBER_SCHEMA.add_extra(cv.deprecated_schema_constant("number"))
async def setup_number_core_(
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_max_value(max_value))

View File

@@ -34,6 +34,7 @@ MULTI_CONF = True
CONF_ON_DOWNLOAD_FINISHED = "on_download_finished"
CONF_PLACEHOLDER = "placeholder"
CONF_UPDATE = "update"
_LOGGER = logging.getLogger(__name__)
@@ -167,6 +168,7 @@ SET_URL_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(OnlineImage),
cv.Required(CONF_URL): cv.templatable(cv.url),
cv.Optional(CONF_UPDATE, default=True): cv.templatable(bool),
}
)
@@ -188,6 +190,9 @@ async def online_image_action_to_code(config, action_id, template_arg, args):
if CONF_URL in config:
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
cg.add(var.set_url(template_))
if CONF_UPDATE in config:
template_ = await cg.templatable(config[CONF_UPDATE], args, bool)
cg.add(var.set_update(template_))
return var

View File

@@ -201,9 +201,12 @@ template<typename... Ts> class OnlineImageSetUrlAction : public Action<Ts...> {
public:
OnlineImageSetUrlAction(OnlineImage *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(std::string, url)
TEMPLATABLE_VALUE(bool, update)
void play(Ts... x) override {
this->parent_->set_url(this->url_.value(x...));
this->parent_->update();
if (this->update_.value(x...)) {
this->parent_->update();
}
}
protected:

View File

@@ -46,7 +46,7 @@ def set_sdkconfig_options(config):
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PANID", config[CONF_PAN_ID])
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_CHANNEL", config[CONF_CHANNEL])
add_idf_sdkconfig_option(
"CONFIG_OPENTHREAD_NETWORK_MASTERKEY", f"{config[CONF_NETWORK_KEY]:X}"
"CONFIG_OPENTHREAD_NETWORK_MASTERKEY", f"{config[CONF_NETWORK_KEY]:X}".lower()
)
if network_name := config.get(CONF_NETWORK_NAME):
@@ -54,14 +54,14 @@ def set_sdkconfig_options(config):
if (ext_pan_id := config.get(CONF_EXT_PAN_ID)) is not None:
add_idf_sdkconfig_option(
"CONFIG_OPENTHREAD_NETWORK_EXTPANID", f"{ext_pan_id:X}"
"CONFIG_OPENTHREAD_NETWORK_EXTPANID", f"{ext_pan_id:X}".lower()
)
if (mesh_local_prefix := config.get(CONF_MESH_LOCAL_PREFIX)) is not None:
add_idf_sdkconfig_option(
"CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX", f"{mesh_local_prefix:X}"
"CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX", f"{mesh_local_prefix}".lower()
)
if (pskc := config.get(CONF_PSKC)) is not None:
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}")
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}".lower())
if CONF_FORCE_DATASET in config:
if config[CONF_FORCE_DATASET]:
@@ -98,7 +98,7 @@ _CONNECTION_SCHEMA = cv.Schema(
cv.Optional(CONF_EXT_PAN_ID): cv.hex_int,
cv.Optional(CONF_NETWORK_NAME): cv.string_strict,
cv.Optional(CONF_PSKC): cv.hex_int,
cv.Optional(CONF_MESH_LOCAL_PREFIX): cv.hex_int,
cv.Optional(CONF_MESH_LOCAL_PREFIX): cv.ipv6network,
}
)

View File

@@ -137,7 +137,7 @@ void OpenThreadSrpComponent::setup() {
// Copy the mdns services to our local instance so that the c_str pointers remain valid for the lifetime of this
// component
this->mdns_services_ = this->mdns_->get_services();
ESP_LOGW(TAG, "Setting up SRP services. count = %d\n", this->mdns_services_.size());
ESP_LOGD(TAG, "Setting up SRP services. count = %d\n", this->mdns_services_.size());
for (const auto &service : this->mdns_services_) {
otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance);
if (!entry) {
@@ -185,11 +185,11 @@ void OpenThreadSrpComponent::setup() {
if (error != OT_ERROR_NONE) {
ESP_LOGW(TAG, "Failed to add service: %s", otThreadErrorToString(error));
}
ESP_LOGW(TAG, "Added service: %s", full_service.c_str());
ESP_LOGD(TAG, "Added service: %s", full_service.c_str());
}
otSrpClientEnableAutoStartMode(instance, srp_start_callback, nullptr);
ESP_LOGW(TAG, "Finished SRP setup");
ESP_LOGD(TAG, "Finished SRP setup");
}
void *OpenThreadSrpComponent::pool_alloc_(size_t size) {

View File

@@ -1,5 +1,6 @@
# Sourced from https://gist.github.com/agners/0338576e0003318b63ec1ea75adc90f9
import binascii
import ipaddress
from esphome.const import CONF_CHANNEL
@@ -37,6 +38,12 @@ def parse_tlv(tlv) -> dict:
if tag in TLV_TYPES:
if tag == 3:
output[TLV_TYPES[tag]] = val.decode("utf-8")
elif tag == 7:
mesh_local_prefix = binascii.hexlify(val).decode("utf-8")
mesh_local_prefix_str = f"{mesh_local_prefix}0000000000000000"
ipv6_bytes = bytes.fromhex(mesh_local_prefix_str)
ipv6_address = ipaddress.IPv6Address(ipv6_bytes)
output[TLV_TYPES[tag]] = f"{ipv6_address}/64"
else:
output[TLV_TYPES[tag]] = int.from_bytes(val)
return output

View File

View File

@@ -0,0 +1,122 @@
#include "opt3001.h"
#include "esphome/core/log.h"
namespace esphome {
namespace opt3001 {
static const char *const TAG = "opt3001.sensor";
static const uint8_t OPT3001_REG_RESULT = 0x00;
static const uint8_t OPT3001_REG_CONFIGURATION = 0x01;
// See datasheet for full description of each bit.
static const uint16_t OPT3001_CONFIGURATION_RANGE_FULL = 0b1100000000000000;
static const uint16_t OPT3001_CONFIGURATION_CONVERSION_TIME_800 = 0b100000000000;
static const uint16_t OPT3001_CONFIGURATION_CONVERSION_MODE_MASK = 0b11000000000;
static const uint16_t OPT3001_CONFIGURATION_CONVERSION_MODE_SINGLE_SHOT = 0b01000000000;
static const uint16_t OPT3001_CONFIGURATION_CONVERSION_MODE_SHUTDOWN = 0b00000000000;
// tl;dr: Configure an automatic-ranged, 800ms single shot reading,
// with INT processing disabled
static const uint16_t OPT3001_CONFIGURATION_FULL_RANGE_ONE_SHOT = OPT3001_CONFIGURATION_RANGE_FULL |
OPT3001_CONFIGURATION_CONVERSION_TIME_800 |
OPT3001_CONFIGURATION_CONVERSION_MODE_SINGLE_SHOT;
static const uint16_t OPT3001_CONVERSION_TIME_800 = 825; // give it 25 extra ms; it seems to not be ready quite often
/*
opt3001 properties:
- e (exponent) = high 4 bits of result register
- m (mantissa) = low 12 bits of result register
- formula: (0.01 * 2^e) * m lx
*/
void OPT3001Sensor::read_result_(const std::function<void(float)> &f) {
// ensure the single shot flag is clear, indicating it's done
uint16_t raw_value;
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Reading configuration register failed");
f(NAN);
return;
}
raw_value = i2c::i2ctohs(raw_value);
if ((raw_value & OPT3001_CONFIGURATION_CONVERSION_MODE_MASK) != OPT3001_CONFIGURATION_CONVERSION_MODE_SHUTDOWN) {
// not ready; wait 10ms and try again
ESP_LOGW(TAG, "Data not ready; waiting 10ms");
this->set_timeout("opt3001_wait", 10, [this, f]() { read_result_(f); });
return;
}
if (this->read_register(OPT3001_REG_RESULT, reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Reading result register failed");
f(NAN);
return;
}
raw_value = i2c::i2ctohs(raw_value);
uint8_t exponent = raw_value >> 12;
uint16_t mantissa = raw_value & 0b111111111111;
double lx = 0.01 * pow(2.0, double(exponent)) * double(mantissa);
f(float(lx));
}
void OPT3001Sensor::read_lx_(const std::function<void(float)> &f) {
// turn on (after one-shot sensor automatically powers down)
uint16_t start_measurement = i2c::htoi2cs(OPT3001_CONFIGURATION_FULL_RANGE_ONE_SHOT);
if (this->write_register(OPT3001_REG_CONFIGURATION, reinterpret_cast<uint8_t *>(&start_measurement), 2) !=
i2c::ERROR_OK) {
ESP_LOGW(TAG, "Triggering one shot measurement failed");
f(NAN);
return;
}
this->set_timeout("read", OPT3001_CONVERSION_TIME_800, [this, f]() {
if (this->write(&OPT3001_REG_CONFIGURATION, 1, true) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Starting configuration register read failed");
f(NAN);
return;
}
this->read_result_(f);
});
}
void OPT3001Sensor::dump_config() {
LOG_SENSOR("", "OPT3001", this);
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
}
LOG_UPDATE_INTERVAL(this);
}
void OPT3001Sensor::update() {
// Set a flag and skip just in case the sensor isn't responding,
// and we just keep waiting for it in read_result_.
// This way we don't end up with potentially boundless "threads"
// using up memory and eventually crashing the device
if (this->updating_) {
return;
}
this->updating_ = true;
this->read_lx_([this](float val) {
this->updating_ = false;
if (std::isnan(val)) {
this->status_set_warning();
this->publish_state(NAN);
return;
}
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val);
this->status_clear_warning();
this->publish_state(val);
});
}
float OPT3001Sensor::get_setup_priority() const { return setup_priority::DATA; }
} // namespace opt3001
} // namespace esphome

View File

@@ -0,0 +1,27 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace opt3001 {
/// This class implements support for the i2c-based OPT3001 ambient light sensor.
class OPT3001Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
public:
void dump_config() override;
void update() override;
float get_setup_priority() const override;
protected:
// checks if one-shot is complete before reading the result and returning it
void read_result_(const std::function<void(float)> &f);
// begins a one-shot measurement
void read_lx_(const std::function<void(float)> &f);
bool updating_{false};
};
} // namespace opt3001
} // namespace esphome

View File

@@ -0,0 +1,35 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
DEVICE_CLASS_ILLUMINANCE,
STATE_CLASS_MEASUREMENT,
UNIT_LUX,
)
DEPENDENCIES = ["i2c"]
CODEOWNERS = ["@ccutrer"]
opt3001_ns = cg.esphome_ns.namespace("opt3001")
OPT3001Sensor = opt3001_ns.class_(
"OPT3001Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
sensor.sensor_schema(
OPT3001Sensor,
unit_of_measurement=UNIT_LUX,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x44))
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)

View File

@@ -17,8 +17,8 @@ from esphome.const import (
CONF_WEB_SERVER,
)
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_helpers import setup_entity
CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True
@@ -65,6 +65,9 @@ _SELECT_SCHEMA = (
)
_SELECT_SCHEMA.add_extra(entity_duplicate_validator("select"))
def select_schema(
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]):
await setup_entity(var, config)
await setup_entity(var, config, "select")
cg.add(var.traits.set_options(options))

View File

@@ -101,8 +101,8 @@ from esphome.const import (
ENTITY_CATEGORY_CONFIG,
)
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_helpers import setup_entity
from esphome.util import Registry
CODEOWNERS = ["@esphome/core"]
@@ -318,6 +318,8 @@ _SENSOR_SCHEMA = (
)
)
_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("sensor"))
def sensor_schema(
class_: MockObjClass = cv.UNDEFINED,
@@ -787,7 +789,7 @@ async def build_filters(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:
cg.add(var.set_device_class(device_class))

View File

@@ -20,8 +20,8 @@ from esphome.const import (
DEVICE_CLASS_SWITCH,
)
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_helpers import setup_entity
CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True
@@ -91,6 +91,9 @@ _SWITCH_SCHEMA = (
)
_SWITCH_SCHEMA.add_extra(entity_duplicate_validator("switch"))
def switch_schema(
class_: MockObjClass,
*,
@@ -131,7 +134,7 @@ SWITCH_SCHEMA.add_extra(cv.deprecated_schema_constant("switch"))
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:
cg.add(var.set_inverted(inverted))

View File

@@ -14,8 +14,8 @@ from esphome.const import (
CONF_WEB_SERVER,
)
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_helpers import setup_entity
CODEOWNERS = ["@mauritskorse"]
IS_PLATFORM_COMPONENT = True
@@ -58,6 +58,9 @@ _TEXT_SCHEMA = (
)
_TEXT_SCHEMA.add_extra(entity_duplicate_validator("text"))
def text_schema(
class_: MockObjClass = cv.UNDEFINED,
*,
@@ -94,7 +97,7 @@ async def setup_text_core_(
max_length: int | 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_max_length(max_length))

View File

@@ -21,8 +21,8 @@ from esphome.const import (
DEVICE_CLASS_TIMESTAMP,
)
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_helpers import setup_entity
from esphome.util import Registry
DEVICE_CLASSES = [
@@ -153,6 +153,9 @@ _TEXT_SENSOR_SCHEMA = (
)
_TEXT_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("text_sensor"))
def text_sensor_schema(
class_: MockObjClass = cv.UNDEFINED,
*,
@@ -186,7 +189,7 @@ async def build_filters(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:
cg.add(var.set_device_class(device_class))

View File

@@ -15,8 +15,8 @@ from esphome.const import (
ENTITY_CATEGORY_CONFIG,
)
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_helpers import setup_entity
CODEOWNERS = ["@jesserockz"]
IS_PLATFORM_COMPONENT = True
@@ -58,6 +58,9 @@ _UPDATE_SCHEMA = (
)
_UPDATE_SCHEMA.add_extra(entity_duplicate_validator("update"))
def update_schema(
class_: MockObjClass = cv.UNDEFINED,
*,
@@ -87,7 +90,7 @@ UPDATE_SCHEMA.add_extra(cv.deprecated_schema_constant("update"))
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):
cg.add(var.set_device_class(device_class_config))

View File

@@ -6,7 +6,7 @@ from esphome.components.esp32 import (
only_on_variant,
)
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.const import CONF_DEVICES, CONF_ID
from esphome.cpp_types import Component
AUTO_LOAD = ["bytebuffer"]
@@ -16,7 +16,6 @@ usb_host_ns = cg.esphome_ns.namespace("usb_host")
USBHost = usb_host_ns.class_("USBHost", Component)
USBClient = usb_host_ns.class_("USBClient", Component)
CONF_DEVICES = "devices"
CONF_VID = "vid"
CONF_PID = "pid"
CONF_ENABLE_HUBS = "enable_hubs"

View File

@@ -22,8 +22,8 @@ from esphome.const import (
DEVICE_CLASS_WATER,
)
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_helpers import setup_entity
IS_PLATFORM_COMPONENT = True
@@ -103,6 +103,9 @@ _VALVE_SCHEMA = (
)
_VALVE_SCHEMA.add_extra(entity_duplicate_validator("valve"))
def valve_schema(
class_: MockObjClass = cv.UNDEFINED,
*,
@@ -132,7 +135,7 @@ VALVE_SCHEMA.add_extra(cv.deprecated_schema_constant("valve"))
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):
cg.add(var.set_device_class(device_class_config))

View File

@@ -1,9 +1,19 @@
"""Helpers for config validation using voluptuous."""
from __future__ import annotations
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime
from ipaddress import AddressValueError, IPv4Address, ip_address
from ipaddress import (
AddressValueError,
IPv4Address,
IPv4Network,
IPv6Address,
IPv6Network,
ip_address,
ip_network,
)
import logging
import os
import re
@@ -21,6 +31,7 @@ from esphome.const import (
CONF_COMMAND_RETAIN,
CONF_COMMAND_TOPIC,
CONF_DAY,
CONF_DEVICE_ID,
CONF_DISABLED_BY_DEFAULT,
CONF_DISCOVERY,
CONF_ENTITY_CATEGORY,
@@ -347,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):
"""Validate the given config option to be a boolean.
@@ -1176,6 +1194,14 @@ def ipv4address(value):
return address
def ipv6address(value):
try:
address = IPv6Address(value)
except AddressValueError as exc:
raise Invalid(f"{value} is not a valid IPv6 address") from exc
return address
def ipv4address_multi_broadcast(value):
address = ipv4address(value)
if not (address.is_multicast or (address == IPv4Address("255.255.255.255"))):
@@ -1193,6 +1219,33 @@ def ipaddress(value):
return address
def ipv4network(value):
"""Validate that the value is a valid IPv4 network."""
try:
network = IPv4Network(value, strict=False)
except ValueError as exc:
raise Invalid(f"{value} is not a valid IPv4 network") from exc
return network
def ipv6network(value):
"""Validate that the value is a valid IPv6 network."""
try:
network = IPv6Network(value, strict=False)
except ValueError as exc:
raise Invalid(f"{value} is not a valid IPv6 network") from exc
return network
def ipnetwork(value):
"""Validate that the value is a valid IP network."""
try:
network = ip_network(value, strict=False)
except ValueError as exc:
raise Invalid(f"{value} is not a valid IP network") from exc
return network
def _valid_topic(value):
"""Validate that this is a valid topic name/filter."""
if value is None: # Used to disable publishing and subscribing
@@ -1853,6 +1906,7 @@ ENTITY_BASE_SCHEMA = Schema(
Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean,
Optional(CONF_ICON): icon,
Optional(CONF_ENTITY_CATEGORY): entity_category,
Optional(CONF_DEVICE_ID): sub_device_id,
}
)
@@ -1921,7 +1975,7 @@ class Version:
return f"{self.major}.{self.minor}.{self.patch}"
@classmethod
def parse(cls, value: str) -> "Version":
def parse(cls, value: str) -> Version:
match = re.match(r"^(\d+).(\d+).(\d+)-?\w*$", value)
if match is None:
raise ValueError(f"Not a valid version number {value}")

View File

@@ -56,6 +56,8 @@ CONF_AP = "ap"
CONF_APPARENT_POWER = "apparent_power"
CONF_ARDUINO_VERSION = "arduino_version"
CONF_AREA = "area"
CONF_AREA_ID = "area_id"
CONF_AREAS = "areas"
CONF_ARGS = "args"
CONF_ASSUMED_STATE = "assumed_state"
CONF_AT = "at"
@@ -217,6 +219,8 @@ CONF_DEST = "dest"
CONF_DEVICE = "device"
CONF_DEVICE_CLASS = "device_class"
CONF_DEVICE_FACTOR = "device_factor"
CONF_DEVICE_ID = "device_id"
CONF_DEVICES = "devices"
CONF_DIELECTRIC_CONSTANT = "dielectric_constant"
CONF_DIMENSIONS = "dimensions"
CONF_DIO_PIN = "dio_pin"

View File

@@ -522,6 +522,9 @@ class EsphomeCore:
# Dict to track platform entity counts for pre-allocation
# Key: platform name (e.g. "sensor", "binary_sensor"), Value: count
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
self.verbose = False
# Whether ESPHome was started in quiet mode
@@ -553,6 +556,7 @@ class EsphomeCore:
self.loaded_integrations = set()
self.component_ids = set()
self.platform_counts = defaultdict(int)
self.unique_ids = set()
PIN_SCHEMA_REGISTRY.reset()
@property

View File

@@ -257,6 +257,17 @@ void Application::teardown_components(uint32_t timeout_ms) {
}
void Application::calculate_looping_components_() {
// Count total components that need looping
size_t total_looping = 0;
for (auto *obj : this->components_) {
if (obj->has_overridden_loop()) {
total_looping++;
}
}
// Pre-reserve vector to avoid reallocations
this->looping_components_.reserve(total_looping);
// First add all active components
for (auto *obj : this->components_) {
if (obj->has_overridden_loop() &&

View File

@@ -9,6 +9,13 @@
#include "esphome/core/preferences.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
#include <sys/select.h>
#endif
@@ -87,7 +94,7 @@ static const uint32_t TEARDOWN_TIMEOUT_REBOOT_MS = 1000; // 1 second for quick
class Application {
public:
void pre_setup(const std::string &name, const std::string &friendly_name, const char *area, const char *comment,
void pre_setup(const std::string &name, const std::string &friendly_name, const char *comment,
const char *compilation_time, bool name_add_mac_suffix) {
arch_init();
this->name_add_mac_suffix_ = name_add_mac_suffix;
@@ -102,11 +109,17 @@ class Application {
this->name_ = name;
this->friendly_name_ = friendly_name;
}
this->area_ = area;
this->comment_ = comment;
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; }
Component *get_current_component() { return this->current_component_; }
@@ -264,6 +277,12 @@ class Application {
#ifdef USE_UPDATE
void reserve_update(size_t count) { this->updates_.reserve(count); }
#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.
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_; }
/// 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().
std::string get_comment() const { return this->comment_; }
@@ -334,6 +361,12 @@ class Application {
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
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) {
@@ -610,6 +643,12 @@ class Application {
uint16_t current_loop_index_{0};
bool in_loop_{false};
#ifdef USE_DEVICES
std::vector<Device *> devices_{};
#endif
#ifdef USE_AREAS
std::vector<Area *> areas_{};
#endif
#ifdef USE_BINARY_SENSOR
std::vector<binary_sensor::BinarySensor *> binary_sensors_{};
#endif
@@ -676,7 +715,6 @@ class Application {
std::string name_;
std::string friendly_name_;
const char *area_{nullptr};
const char *comment_{nullptr};
const char *compilation_time_{nullptr};
bool name_add_mac_suffix_;

19
esphome/core/area.h Normal file
View 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

View File

@@ -1,18 +1,24 @@
from __future__ import annotations
import logging
import os
from pathlib import Path
from esphome import automation
from esphome import automation, core
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_AREA,
CONF_AREA_ID,
CONF_AREAS,
CONF_BUILD_PATH,
CONF_COMMENT,
CONF_COMPILE_PROCESS_LIMIT,
CONF_DEBUG_SCHEDULER,
CONF_DEVICES,
CONF_ESPHOME,
CONF_FRIENDLY_NAME,
CONF_ID,
CONF_INCLUDES,
CONF_LIBRARIES,
CONF_MIN_VERSION,
@@ -32,7 +38,13 @@ from esphome.const import (
__version__ as ESPHOME_VERSION,
)
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__)
@@ -48,7 +60,8 @@ LoopTrigger = cg.esphome_ns.class_(
ProjectUpdateTrigger = cg.esphome_ns.class_(
"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"}
@@ -71,6 +84,56 @@ def validate_hostname(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):
# Look for "<...>" includes
if value.startswith("<") and value.endswith(">"):
@@ -111,13 +174,32 @@ if "ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT" in os.environ:
else:
_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(
cv.Schema(
{
cv.Required(CONF_NAME): cv.valid_name,
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.Required(CONF_BUILD_PATH): cv.string,
cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema(
@@ -167,11 +249,17 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(
CONF_COMPILE_PROCESS_LIMIT, default=_compile_process_limit_default
): 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,
)
FINAL_VALIDATE_SCHEMA = cv.All(validate_ids_and_references)
PRELOAD_CONFIG_SCHEMA = cv.Schema(
{
cv.Required(CONF_NAME): cv.valid_name,
@@ -336,7 +424,7 @@ async def _add_platform_reserves() -> None:
@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)
# These can be used by user lambdas, put them to default scope
cg.add_global(cg.RawExpression("using std::isnan"))
@@ -347,7 +435,6 @@ async def to_code(config):
cg.App.pre_setup(
config[CONF_NAME],
config[CONF_FRIENDLY_NAME],
config[CONF_AREA],
config.get(CONF_COMMENT, ""),
cg.RawExpression('__DATE__ ", " __TIME__'),
config[CONF_NAME_ADD_MAC_SUFFIX],
@@ -417,3 +504,50 @@ async def to_code(config):
if 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))

View File

@@ -20,6 +20,7 @@
// Feature flags
#define USE_ALARM_CONTROL_PANEL
#define USE_AREAS
#define USE_BINARY_SENSOR
#define USE_BUTTON
#define USE_CLIMATE
@@ -29,6 +30,7 @@
#define USE_DATETIME_DATETIME
#define USE_DATETIME_TIME
#define USE_DEEP_SLEEP
#define USE_DEVICES
#define USE_DISPLAY
#define USE_ESP32_IMPROV_STATE_CALLBACK
#define USE_EVENT

20
esphome/core/device.h Normal file
View 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

View File

@@ -11,7 +11,14 @@ const StringRef &EntityBase::get_name() const { return this->name_; }
void EntityBase::set_name(const char *name) {
this->name_ = StringRef(name);
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;
} else {
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
void EntityBase::calc_object_id_() {
// Check if `App.get_friendly_name()` is constant or dynamic.
if (!this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled()) {
// `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_);
}
}
void EntityBase::calc_object_id_() { this->object_id_hash_ = fnv1_hash(this->get_object_id()); }
uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; }

View File

@@ -6,6 +6,10 @@
#include "helpers.h"
#include "log.h"
#ifdef USE_DEVICES
#include "device.h"
#endif
namespace esphome {
enum EntityCategory : uint8_t {
@@ -51,6 +55,17 @@ class EntityBase {
std::string get_icon() const;
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
bool has_state() const { return this->flags_.has_state; }
@@ -67,6 +82,9 @@ class EntityBase {
const char *object_id_c_str_{nullptr};
const char *icon_c_str_{nullptr};
uint32_t object_id_hash_{};
#ifdef USE_DEVICES
Device *device_{};
#endif
// Bit-packed flags to save memory (1 byte instead of 5)
struct EntityFlags {

View File

@@ -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
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):
@@ -54,3 +165,48 @@ def inherit_property_from(property_to_inherit, parent_id_property, transform=Non
return config
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

View File

@@ -29,7 +29,7 @@ void HOT esp_log_vprintf_(int level, const char *tag, int line, const char *form
if (log == nullptr)
return;
log->log_vprintf_(level, tag, line, format, args);
log->log_vprintf_(static_cast<uint8_t>(level), tag, line, format, args);
#endif
}
@@ -41,7 +41,7 @@ void HOT esp_log_vprintf_(int level, const char *tag, int line, const __FlashStr
if (log == nullptr)
return;
log->log_vprintf_(level, tag, line, format, args);
log->log_vprintf_(static_cast<uint8_t>(level), tag, line, format, args);
#endif
}
#endif

View File

@@ -319,13 +319,17 @@ bool HOT Scheduler::cancel_item_(Component *component, const std::string &name,
return ret;
}
uint64_t Scheduler::millis_() {
// Get the current 32-bit millis value
const uint32_t now = millis();
// Check for rollover by comparing with last value
if (now < this->last_millis_) {
// Detected rollover (happens every ~49.7 days)
this->millis_major_++;
ESP_LOGD(TAG, "Incrementing scheduler major at %" PRIu64 "ms",
now + (static_cast<uint64_t>(this->millis_major_) << 32));
}
this->last_millis_ = now;
// Combine major (high 32 bits) and now (low 32 bits) into 64-bit time
return now + (static_cast<uint64_t>(this->millis_major_) << 32);
}

View File

@@ -29,12 +29,16 @@ class Scheduler {
protected:
struct SchedulerItem {
// Ordered by size to minimize padding
Component *component;
std::string name;
enum Type { TIMEOUT, INTERVAL } type;
uint32_t interval;
// 64-bit time to handle millis() rollover. The scheduler combines the 32-bit millis()
// with a 16-bit rollover counter to create a 64-bit time that won't roll over for
// billions of years. This ensures correct scheduling even when devices run for months.
uint64_t next_execution_;
std::string name;
std::function<void()> callback;
enum Type : uint8_t { TIMEOUT, INTERVAL } type;
bool remove;
static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b);

View File

@@ -1,11 +1,6 @@
import logging
from esphome.const import (
CONF_DISABLED_BY_DEFAULT,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_INTERNAL,
CONF_NAME,
CONF_SAFE_MODE,
CONF_SETUP_PRIORITY,
CONF_TYPE_ID,
@@ -16,7 +11,6 @@ from esphome.core import CORE, ID, coroutine
from esphome.coroutine import FakeAwaitable
from esphome.cpp_generator import add, get_variable
from esphome.cpp_types import App
from esphome.helpers import sanitize, snake_case
from esphome.types import ConfigFragmentType, ConfigType
from esphome.util import Registry, RegistryEntry
@@ -96,22 +90,6 @@ async def register_parented(var, value):
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(
registry: Registry,
full_config: ConfigType,

View File

@@ -1,25 +1,9 @@
from __future__ import annotations
import unicodedata
from esphome.const import ALLOWED_NAME_CHARS
from esphome.helpers import slugify
def strip_accents(value):
return "".join(
c
for c in unicodedata.normalize("NFD", str(value))
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)
def friendly_name_slugify(value: str) -> str:
"""Convert a friendly name to a slug with dashes instead of underscores."""
# First use the standard slugify, then convert underscores to dashes
return slugify(value).replace("_", "-")

View File

@@ -29,6 +29,53 @@ def ensure_unique_string(preferred_string, current_strings):
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=" "):
lines = text.splitlines(True)
if len(lines) <= 2:

View File

@@ -5,7 +5,7 @@ import fnmatch
import functools
import inspect
from io import BytesIO, TextIOBase, TextIOWrapper
from ipaddress import _BaseAddress
from ipaddress import _BaseAddress, _BaseNetwork
import logging
import math
import os
@@ -621,6 +621,7 @@ ESPHomeDumper.add_multi_representer(str, ESPHomeDumper.represent_stringify)
ESPHomeDumper.add_multi_representer(int, ESPHomeDumper.represent_int)
ESPHomeDumper.add_multi_representer(float, ESPHomeDumper.represent_float)
ESPHomeDumper.add_multi_representer(_BaseAddress, ESPHomeDumper.represent_stringify)
ESPHomeDumper.add_multi_representer(_BaseNetwork, ESPHomeDumper.represent_stringify)
ESPHomeDumper.add_multi_representer(MACAddress, ESPHomeDumper.represent_stringify)
ESPHomeDumper.add_multi_representer(TimePeriod, ESPHomeDumper.represent_stringify)
ESPHomeDumper.add_multi_representer(Lambda, ESPHomeDumper.represent_lambda)

View File

@@ -13,7 +13,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
esptool==4.9.0
click==8.1.7
esphome-dashboard==20250514.0
aioesphomeapi==33.1.0
aioesphomeapi==33.1.1
zeroconf==0.147.0
puremagic==1.29
ruamel.yaml==0.18.14 # dashboard_import

View File

@@ -12,12 +12,12 @@ sensor:
frequency: 60Hz
phase_a:
name: Channel A
voltage: Voltage
current: Current
active_power: Active Power
power_factor: Power Factor
forward_active_energy: Forward Active Energy
reverse_active_energy: Reverse Active Energy
voltage: Channel A Voltage
current: Channel A Current
active_power: Channel A Active Power
power_factor: Channel A Power Factor
forward_active_energy: Channel A Forward Active Energy
reverse_active_energy: Channel A Reverse Active Energy
calibration:
current_gain: 3116628
voltage_gain: -757178
@@ -25,12 +25,12 @@ sensor:
phase_angle: 188
phase_b:
name: Channel B
voltage: Voltage
current: Current
active_power: Active Power
power_factor: Power Factor
forward_active_energy: Forward Active Energy
reverse_active_energy: Reverse Active Energy
voltage: Channel B Voltage
current: Channel B Current
active_power: Channel B Active Power
power_factor: Channel B Power Factor
forward_active_energy: Channel B Forward Active Energy
reverse_active_energy: Channel B Reverse Active Energy
calibration:
current_gain: 3133655
voltage_gain: -755235
@@ -38,12 +38,12 @@ sensor:
phase_angle: 188
phase_c:
name: Channel C
voltage: Voltage
current: Current
active_power: Active Power
power_factor: Power Factor
forward_active_energy: Forward Active Energy
reverse_active_energy: Reverse Active Energy
voltage: Channel C Voltage
current: Channel C Current
active_power: Channel C Active Power
power_factor: Channel C Power Factor
forward_active_energy: Channel C Forward Active Energy
reverse_active_energy: Channel C Reverse Active Energy
calibration:
current_gain: 3111158
voltage_gain: -743813
@@ -51,6 +51,6 @@ sensor:
phase_angle: 180
neutral:
name: Neutral
current: Current
current: Neutral Current
calibration:
current_gain: 3189

View File

@@ -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())));
- platform: template
id: alarmcontrolpanel2
name: Alarm Panel
name: Alarm Panel 2
codes:
- "1234"
requires_code_to_arm: true

View File

@@ -26,7 +26,7 @@ binary_sensor:
sensor:
- platform: binary_sensor_map
name: Binary Sensor Map
name: Binary Sensor Map Group
type: group
channels:
- binary_sensor: bin1
@@ -36,7 +36,7 @@ sensor:
- binary_sensor: bin3
value: 100.0
- platform: binary_sensor_map
name: Binary Sensor Map
name: Binary Sensor Map Sum
type: sum
channels:
- binary_sensor: bin1
@@ -46,7 +46,7 @@ sensor:
- binary_sensor: bin3
value: 100.0
- platform: binary_sensor_map
name: Binary Sensor Map
name: Binary Sensor Map Bayesian
type: bayesian
prior: 0.4
observations:

View File

@@ -5,7 +5,7 @@ one_wire:
sensor:
- platform: dallas_temp
address: 0x1C0000031EDD2A28
name: Dallas Temperature
name: Dallas Temperature 1
resolution: 9
- platform: dallas_temp
name: Dallas Temperature
name: Dallas Temperature 2

View File

@@ -2,7 +2,9 @@ esphome:
debug_scheduler: true
platformio_options:
board_build.flash_mode: dio
area: testing
area:
id: testing_area
name: Testing Area
on_boot:
logger.log: on_boot
on_shutdown:
@@ -17,4 +19,20 @@ esphome:
version: "1.1"
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

View File

@@ -7,20 +7,20 @@ climate:
protocol: mitsubishi_heavy_zm
horizontal_default: left
vertical_default: up
name: HeatpumpIR Climate
name: HeatpumpIR Climate Mitsubishi
min_temperature: 18
max_temperature: 30
- platform: heatpumpir
protocol: daikin
horizontal_default: mleft
vertical_default: mup
name: HeatpumpIR Climate
name: HeatpumpIR Climate Daikin
min_temperature: 18
max_temperature: 30
- platform: heatpumpir
protocol: panasonic_altdke
horizontal_default: mright
vertical_default: mdown
name: HeatpumpIR Climate
name: HeatpumpIR Climate Panasonic
min_temperature: 18
max_temperature: 30

View File

@@ -114,7 +114,7 @@ light:
warm_white_color_temperature: 500 mireds
- platform: rgb
id: test_rgb_light_initial_state
name: RGB Light
name: RGB Light Initial State
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3

View File

@@ -6,13 +6,13 @@ i2c:
sensor:
- platform: ltr390
uv:
name: LTR390 UV
name: LTR390 UV 1
uv_index:
name: LTR390 UVI
name: LTR390 UVI 1
light:
name: LTR390 Light
name: LTR390 Light 1
ambient_light:
name: LTR390 ALS
name: LTR390 ALS 1
gain: X3
resolution: 18
window_correction_factor: 1.0
@@ -20,13 +20,13 @@ sensor:
update_interval: 60s
- platform: ltr390
uv:
name: LTR390 UV
name: LTR390 UV 2
uv_index:
name: LTR390 UVI
name: LTR390 UVI 2
light:
name: LTR390 Light
name: LTR390 Light 2
ambient_light:
name: LTR390 ALS
name: LTR390 ALS 2
gain:
ambient_light: X9
uv: X3

View File

@@ -24,33 +24,33 @@ sensor:
widget: lv_arc
- platform: lvgl
widget: slider_id
name: LVGL Slider
name: LVGL Slider Sensor
- platform: lvgl
widget: bar_id
id: lvgl_bar_sensor
name: LVGL Bar
name: LVGL Bar Sensor
- platform: lvgl
widget: spinbox_id
name: LVGL Spinbox
name: LVGL Spinbox Sensor
number:
- platform: lvgl
widget: slider_id
name: LVGL Slider
name: LVGL Slider Number
update_on_release: true
restore_value: true
- platform: lvgl
widget: lv_arc
id: lvgl_arc_number
name: LVGL Arc
name: LVGL Arc Number
- platform: lvgl
widget: bar_id
id: lvgl_bar_number
name: LVGL Bar
name: LVGL Bar Number
- platform: lvgl
widget: spinbox_id
id: lvgl_spinbox_number
name: LVGL Spinbox
name: LVGL Spinbox Number
light:
- platform: lvgl

View File

@@ -646,7 +646,9 @@ lvgl:
on_click:
lvgl.qrcode.update:
id: lv_qr
text: homeassistant.io
text:
format: "A string with a number %d"
args: ['(int)(random_uint32() % 1000)']
- slider:
min_value: 0
@@ -728,12 +730,15 @@ lvgl:
value: 30
max_value: 100
min_value: 10
start_value: 20
mode: range
on_click:
then:
- lvgl.bar.update:
id: bar_id
value: !lambda return (int)((float)rand() / RAND_MAX * 100);
start_value: !lambda return (int)((float)rand() / RAND_MAX * 100);
mode: symmetrical
- logger.log:
format: "bar value %f"
args: [x]

View File

@@ -170,4 +170,4 @@ switch:
otc_active:
name: "Boiler Outside temperature compensation active"
ch2_active:
name: "Boiler Central Heating 2 active"
name: "Boiler Central Heating 2 active status"

View File

@@ -8,4 +8,6 @@ openthread:
pan_id: 0x8f28
ext_pan_id: 0xd63e8e3e495ebbc3
pskc: 0xc23a76e98f1a6483639b1ac1271e2e27
mesh_local_prefix: fd53:145f:ed22:ad81::/64
force_dataset: true

View File

@@ -0,0 +1,10 @@
i2c:
- id: i2c_opt3001
scl: ${scl_pin}
sda: ${sda_pin}
sensor:
- platform: opt3001
name: Living Room Brightness
address: 0x44
update_interval: 30s

View File

@@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO16
sda_pin: GPIO17
<<: !include common.yaml

View File

@@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml

View File

@@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml

View File

@@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO16
sda_pin: GPIO17
<<: !include common.yaml

View File

@@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml

View File

@@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml

View File

@@ -5,7 +5,7 @@ packages:
- !include package.yaml
- github://esphome/esphome/tests/components/template/common.yaml@dev
- url: https://github.com/esphome/esphome
file: tests/components/binary_sensor_map/common.yaml
file: tests/components/absolute_humidity/common.yaml
ref: dev
refresh: 1d

View File

@@ -7,7 +7,7 @@ packages:
shorthand: github://esphome/esphome/tests/components/template/common.yaml@dev
github:
url: https://github.com/esphome/esphome
file: tests/components/binary_sensor_map/common.yaml
file: tests/components/absolute_humidity/common.yaml
ref: dev
refresh: 1d

View File

@@ -115,7 +115,7 @@ button:
address: 0x00
command: 0x0B
- platform: template
name: RC5
name: RC5 Raw
on_press:
remote_transmitter.transmit_raw:
code: [1000, -1000]

View File

@@ -12,7 +12,7 @@
using namespace esphome;
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
log->pre_setup();
log->set_uart_selection(logger::UART_SELECTION_UART0);

Some files were not shown because too many files have changed in this diff Show More