mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 08:31:47 +00:00 
			
		
		
		
	Compare commits
	
		
			468 Commits
		
	
	
		
			2025.2.0b4
			...
			add-heap-t
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					fcdf209ac3 | ||
| 
						 | 
					4c1111a395 | ||
| 
						 | 
					57425a765b | ||
| 
						 | 
					abb09b7fee | ||
| 
						 | 
					b69fd2762e | ||
| 
						 | 
					a38f0067ae | ||
| 
						 | 
					00e128bdd5 | ||
| 
						 | 
					35238c1437 | ||
| 
						 | 
					8e29437900 | ||
| 
						 | 
					9e64e71cdf | ||
| 
						 | 
					ef2621aa54 | ||
| 
						 | 
					882273cb56 | ||
| 
						 | 
					ad2b74d9b4 | ||
| 
						 | 
					26669bd1b6 | ||
| 
						 | 
					54ead9a6b4 | ||
| 
						 | 
					d60e1f02c0 | ||
| 
						 | 
					213648564c | ||
| 
						 | 
					8bdbde9732 | ||
| 
						 | 
					e988762576 | ||
| 
						 | 
					75496849eb | ||
| 
						 | 
					39b119e9cc | ||
| 
						 | 
					4d43caf6c1 | ||
| 
						 | 
					ce5e1a6294 | ||
| 
						 | 
					88be14aaa3 | ||
| 
						 | 
					1ac56b06c5 | ||
| 
						 | 
					8bbc509b0b | ||
| 
						 | 
					6f35d0ac88 | ||
| 
						 | 
					3b8a5db97c | ||
| 
						 | 
					b8d83d0765 | ||
| 
						 | 
					e7a2b395fd | ||
| 
						 | 
					ad99d7fb45 | ||
| 
						 | 
					0b032e5c19 | ||
| 
						 | 
					c7523ace78 | ||
| 
						 | 
					2a6827e1d2 | ||
| 
						 | 
					125aff79ec | ||
| 
						 | 
					a31d8ec309 | ||
| 
						 | 
					3ed03edfec | ||
| 
						 | 
					4dc6cbe2d7 | ||
| 
						 | 
					524cd4b4e3 | ||
| 
						 | 
					84ebbf0762 | ||
| 
						 | 
					670ad7192c | ||
| 
						 | 
					bc6ee20270 | ||
| 
						 | 
					e869a3aec3 | ||
| 
						 | 
					8aff6d2fdd | ||
| 
						 | 
					8d33c6de36 | ||
| 
						 | 
					f4b5f32cb4 | ||
| 
						 | 
					2eb9582d0f | ||
| 
						 | 
					db97440b04 | ||
| 
						 | 
					ced7ae1d7a | ||
| 
						 | 
					d6699fa3c0 | ||
| 
						 | 
					836e5ffa43 | ||
| 
						 | 
					c7f597bc75 | ||
| 
						 | 
					e215fafebe | ||
| 
						 | 
					da9c755f67 | ||
| 
						 | 
					087ff865a7 | ||
| 
						 | 
					8cd62c0308 | ||
| 
						 | 
					f5241ff777 | ||
| 
						 | 
					1aa2b79311 | ||
| 
						 | 
					2dca2d5f85 | ||
| 
						 | 
					f03b42ced5 | ||
| 
						 | 
					0f8a0af244 | ||
| 
						 | 
					62646f5f32 | ||
| 
						 | 
					71f81d2f18 | ||
| 
						 | 
					4ec8414050 | ||
| 
						 | 
					807925fd38 | ||
| 
						 | 
					b597565165 | ||
| 
						 | 
					9a9b91b180 | ||
| 
						 | 
					9dcf295df8 | ||
| 
						 | 
					e8a3de2642 | ||
| 
						 | 
					d2b4dba51f | ||
| 
						 | 
					bf527b0331 | ||
| 
						 | 
					cdc77506de | ||
| 
						 | 
					6de6a0c82c | ||
| 
						 | 
					20062576a3 | ||
| 
						 | 
					07ba9fdf8f | ||
| 
						 | 
					caa255f5d1 | ||
| 
						 | 
					c0be2c14f3 | ||
| 
						 | 
					9f629dcaa2 | ||
| 
						 | 
					0fe6c65ba3 | ||
| 
						 | 
					c756bb3b3e | ||
| 
						 | 
					ecb91b0101 | ||
| 
						 | 
					5f9a509bdc | ||
| 
						 | 
					dc6dd9fe0d | ||
| 
						 | 
					5baa034d0d | ||
| 
						 | 
					b8ba26787e | ||
| 
						 | 
					844569e96b | ||
| 
						 | 
					43580739ac | ||
| 
						 | 
					c9f7ab6948 | ||
| 
						 | 
					7900660bb8 | ||
| 
						 | 
					f096567ac7 | ||
| 
						 | 
					5bfb5ccc34 | ||
| 
						 | 
					1c60038111 | ||
| 
						 | 
					b940db6549 | ||
| 
						 | 
					aa6e172e14 | ||
| 
						 | 
					86033b6612 | ||
| 
						 | 
					59b4a1f554 | ||
| 
						 | 
					b5bdfb3089 | ||
| 
						 | 
					a31a5e74bd | ||
| 
						 | 
					629481a526 | ||
| 
						 | 
					3291a11824 | ||
| 
						 | 
					d2ee2d3b23 | ||
| 
						 | 
					253e3ec6f6 | ||
| 
						 | 
					fdc4ec8a57 | ||
| 
						 | 
					1da0dff8b1 | ||
| 
						 | 
					38dae8489e | ||
| 
						 | 
					22c0e1079e | ||
| 
						 | 
					2d3f141140 | ||
| 
						 | 
					e49252ca3d | ||
| 
						 | 
					c9d1476ae0 | ||
| 
						 | 
					ee646d7324 | ||
| 
						 | 
					e557bca420 | ||
| 
						 | 
					adcd6517db | ||
| 
						 | 
					4c8f5275f9 | ||
| 
						 | 
					526db0102c | ||
| 
						 | 
					8a3fe9ce4c | ||
| 
						 | 
					fb97ef33a8 | ||
| 
						 | 
					805a6d85a5 | ||
| 
						 | 
					8f9fbb15b8 | ||
| 
						 | 
					3d24dea455 | ||
| 
						 | 
					666d5374ea | ||
| 
						 | 
					6792ff6d58 | ||
| 
						 | 
					f29ccb9e75 | ||
| 
						 | 
					911bd54765 | ||
| 
						 | 
					89b1b12993 | ||
| 
						 | 
					33d79e03d9 | ||
| 
						 | 
					991f3d3a10 | ||
| 
						 | 
					97823ddd16 | ||
| 
						 | 
					6ff180152a | ||
| 
						 | 
					dbb7cbed3e | ||
| 
						 | 
					fbf00f0af4 | ||
| 
						 | 
					82c6a40371 | ||
| 
						 | 
					0242ac56df | ||
| 
						 | 
					b82666002d | ||
| 
						 | 
					e11883e431 | ||
| 
						 | 
					ff5b9df607 | ||
| 
						 | 
					e5b7e3039a | ||
| 
						 | 
					31ed1eb6f0 | ||
| 
						 | 
					0c3daab649 | ||
| 
						 | 
					816371e3e9 | ||
| 
						 | 
					3c7bb65a23 | ||
| 
						 | 
					4a65fd76b3 | ||
| 
						 | 
					2704db5eef | ||
| 
						 | 
					f10bc73d31 | ||
| 
						 | 
					55e099450c | ||
| 
						 | 
					248dbd32a5 | ||
| 
						 | 
					a7b676231a | ||
| 
						 | 
					2fd5f9ac58 | ||
| 
						 | 
					ca4838a5f4 | ||
| 
						 | 
					1b72550236 | ||
| 
						 | 
					71afd49e3e | ||
| 
						 | 
					e5d718d1b1 | ||
| 
						 | 
					61f33d6401 | ||
| 
						 | 
					af9b568778 | ||
| 
						 | 
					4a1eec567f | ||
| 
						 | 
					5706b8476f | ||
| 
						 | 
					8981a86793 | ||
| 
						 | 
					a7fd6dc382 | ||
| 
						 | 
					cb0a87c1f9 | ||
| 
						 | 
					b913a0b178 | ||
| 
						 | 
					214454ff51 | ||
| 
						 | 
					3677ef71d1 | ||
| 
						 | 
					7e133171e0 | ||
| 
						 | 
					bc56d319b5 | ||
| 
						 | 
					c423a6fb61 | ||
| 
						 | 
					4034bf4f04 | ||
| 
						 | 
					477abc05ae | ||
| 
						 | 
					ff2b93a3e4 | ||
| 
						 | 
					a52d6388a9 | ||
| 
						 | 
					6259ca9ded | ||
| 
						 | 
					f6ef50505b | ||
| 
						 | 
					b4cf437761 | ||
| 
						 | 
					1d9f5f1f1e | ||
| 
						 | 
					e47489708e | ||
| 
						 | 
					8e1bdcd211 | ||
| 
						 | 
					3432d73584 | ||
| 
						 | 
					2bb86641f8 | ||
| 
						 | 
					6ca72a3a26 | ||
| 
						 | 
					c215098cb7 | ||
| 
						 | 
					566968b6be | ||
| 
						 | 
					fe51ee6257 | ||
| 
						 | 
					2c499b326a | ||
| 
						 | 
					7c4ab7abfe | ||
| 
						 | 
					3c242b7296 | ||
| 
						 | 
					00dd5b8339 | ||
| 
						 | 
					a007a8237a | ||
| 
						 | 
					9b86cc37f0 | ||
| 
						 | 
					2dfcf950fa | ||
| 
						 | 
					5908b93e82 | ||
| 
						 | 
					995db1f961 | ||
| 
						 | 
					abcc656a6f | ||
| 
						 | 
					4a9f323d92 | ||
| 
						 | 
					34a4e70cc5 | ||
| 
						 | 
					fb5d697c22 | ||
| 
						 | 
					df4642208e | ||
| 
						 | 
					264e234efc | ||
| 
						 | 
					ca78dd44b5 | ||
| 
						 | 
					7edf458898 | ||
| 
						 | 
					d9873e24a7 | ||
| 
						 | 
					645bd490ba | ||
| 
						 | 
					27f6d00e7a | ||
| 
						 | 
					f9d668eeca | ||
| 
						 | 
					92d1557efd | ||
| 
						 | 
					6b930595e2 | ||
| 
						 | 
					4a1cbfc533 | ||
| 
						 | 
					1f7a84cc8e | ||
| 
						 | 
					8c5adfb33f | ||
| 
						 | 
					399c9ba4be | ||
| 
						 | 
					a866370a2e | ||
| 
						 | 
					6240bfff97 | ||
| 
						 | 
					1c72fd4674 | ||
| 
						 | 
					2291a1dc39 | ||
| 
						 | 
					8269e2c961 | ||
| 
						 | 
					23dec912ad | ||
| 
						 | 
					9637ef35bd | ||
| 
						 | 
					23e5cdb30e | ||
| 
						 | 
					f3b1b11eba | ||
| 
						 | 
					5ceba618f6 | ||
| 
						 | 
					99d5ca3266 | ||
| 
						 | 
					219ba6152c | ||
| 
						 | 
					ef0f969604 | ||
| 
						 | 
					82adcd656f | ||
| 
						 | 
					fe35eee8df | ||
| 
						 | 
					864dd69038 | ||
| 
						 | 
					79f198ebff | ||
| 
						 | 
					4b0622aa23 | ||
| 
						 | 
					4ecc72ed54 | ||
| 
						 | 
					791740e554 | ||
| 
						 | 
					6bccc7e389 | ||
| 
						 | 
					655075e71b | ||
| 
						 | 
					1df1e3cf48 | ||
| 
						 | 
					05e52cae2b | ||
| 
						 | 
					be60d9be9b | ||
| 
						 | 
					e3eb3ee5d2 | ||
| 
						 | 
					0812b3dd70 | ||
| 
						 | 
					28a9f12595 | ||
| 
						 | 
					36b75c3faa | ||
| 
						 | 
					584c5bd5be | ||
| 
						 | 
					bc372dbeb2 | ||
| 
						 | 
					37a03de849 | ||
| 
						 | 
					c8395cdf0a | ||
| 
						 | 
					79c8a55459 | ||
| 
						 | 
					36d6fe29f2 | ||
| 
						 | 
					e1868ddecb | ||
| 
						 | 
					6151644b96 | ||
| 
						 | 
					a4914eb5b7 | ||
| 
						 | 
					57a57f0d6a | ||
| 
						 | 
					7e9f93a290 | ||
| 
						 | 
					402ada07b5 | ||
| 
						 | 
					9aa9abfc08 | ||
| 
						 | 
					4c1f83614b | ||
| 
						 | 
					d1763f9831 | ||
| 
						 | 
					c49391427f | ||
| 
						 | 
					c42343be3a | ||
| 
						 | 
					ffc233d99d | ||
| 
						 | 
					5ed0046bdd | ||
| 
						 | 
					2e16dd788c | ||
| 
						 | 
					58fe8b39b2 | ||
| 
						 | 
					ccd55a8e84 | ||
| 
						 | 
					4bb59ce1d1 | ||
| 
						 | 
					bb988604c8 | ||
| 
						 | 
					573088aadb | ||
| 
						 | 
					031b1c8bd0 | ||
| 
						 | 
					f95b2ba898 | ||
| 
						 | 
					ea4b573f9a | ||
| 
						 | 
					8fcbd57f2f | ||
| 
						 | 
					f131186e6b | ||
| 
						 | 
					20c7778524 | ||
| 
						 | 
					2d8e86324b | ||
| 
						 | 
					2dfd28ba3e | ||
| 
						 | 
					63221d7a1f | ||
| 
						 | 
					fb9a15f0af | ||
| 
						 | 
					ce2e966005 | ||
| 
						 | 
					e7d1072c85 | ||
| 
						 | 
					bc999b50b3 | ||
| 
						 | 
					6cfe3ac44d | ||
| 
						 | 
					6787730aa4 | ||
| 
						 | 
					48a7927a60 | ||
| 
						 | 
					8ea4d8402f | ||
| 
						 | 
					2c53408cfc | ||
| 
						 | 
					33dce6e522 | ||
| 
						 | 
					e213932b7c | ||
| 
						 | 
					42fb0e2809 | ||
| 
						 | 
					acce0bc45b | ||
| 
						 | 
					f5885de6f1 | ||
| 
						 | 
					17e3bb7324 | ||
| 
						 | 
					d891521ce2 | ||
| 
						 | 
					3320e4112b | ||
| 
						 | 
					c0e4701e1d | ||
| 
						 | 
					f3390ff7f5 | ||
| 
						 | 
					dfbfb2a2bb | ||
| 
						 | 
					874026ca8f | ||
| 
						 | 
					4adda632bb | ||
| 
						 | 
					6ea89644e7 | ||
| 
						 | 
					64ff62c005 | ||
| 
						 | 
					c4de9e87e4 | ||
| 
						 | 
					918924d697 | ||
| 
						 | 
					43805e6c56 | ||
| 
						 | 
					38bbfaccc6 | ||
| 
						 | 
					56b32aae11 | ||
| 
						 | 
					e2c16b4baa | ||
| 
						 | 
					10a9162f48 | ||
| 
						 | 
					fbc884772c | ||
| 
						 | 
					cbf68f1fd2 | ||
| 
						 | 
					cf227d6f32 | ||
| 
						 | 
					54e3153f27 | ||
| 
						 | 
					c2e0a01106 | ||
| 
						 | 
					d2c2439b97 | ||
| 
						 | 
					a8d33dd26a | ||
| 
						 | 
					da41a9204e | ||
| 
						 | 
					f993bb08c7 | ||
| 
						 | 
					afa481aeea | ||
| 
						 | 
					dfb162e7a6 | ||
| 
						 | 
					098921b88f | ||
| 
						 | 
					5c6368b6b8 | ||
| 
						 | 
					9bd7060f6b | ||
| 
						 | 
					fb1d178abc | ||
| 
						 | 
					90c96a0a0f | ||
| 
						 | 
					c63a545750 | ||
| 
						 | 
					89f82be4cd | ||
| 
						 | 
					c336dd9436 | ||
| 
						 | 
					fa25cebed5 | ||
| 
						 | 
					7679c716b3 | ||
| 
						 | 
					225e2585e8 | ||
| 
						 | 
					1bdf0fdc57 | ||
| 
						 | 
					4d95ff2ae0 | ||
| 
						 | 
					f36d400058 | ||
| 
						 | 
					c63cf9d151 | ||
| 
						 | 
					0a02c1461e | ||
| 
						 | 
					b3a69c6c05 | ||
| 
						 | 
					dd113f2972 | ||
| 
						 | 
					3c5a0091ee | ||
| 
						 | 
					bf65b73569 | ||
| 
						 | 
					a2b123a29a | ||
| 
						 | 
					3575f52cdf | ||
| 
						 | 
					c90185854e | ||
| 
						 | 
					7d8c39d295 | ||
| 
						 | 
					59d282489a | ||
| 
						 | 
					f9a0a63290 | ||
| 
						 | 
					00000e0ea8 | ||
| 
						 | 
					bd853e6883 | ||
| 
						 | 
					64d1d93fe0 | ||
| 
						 | 
					266c2ef337 | ||
| 
						 | 
					35199c9b96 | ||
| 
						 | 
					0a29138045 | ||
| 
						 | 
					52269305ec | ||
| 
						 | 
					04dc0ed129 | ||
| 
						 | 
					37fabd7c0a | ||
| 
						 | 
					4aa7ad1e33 | ||
| 
						 | 
					42e432754e | ||
| 
						 | 
					2379f02008 | ||
| 
						 | 
					d3145dd95b | ||
| 
						 | 
					ab77dd691b | ||
| 
						 | 
					b54c0fd60a | ||
| 
						 | 
					75d1eeeffe | ||
| 
						 | 
					10cea51739 | ||
| 
						 | 
					83e090cc7e | ||
| 
						 | 
					583f8f598a | ||
| 
						 | 
					3e9556c6c2 | ||
| 
						 | 
					83cba0d7bd | ||
| 
						 | 
					1d6d0d66dc | ||
| 
						 | 
					4ed78023b6 | ||
| 
						 | 
					323209523b | ||
| 
						 | 
					46a4f4eba9 | ||
| 
						 | 
					53fda0e96d | ||
| 
						 | 
					0350eafc1e | ||
| 
						 | 
					7b8e68c73a | ||
| 
						 | 
					db666e44a7 | ||
| 
						 | 
					903d033e0f | ||
| 
						 | 
					19d938ce48 | ||
| 
						 | 
					653318479a | ||
| 
						 | 
					2af5fd5210 | ||
| 
						 | 
					29e388b231 | ||
| 
						 | 
					d9e23fdb5c | ||
| 
						 | 
					10eacaccba | ||
| 
						 | 
					23687b2afd | ||
| 
						 | 
					f11ad9ad5b | ||
| 
						 | 
					74a25a7e76 | ||
| 
						 | 
					23e04e18f8 | ||
| 
						 | 
					aed5020a83 | ||
| 
						 | 
					476f1b701b | ||
| 
						 | 
					7c3a7b68d3 | ||
| 
						 | 
					75dc0d3fb7 | ||
| 
						 | 
					9bc4f68d87 | ||
| 
						 | 
					1029202848 | ||
| 
						 | 
					a831905bba | ||
| 
						 | 
					faffd79545 | ||
| 
						 | 
					7714147071 | ||
| 
						 | 
					4da42dedc8 | ||
| 
						 | 
					28f283d545 | ||
| 
						 | 
					3048f303c5 | ||
| 
						 | 
					63a7234767 | ||
| 
						 | 
					c19621e238 | ||
| 
						 | 
					bc96eb9d52 | ||
| 
						 | 
					7375dde39c | ||
| 
						 | 
					1b7111affb | ||
| 
						 | 
					a511926aed | ||
| 
						 | 
					6b36cb95c9 | ||
| 
						 | 
					c13174c318 | ||
| 
						 | 
					d5da341138 | ||
| 
						 | 
					8fa157581e | ||
| 
						 | 
					7114d6bdd1 | ||
| 
						 | 
					eca0c21966 | ||
| 
						 | 
					20c9c410af | ||
| 
						 | 
					79af437f48 | ||
| 
						 | 
					6e27003787 | ||
| 
						 | 
					b7b2f3e61c | ||
| 
						 | 
					9448737a92 | ||
| 
						 | 
					6f2bf4ec4c | ||
| 
						 | 
					54cea6c41e | ||
| 
						 | 
					e754d0a58b | ||
| 
						 | 
					5e44a035a3 | ||
| 
						 | 
					c424fea524 | ||
| 
						 | 
					6aba1dbd73 | ||
| 
						 | 
					422fb8f1a5 | ||
| 
						 | 
					2988bbb8ce | ||
| 
						 | 
					59299bffc8 | ||
| 
						 | 
					3410aee42e | ||
| 
						 | 
					96682f5cbe | ||
| 
						 | 
					bfa3254d6c | ||
| 
						 | 
					990d1e3bb0 | ||
| 
						 | 
					755b0bbfc7 | ||
| 
						 | 
					c281351732 | ||
| 
						 | 
					9f603a474f | ||
| 
						 | 
					bf739506c3 | ||
| 
						 | 
					3020083564 | ||
| 
						 | 
					31e90e5544 | ||
| 
						 | 
					7c9726859f | ||
| 
						 | 
					7529fb10b4 | ||
| 
						 | 
					ba79e2d7b1 | ||
| 
						 | 
					7006bd24a5 | ||
| 
						 | 
					58311c9a0d | ||
| 
						 | 
					ae65f76dfe | ||
| 
						 | 
					4d380214df | ||
| 
						 | 
					c5ebf7683e | ||
| 
						 | 
					a973adda67 | ||
| 
						 | 
					d9b419eaf5 | ||
| 
						 | 
					02bf33c548 | ||
| 
						 | 
					b3db04a3d3 | ||
| 
						 | 
					56034e3e79 | ||
| 
						 | 
					abbd72e802 | ||
| 
						 | 
					1257640e48 | ||
| 
						 | 
					2bc9782ce7 | ||
| 
						 | 
					6583e17810 | ||
| 
						 | 
					64c8bcef2e | ||
| 
						 | 
					f9da8dbfb8 | ||
| 
						 | 
					74f7197543 | ||
| 
						 | 
					c21b8bd417 | ||
| 
						 | 
					1eb658cc5b | ||
| 
						 | 
					8b251efb75 | ||
| 
						 | 
					a47e27885f | ||
| 
						 | 
					2e66b33672 | ||
| 
						 | 
					e21ef22706 | ||
| 
						 | 
					93c2878c21 | ||
| 
						 | 
					b3ad6a03e6 | ||
| 
						 | 
					143b0d3de4 | ||
| 
						 | 
					788c41e6f4 | ||
| 
						 | 
					46b6dcdfbf | ||
| 
						 | 
					d05f641dd0 | ||
| 
						 | 
					fa029e8fc7 | ||
| 
						 | 
					ace953bd50 | ||
| 
						 | 
					e190ef9e9b | ||
| 
						 | 
					2868210d46 | ||
| 
						 | 
					72f6461871 | ||
| 
						 | 
					4a95468fd2 | ||
| 
						 | 
					43319d4c8a | ||
| 
						 | 
					3b7a7a2262 | ||
| 
						 | 
					de2d21862b | ||
| 
						 | 
					ab0d38fbda | 
@@ -31,7 +31,7 @@
 | 
			
		||||
        "ms-python.python",
 | 
			
		||||
        "ms-python.pylint",
 | 
			
		||||
        "ms-python.flake8",
 | 
			
		||||
        "ms-python.black-formatter",
 | 
			
		||||
        "charliermarsh.ruff",
 | 
			
		||||
        "visualstudioexptteam.vscodeintellicode",
 | 
			
		||||
        // yaml
 | 
			
		||||
        "redhat.vscode-yaml",
 | 
			
		||||
@@ -49,14 +49,11 @@
 | 
			
		||||
        "flake8.args": [
 | 
			
		||||
          "--config=${workspaceFolder}/.flake8"
 | 
			
		||||
        ],
 | 
			
		||||
        "black-formatter.args": [
 | 
			
		||||
          "--config",
 | 
			
		||||
          "${workspaceFolder}/pyproject.toml"
 | 
			
		||||
        ],
 | 
			
		||||
        "ruff.configuration": "${workspaceFolder}/pyproject.toml",
 | 
			
		||||
        "[python]": {
 | 
			
		||||
          // VS will say "Value is not accepted" before building the devcontainer, but the warning
 | 
			
		||||
          // should go away after build is completed.
 | 
			
		||||
          "editor.defaultFormatter": "ms-python.black-formatter"
 | 
			
		||||
          "editor.defaultFormatter": "charliermarsh.ruff"
 | 
			
		||||
        },
 | 
			
		||||
        "editor.formatOnPaste": false,
 | 
			
		||||
        "editor.formatOnSave": true,
 | 
			
		||||
 
 | 
			
		||||
@@ -114,4 +114,5 @@ config/
 | 
			
		||||
examples/
 | 
			
		||||
Dockerfile
 | 
			
		||||
.git/
 | 
			
		||||
tests/build/
 | 
			
		||||
tests/
 | 
			
		||||
.*
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,4 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
# These are supported funding model platforms
 | 
			
		||||
 | 
			
		||||
custom: https://www.nabucasa.com
 | 
			
		||||
							
								
								
									
										33
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							@@ -1,15 +1,11 @@
 | 
			
		||||
name: Build Image
 | 
			
		||||
inputs:
 | 
			
		||||
  platform:
 | 
			
		||||
    description: "Platform to build for"
 | 
			
		||||
    required: true
 | 
			
		||||
    example: "linux/amd64"
 | 
			
		||||
  target:
 | 
			
		||||
    description: "Target to build"
 | 
			
		||||
    required: true
 | 
			
		||||
    example: "docker"
 | 
			
		||||
  baseimg:
 | 
			
		||||
    description: "Base image type"
 | 
			
		||||
  build_type:
 | 
			
		||||
    description: "Build type"
 | 
			
		||||
    required: true
 | 
			
		||||
    example: "docker"
 | 
			
		||||
  suffix:
 | 
			
		||||
@@ -19,6 +15,11 @@ inputs:
 | 
			
		||||
    description: "Version to build"
 | 
			
		||||
    required: true
 | 
			
		||||
    example: "2023.12.0"
 | 
			
		||||
  base_os:
 | 
			
		||||
    description: "Base OS to use"
 | 
			
		||||
    required: false
 | 
			
		||||
    default: "debian"
 | 
			
		||||
    example: "debian"
 | 
			
		||||
runs:
 | 
			
		||||
  using: "composite"
 | 
			
		||||
  steps:
 | 
			
		||||
