1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-11 20:35:51 +00:00

Compare commits

..

442 Commits

Author SHA1 Message Date
J. Nick Koston
217e5a4833 cleanups 2025-11-11 14:05:55 -06:00
J. Nick Koston
fd3689dd64 cleanup 2025-11-11 14:00:25 -06:00
J. Nick Koston
c166e063f9 cleanups 2025-11-11 13:55:29 -06:00
J. Nick Koston
8b6400f24c Fix scan failing after restart 2025-11-11 13:12:10 -06:00
J. Nick Koston
bbff660499 Merge branch 'integration' into memory_api 2025-11-11 12:19:33 -06:00
J. Nick Koston
e7409ac5cd Merge remote-tracking branch 'upstream/dev' into integration 2025-11-11 12:19:24 -06:00
tomaszduda23
661920c51e [nrf52,ssd1306_i2c] fix build error (#11847) 2025-11-11 18:18:17 +00:00
tomaszduda23
a6b905e148 [nrf52,pcf8563] fix build error (#11846)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-11-11 17:50:07 +00:00
J. Nick Koston
366e95f8d8 Merge branch 'integration' into memory_api 2025-11-11 11:25:11 -06:00
J. Nick Koston
326edd5082 Merge branch 'timezone' into integration 2025-11-11 11:25:03 -06:00
J. Nick Koston
d74fc6347b Update esphome/components/homeassistant/time/homeassistant_time.cpp 2025-11-11 11:24:41 -06:00
J. Nick Koston
dbbc4f741d Merge branch 'integration' into memory_api 2025-11-11 11:23:02 -06:00
J. Nick Koston
2d63b69ac1 Merge branch 'timezone' into integration 2025-11-11 11:22:55 -06:00
J. Nick Koston
a14e2d4d08 Update esphome/components/time/real_time_clock.cpp 2025-11-11 11:22:33 -06:00
J. Nick Koston
300bd420f8 Merge branch 'integration' into memory_api 2025-11-11 11:19:12 -06:00
J. Nick Koston
2d2472c50b Merge branch 'timezone' into integration 2025-11-11 11:19:03 -06:00
J. Nick Koston
2e115baf56 Merge remote-tracking branch 'tomaszduda23/timezone' into timezone 2025-11-11 11:17:47 -06:00
J. Nick Koston
b58b706bd6 fix 2025-11-11 11:17:05 -06:00
Tomasz Duda
d389ed585e fix 2025-11-11 18:13:02 +01:00
Tomasz Duda
1b30346c1e fix 2025-11-11 18:08:10 +01:00
Tomasz Duda
6b45debcba Merge remote-tracking branch 'origin/dev' into timezone 2025-11-11 18:00:20 +01:00
J. Nick Koston
e42b29659b Merge branch 'integration' into memory_api 2025-11-11 09:44:58 -06:00
J. Nick Koston
aba9ffccdf Merge branch 'retry_hidden_no_stuck_last_networks_visible' into integration 2025-11-11 09:44:52 -06:00
J. Nick Koston
8e29ae416e Update esphome/components/wifi/wifi_component.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-11 09:44:34 -06:00
J. Nick Koston
75c220eeb6 more tweaks for corner cases 2025-11-11 09:42:09 -06:00
tomaszduda23
a6b7c1f18c [nrf52,gpio] add gpio levels for high voltage mode (#9858)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-11-11 15:17:25 +00:00
J. Nick Koston
d2e1fbd76b Merge branch 'integration' into memory_api 2025-11-11 09:17:19 -06:00
J. Nick Koston
3bb7639470 Merge branch 'retry_hidden_no_stuck_last_networks_visible' into integration 2025-11-11 09:17:11 -06:00
J. Nick Koston
72a6051f0d [wifi] Fix infinite loop in RETRY_HIDDEN when remaining networks are visible 2025-11-11 09:16:31 -06:00
J. Nick Koston
649e27bf62 Merge branch 'integration' into memory_api 2025-11-11 08:53:53 -06:00
J. Nick Koston
77436b85b2 Merge branch 'lost_prio_decrease_merge_conflict_fix' into integration 2025-11-11 08:53:47 -06:00
J. Nick Koston
5f0957c81a Merge branch 'wifi_int8_prio' into lost_prio_decrease_merge_conflict_fix 2025-11-11 08:53:27 -06:00
J. Nick Koston
bee174150b fixes 2025-11-11 08:52:12 -06:00
J. Nick Koston
262f28aec5 Merge remote-tracking branch 'origin/wifi_int8_prio' into wifi_int8_prio 2025-11-11 08:51:10 -06:00
J. Nick Koston
bf312ad9ec fixes 2025-11-11 08:50:54 -06:00
Tomasz Duda
55cf0adb18 [nrf52,pcf8563] fix build error 2025-11-11 15:38:19 +01:00
J. Nick Koston
941feeedbe Merge branch 'dev' into wifi_int8_prio 2025-11-11 08:33:57 -06:00
J. Nick Koston
4565b126e2 Merge branch 'integration' into memory_api 2025-11-11 08:32:27 -06:00
J. Nick Koston
64651b5a07 Merge branch 'lost_prio_decrease_merge_conflict_fix' into integration 2025-11-11 08:31:33 -06:00
J. Nick Koston
f3007a5245 Merge branch 'wifi_manual_ip' into integration 2025-11-11 08:31:27 -06:00
J. Nick Koston
0e62c8b3fb Merge branch 'wifi_int8_prio' into lost_prio_decrease_merge_conflict_fix 2025-11-11 08:28:02 -06:00
J. Nick Koston
4160157457 [wifi] Restore two-attempt BSSID filtering for mesh networks 2025-11-11 08:26:15 -06:00
J. Nick Koston
75d7578491 Merge wifi_int8_prio into wifi_manual_ip
Changes priority type from float to int8_t for memory savings.
Resolves conflict with USE_WIFI_MANUAL_IP conditional compilation.
2025-11-11 08:10:39 -06:00
J. Nick Koston
f28566545f Merge branch 'integration' into memory_api 2025-11-10 22:25:51 -06:00
J. Nick Koston
dc37321aa9 Merge branch 'wifi_manual_ip' into integration 2025-11-10 22:25:43 -06:00
J. Nick Koston
89abd9c817 fix conflict 2025-11-10 22:24:22 -06:00
J. Nick Koston
d4d44a5c08 manual_ip test 2025-11-10 22:23:29 -06:00
J. Nick Koston
b8e4efc1cd manual_ip test 2025-11-10 22:23:02 -06:00
J. Nick Koston
cf66c4cd3e Merge branch 'integration' into memory_api 2025-11-10 22:14:11 -06:00
J. Nick Koston
4b60012814 Merge branch 'wifi_manual_ip' into integration
# Conflicts:
#	esphome/components/wifi/wifi_component.h
2025-11-10 22:13:44 -06:00
J. Nick Koston
c38df0af85 [wifi] Conditionally compile manual_ip to save 24-120 bytes RAM 2025-11-10 22:09:01 -06:00
J. Nick Koston
bb51c6b6d5 Merge branch 'integration' into memory_api 2025-11-10 21:59:32 -06:00
J. Nick Koston
b8f972b6f6 Merge branch 'ethernet_manual_ip_cond' into integration 2025-11-10 21:59:25 -06:00
J. Nick Koston
d87063865c [ethernet] Conditionally compile manual_ip to save 24 bytes RAM 2025-11-10 21:57:52 -06:00
J. Nick Koston
682b6711f3 Merge branch 'integration' into memory_api 2025-11-10 20:44:42 -06:00
J. Nick Koston
066674df19 Merge branch 'fix_wifi_state_machine_hidden_phase_skipped' into integration 2025-11-10 20:44:34 -06:00
J. Nick Koston
48a33611a1 [wifi] Fix infinite retry loop when no hidden networks and captive portal active 2025-11-10 20:43:32 -06:00
J. Nick Koston
caf6045485 Merge branch 'integration' into memory_api 2025-11-10 20:24:34 -06:00
J. Nick Koston
bd7d103813 Merge branch 'wifi_int8_prio' into integration 2025-11-10 20:24:26 -06:00
J. Nick Koston
6631e2ffb2 tweaks 2025-11-10 20:22:24 -06:00
J. Nick Koston
b80b0eb864 save more 2025-11-10 20:17:03 -06:00
Clyde Stubbs
7a700ca077 [core] Update clamp functions to allow mixed but comparable types (#11828) 2025-11-11 02:15:44 +00:00
J. Nick Koston
130a8b853d missed one 2025-11-10 20:14:40 -06:00
J. Nick Koston
0f02c75f66 [wifi] Change priority type from float to int8_t 2025-11-10 20:05:02 -06:00
J. Nick Koston
e4c3ae4b16 Merge branch 'integration' into memory_api 2025-11-10 19:20:45 -06:00
J. Nick Koston
677f65c38c Merge branch 'controller_registry_event_has_prog_lifetime' into integration 2025-11-10 19:20:31 -06:00
J. Nick Koston
80e4eefc4c Merge branch 'integration' into memory_api 2025-11-10 19:18:05 -06:00
J. Nick Koston
0be86aa946 Merge remote-tracking branch 'upstream/dev' into integration 2025-11-10 19:17:55 -06:00
Clyde Stubbs
1539b43074 [wifi][ethernet] Don't block setup until connected (#9823)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-11-10 19:17:16 -06:00
J. Nick Koston
5dc914268c Merge branch 'integration' into memory_api 2025-11-10 19:13:29 -06:00
J. Nick Koston
126a9ef303 Merge branch 'wifi_min_ver' into integration 2025-11-10 19:13:22 -06:00
Jesse Hills
463a00b1ac [CI] Don't request codeowners review in forks (#11827) 2025-11-10 19:10:29 -06:00
J. Nick Koston
f9ef8af18e Merge remote-tracking branch 'upstream/dev' into wifi_min_ver
# Conflicts:
#	esphome/components/wifi/wifi_component.h
2025-11-10 19:09:10 -06:00
J. Nick Koston
82692d7053 [tests] Migrate components to shared packages and fix ID ambiguity (#11819) 2025-11-10 19:00:54 -06:00
J. Nick Koston
1cccfdd2b9 [wifi] Fix mesh network failover and improve retry logic reliability (#11805) 2025-11-11 13:40:23 +13:00
Beormund
855aa32f54 Add support for RX8130 RTC Chip (#10511)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-11-10 19:32:59 -05:00
J. Nick Koston
e263b3194e Merge branch 'integration' into memory_api 2025-11-10 18:30:28 -06:00
J. Nick Koston
bf18751136 Merge branch 'ble_client_automation_no_heap' into integration 2025-11-10 18:30:18 -06:00
J. Nick Koston
a7674cd0e8 [ble_client] Write static BLE data directly from flash without allocation 2025-11-10 18:28:51 -06:00
Stuart Parmenter
0f8332fe3c [lvgl] Automatically register widget types (#11394)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com>
2025-11-11 11:04:03 +11:00
Thomas Rupprecht
40e2976ba2 [ai] simplify namespace syntax (#11824) 2025-11-10 17:33:34 -06:00
J. Nick Koston
4f411dc4f2 help 2025-11-10 16:47:42 -06:00
J. Nick Koston
4964fdc1b0 help 2025-11-10 16:45:54 -06:00
J. Nick Koston
f275a31c3a preen 2025-11-10 14:37:54 -06:00
J. Nick Koston
d7cef22ddb fix defaults 2025-11-10 14:33:11 -06:00
J. Nick Koston
23b8139d24 fix defaults 2025-11-10 14:31:26 -06:00
J. Nick Koston
3fd5e87379 fix namespace conflicts 2025-11-10 13:51:16 -06:00
dependabot[bot]
e46300828e Bump pytest from 8.4.2 to 9.0.0 (#11817)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-10 13:45:56 -06:00
J. Nick Koston
8d284ea90c fixes 2025-11-10 13:30:36 -06:00
dependabot[bot]
8c5b964722 Bump pyupgrade from 3.21.0 to 3.21.1 (#11816)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-10 13:28:25 -06:00
dependabot[bot]
43eafbccb3 Bump pytest-asyncio from 1.2.0 to 1.3.0 (#11815)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-10 13:28:14 -06:00
J. Nick Koston
5a67d2b20b fixes 2025-11-10 13:00:52 -06:00
J. Nick Koston
f84cdad93c [wifi] Add min_auth_mode configuration option 2025-11-10 12:50:17 -06:00
J. Nick Koston
f32b69b8f1 [tests] Add unit test coverage for web_port property (#11811) 2025-11-10 10:00:42 -06:00
On Freund
2a16653642 HLK-FM22X Face Recognition module component (#8059)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-11-10 07:44:27 -06:00
J. Nick Koston
16075e37f5 Merge branch 'integration' into memory_api 2025-11-10 00:00:19 -06:00
J. Nick Koston
848ff22871 Merge branch 'parition_callbacks' into integration 2025-11-10 00:00:04 -06:00
J. Nick Koston
057aede0cd Merge branch 'integration' into memory_api 2025-11-09 23:59:53 -06:00
J. Nick Koston
4258e47c68 Merge upstream/dev into integration
Resolved conflicts:
- event.cpp: Removed duplicate set_event_types methods
- pronto_protocol.cpp: Accepted upstream version of dump() method with pointer-based chunking
2025-11-09 23:59:32 -06:00
J. Nick Koston
f19bbbd1c5 Merge remote-tracking branch 'origin/parition_callbacks' into parition_callbacks 2025-11-09 23:20:01 -06:00
J. Nick Koston
0f136a888c Merge branch 'dev' into parition_callbacks and address Copilot review
- Resolved conflicts in sensor.cpp and text_sensor.cpp to keep the
  PartitionedCallbackManager approach from this branch
- Fixed platform-dependent pointer size documentation (4 bytes on 32-bit, 8 bytes on 64-bit)
- Fixed potential integer underflow in add_first comparison
- Added documentation explaining asymmetric API design rationale
2025-11-09 23:19:02 -06:00
J. Nick Koston
6feaa8dd13 preserve order 2025-11-09 23:10:06 -06:00
J. Nick Koston
4c3931363f Merge remote-tracking branch 'origin/dev' into parition_callbacks 2025-11-09 22:57:10 -06:00
tomaszduda23
b47e89a7d5 [nrf52,watchdog] do not disable watchog if it is not nesesery (#11686) 2025-11-10 15:21:38 +13:00
J. Nick Koston
c17a31a8f8 Ensure event paths are enabled in api compile tests (#11776) 2025-11-10 14:28:49 +13:00
Paul Schulz
fbbdad75f6 [sx126x] Change BUSY, RST, DIO1 pins to general GPIO (from internal) (#11782) 2025-11-10 14:26:02 +13:00
J. Nick Koston
7abb6d4998 [core] Implement Global Controller Registry to reduce RAM usage (#11772) 2025-11-09 17:34:08 -06:00
Ludovic BOUÉ
1dabe83d04 [nrf52] api (#11751) 2025-11-10 11:48:33 +13:00
J. Nick Koston
0d735dc259 [remote_base] Optimize abbwelcome action memory usage - store static data in flash (#11798)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-09 22:46:01 +00:00
J. Nick Koston
7b86e1feb0 [core] Remove deprecated EntityBase::hash_base() method (#11783) 2025-11-10 11:39:27 +13:00
J. Nick Koston
d516627957 [uart] Store static data in flash and use function pointers for lambdas (#11784)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-09 22:37:14 +00:00
J. Nick Koston
fb1c67490a [udp] Optimize udp.write action memory usage - store static data in flash (#11794)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-09 22:33:56 +00:00
J. Nick Koston
8b9600b930 [speaker] Optimize speaker.play action memory usage - store static data in flash (#11796)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-09 22:33:29 +00:00
J. Nick Koston
cbb98c4050 [bl0940] Fix calibration number preference hash for multi-device configs (#11769) 2025-11-10 11:27:56 +13:00
J. Nick Koston
e7ff56f1cd [remote_base] Eliminate substr() allocations in Pronto dump logging (#11726) 2025-11-10 11:27:09 +13:00
J. Nick Koston
7705a5de06 [sx127x] Optimize send_packet action memory usage - store static data in flash (#11792)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-09 22:25:40 +00:00
J. Nick Koston
77ab096b59 [remote_base] Optimize raw transmit action memory usage - use function pointers (#11800) 2025-11-10 11:25:16 +13:00
J. Nick Koston
26a3ec41d6 [sx126x] Optimize send_packet action memory usage - store static data in flash (#11790)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-09 22:23:33 +00:00
J. Nick Koston
3bcbfe8d97 [canbus] Optimize canbus.send memory usage - store static data in flash (#11788)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-09 22:22:15 +00:00
J. Nick Koston
870b2c4f84 [ble_client] Optimize ble_write memory usage - store static data in flash (#11786)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-10 11:21:25 +13:00
J. Nick Koston
5f9c7a70ff Add additional tests for remote_transmitter raw (#11801) 2025-11-10 11:17:14 +13:00
J. Nick Koston
f7179d4255 Add additonal abbwelcome remote_base tests (#11799) 2025-11-10 11:16:53 +13:00
J. Nick Koston
eb0558ca3f Add additional udp lambda tests (#11795) 2025-11-10 11:16:09 +13:00
J. Nick Koston
5585355263 Add additional speaker lambda tests (#11797) 2025-11-10 11:15:50 +13:00
J. Nick Koston
e468ca4881 Add additional sx127x lambda tests (#11793) 2025-11-10 11:11:31 +13:00
J. Nick Koston
4c078dea2c Add additional sx126x lambda tests (#11791) 2025-11-10 11:10:31 +13:00
J. Nick Koston
783dbd1e6b Add additional compile time tests for canbus (#11789) 2025-11-10 11:09:46 +13:00
J. Nick Koston
b49619d9bf Add ble_client lambda compile tests (#11787) 2025-11-10 11:09:25 +13:00
J. Nick Koston
a290b88cd6 Expand uart.write tests (#11785) 2025-11-10 11:09:03 +13:00
J. Nick Koston
ff329a1476 Merge branch 'integration' into memory_api 2025-11-08 23:47:37 -06:00
J. Nick Koston
a2e237e080 Merge branch 'speaker_automation' into integration 2025-11-08 23:47:22 -06:00
J. Nick Koston
bde4937192 Merge branch 'abbwelcome_protocol_automation' into integration 2025-11-08 23:47:18 -06:00
J. Nick Koston
40f0be2d0f Merge remote-tracking branch 'origin/abbwelcome_protocol_automation' into abbwelcome_protocol_automation 2025-11-08 23:45:49 -06:00
J. Nick Koston
ff04a6da4b optimize 2025-11-08 23:45:42 -06:00
J. Nick Koston
cb4d10442f Merge remote-tracking branch 'origin/speaker_automation' into speaker_automation 2025-11-08 23:44:21 -06:00
J. Nick Koston
9abef44ac0 optimize 2025-11-08 23:44:11 -06:00
J. Nick Koston
8cc89ea7ab Merge branch 'integration' into memory_api 2025-11-08 23:43:35 -06:00
J. Nick Koston
c853a5bc2b Merge branch 'udp_actions' into integration 2025-11-08 23:43:21 -06:00
J. Nick Koston
62484d7e1f Merge branch 'sx127x_automation_waste' into integration 2025-11-08 23:43:17 -06:00
J. Nick Koston
1a997cbe2f Merge branch 'sx126x_lam_store' into integration 2025-11-08 23:43:12 -06:00
J. Nick Koston
123bc17a66 Merge branch 'canbus_automations' into integration 2025-11-08 23:43:08 -06:00
J. Nick Koston
7a3cf0209f Merge branch 'ble_client_store_static_data_flash' into integration 2025-11-08 23:43:04 -06:00
J. Nick Koston
5fb62325b1 Merge branch 'uart_write_action_store_flash_state_less' into integration 2025-11-08 23:42:55 -06:00
J. Nick Koston
30c578ac16 Merge branch 'raw_action' into integration 2025-11-08 23:42:51 -06:00
J. Nick Koston
c16cd3bab5 optimize 2025-11-08 23:41:24 -06:00
J. Nick Koston
88bbea7566 Merge remote-tracking branch 'origin/sx127x_automation_waste' into sx127x_automation_waste 2025-11-08 23:39:48 -06:00
J. Nick Koston
bdaeb2cf2e optimize 2025-11-08 23:39:39 -06:00
J. Nick Koston
21d0c8b549 optimize 2025-11-08 23:36:06 -06:00
J. Nick Koston
845fae7716 optimize 2025-11-08 23:30:53 -06:00
J. Nick Koston
a5c9988c5d Merge remote-tracking branch 'origin/ble_client_store_static_data_flash' into ble_client_store_static_data_flash 2025-11-08 23:28:39 -06:00
J. Nick Koston
729304af01 optimize 2025-11-08 23:28:23 -06:00
J. Nick Koston
db8b96f257 tweak 2025-11-08 23:21:57 -06:00
J. Nick Koston
f0062117c4 optimize 2025-11-08 23:20:27 -06:00
J. Nick Koston
0341e4baba Merge remote-tracking branch 'origin/raw_action' into raw_action 2025-11-08 23:15:11 -06:00
J. Nick Koston
59485c1d2b save 4 bytes 2025-11-08 23:14:57 -06:00
J. Nick Koston
2e5dc57ce4 Merge branch 'raw_action_tests' into raw_action 2025-11-08 23:10:28 -06:00
J. Nick Koston
353ea5674d Add additional tests for remote_transmitter raw 2025-11-08 23:09:31 -06:00
J. Nick Koston
5b8827d47a [remote_base] Optimize raw transmit action memory usage - use function pointers 2025-11-08 23:07:43 -06:00
J. Nick Koston
0709c21b8c Merge branch 'integration' into memory_api 2025-11-08 22:57:40 -06:00
J. Nick Koston
c66142e5d6 Merge branch 'canbus_automations' into integration 2025-11-08 22:57:33 -06:00
J. Nick Koston
f10d46252e Merge branch 'abbwelcome_protocol_automation' into integration 2025-11-08 22:57:01 -06:00
J. Nick Koston
0cbfd16e88 Merge branch 'speaker_automation' into integration 2025-11-08 22:56:57 -06:00
J. Nick Koston
f00d3d0cae Merge branch 'udp_actions' into integration 2025-11-08 22:56:51 -06:00
J. Nick Koston
5c7369788b Merge branch 'sx127x_automation_waste' into integration 2025-11-08 22:56:46 -06:00
J. Nick Koston
d728a42416 Merge branch 'sx126x_lam_store' into integration 2025-11-08 22:56:38 -06:00
J. Nick Koston
d1089d26e9 Merge branch 'canbus_lambdas' into integration 2025-11-08 22:56:33 -06:00
J. Nick Koston
efc49d05ad Merge branch 'ble_client_lams' into integration 2025-11-08 22:56:29 -06:00
J. Nick Koston
772340cdc3 Merge branch 'abbwelcome_tests' into abbwelcome_protocol_automation 2025-11-08 22:51:59 -06:00
J. Nick Koston
d29882e4ad Add additonal abbwelcome remote_base tests 2025-11-08 22:51:11 -06:00
J. Nick Koston
a239460724 [remote_base] Optimize abbwelcome action memory usage - store static data in flash 2025-11-08 22:48:50 -06:00
J. Nick Koston
a89ffda69f Merge branch 'speaker_tests' into speaker_automation 2025-11-08 22:41:49 -06:00
J. Nick Koston
99c60bfa42 Add additional speaker lambda tests 2025-11-08 22:41:05 -06:00
J. Nick Koston
ecf7de7743 [speaker] Optimize speaker.play action memory usage - store static data in flash 2025-11-08 22:39:51 -06:00
J. Nick Koston
68f9ce9b47 Merge branch 'udp_tests' into udp_actions 2025-11-08 22:34:50 -06:00
J. Nick Koston
5310512123 Add additional udp lambda tests 2025-11-08 22:33:44 -06:00
J. Nick Koston
2cac99dafa [udp] Optimize udp.write action memory usage - store static data in flash 2025-11-08 22:32:47 -06:00
J. Nick Koston
2c835ffb79 Merge branch 'sx127x_tests' into sx127x_automation_waste 2025-11-08 22:29:27 -06:00
J. Nick Koston
a67a433627 Add additional sx127x lambda tests 2025-11-08 22:28:32 -06:00
J. Nick Koston
ba82d968eb [sx127x] Optimize send_packet action memory usage - store static data in flash 2025-11-08 22:25:41 -06:00
J. Nick Koston
716e641fc4 Merge branch 'sx126x_tests' into sx126x_lam_store 2025-11-08 22:20:37 -06:00
J. Nick Koston
a6feea5415 Add additional sx126x lambda tests 2025-11-08 22:19:47 -06:00
J. Nick Koston
17df008092 [sx126x] Optimize send_packet action memory usage - store static data in flash 2025-11-08 22:16:39 -06:00
J. Nick Koston
89e88f77f2 Merge branch 'canbus_lambdas' into canbus_automations 2025-11-08 22:10:22 -06:00
J. Nick Koston
93a57831f4 Add additional compile time tests for canbus 2025-11-08 22:08:07 -06:00
J. Nick Koston
dad5a88ecf [canbus] Optimize canbus.send memory usage - store static data in flash 2025-11-08 22:03:54 -06:00
J. Nick Koston
a9837c90ba Merge branch 'ble_client_lams' into ble_client_store_static_data_flash 2025-11-08 21:54:56 -06:00
J. Nick Koston
4b143e1f3d Add ble_client lambda compile tests 2025-11-08 21:54:05 -06:00
J. Nick Koston
d9503344e3 Merge branch 'intt egration' into memory_api 2025-11-08 21:45:29 -06:00
J. Nick Koston
c101e22041 Merge branch 'ble_client_store_static_data_flash' into integration 2025-11-08 21:45:24 -06:00
J. Nick Koston
0d4a6fa350 [ble_client] Optimize ble_write memory usage - store static data in flash 2025-11-08 21:41:17 -06:00
J. Nick Koston
0ab8ce2bdc Merge branch 'integration' into memory_api 2025-11-08 21:19:58 -06:00
J. Nick Koston
f4a7f40b8e Merge branch 'uart_write_action_store_flash_state_less' into integration 2025-11-08 21:19:52 -06:00
J. Nick Koston
b380a70aa8 Merge branch 'uart_write_tests' into uart_write_action_store_flash_state_less 2025-11-08 21:16:20 -06:00
J. Nick Koston
c5014321a6 Expand uart.write tests 2025-11-08 21:15:28 -06:00
J. Nick Koston
f6bf6bd8ee [uart] Store static data in flash and use function pointers for lambdas 2025-11-08 21:12:00 -06:00
J. Nick Koston
c8d9232bd3 Merge branch 'integration' into memory_api 2025-11-08 19:20:19 -06:00
J. Nick Koston
6632daba2d Merge branch 'hash_base' into integration 2025-11-08 19:20:07 -06:00
J. Nick Koston
041ff7c113 Merge branch 'controller_registry' into integration 2025-11-08 19:20:03 -06:00
J. Nick Koston
e7e091b48c [core] Remove deprecated EntityBase::hash_base() method 2025-11-08 19:18:40 -06:00
dependabot[bot]
b61027607f Bump aioesphomeapi from 42.6.0 to 42.7.0 (#11771)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-08 15:22:40 -06:00
optimusprimespace
f55c872180 Updated AQI calculation for HM3301 to the new standard (#9442)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-11-08 14:56:51 -06:00
J. Nick Koston
fac05dab35 Merge branch 'api_event_test' into controller_registry_event_has_prog_lifetime 2025-11-08 09:46:44 -06:00
J. Nick Koston
f6100a55bc Merge branch 'controller_registry' into api_event_test 2025-11-08 09:46:43 -06:00
J. Nick Koston
c2abf363b6 Ensure event paths are enabled in api compile tests 2025-11-08 09:45:44 -06:00
J. Nick Koston
62f43d3353 dry 2025-11-08 08:41:46 -06:00
J. Nick Koston
7e96f10a79 dry 2025-11-08 08:39:23 -06:00
J. Nick Koston
9a2fc8aa51 part 2025-11-07 23:44:43 -06:00
J. Nick Koston
6b9cb4289a Merge branch 'controller_registry' into controller_registry_event_has_prog_lifetime 2025-11-07 18:16:57 -06:00
J. Nick Koston
b264c6caac cleanup defines 2025-11-07 18:16:22 -06:00
J. Nick Koston
a6c669ff51 cleanup 2025-11-07 18:03:38 -06:00
J. Nick Koston
c15290e386 wip 2025-11-07 17:53:26 -06:00
J. Nick Koston
1329d1af88 Merge branch 'integration' into memory_api 2025-11-07 17:17:11 -06:00
J. Nick Koston
f4eca3872d Merge branch 'controller_registry' into integration 2025-11-07 17:17:05 -06:00
J. Nick Koston
e3fb074a60 preen 2025-11-07 17:14:50 -06:00
J. Nick Koston
6e7f66d393 missing registry 2025-11-07 16:40:36 -06:00
J. Nick Koston
ac85949f17 cleanups 2025-11-07 16:38:32 -06:00
J. Nick Koston
0962024d99 cleanups 2025-11-07 16:35:24 -06:00
J. Nick Koston
327543303c cleanups 2025-11-07 16:34:37 -06:00
J. Nick Koston
8229e3a471 cleanups 2025-11-07 16:33:01 -06:00
J. Nick Koston
1b6471f4b0 cleanups 2025-11-07 16:30:38 -06:00
J. Nick Koston
c87d07ba70 fixes 2025-11-07 16:15:07 -06:00
J. Nick Koston
fc8dc33023 fixes 2025-11-07 16:13:59 -06:00
J. Nick Koston
c0e4f415f1 Revert "no ifdefs needed on forward decs"
This reverts commit 871c5ddb4e.
2025-11-07 16:10:56 -06:00
J. Nick Koston
871c5ddb4e no ifdefs needed on forward decs 2025-11-07 16:07:54 -06:00
J. Nick Koston
6ef2763cab controller registry 2025-11-07 16:01:45 -06:00
J. Nick Koston
929279dc23 controller registry 2025-11-07 15:55:22 -06:00
J. Nick Koston
6fa0f1e290 controller registry 2025-11-07 15:51:13 -06:00
J. Nick Koston
51eb8ea1d0 controller registry 2025-11-07 15:48:02 -06:00
J. Nick Koston
cbdd663fbf Merge remote-tracking branch 'upstream/dev' into controller_registry 2025-11-07 15:46:57 -06:00
J. Nick Koston
c77bb3b269 [event] Store event types in flash memory (#11767) 2025-11-07 15:46:16 -06:00
J. Nick Koston
f1009a7468 tweak 2025-11-07 15:44:17 -06:00
J. Nick Koston
295fe8da04 controller registry phase1/2 2025-11-07 15:32:46 -06:00
J. Nick Koston
0bf2dff056 Merge branch 'integration' into memory_api 2025-11-07 14:35:11 -06:00
J. Nick Koston
1b218fc155 Merge branch 'event_store_in_flash' into integration 2025-11-07 14:35:04 -06:00
J. Nick Koston
f0bcea7749 tweaks 2025-11-07 14:31:53 -06:00
dependabot[bot]
79d1a558af Bump ruff from 0.14.3 to 0.14.4 (#11768)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-11-07 20:12:15 +00:00
J. Nick Koston
9b3f9ee70d Merge branch 'integration' into memory_api 2025-11-07 14:09:19 -06:00
J. Nick Koston
9158b1e6d6 Merge branch 'event_store_in_flash' into integration 2025-11-07 14:09:10 -06:00
J. Nick Koston
e2d949c287 fixed vector will work here 2025-11-07 13:39:57 -06:00
J. Nick Koston
51a238f3d2 [event] Store event types in flash memory 2025-11-07 11:39:17 -06:00
J. Nick Koston
f4fea1a00f [event] Store event types in flash memory 2025-11-07 11:37:49 -06:00
J. Nick Koston
a823fd322e fixes 2025-11-07 11:35:19 -06:00
J. Nick Koston
499ffd84a7 [event] Store event types in flash memory 2025-11-07 11:31:24 -06:00
J. Nick Koston
fca80d81c8 [event] Store event types in flash memory 2025-11-07 11:30:34 -06:00
J. Nick Koston
dc3c18974e [event] Store event types in flash memory 2025-11-07 11:28:25 -06:00
J. Nick Koston
a5bf55b6ac [ci] Fix component batching for beta/release branches (3-4 → 40 per batch) (#11759) 2025-11-07 20:19:45 +13:00
J. Nick Koston
85d2565f25 [tests] Fix determine_jobs tests failing when target branch is beta (#11758) 2025-11-07 20:18:43 +13:00
J. Nick Koston
4f08f0750a [ai_instructions] Add public API and breaking changes guidelines (#11756) 2025-11-06 22:34:53 -06:00
J. Nick Koston
3c41e080c5 [core] Use ESPDEPRECATED macro for deprecation warnings (#11755) 2025-11-07 03:37:02 +00:00
J. Nick Koston
7c30d57391 [wifi] Refactor AP selection to use index instead of copy (saves 88 bytes) (#11749) 2025-11-06 21:26:53 -06:00
J. Nick Koston
182e106bfa [wifi] Guard AP-related members with USE_WIFI_AP to save RAM (#11753) 2025-11-07 15:44:40 +13:00
J. Nick Koston
d0b399d771 [ci] Reduce release time by removing 468 redundant ESP32-C3 IDF tests (#11737) 2025-11-07 15:44:01 +13:00
philippderdiedas
5d20e3a3b4 Add MCP3221 i2c A-D-Converter (#7764) 2025-11-07 14:25:14 +13:00
Kevin Ahrendt
ba5fa7c10a [psram] Add option to disable ignore not found sdkconfig setting (#11411)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-07 14:22:50 +13:00
J. Nick Koston
5cdb891b58 [socket] Deduplicate IP formatting in LWIP raw TCP implementation (#11747) 2025-11-07 14:21:58 +13:00
J. Nick Koston
cf8b88b335 Merge branch 'integration' into memory_api 2025-11-06 18:10:12 -06:00
J. Nick Koston
43cd4a6b93 Merge guard_ap: Guard AP-related members with USE_WIFI_AP 2025-11-06 18:09:45 -06:00
J. Nick Koston
448e6432ea Merge branch 'wifi_get_rid_of_selected' into integration 2025-11-06 18:08:34 -06:00
J. Nick Koston
f3c9ab7cb4 address final bot comments 2025-11-06 18:06:10 -06:00
J. Nick Koston
d46d6f08bd [wifi] Guard AP-related members with USE_WIFI_AP to save RAM 2025-11-06 18:04:57 -06:00
J. Nick Koston
d6528f906e dry 2025-11-06 17:49:45 -06:00
J. Nick Koston
d96e8a9c4b dry 2025-11-06 17:43:52 -06:00
J. Nick Koston
282f6e04b3 dry 2025-11-06 17:40:53 -06:00
J. Nick Koston
148cbc03db dry 2025-11-06 17:39:23 -06:00
J. Nick Koston
c6da4e4777 dry 2025-11-06 17:38:26 -06:00
J. Nick Koston
81bc2d82d6 dry 2025-11-06 17:30:16 -06:00
J. Nick Koston
0893de4f29 dry 2025-11-06 17:26:51 -06:00
J. Nick Koston
3b9570d916 dry 2025-11-06 17:25:52 -06:00
J. Nick Koston
b4b24c500c dry 2025-11-06 17:24:07 -06:00
J. Nick Koston
0a741007bf dry 2025-11-06 17:21:14 -06:00
J. Nick Koston
bf52b9fe06 dry 2025-11-06 17:20:01 -06:00
J. Nick Koston
6e685f1b2d dry 2025-11-06 17:15:57 -06:00
J. Nick Koston
400a18fddc dry 2025-11-06 17:12:05 -06:00
J. Nick Koston
90feecb7bf dry 2025-11-06 17:06:04 -06:00
J. Nick Koston
2e1fd30ea0 dry 2025-11-06 17:00:42 -06:00
J. Nick Koston
03c5655201 dry 2025-11-06 16:58:40 -06:00
J. Nick Koston
df35036c8d refator 2025-11-06 16:52:30 -06:00
J. Nick Koston
936a6cb71e reduce complexity 2025-11-06 16:31:00 -06:00
J. Nick Koston
df1ffbaf5d reduce complexity 2025-11-06 16:30:12 -06:00
J. Nick Koston
645820304f reduce complexity 2025-11-06 16:28:54 -06:00
J. Nick Koston
f4d2b000da reduce 2025-11-06 16:25:23 -06:00
J. Nick Koston
a0b273c6f3 not hidden if found 2025-11-06 16:23:59 -06:00
J. Nick Koston
cde767d83d improve comment 2025-11-06 16:12:19 -06:00
J. Nick Koston
8a92791887 remove non-logical check 2025-11-06 16:08:14 -06:00
J. Nick Koston
7041c3324b revert yet another bad copilot suggesiton 2025-11-06 16:05:15 -06:00
J. Nick Koston
7d48df9fe1 remove overly defensive suggestions from copilot 2025-11-06 16:04:00 -06:00
J. Nick Koston
541e0cfde8 preen 2025-11-06 16:00:37 -06:00
J. Nick Koston
38cf003bf3 preen 2025-11-06 15:58:05 -06:00
J. Nick Koston
47874ef516 revert copilot suggestion .. we will never have more then 5 anyways 2025-11-06 15:53:48 -06:00
J. Nick Koston
8eb509f8f0 revert copilot suggestion .. we will never have more then 5 anyways 2025-11-06 15:52:11 -06:00
J. Nick Koston
db0b1e0b5c defensive to make bot happy 2025-11-06 15:45:41 -06:00
J. Nick Koston
0eafe5259f defensive to make bot happy 2025-11-06 15:44:48 -06:00
J. Nick Koston
b366bc8dba defensive to make bot happy 2025-11-06 15:40:32 -06:00
J. Nick Koston
b74f415509 defensive to make bot happy 2025-11-06 15:37:58 -06:00
J. Nick Koston
0044c51474 defensive to make bot happy 2025-11-06 15:37:01 -06:00
J. Nick Koston
1fb233e22f fix false positive logging 2025-11-06 15:07:47 -06:00
J. Nick Koston
e4a56c6bc9 not needed 2025-11-06 14:49:52 -06:00
J. Nick Koston
ebda7dace0 not needed 2025-11-06 14:46:11 -06:00
J. Nick Koston
d8b419b60c not needed 2025-11-06 14:42:42 -06:00
J. Nick Koston
190668c25f fix false positive logging 2025-11-06 13:58:39 -06:00
J. Nick Koston
ef680933dc cleanup 2025-11-06 13:55:08 -06:00
J. Nick Koston
4439b45fba cleanup 2025-11-06 13:52:32 -06:00
J. Nick Koston
703b1cf314 cleanup 2025-11-06 13:51:47 -06:00
J. Nick Koston
047773e62f fixes for no fast connect yet 2025-11-06 13:32:51 -06:00
J. Nick Koston
57a88e8211 fixes for no fast connect yet 2025-11-06 13:11:06 -06:00
J. Nick Koston
6d958a6640 fixes for no fast connect yet 2025-11-06 12:58:03 -06:00
J. Nick Koston
37620e61f9 fast connect fixes 2025-11-06 12:36:35 -06:00
J. Nick Koston
d38703c18a [wifi] Refactor AP selection with synchronization helpers 2025-11-06 12:31:14 -06:00
J. Nick Koston
5543acf3ab preen 2025-11-06 12:10:18 -06:00
J. Nick Koston
25ef0043d2 preen 2025-11-06 12:07:42 -06:00
J. Nick Koston
60d6144574 preen 2025-11-06 12:02:18 -06:00
J. Nick Koston
2c110a9e7e preen 2025-11-06 11:57:23 -06:00
J. Nick Koston
083f41c43f preen 2025-11-06 11:54:09 -06:00
J. Nick Koston
27fb72a1d3 preen 2025-11-06 11:47:58 -06:00
J. Nick Koston
03fd2eef2f preen 2025-11-06 11:46:59 -06:00
J. Nick Koston
bfca9cb6c2 preen 2025-11-06 11:37:08 -06:00
J. Nick Koston
7d4b3ff3a6 preen 2025-11-06 11:36:52 -06:00
J. Nick Koston
e7e2df5c6d preen 2025-11-06 11:35:57 -06:00
J. Nick Koston
670d85090c preen 2025-11-06 11:34:15 -06:00
J. Nick Koston
4500006aab preen 2025-11-06 11:32:06 -06:00
J. Nick Koston
34317ab343 preen 2025-11-06 11:21:44 -06:00
J. Nick Koston
13ee597ce0 preen 2025-11-06 11:17:17 -06:00
J. Nick Koston
378e591e70 preen 2025-11-06 11:11:58 -06:00
J. Nick Koston
20f2d409f7 wip 2025-11-06 11:06:57 -06:00
J. Nick Koston
398b5337c2 Merge branch 'integration' into memory_api 2025-11-06 09:58:45 -06:00
J. Nick Koston
a5e1136eda Merge branch 'lwip_raw_tcp_dry_ip_formatting' into integration 2025-11-06 09:58:36 -06:00
J. Nick Koston
9168d5e422 [socket] Deduplicate IP formatting in LWIP raw TCP implementation 2025-11-06 09:58:03 -06:00
J. Nick Koston
d80822573a Merge branch 'integration' into memory_api 2025-11-05 23:47:31 -06:00
J. Nick Koston
1e58c400ea Revert "free"
This reverts commit fbc3413ed9.
2025-11-05 23:47:28 -06:00
J. Nick Koston
e17b69c20d Revert "[core] Deduplicate entity icon and device class logging"
This reverts commit 2ddfabe09e.
2025-11-05 23:46:49 -06:00
J. Nick Koston
fbc3413ed9 free 2025-11-05 23:00:40 -06:00
J. Nick Koston
754eaab3be Merge branch 'integration' into memory_api 2025-11-05 22:51:10 -06:00
J. Nick Koston
ffd5b12324 Merge branch 'de_dupe_logging' into integration 2025-11-05 22:51:04 -06:00
J. Nick Koston
2ddfabe09e [core] Deduplicate entity icon and device class logging 2025-11-05 22:49:13 -06:00
rwrozelle
26607713bb [openthread] add poll period for mtd devices (#11374)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-06 16:57:31 +13:00
J. Nick Koston
f0e52227f2 Merge branch 'integration' into memory_api 2025-11-05 21:28:25 -06:00
J. Nick Koston
6c09b16b38 Revert "[esp32_ble] Store custom GAP device name in flash"
This reverts commit 70d947fab9.
2025-11-05 21:28:17 -06:00
J. Nick Koston
ef19d7bb24 Merge branch 'integration' into memory_api 2025-11-05 21:22:24 -06:00
J. Nick Koston
39b63ae87e Merge branch 'store_custom_ble_gap_name_flash' into integration 2025-11-05 21:22:17 -06:00
J. Nick Koston
70d947fab9 [esp32_ble] Store custom GAP device name in flash 2025-11-05 21:20:27 -06:00
Szewcson
895d76ca03 [gdk101] Fix fw version reporting (#11029)
Signed-off-by: szewcu <szewcson@gmail.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-11-05 22:19:29 -05:00
J. Nick Koston
74187845b7 [select] Convert remaining components to use index-based control() (#11693)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-06 15:55:26 +13:00
J. Nick Koston
822eacfd77 [core] Fix wait_until and for_condition timing regression in automation chains (#11716) 2025-11-06 15:49:24 +13:00
Clyde Stubbs
ab5d8f67ae [core] Add helper functions for clamp_at_... (#10387) 2025-11-06 15:48:02 +13:00
J. Nick Koston
83f30a64ed [api] Store YAML service names in flash instead of heap (#11744) 2025-11-06 15:31:59 +13:00
J. Nick Koston
5eea7bdb44 Update AI instructions with C++ style guidelines from developers docs (#11743) 2025-11-06 14:45:48 +13:00
J. Nick Koston
bdfd88441a [ci] Skip memory impact analysis when more than 40 components changed (#11741) 2025-11-05 19:31:23 -06:00
J. Nick Koston
afe4ef17b7 Merge branch 'integration' into memory_api 2025-11-05 19:18:03 -06:00
J. Nick Koston
dc3f4007df Merge branch 'api_services_flash' into integration 2025-11-05 19:17:55 -06:00
J. Nick Koston
8fded918b7 adjust 2025-11-05 19:16:37 -06:00
J. Nick Koston
784dc358f0 Merge branch 'integration' into memory_api 2025-11-05 19:02:28 -06:00
J. Nick Koston
99f5018dd2 Merge branch 'api_services_flash' into integration 2025-11-05 19:02:17 -06:00
J. Nick Koston
ce4f9db778 adjust 2025-11-05 19:01:36 -06:00
J. Nick Koston
b3fdef3ac4 Merge branch 'integration' into memory_api 2025-11-05 18:59:08 -06:00
J. Nick Koston
d7c0ea22c4 Merge branch 'api_services_flash' into integration 2025-11-05 18:58:56 -06:00
J. Nick Koston
15c167b5ce adjust 2025-11-05 18:55:04 -06:00
J. Nick Koston
ab6cb2dee6 remove extra test 2025-11-05 18:51:38 -06:00
J. Nick Koston
bd0705cdc0 [api] Store YAML service names in flash instead of heap
Reduces memory usage for YAML-defined API services by storing service
names and argument names as pointers to string literals in flash instead
of heap-allocated std::string objects.

Implementation:
- Created UserServiceBase<Ts...> for YAML services (const char* storage)
- Created UserServiceDynamic<Ts...> for custom_api_device (std::string storage)
- Updated CustomAPIDeviceService to inherit from UserServiceDynamic
- UserServiceTrigger uses UserServiceBase (YAML-only)

Memory savings per YAML service:
- 0 args: 32 bytes (57% reduction)
- 2 args: 48 bytes (60% reduction)
- 5 args: 96 bytes (63% reduction)

Custom API device services maintain same memory footprint (no regression).

Typical ESPHome device (2-5 services): 100-240 bytes saved
High-service device (10+ services): 400-800 bytes saved
2025-11-05 18:51:17 -06:00
J. Nick Koston
4810c36141 [api] Store YAML service names in flash instead of heap
Reduces memory usage for YAML-defined API services by storing service
names and argument names as pointers to string literals in flash instead
of heap-allocated std::string objects.

Implementation:
- Created UserServiceBase<Ts...> for YAML services (const char* storage)
- Created UserServiceDynamic<Ts...> for custom_api_device (std::string storage)
- Updated CustomAPIDeviceService to inherit from UserServiceDynamic
- UserServiceTrigger uses UserServiceBase (YAML-only)

Memory savings per YAML service:
- 0 args: 32 bytes (57% reduction)
- 2 args: 48 bytes (60% reduction)
- 5 args: 96 bytes (63% reduction)

Custom API device services maintain same memory footprint (no regression).

Typical ESPHome device (2-5 services): 100-240 bytes saved
High-service device (10+ services): 400-800 bytes saved

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 18:49:58 -06:00
J. Nick Koston
351262aea9 Merge branch 'integration' into memory_api 2025-11-05 18:06:09 -06:00
J. Nick Koston
89bd9d8d91 Merge branch 'ai_instructions_code_base' into integration 2025-11-05 18:06:02 -06:00
J. Nick Koston
aaee3f2899 Merge remote-tracking branch 'upstream/dev' into integration
# Conflicts:
#	esphome/components/mqtt/mqtt_binary_sensor.cpp
#	esphome/components/mqtt/mqtt_component.cpp
#	esphome/components/mqtt/mqtt_cover.cpp
#	esphome/components/mqtt/mqtt_event.cpp
#	esphome/components/mqtt/mqtt_number.cpp
#	esphome/components/mqtt/mqtt_sensor.cpp
#	esphome/components/mqtt/mqtt_text_sensor.cpp
#	esphome/components/mqtt/mqtt_valve.cpp
2025-11-05 18:05:09 -06:00
Clyde Stubbs
20b6e0d5c2 [lvgl] Allow text substitution for NaN (#11712) 2025-11-06 10:37:38 +11:00
J. Nick Koston
f8aee13a3a use actual pattern 2025-11-05 16:44:06 -06:00
J. Nick Koston
c83e5e076b cleanup 2025-11-05 16:41:26 -06:00
J. Nick Koston
e331056500 Update AI instructions with C++ style guidelines from developers documentation 2025-11-05 16:35:01 -06:00
J. Nick Koston
ce5e608863 [ci] Skip memory impact analysis for release and beta branches (#11740) 2025-11-05 14:32:45 -06:00
J. Nick Koston
aa5795c019 [tests] Fix ID collision between bl0940 and nau7802 component tests (#11739) 2025-11-05 13:17:34 -06:00
J. Nick Koston
00c0854323 [core] Deprecate get_icon(), get_device_class(), get_unit_of_measurement() and fix remaining non-MQTT usages (#11732) 2025-11-05 12:50:35 -06:00
J. Nick Koston
6d390d5b88 Merge branch 'integration' into memory_api 2025-11-05 12:36:32 -06:00
J. Nick Koston
8514fbcf71 Revert "Revert "Revert "[api] Release excess buffer capacity after initial sync"""
This reverts commit e8c7f74abd.
2025-11-05 12:36:27 -06:00
J. Nick Koston
be006ecadd [mdns] Eliminate redundant hostname copy to save heap memory (#11734) 2025-11-05 18:31:19 +00:00
J. Nick Koston
b08419fa47 [mqtt] Use StringRef to avoid string copies in discovery (#11731) 2025-11-06 07:30:45 +13:00
J. Nick Koston
0d2cdc5ce5 Merge branch 'integration' into memory_api 2025-11-05 12:25:24 -06:00
J. Nick Koston
e8c7f74abd Revert "Revert "[api] Release excess buffer capacity after initial sync""
This reverts commit 1fce2918fb.
2025-11-05 12:25:16 -06:00
J. Nick Koston
d25ff00af4 Merge branch 'integration' into memory_api 2025-11-05 12:22:24 -06:00
J. Nick Koston
1fce2918fb Revert "[api] Release excess buffer capacity after initial sync"
This reverts commit 90e4d15fd9.
2025-11-05 12:22:13 -06:00
J. Nick Koston
d36ef050a9 [template] Mark all component classes as final (#11733) 2025-11-06 07:15:50 +13:00
J. Nick Koston
6e4b99db73 Merge branch 'integration' into memory_api 2025-11-05 12:15:30 -06:00
J. Nick Koston
c9feb5cf65 Merge branch 'api_shrink_after_initial_sync' into integration 2025-11-05 12:15:24 -06:00
J. Nick Koston
90e4d15fd9 [api] Release excess buffer capacity after initial sync 2025-11-05 12:14:09 -06:00
J. Nick Koston
df53ff7afe [scheduler] Extract helper functions to improve code readability (#11730) 2025-11-06 07:13:12 +13:00
J. Nick Koston
ab128fe84a Merge branch 'integration' into memory_api 2025-11-05 11:45:35 -06:00
J. Nick Koston
6ccea58ee2 merge 2025-11-05 11:45:27 -06:00
J. Nick Koston
7f4f4033ee Merge branch 'deprecate_get_icon_get_device_class_get_unit_of_measurement' into integration 2025-11-05 11:44:58 -06:00
J. Nick Koston
2352114757 [graph] Remove unnecessary .c_str() calls when appending StringRef to std::string
StringRef has an operator+= overload that allows direct appending to std::string.
No need to call .c_str() first - this is even more efficient.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 11:42:52 -06:00
J. Nick Koston
cf209e3694 touch ups 2025-11-05 11:42:13 -06:00
J. Nick Koston
ee229bd8f2 Merge branch 'integration' into memory_api 2025-11-05 11:27:55 -06:00
J. Nick Koston
8c1bebb3f4 Merge remote-tracking branch 'upstream/mqtt_copies' into integration 2025-11-05 11:27:46 -06:00
J. Nick Koston
d056db42fa Merge branch 'mdns_dup_storage' into integration 2025-11-05 11:27:31 -06:00
J. Nick Koston
2accba4e0c Merge branch 'template_final' into integration 2025-11-05 11:27:24 -06:00
J. Nick Koston
ed0d9e60b8 [mdns] Eliminate redundant hostname copy to save heap memory 2025-11-05 11:19:06 -06:00
J. Nick Koston
4c5533b2ea move comments 2025-11-05 11:16:43 -06:00
J. Nick Koston
4c097616ae move comments 2025-11-05 11:10:13 -06:00
J. Nick Koston
5dc8bfcf13 [template] Mark all component classes as final 2025-11-05 11:08:10 -06:00
J. Nick Koston
a78767c714 Merge branch 'integration' into memory_api 2025-11-05 11:01:59 -06:00
J. Nick Koston
af61fe3ac3 Merge branch 'mqtt_copies' into integration 2025-11-05 11:01:47 -06:00
J. Nick Koston
14eadb3ccd Merge branch 'deprecate_get_icon_get_device_class_get_unit_of_measurement' into integration 2025-11-05 11:01:43 -06:00
J. Nick Koston
d663ea56b0 tidy 2025-11-05 11:00:02 -06:00
J. Nick Koston
b7838671ae [ld2420] Eliminate substr() allocation in firmware version parsing (#11724) 2025-11-05 10:57:20 -06:00
J. Nick Koston
2c9fdb33e6 [core] Deprecate get_icon(), get_device_class(), get_unit_of_measurement() and fix remaining non-MQTT usages 2025-11-05 10:52:58 -06:00
J. Nick Koston
5372eca46e [mqtt] Use StringRef to avoid string copies in discovery 2025-11-05 10:43:22 -06:00
J. Nick Koston
e648c0315e Merge branch 'integration' into memory_api 2025-11-05 09:18:11 -06:00
J. Nick Koston
c29dbcca5f Merge remote-tracking branch 'upstream/dev' into integration 2025-11-05 09:18:04 -06:00
J. Nick Koston
479f8dd85c [rtttl] Reduce flash usage by eliminating substr() allocations (#11722) 2025-11-05 09:17:28 -06:00
J. Nick Koston
6e2dbbf636 [voice_assistant] Eliminate substr() allocations in text truncation (#11725) 2025-11-05 09:15:05 -06:00
J. Nick Koston
6b522dfee6 [wifi_info] Reduce heap usage by up to 1.7KB in scan_results sensor (#11723) 2025-11-05 09:14:21 -06:00
J. Nick Koston
3a947a205a Merge branch 'integration' into memory_api 2025-11-05 09:03:23 -06:00
J. Nick Koston
558bee8a09 Merge branch 'rtttl_substr' into integration 2025-11-05 09:03:17 -06:00
J. Nick Koston
d77f63eff5 add some safety for future refactoring 2025-11-05 09:02:36 -06:00
J. Nick Koston
d7ea53a44f Merge branch 'integration' into memory_api 2025-11-04 22:33:13 -06:00
J. Nick Koston
c70d154276 Merge branch 'remote_base' into integration 2025-11-04 22:33:05 -06:00
J. Nick Koston
358296a57e [remote_base] Eliminate substr() allocations in Pronto dump logging 2025-11-04 22:32:20 -06:00
J. Nick Koston
6ba0d6b54b Merge branch 'integration' into memory_api 2025-11-04 22:21:45 -06:00
J. Nick Koston
e0831abcd3 Merge branch 'voice_assistant_string_truncate' into integration 2025-11-04 22:21:32 -06:00
J. Nick Koston
34208138c1 [voice_assistant] Eliminate substr() allocations in text truncation 2025-11-04 22:20:55 -06:00
J. Nick Koston
bbe9169975 Merge branch 'integration' into memory_api 2025-11-04 22:13:30 -06:00
J. Nick Koston
5855f3ce33 Merge branch 'ld2420_avoid_string_copy' into integration 2025-11-04 22:13:22 -06:00
J. Nick Koston
f420a8f32d [ld2420] Eliminate substr() allocation in firmware version parsing 2025-11-04 22:11:46 -06:00
J. Nick Koston
f262c671a8 Merge branch 'integration' into memory_api 2025-11-04 22:02:34 -06:00
J. Nick Koston
a0755829bf Merge branch 'wifi_info' into integration 2025-11-04 22:02:18 -06:00
J. Nick Koston
009d6a15f6 [wifi_info] Reduce heap usage by up to 1.7KB in scan_results sensor 2025-11-04 21:58:44 -06:00
J. Nick Koston
28eb79b17b Merge branch 'integration' into memory_api 2025-11-04 21:46:10 -06:00
J. Nick Koston
209091e6a4 Merge branch 'rtttl_substr' into integration 2025-11-04 21:46:03 -06:00
J. Nick Koston
bf83b70a18 [rtttl] Reduce flash usage by eliminating substr() allocations 2025-11-04 21:45:00 -06:00
J. Nick Koston
c588d52bec Merge branch 'integration' into memory_api 2025-11-04 21:13:56 -06:00
J. Nick Koston
d70fe126f6 preen 2025-11-04 21:13:46 -06:00
J. Nick Koston
829455ac43 Merge branch 'integration' into memory_api 2025-11-04 21:12:37 -06:00
J. Nick Koston
0e3f2d3302 Merge remote-tracking branch 'upstream/dev' into integration 2025-11-04 21:12:26 -06:00
J. Nick Koston
32975c9d8b [select][lvgl] Fix FixedVector size() returning 0 when using operator[] after init() (#11721) 2025-11-05 01:49:27 +00:00
J. Nick Koston
1446e7174a [core] Reduce action framework argument copies by 83% (#11704)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-05 01:23:24 +00:00
Gnuspice
64f8963566 [const] Move CONF_ENABLED to const.py (#11719) 2025-11-05 12:46:06 +13:00
J. Nick Koston
6f7e54c3f3 [select] Refactor to index-based operations for immediate and future RAM savings (#11623) 2025-11-05 11:33:01 +13:00
J. Nick Koston
c7ae424613 [display] Optimize display writers with function pointers for stateless lambdas (#11629) 2025-11-05 11:14:54 +13:00
Clyde Stubbs
c5e5609e92 [lvgl] Fix case sensitivity in flex layout (#11717) 2025-11-05 09:00:12 +11:00
J. Nick Koston
885508775f [fan] Remove duplicate preset mode storage to save RAM (#11632) 2025-11-05 10:55:37 +13:00
J. Nick Koston
531b27582a [network] Store use_address in RODATA to save RAM (#11707) 2025-11-05 10:52:10 +13:00
J. Nick Koston
aed7505f53 [automations] Reduce memory usage in if/while/repeat actions (32-36 bytes per instance) (#11650)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-11-05 10:48:20 +13:00
Javier Peletier
191a88c2dc [gt911] Fix gt911 touchscreen with reset pin not initializing when loglevel is set to NONE (#11715) 2025-11-04 13:38:59 -05:00
SeByDocKy
968df6cb3f [gp8403] Add gp8413 (15 bits) DAC model (#7726)
Co-authored-by: Djordje Mandic <6750655+DjordjeMandic@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2025-11-04 12:16:11 -05:00
Cameron Steel
71fa88c9d4 [max7219digit] support flip_x when rotate_chip is 90° or 270° (#6109)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-11-04 16:32:23 +00:00
Chaser Huang
84f7cacef9 [sgp30] Fix reading from preexisting stored baseline even with store_baseline:false (#7922)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-11-04 15:41:30 +00:00
760 changed files with 5811 additions and 3474 deletions

View File

@@ -51,7 +51,79 @@ This document provides essential context for AI models interacting with this pro
* **Naming Conventions:**
* **Python:** Follows PEP 8. Use clear, descriptive names following snake_case.
* **C++:** Follows the Google C++ Style Guide.
* **C++:** Follows the Google C++ Style Guide with these specifics (following clang-tidy conventions):
- Function, method, and variable names: `lower_snake_case`
- Class/struct/enum names: `UpperCamelCase`
- Top-level constants (global/namespace scope): `UPPER_SNAKE_CASE`
- Function-local constants: `lower_snake_case`
- Protected/private fields: `lower_snake_case_with_trailing_underscore_`
- Favor descriptive names over abbreviations
* **C++ Field Visibility:**
* **Prefer `protected`:** Use `protected` for most class fields to enable extensibility and testing. Fields should be `lower_snake_case_with_trailing_underscore_`.
* **Use `private` for safety-critical cases:** Use `private` visibility when direct field access could introduce bugs or violate invariants:
1. **Pointer lifetime issues:** When setters validate and store pointers from known lists to prevent dangling references.
```cpp
// Helper to find matching string in vector and return its pointer
inline const char *vector_find(const std::vector<const char *> &vec, const char *value) {
for (const char *item : vec) {
if (strcmp(item, value) == 0)
return item;
}
return nullptr;
}
class ClimateDevice {
public:
void set_custom_fan_modes(std::initializer_list<const char *> modes) {
this->custom_fan_modes_ = modes;
this->active_custom_fan_mode_ = nullptr; // Reset when modes change
}
bool set_custom_fan_mode(const char *mode) {
// Find mode in supported list and store that pointer (not the input pointer)
const char *validated_mode = vector_find(this->custom_fan_modes_, mode);
if (validated_mode != nullptr) {
this->active_custom_fan_mode_ = validated_mode;
return true;
}
return false;
}
private:
std::vector<const char *> custom_fan_modes_; // Pointers to string literals in flash
const char *active_custom_fan_mode_{nullptr}; // Must point to entry in custom_fan_modes_
};
```
2. **Invariant coupling:** When multiple fields must remain synchronized to prevent buffer overflows or data corruption.
```cpp
class Buffer {
public:
void resize(size_t new_size) {
auto new_data = std::make_unique<uint8_t[]>(new_size);
if (this->data_) {
std::memcpy(new_data.get(), this->data_.get(), std::min(this->size_, new_size));
}
this->data_ = std::move(new_data);
this->size_ = new_size; // Must stay in sync with data_
}
private:
std::unique_ptr<uint8_t[]> data_;
size_t size_{0}; // Must match allocated size of data_
};
```
3. **Resource management:** When setters perform cleanup or registration operations that derived classes might skip.
* **Provide `protected` accessor methods:** When derived classes need controlled access to `private` members.
* **C++ Preprocessor Directives:**
* **Avoid `#define` for constants:** Using `#define` for constants is discouraged and should be replaced with `const` variables or enums.
* **Use `#define` only for:**
- Conditional compilation (`#ifdef`, `#ifndef`)
- Compile-time sizes calculated during Python code generation (e.g., configuring `std::array` or `StaticVector` dimensions via `cg.add_define()`)
* **C++ Additional Conventions:**
* **Member access:** Prefix all class member access with `this->` (e.g., `this->value_` not `value_`)
* **Indentation:** Use spaces (two per indentation level), not tabs
* **Type aliases:** Prefer `using type_t = int;` over `typedef int type_t;`
* **Line length:** Wrap lines at no more than 120 characters
* **Component Structure:**
* **Standard Files:**
@@ -100,8 +172,7 @@ This document provides essential context for AI models interacting with this pro
* **C++ Class Pattern:**
```cpp
namespace esphome {
namespace my_component {
namespace esphome::my_component {
class MyComponent : public Component {
public:
@@ -117,8 +188,7 @@ This document provides essential context for AI models interacting with this pro
int param_{0};
};
} // namespace my_component
} // namespace esphome
} // namespace esphome::my_component
```
* **Common Component Examples:**
@@ -368,3 +438,45 @@ This document provides essential context for AI models interacting with this pro
* **Python:** When adding a new Python dependency, add it to the appropriate `requirements*.txt` file and `pyproject.toml`.
* **C++ / PlatformIO:** When adding a new C++ dependency, add it to `platformio.ini` and use `cg.add_library`.
* **Build Flags:** Use `cg.add_build_flag(...)` to add compiler flags.
## 8. Public API and Breaking Changes
* **Public C++ API:**
* **Components**: Only documented features at [esphome.io](https://esphome.io) are public API. Undocumented `public` members are internal.
* **Core/Base Classes** (`esphome/core/`, `Component`, `Sensor`, etc.): All `public` members are public API.
* **Components with Global Accessors** (`global_api_server`, etc.): All `public` members are public API (except config setters).
* **Public Python API:**
* All documented configuration options at [esphome.io](https://esphome.io) are public API.
* Python code in `esphome/core/` actively used by existing core components is considered stable API.
* Other Python code is internal unless explicitly documented for external component use.
* **Breaking Changes Policy:**
* Aim for **6-month deprecation window** when possible
* Clean breaks allowed for: signature changes, deep refactorings, resource constraints
* Must document migration path in PR description (generates release notes)
* Blog post required for core/base class changes or significant architectural changes
* Full details: https://developers.esphome.io/contributing/code/#public-api-and-breaking-changes
* **Breaking Change Checklist:**
- [ ] Clear justification (RAM/flash savings, architectural improvement)
- [ ] Explored non-breaking alternatives
- [ ] Added deprecation warnings if possible (use `ESPDEPRECATED` macro for C++)
- [ ] Documented migration path in PR description with before/after examples
- [ ] Updated all internal usage and esphome-docs
- [ ] Tested backward compatibility during deprecation period
* **Deprecation Pattern (C++):**
```cpp
// Remove before 2026.6.0
ESPDEPRECATED("Use new_method() instead. Removed in 2026.6.0", "2025.12.0")
void old_method() { this->new_method(); }
```
* **Deprecation Pattern (Python):**
```python
# Remove before 2026.6.0
if CONF_OLD_KEY in config:
_LOGGER.warning(f"'{CONF_OLD_KEY}' deprecated, use '{CONF_NEW_KEY}'. Removed in 2026.6.0")
config[CONF_NEW_KEY] = config.pop(CONF_OLD_KEY) # Auto-migrate
```

View File

@@ -21,7 +21,7 @@ permissions:
jobs:
request-codeowner-reviews:
name: Run
if: ${{ !github.event.pull_request.draft }}
if: ${{ github.repository == 'esphome/esphome' && !github.event.pull_request.draft }}
runs-on: ubuntu-latest
steps:
- name: Request reviews from component codeowners

View File

@@ -11,7 +11,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.14.3
rev: v0.14.4
hooks:
# Run the linter.
- id: ruff

View File

@@ -181,7 +181,7 @@ esphome/components/gdk101/* @Szewcson
esphome/components/gl_r01_i2c/* @pkejval
esphome/components/globals/* @esphome/core
esphome/components/gp2y1010au0f/* @zry98
esphome/components/gp8403/* @jesserockz
esphome/components/gp8403/* @jesserockz @sebydocky
esphome/components/gpio/* @esphome/core
esphome/components/gpio/one_wire/* @ssieb
esphome/components/gps/* @coogle @ximex
@@ -206,6 +206,7 @@ esphome/components/hdc2010/* @optimusprimespace @ssieb
esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/hlk_fm22x/* @OnFreund
esphome/components/hm3301/* @freekode
esphome/components/hmac_md5/* @dwmw2
esphome/components/homeassistant/* @esphome/core @OttoWinter
@@ -290,6 +291,7 @@ esphome/components/mcp23x17_base/* @jesserockz
esphome/components/mcp23xxx_base/* @jesserockz
esphome/components/mcp2515/* @danielschramm @mvturnho
esphome/components/mcp3204/* @rsumner
esphome/components/mcp3221/* @philippderdiedas
esphome/components/mcp4461/* @p1ngb4ck
esphome/components/mcp4728/* @berfenger
esphome/components/mcp47a1/* @jesserockz
@@ -394,6 +396,7 @@ esphome/components/rpi_dpi_rgb/* @clydebarrow
esphome/components/rtl87xx/* @kuba2k2
esphome/components/rtttl/* @glmnet
esphome/components/runtime_stats/* @bdraco
esphome/components/rx8130/* @beormund
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
esphome/components/scd4x/* @martgras @sjtrny
esphome/components/script/* @esphome/core

View File

@@ -1,6 +1,8 @@
#include <utility>
#include "alarm_control_panel.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include <utility>
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
@@ -34,6 +36,9 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
this->current_state_ = state;
this->state_callback_.call();
#if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_alarm_control_panel_update(this);
#endif
if (state == ACP_STATE_TRIGGERED) {
this->triggered_callback_.call();
} else if (state == ACP_STATE_ARMING) {

View File

@@ -227,6 +227,7 @@ CONFIG_SCHEMA = cv.All(
esp32=8, # More RAM, can buffer more
rp2040=5, # Limited RAM
bk72xx=8, # Moderate RAM
nrf52=8, # Moderate RAM
rtl87xx=8, # Moderate RAM
host=16, # Abundant resources
ln882x=8, # Moderate RAM
@@ -244,6 +245,9 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
# Track controller registration for StaticVector sizing
CORE.register_controller()
cg.add(var.set_port(config[CONF_PORT]))
if config[CONF_PASSWORD]:
cg.add_define("USE_API_PASSWORD")

View File

@@ -2147,7 +2147,7 @@ message ListEntitiesEventResponse {
EntityCategory entity_category = 7;
string device_class = 8;
repeated string event_types = 9;
repeated string event_types = 9 [(container_pointer_no_template) = "FixedVector<const char *>"];
uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"];
}
message EventResponse {

View File

@@ -1294,11 +1294,11 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
#endif
#ifdef USE_EVENT
void APIConnection::send_event(event::Event *event, const std::string &event_type) {
void APIConnection::send_event(event::Event *event, const char *event_type) {
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
EventResponse::ESTIMATED_SIZE);
}
uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single) {
EventResponse resp;
resp.set_event_type(StringRef(event_type));
@@ -1310,8 +1310,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
auto *event = static_cast<event::Event *>(entity);
ListEntitiesEventResponse msg;
msg.set_device_class(event->get_device_class_ref());
for (const auto &event_type : event->get_event_types())
msg.event_types.push_back(event_type);
msg.event_types = &event->get_event_types();
return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size,
is_single);
}
@@ -1468,6 +1467,8 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
static constexpr auto MANUFACTURER = StringRef::from_lit("Beken");
#elif defined(USE_LN882X)
static constexpr auto MANUFACTURER = StringRef::from_lit("Lightning");
#elif defined(USE_NRF52)
static constexpr auto MANUFACTURER = StringRef::from_lit("Nordic Semiconductor");
#elif defined(USE_RTL87XX)
static constexpr auto MANUFACTURER = StringRef::from_lit("Realtek");
#elif defined(USE_HOST)
@@ -1649,9 +1650,7 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c
// O(n) but optimized for RAM and not performance.
for (auto &item : items) {
if (item.entity == entity && item.message_type == message_type) {
// Clean up old creator before replacing
item.creator.cleanup(message_type);
// Move assign the new creator
// Replace with new creator
item.creator = std::move(creator);
return;
}
@@ -1821,7 +1820,7 @@ void APIConnection::process_batch_() {
// Handle remaining items more efficiently
if (items_processed < this->deferred_batch_.size()) {
// Remove processed items from the beginning with proper cleanup
// Remove processed items from the beginning
this->deferred_batch_.remove_front(items_processed);
// Reschedule for remaining items
this->schedule_batch_();
@@ -1834,10 +1833,10 @@ void APIConnection::process_batch_() {
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single, uint8_t message_type) const {
#ifdef USE_EVENT
// Special case: EventResponse uses string pointer
// Special case: EventResponse uses const char * pointer
if (message_type == EventResponse::MESSAGE_TYPE) {
auto *e = static_cast<event::Event *>(entity);
return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single);
return APIConnection::try_send_event_response(e, data_.const_char_ptr, conn, remaining_size, is_single);
}
#endif

View File

@@ -177,7 +177,7 @@ class APIConnection final : public APIServerConnection {
#endif
#ifdef USE_EVENT
void send_event(event::Event *event, const std::string &event_type);
void send_event(event::Event *event, const char *event_type);
#endif
#ifdef USE_UPDATE
@@ -450,7 +450,7 @@ class APIConnection final : public APIServerConnection {
bool is_single);
#endif
#ifdef USE_EVENT
static uint16_t try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single);
static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
#endif
@@ -508,10 +508,8 @@ class APIConnection final : public APIServerConnection {
// Constructor for function pointer
MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; }
// Constructor for string state capture
explicit MessageCreator(const std::string &str_value) { data_.string_ptr = new std::string(str_value); }
// No destructor - cleanup must be called explicitly with message_type
// Constructor for const char * (Event types - no allocation needed)
explicit MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; }
// Delete copy operations - MessageCreator should only be moved
MessageCreator(const MessageCreator &other) = delete;
@@ -523,8 +521,6 @@ class APIConnection final : public APIServerConnection {
// Move assignment
MessageCreator &operator=(MessageCreator &&other) noexcept {
if (this != &other) {
// IMPORTANT: Caller must ensure cleanup() was called if this contains a string!
// In our usage, this happens in add_item() deduplication and vector::erase()
data_ = other.data_;
other.data_.function_ptr = nullptr;
}
@@ -535,20 +531,10 @@ class APIConnection final : public APIServerConnection {
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
uint8_t message_type) const;
// Manual cleanup method - must be called before destruction for string types
void cleanup(uint8_t message_type) {
#ifdef USE_EVENT
if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) {
delete data_.string_ptr;
data_.string_ptr = nullptr;
}
#endif
}
private:
union Data {
MessageCreatorPtr function_ptr;
std::string *string_ptr;
const char *const_char_ptr;
} data_; // 4 bytes on 32-bit, 8 bytes on 64-bit - same as before
};
@@ -568,42 +554,24 @@ class APIConnection final : public APIServerConnection {
std::vector<BatchItem> items;
uint32_t batch_start_time{0};
private:
// Helper to cleanup items from the beginning
void cleanup_items_(size_t count) {
for (size_t i = 0; i < count; i++) {
items[i].creator.cleanup(items[i].message_type);
}
}
public:
DeferredBatch() {
// Pre-allocate capacity for typical batch sizes to avoid reallocation
items.reserve(8);
}
~DeferredBatch() {
// Ensure cleanup of any remaining items
clear();
}
// Add item to the batch
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
// Add item to the front of the batch (for high priority messages like ping)
void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
// Clear all items with proper cleanup
// Clear all items
void clear() {
cleanup_items_(items.size());
items.clear();
batch_start_time = 0;
}
// Remove processed items from the front with proper cleanup
void remove_front(size_t count) {
cleanup_items_(count);
items.erase(items.begin(), items.begin() + count);
}
// Remove processed items from the front
void remove_front(size_t count) { items.erase(items.begin(), items.begin() + count); }
bool empty() const { return items.empty(); }
size_t size() const { return items.size(); }

View File

@@ -2877,8 +2877,8 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_uint32(7, static_cast<uint32_t>(this->entity_category));
buffer.encode_string(8, this->device_class_ref_);
for (auto &it : this->event_types) {
buffer.encode_string(9, it, true);
for (const char *it : *this->event_types) {
buffer.encode_string(9, it, strlen(it), true);
}
#ifdef USE_DEVICES
buffer.encode_uint32(10, this->device_id);
@@ -2894,9 +2894,9 @@ void ListEntitiesEventResponse::calculate_size(ProtoSize &size) const {
size.add_bool(1, this->disabled_by_default);
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
size.add_length(1, this->device_class_ref_.size());
if (!this->event_types.empty()) {
for (const auto &it : this->event_types) {
size.add_length_force(1, it.size());
if (!this->event_types->empty()) {
for (const char *it : *this->event_types) {
size.add_length_force(1, strlen(it));
}
}
#ifdef USE_DEVICES

View File

@@ -2788,7 +2788,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage {
#endif
StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
std::vector<std::string> event_types{};
const FixedVector<const char *> *event_types{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP

View File

@@ -2053,7 +2053,7 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const {
dump_field(out, "disabled_by_default", this->disabled_by_default);
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
dump_field(out, "device_class", this->device_class_ref_);
for (const auto &it : this->event_types) {
for (const auto &it : *this->event_types) {
dump_field(out, "event_types", it, 4);
}
#ifdef USE_DEVICES

View File

@@ -5,6 +5,7 @@
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
@@ -34,7 +35,7 @@ APIServer::APIServer() {
}
void APIServer::setup() {
this->setup_controller();
ControllerRegistry::register_controller(this);
#ifdef USE_API_NOISE
uint32_t hash = 88491486UL;
@@ -269,7 +270,7 @@ bool APIServer::check_password(const uint8_t *password_data, size_t password_len
void APIServer::handle_disconnect(APIConnection *conn) {}
// Macro for entities without extra parameters
// Macro for controller update dispatch
#define API_DISPATCH_UPDATE(entity_type, entity_name) \
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
if (obj->is_internal()) \
@@ -278,15 +279,6 @@ void APIServer::handle_disconnect(APIConnection *conn) {}
c->send_##entity_name##_state(obj); \
}
// Macro for entities with extra parameters (but parameters not used in send)
#define API_DISPATCH_UPDATE_IGNORE_PARAMS(entity_type, entity_name, ...) \
void APIServer::on_##entity_name##_update(entity_type *obj, __VA_ARGS__) { /* NOLINT(bugprone-macro-parentheses) */ \
if (obj->is_internal()) \
return; \
for (auto &c : this->clients_) \
c->send_##entity_name##_state(obj); \
}
#ifdef USE_BINARY_SENSOR
API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor)
#endif
@@ -304,15 +296,15 @@ API_DISPATCH_UPDATE(light::LightState, light)
#endif
#ifdef USE_SENSOR
API_DISPATCH_UPDATE_IGNORE_PARAMS(sensor::Sensor, sensor, float state)
API_DISPATCH_UPDATE(sensor::Sensor, sensor)
#endif
#ifdef USE_SWITCH
API_DISPATCH_UPDATE_IGNORE_PARAMS(switch_::Switch, switch, bool state)
API_DISPATCH_UPDATE(switch_::Switch, switch)
#endif
#ifdef USE_TEXT_SENSOR
API_DISPATCH_UPDATE_IGNORE_PARAMS(text_sensor::TextSensor, text_sensor, const std::string &state)
API_DISPATCH_UPDATE(text_sensor::TextSensor, text_sensor)
#endif
#ifdef USE_CLIMATE
@@ -320,7 +312,7 @@ API_DISPATCH_UPDATE(climate::Climate, climate)
#endif
#ifdef USE_NUMBER
API_DISPATCH_UPDATE_IGNORE_PARAMS(number::Number, number, float state)
API_DISPATCH_UPDATE(number::Number, number)
#endif
#ifdef USE_DATETIME_DATE
@@ -336,11 +328,11 @@ API_DISPATCH_UPDATE(datetime::DateTimeEntity, datetime)
#endif
#ifdef USE_TEXT
API_DISPATCH_UPDATE_IGNORE_PARAMS(text::Text, text, const std::string &state)
API_DISPATCH_UPDATE(text::Text, text)
#endif
#ifdef USE_SELECT
API_DISPATCH_UPDATE_IGNORE_PARAMS(select::Select, select, const std::string &state, size_t index)
API_DISPATCH_UPDATE(select::Select, select)
#endif
#ifdef USE_LOCK
@@ -356,12 +348,13 @@ API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player)
#endif
#ifdef USE_EVENT
// Event is a special case - it's the only entity that passes extra parameters to the send method
void APIServer::on_event(event::Event *obj, const std::string &event_type) {
// Event is a special case - unlike other entities with simple state fields,
// events store their state in a member accessed via obj->get_last_event_type()
void APIServer::on_event(event::Event *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_event(obj, event_type);
c->send_event(obj, obj->get_last_event_type());
}
#endif

View File

@@ -72,19 +72,19 @@ class APIServer : public Component, public Controller {
void on_light_update(light::LightState *obj) override;
#endif
#ifdef USE_SENSOR
void on_sensor_update(sensor::Sensor *obj, float state) override;
void on_sensor_update(sensor::Sensor *obj) override;
#endif
#ifdef USE_SWITCH
void on_switch_update(switch_::Switch *obj, bool state) override;
void on_switch_update(switch_::Switch *obj) override;
#endif
#ifdef USE_TEXT_SENSOR
void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) override;
void on_text_sensor_update(text_sensor::TextSensor *obj) override;
#endif
#ifdef USE_CLIMATE
void on_climate_update(climate::Climate *obj) override;
#endif
#ifdef USE_NUMBER
void on_number_update(number::Number *obj, float state) override;
void on_number_update(number::Number *obj) override;
#endif
#ifdef USE_DATETIME_DATE
void on_date_update(datetime::DateEntity *obj) override;
@@ -96,10 +96,10 @@ class APIServer : public Component, public Controller {
void on_datetime_update(datetime::DateTimeEntity *obj) override;
#endif
#ifdef USE_TEXT
void on_text_update(text::Text *obj, const std::string &state) override;
void on_text_update(text::Text *obj) override;
#endif
#ifdef USE_SELECT
void on_select_update(select::Select *obj, const std::string &state, size_t index) override;
void on_select_update(select::Select *obj) override;
#endif
#ifdef USE_LOCK
void on_lock_update(lock::Lock *obj) override;
@@ -141,7 +141,7 @@ class APIServer : public Component, public Controller {
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
#endif
#ifdef USE_EVENT
void on_event(event::Event *obj, const std::string &event_type) override;
void on_event(event::Event *obj) override;
#endif
#ifdef USE_UPDATE
void on_update(update::UpdateEntity *obj) override;

View File

@@ -9,11 +9,11 @@
namespace esphome::api {
#ifdef USE_API_SERVICES
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceDynamic<Ts...> {
public:
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
void (T::*callback)(Ts...))
: UserServiceBase<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
: UserServiceDynamic<Ts...>(name, arg_names), obj_(obj), callback_(callback) {}
protected:
void execute(Ts... x) override { (this->obj_->*this->callback_)(x...); } // NOLINT

View File

@@ -23,11 +23,13 @@ template<typename T> T get_execute_arg_value(const ExecuteServiceArgument &arg);
template<typename T> enums::ServiceArgType to_service_arg_type();
// Base class for YAML-defined services (most common case)
// Stores only pointers to string literals in flash - no heap allocation
template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
public:
UserServiceBase(std::string name, const std::array<std::string, sizeof...(Ts)> &arg_names)
: name_(std::move(name)), arg_names_(arg_names) {
this->key_ = fnv1_hash(this->name_);
UserServiceBase(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
: name_(name), arg_names_(arg_names) {
this->key_ = fnv1_hash(name);
}
ListEntitiesServicesResponse encode_list_service_response() override {
@@ -47,7 +49,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
bool execute_service(const ExecuteServiceRequest &req) override {
if (req.key != this->key_)
return false;
if (req.args.size() != this->arg_names_.size())
if (req.args.size() != sizeof...(Ts))
return false;
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
return true;
@@ -59,14 +61,60 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
this->execute((get_execute_arg_value<Ts>(args[S]))...);
}
std::string name_;
// Pointers to string literals in flash - no heap allocation
const char *name_;
std::array<const char *, sizeof...(Ts)> arg_names_;
uint32_t key_{0};
};
// Separate class for custom_api_device services (rare case)
// Stores copies of runtime-generated names
template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor {
public:
UserServiceDynamic(std::string name, const std::array<std::string, sizeof...(Ts)> &arg_names)
: name_(std::move(name)), arg_names_(arg_names) {
this->key_ = fnv1_hash(this->name_.c_str());
}
ListEntitiesServicesResponse encode_list_service_response() override {
ListEntitiesServicesResponse msg;
msg.set_name(StringRef(this->name_));
msg.key = this->key_;
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
msg.args.init(sizeof...(Ts));
for (size_t i = 0; i < sizeof...(Ts); i++) {
auto &arg = msg.args.emplace_back();
arg.type = arg_types[i];
arg.set_name(StringRef(this->arg_names_[i]));
}
return msg;
}
bool execute_service(const ExecuteServiceRequest &req) override {
if (req.key != this->key_)
return false;
if (req.args.size() != sizeof...(Ts))
return false;
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
return true;
}
protected:
virtual void execute(Ts... x) = 0;
template<typename ArgsContainer, int... S> void execute_(const ArgsContainer &args, seq<S...> type) {
this->execute((get_execute_arg_value<Ts>(args[S]))...);
}
// Heap-allocated strings for runtime-generated names
std::string name_;
std::array<std::string, sizeof...(Ts)> arg_names_;
uint32_t key_{0};
};
template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...>, public Trigger<Ts...> {
public:
UserServiceTrigger(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names)
// Constructor for static names (YAML-defined services - used by code generator)
UserServiceTrigger(const char *name, const std::array<const char *, sizeof...(Ts)> &arg_names)
: UserServiceBase<Ts...>(name, arg_names) {}
protected:

View File

@@ -1,4 +1,6 @@
#include "binary_sensor.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -37,6 +39,9 @@ void BinarySensor::send_state_internal(bool new_state) {
// Note that set_state_ de-dups and will only trigger callbacks if the state has actually changed
if (this->set_state_(new_state)) {
ESP_LOGD(TAG, "'%s': New state is %s", this->get_name().c_str(), ONOFF(new_state));
#if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_binary_sensor_update(this);
#endif
}
}

View File

@@ -9,7 +9,7 @@ static const char *const TAG = "bl0940.number";
void CalibrationNumber::setup() {
float value = 0.0f;
if (this->restore_value_) {
this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash());
if (!this->pref_.load(&value)) {
value = 0.0f;
}

View File

@@ -15,6 +15,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_VALUE,
)
from esphome.core import ID
AUTO_LOAD = ["esp32_ble_client"]
CODEOWNERS = ["@buxtronix", "@clydebarrow"]
@@ -198,7 +199,12 @@ async def ble_write_to_code(config, action_id, template_arg, args):
templ = await cg.templatable(value, args, cg.std_vector.template(cg.uint8))
cg.add(var.set_value_template(templ))
else:
cg.add(var.set_value_simple(value))
# Generate static array in flash to avoid RAM copy
if isinstance(value, bytes):
value = list(value)
arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8)
arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*value))
cg.add(var.set_value_simple(arr, len(value)))
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(

View File

@@ -96,11 +96,8 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
BLEClientWriteAction(BLEClient *ble_client) {
ble_client->register_ble_node(this);
ble_client_ = ble_client;
this->construct_simple_value_();
}
~BLEClientWriteAction() { this->destroy_simple_value_(); }
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
@@ -110,17 +107,14 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_value_template(std::vector<uint8_t> (*func)(Ts...)) {
this->destroy_simple_value_();
this->value_.template_func = func;
this->has_simple_value_ = false;
this->value_.func = func;
this->len_ = -1; // Sentinel value indicates template mode
}
void set_value_simple(const std::vector<uint8_t> &value) {
if (!this->has_simple_value_) {
this->construct_simple_value_();
}
this->value_.simple = value;
this->has_simple_value_ = true;
// Store pointer to static data in flash (no RAM copy)
void set_value_simple(const uint8_t *data, size_t len) {
this->value_.data = data;
this->len_ = len; // Length >= 0 indicates static mode
}
void play(const Ts &...x) override {}
@@ -128,9 +122,19 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
void play_complex(const Ts &...x) override {
this->num_running_++;
this->var_ = std::make_tuple(x...);
auto value = this->has_simple_value_ ? this->value_.simple : this->value_.template_func(x...);
bool result;
if (this->len_ >= 0) {
// Static mode: write directly from flash pointer
result = this->write(this->value_.data, this->len_);
} else {
// Template mode: call function and write the vector
std::vector<uint8_t> value = this->value_.func(x...);
result = this->write(value);
}
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
if (!write(value))
if (!result)
this->play_next_(x...);
}
@@ -143,15 +147,15 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
* errors.
*/
// initiate the write. Return true if all went well, will be followed by a WRITE_CHAR event.
bool write(const std::vector<uint8_t> &value) {
bool write(const uint8_t *data, size_t len) {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected");
return false;
}
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str());
esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(),
this->char_handle_, value.size(), const_cast<uint8_t *>(value.data()),
this->write_type_, ESP_GATT_AUTH_REQ_NONE);
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty(data, len).c_str());
esp_err_t err =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len,
const_cast<uint8_t *>(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE);
if (err != ESP_OK) {
esph_log_e(Automation::TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
return false;
@@ -159,6 +163,8 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
return true;
}
bool write(const std::vector<uint8_t> &value) { return this->write(value.data(), value.size()); }
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override {
switch (event) {
@@ -201,21 +207,11 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
}
private:
void construct_simple_value_() { new (&this->value_.simple) std::vector<uint8_t>(); }
void destroy_simple_value_() {
if (this->has_simple_value_) {
this->value_.simple.~vector();
}
}
BLEClient *ble_client_;
bool has_simple_value_ = true;
ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
union Value {
std::vector<uint8_t> simple;
std::vector<uint8_t> (*template_func)(Ts...);
Value() {} // trivial constructor
~Value() {} // trivial destructor - we manage lifetime via discriminator
std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
const uint8_t *data; // Pointer to static data in flash
} value_;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_;

View File

@@ -4,7 +4,7 @@ from esphome import automation
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_DATA, CONF_ID, CONF_TRIGGER_ID
from esphome.core import CORE
from esphome.core import CORE, ID
CODEOWNERS = ["@mvturnho", "@danielschramm"]
IS_PLATFORM_COMPONENT = True
@@ -176,5 +176,8 @@ async def canbus_action_to_code(config, action_id, template_arg, args):
else:
if isinstance(data, bytes):
data = [int(x) for x in data]
cg.add(var.set_data_static(data))
# Generate static array in flash to avoid RAM copy
arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8)
arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data))
cg.add(var.set_data_static(arr, len(data)))
return var

View File

@@ -112,13 +112,16 @@ class Canbus : public Component {
template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public Parented<Canbus> {
public:
void set_data_template(const std::function<std::vector<uint8_t>(Ts...)> func) {
this->data_func_ = func;
this->static_ = false;
void set_data_template(std::vector<uint8_t> (*func)(Ts...)) {
// Stateless lambdas (generated by ESPHome) implicitly convert to function pointers
this->data_.func = func;
this->len_ = -1; // Sentinel value indicates template mode
}
void set_data_static(const std::vector<uint8_t> &data) {
this->data_static_ = data;
this->static_ = true;
// Store pointer to static data in flash (no RAM copy)
void set_data_static(const uint8_t *data, size_t len) {
this->data_.data = data;
this->len_ = len; // Length >= 0 indicates static mode
}
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
@@ -133,21 +136,26 @@ template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public P
auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
auto use_extended_id =
this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_;
if (this->static_) {
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, this->data_static_);
std::vector<uint8_t> data;
if (this->len_ >= 0) {
// Static mode: copy from flash to vector
data.assign(this->data_.data, this->data_.data + this->len_);
} else {
auto val = this->data_func_(x...);
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, val);
// Template mode: call function
data = this->data_.func(x...);
}
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, data);
}
protected:
optional<uint32_t> can_id_{};
optional<bool> use_extended_id_{};
bool remote_transmission_request_{false};
bool static_{false};
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
std::vector<uint8_t> data_static_{};
ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length
union Data {
std::vector<uint8_t> (*func)(Ts...); // Function pointer (stateless lambdas)
const uint8_t *data; // Pointer to static data in flash
} data_;
};
class CanbusTrigger : public Trigger<std::vector<uint8_t>, uint32_t, bool>, public Component {

View File

@@ -1,4 +1,6 @@
#include "climate.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/macros.h"
namespace esphome {
@@ -463,6 +465,9 @@ void Climate::publish_state() {
// Send state to frontend
this->state_callback_.call(*this);
#if defined(USE_CLIMATE) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_climate_update(this);
#endif
// Save state
this->save_state_();
}

View File

@@ -8,6 +8,8 @@ BYTE_ORDER_BIG = "big_endian"
CONF_COLOR_DEPTH = "color_depth"
CONF_DRAW_ROUNDING = "draw_rounding"
CONF_ENABLED = "enabled"
CONF_IGNORE_NOT_FOUND = "ignore_not_found"
CONF_ON_RECEIVE = "on_receive"
CONF_ON_STATE_CHANGE = "on_state_change"
CONF_REQUEST_HEADERS = "request_headers"

View File

@@ -1,5 +1,9 @@
#include "cover.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include <strings.h>
#include "esphome/core/log.h"
namespace esphome {
@@ -169,6 +173,9 @@ void Cover::publish_state(bool save) {
ESP_LOGD(TAG, " Current Operation: %s", cover_operation_to_str(this->current_operation));
this->state_callback_.call();
#if defined(USE_COVER) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_cover_update(this);
#endif
if (save) {
CoverRestoreState restore{};

View File

@@ -1,5 +1,6 @@
#include "date_entity.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#ifdef USE_DATETIME_DATE
#include "esphome/core/log.h"
@@ -32,6 +33,9 @@ void DateEntity::publish_state() {
this->set_has_state(true);
ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
this->state_callback_.call();
#if defined(USE_DATETIME_DATE) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_date_update(this);
#endif
}
DateCall DateEntity::make_call() { return DateCall(this); }

View File

@@ -1,5 +1,6 @@
#include "datetime_entity.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#ifdef USE_DATETIME_DATETIME
#include "esphome/core/log.h"
@@ -48,6 +49,9 @@ void DateTimeEntity::publish_state() {
ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_,
this->month_, this->day_, this->hour_, this->minute_, this->second_);
this->state_callback_.call();
#if defined(USE_DATETIME_DATETIME) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_datetime_update(this);
#endif
}
DateTimeCall DateTimeEntity::make_call() { return DateTimeCall(this); }

View File

@@ -1,5 +1,6 @@
#include "time_entity.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#ifdef USE_DATETIME_TIME
#include "esphome/core/log.h"
@@ -29,6 +30,9 @@ void TimeEntity::publish_state() {
ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_,
this->second_);
this->state_callback_.call();
#if defined(USE_DATETIME_TIME) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_time_update(this);
#endif
}
TimeCall TimeEntity::make_call() { return TimeCall(this); }

View File

@@ -59,6 +59,7 @@ async def to_code(config):
zephyr_add_prj_conf("SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL", True)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
cg.add_define("USE_DEBUG")
FILTER_SOURCE_FILES = filter_source_files_from_platform(

View File

@@ -8,7 +8,7 @@ namespace demo {
class DemoSelect : public select::Select, public Component {
protected:
void control(const std::string &value) override { this->publish_state(value); }
void control(size_t index) override { this->publish_state(index); }
};
} // namespace demo

View File

@@ -176,7 +176,117 @@ class Display;
class DisplayPage;
class DisplayOnPageChangeTrigger;
using display_writer_t = std::function<void(Display &)>;
/** Optimized display writer that uses function pointers for stateless lambdas.
*
* Similar to TemplatableValue but specialized for display writer callbacks.
* Saves ~8 bytes per stateless lambda on 32-bit platforms (16 bytes std::function → ~8 bytes discriminator+pointer).
*
* Supports both:
* - Stateless lambdas (from YAML) → function pointer (4 bytes)
* - Stateful lambdas/std::function (from C++ code) → std::function* (heap allocated)
*
* @tparam T The display type (e.g., Display, Nextion, GPIOLCDDisplay)
*/
template<typename T> class DisplayWriter {
public:
DisplayWriter() : type_(NONE) {}
// For stateless lambdas (convertible to function pointer): use function pointer (4 bytes)
template<typename F>
DisplayWriter(F f) requires std::invocable<F, T &> && std::convertible_to<F, void (*)(T &)>
: type_(STATELESS_LAMBDA) {
this->stateless_f_ = f; // Implicit conversion to function pointer
}
// For stateful lambdas and std::function (not convertible to function pointer): use std::function* (heap allocated)
// This handles backwards compatibility with external components
template<typename F>
DisplayWriter(F f) requires std::invocable<F, T &> &&(!std::convertible_to<F, void (*)(T &)>) : type_(LAMBDA) {
this->f_ = new std::function<void(T &)>(std::move(f));
}
// Copy constructor
DisplayWriter(const DisplayWriter &other) : type_(other.type_) {
if (type_ == LAMBDA) {
this->f_ = new std::function<void(T &)>(*other.f_);
} else if (type_ == STATELESS_LAMBDA) {
this->stateless_f_ = other.stateless_f_;
}
}
// Move constructor
DisplayWriter(DisplayWriter &&other) noexcept : type_(other.type_) {
if (type_ == LAMBDA) {
this->f_ = other.f_;
other.f_ = nullptr;
} else if (type_ == STATELESS_LAMBDA) {
this->stateless_f_ = other.stateless_f_;
}
other.type_ = NONE;
}
// Assignment operators
DisplayWriter &operator=(const DisplayWriter &other) {
if (this != &other) {
this->~DisplayWriter();
new (this) DisplayWriter(other);
}
return *this;
}
DisplayWriter &operator=(DisplayWriter &&other) noexcept {
if (this != &other) {
this->~DisplayWriter();
new (this) DisplayWriter(std::move(other));
}
return *this;
}
~DisplayWriter() {
if (type_ == LAMBDA) {
delete this->f_;
}
// STATELESS_LAMBDA/NONE: no cleanup needed (function pointer or empty)
}
bool has_value() const { return this->type_ != NONE; }
void call(T &display) const {
switch (this->type_) {
case STATELESS_LAMBDA:
this->stateless_f_(display); // Direct function pointer call
break;
case LAMBDA:
(*this->f_)(display); // std::function call
break;
case NONE:
default:
break;
}
}
// Operator() for convenience
void operator()(T &display) const { this->call(display); }
// Operator* for backwards compatibility with (*writer_)(*this) pattern
DisplayWriter &operator*() { return *this; }
const DisplayWriter &operator*() const { return *this; }
protected:
enum : uint8_t {
NONE,
LAMBDA,
STATELESS_LAMBDA,
} type_;
union {
std::function<void(T &)> *f_;
void (*stateless_f_)(T &);
};
};
// Type alias for Display writer - uses optimized DisplayWriter instead of std::function
using display_writer_t = DisplayWriter<Display>;
#define LOG_DISPLAY(prefix, type, obj) \
if ((obj) != nullptr) { \
@@ -678,7 +788,7 @@ class Display : public PollingComponent {
void sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3);
DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
optional<display_writer_t> writer_{};
display_writer_t writer_{};
DisplayPage *page_{nullptr};
DisplayPage *previous_page_{nullptr};
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;

View File

@@ -23,7 +23,7 @@ void DS1307Component::dump_config() {
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
}
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
RealTimeClock::dump_config();
}
float DS1307Component::get_setup_priority() const { return setup_priority::DATA; }

View File

@@ -3,9 +3,9 @@
namespace esphome {
namespace es8388 {
void ADCInputMicSelect::control(const std::string &value) {
this->publish_state(value);
this->parent_->set_adc_input_mic(static_cast<AdcInputMicLine>(this->index_of(value).value()));
void ADCInputMicSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_adc_input_mic(static_cast<AdcInputMicLine>(index));
}
} // namespace es8388

View File

@@ -8,7 +8,7 @@ namespace es8388 {
class ADCInputMicSelect : public select::Select, public Parented<ES8388> {
protected:
void control(const std::string &value) override;
void control(size_t index) override;
};
} // namespace es8388

View File

@@ -3,9 +3,9 @@
namespace esphome {
namespace es8388 {
void DacOutputSelect::control(const std::string &value) {
this->publish_state(value);
this->parent_->set_dac_output(static_cast<DacOutputLine>(this->index_of(value).value()));
void DacOutputSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_dac_output(static_cast<DacOutputLine>(index));
}
} // namespace es8388

View File

@@ -8,7 +8,7 @@ namespace es8388 {
class DacOutputSelect : public select::Select, public Parented<ES8388> {
protected:
void control(const std::string &value) override;
void control(size_t index) override;
};
} // namespace es8388

View File

@@ -383,6 +383,7 @@ async def to_code(config):
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
if CONF_MANUAL_IP in config:
cg.add_define("USE_ETHERNET_MANUAL_IP")
cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP])))
# Add compile-time define for PHY types with specific code

View File

@@ -418,8 +418,6 @@ void EthernetComponent::dump_config() {
float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; }
bool EthernetComponent::can_proceed() { return this->is_connected(); }
network::IPAddresses EthernetComponent::get_ip_addresses() {
network::IPAddresses addresses;
esp_netif_ip_info_t ip;
@@ -552,11 +550,14 @@ void EthernetComponent::start_connect_() {
}
esp_netif_ip_info_t info;
#ifdef USE_ETHERNET_MANUAL_IP
if (this->manual_ip_.has_value()) {
info.ip = this->manual_ip_->static_ip;
info.gw = this->manual_ip_->gateway;
info.netmask = this->manual_ip_->subnet;
} else {
} else
#endif
{
info.ip.addr = 0;
info.gw.addr = 0;
info.netmask.addr = 0;
@@ -577,6 +578,7 @@ void EthernetComponent::start_connect_() {
err = esp_netif_set_ip_info(this->eth_netif_, &info);
ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
#ifdef USE_ETHERNET_MANUAL_IP
if (this->manual_ip_.has_value()) {
LwIPLock lock;
if (this->manual_ip_->dns1.is_set()) {
@@ -589,7 +591,9 @@ void EthernetComponent::start_connect_() {
d = this->manual_ip_->dns2;
dns_setserver(1, &d);
}
} else {
} else
#endif
{
err = esp_netif_dhcpc_start(this->eth_netif_);
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
ESPHL_ERROR_CHECK(err, "DHCPC start error");
@@ -687,7 +691,9 @@ void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode) { this->cl
void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); }
#endif
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
#ifdef USE_ETHERNET_MANUAL_IP
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
#endif
// set_use_address() is guaranteed to be called during component setup by Python code generation,
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.

View File

@@ -58,7 +58,6 @@ class EthernetComponent : public Component {
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
bool can_proceed() override;
void on_powerdown() override { powerdown(); }
bool is_connected();
@@ -83,7 +82,9 @@ class EthernetComponent : public Component {
void add_phy_register(PHYRegister register_value);
#endif
void set_type(EthernetType type);
#ifdef USE_ETHERNET_MANUAL_IP
void set_manual_ip(const ManualIP &manual_ip);
#endif
void set_fixed_mac(const std::array<uint8_t, 6> &mac) { this->fixed_mac_ = mac; }
network::IPAddresses get_ip_addresses();
@@ -138,7 +139,9 @@ class EthernetComponent : public Component {
uint8_t mdc_pin_{23};
uint8_t mdio_pin_{18};
#endif
#ifdef USE_ETHERNET_MANUAL_IP
optional<ManualIP> manual_ip_{};
#endif
uint32_t connect_begin_;
// Group all uint8_t types together (enums and bools)

View File

@@ -1,5 +1,6 @@
#include "event.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -8,11 +9,11 @@ namespace event {
static const char *const TAG = "event";
void Event::trigger(const std::string &event_type) {
// Linear search - faster than std::set for small datasets (1-5 items typical)
const std::string *found = nullptr;
for (const auto &type : this->types_) {
if (type == event_type) {
found = &type;
// Linear search with strcmp - faster than std::set for small datasets (1-5 items typical)
const char *found = nullptr;
for (const char *type : this->types_) {
if (strcmp(type, event_type.c_str()) == 0) {
found = type;
break;
}
}
@@ -20,9 +21,28 @@ void Event::trigger(const std::string &event_type) {
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str());
return;
}
last_event_type = found;
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), last_event_type->c_str());
this->last_event_type_ = found;
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), this->last_event_type_);
this->event_callback_.call(event_type);
#if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_event(this);
#endif
}
void Event::set_event_types(const FixedVector<const char *> &event_types) {
this->types_.init(event_types.size());
for (const char *type : event_types) {
this->types_.push_back(type);
}
this->last_event_type_ = nullptr; // Reset when types change
}
void Event::set_event_types(const std::vector<const char *> &event_types) {
this->types_.init(event_types.size());
for (const char *type : event_types) {
this->types_.push_back(type);
}
this->last_event_type_ = nullptr; // Reset when types change
}
void Event::add_on_event_callback(std::function<void(const std::string &event_type)> &&callback) {

View File

@@ -1,6 +1,8 @@
#pragma once
#include <cstring>
#include <string>
#include <vector>
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
@@ -22,16 +24,39 @@ namespace event {
class Event : public EntityBase, public EntityBase_DeviceClass {
public:
const std::string *last_event_type;
void trigger(const std::string &event_type);
void set_event_types(const std::initializer_list<std::string> &event_types) { this->types_ = event_types; }
const FixedVector<std::string> &get_event_types() const { return this->types_; }
/// Set the event types supported by this event (from initializer list).
void set_event_types(std::initializer_list<const char *> event_types) {
this->types_ = event_types;
this->last_event_type_ = nullptr; // Reset when types change
}
/// Set the event types supported by this event (from FixedVector).
void set_event_types(const FixedVector<const char *> &event_types);
/// Set the event types supported by this event (from vector).
void set_event_types(const std::vector<const char *> &event_types);
// Deleted overloads to catch incorrect std::string usage at compile time with clear error messages
void set_event_types(std::initializer_list<std::string> event_types) = delete;
void set_event_types(const FixedVector<std::string> &event_types) = delete;
void set_event_types(const std::vector<std::string> &event_types) = delete;
/// Return the event types supported by this event.
const FixedVector<const char *> &get_event_types() const { return this->types_; }
/// Return the last triggered event type (pointer to string in types_), or nullptr if no event triggered yet.
const char *get_last_event_type() const { return this->last_event_type_; }
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
protected:
CallbackManager<void(const std::string &event_type)> event_callback_;
FixedVector<std::string> types_;
FixedVector<const char *> types_;
private:
/// Last triggered event type - must point to entry in types_ to ensure valid lifetime.
/// Set by trigger() after validation, reset to nullptr when types_ changes.
const char *last_event_type_{nullptr};
};
} // namespace event

View File

@@ -1,4 +1,6 @@
#include "fan.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -181,6 +183,9 @@ void Fan::publish_state() {
ESP_LOGD(TAG, " Preset Mode: %s", preset);
}
this->state_callback_.call();
#if defined(USE_FAN) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_fan_update(this);
#endif
this->save_state_();
}
@@ -231,7 +236,7 @@ void Fan::save_state_() {
state.direction = this->direction;
const char *preset = this->get_preset_mode();
if (traits.supports_preset_modes() && preset != nullptr) {
if (preset != nullptr) {
const auto &preset_modes = traits.supported_preset_modes();
// Find index of current preset mode (pointer comparison is safe since preset is from traits)
for (size_t i = 0; i < preset_modes.size(); i++) {

View File

@@ -62,7 +62,6 @@ void GDK101Component::dump_config() {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
}
#ifdef USE_SENSOR
LOG_SENSOR(" ", "Firmware Version", this->fw_version_sensor_);
LOG_SENSOR(" ", "Average Radaition Dose per 1 minute", this->rad_1m_sensor_);
LOG_SENSOR(" ", "Average Radaition Dose per 10 minutes", this->rad_10m_sensor_);
LOG_SENSOR(" ", "Status", this->status_sensor_);
@@ -72,6 +71,10 @@ void GDK101Component::dump_config() {
#ifdef USE_BINARY_SENSOR
LOG_BINARY_SENSOR(" ", "Vibration Status", this->vibration_binary_sensor_);
#endif // USE_BINARY_SENSOR
#ifdef USE_TEXT_SENSOR
LOG_TEXT_SENSOR(" ", "Firmware Version", this->fw_version_text_sensor_);
#endif // USE_TEXT_SENSOR
}
float GDK101Component::get_setup_priority() const { return setup_priority::DATA; }
@@ -153,18 +156,18 @@ bool GDK101Component::read_status_(uint8_t *data) {
}
bool GDK101Component::read_fw_version_(uint8_t *data) {
#ifdef USE_SENSOR
if (this->fw_version_sensor_ != nullptr) {
#ifdef USE_TEXT_SENSOR
if (this->fw_version_text_sensor_ != nullptr) {
if (!this->read_bytes(GDK101_REG_READ_FIRMWARE, data, 2)) {
ESP_LOGE(TAG, "Updating GDK101 failed!");
return false;
}
const float fw_version = data[0] + (data[1] / 10.0f);
const std::string fw_version_str = str_sprintf("%d.%d", data[0], data[1]);
this->fw_version_sensor_->publish_state(fw_version);
this->fw_version_text_sensor_->publish_state(fw_version_str);
}
#endif // USE_SENSOR
#endif // USE_TEXT_SENSOR
return true;
}

View File

@@ -8,6 +8,9 @@
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif // USE_BINARY_SENSOR
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif // USE_TEXT_SENSOR
#include "esphome/components/i2c/i2c.h"
namespace esphome {
@@ -25,12 +28,14 @@ class GDK101Component : public PollingComponent, public i2c::I2CDevice {
SUB_SENSOR(rad_1m)
SUB_SENSOR(rad_10m)
SUB_SENSOR(status)
SUB_SENSOR(fw_version)
SUB_SENSOR(measurement_duration)
#endif // USE_SENSOR
#ifdef USE_BINARY_SENSOR
SUB_BINARY_SENSOR(vibration)
#endif // USE_BINARY_SENSOR
#ifdef USE_TEXT_SENSOR
SUB_TEXT_SENSOR(fw_version)
#endif // USE_TEXT_SENSOR
public:
void setup() override;

View File

@@ -40,9 +40,8 @@ CONFIG_SCHEMA = cv.Schema(
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_VERSION): sensor.sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
accuracy_decimals=1,
cv.Optional(CONF_VERSION): cv.invalid(
"The 'version' option has been moved to the `text_sensor` component."
),
cv.Optional(CONF_STATUS): sensor.sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
@@ -71,10 +70,6 @@ async def to_code(config):
sens = await sensor.new_sensor(radiation_dose_per_10m)
cg.add(hub.set_rad_10m_sensor(sens))
if version_config := config.get(CONF_VERSION):
sens = await sensor.new_sensor(version_config)
cg.add(hub.set_fw_version_sensor(sens))
if status_config := config.get(CONF_STATUS):
sens = await sensor.new_sensor(status_config)
cg.add(hub.set_status_sensor(sens))

View File

@@ -0,0 +1,23 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import CONF_VERSION, ENTITY_CATEGORY_DIAGNOSTIC, ICON_CHIP
from . import CONF_GDK101_ID, GDK101Component
DEPENDENCIES = ["gdk101"]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_GDK101_ID): cv.use_id(GDK101Component),
cv.Required(CONF_VERSION): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP
),
}
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_GDK101_ID])
var = await text_sensor.new_text_sensor(config[CONF_VERSION])
cg.add(hub.set_fw_version_text_sensor(var))

View File

@@ -1,19 +1,25 @@
import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_VOLTAGE
from esphome.const import CONF_ID, CONF_MODEL, CONF_VOLTAGE
CODEOWNERS = ["@jesserockz"]
CODEOWNERS = ["@jesserockz", "@sebydocky"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
gp8403_ns = cg.esphome_ns.namespace("gp8403")
GP8403 = gp8403_ns.class_("GP8403", cg.Component, i2c.I2CDevice)
GP8403Component = gp8403_ns.class_("GP8403Component", cg.Component, i2c.I2CDevice)
GP8403Voltage = gp8403_ns.enum("GP8403Voltage")
GP8403Model = gp8403_ns.enum("GP8403Model")
CONF_GP8403_ID = "gp8403_id"
MODELS = {
"GP8403": GP8403Model.GP8403,
"GP8413": GP8403Model.GP8413,
}
VOLTAGES = {
"5V": GP8403Voltage.GP8403_VOLTAGE_5V,
"10V": GP8403Voltage.GP8403_VOLTAGE_10V,
@@ -22,7 +28,8 @@ VOLTAGES = {
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(GP8403),
cv.GenerateID(): cv.declare_id(GP8403Component),
cv.Optional(CONF_MODEL, default="GP8403"): cv.enum(MODELS, upper=True),
cv.Required(CONF_VOLTAGE): cv.enum(VOLTAGES, upper=True),
}
)
@@ -35,5 +42,5 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_model(config[CONF_MODEL]))
cg.add(var.set_voltage(config[CONF_VOLTAGE]))

View File

@@ -8,16 +8,48 @@ namespace gp8403 {
static const char *const TAG = "gp8403";
static const uint8_t RANGE_REGISTER = 0x01;
static const uint8_t OUTPUT_REGISTER = 0x02;
void GP8403::setup() { this->write_register(RANGE_REGISTER, (uint8_t *) (&this->voltage_), 1); }
const LogString *model_to_string(GP8403Model model) {
switch (model) {
case GP8403Model::GP8403:
return LOG_STR("GP8403");
case GP8403Model::GP8413:
return LOG_STR("GP8413");
}
return LOG_STR("Unknown");
};
void GP8403::dump_config() {
void GP8403Component::setup() { this->write_register(RANGE_REGISTER, (uint8_t *) (&this->voltage_), 1); }
void GP8403Component::dump_config() {
ESP_LOGCONFIG(TAG,
"GP8403:\n"
" Voltage: %dV",
this->voltage_ == GP8403_VOLTAGE_5V ? 5 : 10);
" Voltage: %dV\n"
" Model: %s",
this->voltage_ == GP8403_VOLTAGE_5V ? 5 : 10, LOG_STR_ARG(model_to_string(this->model_)));
LOG_I2C_DEVICE(this);
}
void GP8403Component::write_state(float state, uint8_t channel) {
uint16_t val = 0;
switch (this->model_) {
case GP8403Model::GP8403:
val = ((uint16_t) (4095 * state)) << 4;
break;
case GP8403Model::GP8413:
val = ((uint16_t) (32767 * state)) << 1;
break;
default:
ESP_LOGE(TAG, "Unknown model %s", LOG_STR_ARG(model_to_string(this->model_)));
return;
}
ESP_LOGV(TAG, "Calculated DAC value: %" PRIu16, val);
i2c::ErrorCode err = this->write_register(OUTPUT_REGISTER + (2 * channel), (uint8_t *) &val, 2);
if (err != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Error writing to %s, code %d", LOG_STR_ARG(model_to_string(this->model_)), err);
}
}
} // namespace gp8403
} // namespace esphome

View File

@@ -11,15 +11,24 @@ enum GP8403Voltage {
GP8403_VOLTAGE_10V = 0x11,
};
class GP8403 : public Component, public i2c::I2CDevice {
enum GP8403Model {
GP8403,
GP8413,
};
class GP8403Component : public Component, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_model(GP8403Model model) { this->model_ = model; }
void set_voltage(gp8403::GP8403Voltage voltage) { this->voltage_ = voltage; }
void write_state(float state, uint8_t channel);
protected:
GP8403Voltage voltage_;
GP8403Model model_{GP8403Model::GP8403};
};
} // namespace gp8403

View File

@@ -3,7 +3,7 @@ from esphome.components import i2c, output
import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_ID
from .. import CONF_GP8403_ID, GP8403, gp8403_ns
from .. import CONF_GP8403_ID, GP8403Component, gp8403_ns
DEPENDENCIES = ["gp8403"]
@@ -14,7 +14,7 @@ GP8403Output = gp8403_ns.class_(
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(GP8403Output),
cv.GenerateID(CONF_GP8403_ID): cv.use_id(GP8403),
cv.GenerateID(CONF_GP8403_ID): cv.use_id(GP8403Component),
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=1),
}
).extend(cv.COMPONENT_SCHEMA)

View File

@@ -7,8 +7,6 @@ namespace gp8403 {
static const char *const TAG = "gp8403.output";
static const uint8_t OUTPUT_REGISTER = 0x02;
void GP8403Output::dump_config() {
ESP_LOGCONFIG(TAG,
"GP8403 Output:\n"
@@ -16,13 +14,7 @@ void GP8403Output::dump_config() {
this->channel_);
}
void GP8403Output::write_state(float state) {
uint16_t value = ((uint16_t) (state * 4095)) << 4;
i2c::ErrorCode err = this->parent_->write_register(OUTPUT_REGISTER + (2 * this->channel_), (uint8_t *) &value, 2);
if (err != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Error writing to GP8403, code %d", err);
}
}
void GP8403Output::write_state(float state) { this->parent_->write_state(state, this->channel_); }
} // namespace gp8403
} // namespace esphome

View File

@@ -8,13 +8,11 @@
namespace esphome {
namespace gp8403 {
class GP8403Output : public Component, public output::FloatOutput, public Parented<GP8403> {
class GP8403Output : public Component, public output::FloatOutput, public Parented<GP8403Component> {
public:
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA - 1; }
void set_channel(uint8_t channel) { this->channel_ = channel; }
void write_state(float state) override;
protected:

View File

@@ -235,7 +235,7 @@ void GraphLegend::init(Graph *g) {
std::string valstr =
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
if (this->units_) {
valstr += trace->sensor_->get_unit_of_measurement();
valstr += trace->sensor_->get_unit_of_measurement_ref();
}
this->font_value_->measure(valstr.c_str(), &fw, &fos, &fbl, &fh);
if (fw > valw)
@@ -371,7 +371,7 @@ void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_of
std::string valstr =
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
if (legend_->units_) {
valstr += trace->sensor_->get_unit_of_measurement();
valstr += trace->sensor_->get_unit_of_measurement_ref();
}
buff->printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr.c_str());
ESP_LOGV(TAG, " value: %s", valstr.c_str());

View File

@@ -34,8 +34,8 @@ void GT911Touchscreen::setup() {
this->interrupt_pin_->digital_write(false);
}
delay(2);
this->reset_pin_->digital_write(true); // wait 50ms after reset
this->set_timeout(50, [this] { this->setup_internal_(); });
this->reset_pin_->digital_write(true); // wait at least T3+T4 ms as per the datasheet
this->set_timeout(5 + 50 + 1, [this] { this->setup_internal_(); });
return;
}
this->setup_internal_();

View File

@@ -0,0 +1,247 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import (
CONF_DIRECTION,
CONF_ID,
CONF_NAME,
CONF_ON_ENROLLMENT_DONE,
CONF_ON_ENROLLMENT_FAILED,
CONF_TRIGGER_ID,
)
CODEOWNERS = ["@OnFreund"]
DEPENDENCIES = ["uart"]
AUTO_LOAD = ["binary_sensor", "sensor", "text_sensor"]
MULTI_CONF = True
CONF_HLK_FM22X_ID = "hlk_fm22x_id"
CONF_FACE_ID = "face_id"
CONF_ON_FACE_SCAN_MATCHED = "on_face_scan_matched"
CONF_ON_FACE_SCAN_UNMATCHED = "on_face_scan_unmatched"
CONF_ON_FACE_SCAN_INVALID = "on_face_scan_invalid"
CONF_ON_FACE_INFO = "on_face_info"
hlk_fm22x_ns = cg.esphome_ns.namespace("hlk_fm22x")
HlkFm22xComponent = hlk_fm22x_ns.class_(
"HlkFm22xComponent", cg.PollingComponent, uart.UARTDevice
)
FaceScanMatchedTrigger = hlk_fm22x_ns.class_(
"FaceScanMatchedTrigger", automation.Trigger.template(cg.int16, cg.std_string)
)
FaceScanUnmatchedTrigger = hlk_fm22x_ns.class_(
"FaceScanUnmatchedTrigger", automation.Trigger.template()
)
FaceScanInvalidTrigger = hlk_fm22x_ns.class_(
"FaceScanInvalidTrigger", automation.Trigger.template(cg.uint8)
)
FaceInfoTrigger = hlk_fm22x_ns.class_(
"FaceInfoTrigger",
automation.Trigger.template(
cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16
),
)
EnrollmentDoneTrigger = hlk_fm22x_ns.class_(
"EnrollmentDoneTrigger", automation.Trigger.template(cg.int16, cg.uint8)
)
EnrollmentFailedTrigger = hlk_fm22x_ns.class_(
"EnrollmentFailedTrigger", automation.Trigger.template(cg.uint8)
)
EnrollmentAction = hlk_fm22x_ns.class_("EnrollmentAction", automation.Action)
DeleteAction = hlk_fm22x_ns.class_("DeleteAction", automation.Action)
DeleteAllAction = hlk_fm22x_ns.class_("DeleteAllAction", automation.Action)
ScanAction = hlk_fm22x_ns.class_("ScanAction", automation.Action)
ResetAction = hlk_fm22x_ns.class_("ResetAction", automation.Action)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(HlkFm22xComponent),
cv.Optional(CONF_ON_FACE_SCAN_MATCHED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FaceScanMatchedTrigger
),
}
),
cv.Optional(CONF_ON_FACE_SCAN_UNMATCHED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FaceScanUnmatchedTrigger
),
}
),
cv.Optional(CONF_ON_FACE_SCAN_INVALID): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FaceScanInvalidTrigger
),
}
),
cv.Optional(CONF_ON_FACE_INFO): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FaceInfoTrigger),
}
),
cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
EnrollmentDoneTrigger
),
}
),
cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
EnrollmentFailedTrigger
),
}
),
}
)
.extend(cv.polling_component_schema("50ms"))
.extend(uart.UART_DEVICE_SCHEMA),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
for conf in config.get(CONF_ON_FACE_SCAN_MATCHED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.int16, "face_id"), (cg.std_string, "name")], conf
)
for conf in config.get(CONF_ON_FACE_SCAN_UNMATCHED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_FACE_SCAN_INVALID, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.uint8, "error")], conf)
for conf in config.get(CONF_ON_FACE_INFO, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger,
[
(cg.int16, "status"),
(cg.int16, "left"),
(cg.int16, "top"),
(cg.int16, "right"),
(cg.int16, "bottom"),
(cg.int16, "yaw"),
(cg.int16, "pitch"),
(cg.int16, "roll"),
],
conf,
)
for conf in config.get(CONF_ON_ENROLLMENT_DONE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.int16, "face_id"), (cg.uint8, "direction")], conf
)
for conf in config.get(CONF_ON_ENROLLMENT_FAILED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.uint8, "error")], conf)
@automation.register_action(
"hlk_fm22x.enroll",
EnrollmentAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
cv.Required(CONF_NAME): cv.templatable(cv.string),
cv.Required(CONF_DIRECTION): cv.templatable(cv.uint8_t),
},
key=CONF_NAME,
),
)
async def hlk_fm22x_enroll_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_NAME], args, cg.std_string)
cg.add(var.set_name(template_))
template_ = await cg.templatable(config[CONF_DIRECTION], args, cg.uint8)
cg.add(var.set_direction(template_))
return var
@automation.register_action(
"hlk_fm22x.delete",
DeleteAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
cv.Required(CONF_FACE_ID): cv.templatable(cv.uint16_t),
},
key=CONF_FACE_ID,
),
)
async def hlk_fm22x_delete_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_FACE_ID], args, cg.int16)
cg.add(var.set_face_id(template_))
return var
@automation.register_action(
"hlk_fm22x.delete_all",
DeleteAllAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
}
),
)
async def hlk_fm22x_delete_all_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"hlk_fm22x.scan",
ScanAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
}
),
)
async def hlk_fm22x_scan_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
@automation.register_action(
"hlk_fm22x.reset",
ResetAction,
cv.Schema(
{
cv.GenerateID(): cv.use_id(HlkFm22xComponent),
}
),
)
async def hlk_fm22x_reset_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var

View File

@@ -0,0 +1,21 @@
import esphome.codegen as cg
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_ICON, ICON_KEY_PLUS
from . import CONF_HLK_FM22X_ID, HlkFm22xComponent
DEPENDENCIES = ["hlk_fm22x"]
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend(
{
cv.GenerateID(CONF_HLK_FM22X_ID): cv.use_id(HlkFm22xComponent),
cv.Optional(CONF_ICON, default=ICON_KEY_PLUS): cv.icon,
}
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_HLK_FM22X_ID])
var = await binary_sensor.new_binary_sensor(config)
cg.add(hub.set_enrolling_binary_sensor(var))

View File

@@ -0,0 +1,325 @@
#include "hlk_fm22x.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include <array>
#include <cinttypes>
namespace esphome::hlk_fm22x {
static const char *const TAG = "hlk_fm22x";
void HlkFm22xComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up HLK-FM22X...");
this->set_enrolling_(false);
while (this->available()) {
this->read();
}
this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_STATUS); });
}
void HlkFm22xComponent::update() {
if (this->active_command_ != HlkFm22xCommand::NONE) {
if (this->wait_cycles_ > 600) {
ESP_LOGE(TAG, "Command 0x%.2X timed out", this->active_command_);
if (HlkFm22xCommand::RESET == this->active_command_) {
this->mark_failed();
} else {
this->reset();
}
}
}
this->recv_command_();
}
void HlkFm22xComponent::enroll_face(const std::string &name, HlkFm22xFaceDirection direction) {
if (name.length() > 31) {
ESP_LOGE(TAG, "enroll_face(): name too long '%s'", name.c_str());
return;
}
ESP_LOGI(TAG, "Starting enrollment for %s", name.c_str());
std::array<uint8_t, 35> data{};
data[0] = 0; // admin
std::copy(name.begin(), name.end(), data.begin() + 1);
// Remaining bytes are already zero-initialized
data[33] = (uint8_t) direction;
data[34] = 10; // timeout
this->send_command_(HlkFm22xCommand::ENROLL, data.data(), data.size());
this->set_enrolling_(true);
}
void HlkFm22xComponent::scan_face() {
ESP_LOGI(TAG, "Verify face");
static const uint8_t DATA[] = {0, 0};
this->send_command_(HlkFm22xCommand::VERIFY, DATA, sizeof(DATA));
}
void HlkFm22xComponent::delete_face(int16_t face_id) {
ESP_LOGI(TAG, "Deleting face in slot %d", face_id);
const uint8_t data[] = {(uint8_t) (face_id >> 8), (uint8_t) (face_id & 0xFF)};
this->send_command_(HlkFm22xCommand::DELETE_FACE, data, sizeof(data));
}
void HlkFm22xComponent::delete_all_faces() {
ESP_LOGI(TAG, "Deleting all stored faces");
this->send_command_(HlkFm22xCommand::DELETE_ALL_FACES);
}
void HlkFm22xComponent::get_face_count_() {
ESP_LOGD(TAG, "Getting face count");
this->send_command_(HlkFm22xCommand::GET_ALL_FACE_IDS);
}
void HlkFm22xComponent::reset() {
ESP_LOGI(TAG, "Resetting module");
this->active_command_ = HlkFm22xCommand::NONE;
this->wait_cycles_ = 0;
this->set_enrolling_(false);
this->send_command_(HlkFm22xCommand::RESET);
}
void HlkFm22xComponent::send_command_(HlkFm22xCommand command, const uint8_t *data, size_t size) {
ESP_LOGV(TAG, "Send command: 0x%.2X", command);
if (this->active_command_ != HlkFm22xCommand::NONE) {
ESP_LOGW(TAG, "Command 0x%.2X already active", this->active_command_);
return;
}
this->wait_cycles_ = 0;
this->active_command_ = command;
while (this->available())
this->read();
this->write((uint8_t) (START_CODE >> 8));
this->write((uint8_t) (START_CODE & 0xFF));
this->write((uint8_t) command);
uint16_t data_size = size;
this->write((uint8_t) (data_size >> 8));
this->write((uint8_t) (data_size & 0xFF));
uint8_t checksum = 0;
checksum ^= (uint8_t) command;
checksum ^= (data_size >> 8);
checksum ^= (data_size & 0xFF);
for (size_t i = 0; i < size; i++) {
this->write(data[i]);
checksum ^= data[i];
}
this->write(checksum);
this->active_command_ = command;
this->wait_cycles_ = 0;
}
void HlkFm22xComponent::recv_command_() {
uint8_t byte, checksum = 0;
uint16_t length = 0;
if (this->available() < 7) {
++this->wait_cycles_;
return;
}
this->wait_cycles_ = 0;
if ((this->read() != (uint8_t) (START_CODE >> 8)) || (this->read() != (uint8_t) (START_CODE & 0xFF))) {
ESP_LOGE(TAG, "Invalid start code");
return;
}
byte = this->read();
checksum ^= byte;
HlkFm22xResponseType response_type = (HlkFm22xResponseType) byte;
byte = this->read();
checksum ^= byte;
length = byte << 8;
byte = this->read();
checksum ^= byte;
length |= byte;
std::vector<uint8_t> data;
data.reserve(length);
for (uint16_t idx = 0; idx < length; ++idx) {
byte = this->read();
checksum ^= byte;
data.push_back(byte);
}
ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type, format_hex_pretty(data).c_str());
byte = this->read();
if (byte != checksum) {
ESP_LOGE(TAG, "Invalid checksum for data. Calculated: 0x%.2X, Received: 0x%.2X", checksum, byte);
return;
}
switch (response_type) {
case HlkFm22xResponseType::NOTE:
this->handle_note_(data);
break;
case HlkFm22xResponseType::REPLY:
this->handle_reply_(data);
break;
default:
ESP_LOGW(TAG, "Unexpected response type: 0x%.2X", response_type);
break;
}
}
void HlkFm22xComponent::handle_note_(const std::vector<uint8_t> &data) {
switch (data[0]) {
case HlkFm22xNoteType::FACE_STATE:
if (data.size() < 17) {
ESP_LOGE(TAG, "Invalid face note data size: %u", data.size());
break;
}
{
int16_t info[8];
uint8_t offset = 1;
for (int16_t &i : info) {
i = ((int16_t) data[offset + 1] << 8) | data[offset];
offset += 2;
}
ESP_LOGV(TAG, "Face state: status: %d, left: %d, top: %d, right: %d, bottom: %d, yaw: %d, pitch: %d, roll: %d",
info[0], info[1], info[2], info[3], info[4], info[5], info[6], info[7]);
this->face_info_callback_.call(info[0], info[1], info[2], info[3], info[4], info[5], info[6], info[7]);
}
break;
case HlkFm22xNoteType::READY:
ESP_LOGE(TAG, "Command 0x%.2X timed out", this->active_command_);
switch (this->active_command_) {
case HlkFm22xCommand::ENROLL:
this->set_enrolling_(false);
this->enrollment_failed_callback_.call(HlkFm22xResult::FAILED4_TIMEOUT);
break;
case HlkFm22xCommand::VERIFY:
this->face_scan_invalid_callback_.call(HlkFm22xResult::FAILED4_TIMEOUT);
break;
default:
break;
}
this->active_command_ = HlkFm22xCommand::NONE;
this->wait_cycles_ = 0;
break;
default:
ESP_LOGW(TAG, "Unhandled note: 0x%.2X", data[0]);
break;
}
}
void HlkFm22xComponent::handle_reply_(const std::vector<uint8_t> &data) {
auto expected = this->active_command_;
this->active_command_ = HlkFm22xCommand::NONE;
if (data[0] != (uint8_t) expected) {
ESP_LOGE(TAG, "Unexpected response command. Expected: 0x%.2X, Received: 0x%.2X", expected, data[0]);
return;
}
if (data[1] != HlkFm22xResult::SUCCESS) {
ESP_LOGE(TAG, "Command <0x%.2X> failed. Error: 0x%.2X", data[0], data[1]);
switch (expected) {
case HlkFm22xCommand::ENROLL:
this->set_enrolling_(false);
this->enrollment_failed_callback_.call(data[1]);
break;
case HlkFm22xCommand::VERIFY:
if (data[1] == HlkFm22xResult::REJECTED) {
this->face_scan_unmatched_callback_.call();
} else {
this->face_scan_invalid_callback_.call(data[1]);
}
break;
default:
break;
}
return;
}
switch (expected) {
case HlkFm22xCommand::VERIFY: {
int16_t face_id = ((int16_t) data[2] << 8) | data[3];
std::string name(data.begin() + 4, data.begin() + 36);
ESP_LOGD(TAG, "Face verified. ID: %d, name: %s", face_id, name.c_str());
if (this->last_face_id_sensor_ != nullptr) {
this->last_face_id_sensor_->publish_state(face_id);
}
if (this->last_face_name_text_sensor_ != nullptr) {
this->last_face_name_text_sensor_->publish_state(name);
}
this->face_scan_matched_callback_.call(face_id, name);
break;
}
case HlkFm22xCommand::ENROLL: {
int16_t face_id = ((int16_t) data[2] << 8) | data[3];
HlkFm22xFaceDirection direction = (HlkFm22xFaceDirection) data[4];
ESP_LOGI(TAG, "Face enrolled. ID: %d, Direction: 0x%.2X", face_id, direction);
this->enrollment_done_callback_.call(face_id, (uint8_t) direction);
this->set_enrolling_(false);
this->defer([this]() { this->get_face_count_(); });
break;
}
case HlkFm22xCommand::GET_STATUS:
if (this->status_sensor_ != nullptr) {
this->status_sensor_->publish_state(data[2]);
}
this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_VERSION); });
break;
case HlkFm22xCommand::GET_VERSION:
if (this->version_text_sensor_ != nullptr) {
std::string version(data.begin() + 2, data.end());
this->version_text_sensor_->publish_state(version);
}
this->defer([this]() { this->get_face_count_(); });
break;
case HlkFm22xCommand::GET_ALL_FACE_IDS:
if (this->face_count_sensor_ != nullptr) {
this->face_count_sensor_->publish_state(data[2]);
}
break;
case HlkFm22xCommand::DELETE_FACE:
ESP_LOGI(TAG, "Deleted face");
break;
case HlkFm22xCommand::DELETE_ALL_FACES:
ESP_LOGI(TAG, "Deleted all faces");
break;
case HlkFm22xCommand::RESET:
ESP_LOGI(TAG, "Module reset");
this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_STATUS); });
break;
default:
ESP_LOGW(TAG, "Unhandled command: 0x%.2X", this->active_command_);
break;
}
}
void HlkFm22xComponent::set_enrolling_(bool enrolling) {
if (this->enrolling_binary_sensor_ != nullptr) {
this->enrolling_binary_sensor_->publish_state(enrolling);
}
}
void HlkFm22xComponent::dump_config() {
ESP_LOGCONFIG(TAG, "HLK_FM22X:");
LOG_UPDATE_INTERVAL(this);
if (this->version_text_sensor_) {
LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %s", this->version_text_sensor_->get_state().c_str());
}
if (this->enrolling_binary_sensor_) {
LOG_BINARY_SENSOR(" ", "Enrolling", this->enrolling_binary_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %s", this->enrolling_binary_sensor_->state ? "ON" : "OFF");
}
if (this->face_count_sensor_) {
LOG_SENSOR(" ", "Face Count", this->face_count_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->face_count_sensor_->get_state());
}
if (this->status_sensor_) {
LOG_SENSOR(" ", "Status", this->status_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->status_sensor_->get_state());
}
if (this->last_face_id_sensor_) {
LOG_SENSOR(" ", "Last Face ID", this->last_face_id_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %u", (int16_t) this->last_face_id_sensor_->get_state());
}
if (this->last_face_name_text_sensor_) {
LOG_TEXT_SENSOR(" ", "Last Face Name", this->last_face_name_text_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %s", this->last_face_name_text_sensor_->get_state().c_str());
}
}
} // namespace esphome::hlk_fm22x

View File

@@ -0,0 +1,224 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/uart/uart.h"
#include <utility>
#include <vector>
namespace esphome::hlk_fm22x {
static const uint16_t START_CODE = 0xEFAA;
enum HlkFm22xCommand {
NONE = 0x00,
RESET = 0x10,
GET_STATUS = 0x11,
VERIFY = 0x12,
ENROLL = 0x13,
DELETE_FACE = 0x20,
DELETE_ALL_FACES = 0x21,
GET_ALL_FACE_IDS = 0x24,
GET_VERSION = 0x30,
GET_SERIAL_NUMBER = 0x93,
};
enum HlkFm22xResponseType {
REPLY = 0x00,
NOTE = 0x01,
IMAGE = 0x02,
};
enum HlkFm22xNoteType {
READY = 0x00,
FACE_STATE = 0x01,
};
enum HlkFm22xResult {
SUCCESS = 0x00,
REJECTED = 0x01,
ABORTED = 0x02,
FAILED4_CAMERA = 0x04,
FAILED4_UNKNOWNREASON = 0x05,
FAILED4_INVALIDPARAM = 0x06,
FAILED4_NOMEMORY = 0x07,
FAILED4_UNKNOWNUSER = 0x08,
FAILED4_MAXUSER = 0x09,
FAILED4_FACEENROLLED = 0x0A,
FAILED4_LIVENESSCHECK = 0x0C,
FAILED4_TIMEOUT = 0x0D,
FAILED4_AUTHORIZATION = 0x0E,
FAILED4_READ_FILE = 0x13,
FAILED4_WRITE_FILE = 0x14,
FAILED4_NO_ENCRYPT = 0x15,
FAILED4_NO_RGBIMAGE = 0x17,
FAILED4_JPGPHOTO_LARGE = 0x18,
FAILED4_JPGPHOTO_SMALL = 0x19,
};
enum HlkFm22xFaceDirection {
FACE_DIRECTION_UNDEFINED = 0x00,
FACE_DIRECTION_MIDDLE = 0x01,
FACE_DIRECTION_RIGHT = 0x02,
FACE_DIRECTION_LEFT = 0x04,
FACE_DIRECTION_DOWN = 0x08,
FACE_DIRECTION_UP = 0x10,
};
class HlkFm22xComponent : public PollingComponent, public uart::UARTDevice {
public:
void setup() override;
void update() override;
void dump_config() override;
void set_face_count_sensor(sensor::Sensor *face_count_sensor) { this->face_count_sensor_ = face_count_sensor; }
void set_status_sensor(sensor::Sensor *status_sensor) { this->status_sensor_ = status_sensor; }
void set_last_face_id_sensor(sensor::Sensor *last_face_id_sensor) {
this->last_face_id_sensor_ = last_face_id_sensor;
}
void set_last_face_name_text_sensor(text_sensor::TextSensor *last_face_name_text_sensor) {
this->last_face_name_text_sensor_ = last_face_name_text_sensor;
}
void set_enrolling_binary_sensor(binary_sensor::BinarySensor *enrolling_binary_sensor) {
this->enrolling_binary_sensor_ = enrolling_binary_sensor;
}
void set_version_text_sensor(text_sensor::TextSensor *version_text_sensor) {
this->version_text_sensor_ = version_text_sensor;
}
void add_on_face_scan_matched_callback(std::function<void(int16_t, std::string)> callback) {
this->face_scan_matched_callback_.add(std::move(callback));
}
void add_on_face_scan_unmatched_callback(std::function<void()> callback) {
this->face_scan_unmatched_callback_.add(std::move(callback));
}
void add_on_face_scan_invalid_callback(std::function<void(uint8_t)> callback) {
this->face_scan_invalid_callback_.add(std::move(callback));
}
void add_on_face_info_callback(
std::function<void(int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t)> callback) {
this->face_info_callback_.add(std::move(callback));
}
void add_on_enrollment_done_callback(std::function<void(int16_t, uint8_t)> callback) {
this->enrollment_done_callback_.add(std::move(callback));
}
void add_on_enrollment_failed_callback(std::function<void(uint8_t)> callback) {
this->enrollment_failed_callback_.add(std::move(callback));
}
void enroll_face(const std::string &name, HlkFm22xFaceDirection direction);
void scan_face();
void delete_face(int16_t face_id);
void delete_all_faces();
void reset();
protected:
void get_face_count_();
void send_command_(HlkFm22xCommand command, const uint8_t *data = nullptr, size_t size = 0);
void recv_command_();
void handle_note_(const std::vector<uint8_t> &data);
void handle_reply_(const std::vector<uint8_t> &data);
void set_enrolling_(bool enrolling);
HlkFm22xCommand active_command_ = HlkFm22xCommand::NONE;
uint16_t wait_cycles_ = 0;
sensor::Sensor *face_count_sensor_{nullptr};
sensor::Sensor *status_sensor_{nullptr};
sensor::Sensor *last_face_id_sensor_{nullptr};
binary_sensor::BinarySensor *enrolling_binary_sensor_{nullptr};
text_sensor::TextSensor *last_face_name_text_sensor_{nullptr};
text_sensor::TextSensor *version_text_sensor_{nullptr};
CallbackManager<void(uint8_t)> face_scan_invalid_callback_;
CallbackManager<void(int16_t, std::string)> face_scan_matched_callback_;
CallbackManager<void()> face_scan_unmatched_callback_;
CallbackManager<void(int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t)> face_info_callback_;
CallbackManager<void(int16_t, uint8_t)> enrollment_done_callback_;
CallbackManager<void(uint8_t)> enrollment_failed_callback_;
};
class FaceScanMatchedTrigger : public Trigger<int16_t, std::string> {
public:
explicit FaceScanMatchedTrigger(HlkFm22xComponent *parent) {
parent->add_on_face_scan_matched_callback(
[this](int16_t face_id, const std::string &name) { this->trigger(face_id, name); });
}
};
class FaceScanUnmatchedTrigger : public Trigger<> {
public:
explicit FaceScanUnmatchedTrigger(HlkFm22xComponent *parent) {
parent->add_on_face_scan_unmatched_callback([this]() { this->trigger(); });
}
};
class FaceScanInvalidTrigger : public Trigger<uint8_t> {
public:
explicit FaceScanInvalidTrigger(HlkFm22xComponent *parent) {
parent->add_on_face_scan_invalid_callback([this](uint8_t error) { this->trigger(error); });
}
};
class FaceInfoTrigger : public Trigger<int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t> {
public:
explicit FaceInfoTrigger(HlkFm22xComponent *parent) {
parent->add_on_face_info_callback(
[this](int16_t status, int16_t left, int16_t top, int16_t right, int16_t bottom, int16_t yaw, int16_t pitch,
int16_t roll) { this->trigger(status, left, top, right, bottom, yaw, pitch, roll); });
}
};
class EnrollmentDoneTrigger : public Trigger<int16_t, uint8_t> {
public:
explicit EnrollmentDoneTrigger(HlkFm22xComponent *parent) {
parent->add_on_enrollment_done_callback(
[this](int16_t face_id, uint8_t direction) { this->trigger(face_id, direction); });
}
};
class EnrollmentFailedTrigger : public Trigger<uint8_t> {
public:
explicit EnrollmentFailedTrigger(HlkFm22xComponent *parent) {
parent->add_on_enrollment_failed_callback([this](uint8_t error) { this->trigger(error); });
}
};
template<typename... Ts> class EnrollmentAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
public:
TEMPLATABLE_VALUE(std::string, name)
TEMPLATABLE_VALUE(uint8_t, direction)
void play(Ts... x) override {
auto name = this->name_.value(x...);
auto direction = (HlkFm22xFaceDirection) this->direction_.value(x...);
this->parent_->enroll_face(name, direction);
}
};
template<typename... Ts> class DeleteAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
public:
TEMPLATABLE_VALUE(int16_t, face_id)
void play(Ts... x) override {
auto face_id = this->face_id_.value(x...);
this->parent_->delete_face(face_id);
}
};
template<typename... Ts> class DeleteAllAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
public:
void play(Ts... x) override { this->parent_->delete_all_faces(); }
};
template<typename... Ts> class ScanAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
public:
void play(Ts... x) override { this->parent_->scan_face(); }
};
template<typename... Ts> class ResetAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
public:
void play(Ts... x) override { this->parent_->reset(); }
};
} // namespace esphome::hlk_fm22x

View File

@@ -0,0 +1,47 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import CONF_STATUS, ENTITY_CATEGORY_DIAGNOSTIC, ICON_ACCOUNT
from . import CONF_HLK_FM22X_ID, HlkFm22xComponent
DEPENDENCIES = ["hlk_fm22x"]
CONF_FACE_COUNT = "face_count"
CONF_LAST_FACE_ID = "last_face_id"
ICON_FACE = "mdi:face-recognition"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_HLK_FM22X_ID): cv.use_id(HlkFm22xComponent),
cv.Optional(CONF_FACE_COUNT): sensor.sensor_schema(
icon=ICON_FACE,
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_STATUS): sensor.sensor_schema(
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_LAST_FACE_ID): sensor.sensor_schema(
icon=ICON_ACCOUNT,
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_HLK_FM22X_ID])
for key in [
CONF_FACE_COUNT,
CONF_STATUS,
CONF_LAST_FACE_ID,
]:
if key not in config:
continue
conf = config[key]
sens = await sensor.new_sensor(conf)
cg.add(getattr(hub, f"set_{key}_sensor")(sens))

View File

@@ -0,0 +1,42 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_VERSION,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_ACCOUNT,
ICON_RESTART,
)
from . import CONF_HLK_FM22X_ID, HlkFm22xComponent
DEPENDENCIES = ["hlk_fm22x"]
CONF_LAST_FACE_NAME = "last_face_name"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_HLK_FM22X_ID): cv.use_id(HlkFm22xComponent),
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
icon=ICON_RESTART,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_LAST_FACE_NAME): text_sensor.text_sensor_schema(
icon=ICON_ACCOUNT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_HLK_FM22X_ID])
for key in [
CONF_VERSION,
CONF_LAST_FACE_NAME,
]:
if key not in config:
continue
conf = config[key]
sens = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))

View File

@@ -1,7 +1,7 @@
#pragma once
#include <climits>
#include "abstract_aqi_calculator.h"
// https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf
// https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf
namespace esphome {
namespace hm3301 {
@@ -16,16 +16,15 @@ class AQICalculator : public AbstractAQICalculator {
}
protected:
static const int AMOUNT_OF_LEVELS = 7;
static const int AMOUNT_OF_LEVELS = 6;
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200},
{201, 300}, {301, 400}, {401, 500}};
int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 35}, {36, 55}, {56, 150},
{151, 250}, {251, 350}, {351, 500}};
int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55},
{56, 125}, {126, 225}, {226, INT_MAX}};
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254}, {255, 354},
{355, 424}, {425, 504}, {505, 604}};
int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
{255, 354}, {355, 424}, {425, INT_MAX}};
int calculate_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
int grid_index = get_grid_index_(value, array);

View File

@@ -7,10 +7,8 @@ namespace homeassistant {
static const char *const TAG = "homeassistant.time";
void HomeassistantTime::dump_config() {
ESP_LOGCONFIG(TAG,
"Home Assistant Time:\n"
" Timezone: '%s'",
this->timezone_.c_str());
ESP_LOGCONFIG(TAG, "Home Assistant Time");
RealTimeClock::dump_config();
}
float HomeassistantTime::get_setup_priority() const { return setup_priority::DATA; }

View File

@@ -2,13 +2,18 @@
#include "esphome/core/hal.h"
#include "esphome/components/lcd_base/lcd_display.h"
#include "esphome/components/display/display.h"
namespace esphome {
namespace lcd_gpio {
class GPIOLCDDisplay;
using gpio_lcd_writer_t = display::DisplayWriter<GPIOLCDDisplay>;
class GPIOLCDDisplay : public lcd_base::LCDDisplay {
public:
void set_writer(std::function<void(GPIOLCDDisplay &)> &&writer) { this->writer_ = std::move(writer); }
void set_writer(gpio_lcd_writer_t &&writer) { this->writer_ = std::move(writer); }
void setup() override;
void set_data_pins(GPIOPin *d0, GPIOPin *d1, GPIOPin *d2, GPIOPin *d3) {
this->data_pins_[0] = d0;
@@ -43,7 +48,7 @@ class GPIOLCDDisplay : public lcd_base::LCDDisplay {
GPIOPin *rw_pin_{nullptr};
GPIOPin *enable_pin_{nullptr};
GPIOPin *data_pins_[8]{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
std::function<void(GPIOLCDDisplay &)> writer_;
gpio_lcd_writer_t writer_;
};
} // namespace lcd_gpio

View File

@@ -3,13 +3,18 @@
#include "esphome/core/component.h"
#include "esphome/components/lcd_base/lcd_display.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/display/display.h"
namespace esphome {
namespace lcd_pcf8574 {
class PCF8574LCDDisplay;
using pcf8574_lcd_writer_t = display::DisplayWriter<PCF8574LCDDisplay>;
class PCF8574LCDDisplay : public lcd_base::LCDDisplay, public i2c::I2CDevice {
public:
void set_writer(std::function<void(PCF8574LCDDisplay &)> &&writer) { this->writer_ = std::move(writer); }
void set_writer(pcf8574_lcd_writer_t &&writer) { this->writer_ = std::move(writer); }
void setup() override;
void dump_config() override;
void backlight();
@@ -24,7 +29,7 @@ class PCF8574LCDDisplay : public lcd_base::LCDDisplay, public i2c::I2CDevice {
// Stores the current state of the backlight.
uint8_t backlight_value_;
std::function<void(PCF8574LCDDisplay &)> writer_;
pcf8574_lcd_writer_t writer_;
};
} // namespace lcd_pcf8574

View File

@@ -174,7 +174,7 @@ static uint8_t calc_checksum(void *data, size_t size) {
static int get_firmware_int(const char *version_string) {
std::string version_str = version_string;
if (version_str[0] == 'v') {
version_str = version_str.substr(1);
version_str.erase(0, 1);
}
version_str.erase(remove(version_str.begin(), version_str.end(), '.'), version_str.end());
int version_integer = stoi(version_str);

View File

@@ -1,7 +1,8 @@
#include "esphome/core/log.h"
#include "light_output.h"
#include "light_state.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/log.h"
#include "light_output.h"
#include "transformers.h"
namespace esphome {
@@ -137,7 +138,12 @@ void LightState::loop() {
float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
void LightState::publish_state() { this->remote_values_callback_.call(); }
void LightState::publish_state() {
this->remote_values_callback_.call();
#if defined(USE_LIGHT) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_light_update(this);
#endif
}
LightOutput *LightState::get_output() const { return this->output_; }

View File

@@ -1,4 +1,6 @@
#include "lock.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -53,6 +55,9 @@ void Lock::publish_state(LockState state) {
this->rtc_.save(&this->state);
ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), lock_state_to_string(state));
this->state_callback_.call();
#if defined(USE_LOCK) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_lock_update(this);
#endif
}
void Lock::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }

View File

@@ -1,6 +1,8 @@
import importlib
import logging
import pkgutil
from esphome.automation import build_automation, register_action, validate_automation
from esphome.automation import build_automation, validate_automation
import esphome.codegen as cg
from esphome.components.const import CONF_COLOR_DEPTH, CONF_DRAW_ROUNDING
from esphome.components.display import Display
@@ -25,8 +27,8 @@ from esphome.cpp_generator import MockObj
from esphome.final_validate import full_config
from esphome.helpers import write_file_if_changed
from . import defines as df, helpers, lv_validation as lvalid
from .automation import disp_update, focused_widgets, refreshed_widgets, update_to_code
from . import defines as df, helpers, lv_validation as lvalid, widgets
from .automation import disp_update, focused_widgets, refreshed_widgets
from .defines import add_define
from .encoders import (
ENCODERS_CONFIG,
@@ -45,7 +47,6 @@ from .schemas import (
WIDGET_TYPES,
any_widget_schema,
container_schema,
create_modify_schema,
obj_schema,
)
from .styles import add_top_layer, styles_to_code, theme_to_code
@@ -54,7 +55,6 @@ from .trigger import add_on_boot_triggers, generate_triggers
from .types import (
FontEngine,
IdleTrigger,
ObjUpdateAction,
PlainTrigger,
lv_font_t,
lv_group_t,
@@ -69,33 +69,23 @@ from .widgets import (
set_obj_properties,
styles_used,
)
from .widgets.animimg import animimg_spec
from .widgets.arc import arc_spec
from .widgets.button import button_spec
from .widgets.buttonmatrix import buttonmatrix_spec
from .widgets.canvas import canvas_spec
from .widgets.checkbox import checkbox_spec
from .widgets.container import container_spec
from .widgets.dropdown import dropdown_spec
from .widgets.img import img_spec
from .widgets.keyboard import keyboard_spec
from .widgets.label import label_spec
from .widgets.led import led_spec
from .widgets.line import line_spec
from .widgets.lv_bar import bar_spec
from .widgets.meter import meter_spec
# Import only what we actually use directly in this file
from .widgets.msgbox import MSGBOX_SCHEMA, msgboxes_to_code
from .widgets.obj import obj_spec
from .widgets.page import add_pages, generate_page_triggers, page_spec
from .widgets.qrcode import qr_code_spec
from .widgets.roller import roller_spec
from .widgets.slider import slider_spec
from .widgets.spinbox import spinbox_spec
from .widgets.spinner import spinner_spec
from .widgets.switch import switch_spec
from .widgets.tabview import tabview_spec
from .widgets.textarea import textarea_spec
from .widgets.tileview import tileview_spec
from .widgets.obj import obj_spec # Used in LVGL_SCHEMA
from .widgets.page import ( # page_spec used in LVGL_SCHEMA
add_pages,
generate_page_triggers,
page_spec,
)
# Widget registration happens via WidgetType.__init__ in individual widget files
# The imports below trigger creation of the widget types
# Action registration (lvgl.{widget}.update) happens automatically
# in the WidgetType.__init__ method
for module_info in pkgutil.iter_modules(widgets.__path__):
importlib.import_module(f".widgets.{module_info.name}", package=__package__)
DOMAIN = "lvgl"
DEPENDENCIES = ["display"]
@@ -103,41 +93,6 @@ AUTO_LOAD = ["key_provider"]
CODEOWNERS = ["@clydebarrow"]
LOGGER = logging.getLogger(__name__)
for w_type in (
label_spec,
obj_spec,
button_spec,
bar_spec,
slider_spec,
arc_spec,
line_spec,
spinner_spec,
led_spec,
animimg_spec,
checkbox_spec,
img_spec,
switch_spec,
tabview_spec,
buttonmatrix_spec,
meter_spec,
dropdown_spec,
roller_spec,
textarea_spec,
spinbox_spec,
keyboard_spec,
tileview_spec,
qr_code_spec,
canvas_spec,
container_spec,
):
WIDGET_TYPES[w_type.name] = w_type
for w_type in WIDGET_TYPES.values():
register_action(
f"lvgl.{w_type.name}.update",
ObjUpdateAction,
create_modify_schema(w_type),
)(update_to_code)
SIMPLE_TRIGGERS = (
df.CONF_ON_PAUSE,
@@ -402,6 +357,15 @@ def add_hello_world(config):
return config
def _theme_schema(value):
return cv.Schema(
{
cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA)
for name, w in WIDGET_TYPES.items()
}
)(value)
FINAL_VALIDATE_SCHEMA = final_validation
LVGL_SCHEMA = cv.All(
@@ -454,12 +418,7 @@ LVGL_SCHEMA = cv.All(
cv.Optional(
df.CONF_TRANSPARENCY_KEY, default=0x000400
): lvalid.lv_color,
cv.Optional(df.CONF_THEME): cv.Schema(
{
cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA)
for name, w in WIDGET_TYPES.items()
}
),
cv.Optional(df.CONF_THEME): _theme_schema,
cv.Optional(df.CONF_GRADIENTS): GRADIENT_SCHEMA,
cv.Optional(df.CONF_TOUCHSCREENS, default=None): touchscreen_schema,
cv.Optional(df.CONF_ENCODERS, default=None): ENCODERS_CONFIG,

View File

@@ -3,6 +3,8 @@ import re
from esphome import config_validation as cv
from esphome.const import CONF_ARGS, CONF_FORMAT
CONF_IF_NAN = "if_nan"
lv_uses = {
"USER_DATA",
"LOG",
@@ -21,23 +23,48 @@ lv_fonts_used = set()
esphome_fonts_used = set()
lvgl_components_required = set()
def validate_printf(value):
cfmt = r"""
# noqa
f_regex = re.compile(
r"""
( # start of capture group 1
% # literal "%"
(?:[-+0 #]{0,5}) # optional flags
[-+0 #]{0,5} # optional flags
(?:\d+|\*)? # width
(?:\.(?:\d+|\*))? # precision
(?:h|l|ll|w|I|I32|I64)? # size
f # type
)
""",
flags=re.VERBOSE,
)
# noqa
c_regex = re.compile(
r"""
( # start of capture group 1
% # literal "%"
[-+0 #]{0,5} # optional flags
(?:\d+|\*)? # width
(?:\.(?:\d+|\*))? # precision
(?:h|l|ll|w|I|I32|I64)? # size
[cCdiouxXeEfgGaAnpsSZ] # type
)
""" # noqa
matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.VERBOSE)
""",
flags=re.VERBOSE,
)
def validate_printf(value):
format_string = value[CONF_FORMAT]
matches = c_regex.findall(format_string)
if len(matches) != len(value[CONF_ARGS]):
raise cv.Invalid(
f"Found {len(matches)} printf-patterns ({', '.join(matches)}), but {len(value[CONF_ARGS])} args were given!"
)
if value.get(CONF_IF_NAN) and len(f_regex.findall(format_string)) != 1:
raise cv.Invalid(
"Use of 'if_nan' requires a single valid printf-pattern of type %f"
)
return value

View File

@@ -1,4 +1,5 @@
import re
import textwrap
import esphome.config_validation as cv
from esphome.const import CONF_HEIGHT, CONF_TYPE, CONF_WIDTH
@@ -122,7 +123,7 @@ class FlexLayout(Layout):
def get_layout_schemas(self, config: dict) -> tuple:
layout = config.get(CONF_LAYOUT)
if not isinstance(layout, dict) or layout.get(CONF_TYPE) != TYPE_FLEX:
if not isinstance(layout, dict) or layout.get(CONF_TYPE).lower() != TYPE_FLEX:
return None, {}
child_schema = FLEX_OBJ_SCHEMA
if grow := layout.get(CONF_FLEX_GROW):
@@ -161,6 +162,8 @@ class DirectionalLayout(FlexLayout):
return self.direction
def get_layout_schemas(self, config: dict) -> tuple:
if not isinstance(config.get(CONF_LAYOUT), str):
return None, {}
if config.get(CONF_LAYOUT, "").lower() != self.direction:
return None, {}
return cv.one_of(self.direction, lower=True), flex_hv_schema(self.direction)
@@ -206,7 +209,7 @@ class GridLayout(Layout):
# Not a valid grid layout string
return None, {}
if not isinstance(layout, dict) or layout.get(CONF_TYPE) != TYPE_GRID:
if not isinstance(layout, dict) or layout.get(CONF_TYPE).lower() != TYPE_GRID:
return None, {}
return (
{
@@ -259,7 +262,7 @@ class GridLayout(Layout):
)
# should be guaranteed to be a dict at this point
assert isinstance(layout, dict)
assert layout.get(CONF_TYPE) == TYPE_GRID
assert layout.get(CONF_TYPE).lower() == TYPE_GRID
rows = len(layout[CONF_GRID_ROWS])
columns = len(layout[CONF_GRID_COLUMNS])
used_cells = [[None] * columns for _ in range(rows)]
@@ -335,6 +338,17 @@ def append_layout_schema(schema, config: dict):
if CONF_LAYOUT not in config:
# If no layout is specified, return the schema as is
return schema.extend({cv.Optional(CONF_WIDGETS): any_widget_schema()})
layout = config[CONF_LAYOUT]
# Sanity check the layout to avoid redundant checks in each type
if not isinstance(layout, str) and not isinstance(layout, dict):
raise cv.Invalid(
"The 'layout' option must be a string or a dictionary", [CONF_LAYOUT]
)
if isinstance(layout, dict) and not isinstance(layout.get(CONF_TYPE), str):
raise cv.Invalid(
"Invalid layout type; must be a string ('flex' or 'grid')",
[CONF_LAYOUT, CONF_TYPE],
)
for layout_class in LAYOUT_CLASSES:
layout_schema, child_schema = layout_class.get_layout_schemas(config)
@@ -348,10 +362,17 @@ def append_layout_schema(schema, config: dict):
layout_schema.add_extra(layout_class.validate)
return layout_schema.extend(schema)
# If no layout class matched, return a default schema
return cv.Schema(
{
cv.Optional(CONF_LAYOUT): cv.one_of(*LAYOUT_CHOICES, lower=True),
cv.Optional(CONF_WIDGETS): any_widget_schema(),
}
if isinstance(layout, dict):
raise cv.Invalid(
"Invalid layout type; must be 'flex' or 'grid'", [CONF_LAYOUT, CONF_TYPE]
)
raise cv.Invalid(
textwrap.dedent(
"""
Invalid 'layout' value
layout choices are 'horizontal', 'vertical', '<rows>x<cols>',
or a dictionary with a 'type' key
"""
),
[CONF_LAYOUT],
)

View File

@@ -33,7 +33,13 @@ from .defines import (
call_lambda,
literal,
)
from .helpers import add_lv_use, esphome_fonts_used, lv_fonts_used, requires_component
from .helpers import (
CONF_IF_NAN,
add_lv_use,
esphome_fonts_used,
lv_fonts_used,
requires_component,
)
from .types import lv_font_t, lv_gradient_t
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
@@ -412,7 +418,13 @@ class TextValidator(LValidator):
str_args = [str(x) for x in value[CONF_ARGS]]
arg_expr = cg.RawExpression(",".join(str_args))
format_str = cpp_string_escape(format_str)
return literal(f"str_sprintf({format_str}, {arg_expr}).c_str()")
sprintf_str = f"str_sprintf({format_str}, {arg_expr}).c_str()"
if nanval := value.get(CONF_IF_NAN):
nanval = cpp_string_escape(nanval)
return literal(
f"(std::isfinite({arg_expr}) ? {sprintf_str} : {nanval})"
)
return literal(sprintf_str)
if time_format := value.get(CONF_TIME_FORMAT):
source = value[CONF_TIME]
if isinstance(source, Lambda):

View File

@@ -20,7 +20,7 @@ from esphome.core.config import StartupTrigger
from . import defines as df, lv_validation as lvalid
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR
from .helpers import requires_component, validate_printf
from .helpers import CONF_IF_NAN, requires_component, validate_printf
from .layout import (
FLEX_OBJ_SCHEMA,
GRID_CELL_SCHEMA,
@@ -54,6 +54,7 @@ PRINTF_TEXT_SCHEMA = cv.All(
{
cv.Required(CONF_FORMAT): cv.string,
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
cv.Optional(CONF_IF_NAN): cv.string,
},
),
validate_printf,
@@ -410,6 +411,10 @@ def any_widget_schema(extras=None):
Dynamically generate schemas for all possible LVGL widgets. This is what implements the ability to have a list of any kind of
widget under the widgets: key.
This uses lazy evaluation - the schema is built when called during validation,
not at import time. This allows external components to register widgets
before schema validation begins.
:param extras: Additional schema to be applied to each generated one
:return: A validator for the Widgets key
"""

View File

@@ -41,16 +41,16 @@ class LVGLSelect : public select::Select, public Component {
}
void publish() {
this->publish_state(this->widget_->get_selected_text());
auto index = this->widget_->get_selected_index();
this->publish_state(index);
if (this->restore_) {
auto index = this->widget_->get_selected_index();
this->pref_.save(&index);
}
}
protected:
void control(const std::string &value) override {
this->widget_->set_selected_text(value, this->anim_);
void control(size_t index) override {
this->widget_->set_selected_index(index, this->anim_);
this->publish();
}
void set_options_() {
@@ -59,8 +59,8 @@ class LVGLSelect : public select::Select, public Component {
const auto &opts = this->widget_->get_options();
FixedVector<const char *> opt_ptrs;
opt_ptrs.init(opts.size());
for (size_t i = 0; i < opts.size(); i++) {
opt_ptrs[i] = opts[i].c_str();
for (const auto &opt : opts) {
opt_ptrs.push_back(opt.c_str());
}
this->traits.set_options(opt_ptrs);
}

View File

@@ -1,8 +1,10 @@
import sys
from esphome import automation, codegen as cg
from esphome.automation import register_action
from esphome.config_validation import Schema
from esphome.const import CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_TEXT, CONF_VALUE
from esphome.core import EsphomeError
from esphome.cpp_generator import MockObj, MockObjClass
from esphome.cpp_types import esphome_ns
@@ -124,13 +126,16 @@ class WidgetType:
schema=None,
modify_schema=None,
lv_name=None,
is_mock: bool = False,
):
"""
:param name: The widget name, e.g. "bar"
:param w_type: The C type of the widget
:param parts: What parts this widget supports
:param schema: The config schema for defining a widget
:param modify_schema: A schema to update the widget
:param modify_schema: A schema to update the widget, defaults to the same as the schema
:param lv_name: The name of the LVGL widget in the LVGL library, if different from the name
:param is_mock: Whether this widget is a mock widget, i.e. not a real LVGL widget
"""
self.name = name
self.lv_name = lv_name or name
@@ -146,6 +151,22 @@ class WidgetType:
self.modify_schema = modify_schema
self.mock_obj = MockObj(f"lv_{self.lv_name}", "_")
# Local import to avoid circular import
from .automation import update_to_code
from .schemas import WIDGET_TYPES, create_modify_schema
if not is_mock:
if self.name in WIDGET_TYPES:
raise EsphomeError(f"Duplicate definition of widget type '{self.name}'")
WIDGET_TYPES[self.name] = self
# Register the update action automatically
register_action(
f"lvgl.{self.name}.update",
ObjUpdateAction,
create_modify_schema(self),
)(update_to_code)
@property
def animated(self):
return False

View File

@@ -213,17 +213,14 @@ class LvScrActType(WidgetType):
"""
def __init__(self):
super().__init__("lv_scr_act()", lv_obj_t, ())
super().__init__("lv_scr_act()", lv_obj_t, (), is_mock=True)
async def to_code(self, w, config: dict):
return []
lv_scr_act_spec = LvScrActType()
def get_scr_act(lv_comp: MockObj) -> Widget:
return Widget.create(None, lv_comp.get_scr_act(), lv_scr_act_spec, {})
return Widget.create(None, lv_comp.get_scr_act(), LvScrActType(), {})
def get_widget_generator(wid):

View File

@@ -2,7 +2,7 @@ from esphome import automation
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_RANGE_FROM, CONF_RANGE_TO, CONF_STEP, CONF_VALUE
from ..automation import action_to_code, update_to_code
from ..automation import action_to_code
from ..defines import (
CONF_CURSOR,
CONF_DECIMAL_PLACES,
@@ -171,17 +171,3 @@ async def spinbox_decrement(config, action_id, template_arg, args):
lv.spinbox_decrement(w.obj)
return await action_to_code(widgets, do_increment, action_id, template_arg, args)
@automation.register_action(
"lvgl.spinbox.update",
ObjUpdateAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(lv_spinbox_t),
cv.Required(CONF_VALUE): lv_float,
}
),
)
async def spinbox_update_to_code(config, action_id, template_arg, args):
return await update_to_code(config, action_id, template_arg, args)

View File

@@ -4,13 +4,14 @@
#include "esphome/core/time.h"
#include "esphome/components/spi/spi.h"
#include "esphome/components/display/display.h"
namespace esphome {
namespace max7219 {
class MAX7219Component;
using max7219_writer_t = std::function<void(MAX7219Component &)>;
using max7219_writer_t = display::DisplayWriter<MAX7219Component>;
class MAX7219Component : public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
@@ -57,7 +58,7 @@ class MAX7219Component : public PollingComponent,
uint8_t num_chips_{1};
uint8_t *buffer_;
bool reverse_{false};
optional<max7219_writer_t> writer_{};
max7219_writer_t writer_{};
};
} // namespace max7219

View File

@@ -271,7 +271,11 @@ void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) {
}
}
} else if (this->orientation_ == 1) {
b = pixels[col];
if (this->flip_x_) {
b = pixels[7 - col];
} else {
b = pixels[col];
}
} else if (this->orientation_ == 2) {
for (uint8_t i = 0; i < 8; i++) {
if (this->flip_x_) {
@@ -282,7 +286,11 @@ void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) {
}
} else {
for (uint8_t i = 0; i < 8; i++) {
b |= ((pixels[7 - col] >> i) & 1) << (7 - i);
if (this->flip_x_) {
b |= ((pixels[col] >> i) & 1) << (7 - i);
} else {
b |= ((pixels[7 - col] >> i) & 1) << (7 - i);
}
}
}
// send this byte to display at selected chip

View File

@@ -23,7 +23,7 @@ enum ScrollMode {
class MAX7219Component;
using max7219_writer_t = std::function<void(MAX7219Component &)>;
using max7219_writer_t = display::DisplayWriter<MAX7219Component>;
class MAX7219Component : public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
@@ -117,7 +117,7 @@ class MAX7219Component : public display::DisplayBuffer,
uint32_t last_scroll_ = 0;
uint16_t stepsleft_;
size_t get_buffer_length_();
optional<max7219_writer_t> writer_local_{};
max7219_writer_t writer_local_{};
};
} // namespace max7219digit

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@philippderdiedas"]

View File

@@ -0,0 +1,31 @@
#include "mcp3221_sensor.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome {
namespace mcp3221 {
static const char *const TAG = "mcp3221";
float MCP3221Sensor::sample() {
uint8_t data[2];
if (this->read(data, 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read failed");
this->status_set_warning();
return NAN;
}
this->status_clear_warning();
uint16_t value = encode_uint16(data[0], data[1]);
float voltage = value * this->reference_voltage_ / 4096.0f;
return voltage;
}
void MCP3221Sensor::update() {
float v = this->sample();
this->publish_state(v);
}
} // namespace mcp3221
} // namespace esphome

View File

@@ -0,0 +1,28 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
#include <cinttypes>
namespace esphome {
namespace mcp3221 {
class MCP3221Sensor : public sensor::Sensor,
public PollingComponent,
public voltage_sampler::VoltageSampler,
public i2c::I2CDevice {
public:
void set_reference_voltage(float reference_voltage) { this->reference_voltage_ = reference_voltage; }
void update() override;
float sample() override;
protected:
float reference_voltage_;
};
} // namespace mcp3221
} // namespace esphome

View File

@@ -0,0 +1,49 @@
import esphome.codegen as cg
from esphome.components import i2c, sensor, voltage_sampler
import esphome.config_validation as cv
from esphome.const import (
CONF_REFERENCE_VOLTAGE,
DEVICE_CLASS_VOLTAGE,
ICON_SCALE,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
)
AUTO_LOAD = ["voltage_sampler"]
DEPENDENCIES = ["i2c"]
mcp3221_ns = cg.esphome_ns.namespace("mcp3221")
MCP3221Sensor = mcp3221_ns.class_(
"MCP3221Sensor",
sensor.Sensor,
voltage_sampler.VoltageSampler,
cg.PollingComponent,
i2c.I2CDevice,
)
CONFIG_SCHEMA = (
sensor.sensor_schema(
MCP3221Sensor,
icon=ICON_SCALE,
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT,
device_class=DEVICE_CLASS_VOLTAGE,
unit_of_measurement=UNIT_VOLT,
)
.extend(
{
cv.Optional(CONF_REFERENCE_VOLTAGE, default="3.3V"): cv.voltage,
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x48))
)
async def to_code(config):
var = await sensor.new_sensor(config)
cg.add(var.set_reference_voltage(config[CONF_REFERENCE_VOLTAGE]))
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)

View File

@@ -37,8 +37,6 @@ MDNS_STATIC_CONST_CHAR(SERVICE_TCP, "_tcp");
MDNS_STATIC_CONST_CHAR(VALUE_VERSION, ESPHOME_VERSION);
void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services) {
this->hostname_ = App.get_name();
// IMPORTANT: The #ifdef blocks below must match COMPONENTS_WITH_MDNS_SERVICES
// in mdns/__init__.py. If you add a new service here, update both locations.
@@ -179,7 +177,7 @@ void MDNSComponent::dump_config() {
ESP_LOGCONFIG(TAG,
"mDNS:\n"
" Hostname: %s",
this->hostname_.c_str());
App.get_name().c_str());
#ifdef USE_MDNS_STORE_SERVICES
ESP_LOGV(TAG, " Services:");
for (const auto &service : this->services_) {

View File

@@ -76,7 +76,6 @@ class MDNSComponent : public Component {
#ifdef USE_MDNS_STORE_SERVICES
StaticVector<MDNSService, MDNS_SERVICE_COUNT> services_{};
#endif
std::string hostname_;
void compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services);
};

View File

@@ -2,6 +2,7 @@
#if defined(USE_ESP32) && defined(USE_MDNS)
#include <mdns.h>
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "mdns_component.h"
@@ -27,8 +28,9 @@ void MDNSComponent::setup() {
return;
}
mdns_hostname_set(this->hostname_.c_str());
mdns_instance_name_set(this->hostname_.c_str());
const char *hostname = App.get_name().c_str();
mdns_hostname_set(hostname);
mdns_instance_name_set(hostname);
for (const auto &service : services) {
auto txt_records = std::make_unique<mdns_txt_item_t[]>(service.txt_records.size());

View File

@@ -4,6 +4,7 @@
#include <ESP8266mDNS.h>
#include "esphome/components/network/ip_address.h"
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "mdns_component.h"
@@ -20,7 +21,7 @@ void MDNSComponent::setup() {
this->compile_records_(services);
#endif
MDNS.begin(this->hostname_.c_str());
MDNS.begin(App.get_name().c_str());
for (const auto &service : services) {
// Strip the leading underscore from the proto and service_type. While it is

View File

@@ -3,6 +3,7 @@
#include "esphome/components/network/ip_address.h"
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "mdns_component.h"
@@ -20,7 +21,7 @@ void MDNSComponent::setup() {
this->compile_records_(services);
#endif
MDNS.begin(this->hostname_.c_str());
MDNS.begin(App.get_name().c_str());
for (const auto &service : services) {
// Strip the leading underscore from the proto and service_type. While it is

View File

@@ -3,6 +3,7 @@
#include "esphome/components/network/ip_address.h"
#include "esphome/components/network/util.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "mdns_component.h"
@@ -20,7 +21,7 @@ void MDNSComponent::setup() {
this->compile_records_(services);
#endif
MDNS.begin(this->hostname_.c_str());
MDNS.begin(App.get_name().c_str());
for (const auto &service : services) {
// Strip the leading underscore from the proto and service_type. While it is

View File

@@ -1,5 +1,6 @@
#include "media_player.h"
#include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -148,7 +149,12 @@ void MediaPlayer::add_on_state_callback(std::function<void()> &&callback) {
this->state_callback_.add(std::move(callback));
}
void MediaPlayer::publish_state() { this->state_callback_.call(); }
void MediaPlayer::publish_state() {
this->state_callback_.call();
#if defined(USE_MEDIA_PLAYER) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_media_player_update(this);
#endif
}
} // namespace media_player
} // namespace esphome

View File

@@ -3,6 +3,7 @@ import binascii
from esphome import automation
import esphome.codegen as cg
from esphome.components import modbus
from esphome.components.const import CONF_ENABLED
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
@@ -20,7 +21,6 @@ from .const import (
CONF_BYTE_OFFSET,
CONF_COMMAND_THROTTLE,
CONF_CUSTOM_COMMAND,
CONF_ENABLED,
CONF_FORCE_NEW_RANGE,
CONF_MAX_CMD_RETRIES,
CONF_MODBUS_CONTROLLER_ID,

View File

@@ -2,7 +2,6 @@ CONF_ALLOW_DUPLICATE_COMMANDS = "allow_duplicate_commands"
CONF_BITMASK = "bitmask"
CONF_BYTE_OFFSET = "byte_offset"
CONF_COMMAND_THROTTLE = "command_throttle"
CONF_ENABLED = "enabled"
CONF_OFFLINE_SKIP_UPDATES = "offline_skip_updates"
CONF_CUSTOM_COMMAND = "custom_command"
CONF_FORCE_NEW_RANGE = "force_new_range"

View File

@@ -28,8 +28,9 @@ void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
if (map_it != this->mapping_.cend()) {
size_t idx = std::distance(this->mapping_.cbegin(), map_it);
new_state = std::string(this->option_at(idx));
ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value);
ESP_LOGV(TAG, "Found option %s for value %lld", this->option_at(idx), value);
this->publish_state(idx);
return;
} else {
ESP_LOGE(TAG, "No option found for mapping %lld", value);
}
@@ -40,19 +41,16 @@ void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
}
}
void ModbusSelect::control(const std::string &value) {
auto idx = this->index_of(value);
if (!idx.has_value()) {
ESP_LOGW(TAG, "Invalid option '%s'", value.c_str());
return;
}
optional<int64_t> mapval = this->mapping_[idx.value()];
ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, value.c_str());
void ModbusSelect::control(size_t index) {
optional<int64_t> mapval = this->mapping_[index];
const char *option = this->option_at(index);
ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, option);
std::vector<uint16_t> data;
if (this->write_transform_func_.has_value()) {
auto val = (*this->write_transform_func_)(this, value, *mapval, data);
// Transform func requires string parameter for backward compatibility
auto val = (*this->write_transform_func_)(this, std::string(option), *mapval, data);
if (val.has_value()) {
mapval = *val;
ESP_LOGV(TAG, "write_lambda returned mapping value %lld", *mapval);
@@ -85,7 +83,7 @@ void ModbusSelect::control(const std::string &value) {
this->parent_->queue_command(write_cmd);
if (this->optimistic_)
this->publish_state(value);
this->publish_state(index);
}
} // namespace modbus_controller

View File

@@ -38,7 +38,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
void dump_config() override;
void parse_and_publish(const std::vector<uint8_t> &data) override;
void control(const std::string &value) override;
void control(size_t index) override;
protected:
std::vector<int64_t> mapping_{};

View File

@@ -36,7 +36,7 @@ void MQTTAlarmControlPanelComponent::setup() {
} else if (strcasecmp(payload.c_str(), "TRIGGERED") == 0) {
call.triggered();
} else {
ESP_LOGW(TAG, "'%s': Received unknown command payload %s", this->friendly_name().c_str(), payload.c_str());
ESP_LOGW(TAG, "'%s': Received unknown command payload %s", this->friendly_name_().c_str(), payload.c_str());
}
call.perform();
});

View File

@@ -30,9 +30,12 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor
}
void MQTTBinarySensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (!this->binary_sensor_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class();
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
const auto device_class = this->binary_sensor_->get_device_class_ref();
if (!device_class.empty()) {
root[MQTT_DEVICE_CLASS] = device_class;
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
if (this->binary_sensor_->is_status_binary_sensor())
root[MQTT_PAYLOAD_ON] = mqtt::global_mqtt_client->get_availability().payload_available;
if (this->binary_sensor_->is_status_binary_sensor())

View File

@@ -20,7 +20,7 @@ void MQTTButtonComponent::setup() {
if (payload == "PRESS") {
this->button_->press();
} else {
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str());
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str());
this->status_momentary_warning("state", 5000);
}
});
@@ -33,8 +33,9 @@ void MQTTButtonComponent::dump_config() {
void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
config.state_topic = false;
if (!this->button_->get_device_class().empty()) {
root[MQTT_DEVICE_CLASS] = this->button_->get_device_class();
const auto device_class = this->button_->get_device_class_ref();
if (!device_class.empty()) {
root[MQTT_DEVICE_CLASS] = device_class;
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
}

View File

@@ -64,11 +64,11 @@ bool MQTTComponent::send_discovery_() {
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
if (discovery_info.clean) {
ESP_LOGV(TAG, "'%s': Cleaning discovery", this->friendly_name().c_str());
ESP_LOGV(TAG, "'%s': Cleaning discovery", this->friendly_name_().c_str());
return global_mqtt_client->publish(this->get_discovery_topic_(discovery_info), "", 0, this->qos_, true);
}
ESP_LOGV(TAG, "'%s': Sending discovery", this->friendly_name().c_str());
ESP_LOGV(TAG, "'%s': Sending discovery", this->friendly_name_().c_str());
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
return global_mqtt_client->publish_json(
@@ -85,12 +85,16 @@ bool MQTTComponent::send_discovery_() {
}
// Fields from EntityBase
root[MQTT_NAME] = this->get_entity()->has_own_name() ? this->friendly_name() : "";
root[MQTT_NAME] = this->get_entity()->has_own_name() ? this->friendly_name_() : "";
if (this->is_disabled_by_default())
if (this->is_disabled_by_default_())
root[MQTT_ENABLED_BY_DEFAULT] = false;
if (!this->get_icon().empty())
root[MQTT_ICON] = this->get_icon();
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
const auto icon_ref = this->get_icon_ref_();
if (!icon_ref.empty()) {
root[MQTT_ICON] = icon_ref;
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
const auto entity_category = this->get_entity()->get_entity_category();
switch (entity_category) {
@@ -122,7 +126,7 @@ bool MQTTComponent::send_discovery_() {
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
char friendly_name_hash[9];
sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name()));
sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name_()));
friendly_name_hash[8] = 0; // ensure the hash-string ends with null
root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash;
} else {
@@ -184,7 +188,7 @@ bool MQTTComponent::is_discovery_enabled() const {
}
std::string MQTTComponent::get_default_object_id_() const {
return str_sanitize(str_snake_case(this->friendly_name()));
return str_sanitize(str_snake_case(this->friendly_name_()));
}
void MQTTComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) {
@@ -268,9 +272,9 @@ void MQTTComponent::schedule_resend_state() { this->resend_state_ = true; }
bool MQTTComponent::is_connected_() const { return global_mqtt_client->is_connected(); }
// Pull these properties from EntityBase if not overridden
std::string MQTTComponent::friendly_name() const { return this->get_entity()->get_name(); }
std::string MQTTComponent::get_icon() const { return this->get_entity()->get_icon(); }
bool MQTTComponent::is_disabled_by_default() const { return this->get_entity()->is_disabled_by_default(); }
std::string MQTTComponent::friendly_name_() const { return this->get_entity()->get_name(); }
StringRef MQTTComponent::get_icon_ref_() const { return this->get_entity()->get_icon_ref(); }
bool MQTTComponent::is_disabled_by_default_() const { return this->get_entity()->is_disabled_by_default(); }
bool MQTTComponent::is_internal() {
if (this->has_custom_state_topic_) {
// If the custom state_topic is null, return true as it is internal and should not publish

View File

@@ -165,13 +165,13 @@ class MQTTComponent : public Component {
virtual const EntityBase *get_entity() const = 0;
/// Get the friendly name of this MQTT component.
virtual std::string friendly_name() const;
std::string friendly_name_() const;
/// Get the icon field of this component
virtual std::string get_icon() const;
/// Get the icon field of this component as StringRef
StringRef get_icon_ref_() const;
/// Get whether the underlying Entity is disabled by default
virtual bool is_disabled_by_default() const;
bool is_disabled_by_default_() const;
/// Get the MQTT topic that new states will be shared to.
std::string get_state_topic_() const;

View File

@@ -67,9 +67,12 @@ void MQTTCoverComponent::dump_config() {
}
}
void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (!this->cover_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class();
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
const auto device_class = this->cover_->get_device_class_ref();
if (!device_class.empty()) {
root[MQTT_DEVICE_CLASS] = device_class;
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
auto traits = this->cover_->get_traits();
if (traits.get_is_assumed_state()) {

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