@@ -46,52 +47,52 @@ runs:
 | 
			
		||||
 | 
			
		||||
    - name: Build and push to ghcr by digest
 | 
			
		||||
      id: build-ghcr
 | 
			
		||||
      uses: docker/build-push-action@v6.13.0
 | 
			
		||||
      uses: docker/build-push-action@v6.16.0
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_BUILD_SUMMARY: false
 | 
			
		||||
        DOCKER_BUILD_RECORD_UPLOAD: false
 | 
			
		||||
      with:
 | 
			
		||||
        context: .
 | 
			
		||||
        file: ./docker/Dockerfile
 | 
			
		||||
        platforms: ${{ inputs.platform }}
 | 
			
		||||
        target: ${{ inputs.target }}
 | 
			
		||||
        cache-from: type=gha
 | 
			
		||||
        cache-to: ${{ steps.cache-to.outputs.value }}
 | 
			
		||||
        build-args: |
 | 
			
		||||
          BASEIMGTYPE=${{ inputs.baseimg }}
 | 
			
		||||
          BUILD_TYPE=${{ inputs.build_type }}
 | 
			
		||||
          BUILD_VERSION=${{ inputs.version }}
 | 
			
		||||
          BUILD_OS=${{ inputs.base_os }}
 | 
			
		||||
        outputs: |
 | 
			
		||||
          type=image,name=ghcr.io/${{ steps.tags.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
 | 
			
		||||
 | 
			
		||||
    - name: Export ghcr digests
 | 
			
		||||
      shell: bash
 | 
			
		||||
      run: |
 | 
			
		||||
        mkdir -p /tmp/digests/${{ inputs.target }}/ghcr
 | 
			
		||||
        mkdir -p /tmp/digests/${{ inputs.build_type }}/ghcr
 | 
			
		||||
        digest="${{ steps.build-ghcr.outputs.digest }}"
 | 
			
		||||
        touch "/tmp/digests/${{ inputs.target }}/ghcr/${digest#sha256:}"
 | 
			
		||||
        touch "/tmp/digests/${{ inputs.build_type }}/ghcr/${digest#sha256:}"
 | 
			
		||||
 | 
			
		||||
    - name: Build and push to dockerhub by digest
 | 
			
		||||
      id: build-dockerhub
 | 
			
		||||
      uses: docker/build-push-action@v6.13.0
 | 
			
		||||
      uses: docker/build-push-action@v6.16.0
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_BUILD_SUMMARY: false
 | 
			
		||||
        DOCKER_BUILD_RECORD_UPLOAD: false
 | 
			
		||||
      with:
 | 
			
		||||
        context: .
 | 
			
		||||
        file: ./docker/Dockerfile
 | 
			
		||||
        platforms: ${{ inputs.platform }}
 | 
			
		||||
        target: ${{ inputs.target }}
 | 
			
		||||
        cache-from: type=gha
 | 
			
		||||
        cache-to: ${{ steps.cache-to.outputs.value }}
 | 
			
		||||
        build-args: |
 | 
			
		||||
          BASEIMGTYPE=${{ inputs.baseimg }}
 | 
			
		||||
          BUILD_TYPE=${{ inputs.build_type }}
 | 
			
		||||
          BUILD_VERSION=${{ inputs.version }}
 | 
			
		||||
          BUILD_OS=${{ inputs.base_os }}
 | 
			
		||||
        outputs: |
 | 
			
		||||
          type=image,name=docker.io/${{ steps.tags.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
 | 
			
		||||
 | 
			
		||||
    - name: Export dockerhub digests
 | 
			
		||||
      shell: bash
 | 
			
		||||
      run: |
 | 
			
		||||
        mkdir -p /tmp/digests/${{ inputs.target }}/dockerhub
 | 
			
		||||
        mkdir -p /tmp/digests/${{ inputs.build_type }}/dockerhub
 | 
			
		||||
        digest="${{ steps.build-dockerhub.outputs.digest }}"
 | 
			
		||||
        touch "/tmp/digests/${{ inputs.target }}/dockerhub/${digest#sha256:}"
 | 
			
		||||
        touch "/tmp/digests/${{ inputs.build_type }}/dockerhub/${digest#sha256:}"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							@@ -17,12 +17,12 @@ runs:
 | 
			
		||||
  steps:
 | 
			
		||||
    - name: Set up Python ${{ inputs.python-version }}
 | 
			
		||||
      id: python
 | 
			
		||||
      uses: actions/setup-python@v5.4.0
 | 
			
		||||
      uses: actions/setup-python@v5.6.0
 | 
			
		||||
      with:
 | 
			
		||||
        python-version: ${{ inputs.python-version }}
 | 
			
		||||
    - name: Restore Python virtual environment
 | 
			
		||||
      id: cache-venv
 | 
			
		||||
      uses: actions/cache/restore@v4.2.0
 | 
			
		||||
      uses: actions/cache/restore@v4.2.3
 | 
			
		||||
      with:
 | 
			
		||||
        path: venv
 | 
			
		||||
        # yamllint disable-line rule:line-length
 | 
			
		||||
@@ -34,7 +34,7 @@ runs:
 | 
			
		||||
        python -m venv venv
 | 
			
		||||
        source venv/bin/activate
 | 
			
		||||
        python --version
 | 
			
		||||
        pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
 | 
			
		||||
        pip install -r requirements.txt -r requirements_test.txt
 | 
			
		||||
        pip install -e .
 | 
			
		||||
    - name: Create Python virtual environment
 | 
			
		||||
      if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os == 'Windows'
 | 
			
		||||
@@ -43,5 +43,5 @@ runs:
 | 
			
		||||
        python -m venv venv
 | 
			
		||||
        ./venv/Scripts/activate
 | 
			
		||||
        python --version
 | 
			
		||||
        pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
 | 
			
		||||
        pip install -r requirements.txt -r requirements_test.txt
 | 
			
		||||
        pip install -e .
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							@@ -17,7 +17,6 @@ updates:
 | 
			
		||||
      docker-actions:
 | 
			
		||||
        applies-to: version-updates
 | 
			
		||||
        patterns:
 | 
			
		||||
          - "docker/setup-qemu-action"
 | 
			
		||||
          - "docker/login-action"
 | 
			
		||||
          - "docker/setup-buildx-action"
 | 
			
		||||
  - package-ecosystem: github-actions
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							@@ -23,7 +23,7 @@ jobs:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v5.4.0
 | 
			
		||||
        uses: actions/setup-python@v5.6.0
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: "3.11"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -33,22 +33,23 @@ concurrency:
 | 
			
		||||
jobs:
 | 
			
		||||
  check-docker:
 | 
			
		||||
    name: Build docker containers
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ${{ matrix.os }}
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        arch: [amd64, aarch64]
 | 
			
		||||
        build_type: ["ha-addon", "docker", "lint"]
 | 
			
		||||
        os: ["ubuntu-24.04", "ubuntu-24.04-arm"]
 | 
			
		||||
        build_type:
 | 
			
		||||
          - "ha-addon"
 | 
			
		||||
          - "docker"
 | 
			
		||||
          # - "lint"
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v5.4.0
 | 
			
		||||
        uses: actions/setup-python@v5.6.0
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: "3.9"
 | 
			
		||||
      - name: Set up Docker Buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v3.9.0
 | 
			
		||||
      - name: Set up QEMU
 | 
			
		||||
        uses: docker/setup-qemu-action@v3.4.0
 | 
			
		||||
        uses: docker/setup-buildx-action@v3.10.0
 | 
			
		||||
 | 
			
		||||
      - name: Set TAG
 | 
			
		||||
        run: |
 | 
			
		||||
@@ -58,6 +59,6 @@ jobs:
 | 
			
		||||
        run: |
 | 
			
		||||
          docker/build.py \
 | 
			
		||||
            --tag "${TAG}" \
 | 
			
		||||
            --arch "${{ matrix.arch }}" \
 | 
			
		||||
            --arch "${{ matrix.os == 'ubuntu-24.04-arm' && 'aarch64' || 'amd64' }}" \
 | 
			
		||||
            --build-type "${{ matrix.build_type }}" \
 | 
			
		||||
            build
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										27
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -39,15 +39,15 @@ jobs:
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Generate cache-key
 | 
			
		||||
        id: cache-key
 | 
			
		||||
        run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
 | 
			
		||||
        run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
 | 
			
		||||
      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
        id: python
 | 
			
		||||
        uses: actions/setup-python@v5.4.0
 | 
			
		||||
        uses: actions/setup-python@v5.6.0
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
      - name: Restore Python virtual environment
 | 
			
		||||
        id: cache-venv
 | 
			
		||||
        uses: actions/cache@v4.2.0
 | 
			
		||||
        uses: actions/cache@v4.2.3
 | 
			
		||||
        with:
 | 
			
		||||
          path: venv
 | 
			
		||||
          # yamllint disable-line rule:line-length
 | 
			
		||||
@@ -58,11 +58,11 @@ jobs:
 | 
			
		||||
          python -m venv venv
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          python --version
 | 
			
		||||
          pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
 | 
			
		||||
          pip install -r requirements.txt -r requirements_test.txt
 | 
			
		||||
          pip install -e .
 | 
			
		||||
 | 
			
		||||
  black:
 | 
			
		||||
    name: Check black
 | 
			
		||||
  ruff:
 | 
			
		||||
    name: Check ruff
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
@@ -74,10 +74,10 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
			
		||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
			
		||||
      - name: Run black
 | 
			
		||||
      - name: Run Ruff
 | 
			
		||||
        run: |
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          black --verbose esphome tests
 | 
			
		||||
          ruff format esphome tests
 | 
			
		||||
      - name: Suggested changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
        if: always()
 | 
			
		||||
@@ -165,6 +165,7 @@ jobs:
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          script/ci-custom.py
 | 
			
		||||
          script/build_codeowners.py --check
 | 
			
		||||
          script/build_language_schema.py --check
 | 
			
		||||
 | 
			
		||||
  pytest:
 | 
			
		||||
    name: Run pytest
 | 
			
		||||
@@ -220,7 +221,7 @@ jobs:
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          pytest -vv --cov-report=xml --tb=native tests
 | 
			
		||||
      - name: Upload coverage to Codecov
 | 
			
		||||
        uses: codecov/codecov-action@v5
 | 
			
		||||
        uses: codecov/codecov-action@v5.4.2
 | 
			
		||||
        with:
 | 
			
		||||
          token: ${{ secrets.CODECOV_TOKEN }}
 | 
			
		||||
 | 
			
		||||
@@ -255,7 +256,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
      - black
 | 
			
		||||
      - ruff
 | 
			
		||||
      - ci-custom
 | 
			
		||||
      - clang-format
 | 
			
		||||
      - flake8
 | 
			
		||||
@@ -303,14 +304,14 @@ jobs:
 | 
			
		||||
 | 
			
		||||
      - name: Cache platformio
 | 
			
		||||
        if: github.ref == 'refs/heads/dev'
 | 
			
		||||
        uses: actions/cache@v4.2.0
 | 
			
		||||
        uses: actions/cache@v4.2.3
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          key: platformio-${{ matrix.pio_cache_key }}
 | 
			
		||||
 | 
			
		||||
      - name: Cache platformio
 | 
			
		||||
        if: github.ref != 'refs/heads/dev'
 | 
			
		||||
        uses: actions/cache/restore@v4.2.0
 | 
			
		||||
        uses: actions/cache/restore@v4.2.3
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.platformio
 | 
			
		||||
          key: platformio-${{ matrix.pio_cache_key }}
 | 
			
		||||
@@ -482,7 +483,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-24.04
 | 
			
		||||
    needs:
 | 
			
		||||
      - common
 | 
			
		||||
      - black
 | 
			
		||||
      - ruff
 | 
			
		||||
      - ci-custom
 | 
			
		||||
      - clang-format
 | 
			
		||||
      - flake8
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							@@ -1,11 +1,11 @@
 | 
			
		||||
{
 | 
			
		||||
  "problemMatcher": [
 | 
			
		||||
    {
 | 
			
		||||
      "owner": "black",
 | 
			
		||||
      "owner": "ruff",
 | 
			
		||||
      "severity": "error",
 | 
			
		||||
      "pattern": [
 | 
			
		||||
        {
 | 
			
		||||
          "regexp": "^(.*): (Please format this file with the black formatter)",
 | 
			
		||||
          "regexp": "^(.*): (Please format this file with the ruff formatter)",
 | 
			
		||||
          "file": 1,
 | 
			
		||||
          "message": 2
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										84
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										84
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -53,7 +53,7 @@ jobs:
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v5.4.0
 | 
			
		||||
        uses: actions/setup-python@v5.6.0
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: "3.x"
 | 
			
		||||
      - name: Set up python environment
 | 
			
		||||
@@ -68,39 +68,39 @@ jobs:
 | 
			
		||||
        uses: pypa/gh-action-pypi-publish@v1.12.4
 | 
			
		||||
 | 
			
		||||
  deploy-docker:
 | 
			
		||||
    name: Build ESPHome ${{ matrix.platform }}
 | 
			
		||||
    name: Build ESPHome ${{ matrix.platform.arch }}
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    permissions:
 | 
			
		||||
      contents: read
 | 
			
		||||
      packages: write
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    runs-on: ${{ matrix.platform.os }}
 | 
			
		||||
    needs: [init]
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        platform:
 | 
			
		||||
          - linux/amd64
 | 
			
		||||
          - linux/arm64
 | 
			
		||||
          - arch: amd64
 | 
			
		||||
            os: "ubuntu-24.04"
 | 
			
		||||
          - arch: arm64
 | 
			
		||||
            os: "ubuntu-24.04-arm"
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
      - name: Set up Python
 | 
			
		||||
        uses: actions/setup-python@v5.4.0
 | 
			
		||||
        uses: actions/setup-python@v5.6.0
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: "3.9"
 | 
			
		||||
 | 
			
		||||
      - name: Set up Docker Buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v3.9.0
 | 
			
		||||
      - name: Set up QEMU
 | 
			
		||||
        if: matrix.platform != 'linux/amd64'
 | 
			
		||||
        uses: docker/setup-qemu-action@v3.4.0
 | 
			
		||||
        uses: docker/setup-buildx-action@v3.10.0
 | 
			
		||||
 | 
			
		||||
      - name: Log in to docker hub
 | 
			
		||||
        uses: docker/login-action@v3.3.0
 | 
			
		||||
        uses: docker/login-action@v3.4.0
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
      - name: Log in to the GitHub container registry
 | 
			
		||||
        uses: docker/login-action@v3.3.0
 | 
			
		||||
        uses: docker/login-action@v3.4.0
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
@@ -109,45 +109,36 @@ jobs:
 | 
			
		||||
      - name: Build docker
 | 
			
		||||
        uses: ./.github/actions/build-image
 | 
			
		||||
        with:
 | 
			
		||||
          platform: ${{ matrix.platform }}
 | 
			
		||||
          target: docker
 | 
			
		||||
          baseimg: docker
 | 
			
		||||
          target: final
 | 
			
		||||
          build_type: docker
 | 
			
		||||
          suffix: ""
 | 
			
		||||
          version: ${{ needs.init.outputs.tag }}
 | 
			
		||||
 | 
			
		||||
      - name: Build ha-addon
 | 
			
		||||
        uses: ./.github/actions/build-image
 | 
			
		||||
        with:
 | 
			
		||||
          platform: ${{ matrix.platform }}
 | 
			
		||||
          target: hassio
 | 
			
		||||
          baseimg: hassio
 | 
			
		||||
          target: final
 | 
			
		||||
          build_type: ha-addon
 | 
			
		||||
          suffix: "hassio"
 | 
			
		||||
          version: ${{ needs.init.outputs.tag }}
 | 
			
		||||
 | 
			
		||||
      - name: Build lint
 | 
			
		||||
        uses: ./.github/actions/build-image
 | 
			
		||||
        with:
 | 
			
		||||
          platform: ${{ matrix.platform }}
 | 
			
		||||
          target: lint
 | 
			
		||||
          baseimg: docker
 | 
			
		||||
          suffix: lint
 | 
			
		||||
          version: ${{ needs.init.outputs.tag }}
 | 
			
		||||
 | 
			
		||||
      - name: Sanitize platform name
 | 
			
		||||
        id: sanitize
 | 
			
		||||
        run: |
 | 
			
		||||
          echo "${{ matrix.platform }}" | sed 's|/|-|g' > /tmp/platform
 | 
			
		||||
          echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
 | 
			
		||||
      # - name: Build lint
 | 
			
		||||
      #   uses: ./.github/actions/build-image
 | 
			
		||||
      #   with:
 | 
			
		||||
      #     target: lint
 | 
			
		||||
      #     build_type: lint
 | 
			
		||||
      #     suffix: lint
 | 
			
		||||
      #     version: ${{ needs.init.outputs.tag }}
 | 
			
		||||
 | 
			
		||||
      - name: Upload digests
 | 
			
		||||
        uses: actions/upload-artifact@v4.6.0
 | 
			
		||||
        uses: actions/upload-artifact@v4.6.2
 | 
			
		||||
        with:
 | 
			
		||||
          name: digests-${{ steps.sanitize.outputs.name }}
 | 
			
		||||
          name: digests-${{ matrix.platform.arch }}
 | 
			
		||||
          path: /tmp/digests
 | 
			
		||||
          retention-days: 1
 | 
			
		||||
 | 
			
		||||
  deploy-manifest:
 | 
			
		||||
    name: Publish ESPHome ${{ matrix.image.title }} to ${{ matrix.registry }}
 | 
			
		||||
    name: Publish ESPHome ${{ matrix.image.build_type }} to ${{ matrix.registry }}
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs:
 | 
			
		||||
      - init
 | 
			
		||||
@@ -160,15 +151,12 @@ jobs:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        image:
 | 
			
		||||
          - title: "ha-addon"
 | 
			
		||||
            target: "hassio"
 | 
			
		||||
            suffix: "hassio"
 | 
			
		||||
          - title: "docker"
 | 
			
		||||
            target: "docker"
 | 
			
		||||
          - build_type: "docker"
 | 
			
		||||
            suffix: ""
 | 
			
		||||
          - title: "lint"
 | 
			
		||||
            target: "lint"
 | 
			
		||||
            suffix: "lint"
 | 
			
		||||
          - build_type: "ha-addon"
 | 
			
		||||
            suffix: "hassio"
 | 
			
		||||
            # - build_type: "lint"
 | 
			
		||||
            #   suffix: "lint"
 | 
			
		||||
        registry:
 | 
			
		||||
          - ghcr
 | 
			
		||||
          - dockerhub
 | 
			
		||||
@@ -176,24 +164,24 @@ jobs:
 | 
			
		||||
      - uses: actions/checkout@v4.1.7
 | 
			
		||||
 | 
			
		||||
      - name: Download digests
 | 
			
		||||
        uses: actions/download-artifact@v4.1.8
 | 
			
		||||
        uses: actions/download-artifact@v4.3.0
 | 
			
		||||
        with:
 | 
			
		||||
          pattern: digests-*
 | 
			
		||||
          path: /tmp/digests
 | 
			
		||||
          merge-multiple: true
 | 
			
		||||
 | 
			
		||||
      - name: Set up Docker Buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v3.9.0
 | 
			
		||||
        uses: docker/setup-buildx-action@v3.10.0
 | 
			
		||||
 | 
			
		||||
      - name: Log in to docker hub
 | 
			
		||||
        if: matrix.registry == 'dockerhub'
 | 
			
		||||
        uses: docker/login-action@v3.3.0
 | 
			
		||||
        uses: docker/login-action@v3.4.0
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
      - name: Log in to the GitHub container registry
 | 
			
		||||
        if: matrix.registry == 'ghcr'
 | 
			
		||||
        uses: docker/login-action@v3.3.0
 | 
			
		||||
        uses: docker/login-action@v3.4.0
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
@@ -212,7 +200,7 @@ jobs:
 | 
			
		||||
          done
 | 
			
		||||
 | 
			
		||||
      - name: Create manifest list and push
 | 
			
		||||
        working-directory: /tmp/digests/${{ matrix.image.target }}/${{ matrix.registry }}
 | 
			
		||||
        working-directory: /tmp/digests/${{ matrix.image.build_type }}/${{ matrix.registry }}
 | 
			
		||||
        run: |
 | 
			
		||||
          docker buildx imagetools create $(jq -Rcnr 'inputs | . / "," | map("-t " + .) | join(" ")' <<< "${{ steps.tags.outputs.tags}}") \
 | 
			
		||||
            $(printf '${{ steps.tags.outputs.image }}@sha256:%s ' *)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							@@ -22,7 +22,7 @@ jobs:
 | 
			
		||||
          path: lib/home-assistant
 | 
			
		||||
 | 
			
		||||
      - name: Setup Python
 | 
			
		||||
        uses: actions/setup-python@v5.4.0
 | 
			
		||||
        uses: actions/setup-python@v5.6.0
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: 3.12
 | 
			
		||||
 | 
			
		||||
@@ -36,11 +36,11 @@ jobs:
 | 
			
		||||
          python ./script/sync-device_class.py
 | 
			
		||||
 | 
			
		||||
      - name: Commit changes
 | 
			
		||||
        uses: peter-evans/create-pull-request@v7.0.6
 | 
			
		||||
        uses: peter-evans/create-pull-request@v7.0.8
 | 
			
		||||
        with:
 | 
			
		||||
          commit-message: "Synchronise Device Classes from Home Assistant"
 | 
			
		||||
          committer: esphomebot <esphome@nabucasa.com>
 | 
			
		||||
          author: esphomebot <esphome@nabucasa.com>
 | 
			
		||||
          committer: esphomebot <esphome@openhomefoundation.org>
 | 
			
		||||
          author: esphomebot <esphome@openhomefoundation.org>
 | 
			
		||||
          branch: sync/device-classes
 | 
			
		||||
          delete-branch: true
 | 
			
		||||
          title: "Synchronise Device Classes from Home Assistant"
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
repos:
 | 
			
		||||
  - repo: https://github.com/astral-sh/ruff-pre-commit
 | 
			
		||||
    # Ruff version.
 | 
			
		||||
    rev: v0.5.4
 | 
			
		||||
    rev: v0.11.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      # Run the linter.
 | 
			
		||||
      - id: ruff
 | 
			
		||||
@@ -12,11 +12,11 @@ repos:
 | 
			
		||||
      # Run the formatter.
 | 
			
		||||
      - id: ruff-format
 | 
			
		||||
  - repo: https://github.com/PyCQA/flake8
 | 
			
		||||
    rev: 6.1.0
 | 
			
		||||
    rev: 7.2.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: flake8
 | 
			
		||||
        additional_dependencies:
 | 
			
		||||
          - flake8-docstrings==1.5.0
 | 
			
		||||
          - flake8-docstrings==1.7.0
 | 
			
		||||
          - pydocstyle==5.1.1
 | 
			
		||||
        files: ^(esphome|tests)/.+\.py$
 | 
			
		||||
  - repo: https://github.com/pre-commit/pre-commit-hooks
 | 
			
		||||
@@ -33,7 +33,7 @@ repos:
 | 
			
		||||
      - id: pyupgrade
 | 
			
		||||
        args: [--py39-plus]
 | 
			
		||||
  - repo: https://github.com/adrienverge/yamllint.git
 | 
			
		||||
    rev: v1.35.1
 | 
			
		||||
    rev: v1.37.1
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: yamllint
 | 
			
		||||
  - repo: https://github.com/pre-commit/mirrors-clang-format
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								CODEOWNERS
									
									
									
									
									
								
							@@ -93,10 +93,12 @@ esphome/components/captive_portal/* @OttoWinter
 | 
			
		||||
esphome/components/ccs811/* @habbie
 | 
			
		||||
esphome/components/cd74hc4067/* @asoehlke
 | 
			
		||||
esphome/components/ch422g/* @clydebarrow @jesterret
 | 
			
		||||
esphome/components/chsc6x/* @kkosik20
 | 
			
		||||
esphome/components/climate/* @esphome/core
 | 
			
		||||
esphome/components/climate_ir/* @glmnet
 | 
			
		||||
esphome/components/color_temperature/* @jesserockz
 | 
			
		||||
esphome/components/combination/* @Cat-Ion @kahrendt
 | 
			
		||||
esphome/components/const/* @esphome/core
 | 
			
		||||
esphome/components/coolix/* @glmnet
 | 
			
		||||
esphome/components/copy/* @OttoWinter
 | 
			
		||||
esphome/components/cover/* @esphome/core
 | 
			
		||||
@@ -234,6 +236,7 @@ esphome/components/kuntze/* @ssieb
 | 
			
		||||
esphome/components/lcd_menu/* @numo68
 | 
			
		||||
esphome/components/ld2410/* @regevbr @sebcaps
 | 
			
		||||
esphome/components/ld2420/* @descipher
 | 
			
		||||
esphome/components/ld2450/* @hareeshmu
 | 
			
		||||
esphome/components/ledc/* @OttoWinter
 | 
			
		||||
esphome/components/libretiny/* @kuba2k2
 | 
			
		||||
esphome/components/libretiny_pwm/* @kuba2k2
 | 
			
		||||
@@ -248,6 +251,7 @@ esphome/components/ltr501/* @latonita
 | 
			
		||||
esphome/components/ltr_als_ps/* @latonita
 | 
			
		||||
esphome/components/lvgl/* @clydebarrow
 | 
			
		||||
esphome/components/m5stack_8angle/* @rnauber
 | 
			
		||||
esphome/components/mapping/* @clydebarrow
 | 
			
		||||
esphome/components/matrix_keypad/* @ssieb
 | 
			
		||||
esphome/components/max17043/* @blacknell
 | 
			
		||||
esphome/components/max31865/* @DAVe3283
 | 
			
		||||
@@ -264,6 +268,7 @@ esphome/components/mcp23x17_base/* @jesserockz
 | 
			
		||||
esphome/components/mcp23xxx_base/* @jesserockz
 | 
			
		||||
esphome/components/mcp2515/* @danielschramm @mvturnho
 | 
			
		||||
esphome/components/mcp3204/* @rsumner
 | 
			
		||||
esphome/components/mcp4461/* @p1ngb4ck
 | 
			
		||||
esphome/components/mcp4728/* @berfenger
 | 
			
		||||
esphome/components/mcp47a1/* @jesserockz
 | 
			
		||||
esphome/components/mcp9600/* @mreditor97
 | 
			
		||||
@@ -273,7 +278,7 @@ esphome/components/mdns/* @esphome/core
 | 
			
		||||
esphome/components/media_player/* @jesserockz
 | 
			
		||||
esphome/components/micro_wake_word/* @jesserockz @kahrendt
 | 
			
		||||
esphome/components/micronova/* @jorre05
 | 
			
		||||
esphome/components/microphone/* @jesserockz
 | 
			
		||||
esphome/components/microphone/* @jesserockz @kahrendt
 | 
			
		||||
esphome/components/mics_4514/* @jesserockz
 | 
			
		||||
esphome/components/midea/* @dudanov
 | 
			
		||||
esphome/components/midea_ir/* @dudanov
 | 
			
		||||
@@ -297,6 +302,7 @@ esphome/components/mopeka_std_check/* @Fabian-Schmidt
 | 
			
		||||
esphome/components/mpl3115a2/* @kbickar
 | 
			
		||||
esphome/components/mpu6886/* @fabaff
 | 
			
		||||
esphome/components/ms8607/* @e28eta
 | 
			
		||||
esphome/components/msa3xx/* @latonita
 | 
			
		||||
esphome/components/nau7802/* @cujomalainey
 | 
			
		||||
esphome/components/network/* @esphome/core
 | 
			
		||||
esphome/components/nextion/* @edwardtfn @senexcrenshaw
 | 
			
		||||
@@ -313,6 +319,7 @@ esphome/components/online_image/* @clydebarrow @guillempages
 | 
			
		||||
esphome/components/opentherm/* @olegtarasov
 | 
			
		||||
esphome/components/ota/* @esphome/core
 | 
			
		||||
esphome/components/output/* @esphome/core
 | 
			
		||||
esphome/components/packet_transport/* @clydebarrow
 | 
			
		||||
esphome/components/pca6416a/* @Mat931
 | 
			
		||||
esphome/components/pca9554/* @clydebarrow @hwstar
 | 
			
		||||
esphome/components/pcf85063/* @brogon
 | 
			
		||||
@@ -320,7 +327,9 @@ esphome/components/pcf8563/* @KoenBreeman
 | 
			
		||||
esphome/components/pid/* @OttoWinter
 | 
			
		||||
esphome/components/pipsolar/* @andreashergert1984
 | 
			
		||||
esphome/components/pm1006/* @habbie
 | 
			
		||||
esphome/components/pm2005/* @andrewjswan
 | 
			
		||||
esphome/components/pmsa003i/* @sjtrny
 | 
			
		||||
esphome/components/pmsx003/* @ximex
 | 
			
		||||
esphome/components/pmwcs3/* @SeByDocKy
 | 
			
		||||
esphome/components/pn532/* @OttoWinter @jesserockz
 | 
			
		||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
 | 
			
		||||
@@ -420,6 +429,7 @@ esphome/components/sun/* @OttoWinter
 | 
			
		||||
esphome/components/sun_gtil2/* @Mat931
 | 
			
		||||
esphome/components/switch/* @esphome/core
 | 
			
		||||
esphome/components/switch/binary_sensor/* @ssieb
 | 
			
		||||
esphome/components/syslog/* @clydebarrow
 | 
			
		||||
esphome/components/t6615/* @tylermenezes
 | 
			
		||||
esphome/components/tc74/* @sethgirvan
 | 
			
		||||
esphome/components/tca9548a/* @andreashergert1984
 | 
			
		||||
@@ -445,6 +455,7 @@ esphome/components/tmp102/* @timsavage
 | 
			
		||||
esphome/components/tmp1075/* @sybrenstuvel
 | 
			
		||||
esphome/components/tmp117/* @Azimath
 | 
			
		||||
esphome/components/tof10120/* @wstrzalka
 | 
			
		||||
esphome/components/tormatic/* @ti-mo
 | 
			
		||||
esphome/components/toshiba/* @kbx81
 | 
			
		||||
esphome/components/touchscreen/* @jesserockz @nielsnl68
 | 
			
		||||
esphome/components/tsl2591/* @wjcarpenter
 | 
			
		||||
@@ -458,6 +469,7 @@ esphome/components/tuya/switch/* @jesserockz
 | 
			
		||||
esphome/components/tuya/text_sensor/* @dentra
 | 
			
		||||
esphome/components/uart/* @esphome/core
 | 
			
		||||
esphome/components/uart/button/* @ssieb
 | 
			
		||||
esphome/components/uart/packet_transport/* @clydebarrow
 | 
			
		||||
esphome/components/udp/* @clydebarrow
 | 
			
		||||
esphome/components/ufire_ec/* @pvizeli
 | 
			
		||||
esphome/components/ufire_ise/* @pvizeli
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
 | 
			
		||||
 | 
			
		||||
## Enforcement
 | 
			
		||||
 | 
			
		||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at esphome@nabucasa.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
 | 
			
		||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at esphome@openhomefoundation.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
 | 
			
		||||
 | 
			
		||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,131 +1,54 @@
 | 
			
		||||
# Build these with the build.py script
 | 
			
		||||
# Example:
 | 
			
		||||
#   python3 docker/build.py --tag dev --arch amd64 --build-type docker build
 | 
			
		||||
ARG BUILD_VERSION=dev
 | 
			
		||||
ARG BUILD_OS=alpine
 | 
			
		||||
ARG BUILD_BASE_VERSION=2025.04.0
 | 
			
		||||
ARG BUILD_TYPE=docker
 | 
			
		||||
 | 
			
		||||
# One of "docker", "hassio"
 | 
			
		||||
ARG BASEIMGTYPE=docker
 | 
			
		||||
FROM ghcr.io/esphome/docker-base:${BUILD_OS}-${BUILD_BASE_VERSION} AS base-source-docker
 | 
			
		||||
FROM ghcr.io/esphome/docker-base:${BUILD_OS}-ha-addon-${BUILD_BASE_VERSION} AS base-source-ha-addon
 | 
			
		||||
 | 
			
		||||
ARG BUILD_TYPE
 | 
			
		||||
FROM base-source-${BUILD_TYPE} AS base
 | 
			
		||||
 | 
			
		||||
# https://github.com/hassio-addons/addon-debian-base/releases
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base:7.2.0 AS base-hassio
 | 
			
		||||
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bookworm
 | 
			
		||||
FROM debian:12.2-slim AS base-docker
 | 
			
		||||
RUN git config --system --add safe.directory "*"
 | 
			
		||||
 | 
			
		||||
FROM base-${BASEIMGTYPE} AS base
 | 
			
		||||
RUN pip install uv==0.6.14
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ARG TARGETARCH
 | 
			
		||||
ARG TARGETVARIANT
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Note that --break-system-packages is used below because
 | 
			
		||||
# https://peps.python.org/pep-0668/ added a safety check that prevents
 | 
			
		||||
# installing packages with the same name as a system package. This is
 | 
			
		||||
# not a problem for us because we are not concerned about overwriting
 | 
			
		||||
# system packages because we are running in an isolated container.
 | 
			
		||||
COPY requirements.txt /
 | 
			
		||||
 | 
			
		||||
RUN \
 | 
			
		||||
    apt-get update \
 | 
			
		||||
    # Use pinned versions so that we get updates with build caching
 | 
			
		||||
    && apt-get install -y --no-install-recommends \
 | 
			
		||||
        python3-pip=23.0.1+dfsg-1 \
 | 
			
		||||
        python3-setuptools=66.1.1-1+deb12u1 \
 | 
			
		||||
        python3-venv=3.11.2-1+b1 \
 | 
			
		||||
        python3-wheel=0.38.4-2 \
 | 
			
		||||
        iputils-ping=3:20221126-1+deb12u1 \
 | 
			
		||||
        git=1:2.39.5-0+deb12u1 \
 | 
			
		||||
        curl=7.88.1-10+deb12u8 \
 | 
			
		||||
        openssh-client=1:9.2p1-2+deb12u3 \
 | 
			
		||||
        python3-cffi=1.15.1-5 \
 | 
			
		||||
        libcairo2=1.16.0-7 \
 | 
			
		||||
        libmagic1=1:5.44-3 \
 | 
			
		||||
        patch=2.7.6-7 \
 | 
			
		||||
    && rm -rf \
 | 
			
		||||
        /tmp/* \
 | 
			
		||||
        /var/{cache,log}/* \
 | 
			
		||||
        /var/lib/apt/lists/*
 | 
			
		||||
 | 
			
		||||
ENV \
 | 
			
		||||
  # Fix click python3 lang warning https://click.palletsprojects.com/en/7.x/python3/
 | 
			
		||||
  LANG=C.UTF-8 LC_ALL=C.UTF-8 \
 | 
			
		||||
  # Store globally installed pio libs in /piolibs
 | 
			
		||||
  PLATFORMIO_GLOBALLIB_DIR=/piolibs
 | 
			
		||||
    uv pip install --no-cache-dir \
 | 
			
		||||
    -r /requirements.txt
 | 
			
		||||
 | 
			
		||||
RUN \
 | 
			
		||||
    pip3 install \
 | 
			
		||||
    --break-system-packages --no-cache-dir \
 | 
			
		||||
    # Keep platformio version in sync with requirements.txt
 | 
			
		||||
    platformio==6.1.16 \
 | 
			
		||||
    # Change some platformio settings
 | 
			
		||||
    && platformio settings set enable_telemetry No \
 | 
			
		||||
    platformio settings set enable_telemetry No \
 | 
			
		||||
    && platformio settings set check_platformio_interval 1000000 \
 | 
			
		||||
    && mkdir -p /piolibs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# First install requirements to leverage caching when requirements don't change
 | 
			
		||||
# tmpfs is for https://github.com/rust-lang/cargo/issues/8719
 | 
			
		||||
 | 
			
		||||
COPY requirements.txt requirements_optional.txt /
 | 
			
		||||
RUN --mount=type=tmpfs,target=/root/.cargo <<END-OF-RUN
 | 
			
		||||
# Fail on any non-zero status
 | 
			
		||||
set -e
 | 
			
		||||
 | 
			
		||||
# install build tools in case wheels are not available
 | 
			
		||||
BUILD_DEPS="
 | 
			
		||||
    build-essential=12.9
 | 
			
		||||
    python3-dev=3.11.2-1+b1
 | 
			
		||||
    zlib1g-dev=1:1.2.13.dfsg-1
 | 
			
		||||
    libjpeg-dev=1:2.1.5-2
 | 
			
		||||
    libfreetype-dev=2.12.1+dfsg-5+deb12u3
 | 
			
		||||
    libssl-dev=3.0.15-1~deb12u1
 | 
			
		||||
    libffi-dev=3.4.4-1
 | 
			
		||||
    cargo=0.66.0+ds1-1
 | 
			
		||||
    pkg-config=1.8.1-1
 | 
			
		||||
"
 | 
			
		||||
LIB_DEPS="
 | 
			
		||||
    libtiff6=4.5.0-6+deb12u1
 | 
			
		||||
    libopenjp2-7=2.5.0-2
 | 
			
		||||
"
 | 
			
		||||
if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ]
 | 
			
		||||
then
 | 
			
		||||
    apt-get update
 | 
			
		||||
    apt-get install -y --no-install-recommends $BUILD_DEPS $LIB_DEPS
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo
 | 
			
		||||
pip3 install --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt
 | 
			
		||||
 | 
			
		||||
if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ]
 | 
			
		||||
then
 | 
			
		||||
    apt-get remove -y --purge --auto-remove $BUILD_DEPS
 | 
			
		||||
    rm -rf /tmp/* /var/{cache,log}/* /var/lib/apt/lists/*
 | 
			
		||||
fi
 | 
			
		||||
END-OF-RUN
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
COPY script/platformio_install_deps.py platformio.ini /
 | 
			
		||||
RUN /platformio_install_deps.py /platformio.ini --libraries
 | 
			
		||||
 | 
			
		||||
# Avoid unsafe git error when container user and file config volume permissions don't match
 | 
			
		||||
RUN git config --system --add safe.directory '*'
 | 
			
		||||
ARG BUILD_VERSION
 | 
			
		||||
 | 
			
		||||
LABEL \
 | 
			
		||||
    org.opencontainers.image.authors="The ESPHome Authors" \
 | 
			
		||||
    org.opencontainers.image.title="ESPHome" \
 | 
			
		||||
    org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
 | 
			
		||||
    org.opencontainers.image.url="https://esphome.io/" \
 | 
			
		||||
    org.opencontainers.image.documentation="https://esphome.io/" \
 | 
			
		||||
    org.opencontainers.image.source="https://github.com/esphome/esphome" \
 | 
			
		||||
    org.opencontainers.image.licenses="ESPHome" \
 | 
			
		||||
    org.opencontainers.image.version=${BUILD_VERSION}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ======================= docker-type image =======================
 | 
			
		||||
FROM base AS docker
 | 
			
		||||
 | 
			
		||||
# Copy esphome and install
 | 
			
		||||
COPY . /esphome
 | 
			
		||||
RUN pip3 install --break-system-packages --no-cache-dir -e /esphome
 | 
			
		||||
 | 
			
		||||
# Settings for dashboard
 | 
			
		||||
ENV USERNAME="" PASSWORD=""
 | 
			
		||||
FROM base AS base-docker
 | 
			
		||||
 | 
			
		||||
# Expose the dashboard to Docker
 | 
			
		||||
EXPOSE 6052
 | 
			
		||||
 | 
			
		||||
# Run healthcheck (heartbeat)
 | 
			
		||||
HEALTHCHECK --interval=30s --timeout=30s \
 | 
			
		||||
  CMD curl --fail http://localhost:6052/version -A "HealthCheck" || exit 1
 | 
			
		||||
    CMD curl --fail http://localhost:6052/version -A "HealthCheck" || exit 1
 | 
			
		||||
 | 
			
		||||
COPY docker/docker_entrypoint.sh /entrypoint.sh
 | 
			
		||||
 | 
			
		||||
@@ -139,43 +62,13 @@ ENTRYPOINT ["/entrypoint.sh"]
 | 
			
		||||
CMD ["dashboard", "/config"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ARG BUILD_VERSION=dev
 | 
			
		||||
 | 
			
		||||
# Labels
 | 
			
		||||
LABEL \
 | 
			
		||||
    org.opencontainers.image.authors="The ESPHome Authors" \
 | 
			
		||||
    org.opencontainers.image.title="ESPHome" \
 | 
			
		||||
    org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
 | 
			
		||||
    org.opencontainers.image.url="https://esphome.io/" \
 | 
			
		||||
    org.opencontainers.image.documentation="https://esphome.io/" \
 | 
			
		||||
    org.opencontainers.image.source="https://github.com/esphome/esphome" \
 | 
			
		||||
    org.opencontainers.image.licenses="ESPHome" \
 | 
			
		||||
    org.opencontainers.image.version=${BUILD_VERSION}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ======================= hassio-type image =======================
 | 
			
		||||
FROM base AS hassio
 | 
			
		||||
 | 
			
		||||
RUN \
 | 
			
		||||
    apt-get update \
 | 
			
		||||
    # Use pinned versions so that we get updates with build caching
 | 
			
		||||
    && apt-get install -y --no-install-recommends \
 | 
			
		||||
        nginx-light=1.22.1-9 \
 | 
			
		||||
    && rm -rf \
 | 
			
		||||
        /tmp/* \
 | 
			
		||||
        /var/{cache,log}/* \
 | 
			
		||||
        /var/lib/apt/lists/*
 | 
			
		||||
 | 
			
		||||
ARG BUILD_VERSION=dev
 | 
			
		||||
# ======================= ha-addon-type image =======================
 | 
			
		||||
FROM base AS base-ha-addon
 | 
			
		||||
 | 
			
		||||
# Copy root filesystem
 | 
			
		||||
COPY docker/ha-addon-rootfs/ /
 | 
			
		||||
 | 
			
		||||
# Copy esphome and install
 | 
			
		||||
COPY . /esphome
 | 
			
		||||
RUN pip3 install --break-system-packages --no-cache-dir -e /esphome
 | 
			
		||||
 | 
			
		||||
# Labels
 | 
			
		||||
ARG BUILD_VERSION
 | 
			
		||||
LABEL \
 | 
			
		||||
    io.hass.name="ESPHome" \
 | 
			
		||||
    io.hass.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
 | 
			
		||||
@@ -183,35 +76,9 @@ LABEL \
 | 
			
		||||
    io.hass.version="${BUILD_VERSION}"
 | 
			
		||||
    # io.hass.arch is inherited from addon-debian-base
 | 
			
		||||
 | 
			
		||||
ARG BUILD_TYPE
 | 
			
		||||
FROM base-${BUILD_TYPE} AS final
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ======================= lint-type image =======================
 | 
			
		||||
FROM base AS lint
 | 
			
		||||
 | 
			
		||||
ENV \
 | 
			
		||||
  PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
 | 
			
		||||
 | 
			
		||||
RUN \
 | 
			
		||||
    curl -L https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc \
 | 
			
		||||
    && echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" > /etc/apt/sources.list.d/llvm.sources.list \
 | 
			
		||||
    && apt-get update \
 | 
			
		||||
    # Use pinned versions so that we get updates with build caching
 | 
			
		||||
    && apt-get install -y --no-install-recommends \
 | 
			
		||||
        clang-format-13=1:13.0.1-11+b2 \
 | 
			
		||||
        patch=2.7.6-7 \
 | 
			
		||||
        software-properties-common=0.99.30-4.1~deb12u1 \
 | 
			
		||||
        nano=7.2-1+deb12u1 \
 | 
			
		||||
        build-essential=12.9 \
 | 
			
		||||
        python3-dev=3.11.2-1+b1 \
 | 
			
		||||
        clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 \
 | 
			
		||||
    && rm -rf \
 | 
			
		||||
        /tmp/* \
 | 
			
		||||
        /var/{cache,log}/* \
 | 
			
		||||
        /var/lib/apt/lists/*
 | 
			
		||||
 | 
			
		||||
COPY requirements_test.txt /
 | 
			
		||||
RUN pip3 install --break-system-packages --no-cache-dir -r /requirements_test.txt
 | 
			
		||||
 | 
			
		||||
VOLUME ["/esphome"]
 | 
			
		||||
WORKDIR /esphome
 | 
			
		||||
# Copy esphome and install
 | 
			
		||||
COPY . /esphome
 | 
			
		||||
RUN uv pip install --no-cache-dir -e /esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ manifest_parser = subparsers.add_parser(
 | 
			
		||||
class DockerParams:
 | 
			
		||||
    build_to: str
 | 
			
		||||
    manifest_to: str
 | 
			
		||||
    baseimgtype: str
 | 
			
		||||
    build_type: str
 | 
			
		||||
    platform: str
 | 
			
		||||
    target: str
 | 
			
		||||
 | 
			
		||||
@@ -66,24 +66,19 @@ class DockerParams:
 | 
			
		||||
            TYPE_LINT: "esphome/esphome-lint",
 | 
			
		||||
        }[build_type]
 | 
			
		||||
        build_to = f"{prefix}-{arch}"
 | 
			
		||||
        baseimgtype = {
 | 
			
		||||
            TYPE_DOCKER: "docker",
 | 
			
		||||
            TYPE_HA_ADDON: "hassio",
 | 
			
		||||
            TYPE_LINT: "docker",
 | 
			
		||||
        }[build_type]
 | 
			
		||||
        platform = {
 | 
			
		||||
            ARCH_AMD64: "linux/amd64",
 | 
			
		||||
            ARCH_AARCH64: "linux/arm64",
 | 
			
		||||
        }[arch]
 | 
			
		||||
        target = {
 | 
			
		||||
            TYPE_DOCKER: "docker",
 | 
			
		||||
            TYPE_HA_ADDON: "hassio",
 | 
			
		||||
            TYPE_DOCKER: "final",
 | 
			
		||||
            TYPE_HA_ADDON: "final",
 | 
			
		||||
            TYPE_LINT: "lint",
 | 
			
		||||
        }[build_type]
 | 
			
		||||
        return cls(
 | 
			
		||||
            build_to=build_to,
 | 
			
		||||
            manifest_to=prefix,
 | 
			
		||||
            baseimgtype=baseimgtype,
 | 
			
		||||
            build_type=build_type,
 | 
			
		||||
            platform=platform,
 | 
			
		||||
            target=target,
 | 
			
		||||
        )
 | 
			
		||||
@@ -145,7 +140,7 @@ def main():
 | 
			
		||||
            "buildx",
 | 
			
		||||
            "build",
 | 
			
		||||
            "--build-arg",
 | 
			
		||||
            f"BASEIMGTYPE={params.baseimgtype}",
 | 
			
		||||
            f"BUILD_TYPE={params.build_type}",
 | 
			
		||||
            "--build-arg",
 | 
			
		||||
            f"BUILD_VERSION={args.tag}",
 | 
			
		||||
            "--cache-from",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
import re
 | 
			
		||||
import argparse
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
CHANNEL_DEV = "dev"
 | 
			
		||||
CHANNEL_BETA = "beta"
 | 
			
		||||
 
 | 
			
		||||
@@ -23,10 +23,6 @@ if bashio::config.true 'streamer_mode'; then
 | 
			
		||||
    export ESPHOME_STREAMER_MODE=true
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if bashio::config.true 'status_use_ping'; then
 | 
			
		||||
    export ESPHOME_DASHBOARD_USE_PING=true
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if bashio::config.has_value 'relative_url'; then
 | 
			
		||||
    export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
 | 
			
		||||
fi
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
import argparse
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
import functools
 | 
			
		||||
import importlib
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
@@ -66,7 +67,7 @@ def choose_prompt(options, purpose: str = None):
 | 
			
		||||
        return options[0][1]
 | 
			
		||||
 | 
			
		||||
    safe_print(
 | 
			
		||||
        f'Found multiple options{f" for {purpose}" if purpose else ""}, please choose one:'
 | 
			
		||||
        f"Found multiple options{f' for {purpose}' if purpose else ''}, please choose one:"
 | 
			
		||||
    )
 | 
			
		||||
    for i, (desc, _) in enumerate(options):
 | 
			
		||||
        safe_print(f"  [{i + 1}] {desc}")
 | 
			
		||||
@@ -132,7 +133,7 @@ def get_port_type(port):
 | 
			
		||||
    return "NETWORK"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run_miniterm(config, port):
 | 
			
		||||
def run_miniterm(config, port, args):
 | 
			
		||||
    import serial
 | 
			
		||||
 | 
			
		||||
    from esphome import platformio_api
 | 
			
		||||
@@ -153,7 +154,7 @@ def run_miniterm(config, port):
 | 
			
		||||
 | 
			
		||||
    # We can't set to False by default since it leads to toggling and hence
 | 
			
		||||
    # ESP32 resets on some platforms.
 | 
			
		||||
    if config["logger"][CONF_DEASSERT_RTS_DTR]:
 | 
			
		||||
    if config["logger"][CONF_DEASSERT_RTS_DTR] or args.reset:
 | 
			
		||||
        ser.dtr = False
 | 
			
		||||
        ser.rts = False
 | 
			
		||||
 | 
			
		||||
@@ -243,11 +244,11 @@ def compile_program(args, config):
 | 
			
		||||
    return 0 if idedata is not None else 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def upload_using_esptool(config, port, file):
 | 
			
		||||
def upload_using_esptool(config, port, file, speed):
 | 
			
		||||
    from esphome import platformio_api
 | 
			
		||||
 | 
			
		||||
    first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
 | 
			
		||||
        "upload_speed", 460800
 | 
			
		||||
    first_baudrate = speed or config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
 | 
			
		||||
        "upload_speed", os.getenv("ESPHOME_UPLOAD_SPEED", "460800")
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if file is not None:
 | 
			
		||||
@@ -336,11 +337,18 @@ def check_permissions(port):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def upload_program(config, args, host):
 | 
			
		||||
    try:
 | 
			
		||||
        module = importlib.import_module("esphome.components." + CORE.target_platform)
 | 
			
		||||
        if getattr(module, "upload_program")(config, args, host):
 | 
			
		||||
            return 0
 | 
			
		||||
    except AttributeError:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    if get_port_type(host) == "SERIAL":
 | 
			
		||||
        check_permissions(host)
 | 
			
		||||
        if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
 | 
			
		||||
            file = getattr(args, "file", None)
 | 
			
		||||
            return upload_using_esptool(config, host, file)
 | 
			
		||||
            return upload_using_esptool(config, host, file, args.upload_speed)
 | 
			
		||||
 | 
			
		||||
        if CORE.target_platform in (PLATFORM_RP2040):
 | 
			
		||||
            return upload_using_platformio(config, args.device)
 | 
			
		||||
@@ -367,10 +375,12 @@ def upload_program(config, args, host):
 | 
			
		||||
    password = ota_conf.get(CONF_PASSWORD, "")
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        not is_ip_address(CORE.address)  # pylint: disable=too-many-boolean-expressions
 | 
			
		||||
        and (get_port_type(host) == "MQTT" or config[CONF_MDNS][CONF_DISABLED])
 | 
			
		||||
        and CONF_MQTT in config
 | 
			
		||||
        CONF_MQTT in config  # pylint: disable=too-many-boolean-expressions
 | 
			
		||||
        and (not args.device or args.device in ("MQTT", "OTA"))
 | 
			
		||||
        and (
 | 
			
		||||
            ((config[CONF_MDNS][CONF_DISABLED]) and not is_ip_address(CORE.address))
 | 
			
		||||
            or get_port_type(host) == "MQTT"
 | 
			
		||||
        )
 | 
			
		||||
    ):
 | 
			
		||||
        from esphome import mqtt
 | 
			
		||||
 | 
			
		||||
@@ -389,7 +399,7 @@ def show_logs(config, args, port):
 | 
			
		||||
        raise EsphomeError("Logger is not configured!")
 | 
			
		||||
    if get_port_type(port) == "SERIAL":
 | 
			
		||||
        check_permissions(port)
 | 
			
		||||
        return run_miniterm(config, port)
 | 
			
		||||
        return run_miniterm(config, port, args)
 | 
			
		||||
    if get_port_type(port) == "NETWORK" and "api" in config:
 | 
			
		||||
        if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:
 | 
			
		||||
            from esphome import mqtt
 | 
			
		||||
@@ -834,6 +844,10 @@ def parse_args(argv):
 | 
			
		||||
        "--device",
 | 
			
		||||
        help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
 | 
			
		||||
    )
 | 
			
		||||
    parser_upload.add_argument(
 | 
			
		||||
        "--upload_speed",
 | 
			
		||||
        help="Override the default or configured upload speed.",
 | 
			
		||||
    )
 | 
			
		||||
    parser_upload.add_argument(
 | 
			
		||||
        "--file",
 | 
			
		||||
        help="Manually specify the binary file to upload.",
 | 
			
		||||
@@ -852,6 +866,13 @@ def parse_args(argv):
 | 
			
		||||
        "--device",
 | 
			
		||||
        help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
 | 
			
		||||
    )
 | 
			
		||||
    parser_logs.add_argument(
 | 
			
		||||
        "--reset",
 | 
			
		||||
        "-r",
 | 
			
		||||
        action="store_true",
 | 
			
		||||
        help="Reset the device before starting serial logs.",
 | 
			
		||||
        default=os.getenv("ESPHOME_SERIAL_LOGGING_RESET"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser_discover = subparsers.add_parser(
 | 
			
		||||
        "discover",
 | 
			
		||||
@@ -874,9 +895,20 @@ def parse_args(argv):
 | 
			
		||||
        "--device",
 | 
			
		||||
        help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
 | 
			
		||||
    )
 | 
			
		||||
    parser_run.add_argument(
 | 
			
		||||
        "--upload_speed",
 | 
			
		||||
        help="Override the default or configured upload speed.",
 | 
			
		||||
    )
 | 
			
		||||
    parser_run.add_argument(
 | 
			
		||||
        "--no-logs", help="Disable starting logs.", action="store_true"
 | 
			
		||||
    )
 | 
			
		||||
    parser_run.add_argument(
 | 
			
		||||
        "--reset",
 | 
			
		||||
        "-r",
 | 
			
		||||
        action="store_true",
 | 
			
		||||
        help="Reset the device before starting serial logs.",
 | 
			
		||||
        default=os.getenv("ESPHOME_SERIAL_LOGGING_RESET"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser_clean = subparsers.add_parser(
 | 
			
		||||
        "clean-mqtt",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import sensor, uart
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_DISTANCE,
 | 
			
		||||
    ICON_ARROW_EXPAND_VERTICAL,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_METER,
 | 
			
		||||
    ICON_ARROW_EXPAND_VERTICAL,
 | 
			
		||||
    DEVICE_CLASS_DISTANCE,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@MrSuicideParrot"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import sensor, uart
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    ICON_ARROW_EXPAND_VERTICAL,
 | 
			
		||||
    DEVICE_CLASS_DISTANCE,
 | 
			
		||||
    ICON_ARROW_EXPAND_VERTICAL,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_MILLIMETER,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
from esphome import pins
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import stepper
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
a4988_ns = cg.esphome_ns.namespace("a4988")
 | 
			
		||||
A4988 = a4988_ns.class_("A4988", stepper.Stepper, cg.Component)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_EQUATION,
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    CONF_EQUATION,
 | 
			
		||||
    ICON_WATER,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_GRAMS_PER_CUBIC_METER,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -114,13 +114,14 @@ void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
 | 
			
		||||
    // fully off, disable output immediately
 | 
			
		||||
    this->gate_pin.digital_write(false);
 | 
			
		||||
  } else {
 | 
			
		||||
    auto min_us = this->cycle_time_us * this->min_power / 1000;
 | 
			
		||||
    if (this->method == DIM_METHOD_TRAILING) {
 | 
			
		||||
      this->enable_time_us = 1;  // cannot be 0
 | 
			
		||||
      this->disable_time_us = std::max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
 | 
			
		||||
      // calculate time until disable in µs with integer arithmetic and take into account min_power
 | 
			
		||||
      this->disable_time_us = std::max((uint32_t) 10, this->value * (this->cycle_time_us - min_us) / 65535 + min_us);
 | 
			
		||||
    } else {
 | 
			
		||||
      // calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
 | 
			
		||||
      // also take into account min_power
 | 
			
		||||
      auto min_us = this->cycle_time_us * this->min_power / 1000;
 | 
			
		||||
      this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
 | 
			
		||||
 | 
			
		||||
      if (this->method == DIM_METHOD_LEADING_PULSE) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import output
 | 
			
		||||
from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID, CONF_METHOD, CONF_MIN_POWER
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@glmnet"]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import uart
 | 
			
		||||
from esphome.components.light.types import AddressableLightEffect
 | 
			
		||||
from esphome.components.light.effects import register_addressable_effect
 | 
			
		||||
from esphome.components.light.types import AddressableLightEffect
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_NAME, CONF_UART_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["uart"]
 | 
			
		||||
 
 | 
			
		||||
@@ -47,9 +47,10 @@ SAMPLING_MODES = {
 | 
			
		||||
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
 | 
			
		||||
adc2_channel_t = cg.global_ns.enum("adc2_channel_t")
 | 
			
		||||
 | 
			
		||||
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
 | 
			
		||||
# pin to adc1 channel mapping
 | 
			
		||||
# https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/include/driver/adc.h
 | 
			
		||||
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32: {
 | 
			
		||||
        36: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        37: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
@@ -60,6 +61,41 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
			
		||||
        34: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        35: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c2/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32C2: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c3/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32C3: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32C6: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        6: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32H2: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32S2: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
@@ -72,6 +108,7 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
			
		||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
			
		||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32S3: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
@@ -84,40 +121,12 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
			
		||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
			
		||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C3: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C2: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C6: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        6: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32H2: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# pin to adc2 channel mapping
 | 
			
		||||
# https://github.com/espressif/esp-idf/blob/v4.4.8/components/driver/include/driver/adc.h
 | 
			
		||||
ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
			
		||||
    # TODO: add other variants
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32: {
 | 
			
		||||
        4: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
        0: adc2_channel_t.ADC2_CHANNEL_1,
 | 
			
		||||
@@ -130,6 +139,19 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
			
		||||
        25: adc2_channel_t.ADC2_CHANNEL_8,
 | 
			
		||||
        26: adc2_channel_t.ADC2_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c2/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32C2: {
 | 
			
		||||
        5: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c3/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32C3: {
 | 
			
		||||
        5: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32c6/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32C6: {},  # no ADC2
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32H2: {},  # no ADC2
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32S2: {
 | 
			
		||||
        11: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
        12: adc2_channel_t.ADC2_CHANNEL_1,
 | 
			
		||||
@@ -142,6 +164,7 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
			
		||||
        19: adc2_channel_t.ADC2_CHANNEL_8,
 | 
			
		||||
        20: adc2_channel_t.ADC2_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/adc_channel.h
 | 
			
		||||
    VARIANT_ESP32S3: {
 | 
			
		||||
        11: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
        12: adc2_channel_t.ADC2_CHANNEL_1,
 | 
			
		||||
@@ -154,12 +177,6 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
 | 
			
		||||
        19: adc2_channel_t.ADC2_CHANNEL_8,
 | 
			
		||||
        20: adc2_channel_t.ADC2_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C3: {
 | 
			
		||||
        5: adc2_channel_t.ADC2_CHANNEL_0,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C2: {},
 | 
			
		||||
    VARIANT_ESP32C6: {},
 | 
			
		||||
    VARIANT_ESP32H2: {},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import spi
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["spi"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, voltage_sampler
 | 
			
		||||
from esphome.const import CONF_ID, CONF_CHANNEL
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_CHANNEL, CONF_ID
 | 
			
		||||
 | 
			
		||||
from .. import adc128s102_ns, ADC128S102
 | 
			
		||||
from .. import ADC128S102, adc128s102_ns
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["voltage_sampler"]
 | 
			
		||||
DEPENDENCIES = ["adc128s102"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,15 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import display, light
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ADDRESSABLE_LIGHT_ID,
 | 
			
		||||
    CONF_HEIGHT,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_LAMBDA,
 | 
			
		||||
    CONF_PAGES,
 | 
			
		||||
    CONF_ADDRESSABLE_LIGHT_ID,
 | 
			
		||||
    CONF_HEIGHT,
 | 
			
		||||
    CONF_WIDTH,
 | 
			
		||||
    CONF_UPDATE_INTERVAL,
 | 
			
		||||
    CONF_PIXEL_MAPPER,
 | 
			
		||||
    CONF_UPDATE_INTERVAL,
 | 
			
		||||
    CONF_WIDTH,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@justfalter"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, i2c
 | 
			
		||||
from esphome import pins
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ACTIVE_POWER,
 | 
			
		||||
    CONF_APPARENT_POWER,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,27 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
from esphome import pins
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_FREQUENCY,
 | 
			
		||||
    CONF_IRQ_PIN,
 | 
			
		||||
    CONF_VOLTAGE,
 | 
			
		||||
    CONF_FREQUENCY,
 | 
			
		||||
    CONF_VOLTAGE_GAIN,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_APPARENT_POWER,
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_REACTIVE_POWER,
 | 
			
		||||
    DEVICE_CLASS_POWER_FACTOR,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_FREQUENCY,
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_POWER_FACTOR,
 | 
			
		||||
    DEVICE_CLASS_REACTIVE_POWER,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_HERTZ,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_VOLT_AMPS,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
    UNIT_VOLT_AMPS_REACTIVE,
 | 
			
		||||
    UNIT_HERTZ,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_VOLT_AMPS,
 | 
			
		||||
    UNIT_VOLT_AMPS_REACTIVE,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_CURRENT_A = "current_a"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import ade7953_base, i2c
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, ade7953_base
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
AUTO_LOAD = ["ade7953_base"]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import ade7953_base, spi
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import spi, ade7953_base
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["spi"]
 | 
			
		||||
AUTO_LOAD = ["ade7953_base"]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,18 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, voltage_sampler
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_GAIN,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MULTIPLEXER,
 | 
			
		||||
    CONF_RESOLUTION,
 | 
			
		||||
    CONF_SAMPLE_RATE,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
)
 | 
			
		||||
from .. import ads1115_ns, ADS1115Component, CONF_ADS1115_ID
 | 
			
		||||
 | 
			
		||||
from .. import CONF_ADS1115_ID, ADS1115Component, ads1115_ns
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["voltage_sampler"]
 | 
			
		||||
DEPENDENCIES = ["ads1115"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import spi
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@solomondg1"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,18 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, voltage_sampler
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_GAIN,
 | 
			
		||||
    CONF_MULTIPLEXER,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
)
 | 
			
		||||
from .. import ads1118_ns, ADS1118, CONF_ADS1118_ID
 | 
			
		||||
 | 
			
		||||
from .. import ADS1118, CONF_ADS1118_ID, ads1118_ns
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["voltage_sampler"]
 | 
			
		||||
DEPENDENCIES = ["ads1118"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,21 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome import automation
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ADDRESS,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    ICON_RADIATOR,
 | 
			
		||||
    ICON_RESTART,
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_TVOC,
 | 
			
		||||
    CONF_VALUE,
 | 
			
		||||
    CONF_VERSION,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    ICON_RADIATOR,
 | 
			
		||||
    ICON_RESTART,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_OHM,
 | 
			
		||||
    UNIT_PARTS_PER_BILLION,
 | 
			
		||||
    CONF_ADDRESS,
 | 
			
		||||
    CONF_TVOC,
 | 
			
		||||
    CONF_VERSION,
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_VALUE,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_RESISTANCE = "resistance"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,16 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    CONF_VARIANT,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
    CONF_VARIANT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import esp32_ble_tracker
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32_ble_tracker"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,17 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import ble_client, sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, ble_client
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_BATTERY_VOLTAGE,
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_PRESSURE,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    CONF_TVOC,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_PRESSURE,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import airthings_wave_base
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
)
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = airthings_wave_base.DEPENDENCIES
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,19 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import airthings_wave_base, sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, airthings_wave_base
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    ICON_RADIOACTIVE,
 | 
			
		||||
    CONF_CO2,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_ILLUMINANCE,
 | 
			
		||||
    CONF_RADON,
 | 
			
		||||
    CONF_RADON_LONG_TERM,
 | 
			
		||||
    CONF_CO2,
 | 
			
		||||
    UNIT_BECQUEREL_PER_CUBIC_METER,
 | 
			
		||||
    UNIT_PARTS_PER_MILLION,
 | 
			
		||||
    CONF_ILLUMINANCE,
 | 
			
		||||
    UNIT_LUX,
 | 
			
		||||
    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
			
		||||
    DEVICE_CLASS_ILLUMINANCE,
 | 
			
		||||
    ICON_RADIOACTIVE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_BECQUEREL_PER_CUBIC_METER,
 | 
			
		||||
    UNIT_LUX,
 | 
			
		||||
    UNIT_PARTS_PER_MILLION,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = airthings_wave_base.DEPENDENCIES
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,20 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import ble_client, sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, ble_client
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_CURRENT,
 | 
			
		||||
    CONF_FLOW,
 | 
			
		||||
    CONF_HEAD,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_POWER,
 | 
			
		||||
    CONF_SPEED,
 | 
			
		||||
    CONF_VOLTAGE,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_CUBIC_METER_PER_HOUR,
 | 
			
		||||
    UNIT_METER,
 | 
			
		||||
    UNIT_REVOLUTIONS_PER_MINUTE,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
    UNIT_METER,
 | 
			
		||||
    UNIT_CUBIC_METER_PER_HOUR,
 | 
			
		||||
    UNIT_REVOLUTIONS_PER_MINUTE,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
alpha3_ns = cg.esphome_ns.namespace("alpha3")
 | 
			
		||||
 
 | 
			
		||||
@@ -128,7 +128,7 @@ void AM2315C::update() {
 | 
			
		||||
  data[2] = 0x00;
 | 
			
		||||
  if (this->write(data, 3) != i2c::ERROR_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "Write failed!");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -138,12 +138,12 @@ void AM2315C::update() {
 | 
			
		||||
    uint8_t status = 0;
 | 
			
		||||
    if (this->read(&status, 1) != i2c::ERROR_OK) {
 | 
			
		||||
      ESP_LOGE(TAG, "Read failed!");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if ((status & 0x80) == 0x80) {
 | 
			
		||||
      ESP_LOGE(TAG, "HW still busy!");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -151,7 +151,7 @@ void AM2315C::update() {
 | 
			
		||||
    uint8_t data[7];
 | 
			
		||||
    if (this->read(data, 7) != i2c::ERROR_OK) {
 | 
			
		||||
      ESP_LOGE(TAG, "Read failed!");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import ble_client, cover
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import cover, ble_client
 | 
			
		||||
from esphome.const import CONF_ID, CONF_PIN
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@buxtronix"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import ble_client, sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, ble_client
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_BATTERY_LEVEL,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_ILLUMINANCE,
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    CONF_ILLUMINANCE,
 | 
			
		||||
    ICON_BRIGHTNESS_5,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,8 @@ void AnalogThresholdBinarySensor::setup() {
 | 
			
		||||
  if (std::isnan(sensor_value)) {
 | 
			
		||||
    this->publish_initial_state(false);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->publish_initial_state(sensor_value >= (this->lower_threshold_ + this->upper_threshold_) / 2.0f);
 | 
			
		||||
    this->publish_initial_state(sensor_value >=
 | 
			
		||||
                                (this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -24,7 +25,8 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
 | 
			
		||||
  this->sensor_->add_on_state_callback([this](float sensor_value) {
 | 
			
		||||
    // if there is an invalid sensor reading, ignore the change and keep the current state
 | 
			
		||||
    if (!std::isnan(sensor_value)) {
 | 
			
		||||
      this->publish_state(sensor_value >= (this->state ? this->lower_threshold_ : this->upper_threshold_));
 | 
			
		||||
      this->publish_state(sensor_value >=
 | 
			
		||||
                          (this->state ? this->lower_threshold_.value() : this->upper_threshold_.value()));
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
@@ -32,8 +34,8 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
 | 
			
		||||
void AnalogThresholdBinarySensor::dump_config() {
 | 
			
		||||
  LOG_BINARY_SENSOR("", "Analog Threshold Binary Sensor", this);
 | 
			
		||||
  LOG_SENSOR("  ", "Sensor", this->sensor_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Upper threshold: %.11f", this->upper_threshold_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Lower threshold: %.11f", this->lower_threshold_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Upper threshold: %.11f", this->upper_threshold_.value());
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Lower threshold: %.11f", this->lower_threshold_.value());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace analog_threshold
 | 
			
		||||
 
 | 
			
		||||
@@ -15,14 +15,13 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
  void set_sensor(sensor::Sensor *analog_sensor);
 | 
			
		||||
  void set_upper_threshold(float threshold) { this->upper_threshold_ = threshold; }
 | 
			
		||||
  void set_lower_threshold(float threshold) { this->lower_threshold_ = threshold; }
 | 
			
		||||
  template<typename T> void set_upper_threshold(T upper_threshold) { this->upper_threshold_ = upper_threshold; }
 | 
			
		||||
  template<typename T> void set_lower_threshold(T lower_threshold) { this->lower_threshold_ = lower_threshold; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  sensor::Sensor *sensor_{nullptr};
 | 
			
		||||
 | 
			
		||||
  float upper_threshold_;
 | 
			
		||||
  float lower_threshold_;
 | 
			
		||||
  TemplatableValue<float> upper_threshold_{};
 | 
			
		||||
  TemplatableValue<float> lower_threshold_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace analog_threshold
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import binary_sensor, sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_SENSOR_ID,
 | 
			
		||||
    CONF_THRESHOLD,
 | 
			
		||||
)
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_SENSOR_ID, CONF_THRESHOLD
 | 
			
		||||
 | 
			
		||||
analog_threshold_ns = cg.esphome_ns.namespace("analog_threshold")
 | 
			
		||||
 | 
			
		||||
@@ -21,11 +18,11 @@ CONFIG_SCHEMA = (
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor),
 | 
			
		||||
            cv.Required(CONF_THRESHOLD): cv.Any(
 | 
			
		||||
                cv.float_,
 | 
			
		||||
                cv.templatable(cv.float_),
 | 
			
		||||
                cv.Schema(
 | 
			
		||||
                    {
 | 
			
		||||
                        cv.Required(CONF_UPPER): cv.float_,
 | 
			
		||||
                        cv.Required(CONF_LOWER): cv.float_,
 | 
			
		||||
                        cv.Required(CONF_UPPER): cv.templatable(cv.float_),
 | 
			
		||||
                        cv.Required(CONF_LOWER): cv.templatable(cv.float_),
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
@@ -42,9 +39,11 @@ async def to_code(config):
 | 
			
		||||
    sens = await cg.get_variable(config[CONF_SENSOR_ID])
 | 
			
		||||
    cg.add(var.set_sensor(sens))
 | 
			
		||||
 | 
			
		||||
    if isinstance(config[CONF_THRESHOLD], float):
 | 
			
		||||
        cg.add(var.set_upper_threshold(config[CONF_THRESHOLD]))
 | 
			
		||||
        cg.add(var.set_lower_threshold(config[CONF_THRESHOLD]))
 | 
			
		||||
    if isinstance(config[CONF_THRESHOLD], dict):
 | 
			
		||||
        lower = await cg.templatable(config[CONF_THRESHOLD][CONF_LOWER], [], float)
 | 
			
		||||
        upper = await cg.templatable(config[CONF_THRESHOLD][CONF_UPPER], [], float)
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_upper_threshold(config[CONF_THRESHOLD][CONF_UPPER]))
 | 
			
		||||
        cg.add(var.set_lower_threshold(config[CONF_THRESHOLD][CONF_LOWER]))
 | 
			
		||||
        lower = await cg.templatable(config[CONF_THRESHOLD], [], float)
 | 
			
		||||
        upper = lower
 | 
			
		||||
    cg.add(var.set_upper_threshold(upper))
 | 
			
		||||
    cg.add(var.set_lower_threshold(lower))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import ble_client, climate
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import climate, ble_client
 | 
			
		||||
from esphome.const import CONF_ID, CONF_UNIT_OF_MEASUREMENT
 | 
			
		||||
 | 
			
		||||
UNITS = {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,8 @@
 | 
			
		||||
# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_GAIN,
 | 
			
		||||
    DEVICE_CLASS_ILLUMINANCE,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,8 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import binary_sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_DIRECTION, DEVICE_CLASS_MOVING
 | 
			
		||||
 | 
			
		||||
from . import APDS9960, CONF_APDS9960_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["apds9960"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,13 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
    ICON_LIGHTBULB,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
    ICON_LIGHTBULB,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from . import APDS9960, CONF_APDS9960_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["apds9960"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,10 @@
 | 
			
		||||
import base64
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.automation import Condition
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components.esp32 import add_idf_sdkconfig_option
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ACTION,
 | 
			
		||||
@@ -23,12 +25,14 @@ from esphome.const import (
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_VARIABLES,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import coroutine_with_priority
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["network"]
 | 
			
		||||
AUTO_LOAD = ["socket"]
 | 
			
		||||
CODEOWNERS = ["@OttoWinter"]
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
api_ns = cg.esphome_ns.namespace("api")
 | 
			
		||||
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
 | 
			
		||||
HomeAssistantServiceCallAction = api_ns.class_(
 | 
			
		||||
@@ -49,6 +53,11 @@ SERVICE_ARG_NATIVE_TYPES = {
 | 
			
		||||
    "string[]": cg.std_vector.template(cg.std_string),
 | 
			
		||||
}
 | 
			
		||||
CONF_ENCRYPTION = "encryption"
 | 
			
		||||
CONF_HEAP_TRACING = "heap_tracing"
 | 
			
		||||
CONF_HEAP_TRACING_STANDALONE = "standalone"  # vs SYSTEM
 | 
			
		||||
CONF_HEAP_TRACING_RECORDS = "num_records"
 | 
			
		||||
CONF_HEAP_TASK_TRACKING = "task_tracking"
 | 
			
		||||
CONF_HEAP_TASK_MAX = "max_tasks"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_encryption_key(value):
 | 
			
		||||
@@ -82,6 +91,35 @@ ACTIONS_SCHEMA = automation.validate_automation(
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ENCRYPTION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_KEY): validate_encryption_key,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _encryption_schema(config):
 | 
			
		||||
    if config is None:
 | 
			
		||||
        config = {}
 | 
			
		||||
    return ENCRYPTION_SCHEMA(config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
HEAP_TRACING_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_HEAP_TRACING_STANDALONE, default=True): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_HEAP_TRACING_RECORDS, default=100): cv.positive_int,
 | 
			
		||||
        cv.Optional(CONF_HEAP_TASK_TRACKING, default=True): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_HEAP_TASK_MAX, default=10): cv.positive_int,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _heap_tracing_schema(config):
 | 
			
		||||
    if config is None:
 | 
			
		||||
        config = {}
 | 
			
		||||
    return HEAP_TRACING_SCHEMA(config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
@@ -95,11 +133,8 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
                CONF_SERVICES, group_of_exclusion=CONF_ACTIONS
 | 
			
		||||
            ): ACTIONS_SCHEMA,
 | 
			
		||||
            cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_ENCRYPTION): cv.Schema(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Required(CONF_KEY): validate_encryption_key,
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ENCRYPTION): _encryption_schema,
 | 
			
		||||
            cv.Optional(CONF_HEAP_TRACING): _heap_tracing_schema,
 | 
			
		||||
            cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
 | 
			
		||||
                single=True
 | 
			
		||||
            ),
 | 
			
		||||
@@ -151,14 +186,90 @@ async def to_code(config):
 | 
			
		||||
            config[CONF_ON_CLIENT_DISCONNECTED],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if encryption_config := config.get(CONF_ENCRYPTION):
 | 
			
		||||
        decoded = base64.b64decode(encryption_config[CONF_KEY])
 | 
			
		||||
        cg.add(var.set_noise_psk(list(decoded)))
 | 
			
		||||
    if (encryption_config := config.get(CONF_ENCRYPTION, None)) is not None:
 | 
			
		||||
        if key := encryption_config.get(CONF_KEY):
 | 
			
		||||
            decoded = base64.b64decode(key)
 | 
			
		||||
            cg.add(var.set_noise_psk(list(decoded)))
 | 
			
		||||
        else:
 | 
			
		||||
            # No key provided, but encryption desired
 | 
			
		||||
            # This will allow a plaintext client to provide a noise key,
 | 
			
		||||
            # send it to the device, and then switch to noise.
 | 
			
		||||
            # The key will be saved in flash and used for future connections
 | 
			
		||||
            # and plaintext disabled. Only a factory reset can remove it.
 | 
			
		||||
            cg.add_define("USE_API_PLAINTEXT")
 | 
			
		||||
        cg.add_define("USE_API_NOISE")
 | 
			
		||||
        cg.add_library("esphome/noise-c", "0.1.6")
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add_define("USE_API_PLAINTEXT")
 | 
			
		||||
 | 
			
		||||
    # Handle heap tracing configuration if ESP32 platform and using ESP-IDF
 | 
			
		||||
    if (heap_tracing_config := config.get(CONF_HEAP_TRACING, None)) is not None:
 | 
			
		||||
        if CORE.using_esp_idf:
 | 
			
		||||
            # Enable heap tracing in sdkconfig
 | 
			
		||||
            add_idf_sdkconfig_option("CONFIG_HEAP_TRACING", True)
 | 
			
		||||
            add_idf_sdkconfig_option("CONFIG_HEAP_TRACE_STACK_DEPTH", "30")
 | 
			
		||||
            add_idf_sdkconfig_option("CONFIG_ESP32_APPTRACE_ENABLE", True)
 | 
			
		||||
 | 
			
		||||
            # Set tracing mode (standalone or system)
 | 
			
		||||
            if heap_tracing_config[CONF_HEAP_TRACING_STANDALONE]:
 | 
			
		||||
                add_idf_sdkconfig_option("CONFIG_HEAP_TRACING_STANDALONE", True)
 | 
			
		||||
            else:
 | 
			
		||||
                add_idf_sdkconfig_option("CONFIG_HEAP_TRACING_SYSTEM", True)
 | 
			
		||||
 | 
			
		||||
            # Enable runtime stats gathering for task info
 | 
			
		||||
            if heap_tracing_config[CONF_HEAP_TASK_TRACKING]:
 | 
			
		||||
                add_idf_sdkconfig_option(
 | 
			
		||||
                    "CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS", True
 | 
			
		||||
                )
 | 
			
		||||
                add_idf_sdkconfig_option(
 | 
			
		||||
                    "CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS", True
 | 
			
		||||
                )
 | 
			
		||||
                add_idf_sdkconfig_option("CONFIG_FREERTOS_USE_TRACE_FACILITY", True)
 | 
			
		||||
 | 
			
		||||
            # Generate code to implement heap tracing
 | 
			
		||||
            cg.add_global(cg.RawStatement('#include "esp_heap_trace.h"'))
 | 
			
		||||
 | 
			
		||||
            # Define the trace record buffer
 | 
			
		||||
            num_records = heap_tracing_config[CONF_HEAP_TRACING_RECORDS]
 | 
			
		||||
            cg.add_global(
 | 
			
		||||
                cg.RawStatement(
 | 
			
		||||
                    f"static heap_trace_record_t trace_record[{num_records}];"
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # No additional setup needed for task tracking
 | 
			
		||||
 | 
			
		||||
            # Add helper functions for heap tracing with extern "C" to make them globally accessible
 | 
			
		||||
            cg.add_global(
 | 
			
		||||
                cg.RawStatement(
 | 
			
		||||
                    """
 | 
			
		||||
// Global heap tracing functions that can be called from any context
 | 
			
		||||
extern "C" void start_heap_trace() {
 | 
			
		||||
    heap_trace_init_standalone(trace_record, """
 | 
			
		||||
                    + str(num_records)
 | 
			
		||||
                    + """);
 | 
			
		||||
    heap_trace_start(HEAP_TRACE_LEAKS);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" void stop_and_dump_heap_trace() {
 | 
			
		||||
    heap_trace_stop();
 | 
			
		||||
    heap_trace_dump();
 | 
			
		||||
}
 | 
			
		||||
"""
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # Add periodic heap trace dumping to the api_server.cpp file
 | 
			
		||||
            # This will be added in C++ code
 | 
			
		||||
            cg.add_define("USE_API_HEAP_TRACE")
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            # Not using ESP-IDF, so we can't use heap tracing
 | 
			
		||||
            _LOGGER.warning(
 | 
			
		||||
                "Heap tracing is only available when using ESP-IDF. "
 | 
			
		||||
                "Disabling heap tracing configuration."
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    cg.add_define("USE_API")
 | 
			
		||||
    cg.add_global(api_ns.using)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ service APIConnection {
 | 
			
		||||
    option (needs_authentication) = false;
 | 
			
		||||
  }
 | 
			
		||||
  rpc execute_service (ExecuteServiceRequest) returns (void) {}
 | 
			
		||||
  rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
 | 
			
		||||
 | 
			
		||||
  rpc cover_command (CoverCommandRequest) returns (void) {}
 | 
			
		||||
  rpc fan_command (FanCommandRequest) returns (void) {}
 | 
			
		||||
@@ -60,6 +61,7 @@ service APIConnection {
 | 
			
		||||
  rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
 | 
			
		||||
  rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
 | 
			
		||||
  rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
 | 
			
		||||
  rpc bluetooth_scanner_set_mode(BluetoothScannerSetModeRequest) returns (void) {}
 | 
			
		||||
 | 
			
		||||
  rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
 | 
			
		||||
  rpc voice_assistant_get_configuration(VoiceAssistantConfigurationRequest) returns (VoiceAssistantConfigurationResponse) {}
 | 
			
		||||
@@ -227,6 +229,12 @@ message DeviceInfoResponse {
 | 
			
		||||
  uint32 voice_assistant_feature_flags = 17;
 | 
			
		||||
 | 
			
		||||
  string suggested_area = 16;
 | 
			
		||||
 | 
			
		||||
  // The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA"
 | 
			
		||||
  string bluetooth_mac_address = 18;
 | 
			
		||||
 | 
			
		||||
  // Supports receiving and saving api encryption key
 | 
			
		||||
  bool api_encryption_supported = 19;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message ListEntitiesRequest {
 | 
			
		||||
@@ -651,6 +659,23 @@ message SubscribeLogsResponse {
 | 
			
		||||
  bool send_failed = 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== NOISE ENCRYPTION ====================
 | 
			
		||||
message NoiseEncryptionSetKeyRequest {
 | 
			
		||||
  option (id) = 124;
 | 
			
		||||
  option (source) = SOURCE_CLIENT;
 | 
			
		||||
  option (ifdef) = "USE_API_NOISE";
 | 
			
		||||
 | 
			
		||||
  bytes key = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message NoiseEncryptionSetKeyResponse {
 | 
			
		||||
  option (id) = 125;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_API_NOISE";
 | 
			
		||||
 | 
			
		||||
  bool success = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== HOMEASSISTANT.SERVICE ====================
 | 
			
		||||
message SubscribeHomeassistantServicesRequest {
 | 
			
		||||
  option (id) = 34;
 | 
			
		||||
@@ -1448,6 +1473,37 @@ message BluetoothDeviceClearCacheResponse {
 | 
			
		||||
  int32 error = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum BluetoothScannerState {
 | 
			
		||||
  BLUETOOTH_SCANNER_STATE_IDLE = 0;
 | 
			
		||||
  BLUETOOTH_SCANNER_STATE_STARTING = 1;
 | 
			
		||||
  BLUETOOTH_SCANNER_STATE_RUNNING = 2;
 | 
			
		||||
  BLUETOOTH_SCANNER_STATE_FAILED = 3;
 | 
			
		||||
  BLUETOOTH_SCANNER_STATE_STOPPING = 4;
 | 
			
		||||
  BLUETOOTH_SCANNER_STATE_STOPPED = 5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum BluetoothScannerMode {
 | 
			
		||||
  BLUETOOTH_SCANNER_MODE_PASSIVE = 0;
 | 
			
		||||
  BLUETOOTH_SCANNER_MODE_ACTIVE = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothScannerStateResponse {
 | 
			
		||||
  option(id) = 126;
 | 
			
		||||
  option(source) = SOURCE_SERVER;
 | 
			
		||||
  option(ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  BluetoothScannerState state = 1;
 | 
			
		||||
  BluetoothScannerMode mode = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothScannerSetModeRequest {
 | 
			
		||||
  option(id) = 127;
 | 
			
		||||
  option(source) = SOURCE_CLIENT;
 | 
			
		||||
  option(ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  BluetoothScannerMode mode = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== PUSH TO TALK ====================
 | 
			
		||||
enum VoiceAssistantSubscribeFlag {
 | 
			
		||||
  VOICE_ASSISTANT_SUBSCRIBE_NONE = 0;
 | 
			
		||||
@@ -1564,6 +1620,8 @@ message VoiceAssistantAnnounceRequest {
 | 
			
		||||
 | 
			
		||||
  string media_id = 1;
 | 
			
		||||
  string text = 2;
 | 
			
		||||
  string preannounce_media_id = 3;
 | 
			
		||||
  bool start_conversation = 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message VoiceAssistantAnnounceFinished {
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -14,6 +14,46 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
using send_message_t = bool(APIConnection *, void *);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
  This class holds a pointer to the source component that wants to publish a message, and a pointer to a function that
 | 
			
		||||
  will lazily publish that message.  The two pointers allow dedup in the deferred queue if multiple publishes for the
 | 
			
		||||
  same component are backed up, and take up only 8 bytes of memory.  The entry in the deferred queue (a std::vector) is
 | 
			
		||||
  the DeferredMessage instance itself (not a pointer to one elsewhere in heap) so still only 8 bytes per entry.  Even
 | 
			
		||||
  100 backed up messages (you'd have to have at least 100 sensors publishing because of dedup) would take up only 0.8
 | 
			
		||||
  kB.
 | 
			
		||||
*/
 | 
			
		||||
class DeferredMessageQueue {
 | 
			
		||||
  struct DeferredMessage {
 | 
			
		||||
    friend class DeferredMessageQueue;
 | 
			
		||||
 | 
			
		||||
   protected:
 | 
			
		||||
    void *source_;
 | 
			
		||||
    send_message_t *send_message_;
 | 
			
		||||
 | 
			
		||||
   public:
 | 
			
		||||
    DeferredMessage(void *source, send_message_t *send_message) : source_(source), send_message_(send_message) {}
 | 
			
		||||
    bool operator==(const DeferredMessage &test) const {
 | 
			
		||||
      return (source_ == test.source_ && send_message_ == test.send_message_);
 | 
			
		||||
    }
 | 
			
		||||
  } __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  // vector is used very specifically for its zero memory overhead even though items are popped from the front (memory
 | 
			
		||||
  // footprint is more important than speed here)
 | 
			
		||||
  std::vector<DeferredMessage> deferred_queue_;
 | 
			
		||||
  APIConnection *api_connection_;
 | 
			
		||||
 | 
			
		||||
  // helper for allowing only unique entries in the queue
 | 
			
		||||
  void dmq_push_back_with_dedup_(void *source, send_message_t *send_message);
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  DeferredMessageQueue(APIConnection *api_connection) : api_connection_(api_connection) {}
 | 
			
		||||
  void process_queue();
 | 
			
		||||
  void defer(void *source, send_message_t *send_message);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class APIConnection : public APIServerConnection {
 | 
			
		||||
 public:
 | 
			
		||||
  APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
 | 
			
		||||
@@ -28,96 +68,140 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  }
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
  bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
 | 
			
		||||
  bool send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor);
 | 
			
		||||
  void send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor);
 | 
			
		||||
  static bool try_send_binary_sensor_state(APIConnection *api, void *v_binary_sensor);
 | 
			
		||||
  static bool try_send_binary_sensor_state(APIConnection *api, binary_sensor::BinarySensor *binary_sensor, bool state);
 | 
			
		||||
  static bool try_send_binary_sensor_info(APIConnection *api, void *v_binary_sensor);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
  bool send_cover_state(cover::Cover *cover);
 | 
			
		||||
  bool send_cover_info(cover::Cover *cover);
 | 
			
		||||
  void send_cover_info(cover::Cover *cover);
 | 
			
		||||
  static bool try_send_cover_state(APIConnection *api, void *v_cover);
 | 
			
		||||
  static bool try_send_cover_info(APIConnection *api, void *v_cover);
 | 
			
		||||
  void cover_command(const CoverCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_FAN
 | 
			
		||||
  bool send_fan_state(fan::Fan *fan);
 | 
			
		||||
  bool send_fan_info(fan::Fan *fan);
 | 
			
		||||
  void send_fan_info(fan::Fan *fan);
 | 
			
		||||
  static bool try_send_fan_state(APIConnection *api, void *v_fan);
 | 
			
		||||
  static bool try_send_fan_info(APIConnection *api, void *v_fan);
 | 
			
		||||
  void fan_command(const FanCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LIGHT
 | 
			
		||||
  bool send_light_state(light::LightState *light);
 | 
			
		||||
  bool send_light_info(light::LightState *light);
 | 
			
		||||
  void send_light_info(light::LightState *light);
 | 
			
		||||
  static bool try_send_light_state(APIConnection *api, void *v_light);
 | 
			
		||||
  static bool try_send_light_info(APIConnection *api, void *v_light);
 | 
			
		||||
  void light_command(const LightCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  bool send_sensor_state(sensor::Sensor *sensor, float state);
 | 
			
		||||
  bool send_sensor_info(sensor::Sensor *sensor);
 | 
			
		||||
  void send_sensor_info(sensor::Sensor *sensor);
 | 
			
		||||
  static bool try_send_sensor_state(APIConnection *api, void *v_sensor);
 | 
			
		||||
  static bool try_send_sensor_state(APIConnection *api, sensor::Sensor *sensor, float state);
 | 
			
		||||
  static bool try_send_sensor_info(APIConnection *api, void *v_sensor);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
  bool send_switch_state(switch_::Switch *a_switch, bool state);
 | 
			
		||||
  bool send_switch_info(switch_::Switch *a_switch);
 | 
			
		||||
  void send_switch_info(switch_::Switch *a_switch);
 | 
			
		||||
  static bool try_send_switch_state(APIConnection *api, void *v_a_switch);
 | 
			
		||||
  static bool try_send_switch_state(APIConnection *api, switch_::Switch *a_switch, bool state);
 | 
			
		||||
  static bool try_send_switch_info(APIConnection *api, void *v_a_switch);
 | 
			
		||||
  void switch_command(const SwitchCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
 | 
			
		||||
  bool send_text_sensor_info(text_sensor::TextSensor *text_sensor);
 | 
			
		||||
  void send_text_sensor_info(text_sensor::TextSensor *text_sensor);
 | 
			
		||||
  static bool try_send_text_sensor_state(APIConnection *api, void *v_text_sensor);
 | 
			
		||||
  static bool try_send_text_sensor_state(APIConnection *api, text_sensor::TextSensor *text_sensor, std::string state);
 | 
			
		||||
  static bool try_send_text_sensor_info(APIConnection *api, void *v_text_sensor);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
  void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
 | 
			
		||||
  bool send_camera_info(esp32_camera::ESP32Camera *camera);
 | 
			
		||||
  void set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
 | 
			
		||||
  void send_camera_info(esp32_camera::ESP32Camera *camera);
 | 
			
		||||
  static bool try_send_camera_info(APIConnection *api, void *v_camera);
 | 
			
		||||
  void camera_image(const CameraImageRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
  bool send_climate_state(climate::Climate *climate);
 | 
			
		||||
  bool send_climate_info(climate::Climate *climate);
 | 
			
		||||
  void send_climate_info(climate::Climate *climate);
 | 
			
		||||
  static bool try_send_climate_state(APIConnection *api, void *v_climate);
 | 
			
		||||
  static bool try_send_climate_info(APIConnection *api, void *v_climate);
 | 
			
		||||
  void climate_command(const ClimateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  bool send_number_state(number::Number *number, float state);
 | 
			
		||||
  bool send_number_info(number::Number *number);
 | 
			
		||||
  void send_number_info(number::Number *number);
 | 
			
		||||
  static bool try_send_number_state(APIConnection *api, void *v_number);
 | 
			
		||||
  static bool try_send_number_state(APIConnection *api, number::Number *number, float state);
 | 
			
		||||
  static bool try_send_number_info(APIConnection *api, void *v_number);
 | 
			
		||||
  void number_command(const NumberCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATE
 | 
			
		||||
  bool send_date_state(datetime::DateEntity *date);
 | 
			
		||||
  bool send_date_info(datetime::DateEntity *date);
 | 
			
		||||
  void send_date_info(datetime::DateEntity *date);
 | 
			
		||||
  static bool try_send_date_state(APIConnection *api, void *v_date);
 | 
			
		||||
  static bool try_send_date_info(APIConnection *api, void *v_date);
 | 
			
		||||
  void date_command(const DateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_TIME
 | 
			
		||||
  bool send_time_state(datetime::TimeEntity *time);
 | 
			
		||||
  bool send_time_info(datetime::TimeEntity *time);
 | 
			
		||||
  void send_time_info(datetime::TimeEntity *time);
 | 
			
		||||
  static bool try_send_time_state(APIConnection *api, void *v_time);
 | 
			
		||||
  static bool try_send_time_info(APIConnection *api, void *v_time);
 | 
			
		||||
  void time_command(const TimeCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
  bool send_datetime_state(datetime::DateTimeEntity *datetime);
 | 
			
		||||
  bool send_datetime_info(datetime::DateTimeEntity *datetime);
 | 
			
		||||
  void send_datetime_info(datetime::DateTimeEntity *datetime);
 | 
			
		||||
  static bool try_send_datetime_state(APIConnection *api, void *v_datetime);
 | 
			
		||||
  static bool try_send_datetime_info(APIConnection *api, void *v_datetime);
 | 
			
		||||
  void datetime_command(const DateTimeCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
  bool send_text_state(text::Text *text, std::string state);
 | 
			
		||||
  bool send_text_info(text::Text *text);
 | 
			
		||||
  void send_text_info(text::Text *text);
 | 
			
		||||
  static bool try_send_text_state(APIConnection *api, void *v_text);
 | 
			
		||||
  static bool try_send_text_state(APIConnection *api, text::Text *text, std::string state);
 | 
			
		||||
  static bool try_send_text_info(APIConnection *api, void *v_text);
 | 
			
		||||
  void text_command(const TextCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
  bool send_select_state(select::Select *select, std::string state);
 | 
			
		||||
  bool send_select_info(select::Select *select);
 | 
			
		||||
  void send_select_info(select::Select *select);
 | 
			
		||||
  static bool try_send_select_state(APIConnection *api, void *v_select);
 | 
			
		||||
  static bool try_send_select_state(APIConnection *api, select::Select *select, std::string state);
 | 
			
		||||
  static bool try_send_select_info(APIConnection *api, void *v_select);
 | 
			
		||||
  void select_command(const SelectCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
  bool send_button_info(button::Button *button);
 | 
			
		||||
  void send_button_info(button::Button *button);
 | 
			
		||||
  static bool try_send_button_info(APIConnection *api, void *v_button);
 | 
			
		||||
  void button_command(const ButtonCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
  bool send_lock_state(lock::Lock *a_lock, lock::LockState state);
 | 
			
		||||
  bool send_lock_info(lock::Lock *a_lock);
 | 
			
		||||
  void send_lock_info(lock::Lock *a_lock);
 | 
			
		||||
  static bool try_send_lock_state(APIConnection *api, void *v_a_lock);
 | 
			
		||||
  static bool try_send_lock_state(APIConnection *api, lock::Lock *a_lock, lock::LockState state);
 | 
			
		||||
  static bool try_send_lock_info(APIConnection *api, void *v_a_lock);
 | 
			
		||||
  void lock_command(const LockCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
  bool send_valve_state(valve::Valve *valve);
 | 
			
		||||
  bool send_valve_info(valve::Valve *valve);
 | 
			
		||||
  void send_valve_info(valve::Valve *valve);
 | 
			
		||||
  static bool try_send_valve_state(APIConnection *api, void *v_valve);
 | 
			
		||||
  static bool try_send_valve_info(APIConnection *api, void *v_valve);
 | 
			
		||||
  void valve_command(const ValveCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
  bool send_media_player_state(media_player::MediaPlayer *media_player);
 | 
			
		||||
  bool send_media_player_info(media_player::MediaPlayer *media_player);
 | 
			
		||||
  void send_media_player_info(media_player::MediaPlayer *media_player);
 | 
			
		||||
  static bool try_send_media_player_state(APIConnection *api, void *v_media_player);
 | 
			
		||||
  static bool try_send_media_player_info(APIConnection *api, void *v_media_player);
 | 
			
		||||
  void media_player_command(const MediaPlayerCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
  bool send_log_message(int level, const char *tag, const char *line);
 | 
			
		||||
  bool try_send_log_message(int level, const char *tag, const char *line);
 | 
			
		||||
  void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
 | 
			
		||||
    if (!this->service_call_subscription_)
 | 
			
		||||
      return;
 | 
			
		||||
@@ -137,6 +221,7 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
 | 
			
		||||
  BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
 | 
			
		||||
      const SubscribeBluetoothConnectionsFreeRequest &msg) override;
 | 
			
		||||
  void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_HOMEASSISTANT_TIME
 | 
			
		||||
@@ -160,18 +245,25 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
  bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
			
		||||
  bool send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
			
		||||
  void send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
			
		||||
  static bool try_send_alarm_control_panel_state(APIConnection *api, void *v_a_alarm_control_panel);
 | 
			
		||||
  static bool try_send_alarm_control_panel_info(APIConnection *api, void *v_a_alarm_control_panel);
 | 
			
		||||
  void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_EVENT
 | 
			
		||||
  bool send_event(event::Event *event, std::string event_type);
 | 
			
		||||
  bool send_event_info(event::Event *event);
 | 
			
		||||
  void send_event(event::Event *event, std::string event_type);
 | 
			
		||||
  void send_event_info(event::Event *event);
 | 
			
		||||
  static bool try_send_event(APIConnection *api, void *v_event);
 | 
			
		||||
  static bool try_send_event(APIConnection *api, event::Event *event, std::string event_type);
 | 
			
		||||
  static bool try_send_event_info(APIConnection *api, void *v_event);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  bool send_update_state(update::UpdateEntity *update);
 | 
			
		||||
  bool send_update_info(update::UpdateEntity *update);
 | 
			
		||||
  void send_update_info(update::UpdateEntity *update);
 | 
			
		||||
  static bool try_send_update_state(APIConnection *api, void *v_update);
 | 
			
		||||
  static bool try_send_update_info(APIConnection *api, void *v_update);
 | 
			
		||||
  void update_command(const UpdateCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@@ -209,6 +301,9 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
  void execute_service(const ExecuteServiceRequest &msg) override;
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
  NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  bool is_authenticated() override { return this->connection_state_ == ConnectionState::AUTHENTICATED; }
 | 
			
		||||
  bool is_connection_setup() override {
 | 
			
		||||
@@ -217,9 +312,10 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  void on_fatal_error() override;
 | 
			
		||||
  void on_unauthenticated_access() override;
 | 
			
		||||
  void on_no_setup_connection() override;
 | 
			
		||||
  ProtoWriteBuffer create_buffer() override {
 | 
			
		||||
  ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
 | 
			
		||||
    // FIXME: ensure no recursive writes can happen
 | 
			
		||||
    this->proto_write_buffer_.clear();
 | 
			
		||||
    this->proto_write_buffer_.reserve(reserve_size);
 | 
			
		||||
    return {&this->proto_write_buffer_};
 | 
			
		||||
  }
 | 
			
		||||
  bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
 | 
			
		||||
@@ -262,6 +358,7 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
  bool service_call_subscription_{false};
 | 
			
		||||
  bool next_close_ = false;
 | 
			
		||||
  APIServer *parent_;
 | 
			
		||||
  DeferredMessageQueue deferred_message_queue_;
 | 
			
		||||
  InitialStateIterator initial_state_iterator_;
 | 
			
		||||
  ListEntitiesIterator list_entities_iterator_;
 | 
			
		||||
  int state_subs_at_ = -1;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "proto.h"
 | 
			
		||||
#include "api_pb2_size.h"
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -311,6 +312,10 @@ APIError APINoiseFrameHelper::state_action_() {
 | 
			
		||||
    const std::string &name = App.get_name();
 | 
			
		||||
    const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str());
 | 
			
		||||
    msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1);
 | 
			
		||||
    // node mac, terminated by null byte
 | 
			
		||||
    const std::string &mac = get_mac_address();
 | 
			
		||||
    const uint8_t *mac_ptr = reinterpret_cast<const uint8_t *>(mac.c_str());
 | 
			
		||||
    msg.insert(msg.end(), mac_ptr, mac_ptr + mac.size() + 1);
 | 
			
		||||
 | 
			
		||||
    aerr = write_frame_(msg.data(), msg.size());
 | 
			
		||||
    if (aerr != APIError::OK)
 | 
			
		||||
@@ -570,6 +575,8 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
 | 
			
		||||
  if (!tx_buf_.empty()) {
 | 
			
		||||
    // tx buf not empty, can't write now because then stream would be inconsistent
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf_.reserve(tx_buf_.size() + total_write_len);
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
@@ -580,6 +587,8 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
  ssize_t sent = socket_->writev(iov, iovcnt);
 | 
			
		||||
  if (is_would_block(sent)) {
 | 
			
		||||
    // operation would block, add buffer to tx_buf
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf_.reserve(tx_buf_.size() + total_write_len);
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
@@ -592,6 +601,10 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
 | 
			
		||||
    return APIError::SOCKET_WRITE_FAILED;
 | 
			
		||||
  } else if ((size_t) sent != total_write_len) {
 | 
			
		||||
    // partially sent, add end to tx_buf
 | 
			
		||||
    size_t remaining = total_write_len - sent;
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf_.reserve(tx_buf_.size() + remaining);
 | 
			
		||||
 | 
			
		||||
    size_t to_consume = sent;
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      if (to_consume >= iov[i].iov_len) {
 | 
			
		||||
@@ -893,8 +906,28 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
 | 
			
		||||
 | 
			
		||||
  ParsedFrame frame;
 | 
			
		||||
  aerr = try_read_frame_(&frame);
 | 
			
		||||
  if (aerr != APIError::OK)
 | 
			
		||||
  if (aerr != APIError::OK) {
 | 
			
		||||
    if (aerr == APIError::BAD_INDICATOR) {
 | 
			
		||||
      // Make sure to tell the remote that we don't
 | 
			
		||||
      // understand the indicator byte so it knows
 | 
			
		||||
      // we do not support it.
 | 
			
		||||
      struct iovec iov[1];
 | 
			
		||||
      // The \x00 first byte is the marker for plaintext.
 | 
			
		||||
      //
 | 
			
		||||
      // The remote will know how to handle the indicator byte,
 | 
			
		||||
      // but it likely won't understand the rest of the message.
 | 
			
		||||
      //
 | 
			
		||||
      // We must send at least 3 bytes to be read, so we add
 | 
			
		||||
      // a message after the indicator byte to ensures its long
 | 
			
		||||
      // enough and can aid in debugging.
 | 
			
		||||
      const char msg[] = "\x00"
 | 
			
		||||
                         "Bad indicator byte";
 | 
			
		||||
      iov[0].iov_base = (void *) msg;
 | 
			
		||||
      iov[0].iov_len = 19;
 | 
			
		||||
      write_raw_(iov, 1);
 | 
			
		||||
    }
 | 
			
		||||
    return aerr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  buffer->container = std::move(frame.msg);
 | 
			
		||||
  buffer->data_offset = 0;
 | 
			
		||||
@@ -909,6 +942,8 @@ APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *pay
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::vector<uint8_t> header;
 | 
			
		||||
  header.reserve(1 + api::ProtoSize::varint(static_cast<uint32_t>(payload_len)) +
 | 
			
		||||
                 api::ProtoSize::varint(static_cast<uint32_t>(type)));
 | 
			
		||||
  header.push_back(0x00);
 | 
			
		||||
  ProtoVarInt(payload_len).encode(header);
 | 
			
		||||
  ProtoVarInt(type).encode(header);
 | 
			
		||||
@@ -970,6 +1005,8 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt
 | 
			
		||||
 | 
			
		||||
  if (!tx_buf_.empty()) {
 | 
			
		||||
    // tx buf not empty, can't write now because then stream would be inconsistent
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf_.reserve(tx_buf_.size() + total_write_len);
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
@@ -980,6 +1017,8 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt
 | 
			
		||||
  ssize_t sent = socket_->writev(iov, iovcnt);
 | 
			
		||||
  if (is_would_block(sent)) {
 | 
			
		||||
    // operation would block, add buffer to tx_buf
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf_.reserve(tx_buf_.size() + total_write_len);
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
 | 
			
		||||
                     reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
 | 
			
		||||
@@ -992,6 +1031,10 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt
 | 
			
		||||
    return APIError::SOCKET_WRITE_FAILED;
 | 
			
		||||
  } else if ((size_t) sent != total_write_len) {
 | 
			
		||||
    // partially sent, add end to tx_buf
 | 
			
		||||
    size_t remaining = total_write_len - sent;
 | 
			
		||||
    // Reserve space upfront to avoid multiple reallocations
 | 
			
		||||
    tx_buf_.reserve(tx_buf_.size() + remaining);
 | 
			
		||||
 | 
			
		||||
    size_t to_consume = sent;
 | 
			
		||||
    for (int i = 0; i < iovcnt; i++) {
 | 
			
		||||
      if (to_consume >= iov[i].iov_len) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -11,11 +11,20 @@ using psk_t = std::array<uint8_t, 32>;
 | 
			
		||||
 | 
			
		||||
class APINoiseContext {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_psk(psk_t psk) { psk_ = psk; }
 | 
			
		||||
  const psk_t &get_psk() const { return psk_; }
 | 
			
		||||
  void set_psk(psk_t psk) {
 | 
			
		||||
    this->psk_ = psk;
 | 
			
		||||
    bool has_psk = false;
 | 
			
		||||
    for (auto i : psk) {
 | 
			
		||||
      has_psk |= i;
 | 
			
		||||
    }
 | 
			
		||||
    this->has_psk_ = has_psk;
 | 
			
		||||
  }
 | 
			
		||||
  const psk_t &get_psk() const { return this->psk_; }
 | 
			
		||||
  bool has_psk() const { return this->has_psk_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  psk_t psk_;
 | 
			
		||||
  psk_t psk_{};
 | 
			
		||||
  bool has_psk_{false};
 | 
			
		||||
};
 | 
			
		||||
#endif  // USE_API_NOISE
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
// This file was automatically generated with a tool.
 | 
			
		||||
// See scripts/api_protobuf/api_protobuf.py
 | 
			
		||||
// See script/api_protobuf/api_protobuf.py
 | 
			
		||||
#include "api_pb2_service.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
@@ -179,6 +179,16 @@ bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorSt
 | 
			
		||||
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
 | 
			
		||||
  return this->send_message_<SubscribeLogsResponse>(msg, 29);
 | 
			
		||||
}
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
bool APIServerConnectionBase::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_noise_encryption_set_key_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<NoiseEncryptionSetKeyResponse>(msg, 125);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
 | 
			
		||||
@@ -462,6 +472,16 @@ bool APIServerConnectionBase::send_bluetooth_device_clear_cache_response(const B
 | 
			
		||||
  return this->send_message_<BluetoothDeviceClearCacheResponse>(msg, 88);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_scanner_state_response(const BluetoothScannerStateResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_scanner_state_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothScannerStateResponse>(msg, 126);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
@@ -1191,6 +1211,28 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
			
		||||
      ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_voice_assistant_set_configuration(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 124: {
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
      NoiseEncryptionSetKeyRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_noise_encryption_set_key_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_noise_encryption_set_key_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 127: {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
      BluetoothScannerSetModeRequest msg;
 | 
			
		||||
      msg.decode(msg_data, msg_size);
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
      ESP_LOGVV(TAG, "on_bluetooth_scanner_set_mode_request: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
      this->on_bluetooth_scanner_set_mode_request(msg);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -1311,6 +1353,22 @@ void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest
 | 
			
		||||
  }
 | 
			
		||||
  this->execute_service(msg);
 | 
			
		||||
}
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  NoiseEncryptionSetKeyResponse ret = this->noise_encryption_set_key(msg);
 | 
			
		||||
  if (!this->send_noise_encryption_set_key_response(ret)) {
 | 
			
		||||
    this->on_fatal_error();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
@@ -1668,6 +1726,19 @@ void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
  this->unsubscribe_bluetooth_le_advertisements(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
    this->on_no_setup_connection();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->is_authenticated()) {
 | 
			
		||||
    this->on_unauthenticated_access();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->bluetooth_scanner_set_mode(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
 | 
			
		||||
  if (!this->is_connection_setup()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
// This file was automatically generated with a tool.
 | 
			
		||||
// See scripts/api_protobuf/api_protobuf.py
 | 
			
		||||
// See script/api_protobuf/api_protobuf.py
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "api_pb2.h"
 | 
			
		||||
@@ -83,6 +83,12 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
#endif
 | 
			
		||||
  virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
 | 
			
		||||
  bool send_subscribe_logs_response(const SubscribeLogsResponse &msg);
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
  virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
  bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
  virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
 | 
			
		||||
  bool send_homeassistant_service_response(const HomeassistantServiceResponse &msg);
 | 
			
		||||
  virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
 | 
			
		||||
@@ -228,6 +234,12 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_device_clear_cache_response(const BluetoothDeviceClearCacheResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_scanner_state_response(const BluetoothScannerStateResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
 | 
			
		||||
#endif
 | 
			
		||||
@@ -349,6 +361,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
  virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
 | 
			
		||||
  virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
 | 
			
		||||
  virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
  virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
  virtual void cover_command(const CoverCommandRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -431,6 +446,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  virtual void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -457,6 +475,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
  void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
 | 
			
		||||
  void on_get_time_request(const GetTimeRequest &msg) override;
 | 
			
		||||
  void on_execute_service_request(const ExecuteServiceRequest &msg) override;
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
  void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
  void on_cover_command_request(const CoverCommandRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
@@ -539,6 +560,9 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
			
		||||
  void on_unsubscribe_bluetooth_le_advertisements_request(
 | 
			
		||||
      const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
  void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										361
									
								
								esphome/components/api/api_pb2_size.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										361
									
								
								esphome/components/api/api_pb2_size.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,361 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "proto.h"
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
class ProtoSize {
 | 
			
		||||
 public:
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief ProtoSize class for Protocol Buffer serialization size calculation
 | 
			
		||||
   *
 | 
			
		||||
   * This class provides static methods to calculate the exact byte counts needed
 | 
			
		||||
   * for encoding various Protocol Buffer field types. All methods are designed to be
 | 
			
		||||
   * efficient for the common case where many fields have default values.
 | 
			
		||||
   *
 | 
			
		||||
   * Implements Protocol Buffer encoding size calculation according to:
 | 
			
		||||
   * https://protobuf.dev/programming-guides/encoding/
 | 
			
		||||
   *
 | 
			
		||||
   * Key features:
 | 
			
		||||
   * - Early-return optimization for zero/default values
 | 
			
		||||
   * - Direct total_size updates to avoid unnecessary additions
 | 
			
		||||
   * - Specialized handling for different field types according to protobuf spec
 | 
			
		||||
   * - Templated helpers for repeated fields and messages
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
 | 
			
		||||
   *
 | 
			
		||||
   * @param value The uint32_t value to calculate size for
 | 
			
		||||
   * @return The number of bytes needed to encode the value
 | 
			
		||||
   */
 | 
			
		||||
  static inline uint32_t varint(uint32_t value) {
 | 
			
		||||
    // Optimized varint size calculation using leading zeros
 | 
			
		||||
    // Each 7 bits requires one byte in the varint encoding
 | 
			
		||||
    if (value < 128)
 | 
			
		||||
      return 1;  // 7 bits, common case for small values
 | 
			
		||||
 | 
			
		||||
    // For larger values, count bytes needed based on the position of the highest bit set
 | 
			
		||||
    if (value < 16384) {
 | 
			
		||||
      return 2;  // 14 bits
 | 
			
		||||
    } else if (value < 2097152) {
 | 
			
		||||
      return 3;  // 21 bits
 | 
			
		||||
    } else if (value < 268435456) {
 | 
			
		||||
      return 4;  // 28 bits
 | 
			
		||||
    } else {
 | 
			
		||||
      return 5;  // 32 bits (maximum for uint32_t)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates the size in bytes needed to encode a uint64_t value as a varint
 | 
			
		||||
   *
 | 
			
		||||
   * @param value The uint64_t value to calculate size for
 | 
			
		||||
   * @return The number of bytes needed to encode the value
 | 
			
		||||
   */
 | 
			
		||||
  static inline uint32_t varint(uint64_t value) {
 | 
			
		||||
    // Handle common case of values fitting in uint32_t (vast majority of use cases)
 | 
			
		||||
    if (value <= UINT32_MAX) {
 | 
			
		||||
      return varint(static_cast<uint32_t>(value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // For larger values, determine size based on highest bit position
 | 
			
		||||
    if (value < (1ULL << 35)) {
 | 
			
		||||
      return 5;  // 35 bits
 | 
			
		||||
    } else if (value < (1ULL << 42)) {
 | 
			
		||||
      return 6;  // 42 bits
 | 
			
		||||
    } else if (value < (1ULL << 49)) {
 | 
			
		||||
      return 7;  // 49 bits
 | 
			
		||||
    } else if (value < (1ULL << 56)) {
 | 
			
		||||
      return 8;  // 56 bits
 | 
			
		||||
    } else if (value < (1ULL << 63)) {
 | 
			
		||||
      return 9;  // 63 bits
 | 
			
		||||
    } else {
 | 
			
		||||
      return 10;  // 64 bits (maximum for uint64_t)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates the size in bytes needed to encode an int32_t value as a varint
 | 
			
		||||
   *
 | 
			
		||||
   * Special handling is needed for negative values, which are sign-extended to 64 bits
 | 
			
		||||
   * in Protocol Buffers, resulting in a 10-byte varint.
 | 
			
		||||
   *
 | 
			
		||||
   * @param value The int32_t value to calculate size for
 | 
			
		||||
   * @return The number of bytes needed to encode the value
 | 
			
		||||
   */
 | 
			
		||||
  static inline uint32_t varint(int32_t value) {
 | 
			
		||||
    // Negative values are sign-extended to 64 bits in protocol buffers,
 | 
			
		||||
    // which always results in a 10-byte varint for negative int32
 | 
			
		||||
    if (value < 0) {
 | 
			
		||||
      return 10;  // Negative int32 is always 10 bytes long
 | 
			
		||||
    }
 | 
			
		||||
    // For non-negative values, use the uint32_t implementation
 | 
			
		||||
    return varint(static_cast<uint32_t>(value));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates the size in bytes needed to encode an int64_t value as a varint
 | 
			
		||||
   *
 | 
			
		||||
   * @param value The int64_t value to calculate size for
 | 
			
		||||
   * @return The number of bytes needed to encode the value
 | 
			
		||||
   */
 | 
			
		||||
  static inline uint32_t varint(int64_t value) {
 | 
			
		||||
    // For int64_t, we convert to uint64_t and calculate the size
 | 
			
		||||
    // This works because the bit pattern determines the encoding size,
 | 
			
		||||
    // and we've handled negative int32 values as a special case above
 | 
			
		||||
    return varint(static_cast<uint64_t>(value));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates the size in bytes needed to encode a field ID and wire type
 | 
			
		||||
   *
 | 
			
		||||
   * @param field_id The field identifier
 | 
			
		||||
   * @param type The wire type value (from the WireType enum in the protobuf spec)
 | 
			
		||||
   * @return The number of bytes needed to encode the field ID and wire type
 | 
			
		||||
   */
 | 
			
		||||
  static inline uint32_t field(uint32_t field_id, uint32_t type) {
 | 
			
		||||
    uint32_t tag = (field_id << 3) | (type & 0b111);
 | 
			
		||||
    return varint(tag);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Common parameters for all add_*_field methods
 | 
			
		||||
   *
 | 
			
		||||
   * All add_*_field methods follow these common patterns:
 | 
			
		||||
   *
 | 
			
		||||
   * @param total_size Reference to the total message size to update
 | 
			
		||||
   * @param field_id_size Pre-calculated size of the field ID in bytes
 | 
			
		||||
   * @param value The value to calculate size for (type varies)
 | 
			
		||||
   * @param force Whether to calculate size even if the value is default/zero/empty
 | 
			
		||||
   *
 | 
			
		||||
   * Each method follows this implementation pattern:
 | 
			
		||||
   * 1. Skip calculation if value is default (0, false, empty) and not forced
 | 
			
		||||
   * 2. Calculate the size based on the field's encoding rules
 | 
			
		||||
   * 3. Add the field_id_size + calculated value size to total_size
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of an int32 field to the total message size
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (value == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate and directly add to total_size
 | 
			
		||||
    if (value < 0) {
 | 
			
		||||
      // Negative values are encoded as 10-byte varints in protobuf
 | 
			
		||||
      total_size += field_id_size + 10;
 | 
			
		||||
    } else {
 | 
			
		||||
      // For non-negative values, use the standard varint size
 | 
			
		||||
      total_size += field_id_size + varint(static_cast<uint32_t>(value));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a uint32 field to the total message size
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value,
 | 
			
		||||
                                      bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (value == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate and directly add to total_size
 | 
			
		||||
    total_size += field_id_size + varint(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a boolean field to the total message size
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value, bool force = false) {
 | 
			
		||||
    // Skip calculation if value is false and not forced
 | 
			
		||||
    if (!value && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Boolean fields always use 1 byte when true
 | 
			
		||||
    total_size += field_id_size + 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a fixed field to the total message size
 | 
			
		||||
   *
 | 
			
		||||
   * Fixed fields always take exactly N bytes (4 for fixed32/float, 8 for fixed64/double).
 | 
			
		||||
   *
 | 
			
		||||
   * @tparam NumBytes The number of bytes for this fixed field (4 or 8)
 | 
			
		||||
   * @param is_nonzero Whether the value is non-zero
 | 
			
		||||
   */
 | 
			
		||||
  template<uint32_t NumBytes>
 | 
			
		||||
  static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero,
 | 
			
		||||
                                     bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (!is_nonzero && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Fixed fields always take exactly NumBytes
 | 
			
		||||
    total_size += field_id_size + NumBytes;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of an enum field to the total message size
 | 
			
		||||
   *
 | 
			
		||||
   * Enum fields are encoded as uint32 varints.
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value, bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (value == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Enums are encoded as uint32
 | 
			
		||||
    total_size += field_id_size + varint(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a sint32 field to the total message size
 | 
			
		||||
   *
 | 
			
		||||
   * Sint32 fields use ZigZag encoding, which is more efficient for negative values.
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (value == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
 | 
			
		||||
    uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
 | 
			
		||||
    total_size += field_id_size + varint(zigzag);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of an int64 field to the total message size
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (value == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate and directly add to total_size
 | 
			
		||||
    total_size += field_id_size + varint(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a uint64 field to the total message size
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value,
 | 
			
		||||
                                      bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (value == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate and directly add to total_size
 | 
			
		||||
    total_size += field_id_size + varint(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a sint64 field to the total message size
 | 
			
		||||
   *
 | 
			
		||||
   * Sint64 fields use ZigZag encoding, which is more efficient for negative values.
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_sint64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) {
 | 
			
		||||
    // Skip calculation if value is zero and not forced
 | 
			
		||||
    if (value == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ZigZag encoding for sint64: (n << 1) ^ (n >> 63)
 | 
			
		||||
    uint64_t zigzag = (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
 | 
			
		||||
    total_size += field_id_size + varint(zigzag);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a string/bytes field to the total message size
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, const std::string &str,
 | 
			
		||||
                                      bool force = false) {
 | 
			
		||||
    // Skip calculation if string is empty and not forced
 | 
			
		||||
    if (str.empty() && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate and directly add to total_size
 | 
			
		||||
    const uint32_t str_size = static_cast<uint32_t>(str.size());
 | 
			
		||||
    total_size += field_id_size + varint(str_size) + str_size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a nested message field to the total message size
 | 
			
		||||
   *
 | 
			
		||||
   * This helper function directly updates the total_size reference if the nested size
 | 
			
		||||
   * is greater than zero or force is true.
 | 
			
		||||
   *
 | 
			
		||||
   * @param nested_size The pre-calculated size of the nested message
 | 
			
		||||
   */
 | 
			
		||||
  static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size,
 | 
			
		||||
                                       bool force = false) {
 | 
			
		||||
    // Skip calculation if nested message is empty and not forced
 | 
			
		||||
    if (nested_size == 0 && !force) {
 | 
			
		||||
      return;  // No need to update total_size
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate and directly add to total_size
 | 
			
		||||
    // Field ID + length varint + nested message content
 | 
			
		||||
    total_size += field_id_size + varint(nested_size) + nested_size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the size of a nested message field to the total message size
 | 
			
		||||
   *
 | 
			
		||||
   * This templated version directly takes a message object, calculates its size internally,
 | 
			
		||||
   * and updates the total_size reference. This eliminates the need for a temporary variable
 | 
			
		||||
   * at the call site.
 | 
			
		||||
   *
 | 
			
		||||
   * @tparam MessageType The type of the nested message (inferred from parameter)
 | 
			
		||||
   * @param message The nested message object
 | 
			
		||||
   */
 | 
			
		||||
  template<typename MessageType>
 | 
			
		||||
  static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const MessageType &message,
 | 
			
		||||
                                        bool force = false) {
 | 
			
		||||
    uint32_t nested_size = 0;
 | 
			
		||||
    message.calculate_size(nested_size);
 | 
			
		||||
 | 
			
		||||
    // Use the base implementation with the calculated nested_size
 | 
			
		||||
    add_message_field(total_size, field_id_size, nested_size, force);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @brief Calculates and adds the sizes of all messages in a repeated field to the total message size
 | 
			
		||||
   *
 | 
			
		||||
   * This helper processes a vector of message objects, calculating the size for each message
 | 
			
		||||
   * and adding it to the total size.
 | 
			
		||||
   *
 | 
			
		||||
   * @tparam MessageType The type of the nested messages in the vector
 | 
			
		||||
   * @param messages Vector of message objects
 | 
			
		||||
   */
 | 
			
		||||
  template<typename MessageType>
 | 
			
		||||
  static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size,
 | 
			
		||||
                                          const std::vector<MessageType> &messages) {
 | 
			
		||||
    // Skip if the vector is empty
 | 
			
		||||
    if (messages.empty()) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // For repeated fields, always use force=true
 | 
			
		||||
    for (const auto &message : messages) {
 | 
			
		||||
      add_message_object(total_size, field_id_size, message, true);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -14,6 +14,96 @@
 | 
			
		||||
#include "esphome/components/logger/logger.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_HEAP_TRACE
 | 
			
		||||
#include "esp_heap_trace.h"
 | 
			
		||||
#include "esp_heap_caps.h"
 | 
			
		||||
#include "freertos/FreeRTOS.h"
 | 
			
		||||
#include "freertos/task.h"
 | 
			
		||||
 | 
			
		||||
// Forward declare heap tracing functions that will be used in the API class
 | 
			
		||||
extern "C" void start_heap_trace();
 | 
			
		||||
extern "C" void stop_and_dump_heap_trace();
 | 
			
		||||
 | 
			
		||||
// Task heap information tracking
 | 
			
		||||
extern "C" void dump_task_heap_info() {
 | 
			
		||||
  // Get basic heap statistics
 | 
			
		||||
  multi_heap_info_t info;
 | 
			
		||||
  heap_caps_get_info(&info, MALLOC_CAP_INTERNAL);
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI("HEAP", "=== Task Heap Information ===");
 | 
			
		||||
  ESP_LOGI("HEAP", "-------------------------------------");
 | 
			
		||||
  ESP_LOGI("HEAP", "Total free bytes: %u", info.total_free_bytes);
 | 
			
		||||
  ESP_LOGI("HEAP", "Total allocated bytes: %u", info.total_allocated_bytes);
 | 
			
		||||
  ESP_LOGI("HEAP", "Minimum free bytes: %u", info.minimum_free_bytes);
 | 
			
		||||
  ESP_LOGI("HEAP", "Largest free block: %u", info.largest_free_block);
 | 
			
		||||
  ESP_LOGI("HEAP", "Free blocks: %u", info.free_blocks);
 | 
			
		||||
  ESP_LOGI("HEAP", "Allocated blocks: %u", info.allocated_blocks);
 | 
			
		||||
  ESP_LOGI("HEAP", "Total blocks: %u", info.total_blocks);
 | 
			
		||||
  ESP_LOGI("HEAP", "-------------------------------------");
 | 
			
		||||
 | 
			
		||||
  // Get information about running tasks with a much larger buffer to prevent overflow
 | 
			
		||||
  // The FreeRTOS functions don't provide a way to check buffer size requirements in advance
 | 
			
		||||
  static char buffer[2048];
 | 
			
		||||
 | 
			
		||||
  // Zero out the buffer for safety
 | 
			
		||||
  memset(buffer, 0, sizeof(buffer));
 | 
			
		||||
 | 
			
		||||
  // Get task list
 | 
			
		||||
  vTaskList(buffer);
 | 
			
		||||
 | 
			
		||||
  // Check if buffer has valid content
 | 
			
		||||
  if (buffer[0] != '\0') {
 | 
			
		||||
    ESP_LOGI("HEAP", "Task Information:");
 | 
			
		||||
    ESP_LOGI("HEAP", "Name          State  Priority  Stack   Num");
 | 
			
		||||
    ESP_LOGI("HEAP", "-------------------------------------");
 | 
			
		||||
 | 
			
		||||
    // Process the buffer line by line to add the log prefix to each line
 | 
			
		||||
    char *line = strtok(buffer, "\n\r");
 | 
			
		||||
    int count = 0;
 | 
			
		||||
    while (line != nullptr && strlen(line) > 0 && count < 20) {
 | 
			
		||||
      ESP_LOGI("HEAP", "%s", line);
 | 
			
		||||
      line = strtok(nullptr, "\n\r");
 | 
			
		||||
      count++;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGE("HEAP", "Could not get task information");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI("HEAP", "-------------------------------------");
 | 
			
		||||
 | 
			
		||||
  // Runtime statistics - use a separate section with a different buffer to avoid corruption
 | 
			
		||||
  static char stats_buffer[2048];
 | 
			
		||||
  memset(stats_buffer, 0, sizeof(stats_buffer));
 | 
			
		||||
 | 
			
		||||
  // Get runtime stats
 | 
			
		||||
  vTaskGetRunTimeStats(stats_buffer);
 | 
			
		||||
 | 
			
		||||
  // Check if buffer has valid content
 | 
			
		||||
  if (stats_buffer[0] != '\0') {
 | 
			
		||||
    ESP_LOGI("HEAP", "Task Runtime Statistics:");
 | 
			
		||||
    ESP_LOGI("HEAP", "Name          Time      Percentage");
 | 
			
		||||
    ESP_LOGI("HEAP", "-------------------------------------");
 | 
			
		||||
 | 
			
		||||
    // Process the runtime stats buffer line by line safely
 | 
			
		||||
    char *line = strtok(stats_buffer, "\n\r");
 | 
			
		||||
    int count = 0;
 | 
			
		||||
    // Limit to 20 lines to prevent buffer overruns
 | 
			
		||||
    while (line != nullptr && count < 20) {
 | 
			
		||||
      // Skip empty lines
 | 
			
		||||
      if (strlen(line) > 0) {
 | 
			
		||||
        ESP_LOGI("HEAP", "%s", line);
 | 
			
		||||
      }
 | 
			
		||||
      line = strtok(nullptr, "\n\r");
 | 
			
		||||
      count++;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGE("HEAP", "Could not get task runtime statistics");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI("HEAP", "-------------------------------------");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -22,22 +112,45 @@ namespace api {
 | 
			
		||||
static const char *const TAG = "api";
 | 
			
		||||
 | 
			
		||||
// APIServer
 | 
			
		||||
APIServer *global_api_server = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
APIServer::APIServer() { global_api_server = this; }
 | 
			
		||||
 | 
			
		||||
void APIServer::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
 | 
			
		||||
  this->setup_controller();
 | 
			
		||||
  socket_ = socket::socket_ip(SOCK_STREAM, 0);
 | 
			
		||||
  if (socket_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Could not create socket.");
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_HEAP_TRACE
 | 
			
		||||
  ESP_LOGI(TAG, "Initializing heap tracing");
 | 
			
		||||
  start_heap_trace();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
  uint32_t hash = 88491486UL;
 | 
			
		||||
 | 
			
		||||
  this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true);
 | 
			
		||||
 | 
			
		||||
  SavedNoisePsk noise_pref_saved{};
 | 
			
		||||
  if (this->noise_pref_.load(&noise_pref_saved)) {
 | 
			
		||||
    ESP_LOGD(TAG, "Loaded saved Noise PSK");
 | 
			
		||||
 | 
			
		||||
    this->set_noise_psk(noise_pref_saved.psk);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  this->socket_ = socket::socket_ip(SOCK_STREAM, 0);
 | 
			
		||||
  if (this->socket_ == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Could not create socket");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  int enable = 1;
 | 
			
		||||
  int err = socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
 | 
			
		||||
  int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
 | 
			
		||||
  if (err != 0) {
 | 
			
		||||
    ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
 | 
			
		||||
    // we can still continue
 | 
			
		||||
  }
 | 
			
		||||
  err = socket_->setblocking(false);
 | 
			
		||||
  err = this->socket_->setblocking(false);
 | 
			
		||||
  if (err != 0) {
 | 
			
		||||
    ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
@@ -53,14 +166,14 @@ void APIServer::setup() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  err = socket_->bind((struct sockaddr *) &server, sl);
 | 
			
		||||
  err = this->socket_->bind((struct sockaddr *) &server, sl);
 | 
			
		||||
  if (err != 0) {
 | 
			
		||||
    ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  err = socket_->listen(4);
 | 
			
		||||
  err = this->socket_->listen(4);
 | 
			
		||||
  if (err != 0) {
 | 
			
		||||
    ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
@@ -72,7 +185,7 @@ void APIServer::setup() {
 | 
			
		||||
    logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
 | 
			
		||||
      for (auto &c : this->clients_) {
 | 
			
		||||
        if (!c->remove_)
 | 
			
		||||
          c->send_log_message(level, tag, message);
 | 
			
		||||
          c->try_send_log_message(level, tag, message);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
@@ -86,24 +199,25 @@ void APIServer::setup() {
 | 
			
		||||
        [this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
 | 
			
		||||
          for (auto &c : this->clients_) {
 | 
			
		||||
            if (!c->remove_)
 | 
			
		||||
              c->send_camera_state(image);
 | 
			
		||||
              c->set_camera_state(image);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::loop() {
 | 
			
		||||
  // Accept new clients
 | 
			
		||||
  while (true) {
 | 
			
		||||
    struct sockaddr_storage source_addr;
 | 
			
		||||
    socklen_t addr_len = sizeof(source_addr);
 | 
			
		||||
    auto sock = socket_->accept((struct sockaddr *) &source_addr, &addr_len);
 | 
			
		||||
    auto sock = this->socket_->accept((struct sockaddr *) &source_addr, &addr_len);
 | 
			
		||||
    if (!sock)
 | 
			
		||||
      break;
 | 
			
		||||
    ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
 | 
			
		||||
 | 
			
		||||
    auto *conn = new APIConnection(std::move(sock), this);
 | 
			
		||||
    clients_.emplace_back(conn);
 | 
			
		||||
    this->clients_.emplace_back(conn);
 | 
			
		||||
    conn->start();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -135,17 +249,41 @@ void APIServer::loop() {
 | 
			
		||||
      this->status_clear_warning();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_HEAP_TRACE
 | 
			
		||||
  // Periodically dump heap trace information (every 30 seconds)
 | 
			
		||||
  static uint32_t last_heap_trace_dump = 0;
 | 
			
		||||
  const uint32_t now = millis();
 | 
			
		||||
  if (now - last_heap_trace_dump > 30000) {  // 30 seconds
 | 
			
		||||
    ESP_LOGI(TAG, "Dumping heap trace information");
 | 
			
		||||
    stop_and_dump_heap_trace();
 | 
			
		||||
 | 
			
		||||
    // Also dump task-specific heap information
 | 
			
		||||
    dump_task_heap_info();
 | 
			
		||||
 | 
			
		||||
    // Start a new trace for the next period
 | 
			
		||||
    start_heap_trace();
 | 
			
		||||
 | 
			
		||||
    last_heap_trace_dump = now;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "API Server:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Address: %s:%u", network::get_use_address().c_str(), this->port_);
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Using noise encryption: YES");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Using noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
 | 
			
		||||
  if (!this->noise_ctx_->has_psk()) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Supports noise encryption: YES");
 | 
			
		||||
  }
 | 
			
		||||
#else
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Using noise encryption: NO");
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool APIServer::uses_password() const { return !this->password_.empty(); }
 | 
			
		||||
 | 
			
		||||
bool APIServer::check_password(const std::string &password) const {
 | 
			
		||||
  // depend only on input password length
 | 
			
		||||
  const char *a = this->password_.c_str();
 | 
			
		||||
@@ -174,7 +312,9 @@ bool APIServer::check_password(const std::string &password) const {
 | 
			
		||||
 | 
			
		||||
  return result == 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::handle_disconnect(APIConnection *conn) {}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
 | 
			
		||||
  if (obj->is_internal())
 | 
			
		||||
@@ -342,57 +482,6 @@ void APIServer::on_update(update::UpdateEntity *obj) {
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
 | 
			
		||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
 | 
			
		||||
APIServer *global_api_server = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
void APIServer::set_password(const std::string &password) { this->password_ = password; }
 | 
			
		||||
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_homeassistant_service_call(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APIServer::APIServer() { global_api_server = this; }
 | 
			
		||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
 | 
			
		||||
                                               std::function<void(std::string)> f) {
 | 
			
		||||
  this->state_subs_.push_back(HomeAssistantStateSubscription{
 | 
			
		||||
      .entity_id = std::move(entity_id),
 | 
			
		||||
      .attribute = std::move(attribute),
 | 
			
		||||
      .callback = std::move(f),
 | 
			
		||||
      .once = false,
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
 | 
			
		||||
                                         std::function<void(std::string)> f) {
 | 
			
		||||
  this->state_subs_.push_back(HomeAssistantStateSubscription{
 | 
			
		||||
      .entity_id = std::move(entity_id),
 | 
			
		||||
      .attribute = std::move(attribute),
 | 
			
		||||
      .callback = std::move(f),
 | 
			
		||||
      .once = true,
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
 | 
			
		||||
  return this->state_subs_;
 | 
			
		||||
}
 | 
			
		||||
uint16_t APIServer::get_port() const { return this->port_; }
 | 
			
		||||
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
 | 
			
		||||
#ifdef USE_HOMEASSISTANT_TIME
 | 
			
		||||
void APIServer::request_time() {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    if (!client->remove_ && client->is_authenticated())
 | 
			
		||||
      client->send_time_request();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool APIServer::is_connected() const { return !this->clients_.empty(); }
 | 
			
		||||
void APIServer::on_shutdown() {
 | 
			
		||||
  for (auto &c : this->clients_) {
 | 
			
		||||
    c->send_disconnect_request(DisconnectRequest());
 | 
			
		||||
  }
 | 
			
		||||
  delay(10);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
 | 
			
		||||
  if (obj->is_internal())
 | 
			
		||||
@@ -402,6 +491,106 @@ void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
 | 
			
		||||
 | 
			
		||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
 | 
			
		||||
 | 
			
		||||
void APIServer::set_password(const std::string &password) { this->password_ = password; }
 | 
			
		||||
 | 
			
		||||
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_homeassistant_service_call(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
 | 
			
		||||
                                               std::function<void(std::string)> f) {
 | 
			
		||||
  this->state_subs_.push_back(HomeAssistantStateSubscription{
 | 
			
		||||
      .entity_id = std::move(entity_id),
 | 
			
		||||
      .attribute = std::move(attribute),
 | 
			
		||||
      .callback = std::move(f),
 | 
			
		||||
      .once = false,
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
 | 
			
		||||
                                         std::function<void(std::string)> f) {
 | 
			
		||||
  this->state_subs_.push_back(HomeAssistantStateSubscription{
 | 
			
		||||
      .entity_id = std::move(entity_id),
 | 
			
		||||
      .attribute = std::move(attribute),
 | 
			
		||||
      .callback = std::move(f),
 | 
			
		||||
      .once = true,
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
 | 
			
		||||
  return this->state_subs_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t APIServer::get_port() const { return this->port_; }
 | 
			
		||||
 | 
			
		||||
void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
 | 
			
		||||
  auto &old_psk = this->noise_ctx_->get_psk();
 | 
			
		||||
  if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
 | 
			
		||||
    ESP_LOGW(TAG, "New PSK matches old");
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  SavedNoisePsk new_saved_psk{psk};
 | 
			
		||||
  if (!this->noise_pref_.save(&new_saved_psk)) {
 | 
			
		||||
    ESP_LOGW(TAG, "Failed to save Noise PSK");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  // ensure it's written immediately
 | 
			
		||||
  if (!global_preferences->sync()) {
 | 
			
		||||
    ESP_LOGW(TAG, "Failed to sync preferences");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "Noise PSK saved");
 | 
			
		||||
  if (make_active) {
 | 
			
		||||
    this->set_timeout(100, [this, psk]() {
 | 
			
		||||
      ESP_LOGW(TAG, "Disconnecting all clients to reset connections");
 | 
			
		||||
      this->set_noise_psk(psk);
 | 
			
		||||
      for (auto &c : this->clients_) {
 | 
			
		||||
        c->send_disconnect_request(DisconnectRequest());
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_HOMEASSISTANT_TIME
 | 
			
		||||
void APIServer::request_time() {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    if (!client->remove_ && client->is_authenticated())
 | 
			
		||||
      client->send_time_request();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
bool APIServer::is_connected() const { return !this->clients_.empty(); }
 | 
			
		||||
 | 
			
		||||
void APIServer::on_shutdown() {
 | 
			
		||||
  for (auto &c : this->clients_) {
 | 
			
		||||
    c->send_disconnect_request(DisconnectRequest());
 | 
			
		||||
  }
 | 
			
		||||
  delay(10);
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_HEAP_TRACE
 | 
			
		||||
  // Make sure to stop tracing on shutdown to get final results
 | 
			
		||||
  ESP_LOGI(TAG, "Final heap trace dump on shutdown");
 | 
			
		||||
  stop_and_dump_heap_trace();
 | 
			
		||||
 | 
			
		||||
  // Dump final task heap information
 | 
			
		||||
  ESP_LOGI(TAG, "Final task heap information dump on shutdown");
 | 
			
		||||
  dump_task_heap_info();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,12 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
struct SavedNoisePsk {
 | 
			
		||||
  psk_t psk;
 | 
			
		||||
} PACKED;  // NOLINT
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
class APIServer : public Component, public Controller {
 | 
			
		||||
 public:
 | 
			
		||||
  APIServer();
 | 
			
		||||
@@ -35,6 +41,7 @@ class APIServer : public Component, public Controller {
 | 
			
		||||
  void set_reboot_timeout(uint32_t reboot_timeout);
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
  bool save_noise_psk(psk_t psk, bool make_active = true);
 | 
			
		||||
  void set_noise_psk(psk_t psk) { noise_ctx_->set_psk(psk); }
 | 
			
		||||
  std::shared_ptr<APINoiseContext> get_noise_ctx() { return noise_ctx_; }
 | 
			
		||||
#endif  // USE_API_NOISE
 | 
			
		||||
@@ -142,6 +149,7 @@ class APIServer : public Component, public Controller {
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
  std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
 | 
			
		||||
  ESPPreferenceObject noise_pref_;
 | 
			
		||||
#endif  // USE_API_NOISE
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,11 @@
 | 
			
		||||
from __future__ import annotations
 | 
			
		||||
 | 
			
		||||
import asyncio
 | 
			
		||||
import logging
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from typing import Any
 | 
			
		||||
import logging
 | 
			
		||||
from typing import TYPE_CHECKING, Any
 | 
			
		||||
 | 
			
		||||
from aioesphomeapi import APIClient
 | 
			
		||||
from aioesphomeapi.api_pb2 import SubscribeLogsResponse
 | 
			
		||||
from aioesphomeapi.log_runner import async_run
 | 
			
		||||
 | 
			
		||||
from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__
 | 
			
		||||
@@ -14,6 +13,12 @@ from esphome.core import CORE
 | 
			
		||||
 | 
			
		||||
from . import CONF_ENCRYPTION
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from aioesphomeapi.api_pb2 import (
 | 
			
		||||
        SubscribeLogsResponse,  # pylint: disable=no-name-in-module
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,37 +10,63 @@ namespace api {
 | 
			
		||||
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
 | 
			
		||||
  return this->client_->send_binary_sensor_info(binary_sensor);
 | 
			
		||||
  this->client_->send_binary_sensor_info(binary_sensor);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_COVER
 | 
			
		||||
bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); }
 | 
			
		||||
bool ListEntitiesIterator::on_cover(cover::Cover *cover) {
 | 
			
		||||
  this->client_->send_cover_info(cover);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_FAN
 | 
			
		||||
bool ListEntitiesIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_info(fan); }
 | 
			
		||||
bool ListEntitiesIterator::on_fan(fan::Fan *fan) {
 | 
			
		||||
  this->client_->send_fan_info(fan);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LIGHT
 | 
			
		||||
bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); }
 | 
			
		||||
bool ListEntitiesIterator::on_light(light::LightState *light) {
 | 
			
		||||
  this->client_->send_light_info(light);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_info(sensor); }
 | 
			
		||||
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) {
 | 
			
		||||
  this->client_->send_sensor_info(sensor);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); }
 | 
			
		||||
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) {
 | 
			
		||||
  this->client_->send_switch_info(a_switch);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BUTTON
 | 
			
		||||
bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); }
 | 
			
		||||
bool ListEntitiesIterator::on_button(button::Button *button) {
 | 
			
		||||
  this->client_->send_button_info(button);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
 | 
			
		||||
  return this->client_->send_text_sensor_info(text_sensor);
 | 
			
		||||
  this->client_->send_text_sensor_info(text_sensor);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LOCK
 | 
			
		||||
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_info(a_lock); }
 | 
			
		||||
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) {
 | 
			
		||||
  this->client_->send_lock_info(a_lock);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_VALVE
 | 
			
		||||
bool ListEntitiesIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_info(valve); }
 | 
			
		||||
bool ListEntitiesIterator::on_valve(valve::Valve *valve) {
 | 
			
		||||
  this->client_->send_valve_info(valve);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
 | 
			
		||||
@@ -52,55 +78,83 @@ bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
 | 
			
		||||
  return this->client_->send_camera_info(camera);
 | 
			
		||||
  this->client_->send_camera_info(camera);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_CLIMATE
 | 
			
		||||
bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); }
 | 
			
		||||
bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
 | 
			
		||||
  this->client_->send_climate_info(climate);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); }
 | 
			
		||||
bool ListEntitiesIterator::on_number(number::Number *number) {
 | 
			
		||||
  this->client_->send_number_info(number);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_DATETIME_DATE
 | 
			
		||||
bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_info(date); }
 | 
			
		||||
bool ListEntitiesIterator::on_date(datetime::DateEntity *date) {
 | 
			
		||||
  this->client_->send_date_info(date);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_DATETIME_TIME
 | 
			
		||||
bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_info(time); }
 | 
			
		||||
bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) {
 | 
			
		||||
  this->client_->send_time_info(time);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_DATETIME_DATETIME
 | 
			
		||||
bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) {
 | 
			
		||||
  return this->client_->send_datetime_info(datetime);
 | 
			
		||||
  this->client_->send_datetime_info(datetime);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TEXT
 | 
			
		||||
bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); }
 | 
			
		||||
bool ListEntitiesIterator::on_text(text::Text *text) {
 | 
			
		||||
  this->client_->send_text_info(text);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
bool ListEntitiesIterator::on_select(select::Select *select) { return this->client_->send_select_info(select); }
 | 
			
		||||
bool ListEntitiesIterator::on_select(select::Select *select) {
 | 
			
		||||
  this->client_->send_select_info(select);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_MEDIA_PLAYER
 | 
			
		||||
bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_player) {
 | 
			
		||||
  return this->client_->send_media_player_info(media_player);
 | 
			
		||||
  this->client_->send_media_player_info(media_player);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
			
		||||
bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
 | 
			
		||||
  return this->client_->send_alarm_control_panel_info(a_alarm_control_panel);
 | 
			
		||||
  this->client_->send_alarm_control_panel_info(a_alarm_control_panel);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_EVENT
 | 
			
		||||
bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); }
 | 
			
		||||
bool ListEntitiesIterator::on_event(event::Event *event) {
 | 
			
		||||
  this->client_->send_event_info(event);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_info(update); }
 | 
			
		||||
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) {
 | 
			
		||||
  this->client_->send_update_info(update);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
 
 | 
			
		||||
@@ -80,6 +80,7 @@ class ListEntitiesIterator : public ComponentIterator {
 | 
			
		||||
  bool on_update(update::UpdateEntity *update) override;
 | 
			
		||||
#endif
 | 
			
		||||
  bool on_end() override;
 | 
			
		||||
  bool completed() { return this->state_ == IteratorState::NONE; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  APIConnection *client_;
 | 
			
		||||
 
 | 
			
		||||
@@ -149,6 +149,18 @@ class ProtoWriteBuffer {
 | 
			
		||||
  void write(uint8_t value) { this->buffer_->push_back(value); }
 | 
			
		||||
  void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); }
 | 
			
		||||
  void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); }
 | 
			
		||||
  /**
 | 
			
		||||
   * Encode a field key (tag/wire type combination).
 | 
			
		||||
   *
 | 
			
		||||
   * @param field_id Field number (tag) in the protobuf message
 | 
			
		||||
   * @param type Wire type value:
 | 
			
		||||
   *   - 0: Varint (int32, int64, uint32, uint64, sint32, sint64, bool, enum)
 | 
			
		||||
   *   - 1: 64-bit (fixed64, sfixed64, double)
 | 
			
		||||
   *   - 2: Length-delimited (string, bytes, embedded messages, packed repeated fields)
 | 
			
		||||
   *   - 5: 32-bit (fixed32, sfixed32, float)
 | 
			
		||||
   *
 | 
			
		||||
   * Following https://protobuf.dev/programming-guides/encoding/#structure
 | 
			
		||||
   */
 | 
			
		||||
  void encode_field_raw(uint32_t field_id, uint32_t type) {
 | 
			
		||||
    uint32_t val = (field_id << 3) | (type & 0b111);
 | 
			
		||||
    this->encode_varint_raw(val);
 | 
			
		||||
@@ -157,7 +169,7 @@ class ProtoWriteBuffer {
 | 
			
		||||
    if (len == 0 && !force)
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    this->encode_field_raw(field_id, 2);
 | 
			
		||||
    this->encode_field_raw(field_id, 2);  // type 2: Length-delimited string
 | 
			
		||||
    this->encode_varint_raw(len);
 | 
			
		||||
    auto *data = reinterpret_cast<const uint8_t *>(string);
 | 
			
		||||
    this->buffer_->insert(this->buffer_->end(), data, data + len);
 | 
			
		||||
@@ -171,26 +183,26 @@ class ProtoWriteBuffer {
 | 
			
		||||
  void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) {
 | 
			
		||||
    if (value == 0 && !force)
 | 
			
		||||
      return;
 | 
			
		||||
    this->encode_field_raw(field_id, 0);
 | 
			
		||||
    this->encode_field_raw(field_id, 0);  // type 0: Varint - uint32
 | 
			
		||||
    this->encode_varint_raw(value);
 | 
			
		||||
  }
 | 
			
		||||
  void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) {
 | 
			
		||||
    if (value == 0 && !force)
 | 
			
		||||
      return;
 | 
			
		||||
    this->encode_field_raw(field_id, 0);
 | 
			
		||||
    this->encode_field_raw(field_id, 0);  // type 0: Varint - uint64
 | 
			
		||||
    this->encode_varint_raw(ProtoVarInt(value));
 | 
			
		||||
  }
 | 
			
		||||
  void encode_bool(uint32_t field_id, bool value, bool force = false) {
 | 
			
		||||
    if (!value && !force)
 | 
			
		||||
      return;
 | 
			
		||||
    this->encode_field_raw(field_id, 0);
 | 
			
		||||
    this->encode_field_raw(field_id, 0);  // type 0: Varint - bool
 | 
			
		||||
    this->write(0x01);
 | 
			
		||||
  }
 | 
			
		||||
  void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
 | 
			
		||||
    if (value == 0 && !force)
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    this->encode_field_raw(field_id, 5);
 | 
			
		||||
    this->encode_field_raw(field_id, 5);  // type 5: 32-bit fixed32
 | 
			
		||||
    this->write((value >> 0) & 0xFF);
 | 
			
		||||
    this->write((value >> 8) & 0xFF);
 | 
			
		||||
    this->write((value >> 16) & 0xFF);
 | 
			
		||||
@@ -200,7 +212,7 @@ class ProtoWriteBuffer {
 | 
			
		||||
    if (value == 0 && !force)
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    this->encode_field_raw(field_id, 5);
 | 
			
		||||
    this->encode_field_raw(field_id, 1);  // type 1: 64-bit fixed64
 | 
			
		||||
    this->write((value >> 0) & 0xFF);
 | 
			
		||||
    this->write((value >> 8) & 0xFF);
 | 
			
		||||
    this->write((value >> 16) & 0xFF);
 | 
			
		||||
@@ -254,7 +266,7 @@ class ProtoWriteBuffer {
 | 
			
		||||
    this->encode_uint64(field_id, uvalue, force);
 | 
			
		||||
  }
 | 
			
		||||
  template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
 | 
			
		||||
    this->encode_field_raw(field_id, 2);
 | 
			
		||||
    this->encode_field_raw(field_id, 2);  // type 2: Length-delimited message
 | 
			
		||||
    size_t begin = this->buffer_->size();
 | 
			
		||||
 | 
			
		||||
    value.encode(*this);
 | 
			
		||||
@@ -276,6 +288,7 @@ class ProtoMessage {
 | 
			
		||||
  virtual ~ProtoMessage() = default;
 | 
			
		||||
  virtual void encode(ProtoWriteBuffer buffer) const = 0;
 | 
			
		||||
  void decode(const uint8_t *buffer, size_t length);
 | 
			
		||||
  virtual void calculate_size(uint32_t &total_size) const = 0;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  std::string dump() const;
 | 
			
		||||
  virtual void dump_to(std::string &out) const = 0;
 | 
			
		||||
@@ -298,13 +311,29 @@ class ProtoService {
 | 
			
		||||
  virtual void on_fatal_error() = 0;
 | 
			
		||||
  virtual void on_unauthenticated_access() = 0;
 | 
			
		||||
  virtual void on_no_setup_connection() = 0;
 | 
			
		||||
  virtual ProtoWriteBuffer create_buffer() = 0;
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a buffer with a reserved size.
 | 
			
		||||
   * @param reserve_size The number of bytes to pre-allocate in the buffer. This is a hint
 | 
			
		||||
   *                     to optimize memory usage and avoid reallocations during encoding.
 | 
			
		||||
   *                     Implementations should aim to allocate at least this size.
 | 
			
		||||
   * @return A ProtoWriteBuffer object with the reserved size.
 | 
			
		||||
   */
 | 
			
		||||
  virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0;
 | 
			
		||||
  virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0;
 | 
			
		||||
  virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
 | 
			
		||||
 | 
			
		||||
  // Optimized method that pre-allocates buffer based on message size
 | 
			
		||||
  template<class C> bool send_message_(const C &msg, uint32_t message_type) {
 | 
			
		||||
    auto buffer = this->create_buffer();
 | 
			
		||||
    uint32_t msg_size = 0;
 | 
			
		||||
    msg.calculate_size(msg_size);
 | 
			
		||||
 | 
			
		||||
    // Create a pre-sized buffer
 | 
			
		||||
    auto buffer = this->create_buffer(msg_size);
 | 
			
		||||
 | 
			
		||||
    // Encode message into the buffer
 | 
			
		||||
    msg.encode(buffer);
 | 
			
		||||
 | 
			
		||||
    // Send the buffer
 | 
			
		||||
    return this->send_buffer(buffer, message_type);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -76,6 +76,8 @@ class InitialStateIterator : public ComponentIterator {
 | 
			
		||||
#ifdef USE_UPDATE
 | 
			
		||||
  bool on_update(update::UpdateEntity *update) override;
 | 
			
		||||
#endif
 | 
			
		||||
  bool completed() { return this->state_ == IteratorState::NONE; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  APIConnection *client_;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,17 @@
 | 
			
		||||
from esphome import pins
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_CALIBRATION,
 | 
			
		||||
    CONF_CAPACITANCE,
 | 
			
		||||
    CONF_DIV_RATIO,
 | 
			
		||||
    CONF_INDOOR,
 | 
			
		||||
    CONF_IRQ_PIN,
 | 
			
		||||
    CONF_LIGHTNING_THRESHOLD,
 | 
			
		||||
    CONF_MASK_DISTURBER,
 | 
			
		||||
    CONF_CALIBRATION,
 | 
			
		||||
    CONF_TUNE_ANTENNA,
 | 
			
		||||
    CONF_NOISE_LEVEL,
 | 
			
		||||
    CONF_SPIKE_REJECTION,
 | 
			
		||||
    CONF_TUNE_ANTENNA,
 | 
			
		||||
    CONF_WATCHDOG_THRESHOLD,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import binary_sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
from . import AS3935, CONF_AS3935_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["as3935"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,14 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DISTANCE,
 | 
			
		||||
    CONF_LIGHTNING_ENERGY,
 | 
			
		||||
    UNIT_KILOMETER,
 | 
			
		||||
    ICON_SIGNAL_DISTANCE_VARIANT,
 | 
			
		||||
    ICON_FLASH,
 | 
			
		||||
    ICON_SIGNAL_DISTANCE_VARIANT,
 | 
			
		||||
    UNIT_KILOMETER,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from . import AS3935, CONF_AS3935_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["as3935"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import as3935, i2c
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["as3935"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/as3935/as3935.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace as3935_i2c {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import as3935, spi
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["as3935"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
from esphome import pins
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_DIR_PIN,
 | 
			
		||||
    CONF_DIRECTION,
 | 
			
		||||
    CONF_HYSTERESIS,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_RANGE,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,20 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ANGLE,
 | 
			
		||||
    CONF_GAIN,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    CONF_MAGNITUDE,
 | 
			
		||||
    CONF_POSITION,
 | 
			
		||||
    CONF_STATUS,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    ICON_MAGNET,
 | 
			
		||||
    ICON_ROTATE_RIGHT,
 | 
			
		||||
    CONF_GAIN,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    CONF_MAGNITUDE,
 | 
			
		||||
    CONF_STATUS,
 | 
			
		||||
    CONF_POSITION,
 | 
			
		||||
    CONF_ANGLE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
)
 | 
			
		||||
from .. import as5600_ns, AS5600Component
 | 
			
		||||
 | 
			
		||||
from .. import AS5600Component, as5600_ns
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@ammmze"]
 | 
			
		||||
DEPENDENCIES = ["as5600"]
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace as7341 {
 | 
			
		||||
 | 
			
		||||
static const uint8_t AS7341_CHIP_ID = 0X09;
 | 
			
		||||
static const uint8_t AS7341_CHIP_ID = 0x09;
 | 
			
		||||
 | 
			
		||||
static const uint8_t AS7341_CONFIG = 0x70;
 | 
			
		||||
static const uint8_t AS7341_LED = 0x74;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_GAIN,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
@@ -9,7 +9,6 @@ from esphome.const import (
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@mrgnr"]
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,9 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation, core
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
from esphome.automation import maybe_simple_id
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_FREQUENCY,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_FREQUENCY, CONF_ID
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@X-Ryl669"]
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,8 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import switch
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_SWITCH,
 | 
			
		||||
    ICON_WIFI,
 | 
			
		||||
)
 | 
			
		||||
from esphome.const import DEVICE_CLASS_SWITCH, ICON_WIFI
 | 
			
		||||
 | 
			
		||||
from .. import CONF_AT581X_ID, AT581XComponent, at581x_ns
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["at581x"]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,14 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import esp32_ble_tracker, sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, esp32_ble_tracker
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_BATTERY_LEVEL,
 | 
			
		||||
    CONF_BATTERY_VOLTAGE,
 | 
			
		||||
    CONF_MAC_ADDRESS,
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_MAC_ADDRESS,
 | 
			
		||||
    CONF_SIGNAL_STRENGTH,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_SIGNAL_STRENGTH,
 | 
			
		||||
 
 | 
			
		||||
@@ -48,6 +48,12 @@ def set_stream_limits(
 | 
			
		||||
    min_sample_rate: int = _UNDEF,
 | 
			
		||||
    max_sample_rate: int = _UNDEF,
 | 
			
		||||
):
 | 
			
		||||
    """Sets the limits for the audio stream that audio component can handle
 | 
			
		||||
 | 
			
		||||
    When the component sinks audio (e.g., a speaker), these indicate the limits to the audio it can receive.
 | 
			
		||||
    When the component sources audio (e.g., a microphone), these indicate the limits to the audio it can send.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def set_limits_in_config(config):
 | 
			
		||||
        if min_bits_per_sample is not _UNDEF:
 | 
			
		||||
            config[CONF_MIN_BITS_PER_SAMPLE] = min_bits_per_sample
 | 
			
		||||
@@ -69,43 +75,87 @@ def final_validate_audio_schema(
 | 
			
		||||
    name: str,
 | 
			
		||||
    *,
 | 
			
		||||
    audio_device: str,
 | 
			
		||||
    bits_per_sample: int,
 | 
			
		||||
    channels: int,
 | 
			
		||||
    sample_rate: int,
 | 
			
		||||
    bits_per_sample: int = _UNDEF,
 | 
			
		||||
    channels: int = _UNDEF,
 | 
			
		||||
    sample_rate: int = _UNDEF,
 | 
			
		||||
    enabled_channels: list[int] = _UNDEF,
 | 
			
		||||
    audio_device_issue: bool = False,
 | 
			
		||||
):
 | 
			
		||||
    """Validates audio compatibility when passed between different components.
 | 
			
		||||
 | 
			
		||||
    The component derived from ``AUDIO_COMPONENT_SCHEMA`` should call ``set_stream_limits`` in a validator to specify its compatible settings
 | 
			
		||||
 | 
			
		||||
      - If audio_device_issue is True, then the error message indicates the user should adjust the AUDIO_COMPONENT_SCHEMA derived component's configuration to match the values passed to this function
 | 
			
		||||
      - If audio_device_issue is False, then the error message indicates the user should adjust the configuration of the component calling this function, as it falls out of the valid stream limits
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        name (str): Friendly name of the component calling this function with an audio component to validate
 | 
			
		||||
        audio_device (str): The configuration parameter name that contains the ID of an AUDIO_COMPONENT_SCHEMA derived component to validate against
 | 
			
		||||
        bits_per_sample (int, optional): The desired bits per sample
 | 
			
		||||
        channels (int, optional): The desired number of channels
 | 
			
		||||
        sample_rate (int, optional): The desired sample rate
 | 
			
		||||
        enabled_channels (list[int], optional): The desired enabled channels
 | 
			
		||||
        audio_device_issue (bool, optional): Format the error message to indicate the problem is in the configuration for the ``audio_device`` component. Defaults to False.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def validate_audio_compatiblity(audio_config):
 | 
			
		||||
        audio_schema = {}
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            cv.int_range(
 | 
			
		||||
                min=audio_config.get(CONF_MIN_BITS_PER_SAMPLE),
 | 
			
		||||
                max=audio_config.get(CONF_MAX_BITS_PER_SAMPLE),
 | 
			
		||||
            )(bits_per_sample)
 | 
			
		||||
        except cv.Invalid as exc:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"Invalid configuration for the {name} component. The {CONF_BITS_PER_SAMPLE} {str(exc)}"
 | 
			
		||||
            ) from exc
 | 
			
		||||
        if bits_per_sample is not _UNDEF:
 | 
			
		||||
            try:
 | 
			
		||||
                cv.int_range(
 | 
			
		||||
                    min=audio_config.get(CONF_MIN_BITS_PER_SAMPLE),
 | 
			
		||||
                    max=audio_config.get(CONF_MAX_BITS_PER_SAMPLE),
 | 
			
		||||
                )(bits_per_sample)
 | 
			
		||||
            except cv.Invalid as exc:
 | 
			
		||||
                if audio_device_issue:
 | 
			
		||||
                    error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires {bits_per_sample} bits per sample."
 | 
			
		||||
                else:
 | 
			
		||||
                    error_string = f"Invalid configuration for the {name} component. The {CONF_BITS_PER_SAMPLE} {str(exc)}"
 | 
			
		||||
                raise cv.Invalid(error_string) from exc
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            cv.int_range(
 | 
			
		||||
                min=audio_config.get(CONF_MIN_CHANNELS),
 | 
			
		||||
                max=audio_config.get(CONF_MAX_CHANNELS),
 | 
			
		||||
            )(channels)
 | 
			
		||||
        except cv.Invalid as exc:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"Invalid configuration for the {name} component. The {CONF_NUM_CHANNELS} {str(exc)}"
 | 
			
		||||
            ) from exc
 | 
			
		||||
        if channels is not _UNDEF:
 | 
			
		||||
            try:
 | 
			
		||||
                cv.int_range(
 | 
			
		||||
                    min=audio_config.get(CONF_MIN_CHANNELS),
 | 
			
		||||
                    max=audio_config.get(CONF_MAX_CHANNELS),
 | 
			
		||||
                )(channels)
 | 
			
		||||
            except cv.Invalid as exc:
 | 
			
		||||
                if audio_device_issue:
 | 
			
		||||
                    error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires {channels} channels."
 | 
			
		||||
                else:
 | 
			
		||||
                    error_string = f"Invalid configuration for the {name} component. The {CONF_NUM_CHANNELS} {str(exc)}"
 | 
			
		||||
                raise cv.Invalid(error_string) from exc
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            cv.int_range(
 | 
			
		||||
                min=audio_config.get(CONF_MIN_SAMPLE_RATE),
 | 
			
		||||
                max=audio_config.get(CONF_MAX_SAMPLE_RATE),
 | 
			
		||||
            )(sample_rate)
 | 
			
		||||
            return cv.Schema(audio_schema, extra=cv.ALLOW_EXTRA)(audio_config)
 | 
			
		||||
        except cv.Invalid as exc:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"Invalid configuration for the {name} component. The {CONF_SAMPLE_RATE} {str(exc)}"
 | 
			
		||||
            ) from exc
 | 
			
		||||
        if sample_rate is not _UNDEF:
 | 
			
		||||
            try:
 | 
			
		||||
                cv.int_range(
 | 
			
		||||
                    min=audio_config.get(CONF_MIN_SAMPLE_RATE),
 | 
			
		||||
                    max=audio_config.get(CONF_MAX_SAMPLE_RATE),
 | 
			
		||||
                )(sample_rate)
 | 
			
		||||
            except cv.Invalid as exc:
 | 
			
		||||
                if audio_device_issue:
 | 
			
		||||
                    error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires a {sample_rate} sample rate."
 | 
			
		||||
                else:
 | 
			
		||||
                    error_string = f"Invalid configuration for the {name} component. The {CONF_SAMPLE_RATE} {str(exc)}"
 | 
			
		||||
                raise cv.Invalid(error_string) from exc
 | 
			
		||||
 | 
			
		||||
        if enabled_channels is not _UNDEF:
 | 
			
		||||
            for channel in enabled_channels:
 | 
			
		||||
                try:
 | 
			
		||||
                    # Channels are 0-indexed
 | 
			
		||||
                    cv.int_range(
 | 
			
		||||
                        min=0,
 | 
			
		||||
                        max=audio_config.get(CONF_MAX_CHANNELS) - 1,
 | 
			
		||||
                    )(channel)
 | 
			
		||||
                except cv.Invalid as exc:
 | 
			
		||||
                    if audio_device_issue:
 | 
			
		||||
                        error_string = f"Invalid configuration for the specified {audio_device}. The {name} component requires channel {channel}."
 | 
			
		||||
                    else:
 | 
			
		||||
                        error_string = f"Invalid configuration for the {name} component. Enabled channel {channel} {str(exc)}"
 | 
			
		||||
                    raise cv.Invalid(error_string) from exc
 | 
			
		||||
 | 
			
		||||
        return cv.Schema(audio_schema, extra=cv.ALLOW_EXTRA)(audio_config)
 | 
			
		||||
 | 
			
		||||
    return cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
@@ -118,4 +168,4 @@ def final_validate_audio_schema(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    cg.add_library("esphome/esp-audio-libs", "1.1.1")
 | 
			
		||||
    cg.add_library("esphome/esp-audio-libs", "1.1.3")
 | 
			
		||||
 
 | 
			
		||||
@@ -135,5 +135,30 @@ const char *audio_file_type_to_string(AudioFileType file_type);
 | 
			
		||||
void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor,
 | 
			
		||||
                         size_t samples_to_scale);
 | 
			
		||||
 | 
			
		||||
/// @brief Unpacks a quantized audio sample into a Q31 fixed point number.
 | 
			
		||||
/// @param data Pointer to uint8_t array containing the audio sample
 | 
			
		||||
/// @param bytes_per_sample The number of bytes per sample
 | 
			
		||||
/// @return Q31 sample
 | 
			
		||||
inline int32_t unpack_audio_sample_to_q31(const uint8_t *data, size_t bytes_per_sample) {
 | 
			
		||||
  int32_t sample = 0;
 | 
			
		||||
  if (bytes_per_sample == 1) {
 | 
			
		||||
    sample |= data[0] << 24;
 | 
			
		||||
  } else if (bytes_per_sample == 2) {
 | 
			
		||||
    sample |= data[0] << 16;
 | 
			
		||||
    sample |= data[1] << 24;
 | 
			
		||||
  } else if (bytes_per_sample == 3) {
 | 
			
		||||
    sample |= data[0] << 8;
 | 
			
		||||
    sample |= data[1] << 16;
 | 
			
		||||
    sample |= data[2] << 24;
 | 
			
		||||
  } else if (bytes_per_sample == 4) {
 | 
			
		||||
    sample |= data[0];
 | 
			
		||||
    sample |= data[1] << 8;
 | 
			
		||||
    sample |= data[2] << 16;
 | 
			
		||||
    sample |= data[3] << 24;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return sample;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace audio
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -66,19 +66,30 @@ esp_err_t AudioDecoder::start(AudioFileType audio_file_type) {
 | 
			
		||||
    case AudioFileType::FLAC:
 | 
			
		||||
      this->flac_decoder_ = make_unique<esp_audio_libs::flac::FLACDecoder>();
 | 
			
		||||
      this->free_buffer_required_ =
 | 
			
		||||
          this->output_transfer_buffer_->capacity();  // We'll revise this after reading the header
 | 
			
		||||
          this->output_transfer_buffer_->capacity();  // Adjusted and reallocated after reading the header
 | 
			
		||||
      break;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_AUDIO_MP3_SUPPORT
 | 
			
		||||
    case AudioFileType::MP3:
 | 
			
		||||
      this->mp3_decoder_ = esp_audio_libs::helix_decoder::MP3InitDecoder();
 | 
			
		||||
 | 
			
		||||
      // MP3 always has 1152 samples per chunk
 | 
			
		||||
      this->free_buffer_required_ = 1152 * sizeof(int16_t) * 2;  // samples * size per sample * channels
 | 
			
		||||
 | 
			
		||||
      // Always reallocate the output transfer buffer to the smallest necessary size
 | 
			
		||||
      this->output_transfer_buffer_->reallocate(this->free_buffer_required_);
 | 
			
		||||
      break;
 | 
			
		||||
#endif
 | 
			
		||||
    case AudioFileType::WAV:
 | 
			
		||||
      this->wav_decoder_ = make_unique<esp_audio_libs::wav_decoder::WAVDecoder>();
 | 
			
		||||
      this->wav_decoder_->reset();
 | 
			
		||||
 | 
			
		||||
      // Processing WAVs doesn't actually require a specific amount of buffer size, as it is already in PCM format.
 | 
			
		||||
      // Thus, we don't reallocate to a minimum size.
 | 
			
		||||
      this->free_buffer_required_ = 1024;
 | 
			
		||||
      if (this->output_transfer_buffer_->capacity() < this->free_buffer_required_) {
 | 
			
		||||
        this->output_transfer_buffer_->reallocate(this->free_buffer_required_);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case AudioFileType::NONE:
 | 
			
		||||
    default:
 | 
			
		||||
@@ -116,10 +127,18 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
 | 
			
		||||
 | 
			
		||||
  uint32_t decoding_start = millis();
 | 
			
		||||
 | 
			
		||||
  bool first_loop_iteration = true;
 | 
			
		||||
 | 
			
		||||
  size_t bytes_processed = 0;
 | 
			
		||||
  size_t bytes_available_before_processing = 0;
 | 
			
		||||
 | 
			
		||||
  while (state == FileDecoderState::MORE_TO_PROCESS) {
 | 
			
		||||
    // Transfer decoded out
 | 
			
		||||
    if (!this->pause_output_) {
 | 
			
		||||
      size_t bytes_written = this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS));
 | 
			
		||||
      // Never shift the data in the output transfer buffer to avoid unnecessary, slow data moves
 | 
			
		||||
      size_t bytes_written =
 | 
			
		||||
          this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS), false);
 | 
			
		||||
 | 
			
		||||
      if (this->audio_stream_info_.has_value()) {
 | 
			
		||||
        this->accumulated_frames_written_ += this->audio_stream_info_.value().bytes_to_frames(bytes_written);
 | 
			
		||||
        this->playback_ms_ +=
 | 
			
		||||
@@ -138,12 +157,24 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
 | 
			
		||||
 | 
			
		||||
    // Decode more audio
 | 
			
		||||
 | 
			
		||||
    size_t bytes_read = this->input_transfer_buffer_->transfer_data_from_source(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS));
 | 
			
		||||
    // Only shift data on the first loop iteration to avoid unnecessary, slow moves
 | 
			
		||||
    size_t bytes_read = this->input_transfer_buffer_->transfer_data_from_source(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS),
 | 
			
		||||
                                                                                first_loop_iteration);
 | 
			
		||||
 | 
			
		||||
    if ((this->potentially_failed_count_ > 0) && (bytes_read == 0)) {
 | 
			
		||||
    if (!first_loop_iteration && (this->input_transfer_buffer_->available() < bytes_processed)) {
 | 
			
		||||
      // Less data is available than what was processed in last iteration, so don't attempt to decode.
 | 
			
		||||
      // This attempts to avoid the decoder from consistently trying to decode an incomplete frame. The transfer buffer
 | 
			
		||||
      // will shift the remaining data to the start and copy more from the source the next time the decode function is
 | 
			
		||||
      // called
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bytes_available_before_processing = this->input_transfer_buffer_->available();
 | 
			
		||||
 | 
			
		||||
    if ((this->potentially_failed_count_ > 10) && (bytes_read == 0)) {
 | 
			
		||||
      // Failed to decode in last attempt and there is no new data
 | 
			
		||||
 | 
			
		||||
      if (this->input_transfer_buffer_->free() == 0) {
 | 
			
		||||
      if ((this->input_transfer_buffer_->free() == 0) && first_loop_iteration) {
 | 
			
		||||
        // The input buffer is full. Since it previously failed on the exact same data, we can never recover
 | 
			
		||||
        state = FileDecoderState::FAILED;
 | 
			
		||||
      } else {
 | 
			
		||||
@@ -175,6 +206,9 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    first_loop_iteration = false;
 | 
			
		||||
    bytes_processed = bytes_available_before_processing - this->input_transfer_buffer_->available();
 | 
			
		||||
 | 
			
		||||
    if (state == FileDecoderState::POTENTIALLY_FAILED) {
 | 
			
		||||
      ++this->potentially_failed_count_;
 | 
			
		||||
    } else if (state == FileDecoderState::END_OF_FILE) {
 | 
			
		||||
@@ -207,13 +241,11 @@ FileDecoderState AudioDecoder::decode_flac_() {
 | 
			
		||||
    size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
 | 
			
		||||
    this->input_transfer_buffer_->decrease_buffer_length(bytes_consumed);
 | 
			
		||||
 | 
			
		||||
    // Reallocate the output transfer buffer to the smallest necessary size
 | 
			
		||||
    this->free_buffer_required_ = flac_decoder_->get_output_buffer_size_bytes();
 | 
			
		||||
    if (this->output_transfer_buffer_->capacity() < this->free_buffer_required_) {
 | 
			
		||||
      // Output buffer is not big enough
 | 
			
		||||
      if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) {
 | 
			
		||||
        // Couldn't reallocate output buffer
 | 
			
		||||
        return FileDecoderState::FAILED;
 | 
			
		||||
      }
 | 
			
		||||
    if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) {
 | 
			
		||||
      // Couldn't reallocate output buffer
 | 
			
		||||
      return FileDecoderState::FAILED;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->audio_stream_info_ =
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,8 @@ namespace audio {
 | 
			
		||||
 | 
			
		||||
static const uint32_t READ_WRITE_TIMEOUT_MS = 20;
 | 
			
		||||
 | 
			
		||||
static const uint32_t CONNECTION_TIMEOUT_MS = 5000;
 | 
			
		||||
 | 
			
		||||
// The number of times the http read times out with no data before throwing an error
 | 
			
		||||
static const uint32_t ERROR_COUNT_NO_DATA_READ_TIMEOUT = 100;
 | 
			
		||||
 | 
			
		||||
@@ -97,7 +99,7 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
 | 
			
		||||
  client_config.user_data = this;
 | 
			
		||||
  client_config.buffer_size = HTTP_STREAM_BUFFER_SIZE;
 | 
			
		||||
  client_config.keep_alive_enable = true;
 | 
			
		||||
  client_config.timeout_ms = 5000;  // Shouldn't trigger watchdog resets if caller runs in a task
 | 
			
		||||
  client_config.timeout_ms = CONNECTION_TIMEOUT_MS;  // Shouldn't trigger watchdog resets if caller runs in a task
 | 
			
		||||
 | 
			
		||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
 | 
			
		||||
  if (uri.find("https:") != std::string::npos) {
 | 
			
		||||
@@ -189,7 +191,7 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
 | 
			
		||||
    file_type = this->audio_file_type_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->no_data_read_count_ = 0;
 | 
			
		||||
  this->last_data_read_ms_ = millis();
 | 
			
		||||
 | 
			
		||||
  this->output_transfer_buffer_ = AudioSinkTransferBuffer::create(this->buffer_size_);
 | 
			
		||||
  if (this->output_transfer_buffer_ == nullptr) {
 | 
			
		||||
@@ -257,22 +259,21 @@ AudioReaderState AudioReader::file_read_() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AudioReaderState AudioReader::http_read_() {
 | 
			
		||||
  this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS));
 | 
			
		||||
  this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS), false);
 | 
			
		||||
 | 
			
		||||
  if (esp_http_client_is_complete_data_received(this->client_)) {
 | 
			
		||||
    if (this->output_transfer_buffer_->available() == 0) {
 | 
			
		||||
      this->cleanup_connection_();
 | 
			
		||||
      return AudioReaderState::FINISHED;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
  } else if (this->output_transfer_buffer_->free() > 0) {
 | 
			
		||||
    size_t bytes_to_read = this->output_transfer_buffer_->free();
 | 
			
		||||
    int received_len =
 | 
			
		||||
        esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(), bytes_to_read);
 | 
			
		||||
 | 
			
		||||
    if (received_len > 0) {
 | 
			
		||||
      this->output_transfer_buffer_->increase_buffer_length(received_len);
 | 
			
		||||
 | 
			
		||||
      this->no_data_read_count_ = 0;
 | 
			
		||||
      this->last_data_read_ms_ = millis();
 | 
			
		||||
    } else if (received_len < 0) {
 | 
			
		||||
      // HTTP read error
 | 
			
		||||
      this->cleanup_connection_();
 | 
			
		||||
@@ -280,12 +281,11 @@ AudioReaderState AudioReader::http_read_() {
 | 
			
		||||
    } else {
 | 
			
		||||
      if (bytes_to_read > 0) {
 | 
			
		||||
        // Read timed out
 | 
			
		||||
        ++this->no_data_read_count_;
 | 
			
		||||
        if (this->no_data_read_count_ >= ERROR_COUNT_NO_DATA_READ_TIMEOUT) {
 | 
			
		||||
          // Timed out with no data read too many times, so the http read has failed
 | 
			
		||||
        if ((millis() - this->last_data_read_ms_) > CONNECTION_TIMEOUT_MS) {
 | 
			
		||||
          this->cleanup_connection_();
 | 
			
		||||
          return AudioReaderState::FAILED;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        delay(READ_WRITE_TIMEOUT_MS);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -71,7 +71,7 @@ class AudioReader {
 | 
			
		||||
  void cleanup_connection_();
 | 
			
		||||
 | 
			
		||||
  size_t buffer_size_;
 | 
			
		||||
  uint32_t no_data_read_count_;
 | 
			
		||||
  uint32_t last_data_read_ms_;
 | 
			
		||||
 | 
			
		||||
  esp_http_client_handle_t client_{nullptr};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace audio {
 | 
			
		||||
 | 
			
		||||
@@ -93,8 +95,9 @@ AudioResamplerState AudioResampler::resample(bool stop_gracefully, int32_t *ms_d
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!this->pause_output_) {
 | 
			
		||||
    // Move audio data to the sink
 | 
			
		||||
    this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS));
 | 
			
		||||
    // Move audio data to the sink without shifting the data in the output transfer buffer to avoid unnecessary, slow
 | 
			
		||||
    // data moves
 | 
			
		||||
    this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS), false);
 | 
			
		||||
  } else {
 | 
			
		||||
    // If paused, block to avoid wasting CPU resources
 | 
			
		||||
    delay(READ_WRITE_TIMEOUT_MS);
 | 
			
		||||
@@ -115,6 +118,7 @@ AudioResamplerState AudioResampler::resample(bool stop_gracefully, int32_t *ms_d
 | 
			
		||||
 | 
			
		||||
  if ((this->input_stream_info_.get_sample_rate() != this->output_stream_info_.get_sample_rate()) ||
 | 
			
		||||
      (this->input_stream_info_.get_bits_per_sample() != this->output_stream_info_.get_bits_per_sample())) {
 | 
			
		||||
    // Adjust gain by -3 dB to avoid clipping due to the resampling process
 | 
			
		||||
    esp_audio_libs::resampler::ResamplerResults results =
 | 
			
		||||
        this->resampler_->resample(this->input_transfer_buffer_->get_buffer_start(),
 | 
			
		||||
                                   this->output_transfer_buffer_->get_buffer_end(), frames_available, frames_free, -3);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
#include "audio_transfer_buffer.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/ring_buffer.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SPEAKER
 | 
			
		||||
 
 | 
			
		||||
@@ -33,12 +33,17 @@ size_t AudioTransferBuffer::free() const {
 | 
			
		||||
  if (this->buffer_size_ == 0) {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
  return this->buffer_size_ - (this->buffer_length_ - (this->data_start_ - this->buffer_));
 | 
			
		||||
  return this->buffer_size_ - (this->buffer_length_ + (this->data_start_ - this->buffer_));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioTransferBuffer::decrease_buffer_length(size_t bytes) {
 | 
			
		||||
  this->buffer_length_ -= bytes;
 | 
			
		||||
  this->data_start_ += bytes;
 | 
			
		||||
  if (this->buffer_length_ > 0) {
 | 
			
		||||
    this->data_start_ += bytes;
 | 
			
		||||
  } else {
 | 
			
		||||
    // All the data in the buffer has been consumed, reset the start pointer
 | 
			
		||||
    this->data_start_ = this->buffer_;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioTransferBuffer::increase_buffer_length(size_t bytes) { this->buffer_length_ += bytes; }
 | 
			
		||||
@@ -71,7 +76,7 @@ bool AudioTransferBuffer::has_buffered_data() const {
 | 
			
		||||
 | 
			
		||||
bool AudioTransferBuffer::reallocate(size_t new_buffer_size) {
 | 
			
		||||
  if (this->buffer_length_ > 0) {
 | 
			
		||||
    // Already has data in the buffer, fail
 | 
			
		||||
    // Buffer currently has data, so reallocation is impossible
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  this->deallocate_buffer_();
 | 
			
		||||
@@ -106,12 +111,14 @@ void AudioTransferBuffer::deallocate_buffer_() {
 | 
			
		||||
  this->buffer_length_ = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t AudioSourceTransferBuffer::transfer_data_from_source(TickType_t ticks_to_wait) {
 | 
			
		||||
  // Shift data in buffer to start
 | 
			
		||||
  if (this->buffer_length_ > 0) {
 | 
			
		||||
    memmove(this->buffer_, this->data_start_, this->buffer_length_);
 | 
			
		||||
size_t AudioSourceTransferBuffer::transfer_data_from_source(TickType_t ticks_to_wait, bool pre_shift) {
 | 
			
		||||
  if (pre_shift) {
 | 
			
		||||
    // Shift data in buffer to start
 | 
			
		||||
    if (this->buffer_length_ > 0) {
 | 
			
		||||
      memmove(this->buffer_, this->data_start_, this->buffer_length_);
 | 
			
		||||
    }
 | 
			
		||||
    this->data_start_ = this->buffer_;
 | 
			
		||||
  }
 | 
			
		||||
  this->data_start_ = this->buffer_;
 | 
			
		||||
 | 
			
		||||
  size_t bytes_to_read = this->free();
 | 
			
		||||
  size_t bytes_read = 0;
 | 
			
		||||
@@ -125,7 +132,7 @@ size_t AudioSourceTransferBuffer::transfer_data_from_source(TickType_t ticks_to_
 | 
			
		||||
  return bytes_read;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t AudioSinkTransferBuffer::transfer_data_to_sink(TickType_t ticks_to_wait) {
 | 
			
		||||
size_t AudioSinkTransferBuffer::transfer_data_to_sink(TickType_t ticks_to_wait, bool post_shift) {
 | 
			
		||||
  size_t bytes_written = 0;
 | 
			
		||||
  if (this->available()) {
 | 
			
		||||
#ifdef USE_SPEAKER
 | 
			
		||||
@@ -139,11 +146,14 @@ size_t AudioSinkTransferBuffer::transfer_data_to_sink(TickType_t ticks_to_wait)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->decrease_buffer_length(bytes_written);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (post_shift) {
 | 
			
		||||
    // Shift unwritten data to the start of the buffer
 | 
			
		||||
    memmove(this->buffer_, this->data_start_, this->buffer_length_);
 | 
			
		||||
    this->data_start_ = this->buffer_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return bytes_written;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,7 @@ class AudioTransferBuffer {
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  /// @brief Allocates the transfer buffer in external memory, if available.
 | 
			
		||||
  /// @param buffer_size The number of bytes to allocate
 | 
			
		||||
  /// @return True is successful, false otherwise.
 | 
			
		||||
  bool allocate_buffer_(size_t buffer_size);
 | 
			
		||||
 | 
			
		||||
@@ -89,8 +90,10 @@ class AudioSinkTransferBuffer : public AudioTransferBuffer {
 | 
			
		||||
 | 
			
		||||
  /// @brief Writes any available data in the transfer buffer to the sink.
 | 
			
		||||
  /// @param ticks_to_wait FreeRTOS ticks to block while waiting for the sink to have enough space
 | 
			
		||||
  /// @param post_shift If true, all remaining data is moved to the start of the buffer after transferring to the sink.
 | 
			
		||||
  ///                   Defaults to true.
 | 
			
		||||
  /// @return Number of bytes written
 | 
			
		||||
  size_t transfer_data_to_sink(TickType_t ticks_to_wait);
 | 
			
		||||
  size_t transfer_data_to_sink(TickType_t ticks_to_wait, bool post_shift = true);
 | 
			
		||||
 | 
			
		||||
  /// @brief Adds a ring buffer as the transfer buffer's sink.
 | 
			
		||||
  /// @param ring_buffer weak_ptr to the allocated ring buffer
 | 
			
		||||
@@ -125,8 +128,10 @@ class AudioSourceTransferBuffer : public AudioTransferBuffer {
 | 
			
		||||
 | 
			
		||||
  /// @brief Reads any available data from the sink into the transfer buffer.
 | 
			
		||||
  /// @param ticks_to_wait FreeRTOS ticks to block while waiting for the source to have enough data
 | 
			
		||||
  /// @param pre_shift If true, any unwritten data is moved to the start of the buffer before transferring from the
 | 
			
		||||
  ///                  source. Defaults to true.
 | 
			
		||||
  /// @return Number of bytes read
 | 
			
		||||
  size_t transfer_data_from_source(TickType_t ticks_to_wait);
 | 
			
		||||
  size_t transfer_data_from_source(TickType_t ticks_to_wait, bool pre_shift = true);
 | 
			
		||||
 | 
			
		||||
  /// @brief Adds a ring buffer as the transfer buffer's source.
 | 
			
		||||
  /// @param ring_buffer weak_ptr to the allocated ring buffer
 | 
			
		||||
 
 | 
			
		||||
@@ -30,8 +30,12 @@ void AXS15231Touchscreen::setup() {
 | 
			
		||||
    this->interrupt_pin_->setup();
 | 
			
		||||
    this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
 | 
			
		||||
  }
 | 
			
		||||
  this->x_raw_max_ = this->display_->get_native_width();
 | 
			
		||||
  this->y_raw_max_ = this->display_->get_native_height();
 | 
			
		||||
  if (this->x_raw_max_ == 0) {
 | 
			
		||||
    this->x_raw_max_ = this->display_->get_native_width();
 | 
			
		||||
  }
 | 
			
		||||
  if (this->y_raw_max_ == 0) {
 | 
			
		||||
    this->y_raw_max_ = this->display_->get_native_height();
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "AXS15231 Touchscreen setup complete");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -44,7 +48,7 @@ void AXS15231Touchscreen::update_touches() {
 | 
			
		||||
  err = this->read(data, sizeof(data));
 | 
			
		||||
  ERROR_CHECK(err);
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
  if (data[0] != 0)  // no touches
 | 
			
		||||
  if (data[0] != 0 || data[1] == 0)  // no touches
 | 
			
		||||
    return;
 | 
			
		||||
  uint16_t x = encode_uint16(data[2] & 0xF, data[3]);
 | 
			
		||||
  uint16_t y = encode_uint16(data[4] & 0xF, data[5]);
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user