mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 16:41:50 +00:00 
			
		
		
		
	Compare commits
	
		
			692 Commits
		
	
	
		
			jesserockz
			...
			2025.10.0b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					13cfa30c67 | ||
| 
						 | 
					da1959ab5d | ||
| 
						 | 
					2b42903e9c | ||
| 
						 | 
					742c9cbb53 | ||
| 
						 | 
					e4bc465a3d | ||
| 
						 | 
					5cec0941f8 | ||
| 
						 | 
					72a7aeb430 | ||
| 
						 | 
					53e6b28092 | ||
| 
						 | 
					7f3c7bb5c6 | ||
| 
						 | 
					c02c0b2a96 | ||
| 
						 | 
					5f5092e29f | ||
| 
						 | 
					2864bf1674 | ||
| 
						 | 
					132e949927 | ||
| 
						 | 
					8fa44e471d | ||
| 
						 | 
					ccedcfb600 | ||
| 
						 | 
					8b0ec0afe3 | ||
| 
						 | 
					dca29ed89b | ||
| 
						 | 
					728726e29e | ||
| 
						 | 
					79f4ca20b8 | ||
| 
						 | 
					3eca72e0b8 | ||
| 
						 | 
					22c0f55cef | ||
| 
						 | 
					fd8ecc9608 | ||
| 
						 | 
					ac96a59d58 | ||
| 
						 | 
					dceed992d8 | ||
| 
						 | 
					b0c66c1c09 | ||
| 
						 | 
					8f04a5b944 | ||
| 
						 | 
					e6c21df30b | ||
| 
						 | 
					842cb9033a | ||
| 
						 | 
					a2cb415dfa | ||
| 
						 | 
					1fac193535 | ||
| 
						 | 
					34632f78cf | ||
| 
						 | 
					b93c60e85a | ||
| 
						 | 
					60dc055509 | ||
| 
						 | 
					9ad462d8c6 | ||
| 
						 | 
					f1af9d978c | ||
| 
						 | 
					785df05631 | ||
| 
						 | 
					93266ad08f | ||
| 
						 | 
					2fac813f18 | ||
| 
						 | 
					a62c7a03dd | ||
| 
						 | 
					ec63247ae0 | ||
| 
						 | 
					0fe6e7169c | ||
| 
						 | 
					a0f4de1bfb | ||
| 
						 | 
					a541549d23 | ||
| 
						 | 
					b74715fe14 | ||
| 
						 | 
					5aff20a624 | ||
| 
						 | 
					7682b4e9a3 | ||
| 
						 | 
					6eabf709c6 | ||
| 
						 | 
					6209d4b493 | ||
| 
						 | 
					f10c361454 | ||
| 
						 | 
					27456c1370 | ||
| 
						 | 
					1aeefbe547 | ||
| 
						 | 
					3f3bce7ef4 | ||
| 
						 | 
					0acc58d5a1 | ||
| 
						 | 
					0b4ef0fea2 | ||
| 
						 | 
					a067bdb769 | ||
| 
						 | 
					301e7a7ac5 | ||
| 
						 | 
					ac566b7fd6 | ||
| 
						 | 
					fddb8b35f2 | ||
| 
						 | 
					27e1095cd7 | ||
| 
						 | 
					fa4541a4f3 | ||
| 
						 | 
					24dcc1843e | ||
| 
						 | 
					f670d775ac | ||
| 
						 | 
					59a31adac2 | ||
| 
						 | 
					a3c0acc7c9 | ||
| 
						 | 
					ad2c5b96a9 | ||
| 
						 | 
					9adc3bd943 | ||
| 
						 | 
					ad296a7d74 | ||
| 
						 | 
					fdd422c42a | ||
| 
						 | 
					3d82301c3d | ||
| 
						 | 
					2fa49be17d | ||
| 
						 | 
					75867842ea | ||
| 
						 | 
					cba85c0925 | ||
| 
						 | 
					42d1269aaf | ||
| 
						 | 
					f4df17673b | ||
| 
						 | 
					e340397b41 | ||
| 
						 | 
					abeadc7830 | ||
| 
						 | 
					8d4b347e5c | ||
| 
						 | 
					a7f556c25f | ||
| 
						 | 
					3f4250fcd7 | ||
| 
						 | 
					b532e04ae4 | ||
| 
						 | 
					697cab45dd | ||
| 
						 | 
					a88182c8e3 | ||
| 
						 | 
					8cfb6578d1 | ||
| 
						 | 
					eb16d322cd | ||
| 
						 | 
					22e06ba063 | ||
| 
						 | 
					7147479f90 | ||
| 
						 | 
					e55df1babc | ||
| 
						 | 
					4c8fc5f4e6 | ||
| 
						 | 
					646508006c | ||
| 
						 | 
					9384f0683b | ||
| 
						 | 
					5e7f5bf890 | ||
| 
						 | 
					2a8796437d | ||
| 
						 | 
					1635767aa2 | ||
| 
						 | 
					192856e8d1 | ||
| 
						 | 
					71be5a5f65 | ||
| 
						 | 
					f86b83cda5 | ||
| 
						 | 
					74c055745f | ||
| 
						 | 
					3edcdc7d80 | ||
| 
						 | 
					94fea68e3e | ||
| 
						 | 
					6880f9fc5c | ||
| 
						 | 
					26ebac8cb8 | ||
| 
						 | 
					5cf0046601 | ||
| 
						 | 
					c68017ddb4 | ||
| 
						 | 
					cfd241ff29 | ||
| 
						 | 
					f757a19e82 | ||
| 
						 | 
					e8854e0659 | ||
| 
						 | 
					a3622d878d | ||
| 
						 | 
					da2089c8be | ||
| 
						 | 
					118663f9e2 | ||
| 
						 | 
					4a99987bfe | ||
| 
						 | 
					d164c06f01 | ||
| 
						 | 
					972987acdf | ||
| 
						 | 
					eea2b6b81b | ||
| 
						 | 
					f62e06104e | ||
| 
						 | 
					f26e71bae6 | ||
| 
						 | 
					c6e4a7911c | ||
| 
						 | 
					e2c5eeef97 | ||
| 
						 | 
					7ea51b1865 | ||
| 
						 | 
					aa1afbd152 | ||
| 
						 | 
					20d9ae699c | ||
| 
						 | 
					c0fb0ae06f | ||
| 
						 | 
					9b6d62cd69 | ||
| 
						 | 
					5932a4bd0e | ||
| 
						 | 
					84c3cf5f17 | ||
| 
						 | 
					120a445abf | ||
| 
						 | 
					41c073a451 | ||
| 
						 | 
					0fd71ca211 | ||
| 
						 | 
					19439199cc | ||
| 
						 | 
					39d5cbc74a | ||
| 
						 | 
					722c5a94f2 | ||
| 
						 | 
					7b48fc292f | ||
| 
						 | 
					6c7d92e726 | ||
| 
						 | 
					b1859c50bd | ||
| 
						 | 
					3f9924eac2 | ||
| 
						 | 
					874db20b7d | ||
| 
						 | 
					2eea674c04 | ||
| 
						 | 
					0137954f2b | ||
| 
						 | 
					0a40a30e4a | ||
| 
						 | 
					d43b844e06 | ||
| 
						 | 
					2596b6096f | ||
| 
						 | 
					6f8e82aeb6 | ||
| 
						 | 
					ca0e738799 | ||
| 
						 | 
					14a23101f2 | ||
| 
						 | 
					2b389bb8f2 | ||
| 
						 | 
					89c3340ef6 | ||
| 
						 | 
					ba0532cda7 | ||
| 
						 | 
					5419b8bddb | ||
| 
						 | 
					624868bb05 | ||
| 
						 | 
					f2aa5a754c | ||
| 
						 | 
					638c6cc14e | ||
| 
						 | 
					8137d7600a | ||
| 
						 | 
					08afc3030a | ||
| 
						 | 
					1deb79a24b | ||
| 
						 | 
					de21c61b6a | ||
| 
						 | 
					db1aa82350 | ||
| 
						 | 
					fe4799b300 | ||
| 
						 | 
					93e18e850e | ||
| 
						 | 
					5cef75dbe1 | ||
| 
						 | 
					4194a940ae | ||
| 
						 | 
					59c0ffb98b | ||
| 
						 | 
					29658b79bc | ||
| 
						 | 
					158a59aa83 | ||
| 
						 | 
					c95180504a | ||
| 
						 | 
					848ba6b717 | ||
| 
						 | 
					922f4b6352 | ||
| 
						 | 
					fd3c05b42e | ||
| 
						 | 
					ab1f8326ee | ||
| 
						 | 
					2a915e4efd | ||
| 
						 | 
					f5e85a424f | ||
| 
						 | 
					c69603d916 | ||
| 
						 | 
					d75b7708a5 | ||
| 
						 | 
					b023453e81 | ||
| 
						 | 
					a5ba6237cb | ||
| 
						 | 
					0e623055df | ||
| 
						 | 
					6018f5f5d1 | ||
| 
						 | 
					96868aa754 | ||
| 
						 | 
					83d86c8c59 | ||
| 
						 | 
					7703cabb7b | ||
| 
						 | 
					300f1de11c | ||
| 
						 | 
					3b73738d9f | ||
| 
						 | 
					b176d1f890 | ||
| 
						 | 
					2aaafd6ebb | ||
| 
						 | 
					a96c013eb1 | ||
| 
						 | 
					054b215d8d | ||
| 
						 | 
					e3e98e2568 | ||
| 
						 | 
					29db576f79 | ||
| 
						 | 
					58166b3e71 | ||
| 
						 | 
					345fc0b6ca | ||
| 
						 | 
					127058e700 | ||
| 
						 | 
					57f7a709cf | ||
| 
						 | 
					f2a9e9265e | ||
| 
						 | 
					1ecd26adb5 | ||
| 
						 | 
					6d9fc672d5 | ||
| 
						 | 
					b9361b0868 | ||
| 
						 | 
					0246a8eb1d | ||
| 
						 | 
					a56d044d98 | ||
| 
						 | 
					f6253d52b4 | ||
| 
						 | 
					77dff52183 | ||
| 
						 | 
					4b86f31b66 | ||
| 
						 | 
					78655968df | ||
| 
						 | 
					ab79e596b5 | ||
| 
						 | 
					ef73ae2116 | ||
| 
						 | 
					0111f725ff | ||
| 
						 | 
					34b4cb46f6 | ||
| 
						 | 
					a2f833d665 | ||
| 
						 | 
					a7042687c1 | ||
| 
						 | 
					0d2d18c198 | ||
| 
						 | 
					3f03e8c423 | ||
| 
						 | 
					9dd6be4061 | ||
| 
						 | 
					2bf79a607f | ||
| 
						 | 
					61a11547ca | ||
| 
						 | 
					abf522bbb9 | ||
| 
						 | 
					25fc16163b | ||
| 
						 | 
					55593628ef | ||
| 
						 | 
					1f90d89731 | ||
| 
						 | 
					1560b8b8e2 | ||
| 
						 | 
					b26776fad4 | ||
| 
						 | 
					875ada86b0 | ||
| 
						 | 
					195d1be4a9 | ||
| 
						 | 
					2b12ff5874 | ||
| 
						 | 
					250b94d113 | ||
| 
						 | 
					28199c1cf8 | ||
| 
						 | 
					eeb3ccaef7 | ||
| 
						 | 
					460eb219ba | ||
| 
						 | 
					cef9cf49bf | ||
| 
						 | 
					28f09f9ed1 | ||
| 
						 | 
					3eb502b328 | ||
| 
						 | 
					7af77d0f82 | ||
| 
						 | 
					1c229947a8 | ||
| 
						 | 
					74f09a2b59 | ||
| 
						 | 
					549626bee2 | ||
| 
						 | 
					65a1d2b2ff | ||
| 
						 | 
					f7ed127182 | ||
| 
						 | 
					44767c32cf | ||
| 
						 | 
					0cc03dfe32 | ||
| 
						 | 
					1922b7b3ed | ||
| 
						 | 
					f22143f090 | ||
| 
						 | 
					be92903a6f | ||
| 
						 | 
					538941b3fd | ||
| 
						 | 
					ce8ac8b89d | ||
| 
						 | 
					6d0f134ff1 | ||
| 
						 | 
					11ccf0e591 | ||
| 
						 | 
					adfacdf1b7 | ||
| 
						 | 
					f8226cd481 | ||
| 
						 | 
					63326cbd6d | ||
| 
						 | 
					d0d7abb542 | ||
| 
						 | 
					cd7922faaf | ||
| 
						 | 
					365e3afa9b | ||
| 
						 | 
					e9c2e211ef | ||
| 
						 | 
					afda9500bf | ||
| 
						 | 
					bc7fc8df18 | ||
| 
						 | 
					2f8a4d0caa | ||
| 
						 | 
					a7ee7b962e | ||
| 
						 | 
					3cb2a4569c | ||
| 
						 | 
					3b20969171 | ||
| 
						 | 
					3b40172073 | ||
| 
						 | 
					2e220fcca2 | ||
| 
						 | 
					56e8af79c3 | ||
| 
						 | 
					25e9ec1782 | ||
| 
						 | 
					1771c852af | ||
| 
						 | 
					8714a45a5c | ||
| 
						 | 
					5e94460608 | ||
| 
						 | 
					d302c0c600 | ||
| 
						 | 
					5c943d7c13 | ||
| 
						 | 
					7629903afb | ||
| 
						 | 
					68eb4091b8 | ||
| 
						 | 
					5062e7a0e1 | ||
| 
						 | 
					30bb640c89 | ||
| 
						 | 
					fbb48c504f | ||
| 
						 | 
					440b0b5574 | ||
| 
						 | 
					c64d385fa6 | ||
| 
						 | 
					0432a10543 | ||
| 
						 | 
					4729bc87fa | ||
| 
						 | 
					e3b64103cc | ||
| 
						 | 
					ebdcb3e4d9 | ||
| 
						 | 
					971522574d | ||
| 
						 | 
					73e939dbbc | ||
| 
						 | 
					a96798ef98 | ||
| 
						 | 
					923e7049f1 | ||
| 
						 | 
					26df542036 | ||
| 
						 | 
					1ccec6950a | ||
| 
						 | 
					b3a122de3c | ||
| 
						 | 
					9ea3643b74 | ||
| 
						 | 
					de617c85c7 | ||
| 
						 | 
					e47f4ef602 | ||
| 
						 | 
					9c201afe76 | ||
| 
						 | 
					2bb64a189d | ||
| 
						 | 
					9853a2e6ab | ||
| 
						 | 
					961be7fd12 | ||
| 
						 | 
					a5a21f47d1 | ||
| 
						 | 
					a06cd84974 | ||
| 
						 | 
					e3703b43c1 | ||
| 
						 | 
					f6dc25c0ce | ||
| 
						 | 
					fad0ec7793 | ||
| 
						 | 
					a302cec993 | ||
| 
						 | 
					6781da45cb | ||
| 
						 | 
					37d526f003 | ||
| 
						 | 
					d74cfefeef | ||
| 
						 | 
					1ffb9d972a | ||
| 
						 | 
					4e5339801b | ||
| 
						 | 
					b8cee477fe | ||
| 
						 | 
					ff2df278d6 | ||
| 
						 | 
					429e989b69 | ||
| 
						 | 
					28541bdb1c | ||
| 
						 | 
					11c595bb09 | ||
| 
						 | 
					fd888eaa68 | ||
| 
						 | 
					3a233b2fd0 | ||
| 
						 | 
					d2df232706 | ||
| 
						 | 
					404e679e66 | ||
| 
						 | 
					4426bf6029 | ||
| 
						 | 
					8d401ad05a | ||
| 
						 | 
					e542816f7d | ||
| 
						 | 
					12cadf0a04 | ||
| 
						 | 
					adc3d3127d | ||
| 
						 | 
					61ab682099 | ||
| 
						 | 
					27fa18dcec | ||
| 
						 | 
					22989592f0 | ||
| 
						 | 
					1f4b10f523 | ||
| 
						 | 
					cbaf8d309b | ||
| 
						 | 
					c05b7cca5e | ||
| 
						 | 
					6ac395da6d | ||
| 
						 | 
					54616ae1b4 | ||
| 
						 | 
					e33dcda907 | ||
| 
						 | 
					04c1b90e57 | ||
| 
						 | 
					ddb8fedef7 | ||
| 
						 | 
					04f4f79cb4 | ||
| 
						 | 
					8890071360 | ||
| 
						 | 
					4b3a997a8e | ||
| 
						 | 
					660223e269 | ||
| 
						 | 
					6d1de2106e | ||
| 
						 | 
					90e33306f1 | ||
| 
						 | 
					f3ac21b3b4 | ||
| 
						 | 
					4859fe67eb | ||
| 
						 | 
					a723673dcc | ||
| 
						 | 
					612fb4cc3c | ||
| 
						 | 
					5fac67d195 | ||
| 
						 | 
					d671862e9a | ||
| 
						 | 
					2a4ab6a811 | ||
| 
						 | 
					459ef7f262 | ||
| 
						 | 
					bd9dc43e59 | ||
| 
						 | 
					971de64494 | ||
| 
						 | 
					926fdcbecd | ||
| 
						 | 
					6b147312cd | ||
| 
						 | 
					2d9152d9b9 | ||
| 
						 | 
					24f9550ce5 | ||
| 
						 | 
					3427aaab8c | ||
| 
						 | 
					4e17d14acc | ||
| 
						 | 
					1750f02ef3 | ||
| 
						 | 
					ae158179bd | ||
| 
						 | 
					c601494779 | ||
| 
						 | 
					646f4e66be | ||
| 
						 | 
					5b5e5c213c | ||
| 
						 | 
					46235684b1 | ||
| 
						 | 
					5b702a1efa | ||
| 
						 | 
					56e9fd2e38 | ||
| 
						 | 
					65f15a706f | ||
| 
						 | 
					eee64cc3a6 | ||
| 
						 | 
					f43fb3c3a3 | ||
| 
						 | 
					79b0025fe6 | ||
| 
						 | 
					c6a039a72f | ||
| 
						 | 
					6f1fa094c2 | ||
| 
						 | 
					1d5a3b647d | ||
| 
						 | 
					af3e1788d1 | ||
| 
						 | 
					b946cb160d | ||
| 
						 | 
					e0241e9dcd | ||
| 
						 | 
					1accc409f6 | ||
| 
						 | 
					f756de276b | ||
| 
						 | 
					ac07a00141 | ||
| 
						 | 
					7ae11de2e4 | ||
| 
						 | 
					bb6be9c939 | ||
| 
						 | 
					9c85a7eff3 | ||
| 
						 | 
					10a665b864 | ||
| 
						 | 
					35dce3c80d | ||
| 
						 | 
					7e6b11ce84 | ||
| 
						 | 
					adcba4fd9a | ||
| 
						 | 
					d3592c451b | ||
| 
						 | 
					24eb33a1c0 | ||
| 
						 | 
					cf1fef8cfb | ||
| 
						 | 
					28bba0666c | ||
| 
						 | 
					4390fd80a3 | ||
| 
						 | 
					4813c5134e | ||
| 
						 | 
					bbef0e173e | ||
| 
						 | 
					3240e19a7c | ||
| 
						 | 
					ac0cd946f0 | ||
| 
						 | 
					61bac6c6e6 | ||
| 
						 | 
					5fd64c5c89 | ||
| 
						 | 
					625f108183 | ||
| 
						 | 
					c45efe8f40 | ||
| 
						 | 
					fe1371f4dc | ||
| 
						 | 
					e3f8a36eaa | ||
| 
						 | 
					41f0d1c622 | ||
| 
						 | 
					6469bb168d | ||
| 
						 | 
					7a869a33f0 | ||
| 
						 | 
					af0da3f897 | ||
| 
						 | 
					32e4eb26ad | ||
| 
						 | 
					10aae33979 | ||
| 
						 | 
					56e85b3ef9 | ||
| 
						 | 
					55dd12c66b | ||
| 
						 | 
					9dd17b464d | ||
| 
						 | 
					2401f81be3 | ||
| 
						 | 
					52a7e26c6d | ||
| 
						 | 
					d729dc20a8 | ||
| 
						 | 
					d3b7a9687b | ||
| 
						 | 
					9d7fc11108 | ||
| 
						 | 
					7969627d3e | ||
| 
						 | 
					82d2e367d4 | ||
| 
						 | 
					972aa691e4 | ||
| 
						 | 
					ac61b8f893 | ||
| 
						 | 
					d9f625e5c8 | ||
| 
						 | 
					e218f16f0f | ||
| 
						 | 
					422d209786 | ||
| 
						 | 
					e972e1f8c2 | ||
| 
						 | 
					cfb90b7b18 | ||
| 
						 | 
					8976ea2436 | ||
| 
						 | 
					01ff09064d | ||
| 
						 | 
					39212f0d7f | ||
| 
						 | 
					8993f4e6b4 | ||
| 
						 | 
					7adad0ee49 | ||
| 
						 | 
					dd8815ec9d | ||
| 
						 | 
					59e62a1f44 | ||
| 
						 | 
					f5f84fe825 | ||
| 
						 | 
					90c2fdd565 | ||
| 
						 | 
					f6d69231e8 | ||
| 
						 | 
					5cc0e21bc7 | ||
| 
						 | 
					703b592793 | ||
| 
						 | 
					75c9430d91 | ||
| 
						 | 
					e5bba00deb | ||
| 
						 | 
					8d90f13e97 | ||
| 
						 | 
					666e33e70b | ||
| 
						 | 
					7eaaa4e426 | ||
| 
						 | 
					166ad942ef | ||
| 
						 | 
					0ff08bbc09 | ||
| 
						 | 
					6e2bcabbc9 | ||
| 
						 | 
					afa191ae41 | ||
| 
						 | 
					93da52c4d2 | ||
| 
						 | 
					0cc0979674 | ||
| 
						 | 
					629f1e94f1 | ||
| 
						 | 
					8c28f346c7 | ||
| 
						 | 
					3cf36e2f94 | ||
| 
						 | 
					1ac07c96b1 | ||
| 
						 | 
					91228c82e6 | ||
| 
						 | 
					28d16728d3 | ||
| 
						 | 
					f24a182ba2 | ||
| 
						 | 
					0065fe1516 | ||
| 
						 | 
					148fa698cc | ||
| 
						 | 
					b25506b045 | ||
| 
						 | 
					0c737fc4df | ||
| 
						 | 
					a8b8507ffc | ||
| 
						 | 
					c33bb3a8a9 | ||
| 
						 | 
					4d09932320 | ||
| 
						 | 
					e018b15641 | ||
| 
						 | 
					3fd469cfe8 | ||
| 
						 | 
					1359142106 | ||
| 
						 | 
					487ba4dad0 | ||
| 
						 | 
					694c590eb6 | ||
| 
						 | 
					b74463c3e6 | ||
| 
						 | 
					98e8a0c201 | ||
| 
						 | 
					91b2f75d04 | ||
| 
						 | 
					f1806046a9 | ||
| 
						 | 
					5b283d6d38 | ||
| 
						 | 
					1340665ac7 | ||
| 
						 | 
					1510db277c | ||
| 
						 | 
					a49669ee58 | ||
| 
						 | 
					09b40b882e | ||
| 
						 | 
					0069163d31 | ||
| 
						 | 
					86c2af4882 | ||
| 
						 | 
					b4b795dcaf | ||
| 
						 | 
					b8ed7ec145 | ||
| 
						 | 
					365a427b57 | ||
| 
						 | 
					e327ae8c95 | ||
| 
						 | 
					4c2f356b35 | ||
| 
						 | 
					e55bce83e3 | ||
| 
						 | 
					ba2433197e | ||
| 
						 | 
					c471bdb446 | ||
| 
						 | 
					cbac9caa52 | ||
| 
						 | 
					edf7094662 | ||
| 
						 | 
					25489b6009 | ||
| 
						 | 
					dc45a613f3 | ||
| 
						 | 
					e0617e01e0 | ||
| 
						 | 
					c7ee727af4 | ||
| 
						 | 
					c5b2a9e24b | ||
| 
						 | 
					101d553df9 | ||
| 
						 | 
					8fb6420b1c | ||
| 
						 | 
					c03d978b46 | ||
| 
						 | 
					2d3cdf60ba | ||
| 
						 | 
					a29fef166b | ||
| 
						 | 
					9fe94f1201 | ||
| 
						 | 
					1b8978a89a | ||
| 
						 | 
					6f188d1284 | ||
| 
						 | 
					a1a336783e | ||
| 
						 | 
					c55bc93f70 | ||
| 
						 | 
					de998f2f39 | ||
| 
						 | 
					950299e52b | ||
| 
						 | 
					23c6650902 | ||
| 
						 | 
					5759692627 | ||
| 
						 | 
					0ab65c225e | ||
| 
						 | 
					8aeb6d3ba2 | ||
| 
						 | 
					c3359edb33 | ||
| 
						 | 
					4d681ffe3d | ||
| 
						 | 
					68628a85b1 | ||
| 
						 | 
					086f1982fa | ||
| 
						 | 
					5ba1c32242 | ||
| 
						 | 
					d2b23ba3a7 | ||
| 
						 | 
					83fbd77c4a | ||
| 
						 | 
					1a054299d4 | ||
| 
						 | 
					e3fb9c2a78 | ||
| 
						 | 
					d1276dc6df | ||
| 
						 | 
					f286bc57f3 | ||
| 
						 | 
					ed48282d09 | ||
| 
						 | 
					2ddd8c72d6 | ||
| 
						 | 
					d0b4bc48e4 | ||
| 
						 | 
					77dbe77117 | ||
| 
						 | 
					6daeffcefd | ||
| 
						 | 
					6d834c019d | ||
| 
						 | 
					905e2906fe | ||
| 
						 | 
					a25b544c3b | ||
| 
						 | 
					da21174c6d | ||
| 
						 | 
					e29f0ee7f8 | ||
| 
						 | 
					983b3cb879 | ||
| 
						 | 
					fd568d9af3 | ||
| 
						 | 
					ca72286386 | ||
| 
						 | 
					dea68bebd8 | ||
| 
						 | 
					ef98f67b41 | ||
| 
						 | 
					6a92b691a0 | ||
| 
						 | 
					bc960cf6d2 | ||
| 
						 | 
					461ce69296 | ||
| 
						 | 
					6a20e6f9ad | ||
| 
						 | 
					cde00a1f4c | ||
| 
						 | 
					5dc691874b | ||
| 
						 | 
					c526ab9a3f | ||
| 
						 | 
					07875a8b1e | ||
| 
						 | 
					ba4789970c | ||
| 
						 | 
					015977cfdf | ||
| 
						 | 
					e513c0f004 | ||
| 
						 | 
					a11970aee0 | ||
| 
						 | 
					4ab37b069b | ||
| 
						 | 
					b6bb6699d1 | ||
| 
						 | 
					078eaff9a8 | ||
| 
						 | 
					a7786b75a0 | ||
| 
						 | 
					d4c11dac8c | ||
| 
						 | 
					2f2f2f7d15 | ||
| 
						 | 
					a92a08c2de | ||
| 
						 | 
					75595b08be | ||
| 
						 | 
					3c7aba0681 | ||
| 
						 | 
					e5d1c30797 | ||
| 
						 | 
					c171d13c8c | ||
| 
						 | 
					65d63de9b6 | ||
| 
						 | 
					9e712e4127 | ||
| 
						 | 
					9007621fd7 | ||
| 
						 | 
					c01a26607e | ||
| 
						 | 
					f6ca70970f | ||
| 
						 | 
					4dc11f05a7 | ||
| 
						 | 
					5e508f7461 | ||
| 
						 | 
					2aceb56606 | ||
| 
						 | 
					d071a074ef | ||
| 
						 | 
					7a459c8c20 | ||
| 
						 | 
					aebd21958a | ||
| 
						 | 
					c542db8bfe | ||
| 
						 | 
					d9dcfe66ec | ||
| 
						 | 
					8517c2e903 | ||
| 
						 | 
					684384892a | ||
| 
						 | 
					d560831d79 | ||
| 
						 | 
					fcc3c8e1b6 | ||
| 
						 | 
					959ffde60e | ||
| 
						 | 
					07715dd50f | ||
| 
						 | 
					03836ee2d2 | ||
| 
						 | 
					50408d9abb | ||
| 
						 | 
					0de7259428 | ||
| 
						 | 
					d054709c2d | ||
| 
						 | 
					da16887915 | ||
| 
						 | 
					6da8ec8d55 | ||
| 
						 | 
					d2752b38c9 | ||
| 
						 | 
					6004367ee2 | ||
| 
						 | 
					ecfeb8e4d3 | ||
| 
						 | 
					456c31262d | ||
| 
						 | 
					9f02575287 | ||
| 
						 | 
					07bca6103f | ||
| 
						 | 
					a58c3950bc | ||
| 
						 | 
					8fe582309e | ||
| 
						 | 
					b41a61c76e | ||
| 
						 | 
					61a5023888 | ||
| 
						 | 
					4396bc0d1a | ||
| 
						 | 
					acfce581fa | ||
| 
						 | 
					88303f39fa | ||
| 
						 | 
					ca19959d7c | ||
| 
						 | 
					9737b35579 | ||
| 
						 | 
					be9c20c357 | ||
| 
						 | 
					12ba4b142e | ||
| 
						 | 
					c096c6934d | ||
| 
						 | 
					17f787fc36 | ||
| 
						 | 
					5cd9a86dcb | ||
| 
						 | 
					83fe4b4ff3 | ||
| 
						 | 
					94accd5abe | ||
| 
						 | 
					3ca0015284 | ||
| 
						 | 
					33eddb6035 | ||
| 
						 | 
					72c58ae36d | ||
| 
						 | 
					35411d199f | ||
| 
						 | 
					d45944a9e2 | ||
| 
						 | 
					86f306ba9e | ||
| 
						 | 
					1b3b2f6e6f | ||
| 
						 | 
					2adb993242 | ||
| 
						 | 
					3ff5b4773b | ||
| 
						 | 
					2cbf4f30f9 | ||
| 
						 | 
					56b6dd31f1 | ||
| 
						 | 
					fc1b49e87d | ||
| 
						 | 
					0089619518 | ||
| 
						 | 
					5a6db28f1d | ||
| 
						 | 
					6819bbd8f8 | ||
| 
						 | 
					634f687c3e | ||
| 
						 | 
					e2a9b85924 | ||
| 
						 | 
					4ccc6aee09 | ||
| 
						 | 
					0eab908b0e | ||
| 
						 | 
					3964f9794b | ||
| 
						 | 
					a45137434b | ||
| 
						 | 
					9b1ebdb6da | ||
| 
						 | 
					5a1533bea9 | ||
| 
						 | 
					0b50ef227b | ||
| 
						 | 
					0e31bc1a67 | ||
| 
						 | 
					8e67df8059 | ||
| 
						 | 
					e1a0949ddb | ||
| 
						 | 
					c5b2c8d971 | ||
| 
						 | 
					a8775ba60b | ||
| 
						 | 
					104906ca11 | ||
| 
						 | 
					ad5f6f0cfe | ||
| 
						 | 
					8356f7fcd3 | ||
| 
						 | 
					225de226b0 | ||
| 
						 | 
					2aaf951357 | ||
| 
						 | 
					82718e62e7 | ||
| 
						 | 
					fd07e1d979 | ||
| 
						 | 
					4dab9c4400 | ||
| 
						 | 
					7e23d865e6 | ||
| 
						 | 
					8f118232e4 | ||
| 
						 | 
					23554cda06 | ||
| 
						 | 
					064385eac6 | ||
| 
						 | 
					6502ed70de | ||
| 
						 | 
					bb894c3e32 | ||
| 
						 | 
					c5858b7032 | ||
| 
						 | 
					99f57ecb73 | ||
| 
						 | 
					cc6c892678 | ||
| 
						 | 
					07a98d2525 | ||
| 
						 | 
					e80f616366 | ||
| 
						 | 
					46be877594 | ||
| 
						 | 
					ac8b48a53c | ||
| 
						 | 
					7fdbd8528a | ||
| 
						 | 
					80970f972b | ||
| 
						 | 
					3c7865cd6f | ||
| 
						 | 
					3a6a66537c | ||
| 
						 | 
					7118bea031 | ||
| 
						 | 
					44bd8e5b54 | ||
| 
						 | 
					efaeb91803 | ||
| 
						 | 
					761c6c6685 | ||
| 
						 | 
					1f55486896 | ||
| 
						 | 
					6818439109 | ||
| 
						 | 
					0a77423073 | ||
| 
						 | 
					c29f8d0187 | ||
| 
						 | 
					2a3f80a82c | ||
| 
						 | 
					75f3adcd95 | ||
| 
						 | 
					daf8ec36ab | ||
| 
						 | 
					6c5632a0b3 | ||
| 
						 | 
					abecc0e8d8 | ||
| 
						 | 
					af9ecf3429 | ||
| 
						 | 
					5fa84439c2 | ||
| 
						 | 
					5d18afcd99 | ||
| 
						 | 
					117cffd2b0 | ||
| 
						 | 
					8ea1a3ed64 | ||
| 
						 | 
					4f29b3c7aa | ||
| 
						 | 
					3325592d67 | ||
| 
						 | 
					0a3ee7d84e | ||
| 
						 | 
					882237120e | ||
| 
						 | 
					71efaf097b | ||
| 
						 | 
					bd60dbb746 | ||
| 
						 | 
					6b5e43ca72 | ||
| 
						 | 
					8d61b1e8df | ||
| 
						 | 
					9c897993bb | ||
| 
						 | 
					93f9475105 | ||
| 
						 | 
					95cd224e3e | ||
| 
						 | 
					b7afeafda9 | ||
| 
						 | 
					7922462bcf | ||
| 
						 | 
					46d433775b | ||
| 
						 | 
					7c4a54de90 | ||
| 
						 | 
					c3f1596498 | ||
| 
						 | 
					0d1949a61b | ||
| 
						 | 
					6a8722f33e | ||
| 
						 | 
					fff66072d4 | ||
| 
						 | 
					1c2e1ab3e5 | ||
| 
						 | 
					68ddd98f5f | ||
| 
						 | 
					0dda3faed5 | ||
| 
						 | 
					40c0c36179 | ||
| 
						 | 
					6b7ced1970 | ||
| 
						 | 
					ed2b76050b | ||
| 
						 | 
					113813617d | ||
| 
						 | 
					c3a209d3f4 | 
@@ -9,7 +9,7 @@ This document provides essential context for AI models interacting with this pro
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## 2. Core Technologies & Stack
 | 
					## 2. Core Technologies & Stack
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*   **Languages:** Python (>=3.10), C++ (gnu++20)
 | 
					*   **Languages:** Python (>=3.11), C++ (gnu++20)
 | 
				
			||||||
*   **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
 | 
					*   **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
 | 
				
			||||||
*   **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
 | 
					*   **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
 | 
				
			||||||
*   **Configuration:** YAML.
 | 
					*   **Configuration:** YAML.
 | 
				
			||||||
@@ -38,7 +38,7 @@ This document provides essential context for AI models interacting with this pro
 | 
				
			|||||||
    5.  **Dashboard** (`esphome/dashboard/`): A web-based interface for device configuration, management, and OTA updates.
 | 
					    5.  **Dashboard** (`esphome/dashboard/`): A web-based interface for device configuration, management, and OTA updates.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*   **Platform Support:**
 | 
					*   **Platform Support:**
 | 
				
			||||||
    1.  **ESP32** (`components/esp32/`): Espressif ESP32 family. Supports multiple variants (S2, S3, C3, etc.) and both IDF and Arduino frameworks.
 | 
					    1.  **ESP32** (`components/esp32/`): Espressif ESP32 family. Supports multiple variants (Original, C2, C3, C5, C6, H2, P4, S2, S3) with ESP-IDF framework. Arduino framework supports only a subset of the variants (Original, C3, S2, S3).
 | 
				
			||||||
    2.  **ESP8266** (`components/esp8266/`): Espressif ESP8266. Arduino framework only, with memory constraints.
 | 
					    2.  **ESP8266** (`components/esp8266/`): Espressif ESP8266. Arduino framework only, with memory constraints.
 | 
				
			||||||
    3.  **RP2040** (`components/rp2040/`): Raspberry Pi Pico/RP2040. Arduino framework with PIO (Programmable I/O) support.
 | 
					    3.  **RP2040** (`components/rp2040/`): Raspberry Pi Pico/RP2040. Arduino framework with PIO (Programmable I/O) support.
 | 
				
			||||||
    4.  **LibreTiny** (`components/libretiny/`): Realtek and Beken chips. Supports multiple chip families and auto-generated components.
 | 
					    4.  **LibreTiny** (`components/libretiny/`): Realtek and Beken chips. Supports multiple chip families and auto-generated components.
 | 
				
			||||||
@@ -60,7 +60,7 @@ This document provides essential context for AI models interacting with this pro
 | 
				
			|||||||
        ├── __init__.py          # Component configuration schema and code generation
 | 
					        ├── __init__.py          # Component configuration schema and code generation
 | 
				
			||||||
        ├── [component].h        # C++ header file (if needed)
 | 
					        ├── [component].h        # C++ header file (if needed)
 | 
				
			||||||
        ├── [component].cpp      # C++ implementation (if needed)
 | 
					        ├── [component].cpp      # C++ implementation (if needed)
 | 
				
			||||||
        └── [platform]/         # Platform-specific implementations
 | 
					        └── [platform]/          # Platform-specific implementations
 | 
				
			||||||
            ├── __init__.py      # Platform-specific configuration
 | 
					            ├── __init__.py      # Platform-specific configuration
 | 
				
			||||||
            ├── [platform].h     # Platform C++ header
 | 
					            ├── [platform].h     # Platform C++ header
 | 
				
			||||||
            └── [platform].cpp   # Platform C++ implementation
 | 
					            └── [platform].cpp   # Platform C++ implementation
 | 
				
			||||||
@@ -150,7 +150,8 @@ This document provides essential context for AI models interacting with this pro
 | 
				
			|||||||
*   **Configuration Validation:**
 | 
					*   **Configuration Validation:**
 | 
				
			||||||
    *   **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`.
 | 
					    *   **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`.
 | 
				
			||||||
    *   **Complex Validation:** `cv.All(cv.string, cv.Length(min=1, max=50))`, `cv.Any(cv.int_, cv.string)`.
 | 
					    *   **Complex Validation:** `cv.All(cv.string, cv.Length(min=1, max=50))`, `cv.Any(cv.int_, cv.string)`.
 | 
				
			||||||
    *   **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `cv.only_with_arduino`.
 | 
					    *   **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `esp32.only_on_variant(...)`, `cv.only_on_esp32`, `cv.only_on_esp8266`, `cv.only_on_rp2040`.
 | 
				
			||||||
 | 
					    *   **Framework-Specific:** `cv.only_with_framework(...)`, `cv.only_with_arduino`, `cv.only_with_esp_idf`.
 | 
				
			||||||
    *   **Schema Extensions:**
 | 
					    *   **Schema Extensions:**
 | 
				
			||||||
        ```python
 | 
					        ```python
 | 
				
			||||||
        CONFIG_SCHEMA = cv.Schema({ ... })
 | 
					        CONFIG_SCHEMA = cv.Schema({ ... })
 | 
				
			||||||
@@ -185,6 +186,11 @@ This document provides essential context for AI models interacting with this pro
 | 
				
			|||||||
        └── components/[component]/ # Component-specific tests
 | 
					        └── components/[component]/ # Component-specific tests
 | 
				
			||||||
        ```
 | 
					        ```
 | 
				
			||||||
        Run them using `script/test_build_components`. Use `-c <component>` to test specific components and `-t <target>` for specific platforms.
 | 
					        Run them using `script/test_build_components`. Use `-c <component>` to test specific components and `-t <target>` for specific platforms.
 | 
				
			||||||
 | 
					    *   **Testing All Components Together:** To verify that all components can be tested together without ID conflicts or configuration issues, use:
 | 
				
			||||||
 | 
					        ```bash
 | 
				
			||||||
 | 
					        ./script/test_component_grouping.py -e config --all
 | 
				
			||||||
 | 
					        ```
 | 
				
			||||||
 | 
					        This tests all components in a single build to catch conflicts that might not appear when testing components individually. Use `-e config` for fast configuration validation, or `-e compile` for full compilation testing.
 | 
				
			||||||
*   **Debugging and Troubleshooting:**
 | 
					*   **Debugging and Troubleshooting:**
 | 
				
			||||||
    *   **Debug Tools:**
 | 
					    *   **Debug Tools:**
 | 
				
			||||||
        - `esphome config <file>.yaml` to validate configuration.
 | 
					        - `esphome config <file>.yaml` to validate configuration.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1 +1 @@
 | 
				
			|||||||
6af8b429b94191fe8e239fcb3b73f7982d0266cb5b05ffbc81edaeac1bc8c273
 | 
					049d60eed541730efaa4c0dc5d337b4287bf29b6daa350b5dfc1f23915f1c52f
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/actions/build-image/action.yaml
									
									
									
									
										vendored
									
									
								
							@@ -47,7 +47,7 @@ runs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    - name: Build and push to ghcr by digest
 | 
					    - name: Build and push to ghcr by digest
 | 
				
			||||||
      id: build-ghcr
 | 
					      id: build-ghcr
 | 
				
			||||||
      uses: docker/build-push-action@v6.18.0
 | 
					      uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
 | 
				
			||||||
      env:
 | 
					      env:
 | 
				
			||||||
        DOCKER_BUILD_SUMMARY: false
 | 
					        DOCKER_BUILD_SUMMARY: false
 | 
				
			||||||
        DOCKER_BUILD_RECORD_UPLOAD: false
 | 
					        DOCKER_BUILD_RECORD_UPLOAD: false
 | 
				
			||||||
@@ -73,7 +73,7 @@ runs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    - name: Build and push to dockerhub by digest
 | 
					    - name: Build and push to dockerhub by digest
 | 
				
			||||||
      id: build-dockerhub
 | 
					      id: build-dockerhub
 | 
				
			||||||
      uses: docker/build-push-action@v6.18.0
 | 
					      uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
 | 
				
			||||||
      env:
 | 
					      env:
 | 
				
			||||||
        DOCKER_BUILD_SUMMARY: false
 | 
					        DOCKER_BUILD_SUMMARY: false
 | 
				
			||||||
        DOCKER_BUILD_RECORD_UPLOAD: false
 | 
					        DOCKER_BUILD_RECORD_UPLOAD: false
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/actions/restore-python/action.yml
									
									
									
									
										vendored
									
									
								
							@@ -17,12 +17,12 @@ runs:
 | 
				
			|||||||
  steps:
 | 
					  steps:
 | 
				
			||||||
    - name: Set up Python ${{ inputs.python-version }}
 | 
					    - name: Set up Python ${{ inputs.python-version }}
 | 
				
			||||||
      id: python
 | 
					      id: python
 | 
				
			||||||
      uses: actions/setup-python@v5.6.0
 | 
					      uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        python-version: ${{ inputs.python-version }}
 | 
					        python-version: ${{ inputs.python-version }}
 | 
				
			||||||
    - name: Restore Python virtual environment
 | 
					    - name: Restore Python virtual environment
 | 
				
			||||||
      id: cache-venv
 | 
					      id: cache-venv
 | 
				
			||||||
      uses: actions/cache/restore@v4.2.4
 | 
					      uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        path: venv
 | 
					        path: venv
 | 
				
			||||||
        # yamllint disable-line rule:line-length
 | 
					        # yamllint disable-line rule:line-length
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										36
									
								
								.github/workflows/auto-label-pr.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								.github/workflows/auto-label-pr.yml
									
									
									
									
										vendored
									
									
								
							@@ -22,17 +22,17 @@ jobs:
 | 
				
			|||||||
    if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
 | 
					    if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout
 | 
					      - name: Checkout
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Generate a token
 | 
					      - name: Generate a token
 | 
				
			||||||
        id: generate-token
 | 
					        id: generate-token
 | 
				
			||||||
        uses: actions/create-github-app-token@v2
 | 
					        uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
 | 
					          app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
 | 
				
			||||||
          private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
 | 
					          private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Auto Label PR
 | 
					      - name: Auto Label PR
 | 
				
			||||||
        uses: actions/github-script@v7.0.1
 | 
					        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          github-token: ${{ steps.generate-token.outputs.token }}
 | 
					          github-token: ${{ steps.generate-token.outputs.token }}
 | 
				
			||||||
          script: |
 | 
					          script: |
 | 
				
			||||||
@@ -105,7 +105,9 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            // Calculate data from PR files
 | 
					            // Calculate data from PR files
 | 
				
			||||||
            const changedFiles = prFiles.map(file => file.filename);
 | 
					            const changedFiles = prFiles.map(file => file.filename);
 | 
				
			||||||
            const totalChanges = prFiles.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
 | 
					            const totalAdditions = prFiles.reduce((sum, file) => sum + (file.additions || 0), 0);
 | 
				
			||||||
 | 
					            const totalDeletions = prFiles.reduce((sum, file) => sum + (file.deletions || 0), 0);
 | 
				
			||||||
 | 
					            const totalChanges = totalAdditions + totalDeletions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            console.log('Current labels:', currentLabels.join(', '));
 | 
					            console.log('Current labels:', currentLabels.join(', '));
 | 
				
			||||||
            console.log('Changed files:', changedFiles.length);
 | 
					            console.log('Changed files:', changedFiles.length);
 | 
				
			||||||
@@ -231,16 +233,21 @@ jobs:
 | 
				
			|||||||
            // Strategy: PR size detection
 | 
					            // Strategy: PR size detection
 | 
				
			||||||
            async function detectPRSize() {
 | 
					            async function detectPRSize() {
 | 
				
			||||||
              const labels = new Set();
 | 
					              const labels = new Set();
 | 
				
			||||||
              const testChanges = prFiles
 | 
					 | 
				
			||||||
                .filter(file => file.filename.startsWith('tests/'))
 | 
					 | 
				
			||||||
                .reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              const nonTestChanges = totalChanges - testChanges;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
              if (totalChanges <= SMALL_PR_THRESHOLD) {
 | 
					              if (totalChanges <= SMALL_PR_THRESHOLD) {
 | 
				
			||||||
                labels.add('small-pr');
 | 
					                labels.add('small-pr');
 | 
				
			||||||
 | 
					                return labels;
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              const testAdditions = prFiles
 | 
				
			||||||
 | 
					                .filter(file => file.filename.startsWith('tests/'))
 | 
				
			||||||
 | 
					                .reduce((sum, file) => sum + (file.additions || 0), 0);
 | 
				
			||||||
 | 
					              const testDeletions = prFiles
 | 
				
			||||||
 | 
					                .filter(file => file.filename.startsWith('tests/'))
 | 
				
			||||||
 | 
					                .reduce((sum, file) => sum + (file.deletions || 0), 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              // Don't add too-big if mega-pr label is already present
 | 
					              // Don't add too-big if mega-pr label is already present
 | 
				
			||||||
              if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
 | 
					              if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
 | 
				
			||||||
                labels.add('too-big');
 | 
					                labels.add('too-big');
 | 
				
			||||||
@@ -375,7 +382,7 @@ jobs:
 | 
				
			|||||||
              const labels = new Set();
 | 
					              const labels = new Set();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              // Check for missing tests
 | 
					              // Check for missing tests
 | 
				
			||||||
              if ((allLabels.has('new-component') || allLabels.has('new-platform')) && !allLabels.has('has-tests')) {
 | 
					              if ((allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature')) && !allLabels.has('has-tests')) {
 | 
				
			||||||
                labels.add('needs-tests');
 | 
					                labels.add('needs-tests');
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -412,10 +419,13 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
              // Too big message
 | 
					              // Too big message
 | 
				
			||||||
              if (finalLabels.includes('too-big')) {
 | 
					              if (finalLabels.includes('too-big')) {
 | 
				
			||||||
                const testChanges = prFiles
 | 
					                const testAdditions = prFiles
 | 
				
			||||||
                  .filter(file => file.filename.startsWith('tests/'))
 | 
					                  .filter(file => file.filename.startsWith('tests/'))
 | 
				
			||||||
                  .reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
 | 
					                  .reduce((sum, file) => sum + (file.additions || 0), 0);
 | 
				
			||||||
                const nonTestChanges = totalChanges - testChanges;
 | 
					                const testDeletions = prFiles
 | 
				
			||||||
 | 
					                  .filter(file => file.filename.startsWith('tests/'))
 | 
				
			||||||
 | 
					                  .reduce((sum, file) => sum + (file.deletions || 0), 0);
 | 
				
			||||||
 | 
					                const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const tooManyLabels = finalLabels.length > MAX_LABELS;
 | 
					                const tooManyLabels = finalLabels.length > MAX_LABELS;
 | 
				
			||||||
                const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
 | 
					                const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/ci-api-proto.yml
									
									
									
									
										vendored
									
									
								
							@@ -21,9 +21,9 @@ jobs:
 | 
				
			|||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout
 | 
					      - name: Checkout
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
      - name: Set up Python
 | 
					      - name: Set up Python
 | 
				
			||||||
        uses: actions/setup-python@v5.6.0
 | 
					        uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: "3.11"
 | 
					          python-version: "3.11"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -47,7 +47,7 @@ jobs:
 | 
				
			|||||||
          fi
 | 
					          fi
 | 
				
			||||||
      - if: failure()
 | 
					      - if: failure()
 | 
				
			||||||
        name: Review PR
 | 
					        name: Review PR
 | 
				
			||||||
        uses: actions/github-script@v7.0.1
 | 
					        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          script: |
 | 
					          script: |
 | 
				
			||||||
            await github.rest.pulls.createReview({
 | 
					            await github.rest.pulls.createReview({
 | 
				
			||||||
@@ -62,7 +62,7 @@ jobs:
 | 
				
			|||||||
        run: git diff
 | 
					        run: git diff
 | 
				
			||||||
      - if: failure()
 | 
					      - if: failure()
 | 
				
			||||||
        name: Archive artifacts
 | 
					        name: Archive artifacts
 | 
				
			||||||
        uses: actions/upload-artifact@v4.6.2
 | 
					        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          name: generated-proto-files
 | 
					          name: generated-proto-files
 | 
				
			||||||
          path: |
 | 
					          path: |
 | 
				
			||||||
@@ -70,7 +70,7 @@ jobs:
 | 
				
			|||||||
            esphome/components/api/api_pb2_service.*
 | 
					            esphome/components/api/api_pb2_service.*
 | 
				
			||||||
      - if: success()
 | 
					      - if: success()
 | 
				
			||||||
        name: Dismiss review
 | 
					        name: Dismiss review
 | 
				
			||||||
        uses: actions/github-script@v7.0.1
 | 
					        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          script: |
 | 
					          script: |
 | 
				
			||||||
            let reviews = await github.rest.pulls.listReviews({
 | 
					            let reviews = await github.rest.pulls.listReviews({
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								.github/workflows/ci-clang-tidy-hash.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/ci-clang-tidy-hash.yml
									
									
									
									
										vendored
									
									
								
							@@ -6,6 +6,7 @@ on:
 | 
				
			|||||||
      - ".clang-tidy"
 | 
					      - ".clang-tidy"
 | 
				
			||||||
      - "platformio.ini"
 | 
					      - "platformio.ini"
 | 
				
			||||||
      - "requirements_dev.txt"
 | 
					      - "requirements_dev.txt"
 | 
				
			||||||
 | 
					      - "sdkconfig.defaults"
 | 
				
			||||||
      - ".clang-tidy.hash"
 | 
					      - ".clang-tidy.hash"
 | 
				
			||||||
      - "script/clang_tidy_hash.py"
 | 
					      - "script/clang_tidy_hash.py"
 | 
				
			||||||
      - ".github/workflows/ci-clang-tidy-hash.yml"
 | 
					      - ".github/workflows/ci-clang-tidy-hash.yml"
 | 
				
			||||||
@@ -20,10 +21,10 @@ jobs:
 | 
				
			|||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout
 | 
					      - name: Checkout
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Set up Python
 | 
					      - name: Set up Python
 | 
				
			||||||
        uses: actions/setup-python@v5.6.0
 | 
					        uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: "3.11"
 | 
					          python-version: "3.11"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -41,7 +42,7 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      - if: failure()
 | 
					      - if: failure()
 | 
				
			||||||
        name: Request changes
 | 
					        name: Request changes
 | 
				
			||||||
        uses: actions/github-script@v7.0.1
 | 
					        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          script: |
 | 
					          script: |
 | 
				
			||||||
            await github.rest.pulls.createReview({
 | 
					            await github.rest.pulls.createReview({
 | 
				
			||||||
@@ -54,7 +55,7 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      - if: success()
 | 
					      - if: success()
 | 
				
			||||||
        name: Dismiss review
 | 
					        name: Dismiss review
 | 
				
			||||||
        uses: actions/github-script@v7.0.1
 | 
					        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          script: |
 | 
					          script: |
 | 
				
			||||||
            let reviews = await github.rest.pulls.listReviews({
 | 
					            let reviews = await github.rest.pulls.listReviews({
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -43,13 +43,13 @@ jobs:
 | 
				
			|||||||
          - "docker"
 | 
					          - "docker"
 | 
				
			||||||
          # - "lint"
 | 
					          # - "lint"
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v5.0.0
 | 
					      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
      - name: Set up Python
 | 
					      - name: Set up Python
 | 
				
			||||||
        uses: actions/setup-python@v5.6.0
 | 
					        uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: "3.11"
 | 
					          python-version: "3.11"
 | 
				
			||||||
      - name: Set up Docker Buildx
 | 
					      - name: Set up Docker Buildx
 | 
				
			||||||
        uses: docker/setup-buildx-action@v3.11.1
 | 
					        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Set TAG
 | 
					      - name: Set TAG
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										143
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										143
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -36,18 +36,18 @@ jobs:
 | 
				
			|||||||
      cache-key: ${{ steps.cache-key.outputs.key }}
 | 
					      cache-key: ${{ steps.cache-key.outputs.key }}
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
      - name: Generate cache-key
 | 
					      - name: Generate cache-key
 | 
				
			||||||
        id: cache-key
 | 
					        id: cache-key
 | 
				
			||||||
        run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
 | 
					        run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
 | 
				
			||||||
      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
 | 
					      - name: Set up Python ${{ env.DEFAULT_PYTHON }}
 | 
				
			||||||
        id: python
 | 
					        id: python
 | 
				
			||||||
        uses: actions/setup-python@v5.6.0
 | 
					        uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
					          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
				
			||||||
      - name: Restore Python virtual environment
 | 
					      - name: Restore Python virtual environment
 | 
				
			||||||
        id: cache-venv
 | 
					        id: cache-venv
 | 
				
			||||||
        uses: actions/cache@v4.2.4
 | 
					        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          path: venv
 | 
					          path: venv
 | 
				
			||||||
          # yamllint disable-line rule:line-length
 | 
					          # yamllint disable-line rule:line-length
 | 
				
			||||||
@@ -70,7 +70,7 @@ jobs:
 | 
				
			|||||||
    if: needs.determine-jobs.outputs.python-linters == 'true'
 | 
					    if: needs.determine-jobs.outputs.python-linters == 'true'
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
      - name: Restore Python
 | 
					      - name: Restore Python
 | 
				
			||||||
        uses: ./.github/actions/restore-python
 | 
					        uses: ./.github/actions/restore-python
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
@@ -91,7 +91,7 @@ jobs:
 | 
				
			|||||||
      - common
 | 
					      - common
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
      - name: Restore Python
 | 
					      - name: Restore Python
 | 
				
			||||||
        uses: ./.github/actions/restore-python
 | 
					        uses: ./.github/actions/restore-python
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
@@ -105,6 +105,7 @@ jobs:
 | 
				
			|||||||
          script/ci-custom.py
 | 
					          script/ci-custom.py
 | 
				
			||||||
          script/build_codeowners.py --check
 | 
					          script/build_codeowners.py --check
 | 
				
			||||||
          script/build_language_schema.py --check
 | 
					          script/build_language_schema.py --check
 | 
				
			||||||
 | 
					          script/generate-esp32-boards.py --check
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  pytest:
 | 
					  pytest:
 | 
				
			||||||
    name: Run pytest
 | 
					    name: Run pytest
 | 
				
			||||||
@@ -136,7 +137,7 @@ jobs:
 | 
				
			|||||||
      - common
 | 
					      - common
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
      - name: Restore Python
 | 
					      - name: Restore Python
 | 
				
			||||||
        id: restore-python
 | 
					        id: restore-python
 | 
				
			||||||
        uses: ./.github/actions/restore-python
 | 
					        uses: ./.github/actions/restore-python
 | 
				
			||||||
@@ -156,12 +157,12 @@ jobs:
 | 
				
			|||||||
          . venv/bin/activate
 | 
					          . venv/bin/activate
 | 
				
			||||||
          pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
 | 
					          pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
 | 
				
			||||||
      - name: Upload coverage to Codecov
 | 
					      - name: Upload coverage to Codecov
 | 
				
			||||||
        uses: codecov/codecov-action@v5.4.3
 | 
					        uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          token: ${{ secrets.CODECOV_TOKEN }}
 | 
					          token: ${{ secrets.CODECOV_TOKEN }}
 | 
				
			||||||
      - name: Save Python virtual environment cache
 | 
					      - name: Save Python virtual environment cache
 | 
				
			||||||
        if: github.ref == 'refs/heads/dev'
 | 
					        if: github.ref == 'refs/heads/dev'
 | 
				
			||||||
        uses: actions/cache/save@v4.2.4
 | 
					        uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          path: venv
 | 
					          path: venv
 | 
				
			||||||
          key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
 | 
					          key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
 | 
				
			||||||
@@ -176,10 +177,11 @@ jobs:
 | 
				
			|||||||
      clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
 | 
					      clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
 | 
				
			||||||
      python-linters: ${{ steps.determine.outputs.python-linters }}
 | 
					      python-linters: ${{ steps.determine.outputs.python-linters }}
 | 
				
			||||||
      changed-components: ${{ steps.determine.outputs.changed-components }}
 | 
					      changed-components: ${{ steps.determine.outputs.changed-components }}
 | 
				
			||||||
 | 
					      changed-components-with-tests: ${{ steps.determine.outputs.changed-components-with-tests }}
 | 
				
			||||||
      component-test-count: ${{ steps.determine.outputs.component-test-count }}
 | 
					      component-test-count: ${{ steps.determine.outputs.component-test-count }}
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          # Fetch enough history to find the merge base
 | 
					          # Fetch enough history to find the merge base
 | 
				
			||||||
          fetch-depth: 2
 | 
					          fetch-depth: 2
 | 
				
			||||||
@@ -203,6 +205,7 @@ jobs:
 | 
				
			|||||||
          echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
 | 
					          echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
 | 
				
			||||||
          echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
 | 
					          echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
 | 
				
			||||||
          echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
 | 
					          echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
 | 
				
			||||||
 | 
					          echo "changed-components-with-tests=$(echo "$output" | jq -c '.changed_components_with_tests')" >> $GITHUB_OUTPUT
 | 
				
			||||||
          echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT
 | 
					          echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  integration-tests:
 | 
					  integration-tests:
 | 
				
			||||||
@@ -214,15 +217,15 @@ jobs:
 | 
				
			|||||||
    if: needs.determine-jobs.outputs.integration-tests == 'true'
 | 
					    if: needs.determine-jobs.outputs.integration-tests == 'true'
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
      - name: Set up Python 3.13
 | 
					      - name: Set up Python 3.13
 | 
				
			||||||
        id: python
 | 
					        id: python
 | 
				
			||||||
        uses: actions/setup-python@v5.6.0
 | 
					        uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: "3.13"
 | 
					          python-version: "3.13"
 | 
				
			||||||
      - name: Restore Python virtual environment
 | 
					      - name: Restore Python virtual environment
 | 
				
			||||||
        id: cache-venv
 | 
					        id: cache-venv
 | 
				
			||||||
        uses: actions/cache@v4.2.4
 | 
					        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          path: venv
 | 
					          path: venv
 | 
				
			||||||
          key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
 | 
					          key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
 | 
				
			||||||
@@ -287,7 +290,7 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          # Need history for HEAD~1 to work for checking changed files
 | 
					          # Need history for HEAD~1 to work for checking changed files
 | 
				
			||||||
          fetch-depth: 2
 | 
					          fetch-depth: 2
 | 
				
			||||||
@@ -300,14 +303,14 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      - name: Cache platformio
 | 
					      - name: Cache platformio
 | 
				
			||||||
        if: github.ref == 'refs/heads/dev'
 | 
					        if: github.ref == 'refs/heads/dev'
 | 
				
			||||||
        uses: actions/cache@v4.2.4
 | 
					        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          path: ~/.platformio
 | 
					          path: ~/.platformio
 | 
				
			||||||
          key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
 | 
					          key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Cache platformio
 | 
					      - name: Cache platformio
 | 
				
			||||||
        if: github.ref != 'refs/heads/dev'
 | 
					        if: github.ref != 'refs/heads/dev'
 | 
				
			||||||
        uses: actions/cache/restore@v4.2.4
 | 
					        uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          path: ~/.platformio
 | 
					          path: ~/.platformio
 | 
				
			||||||
          key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
 | 
					          key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
 | 
				
			||||||
@@ -366,31 +369,32 @@ jobs:
 | 
				
			|||||||
      fail-fast: false
 | 
					      fail-fast: false
 | 
				
			||||||
      max-parallel: 2
 | 
					      max-parallel: 2
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        file: ${{ fromJson(needs.determine-jobs.outputs.changed-components) }}
 | 
					        file: ${{ fromJson(needs.determine-jobs.outputs.changed-components-with-tests) }}
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Install dependencies
 | 
					      - name: Cache apt packages
 | 
				
			||||||
        run: |
 | 
					        uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.5.3
 | 
				
			||||||
          sudo apt-get update
 | 
					        with:
 | 
				
			||||||
          sudo apt-get install libsdl2-dev
 | 
					          packages: libsdl2-dev
 | 
				
			||||||
 | 
					          version: 1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
      - name: Restore Python
 | 
					      - name: Restore Python
 | 
				
			||||||
        uses: ./.github/actions/restore-python
 | 
					        uses: ./.github/actions/restore-python
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
					          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
				
			||||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
					          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
				
			||||||
      - name: test_build_components -e config -c ${{ matrix.file }}
 | 
					      - name: Validate config for ${{ matrix.file }}
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          . venv/bin/activate
 | 
					          . venv/bin/activate
 | 
				
			||||||
          ./script/test_build_components -e config -c ${{ matrix.file }}
 | 
					          python3 script/test_build_components.py -e config -c ${{ matrix.file }}
 | 
				
			||||||
      - name: test_build_components -e compile -c ${{ matrix.file }}
 | 
					      - name: Compile config for ${{ matrix.file }}
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          . venv/bin/activate
 | 
					          . venv/bin/activate
 | 
				
			||||||
          ./script/test_build_components -e compile -c ${{ matrix.file }}
 | 
					          python3 script/test_build_components.py -e compile -c ${{ matrix.file }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test-build-components-splitter:
 | 
					  test-build-components-splitter:
 | 
				
			||||||
    name: Split components for testing into 20 groups maximum
 | 
					    name: Split components for intelligent grouping (40 weighted per batch)
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-24.04
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - common
 | 
					      - common
 | 
				
			||||||
@@ -400,15 +404,27 @@ jobs:
 | 
				
			|||||||
      matrix: ${{ steps.split.outputs.components }}
 | 
					      matrix: ${{ steps.split.outputs.components }}
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
      - name: Split components into 20 groups
 | 
					      - name: Restore Python
 | 
				
			||||||
 | 
					        uses: ./.github/actions/restore-python
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
				
			||||||
 | 
					          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
				
			||||||
 | 
					      - name: Split components intelligently based on bus configurations
 | 
				
			||||||
        id: split
 | 
					        id: split
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          components=$(echo '${{ needs.determine-jobs.outputs.changed-components }}' | jq -c '.[]' | shuf | jq -s -c '[_nwise(20) | join(" ")]')
 | 
					          . venv/bin/activate
 | 
				
			||||||
          echo "components=$components" >> $GITHUB_OUTPUT
 | 
					
 | 
				
			||||||
 | 
					          # Use intelligent splitter that groups components with same bus configs
 | 
				
			||||||
 | 
					          components='${{ needs.determine-jobs.outputs.changed-components-with-tests }}'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          echo "Splitting components intelligently..."
 | 
				
			||||||
 | 
					          output=$(python3 script/split_components_for_ci.py --components "$components" --batch-size 40 --output github)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          echo "$output" >> $GITHUB_OUTPUT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test-build-components-split:
 | 
					  test-build-components-split:
 | 
				
			||||||
    name: Test split components
 | 
					    name: Test components batch (${{ matrix.components }})
 | 
				
			||||||
    runs-on: ubuntu-24.04
 | 
					    runs-on: ubuntu-24.04
 | 
				
			||||||
    needs:
 | 
					    needs:
 | 
				
			||||||
      - common
 | 
					      - common
 | 
				
			||||||
@@ -417,39 +433,62 @@ jobs:
 | 
				
			|||||||
    if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) >= 100
 | 
					    if: github.event_name == 'pull_request' && fromJSON(needs.determine-jobs.outputs.component-test-count) >= 100
 | 
				
			||||||
    strategy:
 | 
					    strategy:
 | 
				
			||||||
      fail-fast: false
 | 
					      fail-fast: false
 | 
				
			||||||
      max-parallel: 4
 | 
					      max-parallel: ${{ (github.base_ref == 'beta' || github.base_ref == 'release') && 8 || 4 }}
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        components: ${{ fromJson(needs.test-build-components-splitter.outputs.matrix) }}
 | 
					        components: ${{ fromJson(needs.test-build-components-splitter.outputs.matrix) }}
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Show disk space
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          echo "Available disk space:"
 | 
				
			||||||
 | 
					          df -h
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: List components
 | 
					      - name: List components
 | 
				
			||||||
        run: echo ${{ matrix.components }}
 | 
					        run: echo ${{ matrix.components }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Install dependencies
 | 
					      - name: Cache apt packages
 | 
				
			||||||
        run: |
 | 
					        uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.5.3
 | 
				
			||||||
          sudo apt-get update
 | 
					        with:
 | 
				
			||||||
          sudo apt-get install libsdl2-dev
 | 
					          packages: libsdl2-dev
 | 
				
			||||||
 | 
					          version: 1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
      - name: Restore Python
 | 
					      - name: Restore Python
 | 
				
			||||||
        uses: ./.github/actions/restore-python
 | 
					        uses: ./.github/actions/restore-python
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
					          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
				
			||||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
					          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
				
			||||||
      - name: Validate config
 | 
					      - name: Validate and compile components with intelligent grouping
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          . venv/bin/activate
 | 
					          . venv/bin/activate
 | 
				
			||||||
          for component in ${{ matrix.components }}; do
 | 
					          # Use /mnt for build files (70GB available vs ~29GB on /)
 | 
				
			||||||
            ./script/test_build_components -e config -c $component
 | 
					          # Bind mount PlatformIO directory to /mnt (tools, packages, build cache all go there)
 | 
				
			||||||
          done
 | 
					          sudo mkdir -p /mnt/platformio
 | 
				
			||||||
      - name: Compile config
 | 
					          sudo chown $USER:$USER /mnt/platformio
 | 
				
			||||||
        run: |
 | 
					          mkdir -p ~/.platformio
 | 
				
			||||||
          . venv/bin/activate
 | 
					          sudo mount --bind /mnt/platformio ~/.platformio
 | 
				
			||||||
          mkdir build_cache
 | 
					
 | 
				
			||||||
          export PLATFORMIO_BUILD_CACHE_DIR=$PWD/build_cache
 | 
					          # Bind mount test build directory to /mnt
 | 
				
			||||||
          for component in ${{ matrix.components }}; do
 | 
					          sudo mkdir -p /mnt/test_build_components_build
 | 
				
			||||||
            ./script/test_build_components -e compile -c $component
 | 
					          sudo chown $USER:$USER /mnt/test_build_components_build
 | 
				
			||||||
          done
 | 
					          mkdir -p tests/test_build_components/build
 | 
				
			||||||
 | 
					          sudo mount --bind /mnt/test_build_components_build tests/test_build_components/build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          # Convert space-separated components to comma-separated for Python script
 | 
				
			||||||
 | 
					          components_csv=$(echo "${{ matrix.components }}" | tr ' ' ',')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          echo "Testing components: $components_csv"
 | 
				
			||||||
 | 
					          echo ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          # Run config validation with grouping
 | 
				
			||||||
 | 
					          python3 script/test_build_components.py -e config -c "$components_csv" -f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          echo ""
 | 
				
			||||||
 | 
					          echo "Config validation passed! Starting compilation..."
 | 
				
			||||||
 | 
					          echo ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          # Run compilation with grouping
 | 
				
			||||||
 | 
					          python3 script/test_build_components.py -e compile -c "$components_csv" -f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  pre-commit-ci-lite:
 | 
					  pre-commit-ci-lite:
 | 
				
			||||||
    name: pre-commit.ci lite
 | 
					    name: pre-commit.ci lite
 | 
				
			||||||
@@ -459,16 +498,16 @@ jobs:
 | 
				
			|||||||
    if: github.event_name == 'pull_request' && github.base_ref != 'beta' && github.base_ref != 'release'
 | 
					    if: github.event_name == 'pull_request' && github.base_ref != 'beta' && github.base_ref != 'release'
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Check out code from GitHub
 | 
					      - name: Check out code from GitHub
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
      - name: Restore Python
 | 
					      - name: Restore Python
 | 
				
			||||||
        uses: ./.github/actions/restore-python
 | 
					        uses: ./.github/actions/restore-python
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
					          python-version: ${{ env.DEFAULT_PYTHON }}
 | 
				
			||||||
          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
					          cache-key: ${{ needs.common.outputs.cache-key }}
 | 
				
			||||||
      - uses: pre-commit/action@v3.0.1
 | 
					      - uses: esphome/action@43cd1109c09c544d97196f7730ee5b2e0cc6d81e # v3.0.1 fork with pinned actions/cache
 | 
				
			||||||
        env:
 | 
					        env:
 | 
				
			||||||
          SKIP: pylint,clang-tidy-hash
 | 
					          SKIP: pylint,clang-tidy-hash
 | 
				
			||||||
      - uses: pre-commit-ci/lite-action@v1.1.0
 | 
					      - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0
 | 
				
			||||||
        if: always()
 | 
					        if: always()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ci-status:
 | 
					  ci-status:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,7 @@ jobs:
 | 
				
			|||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Request reviews from component codeowners
 | 
					      - name: Request reviews from component codeowners
 | 
				
			||||||
        uses: actions/github-script@v7.0.1
 | 
					        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          script: |
 | 
					          script: |
 | 
				
			||||||
            const owner = context.repo.owner;
 | 
					            const owner = context.repo.owner;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							@@ -54,11 +54,11 @@ jobs:
 | 
				
			|||||||
            # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
 | 
					            # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout repository
 | 
					      - name: Checkout repository
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      # Initializes the CodeQL tools for scanning.
 | 
					      # Initializes the CodeQL tools for scanning.
 | 
				
			||||||
      - name: Initialize CodeQL
 | 
					      - name: Initialize CodeQL
 | 
				
			||||||
        uses: github/codeql-action/init@v3
 | 
					        uses: github/codeql-action/init@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          languages: ${{ matrix.language }}
 | 
					          languages: ${{ matrix.language }}
 | 
				
			||||||
          build-mode: ${{ matrix.build-mode }}
 | 
					          build-mode: ${{ matrix.build-mode }}
 | 
				
			||||||
@@ -86,6 +86,6 @@ jobs:
 | 
				
			|||||||
          exit 1
 | 
					          exit 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Perform CodeQL Analysis
 | 
					      - name: Perform CodeQL Analysis
 | 
				
			||||||
        uses: github/codeql-action/analyze@v3
 | 
					        uses: github/codeql-action/analyze@e296a935590eb16afc0c0108289f68c87e2a89a5 # v4.30.7
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          category: "/language:${{matrix.language}}"
 | 
					          category: "/language:${{matrix.language}}"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/external-component-bot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/external-component-bot.yml
									
									
									
									
										vendored
									
									
								
							@@ -15,7 +15,7 @@ jobs:
 | 
				
			|||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Add external component comment
 | 
					      - name: Add external component comment
 | 
				
			||||||
        uses: actions/github-script@v7.0.1
 | 
					        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          github-token: ${{ secrets.GITHUB_TOKEN }}
 | 
					          github-token: ${{ secrets.GITHUB_TOKEN }}
 | 
				
			||||||
          script: |
 | 
					          script: |
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/issue-codeowner-notify.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/issue-codeowner-notify.yml
									
									
									
									
										vendored
									
									
								
							@@ -19,7 +19,7 @@ jobs:
 | 
				
			|||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Notify codeowners for component issues
 | 
					      - name: Notify codeowners for component issues
 | 
				
			||||||
        uses: actions/github-script@v7.0.1
 | 
					        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          script: |
 | 
					          script: |
 | 
				
			||||||
            const owner = context.repo.owner;
 | 
					            const owner = context.repo.owner;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										24
									
								
								.github/workflows/needs-docs.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/needs-docs.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,24 +0,0 @@
 | 
				
			|||||||
name: Needs Docs
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
on:
 | 
					 | 
				
			||||||
  pull_request:
 | 
					 | 
				
			||||||
    types: [labeled, unlabeled]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
jobs:
 | 
					 | 
				
			||||||
  check:
 | 
					 | 
				
			||||||
    name: Check
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - name: Check for needs-docs label
 | 
					 | 
				
			||||||
        uses: actions/github-script@v7.0.1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          script: |
 | 
					 | 
				
			||||||
            const { data: labels } = await github.rest.issues.listLabelsOnIssue({
 | 
					 | 
				
			||||||
              owner: context.repo.owner,
 | 
					 | 
				
			||||||
              repo: context.repo.repo,
 | 
					 | 
				
			||||||
              issue_number: context.issue.number
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            const needsDocs = labels.find(label => label.name === 'needs-docs');
 | 
					 | 
				
			||||||
            if (needsDocs) {
 | 
					 | 
				
			||||||
              core.setFailed('Pull request needs docs');
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
							
								
								
									
										34
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										34
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -20,7 +20,7 @@ jobs:
 | 
				
			|||||||
      branch_build: ${{ steps.tag.outputs.branch_build }}
 | 
					      branch_build: ${{ steps.tag.outputs.branch_build }}
 | 
				
			||||||
      deploy_env: ${{ steps.tag.outputs.deploy_env }}
 | 
					      deploy_env: ${{ steps.tag.outputs.deploy_env }}
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v5.0.0
 | 
					      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
      - name: Get tag
 | 
					      - name: Get tag
 | 
				
			||||||
        id: tag
 | 
					        id: tag
 | 
				
			||||||
        # yamllint disable rule:line-length
 | 
					        # yamllint disable rule:line-length
 | 
				
			||||||
@@ -60,9 +60,9 @@ jobs:
 | 
				
			|||||||
      contents: read
 | 
					      contents: read
 | 
				
			||||||
      id-token: write
 | 
					      id-token: write
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v5.0.0
 | 
					      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
      - name: Set up Python
 | 
					      - name: Set up Python
 | 
				
			||||||
        uses: actions/setup-python@v5.6.0
 | 
					        uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: "3.x"
 | 
					          python-version: "3.x"
 | 
				
			||||||
      - name: Build
 | 
					      - name: Build
 | 
				
			||||||
@@ -70,7 +70,7 @@ jobs:
 | 
				
			|||||||
          pip3 install build
 | 
					          pip3 install build
 | 
				
			||||||
          python3 -m build
 | 
					          python3 -m build
 | 
				
			||||||
      - name: Publish
 | 
					      - name: Publish
 | 
				
			||||||
        uses: pypa/gh-action-pypi-publish@v1.12.4
 | 
					        uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          skip-existing: true
 | 
					          skip-existing: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -92,22 +92,22 @@ jobs:
 | 
				
			|||||||
            os: "ubuntu-24.04-arm"
 | 
					            os: "ubuntu-24.04-arm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v5.0.0
 | 
					      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
      - name: Set up Python
 | 
					      - name: Set up Python
 | 
				
			||||||
        uses: actions/setup-python@v5.6.0
 | 
					        uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: "3.11"
 | 
					          python-version: "3.11"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Set up Docker Buildx
 | 
					      - name: Set up Docker Buildx
 | 
				
			||||||
        uses: docker/setup-buildx-action@v3.11.1
 | 
					        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Log in to docker hub
 | 
					      - name: Log in to docker hub
 | 
				
			||||||
        uses: docker/login-action@v3.5.0
 | 
					        uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          username: ${{ secrets.DOCKER_USER }}
 | 
					          username: ${{ secrets.DOCKER_USER }}
 | 
				
			||||||
          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
					          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
				
			||||||
      - name: Log in to the GitHub container registry
 | 
					      - name: Log in to the GitHub container registry
 | 
				
			||||||
        uses: docker/login-action@v3.5.0
 | 
					        uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          registry: ghcr.io
 | 
					          registry: ghcr.io
 | 
				
			||||||
          username: ${{ github.actor }}
 | 
					          username: ${{ github.actor }}
 | 
				
			||||||
@@ -138,7 +138,7 @@ jobs:
 | 
				
			|||||||
      #     version: ${{ needs.init.outputs.tag }}
 | 
					      #     version: ${{ needs.init.outputs.tag }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Upload digests
 | 
					      - name: Upload digests
 | 
				
			||||||
        uses: actions/upload-artifact@v4.6.2
 | 
					        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          name: digests-${{ matrix.platform.arch }}
 | 
					          name: digests-${{ matrix.platform.arch }}
 | 
				
			||||||
          path: /tmp/digests
 | 
					          path: /tmp/digests
 | 
				
			||||||
@@ -168,27 +168,27 @@ jobs:
 | 
				
			|||||||
          - ghcr
 | 
					          - ghcr
 | 
				
			||||||
          - dockerhub
 | 
					          - dockerhub
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v5.0.0
 | 
					      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Download digests
 | 
					      - name: Download digests
 | 
				
			||||||
        uses: actions/download-artifact@v5.0.0
 | 
					        uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          pattern: digests-*
 | 
					          pattern: digests-*
 | 
				
			||||||
          path: /tmp/digests
 | 
					          path: /tmp/digests
 | 
				
			||||||
          merge-multiple: true
 | 
					          merge-multiple: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Set up Docker Buildx
 | 
					      - name: Set up Docker Buildx
 | 
				
			||||||
        uses: docker/setup-buildx-action@v3.11.1
 | 
					        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Log in to docker hub
 | 
					      - name: Log in to docker hub
 | 
				
			||||||
        if: matrix.registry == 'dockerhub'
 | 
					        if: matrix.registry == 'dockerhub'
 | 
				
			||||||
        uses: docker/login-action@v3.5.0
 | 
					        uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          username: ${{ secrets.DOCKER_USER }}
 | 
					          username: ${{ secrets.DOCKER_USER }}
 | 
				
			||||||
          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
					          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
				
			||||||
      - name: Log in to the GitHub container registry
 | 
					      - name: Log in to the GitHub container registry
 | 
				
			||||||
        if: matrix.registry == 'ghcr'
 | 
					        if: matrix.registry == 'ghcr'
 | 
				
			||||||
        uses: docker/login-action@v3.5.0
 | 
					        uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          registry: ghcr.io
 | 
					          registry: ghcr.io
 | 
				
			||||||
          username: ${{ github.actor }}
 | 
					          username: ${{ github.actor }}
 | 
				
			||||||
@@ -220,7 +220,7 @@ jobs:
 | 
				
			|||||||
      - deploy-manifest
 | 
					      - deploy-manifest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Trigger Workflow
 | 
					      - name: Trigger Workflow
 | 
				
			||||||
        uses: actions/github-script@v7.0.1
 | 
					        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
 | 
					          github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
 | 
				
			||||||
          script: |
 | 
					          script: |
 | 
				
			||||||
@@ -246,7 +246,7 @@ jobs:
 | 
				
			|||||||
    environment: ${{ needs.init.outputs.deploy_env }}
 | 
					    environment: ${{ needs.init.outputs.deploy_env }}
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Trigger Workflow
 | 
					      - name: Trigger Workflow
 | 
				
			||||||
        uses: actions/github-script@v7.0.1
 | 
					        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
 | 
					          github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
 | 
				
			||||||
          script: |
 | 
					          script: |
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										52
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										52
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							@@ -15,36 +15,52 @@ concurrency:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  stale:
 | 
					  stale:
 | 
				
			||||||
 | 
					    if: github.repository_owner == 'esphome'
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/stale@v9.1.0
 | 
					      - name: Stale
 | 
				
			||||||
 | 
					        uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
 | 
					          debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch
 | 
				
			||||||
 | 
					          remove-stale-when-updated: true
 | 
				
			||||||
 | 
					          operations-per-run: 150
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          # The 90 day stale policy for PRs
 | 
				
			||||||
 | 
					          # - PRs
 | 
				
			||||||
 | 
					          # - No PRs marked as "not-stale"
 | 
				
			||||||
 | 
					          # - No Issues (see below)
 | 
				
			||||||
          days-before-pr-stale: 90
 | 
					          days-before-pr-stale: 90
 | 
				
			||||||
          days-before-pr-close: 7
 | 
					          days-before-pr-close: 7
 | 
				
			||||||
          days-before-issue-stale: -1
 | 
					 | 
				
			||||||
          days-before-issue-close: -1
 | 
					 | 
				
			||||||
          remove-stale-when-updated: true
 | 
					 | 
				
			||||||
          stale-pr-label: "stale"
 | 
					          stale-pr-label: "stale"
 | 
				
			||||||
          exempt-pr-labels: "not-stale"
 | 
					          exempt-pr-labels: "not-stale"
 | 
				
			||||||
          stale-pr-message: >
 | 
					          stale-pr-message: >
 | 
				
			||||||
            There hasn't been any activity on this pull request recently. This
 | 
					            There hasn't been any activity on this pull request recently. This
 | 
				
			||||||
            pull request has been automatically marked as stale because of that
 | 
					            pull request has been automatically marked as stale because of that
 | 
				
			||||||
            and will be closed if no further activity occurs within 7 days.
 | 
					            and will be closed if no further activity occurs within 7 days.
 | 
				
			||||||
            Thank you for your contributions.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Use stale to automatically close issues with a
 | 
					            If you are the author of this PR, please leave a comment if you want
 | 
				
			||||||
  # reference to the issue tracker
 | 
					            to keep it open. Also, please rebase your PR onto the latest dev
 | 
				
			||||||
  close-issues:
 | 
					            branch to ensure that it's up to date with the latest changes.
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					
 | 
				
			||||||
    steps:
 | 
					            Thank you for your contribution!
 | 
				
			||||||
      - uses: actions/stale@v9.1.0
 | 
					
 | 
				
			||||||
        with:
 | 
					          # The 90 day stale policy for Issues
 | 
				
			||||||
          days-before-pr-stale: -1
 | 
					          # - Issues
 | 
				
			||||||
          days-before-pr-close: -1
 | 
					          # - No Issues marked as "not-stale"
 | 
				
			||||||
          days-before-issue-stale: 1
 | 
					          # - No PRs (see above)
 | 
				
			||||||
          days-before-issue-close: 1
 | 
					          days-before-issue-stale: 90
 | 
				
			||||||
          remove-stale-when-updated: true
 | 
					          days-before-issue-close: 7
 | 
				
			||||||
          stale-issue-label: "stale"
 | 
					          stale-issue-label: "stale"
 | 
				
			||||||
          exempt-issue-labels: "not-stale"
 | 
					          exempt-issue-labels: "not-stale"
 | 
				
			||||||
          stale-issue-message: >
 | 
					          stale-issue-message: >
 | 
				
			||||||
            https://github.com/esphome/esphome/issues/430
 | 
					            There hasn't been any activity on this issue recently. Due to the
 | 
				
			||||||
 | 
					            high number of incoming GitHub notifications, we have to clean some
 | 
				
			||||||
 | 
					            of the old issues, as many of them have already been resolved with
 | 
				
			||||||
 | 
					            the latest updates.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Please make sure to update to the latest ESPHome version and
 | 
				
			||||||
 | 
					            check if that solves the issue. Let us know if that works for you by
 | 
				
			||||||
 | 
					            adding a comment 👍
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            This issue has now been marked as stale and will be closed if no
 | 
				
			||||||
 | 
					            further activity occurs. Thank you for your contributions.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										30
									
								
								.github/workflows/status-check-labels.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/status-check-labels.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					name: Status check labels
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  pull_request:
 | 
				
			||||||
 | 
					    types: [labeled, unlabeled]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  check:
 | 
				
			||||||
 | 
					    name: Check ${{ matrix.label }}
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    strategy:
 | 
				
			||||||
 | 
					      fail-fast: false
 | 
				
			||||||
 | 
					      matrix:
 | 
				
			||||||
 | 
					        label:
 | 
				
			||||||
 | 
					          - needs-docs
 | 
				
			||||||
 | 
					          - merge-after-release
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Check for ${{ matrix.label }} label
 | 
				
			||||||
 | 
					        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          script: |
 | 
				
			||||||
 | 
					            const { data: labels } = await github.rest.issues.listLabelsOnIssue({
 | 
				
			||||||
 | 
					              owner: context.repo.owner,
 | 
				
			||||||
 | 
					              repo: context.repo.repo,
 | 
				
			||||||
 | 
					              issue_number: context.issue.number
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            const hasLabel = labels.find(label => label.name === '${{ matrix.label }}');
 | 
				
			||||||
 | 
					            if (hasLabel) {
 | 
				
			||||||
 | 
					              core.setFailed('Pull request cannot be merged, it is labeled as ${{ matrix.label }}');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
							
								
								
									
										13
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							@@ -13,16 +13,16 @@ jobs:
 | 
				
			|||||||
    if: github.repository == 'esphome/esphome'
 | 
					    if: github.repository == 'esphome/esphome'
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout
 | 
					      - name: Checkout
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Checkout Home Assistant
 | 
					      - name: Checkout Home Assistant
 | 
				
			||||||
        uses: actions/checkout@v5.0.0
 | 
					        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          repository: home-assistant/core
 | 
					          repository: home-assistant/core
 | 
				
			||||||
          path: lib/home-assistant
 | 
					          path: lib/home-assistant
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Setup Python
 | 
					      - name: Setup Python
 | 
				
			||||||
        uses: actions/setup-python@v5.6.0
 | 
					        uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: 3.13
 | 
					          python-version: 3.13
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -30,13 +30,18 @@ jobs:
 | 
				
			|||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          python -m pip install --upgrade pip
 | 
					          python -m pip install --upgrade pip
 | 
				
			||||||
          pip install -e lib/home-assistant
 | 
					          pip install -e lib/home-assistant
 | 
				
			||||||
 | 
					          pip install -r requirements_test.txt pre-commit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Sync
 | 
					      - name: Sync
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          python ./script/sync-device_class.py
 | 
					          python ./script/sync-device_class.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Run pre-commit hooks
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          python script/run-in-env.py pre-commit run --all-files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Commit changes
 | 
					      - name: Commit changes
 | 
				
			||||||
        uses: peter-evans/create-pull-request@v7.0.8
 | 
					        uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          commit-message: "Synchronise Device Classes from Home Assistant"
 | 
					          commit-message: "Synchronise Device Classes from Home Assistant"
 | 
				
			||||||
          committer: esphomebot <esphome@openhomefoundation.org>
 | 
					          committer: esphomebot <esphome@openhomefoundation.org>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ ci:
 | 
				
			|||||||
repos:
 | 
					repos:
 | 
				
			||||||
  - repo: https://github.com/astral-sh/ruff-pre-commit
 | 
					  - repo: https://github.com/astral-sh/ruff-pre-commit
 | 
				
			||||||
    # Ruff version.
 | 
					    # Ruff version.
 | 
				
			||||||
    rev: v0.12.8
 | 
					    rev: v0.14.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      # Run the linter.
 | 
					      # Run the linter.
 | 
				
			||||||
      - id: ruff
 | 
					      - id: ruff
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										41
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								CODEOWNERS
									
									
									
									
									
								
							@@ -66,7 +66,7 @@ esphome/components/binary_sensor/* @esphome/core
 | 
				
			|||||||
esphome/components/bk72xx/* @kuba2k2
 | 
					esphome/components/bk72xx/* @kuba2k2
 | 
				
			||||||
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
 | 
					esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
 | 
				
			||||||
esphome/components/bl0939/* @ziceva
 | 
					esphome/components/bl0939/* @ziceva
 | 
				
			||||||
esphome/components/bl0940/* @tobias-
 | 
					esphome/components/bl0940/* @dan-s-github @tobias-
 | 
				
			||||||
esphome/components/bl0942/* @dbuezas @dwmw2
 | 
					esphome/components/bl0942/* @dbuezas @dwmw2
 | 
				
			||||||
esphome/components/ble_client/* @buxtronix @clydebarrow
 | 
					esphome/components/ble_client/* @buxtronix @clydebarrow
 | 
				
			||||||
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
 | 
					esphome/components/bluetooth_proxy/* @bdraco @jesserockz
 | 
				
			||||||
@@ -88,7 +88,8 @@ esphome/components/bp1658cj/* @Cossid
 | 
				
			|||||||
esphome/components/bp5758d/* @Cossid
 | 
					esphome/components/bp5758d/* @Cossid
 | 
				
			||||||
esphome/components/button/* @esphome/core
 | 
					esphome/components/button/* @esphome/core
 | 
				
			||||||
esphome/components/bytebuffer/* @clydebarrow
 | 
					esphome/components/bytebuffer/* @clydebarrow
 | 
				
			||||||
esphome/components/camera/* @DT-art1 @bdraco
 | 
					esphome/components/camera/* @bdraco @DT-art1
 | 
				
			||||||
 | 
					esphome/components/camera_encoder/* @DT-art1
 | 
				
			||||||
esphome/components/canbus/* @danielschramm @mvturnho
 | 
					esphome/components/canbus/* @danielschramm @mvturnho
 | 
				
			||||||
esphome/components/cap1188/* @mreditor97
 | 
					esphome/components/cap1188/* @mreditor97
 | 
				
			||||||
esphome/components/captive_portal/* @esphome/core
 | 
					esphome/components/captive_portal/* @esphome/core
 | 
				
			||||||
@@ -138,15 +139,16 @@ esphome/components/ens160_base/* @latonita @vincentscode
 | 
				
			|||||||
esphome/components/ens160_i2c/* @latonita
 | 
					esphome/components/ens160_i2c/* @latonita
 | 
				
			||||||
esphome/components/ens160_spi/* @latonita
 | 
					esphome/components/ens160_spi/* @latonita
 | 
				
			||||||
esphome/components/ens210/* @itn3rd77
 | 
					esphome/components/ens210/* @itn3rd77
 | 
				
			||||||
 | 
					esphome/components/epaper_spi/* @esphome/core
 | 
				
			||||||
esphome/components/es7210/* @kahrendt
 | 
					esphome/components/es7210/* @kahrendt
 | 
				
			||||||
esphome/components/es7243e/* @kbx81
 | 
					esphome/components/es7243e/* @kbx81
 | 
				
			||||||
esphome/components/es8156/* @kbx81
 | 
					esphome/components/es8156/* @kbx81
 | 
				
			||||||
esphome/components/es8311/* @kahrendt @kroimon
 | 
					esphome/components/es8311/* @kahrendt @kroimon
 | 
				
			||||||
esphome/components/es8388/* @P4uLT
 | 
					esphome/components/es8388/* @P4uLT
 | 
				
			||||||
esphome/components/esp32/* @esphome/core
 | 
					esphome/components/esp32/* @esphome/core
 | 
				
			||||||
esphome/components/esp32_ble/* @Rapsssito @bdraco @jesserockz
 | 
					esphome/components/esp32_ble/* @bdraco @jesserockz @Rapsssito
 | 
				
			||||||
esphome/components/esp32_ble_client/* @bdraco @jesserockz
 | 
					esphome/components/esp32_ble_client/* @bdraco @jesserockz
 | 
				
			||||||
esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz
 | 
					esphome/components/esp32_ble_server/* @clydebarrow @jesserockz @Rapsssito
 | 
				
			||||||
esphome/components/esp32_ble_tracker/* @bdraco
 | 
					esphome/components/esp32_ble_tracker/* @bdraco
 | 
				
			||||||
esphome/components/esp32_camera_web_server/* @ayufan
 | 
					esphome/components/esp32_camera_web_server/* @ayufan
 | 
				
			||||||
esphome/components/esp32_can/* @Sympatron
 | 
					esphome/components/esp32_can/* @Sympatron
 | 
				
			||||||
@@ -159,14 +161,13 @@ esphome/components/esp_ldo/* @clydebarrow
 | 
				
			|||||||
esphome/components/espnow/* @jesserockz
 | 
					esphome/components/espnow/* @jesserockz
 | 
				
			||||||
esphome/components/ethernet_info/* @gtjadsonsantos
 | 
					esphome/components/ethernet_info/* @gtjadsonsantos
 | 
				
			||||||
esphome/components/event/* @nohat
 | 
					esphome/components/event/* @nohat
 | 
				
			||||||
esphome/components/event_emitter/* @Rapsssito
 | 
					 | 
				
			||||||
esphome/components/exposure_notifications/* @OttoWinter
 | 
					esphome/components/exposure_notifications/* @OttoWinter
 | 
				
			||||||
esphome/components/ezo/* @ssieb
 | 
					esphome/components/ezo/* @ssieb
 | 
				
			||||||
esphome/components/ezo_pmp/* @carlos-sarmiento
 | 
					esphome/components/ezo_pmp/* @carlos-sarmiento
 | 
				
			||||||
esphome/components/factory_reset/* @anatoly-savchenkov
 | 
					esphome/components/factory_reset/* @anatoly-savchenkov
 | 
				
			||||||
esphome/components/fastled_base/* @OttoWinter
 | 
					esphome/components/fastled_base/* @OttoWinter
 | 
				
			||||||
esphome/components/feedback/* @ianchi
 | 
					esphome/components/feedback/* @ianchi
 | 
				
			||||||
esphome/components/fingerprint_grow/* @OnFreund @alexborro @loongyh
 | 
					esphome/components/fingerprint_grow/* @alexborro @loongyh @OnFreund
 | 
				
			||||||
esphome/components/font/* @clydebarrow @esphome/core
 | 
					esphome/components/font/* @clydebarrow @esphome/core
 | 
				
			||||||
esphome/components/fs3000/* @kahrendt
 | 
					esphome/components/fs3000/* @kahrendt
 | 
				
			||||||
esphome/components/ft5x06/* @clydebarrow
 | 
					esphome/components/ft5x06/* @clydebarrow
 | 
				
			||||||
@@ -202,7 +203,7 @@ esphome/components/heatpumpir/* @rob-deutsch
 | 
				
			|||||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
 | 
					esphome/components/hitachi_ac424/* @sourabhjaiswal
 | 
				
			||||||
esphome/components/hm3301/* @freekode
 | 
					esphome/components/hm3301/* @freekode
 | 
				
			||||||
esphome/components/hmac_md5/* @dwmw2
 | 
					esphome/components/hmac_md5/* @dwmw2
 | 
				
			||||||
esphome/components/homeassistant/* @OttoWinter @esphome/core
 | 
					esphome/components/homeassistant/* @esphome/core @OttoWinter
 | 
				
			||||||
esphome/components/homeassistant/number/* @landonr
 | 
					esphome/components/homeassistant/number/* @landonr
 | 
				
			||||||
esphome/components/homeassistant/switch/* @Links2004
 | 
					esphome/components/homeassistant/switch/* @Links2004
 | 
				
			||||||
esphome/components/honeywell_hih_i2c/* @Benichou34
 | 
					esphome/components/honeywell_hih_i2c/* @Benichou34
 | 
				
			||||||
@@ -227,13 +228,13 @@ esphome/components/iaqcore/* @yozik04
 | 
				
			|||||||
esphome/components/ili9xxx/* @clydebarrow @nielsnl68
 | 
					esphome/components/ili9xxx/* @clydebarrow @nielsnl68
 | 
				
			||||||
esphome/components/improv_base/* @esphome/core
 | 
					esphome/components/improv_base/* @esphome/core
 | 
				
			||||||
esphome/components/improv_serial/* @esphome/core
 | 
					esphome/components/improv_serial/* @esphome/core
 | 
				
			||||||
esphome/components/ina226/* @Sergio303 @latonita
 | 
					esphome/components/ina226/* @latonita @Sergio303
 | 
				
			||||||
esphome/components/ina260/* @mreditor97
 | 
					esphome/components/ina260/* @mreditor97
 | 
				
			||||||
esphome/components/ina2xx_base/* @latonita
 | 
					esphome/components/ina2xx_base/* @latonita
 | 
				
			||||||
esphome/components/ina2xx_i2c/* @latonita
 | 
					esphome/components/ina2xx_i2c/* @latonita
 | 
				
			||||||
esphome/components/ina2xx_spi/* @latonita
 | 
					esphome/components/ina2xx_spi/* @latonita
 | 
				
			||||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
 | 
					esphome/components/inkbird_ibsth1_mini/* @fkirill
 | 
				
			||||||
esphome/components/inkplate6/* @jesserockz
 | 
					esphome/components/inkplate/* @jesserockz @JosipKuci
 | 
				
			||||||
esphome/components/integration/* @OttoWinter
 | 
					esphome/components/integration/* @OttoWinter
 | 
				
			||||||
esphome/components/internal_temperature/* @Mat931
 | 
					esphome/components/internal_temperature/* @Mat931
 | 
				
			||||||
esphome/components/interval/* @esphome/core
 | 
					esphome/components/interval/* @esphome/core
 | 
				
			||||||
@@ -256,6 +257,7 @@ esphome/components/libretiny_pwm/* @kuba2k2
 | 
				
			|||||||
esphome/components/light/* @esphome/core
 | 
					esphome/components/light/* @esphome/core
 | 
				
			||||||
esphome/components/lightwaverf/* @max246
 | 
					esphome/components/lightwaverf/* @max246
 | 
				
			||||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
 | 
					esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/lm75b/* @beormund
 | 
				
			||||||
esphome/components/ln882x/* @lamauny
 | 
					esphome/components/ln882x/* @lamauny
 | 
				
			||||||
esphome/components/lock/* @esphome/core
 | 
					esphome/components/lock/* @esphome/core
 | 
				
			||||||
esphome/components/logger/* @esphome/core
 | 
					esphome/components/logger/* @esphome/core
 | 
				
			||||||
@@ -276,8 +278,8 @@ esphome/components/max7219digit/* @rspaargaren
 | 
				
			|||||||
esphome/components/max9611/* @mckaymatthew
 | 
					esphome/components/max9611/* @mckaymatthew
 | 
				
			||||||
esphome/components/mcp23008/* @jesserockz
 | 
					esphome/components/mcp23008/* @jesserockz
 | 
				
			||||||
esphome/components/mcp23017/* @jesserockz
 | 
					esphome/components/mcp23017/* @jesserockz
 | 
				
			||||||
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
 | 
					esphome/components/mcp23s08/* @jesserockz @SenexCrenshaw
 | 
				
			||||||
esphome/components/mcp23s17/* @SenexCrenshaw @jesserockz
 | 
					esphome/components/mcp23s17/* @jesserockz @SenexCrenshaw
 | 
				
			||||||
esphome/components/mcp23x08_base/* @jesserockz
 | 
					esphome/components/mcp23x08_base/* @jesserockz
 | 
				
			||||||
esphome/components/mcp23x17_base/* @jesserockz
 | 
					esphome/components/mcp23x17_base/* @jesserockz
 | 
				
			||||||
esphome/components/mcp23xxx_base/* @jesserockz
 | 
					esphome/components/mcp23xxx_base/* @jesserockz
 | 
				
			||||||
@@ -298,6 +300,7 @@ esphome/components/mics_4514/* @jesserockz
 | 
				
			|||||||
esphome/components/midea/* @dudanov
 | 
					esphome/components/midea/* @dudanov
 | 
				
			||||||
esphome/components/midea_ir/* @dudanov
 | 
					esphome/components/midea_ir/* @dudanov
 | 
				
			||||||
esphome/components/mipi_dsi/* @clydebarrow
 | 
					esphome/components/mipi_dsi/* @clydebarrow
 | 
				
			||||||
 | 
					esphome/components/mipi_rgb/* @clydebarrow
 | 
				
			||||||
esphome/components/mipi_spi/* @clydebarrow
 | 
					esphome/components/mipi_spi/* @clydebarrow
 | 
				
			||||||
esphome/components/mitsubishi/* @RubyBailey
 | 
					esphome/components/mitsubishi/* @RubyBailey
 | 
				
			||||||
esphome/components/mixer/speaker/* @kahrendt
 | 
					esphome/components/mixer/speaker/* @kahrendt
 | 
				
			||||||
@@ -341,7 +344,7 @@ esphome/components/ota/* @esphome/core
 | 
				
			|||||||
esphome/components/output/* @esphome/core
 | 
					esphome/components/output/* @esphome/core
 | 
				
			||||||
esphome/components/packet_transport/* @clydebarrow
 | 
					esphome/components/packet_transport/* @clydebarrow
 | 
				
			||||||
esphome/components/pca6416a/* @Mat931
 | 
					esphome/components/pca6416a/* @Mat931
 | 
				
			||||||
esphome/components/pca9554/* @clydebarrow @hwstar
 | 
					esphome/components/pca9554/* @bdraco @clydebarrow @hwstar
 | 
				
			||||||
esphome/components/pcf85063/* @brogon
 | 
					esphome/components/pcf85063/* @brogon
 | 
				
			||||||
esphome/components/pcf8563/* @KoenBreeman
 | 
					esphome/components/pcf8563/* @KoenBreeman
 | 
				
			||||||
esphome/components/pi4ioe5v6408/* @jesserockz
 | 
					esphome/components/pi4ioe5v6408/* @jesserockz
 | 
				
			||||||
@@ -352,9 +355,9 @@ esphome/components/pm2005/* @andrewjswan
 | 
				
			|||||||
esphome/components/pmsa003i/* @sjtrny
 | 
					esphome/components/pmsa003i/* @sjtrny
 | 
				
			||||||
esphome/components/pmsx003/* @ximex
 | 
					esphome/components/pmsx003/* @ximex
 | 
				
			||||||
esphome/components/pmwcs3/* @SeByDocKy
 | 
					esphome/components/pmwcs3/* @SeByDocKy
 | 
				
			||||||
esphome/components/pn532/* @OttoWinter @jesserockz
 | 
					esphome/components/pn532/* @jesserockz @OttoWinter
 | 
				
			||||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
 | 
					esphome/components/pn532_i2c/* @jesserockz @OttoWinter
 | 
				
			||||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
 | 
					esphome/components/pn532_spi/* @jesserockz @OttoWinter
 | 
				
			||||||
esphome/components/pn7150/* @jesserockz @kbx81
 | 
					esphome/components/pn7150/* @jesserockz @kbx81
 | 
				
			||||||
esphome/components/pn7150_i2c/* @jesserockz @kbx81
 | 
					esphome/components/pn7150_i2c/* @jesserockz @kbx81
 | 
				
			||||||
esphome/components/pn7160/* @jesserockz @kbx81
 | 
					esphome/components/pn7160/* @jesserockz @kbx81
 | 
				
			||||||
@@ -363,7 +366,7 @@ esphome/components/pn7160_spi/* @jesserockz @kbx81
 | 
				
			|||||||
esphome/components/power_supply/* @esphome/core
 | 
					esphome/components/power_supply/* @esphome/core
 | 
				
			||||||
esphome/components/preferences/* @esphome/core
 | 
					esphome/components/preferences/* @esphome/core
 | 
				
			||||||
esphome/components/psram/* @esphome/core
 | 
					esphome/components/psram/* @esphome/core
 | 
				
			||||||
esphome/components/pulse_meter/* @TrentHouliston @cstaahl @stevebaxter
 | 
					esphome/components/pulse_meter/* @cstaahl @stevebaxter @TrentHouliston
 | 
				
			||||||
esphome/components/pvvx_mithermometer/* @pasiz
 | 
					esphome/components/pvvx_mithermometer/* @pasiz
 | 
				
			||||||
esphome/components/pylontech/* @functionpointer
 | 
					esphome/components/pylontech/* @functionpointer
 | 
				
			||||||
esphome/components/qmp6988/* @andrewpc
 | 
					esphome/components/qmp6988/* @andrewpc
 | 
				
			||||||
@@ -404,7 +407,8 @@ esphome/components/sensirion_common/* @martgras
 | 
				
			|||||||
esphome/components/sensor/* @esphome/core
 | 
					esphome/components/sensor/* @esphome/core
 | 
				
			||||||
esphome/components/sfa30/* @ghsensdev
 | 
					esphome/components/sfa30/* @ghsensdev
 | 
				
			||||||
esphome/components/sgp40/* @SenexCrenshaw
 | 
					esphome/components/sgp40/* @SenexCrenshaw
 | 
				
			||||||
esphome/components/sgp4x/* @SenexCrenshaw @martgras
 | 
					esphome/components/sgp4x/* @martgras @SenexCrenshaw
 | 
				
			||||||
 | 
					esphome/components/sha256/* @esphome/core
 | 
				
			||||||
esphome/components/shelly_dimmer/* @edge90 @rnauber
 | 
					esphome/components/shelly_dimmer/* @edge90 @rnauber
 | 
				
			||||||
esphome/components/sht3xd/* @mrtoy-me
 | 
					esphome/components/sht3xd/* @mrtoy-me
 | 
				
			||||||
esphome/components/sht4x/* @sjtrny
 | 
					esphome/components/sht4x/* @sjtrny
 | 
				
			||||||
@@ -426,6 +430,7 @@ esphome/components/speaker/media_player/* @kahrendt @synesthesiam
 | 
				
			|||||||
esphome/components/spi/* @clydebarrow @esphome/core
 | 
					esphome/components/spi/* @clydebarrow @esphome/core
 | 
				
			||||||
esphome/components/spi_device/* @clydebarrow
 | 
					esphome/components/spi_device/* @clydebarrow
 | 
				
			||||||
esphome/components/spi_led_strip/* @clydebarrow
 | 
					esphome/components/spi_led_strip/* @clydebarrow
 | 
				
			||||||
 | 
					esphome/components/split_buffer/* @jesserockz
 | 
				
			||||||
esphome/components/sprinkler/* @kbx81
 | 
					esphome/components/sprinkler/* @kbx81
 | 
				
			||||||
esphome/components/sps30/* @martgras
 | 
					esphome/components/sps30/* @martgras
 | 
				
			||||||
esphome/components/ssd1322_base/* @kbx81
 | 
					esphome/components/ssd1322_base/* @kbx81
 | 
				
			||||||
@@ -531,6 +536,7 @@ esphome/components/wk2204_spi/* @DrCoolZic
 | 
				
			|||||||
esphome/components/wk2212_i2c/* @DrCoolZic
 | 
					esphome/components/wk2212_i2c/* @DrCoolZic
 | 
				
			||||||
esphome/components/wk2212_spi/* @DrCoolZic
 | 
					esphome/components/wk2212_spi/* @DrCoolZic
 | 
				
			||||||
esphome/components/wl_134/* @hobbypunk90
 | 
					esphome/components/wl_134/* @hobbypunk90
 | 
				
			||||||
 | 
					esphome/components/wts01/* @alepee
 | 
				
			||||||
esphome/components/x9c/* @EtienneMD
 | 
					esphome/components/x9c/* @EtienneMD
 | 
				
			||||||
esphome/components/xgzp68xx/* @gcormier
 | 
					esphome/components/xgzp68xx/* @gcormier
 | 
				
			||||||
esphome/components/xiaomi_hhccjcy10/* @fariouche
 | 
					esphome/components/xiaomi_hhccjcy10/* @fariouche
 | 
				
			||||||
@@ -546,3 +552,4 @@ esphome/components/xxtea/* @clydebarrow
 | 
				
			|||||||
esphome/components/zephyr/* @tomaszduda23
 | 
					esphome/components/zephyr/* @tomaszduda23
 | 
				
			||||||
esphome/components/zhlt01/* @cfeenstra1024
 | 
					esphome/components/zhlt01/* @cfeenstra1024
 | 
				
			||||||
esphome/components/zio_ultrasonic/* @kahrendt
 | 
					esphome/components/zio_ultrasonic/* @kahrendt
 | 
				
			||||||
 | 
					esphome/components/zwave_proxy/* @kbx81
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								Doxyfile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Doxyfile
									
									
									
									
									
								
							@@ -48,7 +48,7 @@ PROJECT_NAME           = ESPHome
 | 
				
			|||||||
# could be handy for archiving the generated documentation or if some version
 | 
					# could be handy for archiving the generated documentation or if some version
 | 
				
			||||||
# control system is used.
 | 
					# control system is used.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PROJECT_NUMBER         = 2025.8.0-dev
 | 
					PROJECT_NUMBER         = 2025.10.0b2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
 | 
					# Using the PROJECT_BRIEF tag one can provide an optional one line description
 | 
				
			||||||
# for a project that appears at the top of each page and should give viewer a
 | 
					# for a project that appears at the top of each page and should give viewer a
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ import getpass
 | 
				
			|||||||
import importlib
 | 
					import importlib
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
@@ -13,11 +14,15 @@ from typing import Protocol
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import argcomplete
 | 
					import argcomplete
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Note: Do not import modules from esphome.components here, as this would
 | 
				
			||||||
 | 
					# cause them to be loaded before external components are processed, resulting
 | 
				
			||||||
 | 
					# in the built-in version being used instead of the external component one.
 | 
				
			||||||
from esphome import const, writer, yaml_util
 | 
					from esphome import const, writer, yaml_util
 | 
				
			||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.config import iter_component_configs, read_config, strip_default_ids
 | 
					from esphome.config import iter_component_configs, read_config, strip_default_ids
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    ALLOWED_NAME_CHARS,
 | 
					    ALLOWED_NAME_CHARS,
 | 
				
			||||||
 | 
					    CONF_API,
 | 
				
			||||||
    CONF_BAUD_RATE,
 | 
					    CONF_BAUD_RATE,
 | 
				
			||||||
    CONF_BROKER,
 | 
					    CONF_BROKER,
 | 
				
			||||||
    CONF_DEASSERT_RTS_DTR,
 | 
					    CONF_DEASSERT_RTS_DTR,
 | 
				
			||||||
@@ -43,6 +48,7 @@ from esphome.const import (
 | 
				
			|||||||
    SECRETS_FILES,
 | 
					    SECRETS_FILES,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.core import CORE, EsphomeError, coroutine
 | 
					from esphome.core import CORE, EsphomeError, coroutine
 | 
				
			||||||
 | 
					from esphome.enum import StrEnum
 | 
				
			||||||
from esphome.helpers import get_bool_env, indent, is_ip_address
 | 
					from esphome.helpers import get_bool_env, indent, is_ip_address
 | 
				
			||||||
from esphome.log import AnsiFore, color, setup_log
 | 
					from esphome.log import AnsiFore, color, setup_log
 | 
				
			||||||
from esphome.types import ConfigType
 | 
					from esphome.types import ConfigType
 | 
				
			||||||
@@ -106,13 +112,23 @@ def choose_prompt(options, purpose: str = None):
 | 
				
			|||||||
    return options[opt - 1][1]
 | 
					    return options[opt - 1][1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Purpose(StrEnum):
 | 
				
			||||||
 | 
					    UPLOADING = "uploading"
 | 
				
			||||||
 | 
					    LOGGING = "logging"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _resolve_with_cache(address: str, purpose: Purpose) -> list[str]:
 | 
				
			||||||
 | 
					    """Resolve an address using cache if available, otherwise return the address itself."""
 | 
				
			||||||
 | 
					    if CORE.address_cache and (cached := CORE.address_cache.get_addresses(address)):
 | 
				
			||||||
 | 
					        _LOGGER.debug("Using cached addresses for %s: %s", purpose.value, cached)
 | 
				
			||||||
 | 
					        return cached
 | 
				
			||||||
 | 
					    return [address]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def choose_upload_log_host(
 | 
					def choose_upload_log_host(
 | 
				
			||||||
    default: list[str] | str | None,
 | 
					    default: list[str] | str | None,
 | 
				
			||||||
    check_default: str | None,
 | 
					    check_default: str | None,
 | 
				
			||||||
    show_ota: bool,
 | 
					    purpose: Purpose,
 | 
				
			||||||
    show_mqtt: bool,
 | 
					 | 
				
			||||||
    show_api: bool,
 | 
					 | 
				
			||||||
    purpose: str | None = None,
 | 
					 | 
				
			||||||
) -> list[str]:
 | 
					) -> list[str]:
 | 
				
			||||||
    # Convert to list for uniform handling
 | 
					    # Convert to list for uniform handling
 | 
				
			||||||
    defaults = [default] if isinstance(default, str) else default or []
 | 
					    defaults = [default] if isinstance(default, str) else default or []
 | 
				
			||||||
@@ -132,53 +148,146 @@ def choose_upload_log_host(
 | 
				
			|||||||
                ]
 | 
					                ]
 | 
				
			||||||
                resolved.append(choose_prompt(options, purpose=purpose))
 | 
					                resolved.append(choose_prompt(options, purpose=purpose))
 | 
				
			||||||
            elif device == "OTA":
 | 
					            elif device == "OTA":
 | 
				
			||||||
                if (show_ota and "ota" in CORE.config) or (
 | 
					                # ensure IP adresses are used first
 | 
				
			||||||
                    show_api and "api" in CORE.config
 | 
					                if is_ip_address(CORE.address) and (
 | 
				
			||||||
 | 
					                    (purpose == Purpose.LOGGING and has_api())
 | 
				
			||||||
 | 
					                    or (purpose == Purpose.UPLOADING and has_ota())
 | 
				
			||||||
                ):
 | 
					                ):
 | 
				
			||||||
                    resolved.append(CORE.address)
 | 
					                    resolved.extend(_resolve_with_cache(CORE.address, purpose))
 | 
				
			||||||
                elif show_mqtt and has_mqtt_logging():
 | 
					
 | 
				
			||||||
                    resolved.append("MQTT")
 | 
					                if purpose == Purpose.LOGGING:
 | 
				
			||||||
 | 
					                    if has_api() and has_mqtt_ip_lookup():
 | 
				
			||||||
 | 
					                        resolved.append("MQTTIP")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if has_mqtt_logging():
 | 
				
			||||||
 | 
					                        resolved.append("MQTT")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if has_api() and has_non_ip_address():
 | 
				
			||||||
 | 
					                        resolved.extend(_resolve_with_cache(CORE.address, purpose))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                elif purpose == Purpose.UPLOADING:
 | 
				
			||||||
 | 
					                    if has_ota() and has_mqtt_ip_lookup():
 | 
				
			||||||
 | 
					                        resolved.append("MQTTIP")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if has_ota() and has_non_ip_address():
 | 
				
			||||||
 | 
					                        resolved.extend(_resolve_with_cache(CORE.address, purpose))
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                resolved.append(device)
 | 
					                resolved.append(device)
 | 
				
			||||||
 | 
					        if not resolved:
 | 
				
			||||||
 | 
					            _LOGGER.error("All specified devices: %s could not be resolved.", defaults)
 | 
				
			||||||
        return resolved
 | 
					        return resolved
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # No devices specified, show interactive chooser
 | 
					    # No devices specified, show interactive chooser
 | 
				
			||||||
    options = [
 | 
					    options = [
 | 
				
			||||||
        (f"{port.path} ({port.description})", port.path) for port in get_serial_ports()
 | 
					        (f"{port.path} ({port.description})", port.path) for port in get_serial_ports()
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
 | 
					
 | 
				
			||||||
        options.append((f"Over The Air ({CORE.address})", CORE.address))
 | 
					    if purpose == Purpose.LOGGING:
 | 
				
			||||||
    if show_mqtt and has_mqtt_logging():
 | 
					        if has_mqtt_logging():
 | 
				
			||||||
        mqtt_config = CORE.config[CONF_MQTT]
 | 
					            mqtt_config = CORE.config[CONF_MQTT]
 | 
				
			||||||
        options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
 | 
					            options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if has_api():
 | 
				
			||||||
 | 
					            if has_resolvable_address():
 | 
				
			||||||
 | 
					                options.append((f"Over The Air ({CORE.address})", CORE.address))
 | 
				
			||||||
 | 
					            if has_mqtt_ip_lookup():
 | 
				
			||||||
 | 
					                options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    elif purpose == Purpose.UPLOADING and has_ota():
 | 
				
			||||||
 | 
					        if has_resolvable_address():
 | 
				
			||||||
 | 
					            options.append((f"Over The Air ({CORE.address})", CORE.address))
 | 
				
			||||||
 | 
					        if has_mqtt_ip_lookup():
 | 
				
			||||||
 | 
					            options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if check_default is not None and check_default in [opt[1] for opt in options]:
 | 
					    if check_default is not None and check_default in [opt[1] for opt in options]:
 | 
				
			||||||
        return [check_default]
 | 
					        return [check_default]
 | 
				
			||||||
    return [choose_prompt(options, purpose=purpose)]
 | 
					    return [choose_prompt(options, purpose=purpose)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def mqtt_logging_enabled(mqtt_config):
 | 
					def has_mqtt_logging() -> bool:
 | 
				
			||||||
 | 
					    """Check if MQTT logging is available."""
 | 
				
			||||||
 | 
					    if CONF_MQTT not in CORE.config:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mqtt_config = CORE.config[CONF_MQTT]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # enabled by default
 | 
				
			||||||
 | 
					    if CONF_LOG_TOPIC not in mqtt_config:
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    log_topic = mqtt_config[CONF_LOG_TOPIC]
 | 
					    log_topic = mqtt_config[CONF_LOG_TOPIC]
 | 
				
			||||||
    if log_topic is None:
 | 
					    if log_topic is None:
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if CONF_TOPIC not in log_topic:
 | 
					    if CONF_TOPIC not in log_topic:
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return log_topic.get(CONF_LEVEL, None) != "NONE"
 | 
					    return log_topic.get(CONF_LEVEL, None) != "NONE"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def has_mqtt_logging() -> bool:
 | 
					def has_mqtt() -> bool:
 | 
				
			||||||
    """Check if MQTT logging is available."""
 | 
					    """Check if MQTT is available."""
 | 
				
			||||||
    return (mqtt_config := CORE.config.get(CONF_MQTT)) and mqtt_logging_enabled(
 | 
					    return CONF_MQTT in CORE.config
 | 
				
			||||||
        mqtt_config
 | 
					
 | 
				
			||||||
    )
 | 
					
 | 
				
			||||||
 | 
					def has_api() -> bool:
 | 
				
			||||||
 | 
					    """Check if API is available."""
 | 
				
			||||||
 | 
					    return CONF_API in CORE.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def has_ota() -> bool:
 | 
				
			||||||
 | 
					    """Check if OTA is available."""
 | 
				
			||||||
 | 
					    return CONF_OTA in CORE.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def has_mqtt_ip_lookup() -> bool:
 | 
				
			||||||
 | 
					    """Check if MQTT is available and IP lookup is supported."""
 | 
				
			||||||
 | 
					    from esphome.components.mqtt import CONF_DISCOVER_IP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_MQTT not in CORE.config:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					    # Default Enabled
 | 
				
			||||||
 | 
					    if CONF_DISCOVER_IP not in CORE.config[CONF_MQTT]:
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    return CORE.config[CONF_MQTT][CONF_DISCOVER_IP]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def has_mdns() -> bool:
 | 
				
			||||||
 | 
					    """Check if MDNS is available."""
 | 
				
			||||||
 | 
					    return CONF_MDNS not in CORE.config or not CORE.config[CONF_MDNS][CONF_DISABLED]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def has_non_ip_address() -> bool:
 | 
				
			||||||
 | 
					    """Check if CORE.address is set and is not an IP address."""
 | 
				
			||||||
 | 
					    return CORE.address is not None and not is_ip_address(CORE.address)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def has_ip_address() -> bool:
 | 
				
			||||||
 | 
					    """Check if CORE.address is a valid IP address."""
 | 
				
			||||||
 | 
					    return CORE.address is not None and is_ip_address(CORE.address)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def has_resolvable_address() -> bool:
 | 
				
			||||||
 | 
					    """Check if CORE.address is resolvable (via mDNS or is an IP address)."""
 | 
				
			||||||
 | 
					    return has_mdns() or has_ip_address()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def mqtt_get_ip(config: ConfigType, username: str, password: str, client_id: str):
 | 
				
			||||||
 | 
					    from esphome import mqtt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return mqtt.get_esphome_device_ip(config, username, password, client_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_PORT_TO_PORT_TYPE = {
 | 
				
			||||||
 | 
					    "MQTT": "MQTT",
 | 
				
			||||||
 | 
					    "MQTTIP": "MQTTIP",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_port_type(port: str) -> str:
 | 
					def get_port_type(port: str) -> str:
 | 
				
			||||||
    if port.startswith("/") or port.startswith("COM"):
 | 
					    if port.startswith("/") or port.startswith("COM"):
 | 
				
			||||||
        return "SERIAL"
 | 
					        return "SERIAL"
 | 
				
			||||||
    if port == "MQTT":
 | 
					    return _PORT_TO_PORT_TYPE.get(port, "NETWORK")
 | 
				
			||||||
        return "MQTT"
 | 
					 | 
				
			||||||
    return "NETWORK"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def run_miniterm(config: ConfigType, port: str, args) -> int:
 | 
					def run_miniterm(config: ConfigType, port: str, args) -> int:
 | 
				
			||||||
@@ -223,7 +332,9 @@ def run_miniterm(config: ConfigType, port: str, args) -> int:
 | 
				
			|||||||
                        .replace(b"\n", b"")
 | 
					                        .replace(b"\n", b"")
 | 
				
			||||||
                        .decode("utf8", "backslashreplace")
 | 
					                        .decode("utf8", "backslashreplace")
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    time_str = datetime.now().time().strftime("[%H:%M:%S]")
 | 
					                    time_ = datetime.now()
 | 
				
			||||||
 | 
					                    nanoseconds = time_.microsecond // 1000
 | 
				
			||||||
 | 
					                    time_str = f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
 | 
				
			||||||
                    safe_print(parser.parse_line(line, time_str))
 | 
					                    safe_print(parser.parse_line(line, time_str))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    backtrace_state = platformio_api.process_stacktrace(
 | 
					                    backtrace_state = platformio_api.process_stacktrace(
 | 
				
			||||||
@@ -346,7 +457,7 @@ def upload_using_esptool(
 | 
				
			|||||||
            "detect",
 | 
					            "detect",
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
        for img in flash_images:
 | 
					        for img in flash_images:
 | 
				
			||||||
            cmd += [img.offset, img.path]
 | 
					            cmd += [img.offset, str(img.path)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
 | 
					        if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
 | 
				
			||||||
            import esptool
 | 
					            import esptool
 | 
				
			||||||
@@ -393,27 +504,29 @@ def check_permissions(port: str):
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | str:
 | 
					def upload_program(
 | 
				
			||||||
 | 
					    config: ConfigType, args: ArgsProtocol, devices: list[str]
 | 
				
			||||||
 | 
					) -> tuple[int, str | None]:
 | 
				
			||||||
 | 
					    host = devices[0]
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        module = importlib.import_module("esphome.components." + CORE.target_platform)
 | 
					        module = importlib.import_module("esphome.components." + CORE.target_platform)
 | 
				
			||||||
        if getattr(module, "upload_program")(config, args, host):
 | 
					        if getattr(module, "upload_program")(config, args, host):
 | 
				
			||||||
            return 0
 | 
					            return 0, host
 | 
				
			||||||
    except AttributeError:
 | 
					    except AttributeError:
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if get_port_type(host) == "SERIAL":
 | 
					    if get_port_type(host) == "SERIAL":
 | 
				
			||||||
        check_permissions(host)
 | 
					        check_permissions(host)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        exit_code = 1
 | 
				
			||||||
        if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
 | 
					        if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
 | 
				
			||||||
            file = getattr(args, "file", None)
 | 
					            file = getattr(args, "file", None)
 | 
				
			||||||
            return upload_using_esptool(config, host, file, args.upload_speed)
 | 
					            exit_code = upload_using_esptool(config, host, file, args.upload_speed)
 | 
				
			||||||
 | 
					        elif CORE.target_platform == PLATFORM_RP2040 or CORE.is_libretiny:
 | 
				
			||||||
 | 
					            exit_code = upload_using_platformio(config, host)
 | 
				
			||||||
 | 
					        # else: Unknown target platform, exit_code remains 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if CORE.target_platform in (PLATFORM_RP2040):
 | 
					        return exit_code, host if exit_code == 0 else None
 | 
				
			||||||
            return upload_using_platformio(config, host)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if CORE.is_libretiny:
 | 
					 | 
				
			||||||
            return upload_using_platformio(config, host)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return 1  # Unknown target platform
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ota_conf = {}
 | 
					    ota_conf = {}
 | 
				
			||||||
    for ota_item in config.get(CONF_OTA, []):
 | 
					    for ota_item in config.get(CONF_OTA, []):
 | 
				
			||||||
@@ -430,31 +543,26 @@ def upload_program(config: ConfigType, args: ArgsProtocol, host: str) -> int | s
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    remote_port = int(ota_conf[CONF_PORT])
 | 
					    remote_port = int(ota_conf[CONF_PORT])
 | 
				
			||||||
    password = ota_conf.get(CONF_PASSWORD, "")
 | 
					    password = ota_conf.get(CONF_PASSWORD, "")
 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Check if we should use MQTT for address resolution
 | 
					 | 
				
			||||||
    # This happens when no device was specified, or the current host is "MQTT"/"OTA"
 | 
					 | 
				
			||||||
    devices: list[str] = args.device or []
 | 
					 | 
				
			||||||
    if (
 | 
					 | 
				
			||||||
        CONF_MQTT in config  # pylint: disable=too-many-boolean-expressions
 | 
					 | 
				
			||||||
        and (not devices or host 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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        host = mqtt.get_esphome_device_ip(
 | 
					 | 
				
			||||||
            config, args.username, args.password, args.client_id
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if getattr(args, "file", None) is not None:
 | 
					    if getattr(args, "file", None) is not None:
 | 
				
			||||||
        return espota2.run_ota(host, remote_port, password, args.file)
 | 
					        binary = Path(args.file)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        binary = CORE.firmware_bin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
 | 
					    # MQTT address resolution
 | 
				
			||||||
 | 
					    if get_port_type(host) in ("MQTT", "MQTTIP"):
 | 
				
			||||||
 | 
					        devices = mqtt_get_ip(config, args.username, args.password, args.client_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return espota2.run_ota(devices, remote_port, password, binary)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None:
 | 
					def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int | None:
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        module = importlib.import_module("esphome.components." + CORE.target_platform)
 | 
				
			||||||
 | 
					        if getattr(module, "show_logs")(config, args, devices):
 | 
				
			||||||
 | 
					            return 0
 | 
				
			||||||
 | 
					    except AttributeError:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if "logger" not in config:
 | 
					    if "logger" not in config:
 | 
				
			||||||
        raise EsphomeError("Logger is not configured!")
 | 
					        raise EsphomeError("Logger is not configured!")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -463,20 +571,28 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
 | 
				
			|||||||
    if get_port_type(port) == "SERIAL":
 | 
					    if get_port_type(port) == "SERIAL":
 | 
				
			||||||
        check_permissions(port)
 | 
					        check_permissions(port)
 | 
				
			||||||
        return run_miniterm(config, port, args)
 | 
					        return run_miniterm(config, port, args)
 | 
				
			||||||
    if get_port_type(port) == "NETWORK" and "api" in config:
 | 
					 | 
				
			||||||
        addresses_to_use = devices
 | 
					 | 
				
			||||||
        if config[CONF_MDNS][CONF_DISABLED] and CONF_MQTT in config:
 | 
					 | 
				
			||||||
            from esphome import mqtt
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            mqtt_address = mqtt.get_esphome_device_ip(
 | 
					    port_type = get_port_type(port)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Check if we should use API for logging
 | 
				
			||||||
 | 
					    if has_api():
 | 
				
			||||||
 | 
					        addresses_to_use: list[str] | None = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if port_type == "NETWORK" and (has_mdns() or is_ip_address(port)):
 | 
				
			||||||
 | 
					            addresses_to_use = devices
 | 
				
			||||||
 | 
					        elif port_type in ("NETWORK", "MQTT", "MQTTIP") and has_mqtt_ip_lookup():
 | 
				
			||||||
 | 
					            # Only use MQTT IP lookup if the first condition didn't match
 | 
				
			||||||
 | 
					            # (for MQTT/MQTTIP types, or for NETWORK when mdns/ip check fails)
 | 
				
			||||||
 | 
					            addresses_to_use = mqtt_get_ip(
 | 
				
			||||||
                config, args.username, args.password, args.client_id
 | 
					                config, args.username, args.password, args.client_id
 | 
				
			||||||
            )[0]
 | 
					            )
 | 
				
			||||||
            addresses_to_use = [mqtt_address]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        from esphome.components.api.client import run_logs
 | 
					        if addresses_to_use is not None:
 | 
				
			||||||
 | 
					            from esphome.components.api.client import run_logs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return run_logs(config, addresses_to_use)
 | 
					            return run_logs(config, addresses_to_use)
 | 
				
			||||||
    if get_port_type(port) == "MQTT" and "mqtt" in config:
 | 
					
 | 
				
			||||||
 | 
					    if port_type in ("NETWORK", "MQTT") and has_mqtt_logging():
 | 
				
			||||||
        from esphome import mqtt
 | 
					        from esphome import mqtt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return mqtt.show_logs(
 | 
					        return mqtt.show_logs(
 | 
				
			||||||
@@ -497,7 +613,7 @@ def clean_mqtt(config: ConfigType, args: ArgsProtocol) -> int | None:
 | 
				
			|||||||
def command_wizard(args: ArgsProtocol) -> int | None:
 | 
					def command_wizard(args: ArgsProtocol) -> int | None:
 | 
				
			||||||
    from esphome import wizard
 | 
					    from esphome import wizard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return wizard.wizard(args.configuration)
 | 
					    return wizard.wizard(Path(args.configuration))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
					def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
				
			||||||
@@ -542,23 +658,14 @@ def command_upload(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
				
			|||||||
    devices = choose_upload_log_host(
 | 
					    devices = choose_upload_log_host(
 | 
				
			||||||
        default=args.device,
 | 
					        default=args.device,
 | 
				
			||||||
        check_default=None,
 | 
					        check_default=None,
 | 
				
			||||||
        show_ota=True,
 | 
					        purpose=Purpose.UPLOADING,
 | 
				
			||||||
        show_mqtt=False,
 | 
					 | 
				
			||||||
        show_api=False,
 | 
					 | 
				
			||||||
        purpose="uploading",
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Try each device until one succeeds
 | 
					    exit_code, _ = upload_program(config, args, devices)
 | 
				
			||||||
    exit_code = 1
 | 
					    if exit_code == 0:
 | 
				
			||||||
    for device in devices:
 | 
					        _LOGGER.info("Successfully uploaded program.")
 | 
				
			||||||
        _LOGGER.info("Uploading to %s", device)
 | 
					    else:
 | 
				
			||||||
        exit_code = upload_program(config, args, device)
 | 
					        _LOGGER.warning("Failed to upload to %s", devices)
 | 
				
			||||||
        if exit_code == 0:
 | 
					 | 
				
			||||||
            _LOGGER.info("Successfully uploaded program.")
 | 
					 | 
				
			||||||
            return 0
 | 
					 | 
				
			||||||
        if len(devices) > 1:
 | 
					 | 
				
			||||||
            _LOGGER.warning("Failed to upload to %s", device)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return exit_code
 | 
					    return exit_code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -576,10 +683,7 @@ def command_logs(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
				
			|||||||
    devices = choose_upload_log_host(
 | 
					    devices = choose_upload_log_host(
 | 
				
			||||||
        default=args.device,
 | 
					        default=args.device,
 | 
				
			||||||
        check_default=None,
 | 
					        check_default=None,
 | 
				
			||||||
        show_ota=False,
 | 
					        purpose=Purpose.LOGGING,
 | 
				
			||||||
        show_mqtt=True,
 | 
					 | 
				
			||||||
        show_api=True,
 | 
					 | 
				
			||||||
        purpose="logging",
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    return show_logs(config, args, devices)
 | 
					    return show_logs(config, args, devices)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -605,25 +709,14 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
				
			|||||||
    devices = choose_upload_log_host(
 | 
					    devices = choose_upload_log_host(
 | 
				
			||||||
        default=args.device,
 | 
					        default=args.device,
 | 
				
			||||||
        check_default=None,
 | 
					        check_default=None,
 | 
				
			||||||
        show_ota=True,
 | 
					        purpose=Purpose.UPLOADING,
 | 
				
			||||||
        show_mqtt=False,
 | 
					 | 
				
			||||||
        show_api=True,
 | 
					 | 
				
			||||||
        purpose="uploading",
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Try each device for upload until one succeeds
 | 
					    exit_code, successful_device = upload_program(config, args, devices)
 | 
				
			||||||
    successful_device: str | None = None
 | 
					    if exit_code == 0:
 | 
				
			||||||
    for device in devices:
 | 
					        _LOGGER.info("Successfully uploaded program.")
 | 
				
			||||||
        _LOGGER.info("Uploading to %s", device)
 | 
					    else:
 | 
				
			||||||
        exit_code = upload_program(config, args, device)
 | 
					        _LOGGER.warning("Failed to upload to %s", devices)
 | 
				
			||||||
        if exit_code == 0:
 | 
					 | 
				
			||||||
            _LOGGER.info("Successfully uploaded program.")
 | 
					 | 
				
			||||||
            successful_device = device
 | 
					 | 
				
			||||||
            break
 | 
					 | 
				
			||||||
        if len(devices) > 1:
 | 
					 | 
				
			||||||
            _LOGGER.warning("Failed to upload to %s", device)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if successful_device is None:
 | 
					 | 
				
			||||||
        return exit_code
 | 
					        return exit_code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if args.no_logs:
 | 
					    if args.no_logs:
 | 
				
			||||||
@@ -633,10 +726,7 @@ def command_run(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
				
			|||||||
    devices = choose_upload_log_host(
 | 
					    devices = choose_upload_log_host(
 | 
				
			||||||
        default=successful_device,
 | 
					        default=successful_device,
 | 
				
			||||||
        check_default=successful_device,
 | 
					        check_default=successful_device,
 | 
				
			||||||
        show_ota=False,
 | 
					        purpose=Purpose.LOGGING,
 | 
				
			||||||
        show_mqtt=True,
 | 
					 | 
				
			||||||
        show_api=True,
 | 
					 | 
				
			||||||
        purpose="logging",
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    return show_logs(config, args, devices)
 | 
					    return show_logs(config, args, devices)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -645,6 +735,16 @@ def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
				
			|||||||
    return clean_mqtt(config, args)
 | 
					    return clean_mqtt(config, args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def command_clean_all(args: ArgsProtocol) -> int | None:
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        writer.clean_all(args.configuration)
 | 
				
			||||||
 | 
					    except OSError as err:
 | 
				
			||||||
 | 
					        _LOGGER.error("Error cleaning all files: %s", err)
 | 
				
			||||||
 | 
					        return 1
 | 
				
			||||||
 | 
					    _LOGGER.info("Done!")
 | 
				
			||||||
 | 
					    return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
					def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
				
			||||||
    from esphome import mqtt
 | 
					    from esphome import mqtt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -686,7 +786,7 @@ def command_update_all(args: ArgsProtocol) -> int | None:
 | 
				
			|||||||
        safe_print(f"{half_line}{middle_text}{half_line}")
 | 
					        safe_print(f"{half_line}{middle_text}{half_line}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for f in files:
 | 
					    for f in files:
 | 
				
			||||||
        safe_print(f"Updating {color(AnsiFore.CYAN, f)}")
 | 
					        safe_print(f"Updating {color(AnsiFore.CYAN, str(f))}")
 | 
				
			||||||
        safe_print("-" * twidth)
 | 
					        safe_print("-" * twidth)
 | 
				
			||||||
        safe_print()
 | 
					        safe_print()
 | 
				
			||||||
        if CORE.dashboard:
 | 
					        if CORE.dashboard:
 | 
				
			||||||
@@ -698,10 +798,10 @@ def command_update_all(args: ArgsProtocol) -> int | None:
 | 
				
			|||||||
                "esphome", "run", f, "--no-logs", "--device", "OTA"
 | 
					                "esphome", "run", f, "--no-logs", "--device", "OTA"
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        if rc == 0:
 | 
					        if rc == 0:
 | 
				
			||||||
            print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {f}")
 | 
					            print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}")
 | 
				
			||||||
            success[f] = True
 | 
					            success[f] = True
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {f}")
 | 
					            print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {str(f)}")
 | 
				
			||||||
            success[f] = False
 | 
					            success[f] = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        safe_print()
 | 
					        safe_print()
 | 
				
			||||||
@@ -712,9 +812,9 @@ def command_update_all(args: ArgsProtocol) -> int | None:
 | 
				
			|||||||
    failed = 0
 | 
					    failed = 0
 | 
				
			||||||
    for f in files:
 | 
					    for f in files:
 | 
				
			||||||
        if success[f]:
 | 
					        if success[f]:
 | 
				
			||||||
            safe_print(f"  - {f}: {color(AnsiFore.GREEN, 'SUCCESS')}")
 | 
					            safe_print(f"  - {str(f)}: {color(AnsiFore.GREEN, 'SUCCESS')}")
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            safe_print(f"  - {f}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
 | 
					            safe_print(f"  - {str(f)}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
 | 
				
			||||||
            failed += 1
 | 
					            failed += 1
 | 
				
			||||||
    return failed
 | 
					    return failed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -736,7 +836,8 @@ def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
					def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
				
			||||||
    for c in args.name:
 | 
					    new_name = args.name
 | 
				
			||||||
 | 
					    for c in new_name:
 | 
				
			||||||
        if c not in ALLOWED_NAME_CHARS:
 | 
					        if c not in ALLOWED_NAME_CHARS:
 | 
				
			||||||
            print(
 | 
					            print(
 | 
				
			||||||
                color(
 | 
					                color(
 | 
				
			||||||
@@ -747,8 +848,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
            return 1
 | 
					            return 1
 | 
				
			||||||
    # Load existing yaml file
 | 
					    # Load existing yaml file
 | 
				
			||||||
    with open(CORE.config_path, mode="r+", encoding="utf-8") as raw_file:
 | 
					    raw_contents = CORE.config_path.read_text(encoding="utf-8")
 | 
				
			||||||
        raw_contents = raw_file.read()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    yaml = yaml_util.load_yaml(CORE.config_path)
 | 
					    yaml = yaml_util.load_yaml(CORE.config_path)
 | 
				
			||||||
    if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
 | 
					    if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
 | 
				
			||||||
@@ -763,7 +863,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
				
			|||||||
    if match is None:
 | 
					    if match is None:
 | 
				
			||||||
        new_raw = re.sub(
 | 
					        new_raw = re.sub(
 | 
				
			||||||
            rf"name:\s+[\"']?{old_name}[\"']?",
 | 
					            rf"name:\s+[\"']?{old_name}[\"']?",
 | 
				
			||||||
            f'name: "{args.name}"',
 | 
					            f'name: "{new_name}"',
 | 
				
			||||||
            raw_contents,
 | 
					            raw_contents,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
@@ -783,29 +883,28 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        new_raw = re.sub(
 | 
					        new_raw = re.sub(
 | 
				
			||||||
            rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?",
 | 
					            rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?",
 | 
				
			||||||
            f'\\1: "{args.name}"',
 | 
					            f'\\1: "{new_name}"',
 | 
				
			||||||
            raw_contents,
 | 
					            raw_contents,
 | 
				
			||||||
            flags=re.MULTILINE,
 | 
					            flags=re.MULTILINE,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    new_path = os.path.join(CORE.config_dir, args.name + ".yaml")
 | 
					    new_path: Path = CORE.config_dir / (new_name + ".yaml")
 | 
				
			||||||
    print(
 | 
					    print(
 | 
				
			||||||
        f"Updating {color(AnsiFore.CYAN, CORE.config_path)} to {color(AnsiFore.CYAN, new_path)}"
 | 
					        f"Updating {color(AnsiFore.CYAN, str(CORE.config_path))} to {color(AnsiFore.CYAN, str(new_path))}"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    print()
 | 
					    print()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    with open(new_path, mode="w", encoding="utf-8") as new_file:
 | 
					    new_path.write_text(new_raw, encoding="utf-8")
 | 
				
			||||||
        new_file.write(new_raw)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    rc = run_external_process("esphome", "config", new_path)
 | 
					    rc = run_external_process("esphome", "config", str(new_path))
 | 
				
			||||||
    if rc != 0:
 | 
					    if rc != 0:
 | 
				
			||||||
        print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
 | 
					        print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
 | 
				
			||||||
        os.remove(new_path)
 | 
					        new_path.unlink()
 | 
				
			||||||
        return 1
 | 
					        return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cli_args = [
 | 
					    cli_args = [
 | 
				
			||||||
        "run",
 | 
					        "run",
 | 
				
			||||||
        new_path,
 | 
					        str(new_path),
 | 
				
			||||||
        "--no-logs",
 | 
					        "--no-logs",
 | 
				
			||||||
        "--device",
 | 
					        "--device",
 | 
				
			||||||
        CORE.address,
 | 
					        CORE.address,
 | 
				
			||||||
@@ -819,11 +918,11 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
 | 
				
			|||||||
    except KeyboardInterrupt:
 | 
					    except KeyboardInterrupt:
 | 
				
			||||||
        rc = 1
 | 
					        rc = 1
 | 
				
			||||||
    if rc != 0:
 | 
					    if rc != 0:
 | 
				
			||||||
        os.remove(new_path)
 | 
					        new_path.unlink()
 | 
				
			||||||
        return 1
 | 
					        return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if CORE.config_path != new_path:
 | 
					    if CORE.config_path != new_path:
 | 
				
			||||||
        os.remove(CORE.config_path)
 | 
					        CORE.config_path.unlink()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
 | 
					    print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
 | 
				
			||||||
    print()
 | 
					    print()
 | 
				
			||||||
@@ -836,6 +935,7 @@ PRE_CONFIG_ACTIONS = {
 | 
				
			|||||||
    "dashboard": command_dashboard,
 | 
					    "dashboard": command_dashboard,
 | 
				
			||||||
    "vscode": command_vscode,
 | 
					    "vscode": command_vscode,
 | 
				
			||||||
    "update-all": command_update_all,
 | 
					    "update-all": command_update_all,
 | 
				
			||||||
 | 
					    "clean-all": command_clean_all,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
POST_CONFIG_ACTIONS = {
 | 
					POST_CONFIG_ACTIONS = {
 | 
				
			||||||
@@ -844,9 +944,9 @@ POST_CONFIG_ACTIONS = {
 | 
				
			|||||||
    "upload": command_upload,
 | 
					    "upload": command_upload,
 | 
				
			||||||
    "logs": command_logs,
 | 
					    "logs": command_logs,
 | 
				
			||||||
    "run": command_run,
 | 
					    "run": command_run,
 | 
				
			||||||
 | 
					    "clean": command_clean,
 | 
				
			||||||
    "clean-mqtt": command_clean_mqtt,
 | 
					    "clean-mqtt": command_clean_mqtt,
 | 
				
			||||||
    "mqtt-fingerprint": command_mqtt_fingerprint,
 | 
					    "mqtt-fingerprint": command_mqtt_fingerprint,
 | 
				
			||||||
    "clean": command_clean,
 | 
					 | 
				
			||||||
    "idedata": command_idedata,
 | 
					    "idedata": command_idedata,
 | 
				
			||||||
    "rename": command_rename,
 | 
					    "rename": command_rename,
 | 
				
			||||||
    "discover": command_discover,
 | 
					    "discover": command_discover,
 | 
				
			||||||
@@ -890,6 +990,24 @@ def parse_args(argv):
 | 
				
			|||||||
        help="Add a substitution",
 | 
					        help="Add a substitution",
 | 
				
			||||||
        metavar=("key", "value"),
 | 
					        metavar=("key", "value"),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    options_parser.add_argument(
 | 
				
			||||||
 | 
					        "--mdns-address-cache",
 | 
				
			||||||
 | 
					        help="mDNS address cache mapping in format 'hostname=ip1,ip2'",
 | 
				
			||||||
 | 
					        action="append",
 | 
				
			||||||
 | 
					        default=[],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    options_parser.add_argument(
 | 
				
			||||||
 | 
					        "--dns-address-cache",
 | 
				
			||||||
 | 
					        help="DNS address cache mapping in format 'hostname=ip1,ip2'",
 | 
				
			||||||
 | 
					        action="append",
 | 
				
			||||||
 | 
					        default=[],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    options_parser.add_argument(
 | 
				
			||||||
 | 
					        "--testing-mode",
 | 
				
			||||||
 | 
					        help="Enable testing mode (disables validation checks for grouped component testing)",
 | 
				
			||||||
 | 
					        action="store_true",
 | 
				
			||||||
 | 
					        default=False,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser = argparse.ArgumentParser(
 | 
					    parser = argparse.ArgumentParser(
 | 
				
			||||||
        description=f"ESPHome {const.__version__}", parents=[options_parser]
 | 
					        description=f"ESPHome {const.__version__}", parents=[options_parser]
 | 
				
			||||||
@@ -1047,6 +1165,13 @@ def parse_args(argv):
 | 
				
			|||||||
        "configuration", help="Your YAML configuration file(s).", nargs="+"
 | 
					        "configuration", help="Your YAML configuration file(s).", nargs="+"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parser_clean_all = subparsers.add_parser(
 | 
				
			||||||
 | 
					        "clean-all", help="Clean all build and platform files."
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    parser_clean_all.add_argument(
 | 
				
			||||||
 | 
					        "configuration", help="Your YAML configuration directory.", nargs="*"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser_dashboard = subparsers.add_parser(
 | 
					    parser_dashboard = subparsers.add_parser(
 | 
				
			||||||
        "dashboard", help="Create a simple web server for a dashboard."
 | 
					        "dashboard", help="Create a simple web server for a dashboard."
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
@@ -1093,7 +1218,7 @@ def parse_args(argv):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    parser_update = subparsers.add_parser("update-all")
 | 
					    parser_update = subparsers.add_parser("update-all")
 | 
				
			||||||
    parser_update.add_argument(
 | 
					    parser_update.add_argument(
 | 
				
			||||||
        "configuration", help="Your YAML configuration file directories.", nargs="+"
 | 
					        "configuration", help="Your YAML configuration file or directory.", nargs="+"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser_idedata = subparsers.add_parser("idedata")
 | 
					    parser_idedata = subparsers.add_parser("idedata")
 | 
				
			||||||
@@ -1137,9 +1262,16 @@ def parse_args(argv):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def run_esphome(argv):
 | 
					def run_esphome(argv):
 | 
				
			||||||
 | 
					    from esphome.address_cache import AddressCache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    args = parse_args(argv)
 | 
					    args = parse_args(argv)
 | 
				
			||||||
    CORE.dashboard = args.dashboard
 | 
					    CORE.dashboard = args.dashboard
 | 
				
			||||||
 | 
					    CORE.testing_mode = args.testing_mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Create address cache from command-line arguments
 | 
				
			||||||
 | 
					    CORE.address_cache = AddressCache.from_cli_args(
 | 
				
			||||||
 | 
					        args.mdns_address_cache, args.dns_address_cache
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    # Override log level if verbose is set
 | 
					    # Override log level if verbose is set
 | 
				
			||||||
    if args.verbose:
 | 
					    if args.verbose:
 | 
				
			||||||
        args.log_level = "DEBUG"
 | 
					        args.log_level = "DEBUG"
 | 
				
			||||||
@@ -1162,14 +1294,20 @@ def run_esphome(argv):
 | 
				
			|||||||
    _LOGGER.info("ESPHome %s", const.__version__)
 | 
					    _LOGGER.info("ESPHome %s", const.__version__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for conf_path in args.configuration:
 | 
					    for conf_path in args.configuration:
 | 
				
			||||||
        if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
 | 
					        conf_path = Path(conf_path)
 | 
				
			||||||
 | 
					        if any(conf_path.name == x for x in SECRETS_FILES):
 | 
				
			||||||
            _LOGGER.warning("Skipping secrets file %s", conf_path)
 | 
					            _LOGGER.warning("Skipping secrets file %s", conf_path)
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        CORE.config_path = conf_path
 | 
					        CORE.config_path = conf_path
 | 
				
			||||||
        CORE.dashboard = args.dashboard
 | 
					        CORE.dashboard = args.dashboard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        config = read_config(dict(args.substitution) if args.substitution else {})
 | 
					        # For logs command, skip updating external components
 | 
				
			||||||
 | 
					        skip_external = args.command == "logs"
 | 
				
			||||||
 | 
					        config = read_config(
 | 
				
			||||||
 | 
					            dict(args.substitution) if args.substitution else {},
 | 
				
			||||||
 | 
					            skip_external_update=skip_external,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        if config is None:
 | 
					        if config is None:
 | 
				
			||||||
            return 2
 | 
					            return 2
 | 
				
			||||||
        CORE.config = config
 | 
					        CORE.config = config
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										142
									
								
								esphome/address_cache.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								esphome/address_cache.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
				
			|||||||
 | 
					"""Address cache for DNS and mDNS lookups."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from __future__ import annotations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					from typing import TYPE_CHECKING
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if TYPE_CHECKING:
 | 
				
			||||||
 | 
					    from collections.abc import Iterable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_LOGGER = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def normalize_hostname(hostname: str) -> str:
 | 
				
			||||||
 | 
					    """Normalize hostname for cache lookups.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Removes trailing dots and converts to lowercase.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    return hostname.rstrip(".").lower()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AddressCache:
 | 
				
			||||||
 | 
					    """Cache for DNS and mDNS address lookups.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    This cache stores pre-resolved addresses from command-line arguments
 | 
				
			||||||
 | 
					    to avoid slow DNS/mDNS lookups during builds.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        mdns_cache: dict[str, list[str]] | None = None,
 | 
				
			||||||
 | 
					        dns_cache: dict[str, list[str]] | None = None,
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
 | 
					        """Initialize the address cache.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            mdns_cache: Pre-populated mDNS addresses (hostname -> IPs)
 | 
				
			||||||
 | 
					            dns_cache: Pre-populated DNS addresses (hostname -> IPs)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.mdns_cache = mdns_cache or {}
 | 
				
			||||||
 | 
					        self.dns_cache = dns_cache or {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_cached_addresses(
 | 
				
			||||||
 | 
					        self, hostname: str, cache: dict[str, list[str]], cache_type: str
 | 
				
			||||||
 | 
					    ) -> list[str] | None:
 | 
				
			||||||
 | 
					        """Get cached addresses from a specific cache.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            hostname: The hostname to look up
 | 
				
			||||||
 | 
					            cache: The cache dictionary to check
 | 
				
			||||||
 | 
					            cache_type: Type of cache for logging ("mDNS" or "DNS")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns:
 | 
				
			||||||
 | 
					            List of IP addresses if found in cache, None otherwise
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        normalized = normalize_hostname(hostname)
 | 
				
			||||||
 | 
					        if addresses := cache.get(normalized):
 | 
				
			||||||
 | 
					            _LOGGER.debug("Using %s cache for %s: %s", cache_type, hostname, addresses)
 | 
				
			||||||
 | 
					            return addresses
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_mdns_addresses(self, hostname: str) -> list[str] | None:
 | 
				
			||||||
 | 
					        """Get cached mDNS addresses for a hostname.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            hostname: The hostname to look up (should end with .local)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns:
 | 
				
			||||||
 | 
					            List of IP addresses if found in cache, None otherwise
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return self._get_cached_addresses(hostname, self.mdns_cache, "mDNS")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_dns_addresses(self, hostname: str) -> list[str] | None:
 | 
				
			||||||
 | 
					        """Get cached DNS addresses for a hostname.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            hostname: The hostname to look up
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns:
 | 
				
			||||||
 | 
					            List of IP addresses if found in cache, None otherwise
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return self._get_cached_addresses(hostname, self.dns_cache, "DNS")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_addresses(self, hostname: str) -> list[str] | None:
 | 
				
			||||||
 | 
					        """Get cached addresses for a hostname.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Checks mDNS cache for .local domains, DNS cache otherwise.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            hostname: The hostname to look up
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns:
 | 
				
			||||||
 | 
					            List of IP addresses if found in cache, None otherwise
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        normalized = normalize_hostname(hostname)
 | 
				
			||||||
 | 
					        if normalized.endswith(".local"):
 | 
				
			||||||
 | 
					            return self.get_mdns_addresses(hostname)
 | 
				
			||||||
 | 
					        return self.get_dns_addresses(hostname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def has_cache(self) -> bool:
 | 
				
			||||||
 | 
					        """Check if any cache entries exist."""
 | 
				
			||||||
 | 
					        return bool(self.mdns_cache or self.dns_cache)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def from_cli_args(
 | 
				
			||||||
 | 
					        cls, mdns_args: Iterable[str], dns_args: Iterable[str]
 | 
				
			||||||
 | 
					    ) -> AddressCache:
 | 
				
			||||||
 | 
					        """Create cache from command-line arguments.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            mdns_args: List of mDNS cache entries like ['host=ip1,ip2']
 | 
				
			||||||
 | 
					            dns_args: List of DNS cache entries like ['host=ip1,ip2']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns:
 | 
				
			||||||
 | 
					            Configured AddressCache instance
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        mdns_cache = cls._parse_cache_args(mdns_args)
 | 
				
			||||||
 | 
					        dns_cache = cls._parse_cache_args(dns_args)
 | 
				
			||||||
 | 
					        return cls(mdns_cache=mdns_cache, dns_cache=dns_cache)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def _parse_cache_args(cache_args: Iterable[str]) -> dict[str, list[str]]:
 | 
				
			||||||
 | 
					        """Parse cache arguments into a dictionary.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            cache_args: List of cache mappings like ['host1=ip1,ip2', 'host2=ip3']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns:
 | 
				
			||||||
 | 
					            Dictionary mapping normalized hostnames to list of IP addresses
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        cache: dict[str, list[str]] = {}
 | 
				
			||||||
 | 
					        for arg in cache_args:
 | 
				
			||||||
 | 
					            if "=" not in arg:
 | 
				
			||||||
 | 
					                _LOGGER.warning(
 | 
				
			||||||
 | 
					                    "Invalid cache format: %s (expected 'hostname=ip1,ip2')", arg
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            hostname, ips = arg.split("=", 1)
 | 
				
			||||||
 | 
					            # Normalize hostname for consistent lookups
 | 
				
			||||||
 | 
					            normalized = normalize_hostname(hostname)
 | 
				
			||||||
 | 
					            cache[normalized] = [ip.strip() for ip in ips.split(",")]
 | 
				
			||||||
 | 
					        return cache
 | 
				
			||||||
@@ -15,7 +15,10 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_TYPE_ID,
 | 
					    CONF_TYPE_ID,
 | 
				
			||||||
    CONF_UPDATE_INTERVAL,
 | 
					    CONF_UPDATE_INTERVAL,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from esphome.core import ID
 | 
				
			||||||
 | 
					from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType
 | 
				
			||||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
 | 
					from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
 | 
				
			||||||
 | 
					from esphome.types import ConfigType
 | 
				
			||||||
from esphome.util import Registry
 | 
					from esphome.util import Registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -49,11 +52,11 @@ def maybe_conf(conf, *validators):
 | 
				
			|||||||
    return validate
 | 
					    return validate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def register_action(name, action_type, schema):
 | 
					def register_action(name: str, action_type: MockObjClass, schema: cv.Schema):
 | 
				
			||||||
    return ACTION_REGISTRY.register(name, action_type, schema)
 | 
					    return ACTION_REGISTRY.register(name, action_type, schema)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def register_condition(name, condition_type, schema):
 | 
					def register_condition(name: str, condition_type: MockObjClass, schema: cv.Schema):
 | 
				
			||||||
    return CONDITION_REGISTRY.register(name, condition_type, schema)
 | 
					    return CONDITION_REGISTRY.register(name, condition_type, schema)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -164,43 +167,78 @@ XorCondition = cg.esphome_ns.class_("XorCondition", Condition)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@register_condition("and", AndCondition, validate_condition_list)
 | 
					@register_condition("and", AndCondition, validate_condition_list)
 | 
				
			||||||
async def and_condition_to_code(config, condition_id, template_arg, args):
 | 
					async def and_condition_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    condition_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    conditions = await build_condition_list(config, template_arg, args)
 | 
					    conditions = await build_condition_list(config, template_arg, args)
 | 
				
			||||||
    return cg.new_Pvariable(condition_id, template_arg, conditions)
 | 
					    return cg.new_Pvariable(condition_id, template_arg, conditions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@register_condition("or", OrCondition, validate_condition_list)
 | 
					@register_condition("or", OrCondition, validate_condition_list)
 | 
				
			||||||
async def or_condition_to_code(config, condition_id, template_arg, args):
 | 
					async def or_condition_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    condition_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    conditions = await build_condition_list(config, template_arg, args)
 | 
					    conditions = await build_condition_list(config, template_arg, args)
 | 
				
			||||||
    return cg.new_Pvariable(condition_id, template_arg, conditions)
 | 
					    return cg.new_Pvariable(condition_id, template_arg, conditions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@register_condition("all", AndCondition, validate_condition_list)
 | 
					@register_condition("all", AndCondition, validate_condition_list)
 | 
				
			||||||
async def all_condition_to_code(config, condition_id, template_arg, args):
 | 
					async def all_condition_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    condition_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    conditions = await build_condition_list(config, template_arg, args)
 | 
					    conditions = await build_condition_list(config, template_arg, args)
 | 
				
			||||||
    return cg.new_Pvariable(condition_id, template_arg, conditions)
 | 
					    return cg.new_Pvariable(condition_id, template_arg, conditions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@register_condition("any", OrCondition, validate_condition_list)
 | 
					@register_condition("any", OrCondition, validate_condition_list)
 | 
				
			||||||
async def any_condition_to_code(config, condition_id, template_arg, args):
 | 
					async def any_condition_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    condition_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    conditions = await build_condition_list(config, template_arg, args)
 | 
					    conditions = await build_condition_list(config, template_arg, args)
 | 
				
			||||||
    return cg.new_Pvariable(condition_id, template_arg, conditions)
 | 
					    return cg.new_Pvariable(condition_id, template_arg, conditions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@register_condition("not", NotCondition, validate_potentially_and_condition)
 | 
					@register_condition("not", NotCondition, validate_potentially_and_condition)
 | 
				
			||||||
async def not_condition_to_code(config, condition_id, template_arg, args):
 | 
					async def not_condition_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    condition_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    condition = await build_condition(config, template_arg, args)
 | 
					    condition = await build_condition(config, template_arg, args)
 | 
				
			||||||
    return cg.new_Pvariable(condition_id, template_arg, condition)
 | 
					    return cg.new_Pvariable(condition_id, template_arg, condition)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@register_condition("xor", XorCondition, validate_condition_list)
 | 
					@register_condition("xor", XorCondition, validate_condition_list)
 | 
				
			||||||
async def xor_condition_to_code(config, condition_id, template_arg, args):
 | 
					async def xor_condition_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    condition_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    conditions = await build_condition_list(config, template_arg, args)
 | 
					    conditions = await build_condition_list(config, template_arg, args)
 | 
				
			||||||
    return cg.new_Pvariable(condition_id, template_arg, conditions)
 | 
					    return cg.new_Pvariable(condition_id, template_arg, conditions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@register_condition("lambda", LambdaCondition, cv.returning_lambda)
 | 
					@register_condition("lambda", LambdaCondition, cv.returning_lambda)
 | 
				
			||||||
async def lambda_condition_to_code(config, condition_id, template_arg, args):
 | 
					async def lambda_condition_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    condition_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    lambda_ = await cg.process_lambda(config, args, return_type=bool)
 | 
					    lambda_ = await cg.process_lambda(config, args, return_type=bool)
 | 
				
			||||||
    return cg.new_Pvariable(condition_id, template_arg, lambda_)
 | 
					    return cg.new_Pvariable(condition_id, template_arg, lambda_)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -217,7 +255,12 @@ async def lambda_condition_to_code(config, condition_id, template_arg, args):
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
					    ).extend(cv.COMPONENT_SCHEMA),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
async def for_condition_to_code(config, condition_id, template_arg, args):
 | 
					async def for_condition_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    condition_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    condition = await build_condition(
 | 
					    condition = await build_condition(
 | 
				
			||||||
        config[CONF_CONDITION], cg.TemplateArguments(), []
 | 
					        config[CONF_CONDITION], cg.TemplateArguments(), []
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
@@ -231,7 +274,12 @@ async def for_condition_to_code(config, condition_id, template_arg, args):
 | 
				
			|||||||
@register_action(
 | 
					@register_action(
 | 
				
			||||||
    "delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
 | 
					    "delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
async def delay_action_to_code(config, action_id, template_arg, args):
 | 
					async def delay_action_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    action_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    var = cg.new_Pvariable(action_id, template_arg)
 | 
					    var = cg.new_Pvariable(action_id, template_arg)
 | 
				
			||||||
    await cg.register_component(var, {})
 | 
					    await cg.register_component(var, {})
 | 
				
			||||||
    template_ = await cg.templatable(config, args, cg.uint32)
 | 
					    template_ = await cg.templatable(config, args, cg.uint32)
 | 
				
			||||||
@@ -256,10 +304,15 @@ async def delay_action_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
        cv.has_at_least_one_key(CONF_CONDITION, CONF_ANY, CONF_ALL),
 | 
					        cv.has_at_least_one_key(CONF_CONDITION, CONF_ANY, CONF_ALL),
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
async def if_action_to_code(config, action_id, template_arg, args):
 | 
					async def if_action_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    action_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION))
 | 
					    cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION))
 | 
				
			||||||
    conditions = await build_condition(config[cond_conf], template_arg, args)
 | 
					    condition = await build_condition(config[cond_conf], template_arg, args)
 | 
				
			||||||
    var = cg.new_Pvariable(action_id, template_arg, conditions)
 | 
					    var = cg.new_Pvariable(action_id, template_arg, condition)
 | 
				
			||||||
    if CONF_THEN in config:
 | 
					    if CONF_THEN in config:
 | 
				
			||||||
        actions = await build_action_list(config[CONF_THEN], template_arg, args)
 | 
					        actions = await build_action_list(config[CONF_THEN], template_arg, args)
 | 
				
			||||||
        cg.add(var.add_then(actions))
 | 
					        cg.add(var.add_then(actions))
 | 
				
			||||||
@@ -279,9 +332,14 @@ async def if_action_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
async def while_action_to_code(config, action_id, template_arg, args):
 | 
					async def while_action_to_code(
 | 
				
			||||||
    conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
 | 
					    config: ConfigType,
 | 
				
			||||||
    var = cg.new_Pvariable(action_id, template_arg, conditions)
 | 
					    action_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
 | 
					    condition = await build_condition(config[CONF_CONDITION], template_arg, args)
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(action_id, template_arg, condition)
 | 
				
			||||||
    actions = await build_action_list(config[CONF_THEN], template_arg, args)
 | 
					    actions = await build_action_list(config[CONF_THEN], template_arg, args)
 | 
				
			||||||
    cg.add(var.add_then(actions))
 | 
					    cg.add(var.add_then(actions))
 | 
				
			||||||
    return var
 | 
					    return var
 | 
				
			||||||
@@ -297,7 +355,12 @@ async def while_action_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
async def repeat_action_to_code(config, action_id, template_arg, args):
 | 
					async def repeat_action_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    action_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    var = cg.new_Pvariable(action_id, template_arg)
 | 
					    var = cg.new_Pvariable(action_id, template_arg)
 | 
				
			||||||
    count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
 | 
					    count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
 | 
				
			||||||
    cg.add(var.set_count(count_template))
 | 
					    cg.add(var.set_count(count_template))
 | 
				
			||||||
@@ -320,9 +383,14 @@ _validate_wait_until = cv.maybe_simple_value(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@register_action("wait_until", WaitUntilAction, _validate_wait_until)
 | 
					@register_action("wait_until", WaitUntilAction, _validate_wait_until)
 | 
				
			||||||
async def wait_until_action_to_code(config, action_id, template_arg, args):
 | 
					async def wait_until_action_to_code(
 | 
				
			||||||
    conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
 | 
					    config: ConfigType,
 | 
				
			||||||
    var = cg.new_Pvariable(action_id, template_arg, conditions)
 | 
					    action_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
 | 
					    condition = await build_condition(config[CONF_CONDITION], template_arg, args)
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(action_id, template_arg, condition)
 | 
				
			||||||
    if CONF_TIMEOUT in config:
 | 
					    if CONF_TIMEOUT in config:
 | 
				
			||||||
        template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32)
 | 
					        template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32)
 | 
				
			||||||
        cg.add(var.set_timeout_value(template_))
 | 
					        cg.add(var.set_timeout_value(template_))
 | 
				
			||||||
@@ -331,7 +399,12 @@ async def wait_until_action_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@register_action("lambda", LambdaAction, cv.lambda_)
 | 
					@register_action("lambda", LambdaAction, cv.lambda_)
 | 
				
			||||||
async def lambda_action_to_code(config, action_id, template_arg, args):
 | 
					async def lambda_action_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    action_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
 | 
					    lambda_ = await cg.process_lambda(config, args, return_type=cg.void)
 | 
				
			||||||
    return cg.new_Pvariable(action_id, template_arg, lambda_)
 | 
					    return cg.new_Pvariable(action_id, template_arg, lambda_)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -345,7 +418,12 @@ async def lambda_action_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
async def component_update_action_to_code(config, action_id, template_arg, args):
 | 
					async def component_update_action_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    action_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    comp = await cg.get_variable(config[CONF_ID])
 | 
					    comp = await cg.get_variable(config[CONF_ID])
 | 
				
			||||||
    return cg.new_Pvariable(action_id, template_arg, comp)
 | 
					    return cg.new_Pvariable(action_id, template_arg, comp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -359,7 +437,12 @@ async def component_update_action_to_code(config, action_id, template_arg, args)
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
async def component_suspend_action_to_code(config, action_id, template_arg, args):
 | 
					async def component_suspend_action_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    action_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    comp = await cg.get_variable(config[CONF_ID])
 | 
					    comp = await cg.get_variable(config[CONF_ID])
 | 
				
			||||||
    return cg.new_Pvariable(action_id, template_arg, comp)
 | 
					    return cg.new_Pvariable(action_id, template_arg, comp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -376,7 +459,12 @@ async def component_suspend_action_to_code(config, action_id, template_arg, args
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
async def component_resume_action_to_code(config, action_id, template_arg, args):
 | 
					async def component_resume_action_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    action_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    comp = await cg.get_variable(config[CONF_ID])
 | 
					    comp = await cg.get_variable(config[CONF_ID])
 | 
				
			||||||
    var = cg.new_Pvariable(action_id, template_arg, comp)
 | 
					    var = cg.new_Pvariable(action_id, template_arg, comp)
 | 
				
			||||||
    if CONF_UPDATE_INTERVAL in config:
 | 
					    if CONF_UPDATE_INTERVAL in config:
 | 
				
			||||||
@@ -385,7 +473,9 @@ async def component_resume_action_to_code(config, action_id, template_arg, args)
 | 
				
			|||||||
    return var
 | 
					    return var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def build_action(full_config, template_arg, args):
 | 
					async def build_action(
 | 
				
			||||||
 | 
					    full_config: ConfigType, template_arg: cg.TemplateArguments, args: TemplateArgsType
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    registry_entry, config = cg.extract_registry_entry_config(
 | 
					    registry_entry, config = cg.extract_registry_entry_config(
 | 
				
			||||||
        ACTION_REGISTRY, full_config
 | 
					        ACTION_REGISTRY, full_config
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
@@ -394,15 +484,19 @@ async def build_action(full_config, template_arg, args):
 | 
				
			|||||||
    return await builder(config, action_id, template_arg, args)
 | 
					    return await builder(config, action_id, template_arg, args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def build_action_list(config, templ, arg_type):
 | 
					async def build_action_list(
 | 
				
			||||||
    actions = []
 | 
					    config: list[ConfigType], templ: cg.TemplateArguments, arg_type: TemplateArgsType
 | 
				
			||||||
 | 
					) -> list[MockObj]:
 | 
				
			||||||
 | 
					    actions: list[MockObj] = []
 | 
				
			||||||
    for conf in config:
 | 
					    for conf in config:
 | 
				
			||||||
        action = await build_action(conf, templ, arg_type)
 | 
					        action = await build_action(conf, templ, arg_type)
 | 
				
			||||||
        actions.append(action)
 | 
					        actions.append(action)
 | 
				
			||||||
    return actions
 | 
					    return actions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def build_condition(full_config, template_arg, args):
 | 
					async def build_condition(
 | 
				
			||||||
 | 
					    full_config: ConfigType, template_arg: cg.TemplateArguments, args: TemplateArgsType
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    registry_entry, config = cg.extract_registry_entry_config(
 | 
					    registry_entry, config = cg.extract_registry_entry_config(
 | 
				
			||||||
        CONDITION_REGISTRY, full_config
 | 
					        CONDITION_REGISTRY, full_config
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
@@ -411,15 +505,19 @@ async def build_condition(full_config, template_arg, args):
 | 
				
			|||||||
    return await builder(config, action_id, template_arg, args)
 | 
					    return await builder(config, action_id, template_arg, args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def build_condition_list(config, templ, args):
 | 
					async def build_condition_list(
 | 
				
			||||||
    conditions = []
 | 
					    config: ConfigType, templ: cg.TemplateArguments, args: TemplateArgsType
 | 
				
			||||||
 | 
					) -> list[MockObj]:
 | 
				
			||||||
 | 
					    conditions: list[MockObj] = []
 | 
				
			||||||
    for conf in config:
 | 
					    for conf in config:
 | 
				
			||||||
        condition = await build_condition(conf, templ, args)
 | 
					        condition = await build_condition(conf, templ, args)
 | 
				
			||||||
        conditions.append(condition)
 | 
					        conditions.append(condition)
 | 
				
			||||||
    return conditions
 | 
					    return conditions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def build_automation(trigger, args, config):
 | 
					async def build_automation(
 | 
				
			||||||
 | 
					    trigger: MockObj, args: TemplateArgsType, config: ConfigType
 | 
				
			||||||
 | 
					) -> MockObj:
 | 
				
			||||||
    arg_types = [arg[0] for arg in args]
 | 
					    arg_types = [arg[0] for arg in args]
 | 
				
			||||||
    templ = cg.TemplateArguments(*arg_types)
 | 
					    templ = cg.TemplateArguments(*arg_types)
 | 
				
			||||||
    obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger)
 | 
					    obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,3 @@
 | 
				
			|||||||
import os
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from esphome.const import __version__
 | 
					from esphome.const import __version__
 | 
				
			||||||
from esphome.core import CORE
 | 
					from esphome.core import CORE
 | 
				
			||||||
from esphome.helpers import mkdir_p, read_file, write_file_if_changed
 | 
					from esphome.helpers import mkdir_p, read_file, write_file_if_changed
 | 
				
			||||||
@@ -63,7 +61,7 @@ def write_ini(content):
 | 
				
			|||||||
    update_storage_json()
 | 
					    update_storage_json()
 | 
				
			||||||
    path = CORE.relative_build_path("platformio.ini")
 | 
					    path = CORE.relative_build_path("platformio.ini")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if os.path.isfile(path):
 | 
					    if path.is_file():
 | 
				
			||||||
        text = read_file(path)
 | 
					        text = read_file(path)
 | 
				
			||||||
        content_format = find_begin_end(
 | 
					        content_format = find_begin_end(
 | 
				
			||||||
            text, INI_AUTO_GENERATE_BEGIN, INI_AUTO_GENERATE_END
 | 
					            text, INI_AUTO_GENERATE_BEGIN, INI_AUTO_GENERATE_END
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ from esphome.cpp_generator import (  # noqa: F401
 | 
				
			|||||||
    ArrayInitializer,
 | 
					    ArrayInitializer,
 | 
				
			||||||
    Expression,
 | 
					    Expression,
 | 
				
			||||||
    LineComment,
 | 
					    LineComment,
 | 
				
			||||||
 | 
					    LogStringLiteral,
 | 
				
			||||||
    MockObj,
 | 
					    MockObj,
 | 
				
			||||||
    MockObjClass,
 | 
					    MockObjClass,
 | 
				
			||||||
    Pvariable,
 | 
					    Pvariable,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,11 +61,10 @@ void AbsoluteHumidityComponent::loop() {
 | 
				
			|||||||
      ESP_LOGW(TAG, "No valid state from temperature sensor!");
 | 
					      ESP_LOGW(TAG, "No valid state from temperature sensor!");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (no_humidity) {
 | 
					    if (no_humidity) {
 | 
				
			||||||
      ESP_LOGW(TAG, "No valid state from temperature sensor!");
 | 
					      ESP_LOGW(TAG, "No valid state from humidity sensor!");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    ESP_LOGW(TAG, "Unable to calculate absolute humidity.");
 | 
					 | 
				
			||||||
    this->publish_state(NAN);
 | 
					    this->publish_state(NAN);
 | 
				
			||||||
    this->status_set_warning();
 | 
					    this->status_set_warning(LOG_STR("Unable to calculate absolute humidity."));
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -87,9 +86,8 @@ void AbsoluteHumidityComponent::loop() {
 | 
				
			|||||||
      es = es_wobus(temperature_c);
 | 
					      es = es_wobus(temperature_c);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
 | 
					 | 
				
			||||||
      this->publish_state(NAN);
 | 
					      this->publish_state(NAN);
 | 
				
			||||||
      this->status_set_error();
 | 
					      this->status_set_error("Invalid saturation vapor pressure equation selection!");
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
 | 
					  ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,15 +11,8 @@ from esphome.components.esp32.const import (
 | 
				
			|||||||
    VARIANT_ESP32S2,
 | 
					    VARIANT_ESP32S2,
 | 
				
			||||||
    VARIANT_ESP32S3,
 | 
					    VARIANT_ESP32S3,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.config_helpers import filter_source_files_from_platform
 | 
					 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
 | 
				
			||||||
    CONF_ANALOG,
 | 
					 | 
				
			||||||
    CONF_INPUT,
 | 
					 | 
				
			||||||
    CONF_NUMBER,
 | 
					 | 
				
			||||||
    PLATFORM_ESP8266,
 | 
					 | 
				
			||||||
    PlatformFramework,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from esphome.core import CORE
 | 
					from esphome.core import CORE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@esphome/core"]
 | 
					CODEOWNERS = ["@esphome/core"]
 | 
				
			||||||
@@ -273,21 +266,3 @@ def validate_adc_pin(value):
 | 
				
			|||||||
        )(value)
 | 
					        )(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    raise NotImplementedError
 | 
					    raise NotImplementedError
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        "adc_sensor_esp32.cpp": {
 | 
					 | 
				
			||||||
            PlatformFramework.ESP32_ARDUINO,
 | 
					 | 
				
			||||||
            PlatformFramework.ESP32_IDF,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
 | 
					 | 
				
			||||||
        "adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
 | 
					 | 
				
			||||||
        "adc_sensor_libretiny.cpp": {
 | 
					 | 
				
			||||||
            PlatformFramework.BK72XX_ARDUINO,
 | 
					 | 
				
			||||||
            PlatformFramework.RTL87XX_ARDUINO,
 | 
					 | 
				
			||||||
            PlatformFramework.LN882X_ARDUINO,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "adc_sensor_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -241,6 +241,8 @@ float ADCSensor::sample_autorange_() {
 | 
				
			|||||||
    cali_config.bitwidth = ADC_BITWIDTH_DEFAULT;
 | 
					    cali_config.bitwidth = ADC_BITWIDTH_DEFAULT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
 | 
					    err = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
 | 
				
			||||||
 | 
					    ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
 | 
				
			||||||
 | 
					              (err == ESP_OK) ? "SUCCESS" : "FAILED", err);
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
    adc_cali_line_fitting_config_t cali_config = {
 | 
					    adc_cali_line_fitting_config_t cali_config = {
 | 
				
			||||||
      .unit_id = this->adc_unit_,
 | 
					      .unit_id = this->adc_unit_,
 | 
				
			||||||
@@ -251,10 +253,14 @@ float ADCSensor::sample_autorange_() {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
 | 
					    err = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
 | 
				
			||||||
 | 
					    ESP_LOGVV(TAG, "Autorange atten=%d: Calibration handle creation %s (err=%d)", atten,
 | 
				
			||||||
 | 
					              (err == ESP_OK) ? "SUCCESS" : "FAILED", err);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int raw;
 | 
					    int raw;
 | 
				
			||||||
    err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw);
 | 
					    err = adc_oneshot_read(this->adc_handle_, this->channel_, &raw);
 | 
				
			||||||
 | 
					    ESP_LOGVV(TAG, "Autorange atten=%d: Raw ADC read %s, value=%d (err=%d)", atten,
 | 
				
			||||||
 | 
					              (err == ESP_OK) ? "SUCCESS" : "FAILED", raw, err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (err != ESP_OK) {
 | 
					    if (err != ESP_OK) {
 | 
				
			||||||
      ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
 | 
					      ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
 | 
				
			||||||
@@ -275,8 +281,10 @@ float ADCSensor::sample_autorange_() {
 | 
				
			|||||||
      err = adc_cali_raw_to_voltage(handle, raw, &voltage_mv);
 | 
					      err = adc_cali_raw_to_voltage(handle, raw, &voltage_mv);
 | 
				
			||||||
      if (err == ESP_OK) {
 | 
					      if (err == ESP_OK) {
 | 
				
			||||||
        voltage = voltage_mv / 1000.0f;
 | 
					        voltage = voltage_mv / 1000.0f;
 | 
				
			||||||
 | 
					        ESP_LOGVV(TAG, "Autorange atten=%d: CALIBRATED - raw=%d -> %dmV -> %.6fV", atten, raw, voltage_mv, voltage);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        voltage = raw * 3.3f / 4095.0f;
 | 
					        voltage = raw * 3.3f / 4095.0f;
 | 
				
			||||||
 | 
					        ESP_LOGVV(TAG, "Autorange atten=%d: UNCALIBRATED FALLBACK - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // Clean up calibration handle
 | 
					      // Clean up calibration handle
 | 
				
			||||||
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
 | 
					#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
 | 
				
			||||||
@@ -287,6 +295,7 @@ float ADCSensor::sample_autorange_() {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      voltage = raw * 3.3f / 4095.0f;
 | 
					      voltage = raw * 3.3f / 4095.0f;
 | 
				
			||||||
 | 
					      ESP_LOGVV(TAG, "Autorange atten=%d: NO CALIBRATION - raw=%d -> %.6fV (3.3V ref)", atten, raw, voltage);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {raw, voltage};
 | 
					    return {raw, voltage};
 | 
				
			||||||
@@ -324,18 +333,32 @@ float ADCSensor::sample_autorange_() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const int adc_half = 2048;
 | 
					  const int adc_half = 2048;
 | 
				
			||||||
  uint32_t c12 = std::min(raw12, adc_half);
 | 
					  const uint32_t c12 = std::min(raw12, adc_half);
 | 
				
			||||||
  uint32_t c6 = adc_half - std::abs(raw6 - adc_half);
 | 
					
 | 
				
			||||||
  uint32_t c2 = adc_half - std::abs(raw2 - adc_half);
 | 
					  const int32_t c6_signed = adc_half - std::abs(raw6 - adc_half);
 | 
				
			||||||
  uint32_t c0 = std::min(4095 - raw0, adc_half);
 | 
					  const uint32_t c6 = (c6_signed > 0) ? c6_signed : 0;  // Clamp to prevent underflow
 | 
				
			||||||
  uint32_t csum = c12 + c6 + c2 + c0;
 | 
					
 | 
				
			||||||
 | 
					  const int32_t c2_signed = adc_half - std::abs(raw2 - adc_half);
 | 
				
			||||||
 | 
					  const uint32_t c2 = (c2_signed > 0) ? c2_signed : 0;  // Clamp to prevent underflow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const uint32_t c0 = std::min(4095 - raw0, adc_half);
 | 
				
			||||||
 | 
					  const uint32_t csum = c12 + c6 + c2 + c0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESP_LOGVV(TAG, "Autorange summary:");
 | 
				
			||||||
 | 
					  ESP_LOGVV(TAG, "  Raw readings: 12db=%d, 6db=%d, 2.5db=%d, 0db=%d", raw12, raw6, raw2, raw0);
 | 
				
			||||||
 | 
					  ESP_LOGVV(TAG, "  Voltages: 12db=%.6f, 6db=%.6f, 2.5db=%.6f, 0db=%.6f", mv12, mv6, mv2, mv0);
 | 
				
			||||||
 | 
					  ESP_LOGVV(TAG, "  Coefficients: c12=%u, c6=%u, c2=%u, c0=%u, sum=%u", c12, c6, c2, c0, csum);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (csum == 0) {
 | 
					  if (csum == 0) {
 | 
				
			||||||
    ESP_LOGE(TAG, "Invalid weight sum in autorange calculation");
 | 
					    ESP_LOGE(TAG, "Invalid weight sum in autorange calculation");
 | 
				
			||||||
    return NAN;
 | 
					    return NAN;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
 | 
					  const float final_result = (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "Autorange final: (%.6f*%u + %.6f*%u + %.6f*%u + %.6f*%u)/%u = %.6fV", mv12, c12, mv6, c6, mv2, c2, mv0,
 | 
				
			||||||
 | 
					           c0, csum, final_result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return final_result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace adc
 | 
					}  // namespace adc
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ from esphome.components.zephyr import (
 | 
				
			|||||||
    zephyr_add_prj_conf,
 | 
					    zephyr_add_prj_conf,
 | 
				
			||||||
    zephyr_add_user,
 | 
					    zephyr_add_user,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from esphome.config_helpers import filter_source_files_from_platform
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_ATTENUATION,
 | 
					    CONF_ATTENUATION,
 | 
				
			||||||
@@ -20,6 +21,7 @@ from esphome.const import (
 | 
				
			|||||||
    PLATFORM_NRF52,
 | 
					    PLATFORM_NRF52,
 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_VOLT,
 | 
					    UNIT_VOLT,
 | 
				
			||||||
 | 
					    PlatformFramework,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.core import CORE
 | 
					from esphome.core import CORE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -174,3 +176,21 @@ async def to_code(config):
 | 
				
			|||||||
}};
 | 
					}};
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FILTER_SOURCE_FILES = filter_source_files_from_platform(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        "adc_sensor_esp32.cpp": {
 | 
				
			||||||
 | 
					            PlatformFramework.ESP32_ARDUINO,
 | 
				
			||||||
 | 
					            PlatformFramework.ESP32_IDF,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
 | 
				
			||||||
 | 
					        "adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
 | 
				
			||||||
 | 
					        "adc_sensor_libretiny.cpp": {
 | 
				
			||||||
 | 
					            PlatformFramework.BK72XX_ARDUINO,
 | 
				
			||||||
 | 
					            PlatformFramework.RTL87XX_ARDUINO,
 | 
				
			||||||
 | 
					            PlatformFramework.LN882X_ARDUINO,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "adc_sensor_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -113,7 +113,7 @@ void ADE7880::update() {
 | 
				
			|||||||
  if (this->channel_a_ != nullptr) {
 | 
					  if (this->channel_a_ != nullptr) {
 | 
				
			||||||
    auto *chan = this->channel_a_;
 | 
					    auto *chan = this->channel_a_;
 | 
				
			||||||
    this->update_sensor_from_s24zp_register16_(chan->current, AIRMS, [](float val) { return val / 100000.0f; });
 | 
					    this->update_sensor_from_s24zp_register16_(chan->current, AIRMS, [](float val) { return val / 100000.0f; });
 | 
				
			||||||
    this->update_sensor_from_s24zp_register16_(chan->voltage, BVRMS, [](float val) { return val / 10000.0f; });
 | 
					    this->update_sensor_from_s24zp_register16_(chan->voltage, AVRMS, [](float val) { return val / 10000.0f; });
 | 
				
			||||||
    this->update_sensor_from_s24zp_register16_(chan->active_power, AWATT, [](float val) { return val / 100.0f; });
 | 
					    this->update_sensor_from_s24zp_register16_(chan->active_power, AWATT, [](float val) { return val / 100.0f; });
 | 
				
			||||||
    this->update_sensor_from_s24zp_register16_(chan->apparent_power, AVA, [](float val) { return val / 100.0f; });
 | 
					    this->update_sensor_from_s24zp_register16_(chan->apparent_power, AVA, [](float val) { return val / 100.0f; });
 | 
				
			||||||
    this->update_sensor_from_s16_register16_(chan->power_factor, APF,
 | 
					    this->update_sensor_from_s16_register16_(chan->power_factor, APF,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -89,7 +89,7 @@ void AGS10Component::dump_config() {
 | 
				
			|||||||
bool AGS10Component::new_i2c_address(uint8_t newaddress) {
 | 
					bool AGS10Component::new_i2c_address(uint8_t newaddress) {
 | 
				
			||||||
  uint8_t rev_newaddress = ~newaddress;
 | 
					  uint8_t rev_newaddress = ~newaddress;
 | 
				
			||||||
  std::array<uint8_t, 5> data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0};
 | 
					  std::array<uint8_t, 5> data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0};
 | 
				
			||||||
  data[4] = calc_crc8_(data, 4);
 | 
					  data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
 | 
				
			||||||
  if (!this->write_bytes(REG_ADDRESS, data)) {
 | 
					  if (!this->write_bytes(REG_ADDRESS, data)) {
 | 
				
			||||||
    this->error_code_ = COMMUNICATION_FAILED;
 | 
					    this->error_code_ = COMMUNICATION_FAILED;
 | 
				
			||||||
    this->status_set_warning();
 | 
					    this->status_set_warning();
 | 
				
			||||||
@@ -109,7 +109,7 @@ bool AGS10Component::set_zero_point_with_current_resistance() { return this->set
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
bool AGS10Component::set_zero_point_with(uint16_t value) {
 | 
					bool AGS10Component::set_zero_point_with(uint16_t value) {
 | 
				
			||||||
  std::array<uint8_t, 5> data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0};
 | 
					  std::array<uint8_t, 5> data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0};
 | 
				
			||||||
  data[4] = calc_crc8_(data, 4);
 | 
					  data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
 | 
				
			||||||
  if (!this->write_bytes(REG_CALIBRATION, data)) {
 | 
					  if (!this->write_bytes(REG_CALIBRATION, data)) {
 | 
				
			||||||
    this->error_code_ = COMMUNICATION_FAILED;
 | 
					    this->error_code_ = COMMUNICATION_FAILED;
 | 
				
			||||||
    this->status_set_warning();
 | 
					    this->status_set_warning();
 | 
				
			||||||
@@ -184,7 +184,7 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
 | 
				
			|||||||
  auto res = *data;
 | 
					  auto res = *data;
 | 
				
			||||||
  auto crc_byte = res[len];
 | 
					  auto crc_byte = res[len];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (crc_byte != calc_crc8_(res, len)) {
 | 
					  if (crc_byte != crc8(res.data(), len, 0xFF, 0x31, true)) {
 | 
				
			||||||
    this->error_code_ = CRC_CHECK_FAILED;
 | 
					    this->error_code_ = CRC_CHECK_FAILED;
 | 
				
			||||||
    ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!");
 | 
					    ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!");
 | 
				
			||||||
    return optional<std::array<uint8_t, N>>();
 | 
					    return optional<std::array<uint8_t, N>>();
 | 
				
			||||||
@@ -192,20 +192,5 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return data;
 | 
					  return data;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
template<size_t N> uint8_t AGS10Component::calc_crc8_(std::array<uint8_t, N> dat, uint8_t num) {
 | 
					 | 
				
			||||||
  uint8_t i, byte1, crc = 0xFF;
 | 
					 | 
				
			||||||
  for (byte1 = 0; byte1 < num; byte1++) {
 | 
					 | 
				
			||||||
    crc ^= (dat[byte1]);
 | 
					 | 
				
			||||||
    for (i = 0; i < 8; i++) {
 | 
					 | 
				
			||||||
      if (crc & 0x80) {
 | 
					 | 
				
			||||||
        crc = (crc << 1) ^ 0x31;
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        crc = (crc << 1);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return crc;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
}  // namespace ags10
 | 
					}  // namespace ags10
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/components/i2c/i2c.h"
 | 
				
			||||||
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
#include "esphome/core/automation.h"
 | 
					#include "esphome/core/automation.h"
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
#include "esphome/components/sensor/sensor.h"
 | 
					 | 
				
			||||||
#include "esphome/components/i2c/i2c.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace ags10 {
 | 
					namespace ags10 {
 | 
				
			||||||
@@ -99,16 +99,6 @@ class AGS10Component : public PollingComponent, public i2c::I2CDevice {
 | 
				
			|||||||
   * Read, checks and returns data from the sensor.
 | 
					   * Read, checks and returns data from the sensor.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  template<size_t N> optional<std::array<uint8_t, N>> read_and_check_(uint8_t a_register);
 | 
					  template<size_t N> optional<std::array<uint8_t, N>> read_and_check_(uint8_t a_register);
 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Calculates CRC8 value.
 | 
					 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   * CRC8 calculation, initial value: 0xFF, polynomial: 0x31 (x8+ x5+ x4+1)
 | 
					 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   * @param[in] dat the data buffer
 | 
					 | 
				
			||||||
   * @param num number of bytes in the buffer
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  template<size_t N> uint8_t calc_crc8_(std::array<uint8_t, N> dat, uint8_t num);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> {
 | 
					template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -96,7 +96,7 @@ void AHT10Component::read_data_() {
 | 
				
			|||||||
    ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
 | 
					    ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->read(data, 6) != i2c::ERROR_OK) {
 | 
					  if (this->read(data, 6) != i2c::ERROR_OK) {
 | 
				
			||||||
    this->status_set_warning("Read failed, will retry");
 | 
					    this->status_set_warning(LOG_STR("Read failed, will retry"));
 | 
				
			||||||
    this->restart_read_();
 | 
					    this->restart_read_();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -113,7 +113,7 @@ void AHT10Component::read_data_() {
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      ESP_LOGD(TAG, "Invalid humidity, retrying");
 | 
					      ESP_LOGD(TAG, "Invalid humidity, retrying");
 | 
				
			||||||
      if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
 | 
					      if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
 | 
				
			||||||
        this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
 | 
					        this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this->restart_read_();
 | 
					      this->restart_read_();
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
@@ -144,7 +144,7 @@ void AHT10Component::update() {
 | 
				
			|||||||
    return;
 | 
					    return;
 | 
				
			||||||
  this->start_time_ = millis();
 | 
					  this->start_time_ = millis();
 | 
				
			||||||
  if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
 | 
					  if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
 | 
				
			||||||
    this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
 | 
					    this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->restart_read_();
 | 
					  this->restart_read_();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,6 @@ CONFIG_SCHEMA = cv.Schema(
 | 
				
			|||||||
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
 | 
					).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
    yield esp32_ble_tracker.register_ble_device(var, config)
 | 
					    await esp32_ble_tracker.register_ble_device(var, config)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_TRIGGER_ID,
 | 
					    CONF_TRIGGER_ID,
 | 
				
			||||||
    CONF_WEB_SERVER,
 | 
					    CONF_WEB_SERVER,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.core import CORE, coroutine_with_priority
 | 
					from esphome.core import CORE, CoroPriority, coroutine_with_priority
 | 
				
			||||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
 | 
					from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
 | 
				
			||||||
from esphome.cpp_generator import MockObjClass
 | 
					from esphome.cpp_generator import MockObjClass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -345,6 +345,6 @@ async def alarm_control_panel_is_armed_to_code(
 | 
				
			|||||||
    return cg.new_Pvariable(condition_id, template_arg, paren)
 | 
					    return cg.new_Pvariable(condition_id, template_arg, paren)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@coroutine_with_priority(100.0)
 | 
					@coroutine_with_priority(CoroPriority.CORE)
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    cg.add_global(alarm_control_panel_ns.using)
 | 
					    cg.add_global(alarm_control_panel_ns.using)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,22 +29,6 @@ namespace am2315c {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static const char *const TAG = "am2315c";
 | 
					static const char *const TAG = "am2315c";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint8_t AM2315C::crc8_(uint8_t *data, uint8_t len) {
 | 
					 | 
				
			||||||
  uint8_t crc = 0xFF;
 | 
					 | 
				
			||||||
  while (len--) {
 | 
					 | 
				
			||||||
    crc ^= *data++;
 | 
					 | 
				
			||||||
    for (uint8_t i = 0; i < 8; i++) {
 | 
					 | 
				
			||||||
      if (crc & 0x80) {
 | 
					 | 
				
			||||||
        crc <<= 1;
 | 
					 | 
				
			||||||
        crc ^= 0x31;
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        crc <<= 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return crc;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool AM2315C::reset_register_(uint8_t reg) {
 | 
					bool AM2315C::reset_register_(uint8_t reg) {
 | 
				
			||||||
  //  code based on demo code sent by www.aosong.com
 | 
					  //  code based on demo code sent by www.aosong.com
 | 
				
			||||||
  //  no further documentation.
 | 
					  //  no further documentation.
 | 
				
			||||||
@@ -86,7 +70,7 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
 | 
				
			|||||||
  humidity = raw * 9.5367431640625e-5;
 | 
					  humidity = raw * 9.5367431640625e-5;
 | 
				
			||||||
  raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
 | 
					  raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
 | 
				
			||||||
  temperature = raw * 1.9073486328125e-4 - 50;
 | 
					  temperature = raw * 1.9073486328125e-4 - 50;
 | 
				
			||||||
  return this->crc8_(data, 6) == data[6];
 | 
					  return crc8(data, 6, 0xFF, 0x31, true) == data[6];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AM2315C::setup() {
 | 
					void AM2315C::setup() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,9 +21,9 @@
 | 
				
			|||||||
// SOFTWARE.
 | 
					// SOFTWARE.
 | 
				
			||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					 | 
				
			||||||
#include "esphome/components/sensor/sensor.h"
 | 
					 | 
				
			||||||
#include "esphome/components/i2c/i2c.h"
 | 
					#include "esphome/components/i2c/i2c.h"
 | 
				
			||||||
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace am2315c {
 | 
					namespace am2315c {
 | 
				
			||||||
@@ -39,7 +39,6 @@ class AM2315C : public PollingComponent, public i2c::I2CDevice {
 | 
				
			|||||||
  void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
 | 
					  void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  uint8_t crc8_(uint8_t *data, uint8_t len);
 | 
					 | 
				
			||||||
  bool convert_(uint8_t *data, float &humidity, float &temperature);
 | 
					  bool convert_(uint8_t *data, float &humidity, float &temperature);
 | 
				
			||||||
  bool reset_register_(uint8_t reg);
 | 
					  bool reset_register_(uint8_t reg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,12 +26,12 @@ uint32_t Animation::get_animation_frame_count() const { return this->animation_f
 | 
				
			|||||||
int Animation::get_current_frame() const { return this->current_frame_; }
 | 
					int Animation::get_current_frame() const { return this->current_frame_; }
 | 
				
			||||||
void Animation::next_frame() {
 | 
					void Animation::next_frame() {
 | 
				
			||||||
  this->current_frame_++;
 | 
					  this->current_frame_++;
 | 
				
			||||||
  if (loop_count_ && this->current_frame_ == loop_end_frame_ &&
 | 
					  if (loop_count_ && static_cast<uint32_t>(this->current_frame_) == loop_end_frame_ &&
 | 
				
			||||||
      (this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) {
 | 
					      (this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) {
 | 
				
			||||||
    this->current_frame_ = loop_start_frame_;
 | 
					    this->current_frame_ = loop_start_frame_;
 | 
				
			||||||
    this->loop_current_iteration_++;
 | 
					    this->loop_current_iteration_++;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->current_frame_ >= animation_frame_count_) {
 | 
					  if (static_cast<uint32_t>(this->current_frame_) >= animation_frame_count_) {
 | 
				
			||||||
    this->loop_current_iteration_ = 1;
 | 
					    this->loop_current_iteration_ = 1;
 | 
				
			||||||
    this->current_frame_ = 0;
 | 
					    this->current_frame_ = 0;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import base64
 | 
					import base64
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from esphome import automation
 | 
					from esphome import automation
 | 
				
			||||||
from esphome.automation import Condition
 | 
					from esphome.automation import Condition
 | 
				
			||||||
@@ -8,34 +9,59 @@ import esphome.config_validation as cv
 | 
				
			|||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_ACTION,
 | 
					    CONF_ACTION,
 | 
				
			||||||
    CONF_ACTIONS,
 | 
					    CONF_ACTIONS,
 | 
				
			||||||
 | 
					    CONF_CAPTURE_RESPONSE,
 | 
				
			||||||
    CONF_DATA,
 | 
					    CONF_DATA,
 | 
				
			||||||
    CONF_DATA_TEMPLATE,
 | 
					    CONF_DATA_TEMPLATE,
 | 
				
			||||||
    CONF_EVENT,
 | 
					    CONF_EVENT,
 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
    CONF_KEY,
 | 
					    CONF_KEY,
 | 
				
			||||||
 | 
					    CONF_MAX_CONNECTIONS,
 | 
				
			||||||
    CONF_ON_CLIENT_CONNECTED,
 | 
					    CONF_ON_CLIENT_CONNECTED,
 | 
				
			||||||
    CONF_ON_CLIENT_DISCONNECTED,
 | 
					    CONF_ON_CLIENT_DISCONNECTED,
 | 
				
			||||||
 | 
					    CONF_ON_ERROR,
 | 
				
			||||||
 | 
					    CONF_ON_SUCCESS,
 | 
				
			||||||
    CONF_PASSWORD,
 | 
					    CONF_PASSWORD,
 | 
				
			||||||
    CONF_PORT,
 | 
					    CONF_PORT,
 | 
				
			||||||
    CONF_REBOOT_TIMEOUT,
 | 
					    CONF_REBOOT_TIMEOUT,
 | 
				
			||||||
 | 
					    CONF_RESPONSE_TEMPLATE,
 | 
				
			||||||
    CONF_SERVICE,
 | 
					    CONF_SERVICE,
 | 
				
			||||||
    CONF_SERVICES,
 | 
					    CONF_SERVICES,
 | 
				
			||||||
    CONF_TAG,
 | 
					    CONF_TAG,
 | 
				
			||||||
    CONF_TRIGGER_ID,
 | 
					    CONF_TRIGGER_ID,
 | 
				
			||||||
    CONF_VARIABLES,
 | 
					    CONF_VARIABLES,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.core import CORE, coroutine_with_priority
 | 
					from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
 | 
				
			||||||
 | 
					from esphome.cpp_generator import TemplateArgsType
 | 
				
			||||||
 | 
					from esphome.types import ConfigType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_LOGGER = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DOMAIN = "api"
 | 
					DOMAIN = "api"
 | 
				
			||||||
DEPENDENCIES = ["network"]
 | 
					DEPENDENCIES = ["network"]
 | 
				
			||||||
AUTO_LOAD = ["socket"]
 | 
					 | 
				
			||||||
CODEOWNERS = ["@esphome/core"]
 | 
					CODEOWNERS = ["@esphome/core"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def AUTO_LOAD(config: ConfigType) -> list[str]:
 | 
				
			||||||
 | 
					    """Conditionally auto-load json only when capture_response is used."""
 | 
				
			||||||
 | 
					    base = ["socket"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Check if any homeassistant.action/homeassistant.service has capture_response: true
 | 
				
			||||||
 | 
					    # This flag is set during config validation in _validate_response_config
 | 
				
			||||||
 | 
					    if not config or CORE.data.get(DOMAIN, {}).get(CONF_CAPTURE_RESPONSE, False):
 | 
				
			||||||
 | 
					        return base + ["json"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
api_ns = cg.esphome_ns.namespace("api")
 | 
					api_ns = cg.esphome_ns.namespace("api")
 | 
				
			||||||
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
 | 
					APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
 | 
				
			||||||
HomeAssistantServiceCallAction = api_ns.class_(
 | 
					HomeAssistantServiceCallAction = api_ns.class_(
 | 
				
			||||||
    "HomeAssistantServiceCallAction", automation.Action
 | 
					    "HomeAssistantServiceCallAction", automation.Action
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					ActionResponse = api_ns.class_("ActionResponse")
 | 
				
			||||||
 | 
					HomeAssistantActionResponseTrigger = api_ns.class_(
 | 
				
			||||||
 | 
					    "HomeAssistantActionResponseTrigger", automation.Trigger
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
 | 
					APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
 | 
					UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
 | 
				
			||||||
@@ -55,6 +81,8 @@ CONF_BATCH_DELAY = "batch_delay"
 | 
				
			|||||||
CONF_CUSTOM_SERVICES = "custom_services"
 | 
					CONF_CUSTOM_SERVICES = "custom_services"
 | 
				
			||||||
CONF_HOMEASSISTANT_SERVICES = "homeassistant_services"
 | 
					CONF_HOMEASSISTANT_SERVICES = "homeassistant_services"
 | 
				
			||||||
CONF_HOMEASSISTANT_STATES = "homeassistant_states"
 | 
					CONF_HOMEASSISTANT_STATES = "homeassistant_states"
 | 
				
			||||||
 | 
					CONF_LISTEN_BACKLOG = "listen_backlog"
 | 
				
			||||||
 | 
					CONF_MAX_SEND_QUEUE = "max_send_queue"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def validate_encryption_key(value):
 | 
					def validate_encryption_key(value):
 | 
				
			||||||
@@ -101,6 +129,32 @@ def _encryption_schema(config):
 | 
				
			|||||||
    return ENCRYPTION_SCHEMA(config)
 | 
					    return ENCRYPTION_SCHEMA(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _validate_api_config(config: ConfigType) -> ConfigType:
 | 
				
			||||||
 | 
					    """Validate API configuration with mutual exclusivity check and deprecation warning."""
 | 
				
			||||||
 | 
					    # Check if both password and encryption are configured
 | 
				
			||||||
 | 
					    has_password = CONF_PASSWORD in config and config[CONF_PASSWORD]
 | 
				
			||||||
 | 
					    has_encryption = CONF_ENCRYPTION in config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if has_password and has_encryption:
 | 
				
			||||||
 | 
					        raise cv.Invalid(
 | 
				
			||||||
 | 
					            "The 'password' and 'encryption' options are mutually exclusive. "
 | 
				
			||||||
 | 
					            "The API client only supports one authentication method at a time. "
 | 
				
			||||||
 | 
					            "Please remove one of them. "
 | 
				
			||||||
 | 
					            "Note: 'password' authentication is deprecated and will be removed in version 2026.1.0. "
 | 
				
			||||||
 | 
					            "We strongly recommend using 'encryption' instead for better security."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Warn about password deprecation
 | 
				
			||||||
 | 
					    if has_password:
 | 
				
			||||||
 | 
					        _LOGGER.warning(
 | 
				
			||||||
 | 
					            "API 'password' authentication has been deprecated since May 2022 and will be removed in version 2026.1.0. "
 | 
				
			||||||
 | 
					            "Please migrate to the 'encryption' configuration. "
 | 
				
			||||||
 | 
					            "See https://esphome.io/components/api.html#configuration-variables"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = cv.All(
 | 
					CONFIG_SCHEMA = cv.All(
 | 
				
			||||||
    cv.Schema(
 | 
					    cv.Schema(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -128,13 +182,50 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
            cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation(
 | 
					            cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation(
 | 
				
			||||||
                single=True
 | 
					                single=True
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
 | 
					            # Connection limits to prevent memory exhaustion on resource-constrained devices
 | 
				
			||||||
 | 
					            # Each connection uses ~500-1000 bytes of RAM plus system resources
 | 
				
			||||||
 | 
					            # Platform defaults based on available RAM and network stack implementation:
 | 
				
			||||||
 | 
					            cv.SplitDefault(
 | 
				
			||||||
 | 
					                CONF_LISTEN_BACKLOG,
 | 
				
			||||||
 | 
					                esp8266=1,  # Limited RAM (~40KB free), LWIP raw sockets
 | 
				
			||||||
 | 
					                esp32=4,  # More RAM (520KB), BSD sockets
 | 
				
			||||||
 | 
					                rp2040=1,  # Limited RAM (264KB), LWIP raw sockets like ESP8266
 | 
				
			||||||
 | 
					                bk72xx=4,  # Moderate RAM, BSD-style sockets
 | 
				
			||||||
 | 
					                rtl87xx=4,  # Moderate RAM, BSD-style sockets
 | 
				
			||||||
 | 
					                host=4,  # Abundant resources
 | 
				
			||||||
 | 
					                ln882x=4,  # Moderate RAM
 | 
				
			||||||
 | 
					            ): cv.int_range(min=1, max=10),
 | 
				
			||||||
 | 
					            cv.SplitDefault(
 | 
				
			||||||
 | 
					                CONF_MAX_CONNECTIONS,
 | 
				
			||||||
 | 
					                esp8266=4,  # ~40KB free RAM, each connection uses ~500-1000 bytes
 | 
				
			||||||
 | 
					                esp32=8,  # 520KB RAM available
 | 
				
			||||||
 | 
					                rp2040=4,  # 264KB RAM but LWIP constraints
 | 
				
			||||||
 | 
					                bk72xx=8,  # Moderate RAM
 | 
				
			||||||
 | 
					                rtl87xx=8,  # Moderate RAM
 | 
				
			||||||
 | 
					                host=8,  # Abundant resources
 | 
				
			||||||
 | 
					                ln882x=8,  # Moderate RAM
 | 
				
			||||||
 | 
					            ): cv.int_range(min=1, max=20),
 | 
				
			||||||
 | 
					            # Maximum queued send buffers per connection before dropping connection
 | 
				
			||||||
 | 
					            # Each buffer uses ~8-12 bytes overhead plus actual message size
 | 
				
			||||||
 | 
					            # Platform defaults based on available RAM and typical message rates:
 | 
				
			||||||
 | 
					            cv.SplitDefault(
 | 
				
			||||||
 | 
					                CONF_MAX_SEND_QUEUE,
 | 
				
			||||||
 | 
					                esp8266=5,  # Limited RAM, need to fail fast
 | 
				
			||||||
 | 
					                esp32=8,  # More RAM, can buffer more
 | 
				
			||||||
 | 
					                rp2040=5,  # Limited RAM
 | 
				
			||||||
 | 
					                bk72xx=8,  # Moderate RAM
 | 
				
			||||||
 | 
					                rtl87xx=8,  # Moderate RAM
 | 
				
			||||||
 | 
					                host=16,  # Abundant resources
 | 
				
			||||||
 | 
					                ln882x=8,  # Moderate RAM
 | 
				
			||||||
 | 
					            ): cv.int_range(min=1, max=64),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
					    ).extend(cv.COMPONENT_SCHEMA),
 | 
				
			||||||
    cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
 | 
					    cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
 | 
				
			||||||
 | 
					    _validate_api_config,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@coroutine_with_priority(40.0)
 | 
					@coroutine_with_priority(CoroPriority.WEB)
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
    await cg.register_component(var, config)
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
@@ -145,6 +236,11 @@ async def to_code(config):
 | 
				
			|||||||
        cg.add(var.set_password(config[CONF_PASSWORD]))
 | 
					        cg.add(var.set_password(config[CONF_PASSWORD]))
 | 
				
			||||||
    cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
 | 
					    cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
 | 
				
			||||||
    cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
 | 
					    cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
 | 
				
			||||||
 | 
					    if CONF_LISTEN_BACKLOG in config:
 | 
				
			||||||
 | 
					        cg.add(var.set_listen_backlog(config[CONF_LISTEN_BACKLOG]))
 | 
				
			||||||
 | 
					    if CONF_MAX_CONNECTIONS in config:
 | 
				
			||||||
 | 
					        cg.add(var.set_max_connections(config[CONF_MAX_CONNECTIONS]))
 | 
				
			||||||
 | 
					    cg.add_define("API_MAX_SEND_QUEUE", config[CONF_MAX_SEND_QUEUE])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Set USE_API_SERVICES if any services are enabled
 | 
					    # Set USE_API_SERVICES if any services are enabled
 | 
				
			||||||
    if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
 | 
					    if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
 | 
				
			||||||
@@ -193,6 +289,7 @@ async def to_code(config):
 | 
				
			|||||||
        if key := encryption_config.get(CONF_KEY):
 | 
					        if key := encryption_config.get(CONF_KEY):
 | 
				
			||||||
            decoded = base64.b64decode(key)
 | 
					            decoded = base64.b64decode(key)
 | 
				
			||||||
            cg.add(var.set_noise_psk(list(decoded)))
 | 
					            cg.add(var.set_noise_psk(list(decoded)))
 | 
				
			||||||
 | 
					            cg.add_define("USE_API_NOISE_PSK_FROM_YAML")
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            # No key provided, but encryption desired
 | 
					            # No key provided, but encryption desired
 | 
				
			||||||
            # This will allow a plaintext client to provide a noise key,
 | 
					            # This will allow a plaintext client to provide a noise key,
 | 
				
			||||||
@@ -212,6 +309,29 @@ async def to_code(config):
 | 
				
			|||||||
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
 | 
					KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _validate_response_config(config: ConfigType) -> ConfigType:
 | 
				
			||||||
 | 
					    # Validate dependencies:
 | 
				
			||||||
 | 
					    # - response_template requires capture_response: true
 | 
				
			||||||
 | 
					    # - capture_response: true requires on_success
 | 
				
			||||||
 | 
					    if CONF_RESPONSE_TEMPLATE in config and not config[CONF_CAPTURE_RESPONSE]:
 | 
				
			||||||
 | 
					        raise cv.Invalid(
 | 
				
			||||||
 | 
					            f"`{CONF_RESPONSE_TEMPLATE}` requires `{CONF_CAPTURE_RESPONSE}: true` to be set.",
 | 
				
			||||||
 | 
					            path=[CONF_RESPONSE_TEMPLATE],
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if config[CONF_CAPTURE_RESPONSE] and CONF_ON_SUCCESS not in config:
 | 
				
			||||||
 | 
					        raise cv.Invalid(
 | 
				
			||||||
 | 
					            f"`{CONF_CAPTURE_RESPONSE}: true` requires `{CONF_ON_SUCCESS}` to be set.",
 | 
				
			||||||
 | 
					            path=[CONF_CAPTURE_RESPONSE],
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Track if any action uses capture_response for AUTO_LOAD
 | 
				
			||||||
 | 
					    if config[CONF_CAPTURE_RESPONSE]:
 | 
				
			||||||
 | 
					        CORE.data.setdefault(DOMAIN, {})[CONF_CAPTURE_RESPONSE] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
 | 
					HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
 | 
				
			||||||
    cv.Schema(
 | 
					    cv.Schema(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -227,10 +347,15 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
 | 
				
			|||||||
            cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
 | 
					            cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
 | 
				
			||||||
                {cv.string: cv.returning_lambda}
 | 
					                {cv.string: cv.returning_lambda}
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_RESPONSE_TEMPLATE): cv.templatable(cv.string),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_CAPTURE_RESPONSE, default=False): cv.boolean,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_ON_SUCCESS): automation.validate_automation(single=True),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
 | 
					    cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
 | 
				
			||||||
    cv.rename_key(CONF_SERVICE, CONF_ACTION),
 | 
					    cv.rename_key(CONF_SERVICE, CONF_ACTION),
 | 
				
			||||||
 | 
					    _validate_response_config,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -244,7 +369,12 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
 | 
				
			|||||||
    HomeAssistantServiceCallAction,
 | 
					    HomeAssistantServiceCallAction,
 | 
				
			||||||
    HOMEASSISTANT_ACTION_ACTION_SCHEMA,
 | 
					    HOMEASSISTANT_ACTION_ACTION_SCHEMA,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
async def homeassistant_service_to_code(config, action_id, template_arg, args):
 | 
					async def homeassistant_service_to_code(
 | 
				
			||||||
 | 
					    config: ConfigType,
 | 
				
			||||||
 | 
					    action_id: ID,
 | 
				
			||||||
 | 
					    template_arg: cg.TemplateArguments,
 | 
				
			||||||
 | 
					    args: TemplateArgsType,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
    cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
 | 
					    cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
 | 
				
			||||||
    serv = await cg.get_variable(config[CONF_ID])
 | 
					    serv = await cg.get_variable(config[CONF_ID])
 | 
				
			||||||
    var = cg.new_Pvariable(action_id, template_arg, serv, False)
 | 
					    var = cg.new_Pvariable(action_id, template_arg, serv, False)
 | 
				
			||||||
@@ -259,6 +389,40 @@ async def homeassistant_service_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
    for key, value in config[CONF_VARIABLES].items():
 | 
					    for key, value in config[CONF_VARIABLES].items():
 | 
				
			||||||
        templ = await cg.templatable(value, args, None)
 | 
					        templ = await cg.templatable(value, args, None)
 | 
				
			||||||
        cg.add(var.add_variable(key, templ))
 | 
					        cg.add(var.add_variable(key, templ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if on_error := config.get(CONF_ON_ERROR):
 | 
				
			||||||
 | 
					        cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES")
 | 
				
			||||||
 | 
					        cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES_ERRORS")
 | 
				
			||||||
 | 
					        cg.add(var.set_wants_status())
 | 
				
			||||||
 | 
					        await automation.build_automation(
 | 
				
			||||||
 | 
					            var.get_error_trigger(),
 | 
				
			||||||
 | 
					            [(cg.std_string, "error"), *args],
 | 
				
			||||||
 | 
					            on_error,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if on_success := config.get(CONF_ON_SUCCESS):
 | 
				
			||||||
 | 
					        cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES")
 | 
				
			||||||
 | 
					        cg.add(var.set_wants_status())
 | 
				
			||||||
 | 
					        if config[CONF_CAPTURE_RESPONSE]:
 | 
				
			||||||
 | 
					            cg.add(var.set_wants_response())
 | 
				
			||||||
 | 
					            cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON")
 | 
				
			||||||
 | 
					            await automation.build_automation(
 | 
				
			||||||
 | 
					                var.get_success_trigger_with_response(),
 | 
				
			||||||
 | 
					                [(cg.JsonObjectConst, "response"), *args],
 | 
				
			||||||
 | 
					                on_success,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if response_template := config.get(CONF_RESPONSE_TEMPLATE):
 | 
				
			||||||
 | 
					                templ = await cg.templatable(response_template, args, cg.std_string)
 | 
				
			||||||
 | 
					                cg.add(var.set_response_template(templ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await automation.build_automation(
 | 
				
			||||||
 | 
					                var.get_success_trigger(),
 | 
				
			||||||
 | 
					                args,
 | 
				
			||||||
 | 
					                on_success,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return var
 | 
					    return var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -321,6 +485,7 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
 | 
				
			|||||||
    HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
 | 
					    HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
 | 
					async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
 | 
				
			||||||
 | 
					    cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
 | 
				
			||||||
    serv = await cg.get_variable(config[CONF_ID])
 | 
					    serv = await cg.get_variable(config[CONF_ID])
 | 
				
			||||||
    var = cg.new_Pvariable(action_id, template_arg, serv, True)
 | 
					    var = cg.new_Pvariable(action_id, template_arg, serv, True)
 | 
				
			||||||
    cg.add(var.set_service("esphome.tag_scanned"))
 | 
					    cg.add(var.set_service("esphome.tag_scanned"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ service APIConnection {
 | 
				
			|||||||
    option (needs_setup_connection) = false;
 | 
					    option (needs_setup_connection) = false;
 | 
				
			||||||
    option (needs_authentication) = false;
 | 
					    option (needs_authentication) = false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  rpc connect (ConnectRequest) returns (ConnectResponse) {
 | 
					  rpc authenticate (AuthenticationRequest) returns (AuthenticationResponse) {
 | 
				
			||||||
    option (needs_setup_connection) = false;
 | 
					    option (needs_setup_connection) = false;
 | 
				
			||||||
    option (needs_authentication) = false;
 | 
					    option (needs_authentication) = false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -27,9 +27,6 @@ service APIConnection {
 | 
				
			|||||||
  rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
 | 
					  rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
 | 
				
			||||||
  rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
 | 
					  rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
 | 
				
			||||||
  rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
 | 
					  rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
 | 
				
			||||||
  rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
 | 
					 | 
				
			||||||
    option (needs_authentication) = false;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  rpc execute_service (ExecuteServiceRequest) returns (void) {}
 | 
					  rpc execute_service (ExecuteServiceRequest) returns (void) {}
 | 
				
			||||||
  rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
 | 
					  rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -69,6 +66,9 @@ service APIConnection {
 | 
				
			|||||||
  rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {}
 | 
					  rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
 | 
					  rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  rpc zwave_proxy_frame(ZWaveProxyFrame) returns (void) {}
 | 
				
			||||||
 | 
					  rpc zwave_proxy_request(ZWaveProxyRequest) returns (void) {}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -102,7 +102,7 @@ message HelloRequest {
 | 
				
			|||||||
  // For example "Home Assistant"
 | 
					  // For example "Home Assistant"
 | 
				
			||||||
  // Not strictly necessary to send but nice for debugging
 | 
					  // Not strictly necessary to send but nice for debugging
 | 
				
			||||||
  // purposes.
 | 
					  // purposes.
 | 
				
			||||||
  string client_info = 1;
 | 
					  string client_info = 1 [(pointer_to_buffer) = true];
 | 
				
			||||||
  uint32 api_version_major = 2;
 | 
					  uint32 api_version_major = 2;
 | 
				
			||||||
  uint32 api_version_minor = 3;
 | 
					  uint32 api_version_minor = 3;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -132,21 +132,23 @@ message HelloResponse {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Message sent at the beginning of each connection to authenticate the client
 | 
					// Message sent at the beginning of each connection to authenticate the client
 | 
				
			||||||
// Can only be sent by the client and only at the beginning of the connection
 | 
					// Can only be sent by the client and only at the beginning of the connection
 | 
				
			||||||
message ConnectRequest {
 | 
					message AuthenticationRequest {
 | 
				
			||||||
  option (id) = 3;
 | 
					  option (id) = 3;
 | 
				
			||||||
  option (source) = SOURCE_CLIENT;
 | 
					  option (source) = SOURCE_CLIENT;
 | 
				
			||||||
  option (no_delay) = true;
 | 
					  option (no_delay) = true;
 | 
				
			||||||
 | 
					  option (ifdef) = "USE_API_PASSWORD";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // The password to log in with
 | 
					  // The password to log in with
 | 
				
			||||||
  string password = 1;
 | 
					  string password = 1 [(pointer_to_buffer) = true];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Confirmation of successful connection. After this the connection is available for all traffic.
 | 
					// Confirmation of successful connection. After this the connection is available for all traffic.
 | 
				
			||||||
// Can only be sent by the server and only at the beginning of the connection
 | 
					// Can only be sent by the server and only at the beginning of the connection
 | 
				
			||||||
message ConnectResponse {
 | 
					message AuthenticationResponse {
 | 
				
			||||||
  option (id) = 4;
 | 
					  option (id) = 4;
 | 
				
			||||||
  option (source) = SOURCE_SERVER;
 | 
					  option (source) = SOURCE_SERVER;
 | 
				
			||||||
  option (no_delay) = true;
 | 
					  option (no_delay) = true;
 | 
				
			||||||
 | 
					  option (ifdef) = "USE_API_PASSWORD";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool invalid_password = 1;
 | 
					  bool invalid_password = 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -255,6 +257,10 @@ message DeviceInfoResponse {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // Top-level area info to phase out suggested_area
 | 
					  // Top-level area info to phase out suggested_area
 | 
				
			||||||
  AreaInfo area = 22 [(field_ifdef) = "USE_AREAS"];
 | 
					  AreaInfo area = 22 [(field_ifdef) = "USE_AREAS"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Indicates if Z-Wave proxy support is available and features supported
 | 
				
			||||||
 | 
					  uint32 zwave_proxy_feature_flags = 23 [(field_ifdef) = "USE_ZWAVE_PROXY"];
 | 
				
			||||||
 | 
					  uint32 zwave_home_id = 24 [(field_ifdef) = "USE_ZWAVE_PROXY"];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message ListEntitiesRequest {
 | 
					message ListEntitiesRequest {
 | 
				
			||||||
@@ -763,7 +769,7 @@ message HomeassistantServiceMap {
 | 
				
			|||||||
  string value = 2 [(no_zero_copy) = true];
 | 
					  string value = 2 [(no_zero_copy) = true];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message HomeassistantServiceResponse {
 | 
					message HomeassistantActionRequest {
 | 
				
			||||||
  option (id) = 35;
 | 
					  option (id) = 35;
 | 
				
			||||||
  option (source) = SOURCE_SERVER;
 | 
					  option (source) = SOURCE_SERVER;
 | 
				
			||||||
  option (no_delay) = true;
 | 
					  option (no_delay) = true;
 | 
				
			||||||
@@ -774,6 +780,22 @@ message HomeassistantServiceResponse {
 | 
				
			|||||||
  repeated HomeassistantServiceMap data_template = 3;
 | 
					  repeated HomeassistantServiceMap data_template = 3;
 | 
				
			||||||
  repeated HomeassistantServiceMap variables = 4;
 | 
					  repeated HomeassistantServiceMap variables = 4;
 | 
				
			||||||
  bool is_event = 5;
 | 
					  bool is_event = 5;
 | 
				
			||||||
 | 
					  uint32 call_id = 6 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES"];
 | 
				
			||||||
 | 
					  bool wants_response = 7 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
 | 
				
			||||||
 | 
					  string response_template = 8 [(no_zero_copy) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Message sent by Home Assistant to ESPHome with service call response data
 | 
				
			||||||
 | 
					message HomeassistantActionResponse {
 | 
				
			||||||
 | 
					  option (id) = 130;
 | 
				
			||||||
 | 
					  option (source) = SOURCE_CLIENT;
 | 
				
			||||||
 | 
					  option (no_delay) = true;
 | 
				
			||||||
 | 
					  option (ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint32 call_id = 1; // Matches the call_id from HomeassistantActionRequest
 | 
				
			||||||
 | 
					  bool success = 2; // Whether the service call succeeded
 | 
				
			||||||
 | 
					  string error_message = 3; // Error message if success = false
 | 
				
			||||||
 | 
					  bytes response_data = 4 [(pointer_to_buffer) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ==================== IMPORT HOME ASSISTANT STATES ====================
 | 
					// ==================== IMPORT HOME ASSISTANT STATES ====================
 | 
				
			||||||
@@ -809,15 +831,16 @@ message HomeAssistantStateResponse {
 | 
				
			|||||||
// ==================== IMPORT TIME ====================
 | 
					// ==================== IMPORT TIME ====================
 | 
				
			||||||
message GetTimeRequest {
 | 
					message GetTimeRequest {
 | 
				
			||||||
  option (id) = 36;
 | 
					  option (id) = 36;
 | 
				
			||||||
  option (source) = SOURCE_BOTH;
 | 
					  option (source) = SOURCE_SERVER;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message GetTimeResponse {
 | 
					message GetTimeResponse {
 | 
				
			||||||
  option (id) = 37;
 | 
					  option (id) = 37;
 | 
				
			||||||
  option (source) = SOURCE_BOTH;
 | 
					  option (source) = SOURCE_CLIENT;
 | 
				
			||||||
  option (no_delay) = true;
 | 
					  option (no_delay) = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fixed32 epoch_seconds = 1;
 | 
					  fixed32 epoch_seconds = 1;
 | 
				
			||||||
 | 
					  string timezone = 2 [(pointer_to_buffer) = true];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ==================== USER-DEFINES SERVICES ====================
 | 
					// ==================== USER-DEFINES SERVICES ====================
 | 
				
			||||||
@@ -1458,7 +1481,7 @@ message BluetoothDeviceRequest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  uint64 address = 1;
 | 
					  uint64 address = 1;
 | 
				
			||||||
  BluetoothDeviceRequestType request_type = 2;
 | 
					  BluetoothDeviceRequestType request_type = 2;
 | 
				
			||||||
  bool has_address_type = 3;
 | 
					  bool has_address_type = 3;  // Deprecated, should be removed in 2027.8 - https://github.com/esphome/esphome/pull/10318
 | 
				
			||||||
  uint32 address_type = 4;
 | 
					  uint32 address_type = 4;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1564,7 +1587,7 @@ message BluetoothGATTWriteRequest {
 | 
				
			|||||||
  uint32 handle = 2;
 | 
					  uint32 handle = 2;
 | 
				
			||||||
  bool response = 3;
 | 
					  bool response = 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bytes data = 4;
 | 
					  bytes data = 4 [(pointer_to_buffer) = true];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message BluetoothGATTReadDescriptorRequest {
 | 
					message BluetoothGATTReadDescriptorRequest {
 | 
				
			||||||
@@ -1584,7 +1607,7 @@ message BluetoothGATTWriteDescriptorRequest {
 | 
				
			|||||||
  uint64 address = 1;
 | 
					  uint64 address = 1;
 | 
				
			||||||
  uint32 handle = 2;
 | 
					  uint32 handle = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bytes data = 3;
 | 
					  bytes data = 3 [(pointer_to_buffer) = true];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message BluetoothGATTNotifyRequest {
 | 
					message BluetoothGATTNotifyRequest {
 | 
				
			||||||
@@ -1712,6 +1735,7 @@ message BluetoothScannerStateResponse {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  BluetoothScannerState state = 1;
 | 
					  BluetoothScannerState state = 1;
 | 
				
			||||||
  BluetoothScannerMode mode = 2;
 | 
					  BluetoothScannerMode mode = 2;
 | 
				
			||||||
 | 
					  BluetoothScannerMode configured_mode = 3;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message BluetoothScannerSetModeRequest {
 | 
					message BluetoothScannerSetModeRequest {
 | 
				
			||||||
@@ -1857,10 +1881,22 @@ message VoiceAssistantWakeWord {
 | 
				
			|||||||
  repeated string trained_languages = 3;
 | 
					  repeated string trained_languages = 3;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message VoiceAssistantExternalWakeWord {
 | 
				
			||||||
 | 
					  string id = 1;
 | 
				
			||||||
 | 
					  string wake_word = 2;
 | 
				
			||||||
 | 
					  repeated string trained_languages = 3;
 | 
				
			||||||
 | 
					  string model_type = 4;
 | 
				
			||||||
 | 
					  uint32 model_size = 5;
 | 
				
			||||||
 | 
					  string model_hash = 6;
 | 
				
			||||||
 | 
					  string url = 7;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message VoiceAssistantConfigurationRequest {
 | 
					message VoiceAssistantConfigurationRequest {
 | 
				
			||||||
  option (id) = 121;
 | 
					  option (id) = 121;
 | 
				
			||||||
  option (source) = SOURCE_CLIENT;
 | 
					  option (source) = SOURCE_CLIENT;
 | 
				
			||||||
  option (ifdef) = "USE_VOICE_ASSISTANT";
 | 
					  option (ifdef) = "USE_VOICE_ASSISTANT";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  repeated VoiceAssistantExternalWakeWord external_wake_words = 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message VoiceAssistantConfigurationResponse {
 | 
					message VoiceAssistantConfigurationResponse {
 | 
				
			||||||
@@ -2275,3 +2311,28 @@ message UpdateCommandRequest {
 | 
				
			|||||||
  UpdateCommand command = 2;
 | 
					  UpdateCommand command = 2;
 | 
				
			||||||
  uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
 | 
					  uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ==================== Z-WAVE ====================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					message ZWaveProxyFrame {
 | 
				
			||||||
 | 
					  option (id) = 128;
 | 
				
			||||||
 | 
					  option (source) = SOURCE_BOTH;
 | 
				
			||||||
 | 
					  option (ifdef) = "USE_ZWAVE_PROXY";
 | 
				
			||||||
 | 
					  option (no_delay) = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bytes data = 1 [(pointer_to_buffer) = true];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum ZWaveProxyRequestType {
 | 
				
			||||||
 | 
					  ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE = 0;
 | 
				
			||||||
 | 
					  ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1;
 | 
				
			||||||
 | 
					  ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE = 2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					message ZWaveProxyRequest {
 | 
				
			||||||
 | 
					  option (id) = 129;
 | 
				
			||||||
 | 
					  option (source) = SOURCE_BOTH;
 | 
				
			||||||
 | 
					  option (ifdef) = "USE_ZWAVE_PROXY";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ZWaveProxyRequestType type = 1;
 | 
				
			||||||
 | 
					  bytes data = 2 [(pointer_to_buffer) = true];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,9 +8,9 @@
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
#include <cerrno>
 | 
					#include <cerrno>
 | 
				
			||||||
#include <cinttypes>
 | 
					#include <cinttypes>
 | 
				
			||||||
#include <utility>
 | 
					 | 
				
			||||||
#include <functional>
 | 
					#include <functional>
 | 
				
			||||||
#include <limits>
 | 
					#include <limits>
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
#include "esphome/components/network/util.h"
 | 
					#include "esphome/components/network/util.h"
 | 
				
			||||||
#include "esphome/core/application.h"
 | 
					#include "esphome/core/application.h"
 | 
				
			||||||
#include "esphome/core/entity_base.h"
 | 
					#include "esphome/core/entity_base.h"
 | 
				
			||||||
@@ -30,6 +30,9 @@
 | 
				
			|||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
#include "esphome/components/voice_assistant/voice_assistant.h"
 | 
					#include "esphome/components/voice_assistant/voice_assistant.h"
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					#include "esphome/components/zwave_proxy/zwave_proxy.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome::api {
 | 
					namespace esphome::api {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -42,6 +45,8 @@ static constexpr uint8_t MAX_PING_RETRIES = 60;
 | 
				
			|||||||
static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
 | 
					static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
 | 
				
			||||||
static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
 | 
					static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char *const TAG = "api.connection";
 | 
					static const char *const TAG = "api.connection";
 | 
				
			||||||
#ifdef USE_CAMERA
 | 
					#ifdef USE_CAMERA
 | 
				
			||||||
static const int CAMERA_STOP_STREAM = 5000;
 | 
					static const int CAMERA_STOP_STREAM = 5000;
 | 
				
			||||||
@@ -111,8 +116,7 @@ void APIConnection::start() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  APIError err = this->helper_->init();
 | 
					  APIError err = this->helper_->init();
 | 
				
			||||||
  if (err != APIError::OK) {
 | 
					  if (err != APIError::OK) {
 | 
				
			||||||
    on_fatal_error();
 | 
					    this->fatal_error_with_log_(LOG_STR("Helper init failed"), err);
 | 
				
			||||||
    this->log_warning_("Helper init failed", err);
 | 
					 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->client_info_.peername = helper_->getpeername();
 | 
					  this->client_info_.peername = helper_->getpeername();
 | 
				
			||||||
@@ -142,8 +146,7 @@ void APIConnection::loop() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  APIError err = this->helper_->loop();
 | 
					  APIError err = this->helper_->loop();
 | 
				
			||||||
  if (err != APIError::OK) {
 | 
					  if (err != APIError::OK) {
 | 
				
			||||||
    on_fatal_error();
 | 
					    this->fatal_error_with_log_(LOG_STR("Socket operation failed"), err);
 | 
				
			||||||
    this->log_socket_operation_failed_(err);
 | 
					 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -158,17 +161,13 @@ void APIConnection::loop() {
 | 
				
			|||||||
        // No more data available
 | 
					        // No more data available
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      } else if (err != APIError::OK) {
 | 
					      } else if (err != APIError::OK) {
 | 
				
			||||||
        on_fatal_error();
 | 
					        this->fatal_error_with_log_(LOG_STR("Reading failed"), err);
 | 
				
			||||||
        this->log_warning_("Reading failed", err);
 | 
					 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this->last_traffic_ = now;
 | 
					        this->last_traffic_ = now;
 | 
				
			||||||
        // read a packet
 | 
					        // read a packet
 | 
				
			||||||
        if (buffer.data_len > 0) {
 | 
					        this->read_message(buffer.data_len, buffer.type,
 | 
				
			||||||
          this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
 | 
					                           buffer.data_len > 0 ? &buffer.container[buffer.data_offset] : nullptr);
 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          this->read_message(0, buffer.type, nullptr);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (this->flags_.remove)
 | 
					        if (this->flags_.remove)
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -200,7 +199,8 @@ void APIConnection::loop() {
 | 
				
			|||||||
    // Disconnect if not responded within 2.5*keepalive
 | 
					    // Disconnect if not responded within 2.5*keepalive
 | 
				
			||||||
    if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
 | 
					    if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
 | 
				
			||||||
      on_fatal_error();
 | 
					      on_fatal_error();
 | 
				
			||||||
      ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str());
 | 
					      ESP_LOGW(TAG, "%s (%s) is unresponsive; disconnecting", this->client_info_.name.c_str(),
 | 
				
			||||||
 | 
					               this->client_info_.peername.c_str());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
 | 
					  } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
 | 
				
			||||||
    // Only send ping if we're not disconnecting
 | 
					    // Only send ping if we're not disconnecting
 | 
				
			||||||
@@ -250,7 +250,7 @@ bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
 | 
				
			|||||||
  // remote initiated disconnect_client
 | 
					  // remote initiated disconnect_client
 | 
				
			||||||
  // don't close yet, we still need to send the disconnect response
 | 
					  // don't close yet, we still need to send the disconnect response
 | 
				
			||||||
  // close will happen on next loop
 | 
					  // close will happen on next loop
 | 
				
			||||||
  ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str());
 | 
					  ESP_LOGD(TAG, "%s (%s) disconnected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
 | 
				
			||||||
  this->flags_.next_close = true;
 | 
					  this->flags_.next_close = true;
 | 
				
			||||||
  DisconnectResponse resp;
 | 
					  DisconnectResponse resp;
 | 
				
			||||||
  return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
 | 
					  return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
 | 
				
			||||||
@@ -289,16 +289,26 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
 | 
				
			|||||||
    return 0;  // Doesn't fit
 | 
					    return 0;  // Doesn't fit
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Allocate buffer space - pass payload size, allocation functions add header/footer space
 | 
					 | 
				
			||||||
  ProtoWriteBuffer buffer = is_single ? conn->allocate_single_message_buffer(calculated_size)
 | 
					 | 
				
			||||||
                                      : conn->allocate_batch_message_buffer(calculated_size);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Get buffer size after allocation (which includes header padding)
 | 
					  // Get buffer size after allocation (which includes header padding)
 | 
				
			||||||
  std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
 | 
					  std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
 | 
				
			||||||
  size_t size_before_encode = shared_buf.size();
 | 
					
 | 
				
			||||||
 | 
					  if (is_single || conn->flags_.batch_first_message) {
 | 
				
			||||||
 | 
					    // Single message or first batch message
 | 
				
			||||||
 | 
					    conn->prepare_first_message_buffer(shared_buf, header_padding, total_calculated_size);
 | 
				
			||||||
 | 
					    if (conn->flags_.batch_first_message) {
 | 
				
			||||||
 | 
					      conn->flags_.batch_first_message = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    // Batch message second or later
 | 
				
			||||||
 | 
					    // Add padding for previous message footer + this message header
 | 
				
			||||||
 | 
					    size_t current_size = shared_buf.size();
 | 
				
			||||||
 | 
					    shared_buf.reserve(current_size + total_calculated_size);
 | 
				
			||||||
 | 
					    shared_buf.resize(current_size + footer_size + header_padding);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Encode directly into buffer
 | 
					  // Encode directly into buffer
 | 
				
			||||||
  msg.encode(buffer);
 | 
					  size_t size_before_encode = shared_buf.size();
 | 
				
			||||||
 | 
					  msg.encode({&shared_buf});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Calculate actual encoded size (not including header that was already added)
 | 
					  // Calculate actual encoded size (not including header that was already added)
 | 
				
			||||||
  size_t actual_payload_size = shared_buf.size() - size_before_encode;
 | 
					  size_t actual_payload_size = shared_buf.size() - size_before_encode;
 | 
				
			||||||
@@ -455,9 +465,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
 | 
				
			|||||||
  resp.cold_white = values.get_cold_white();
 | 
					  resp.cold_white = values.get_cold_white();
 | 
				
			||||||
  resp.warm_white = values.get_warm_white();
 | 
					  resp.warm_white = values.get_warm_white();
 | 
				
			||||||
  if (light->supports_effects()) {
 | 
					  if (light->supports_effects()) {
 | 
				
			||||||
    // get_effect_name() returns temporary std::string - must store it
 | 
					    resp.set_effect(light->get_effect_name_ref());
 | 
				
			||||||
    std::string effect_name = light->get_effect_name();
 | 
					 | 
				
			||||||
    resp.set_effect(StringRef(effect_name));
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
 | 
					  return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1062,17 +1070,23 @@ void APIConnection::camera_image(const CameraImageRequest &msg) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#ifdef USE_HOMEASSISTANT_TIME
 | 
					#ifdef USE_HOMEASSISTANT_TIME
 | 
				
			||||||
void APIConnection::on_get_time_response(const GetTimeResponse &value) {
 | 
					void APIConnection::on_get_time_response(const GetTimeResponse &value) {
 | 
				
			||||||
  if (homeassistant::global_homeassistant_time != nullptr)
 | 
					  if (homeassistant::global_homeassistant_time != nullptr) {
 | 
				
			||||||
    homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
 | 
					    homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds);
 | 
				
			||||||
 | 
					#ifdef USE_TIME_TIMEZONE
 | 
				
			||||||
 | 
					    if (value.timezone_len > 0) {
 | 
				
			||||||
 | 
					      const std::string ¤t_tz = homeassistant::global_homeassistant_time->get_timezone();
 | 
				
			||||||
 | 
					      // Compare without allocating a string
 | 
				
			||||||
 | 
					      if (current_tz.length() != value.timezone_len ||
 | 
				
			||||||
 | 
					          memcmp(current_tz.c_str(), value.timezone, value.timezone_len) != 0) {
 | 
				
			||||||
 | 
					        homeassistant::global_homeassistant_time->set_timezone(
 | 
				
			||||||
 | 
					            std::string(reinterpret_cast<const char *>(value.timezone), value.timezone_len));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool APIConnection::send_get_time_response(const GetTimeRequest &msg) {
 | 
					 | 
				
			||||||
  GetTimeResponse resp;
 | 
					 | 
				
			||||||
  resp.epoch_seconds = ::time(nullptr);
 | 
					 | 
				
			||||||
  return this->send_message(resp, GetTimeResponse::MESSAGE_TYPE);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
					void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
				
			||||||
  bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags);
 | 
					  bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags);
 | 
				
			||||||
@@ -1183,6 +1197,23 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA
 | 
				
			|||||||
      resp_wake_word.trained_languages.push_back(lang);
 | 
					      resp_wake_word.trained_languages.push_back(lang);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Filter external wake words
 | 
				
			||||||
 | 
					  for (auto &wake_word : msg.external_wake_words) {
 | 
				
			||||||
 | 
					    if (wake_word.model_type != "micro") {
 | 
				
			||||||
 | 
					      // microWakeWord only
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resp.available_wake_words.emplace_back();
 | 
				
			||||||
 | 
					    auto &resp_wake_word = resp.available_wake_words.back();
 | 
				
			||||||
 | 
					    resp_wake_word.set_id(StringRef(wake_word.id));
 | 
				
			||||||
 | 
					    resp_wake_word.set_wake_word(StringRef(wake_word.wake_word));
 | 
				
			||||||
 | 
					    for (const auto &lang : wake_word.trained_languages) {
 | 
				
			||||||
 | 
					      resp_wake_word.trained_languages.push_back(lang);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  resp.active_wake_words = &config.active_wake_words;
 | 
					  resp.active_wake_words = &config.active_wake_words;
 | 
				
			||||||
  resp.max_active_wake_words = config.max_active_wake_words;
 | 
					  resp.max_active_wake_words = config.max_active_wake_words;
 | 
				
			||||||
  return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
 | 
					  return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
 | 
				
			||||||
@@ -1193,7 +1224,16 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon
 | 
				
			|||||||
    voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words);
 | 
					    voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					void APIConnection::zwave_proxy_frame(const ZWaveProxyFrame &msg) {
 | 
				
			||||||
 | 
					  zwave_proxy::global_zwave_proxy->send_frame(msg.data, msg.data_len);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void APIConnection::zwave_proxy_request(const ZWaveProxyRequest &msg) {
 | 
				
			||||||
 | 
					  zwave_proxy::global_zwave_proxy->zwave_proxy_request(this, msg.type);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
@@ -1340,7 +1380,7 @@ void APIConnection::complete_authentication_() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
 | 
					  this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
 | 
				
			||||||
  ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str());
 | 
					  ESP_LOGD(TAG, "%s (%s) connected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
 | 
				
			||||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
 | 
					#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
 | 
				
			||||||
  this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername);
 | 
					  this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -1349,10 +1389,15 @@ void APIConnection::complete_authentication_() {
 | 
				
			|||||||
    this->send_time_request();
 | 
					    this->send_time_request();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  if (zwave_proxy::global_zwave_proxy != nullptr) {
 | 
				
			||||||
 | 
					    zwave_proxy::global_zwave_proxy->api_connection_authenticated(this);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool APIConnection::send_hello_response(const HelloRequest &msg) {
 | 
					bool APIConnection::send_hello_response(const HelloRequest &msg) {
 | 
				
			||||||
  this->client_info_.name = msg.client_info;
 | 
					  this->client_info_.name.assign(reinterpret_cast<const char *>(msg.client_info), msg.client_info_len);
 | 
				
			||||||
  this->client_info_.peername = this->helper_->getpeername();
 | 
					  this->client_info_.peername = this->helper_->getpeername();
 | 
				
			||||||
  this->client_api_version_major_ = msg.api_version_major;
 | 
					  this->client_api_version_major_ = msg.api_version_major;
 | 
				
			||||||
  this->client_api_version_minor_ = msg.api_version_minor;
 | 
					  this->client_api_version_minor_ = msg.api_version_minor;
 | 
				
			||||||
@@ -1362,9 +1407,8 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
 | 
				
			|||||||
  HelloResponse resp;
 | 
					  HelloResponse resp;
 | 
				
			||||||
  resp.api_version_major = 1;
 | 
					  resp.api_version_major = 1;
 | 
				
			||||||
  resp.api_version_minor = 12;
 | 
					  resp.api_version_minor = 12;
 | 
				
			||||||
  // Temporary string for concatenation - will be valid during send_message call
 | 
					  // Send only the version string - the client only logs this for debugging and doesn't use it otherwise
 | 
				
			||||||
  std::string server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
 | 
					  resp.set_server_info(ESPHOME_VERSION_REF);
 | 
				
			||||||
  resp.set_server_info(StringRef(server_info));
 | 
					 | 
				
			||||||
  resp.set_name(StringRef(App.get_name()));
 | 
					  resp.set_name(StringRef(App.get_name()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_API_PASSWORD
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
@@ -1377,20 +1421,17 @@ bool APIConnection::send_hello_response(const HelloRequest &msg) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
 | 
					  return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
bool APIConnection::send_connect_response(const ConnectRequest &msg) {
 | 
					 | 
				
			||||||
  bool correct = true;
 | 
					 | 
				
			||||||
#ifdef USE_API_PASSWORD
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
  correct = this->parent_->check_password(msg.password);
 | 
					bool APIConnection::send_authenticate_response(const AuthenticationRequest &msg) {
 | 
				
			||||||
#endif
 | 
					  AuthenticationResponse resp;
 | 
				
			||||||
 | 
					 | 
				
			||||||
  ConnectResponse resp;
 | 
					 | 
				
			||||||
  // bool invalid_password = 1;
 | 
					  // bool invalid_password = 1;
 | 
				
			||||||
  resp.invalid_password = !correct;
 | 
					  resp.invalid_password = !this->parent_->check_password(msg.password, msg.password_len);
 | 
				
			||||||
  if (correct) {
 | 
					  if (!resp.invalid_password) {
 | 
				
			||||||
    this->complete_authentication_();
 | 
					    this->complete_authentication_();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return this->send_message(resp, ConnectResponse::MESSAGE_TYPE);
 | 
					  return this->send_message(resp, AuthenticationResponse::MESSAGE_TYPE);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#endif  // USE_API_PASSWORD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool APIConnection::send_ping_response(const PingRequest &msg) {
 | 
					bool APIConnection::send_ping_response(const PingRequest &msg) {
 | 
				
			||||||
  PingResponse resp;
 | 
					  PingResponse resp;
 | 
				
			||||||
@@ -1411,13 +1452,9 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
 | 
				
			|||||||
  std::string mac_address = get_mac_address_pretty();
 | 
					  std::string mac_address = get_mac_address_pretty();
 | 
				
			||||||
  resp.set_mac_address(StringRef(mac_address));
 | 
					  resp.set_mac_address(StringRef(mac_address));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Compile-time StringRef constants
 | 
					 | 
				
			||||||
  static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
 | 
					 | 
				
			||||||
  resp.set_esphome_version(ESPHOME_VERSION_REF);
 | 
					  resp.set_esphome_version(ESPHOME_VERSION_REF);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // get_compilation_time() returns temporary std::string - must store it
 | 
					  resp.set_compilation_time(App.get_compilation_time_ref());
 | 
				
			||||||
  std::string compilation_time = App.get_compilation_time();
 | 
					 | 
				
			||||||
  resp.set_compilation_time(StringRef(compilation_time));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Compile-time StringRef constants for manufacturers
 | 
					  // Compile-time StringRef constants for manufacturers
 | 
				
			||||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
 | 
					#if defined(USE_ESP8266) || defined(USE_ESP32)
 | 
				
			||||||
@@ -1458,6 +1495,10 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
 | 
				
			|||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
  resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
 | 
					  resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  resp.zwave_proxy_feature_flags = zwave_proxy::global_zwave_proxy->get_feature_flags();
 | 
				
			||||||
 | 
					  resp.zwave_home_id = zwave_proxy::global_zwave_proxy->get_home_id();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
#ifdef USE_API_NOISE
 | 
					#ifdef USE_API_NOISE
 | 
				
			||||||
  resp.api_encryption_supported = true;
 | 
					  resp.api_encryption_supported = true;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -1508,6 +1549,20 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					void APIConnection::on_homeassistant_action_response(const HomeassistantActionResponse &msg) {
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  if (msg.response_data_len > 0) {
 | 
				
			||||||
 | 
					    this->parent_->handle_action_response(msg.call_id, msg.success, msg.error_message, msg.response_data,
 | 
				
			||||||
 | 
					                                          msg.response_data_len);
 | 
				
			||||||
 | 
					  } else
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    this->parent_->handle_action_response(msg.call_id, msg.success, msg.error_message);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
#ifdef USE_API_NOISE
 | 
					#ifdef USE_API_NOISE
 | 
				
			||||||
bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) {
 | 
					bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) {
 | 
				
			||||||
  NoiseEncryptionSetKeyResponse resp;
 | 
					  NoiseEncryptionSetKeyResponse resp;
 | 
				
			||||||
@@ -1538,8 +1593,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
 | 
				
			|||||||
  delay(0);
 | 
					  delay(0);
 | 
				
			||||||
  APIError err = this->helper_->loop();
 | 
					  APIError err = this->helper_->loop();
 | 
				
			||||||
  if (err != APIError::OK) {
 | 
					  if (err != APIError::OK) {
 | 
				
			||||||
    on_fatal_error();
 | 
					    this->fatal_error_with_log_(LOG_STR("Socket operation failed"), err);
 | 
				
			||||||
    this->log_socket_operation_failed_(err);
 | 
					 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->helper_->can_write_without_blocking())
 | 
					  if (this->helper_->can_write_without_blocking())
 | 
				
			||||||
@@ -1558,8 +1612,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
 | 
				
			|||||||
  if (err == APIError::WOULD_BLOCK)
 | 
					  if (err == APIError::WOULD_BLOCK)
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  if (err != APIError::OK) {
 | 
					  if (err != APIError::OK) {
 | 
				
			||||||
    on_fatal_error();
 | 
					    this->fatal_error_with_log_(LOG_STR("Packet write failed"), err);
 | 
				
			||||||
    this->log_warning_("Packet write failed", err);
 | 
					 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // Do not set last_traffic_ on send
 | 
					  // Do not set last_traffic_ on send
 | 
				
			||||||
@@ -1568,12 +1621,12 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
 | 
				
			|||||||
#ifdef USE_API_PASSWORD
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
void APIConnection::on_unauthenticated_access() {
 | 
					void APIConnection::on_unauthenticated_access() {
 | 
				
			||||||
  this->on_fatal_error();
 | 
					  this->on_fatal_error();
 | 
				
			||||||
  ESP_LOGD(TAG, "%s access without authentication", this->get_client_combined_info().c_str());
 | 
					  ESP_LOGD(TAG, "%s (%s) no authentication", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
void APIConnection::on_no_setup_connection() {
 | 
					void APIConnection::on_no_setup_connection() {
 | 
				
			||||||
  this->on_fatal_error();
 | 
					  this->on_fatal_error();
 | 
				
			||||||
  ESP_LOGD(TAG, "%s access without full connection", this->get_client_combined_info().c_str());
 | 
					  ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void APIConnection::on_fatal_error() {
 | 
					void APIConnection::on_fatal_error() {
 | 
				
			||||||
  this->helper_->close();
 | 
					  this->helper_->close();
 | 
				
			||||||
@@ -1620,14 +1673,6 @@ bool APIConnection::schedule_batch_() {
 | 
				
			|||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) {
 | 
					 | 
				
			||||||
  ProtoWriteBuffer result = this->prepare_message_buffer(size, this->flags_.batch_first_message);
 | 
					 | 
				
			||||||
  this->flags_.batch_first_message = false;
 | 
					 | 
				
			||||||
  return result;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void APIConnection::process_batch_() {
 | 
					void APIConnection::process_batch_() {
 | 
				
			||||||
  // Ensure PacketInfo remains trivially destructible for our placement new approach
 | 
					  // Ensure PacketInfo remains trivially destructible for our placement new approach
 | 
				
			||||||
  static_assert(std::is_trivially_destructible<PacketInfo>::value,
 | 
					  static_assert(std::is_trivially_destructible<PacketInfo>::value,
 | 
				
			||||||
@@ -1735,7 +1780,7 @@ void APIConnection::process_batch_() {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    remaining_size -= payload_size;
 | 
					    remaining_size -= payload_size;
 | 
				
			||||||
    // Calculate where the next message's header padding will start
 | 
					    // Calculate where the next message's header padding will start
 | 
				
			||||||
    // Current buffer size + footer space (that prepare_message_buffer will add for this message)
 | 
					    // Current buffer size + footer space for this message
 | 
				
			||||||
    current_offset = shared_buf.size() + footer_size;
 | 
					    current_offset = shared_buf.size() + footer_size;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1753,8 +1798,7 @@ void APIConnection::process_batch_() {
 | 
				
			|||||||
  APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf},
 | 
					  APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf},
 | 
				
			||||||
                                                       std::span<const PacketInfo>(packet_info, packet_count));
 | 
					                                                       std::span<const PacketInfo>(packet_info, packet_count));
 | 
				
			||||||
  if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
 | 
					  if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
 | 
				
			||||||
    on_fatal_error();
 | 
					    this->fatal_error_with_log_(LOG_STR("Batch write failed"), err);
 | 
				
			||||||
    this->log_warning_("Batch write failed", err);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
@@ -1832,11 +1876,10 @@ void APIConnection::process_state_subscriptions_() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
#endif  // USE_API_HOMEASSISTANT_STATES
 | 
					#endif  // USE_API_HOMEASSISTANT_STATES
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void APIConnection::log_warning_(const char *message, APIError err) {
 | 
					void APIConnection::log_warning_(const LogString *message, APIError err) {
 | 
				
			||||||
  ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), message, api_error_to_str(err), errno);
 | 
					  ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name.c_str(), this->client_info_.peername.c_str(),
 | 
				
			||||||
 | 
					           LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void APIConnection::log_socket_operation_failed_(APIError err) { this->log_warning_("Socket operation failed", err); }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}  // namespace esphome::api
 | 
					}  // namespace esphome::api
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,8 +10,8 @@
 | 
				
			|||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
#include "esphome/core/entity_base.h"
 | 
					#include "esphome/core/entity_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <vector>
 | 
					 | 
				
			||||||
#include <functional>
 | 
					#include <functional>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome::api {
 | 
					namespace esphome::api {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,14 +19,6 @@ namespace esphome::api {
 | 
				
			|||||||
struct ClientInfo {
 | 
					struct ClientInfo {
 | 
				
			||||||
  std::string name;      // Client name from Hello message
 | 
					  std::string name;      // Client name from Hello message
 | 
				
			||||||
  std::string peername;  // IP:port from socket
 | 
					  std::string peername;  // IP:port from socket
 | 
				
			||||||
 | 
					 | 
				
			||||||
  std::string get_combined_info() const {
 | 
					 | 
				
			||||||
    if (name == peername) {
 | 
					 | 
				
			||||||
      // Before Hello message, both are the same
 | 
					 | 
				
			||||||
      return name;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return name + " (" + peername + ")";
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Keepalive timeout in milliseconds
 | 
					// Keepalive timeout in milliseconds
 | 
				
			||||||
@@ -44,7 +36,7 @@ static constexpr size_t MAX_PACKETS_PER_BATCH = 64;  // ESP32 has 8KB+ stack, HO
 | 
				
			|||||||
static constexpr size_t MAX_PACKETS_PER_BATCH = 32;  // ESP8266/RP2040/etc have smaller stacks
 | 
					static constexpr size_t MAX_PACKETS_PER_BATCH = 32;  // ESP8266/RP2040/etc have smaller stacks
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class APIConnection : public APIServerConnection {
 | 
					class APIConnection final : public APIServerConnection {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  friend class APIServer;
 | 
					  friend class APIServer;
 | 
				
			||||||
  friend class ListEntitiesIterator;
 | 
					  friend class ListEntitiesIterator;
 | 
				
			||||||
@@ -132,12 +124,15 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
  bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
 | 
					  bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
 | 
				
			||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
 | 
					#ifdef USE_API_HOMEASSISTANT_SERVICES
 | 
				
			||||||
  void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
 | 
					  void send_homeassistant_action(const HomeassistantActionRequest &call) {
 | 
				
			||||||
    if (!this->flags_.service_call_subscription)
 | 
					    if (!this->flags_.service_call_subscription)
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    this->send_message(call, HomeassistantServiceResponse::MESSAGE_TYPE);
 | 
					    this->send_message(call, HomeassistantActionRequest::MESSAGE_TYPE);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#endif
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					  void on_homeassistant_action_response(const HomeassistantActionResponse &msg) override;
 | 
				
			||||||
 | 
					#endif  // USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					#endif  // USE_API_HOMEASSISTANT_SERVICES
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
  void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
					  void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
				
			||||||
  void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
					  void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
 | 
				
			||||||
@@ -171,6 +166,11 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
  void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
 | 
					  void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  void zwave_proxy_frame(const ZWaveProxyFrame &msg) override;
 | 
				
			||||||
 | 
					  void zwave_proxy_request(const ZWaveProxyRequest &msg) override;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
  bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
					  bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
 | 
				
			||||||
  void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
 | 
					  void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
 | 
				
			||||||
@@ -197,7 +197,9 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
  void on_get_time_response(const GetTimeResponse &value) override;
 | 
					  void on_get_time_response(const GetTimeResponse &value) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  bool send_hello_response(const HelloRequest &msg) override;
 | 
					  bool send_hello_response(const HelloRequest &msg) override;
 | 
				
			||||||
  bool send_connect_response(const ConnectRequest &msg) override;
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
 | 
					  bool send_authenticate_response(const AuthenticationRequest &msg) override;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
  bool send_disconnect_response(const DisconnectRequest &msg) override;
 | 
					  bool send_disconnect_response(const DisconnectRequest &msg) override;
 | 
				
			||||||
  bool send_ping_response(const PingRequest &msg) override;
 | 
					  bool send_ping_response(const PingRequest &msg) override;
 | 
				
			||||||
  bool send_device_info_response(const DeviceInfoRequest &msg) override;
 | 
					  bool send_device_info_response(const DeviceInfoRequest &msg) override;
 | 
				
			||||||
@@ -219,7 +221,6 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
#ifdef USE_API_HOMEASSISTANT_STATES
 | 
					#ifdef USE_API_HOMEASSISTANT_STATES
 | 
				
			||||||
  void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
 | 
					  void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  bool send_get_time_response(const GetTimeRequest &msg) override;
 | 
					 | 
				
			||||||
#ifdef USE_API_SERVICES
 | 
					#ifdef USE_API_SERVICES
 | 
				
			||||||
  void execute_service(const ExecuteServiceRequest &msg) override;
 | 
					  void execute_service(const ExecuteServiceRequest &msg) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -252,54 +253,28 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Get header padding size - used for both reserve and insert
 | 
					    // Get header padding size - used for both reserve and insert
 | 
				
			||||||
    uint8_t header_padding = this->helper_->frame_header_padding();
 | 
					    uint8_t header_padding = this->helper_->frame_header_padding();
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Get shared buffer from parent server
 | 
					    // Get shared buffer from parent server
 | 
				
			||||||
    std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
 | 
					    std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
 | 
				
			||||||
 | 
					    this->prepare_first_message_buffer(shared_buf, header_padding,
 | 
				
			||||||
 | 
					                                       reserve_size + header_padding + this->helper_->frame_footer_size());
 | 
				
			||||||
 | 
					    return {&shared_buf};
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void prepare_first_message_buffer(std::vector<uint8_t> &shared_buf, size_t header_padding, size_t total_size) {
 | 
				
			||||||
    shared_buf.clear();
 | 
					    shared_buf.clear();
 | 
				
			||||||
    // Reserve space for header padding + message + footer
 | 
					    // Reserve space for header padding + message + footer
 | 
				
			||||||
    // - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
 | 
					    // - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
 | 
				
			||||||
    // - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
 | 
					    // - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
 | 
				
			||||||
    shared_buf.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
 | 
					    shared_buf.reserve(total_size);
 | 
				
			||||||
    // Resize to add header padding so message encoding starts at the correct position
 | 
					    // Resize to add header padding so message encoding starts at the correct position
 | 
				
			||||||
    shared_buf.resize(header_padding);
 | 
					    shared_buf.resize(header_padding);
 | 
				
			||||||
    return {&shared_buf};
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Prepare buffer for next message in batch
 | 
					 | 
				
			||||||
  ProtoWriteBuffer prepare_message_buffer(uint16_t message_size, bool is_first_message) {
 | 
					 | 
				
			||||||
    // Get reference to shared buffer (it maintains state between batch messages)
 | 
					 | 
				
			||||||
    std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (is_first_message) {
 | 
					 | 
				
			||||||
      shared_buf.clear();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    size_t current_size = shared_buf.size();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Calculate padding to add:
 | 
					 | 
				
			||||||
    // - First message: just header padding
 | 
					 | 
				
			||||||
    // - Subsequent messages: footer for previous message + header padding for this message
 | 
					 | 
				
			||||||
    size_t padding_to_add = is_first_message
 | 
					 | 
				
			||||||
                                ? this->helper_->frame_header_padding()
 | 
					 | 
				
			||||||
                                : this->helper_->frame_header_padding() + this->helper_->frame_footer_size();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Reserve space for padding + message
 | 
					 | 
				
			||||||
    shared_buf.reserve(current_size + padding_to_add + message_size);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Resize to add the padding bytes
 | 
					 | 
				
			||||||
    shared_buf.resize(current_size + padding_to_add);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return {&shared_buf};
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool try_to_clear_buffer(bool log_out_of_space);
 | 
					  bool try_to_clear_buffer(bool log_out_of_space);
 | 
				
			||||||
  bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
 | 
					  bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::string get_client_combined_info() const { return this->client_info_.get_combined_info(); }
 | 
					  const std::string &get_name() const { return this->client_info_.name; }
 | 
				
			||||||
 | 
					  const std::string &get_peername() const { return this->client_info_.peername; }
 | 
				
			||||||
  // Buffer allocator methods for batch processing
 | 
					 | 
				
			||||||
  ProtoWriteBuffer allocate_single_message_buffer(uint16_t size);
 | 
					 | 
				
			||||||
  ProtoWriteBuffer allocate_batch_message_buffer(uint16_t size);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  // Helper function to handle authentication completion
 | 
					  // Helper function to handle authentication completion
 | 
				
			||||||
@@ -328,9 +303,17 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
                                              APIConnection *conn, uint32_t remaining_size, bool is_single) {
 | 
					                                              APIConnection *conn, uint32_t remaining_size, bool is_single) {
 | 
				
			||||||
    // Set common fields that are shared by all entity types
 | 
					    // Set common fields that are shared by all entity types
 | 
				
			||||||
    msg.key = entity->get_object_id_hash();
 | 
					    msg.key = entity->get_object_id_hash();
 | 
				
			||||||
    // IMPORTANT: get_object_id() may return a temporary std::string
 | 
					    // Try to use static reference first to avoid allocation
 | 
				
			||||||
    std::string object_id = entity->get_object_id();
 | 
					    StringRef static_ref = entity->get_object_id_ref_for_api_();
 | 
				
			||||||
    msg.set_object_id(StringRef(object_id));
 | 
					    // Store dynamic string outside the if-else to maintain lifetime
 | 
				
			||||||
 | 
					    std::string object_id;
 | 
				
			||||||
 | 
					    if (!static_ref.empty()) {
 | 
				
			||||||
 | 
					      msg.set_object_id(static_ref);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      // Dynamic case - need to allocate
 | 
				
			||||||
 | 
					      object_id = entity->get_object_id();
 | 
				
			||||||
 | 
					      msg.set_object_id(StringRef(object_id));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (entity->has_own_name()) {
 | 
					    if (entity->has_own_name()) {
 | 
				
			||||||
      msg.set_name(entity->get_name());
 | 
					      msg.set_name(entity->get_name());
 | 
				
			||||||
@@ -751,9 +734,12 @@ class APIConnection : public APIServerConnection {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Helper function to log API errors with errno
 | 
					  // Helper function to log API errors with errno
 | 
				
			||||||
  void log_warning_(const char *message, APIError err);
 | 
					  void log_warning_(const LogString *message, APIError err);
 | 
				
			||||||
  // Specific helper for duplicated error message
 | 
					  // Helper to handle fatal errors with logging
 | 
				
			||||||
  void log_socket_operation_failed_(APIError err);
 | 
					  inline void fatal_error_with_log_(const LogString *message, APIError err) {
 | 
				
			||||||
 | 
					    this->on_fatal_error();
 | 
				
			||||||
 | 
					    this->log_warning_(message, err);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace esphome::api
 | 
					}  // namespace esphome::api
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,8 @@ namespace esphome::api {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static const char *const TAG = "api.frame_helper";
 | 
					static const char *const TAG = "api.frame_helper";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
 | 
					#define HELPER_LOG(msg, ...) \
 | 
				
			||||||
 | 
					  ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef HELPER_LOG_PACKETS
 | 
					#ifdef HELPER_LOG_PACKETS
 | 
				
			||||||
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
 | 
					#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
 | 
				
			||||||
@@ -23,64 +24,64 @@ static const char *const TAG = "api.frame_helper";
 | 
				
			|||||||
#define LOG_PACKET_SENDING(data, len) ((void) 0)
 | 
					#define LOG_PACKET_SENDING(data, len) ((void) 0)
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const char *api_error_to_str(APIError err) {
 | 
					const LogString *api_error_to_logstr(APIError err) {
 | 
				
			||||||
  // not using switch to ensure compiler doesn't try to build a big table out of it
 | 
					  // not using switch to ensure compiler doesn't try to build a big table out of it
 | 
				
			||||||
  if (err == APIError::OK) {
 | 
					  if (err == APIError::OK) {
 | 
				
			||||||
    return "OK";
 | 
					    return LOG_STR("OK");
 | 
				
			||||||
  } else if (err == APIError::WOULD_BLOCK) {
 | 
					  } else if (err == APIError::WOULD_BLOCK) {
 | 
				
			||||||
    return "WOULD_BLOCK";
 | 
					    return LOG_STR("WOULD_BLOCK");
 | 
				
			||||||
  } else if (err == APIError::BAD_INDICATOR) {
 | 
					  } else if (err == APIError::BAD_INDICATOR) {
 | 
				
			||||||
    return "BAD_INDICATOR";
 | 
					    return LOG_STR("BAD_INDICATOR");
 | 
				
			||||||
  } else if (err == APIError::BAD_DATA_PACKET) {
 | 
					  } else if (err == APIError::BAD_DATA_PACKET) {
 | 
				
			||||||
    return "BAD_DATA_PACKET";
 | 
					    return LOG_STR("BAD_DATA_PACKET");
 | 
				
			||||||
  } else if (err == APIError::TCP_NODELAY_FAILED) {
 | 
					  } else if (err == APIError::TCP_NODELAY_FAILED) {
 | 
				
			||||||
    return "TCP_NODELAY_FAILED";
 | 
					    return LOG_STR("TCP_NODELAY_FAILED");
 | 
				
			||||||
  } else if (err == APIError::TCP_NONBLOCKING_FAILED) {
 | 
					  } else if (err == APIError::TCP_NONBLOCKING_FAILED) {
 | 
				
			||||||
    return "TCP_NONBLOCKING_FAILED";
 | 
					    return LOG_STR("TCP_NONBLOCKING_FAILED");
 | 
				
			||||||
  } else if (err == APIError::CLOSE_FAILED) {
 | 
					  } else if (err == APIError::CLOSE_FAILED) {
 | 
				
			||||||
    return "CLOSE_FAILED";
 | 
					    return LOG_STR("CLOSE_FAILED");
 | 
				
			||||||
  } else if (err == APIError::SHUTDOWN_FAILED) {
 | 
					  } else if (err == APIError::SHUTDOWN_FAILED) {
 | 
				
			||||||
    return "SHUTDOWN_FAILED";
 | 
					    return LOG_STR("SHUTDOWN_FAILED");
 | 
				
			||||||
  } else if (err == APIError::BAD_STATE) {
 | 
					  } else if (err == APIError::BAD_STATE) {
 | 
				
			||||||
    return "BAD_STATE";
 | 
					    return LOG_STR("BAD_STATE");
 | 
				
			||||||
  } else if (err == APIError::BAD_ARG) {
 | 
					  } else if (err == APIError::BAD_ARG) {
 | 
				
			||||||
    return "BAD_ARG";
 | 
					    return LOG_STR("BAD_ARG");
 | 
				
			||||||
  } else if (err == APIError::SOCKET_READ_FAILED) {
 | 
					  } else if (err == APIError::SOCKET_READ_FAILED) {
 | 
				
			||||||
    return "SOCKET_READ_FAILED";
 | 
					    return LOG_STR("SOCKET_READ_FAILED");
 | 
				
			||||||
  } else if (err == APIError::SOCKET_WRITE_FAILED) {
 | 
					  } else if (err == APIError::SOCKET_WRITE_FAILED) {
 | 
				
			||||||
    return "SOCKET_WRITE_FAILED";
 | 
					    return LOG_STR("SOCKET_WRITE_FAILED");
 | 
				
			||||||
  } else if (err == APIError::OUT_OF_MEMORY) {
 | 
					  } else if (err == APIError::OUT_OF_MEMORY) {
 | 
				
			||||||
    return "OUT_OF_MEMORY";
 | 
					    return LOG_STR("OUT_OF_MEMORY");
 | 
				
			||||||
  } else if (err == APIError::CONNECTION_CLOSED) {
 | 
					  } else if (err == APIError::CONNECTION_CLOSED) {
 | 
				
			||||||
    return "CONNECTION_CLOSED";
 | 
					    return LOG_STR("CONNECTION_CLOSED");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#ifdef USE_API_NOISE
 | 
					#ifdef USE_API_NOISE
 | 
				
			||||||
  else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
 | 
					  else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
 | 
				
			||||||
    return "BAD_HANDSHAKE_PACKET_LEN";
 | 
					    return LOG_STR("BAD_HANDSHAKE_PACKET_LEN");
 | 
				
			||||||
  } else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
 | 
					  } else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
 | 
				
			||||||
    return "HANDSHAKESTATE_READ_FAILED";
 | 
					    return LOG_STR("HANDSHAKESTATE_READ_FAILED");
 | 
				
			||||||
  } else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
 | 
					  } else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
 | 
				
			||||||
    return "HANDSHAKESTATE_WRITE_FAILED";
 | 
					    return LOG_STR("HANDSHAKESTATE_WRITE_FAILED");
 | 
				
			||||||
  } else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
 | 
					  } else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
 | 
				
			||||||
    return "HANDSHAKESTATE_BAD_STATE";
 | 
					    return LOG_STR("HANDSHAKESTATE_BAD_STATE");
 | 
				
			||||||
  } else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
 | 
					  } else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
 | 
				
			||||||
    return "CIPHERSTATE_DECRYPT_FAILED";
 | 
					    return LOG_STR("CIPHERSTATE_DECRYPT_FAILED");
 | 
				
			||||||
  } else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
 | 
					  } else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
 | 
				
			||||||
    return "CIPHERSTATE_ENCRYPT_FAILED";
 | 
					    return LOG_STR("CIPHERSTATE_ENCRYPT_FAILED");
 | 
				
			||||||
  } else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
 | 
					  } else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
 | 
				
			||||||
    return "HANDSHAKESTATE_SETUP_FAILED";
 | 
					    return LOG_STR("HANDSHAKESTATE_SETUP_FAILED");
 | 
				
			||||||
  } else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
 | 
					  } else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
 | 
				
			||||||
    return "HANDSHAKESTATE_SPLIT_FAILED";
 | 
					    return LOG_STR("HANDSHAKESTATE_SPLIT_FAILED");
 | 
				
			||||||
  } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
 | 
					  } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
 | 
				
			||||||
    return "BAD_HANDSHAKE_ERROR_BYTE";
 | 
					    return LOG_STR("BAD_HANDSHAKE_ERROR_BYTE");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  return "UNKNOWN";
 | 
					  return LOG_STR("UNKNOWN");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Default implementation for loop - handles sending buffered data
 | 
					// Default implementation for loop - handles sending buffered data
 | 
				
			||||||
APIError APIFrameHelper::loop() {
 | 
					APIError APIFrameHelper::loop() {
 | 
				
			||||||
  if (!this->tx_buf_.empty()) {
 | 
					  if (this->tx_buf_count_ > 0) {
 | 
				
			||||||
    APIError err = try_send_tx_buf_();
 | 
					    APIError err = try_send_tx_buf_();
 | 
				
			||||||
    if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
 | 
					    if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
 | 
				
			||||||
      return err;
 | 
					      return err;
 | 
				
			||||||
@@ -102,9 +103,20 @@ APIError APIFrameHelper::handle_socket_write_error_() {
 | 
				
			|||||||
// Helper method to buffer data from IOVs
 | 
					// Helper method to buffer data from IOVs
 | 
				
			||||||
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len,
 | 
					void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len,
 | 
				
			||||||
                                           uint16_t offset) {
 | 
					                                           uint16_t offset) {
 | 
				
			||||||
  SendBuffer buffer;
 | 
					  // Check if queue is full
 | 
				
			||||||
  buffer.size = total_write_len - offset;
 | 
					  if (this->tx_buf_count_ >= API_MAX_SEND_QUEUE) {
 | 
				
			||||||
  buffer.data = std::make_unique<uint8_t[]>(buffer.size);
 | 
					    HELPER_LOG("Send queue full (%u buffers), dropping connection", this->tx_buf_count_);
 | 
				
			||||||
 | 
					    this->state_ = State::FAILED;
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint16_t buffer_size = total_write_len - offset;
 | 
				
			||||||
 | 
					  auto &buffer = this->tx_buf_[this->tx_buf_tail_];
 | 
				
			||||||
 | 
					  buffer = std::make_unique<SendBuffer>(SendBuffer{
 | 
				
			||||||
 | 
					      .data = std::make_unique<uint8_t[]>(buffer_size),
 | 
				
			||||||
 | 
					      .size = buffer_size,
 | 
				
			||||||
 | 
					      .offset = 0,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uint16_t to_skip = offset;
 | 
					  uint16_t to_skip = offset;
 | 
				
			||||||
  uint16_t write_pos = 0;
 | 
					  uint16_t write_pos = 0;
 | 
				
			||||||
@@ -117,12 +129,15 @@ void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt,
 | 
				
			|||||||
      // Include this segment (partially or fully)
 | 
					      // Include this segment (partially or fully)
 | 
				
			||||||
      const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip;
 | 
					      const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip;
 | 
				
			||||||
      uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip;
 | 
					      uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip;
 | 
				
			||||||
      std::memcpy(buffer.data.get() + write_pos, src, len);
 | 
					      std::memcpy(buffer->data.get() + write_pos, src, len);
 | 
				
			||||||
      write_pos += len;
 | 
					      write_pos += len;
 | 
				
			||||||
      to_skip = 0;
 | 
					      to_skip = 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->tx_buf_.push_back(std::move(buffer));
 | 
					
 | 
				
			||||||
 | 
					  // Update circular buffer tracking
 | 
				
			||||||
 | 
					  this->tx_buf_tail_ = (this->tx_buf_tail_ + 1) % API_MAX_SEND_QUEUE;
 | 
				
			||||||
 | 
					  this->tx_buf_count_++;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// This method writes data to socket or buffers it
 | 
					// This method writes data to socket or buffers it
 | 
				
			||||||
@@ -140,7 +155,7 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Try to send any existing buffered data first if there is any
 | 
					  // Try to send any existing buffered data first if there is any
 | 
				
			||||||
  if (!this->tx_buf_.empty()) {
 | 
					  if (this->tx_buf_count_ > 0) {
 | 
				
			||||||
    APIError send_result = try_send_tx_buf_();
 | 
					    APIError send_result = try_send_tx_buf_();
 | 
				
			||||||
    // If real error occurred (not just WOULD_BLOCK), return it
 | 
					    // If real error occurred (not just WOULD_BLOCK), return it
 | 
				
			||||||
    if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) {
 | 
					    if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) {
 | 
				
			||||||
@@ -149,7 +164,7 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // If there is still data in the buffer, we can't send, buffer
 | 
					    // If there is still data in the buffer, we can't send, buffer
 | 
				
			||||||
    // the new data and return
 | 
					    // the new data and return
 | 
				
			||||||
    if (!this->tx_buf_.empty()) {
 | 
					    if (this->tx_buf_count_ > 0) {
 | 
				
			||||||
      this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
 | 
					      this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
 | 
				
			||||||
      return APIError::OK;  // Success, data buffered
 | 
					      return APIError::OK;  // Success, data buffered
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -177,32 +192,31 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Common implementation for trying to send buffered data
 | 
					// Common implementation for trying to send buffered data
 | 
				
			||||||
// IMPORTANT: Caller MUST ensure tx_buf_ is not empty before calling this method
 | 
					// IMPORTANT: Caller MUST ensure tx_buf_count_ > 0 before calling this method
 | 
				
			||||||
APIError APIFrameHelper::try_send_tx_buf_() {
 | 
					APIError APIFrameHelper::try_send_tx_buf_() {
 | 
				
			||||||
  // Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check
 | 
					  // Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check
 | 
				
			||||||
  bool tx_buf_empty = false;
 | 
					  while (this->tx_buf_count_ > 0) {
 | 
				
			||||||
  while (!tx_buf_empty) {
 | 
					 | 
				
			||||||
    // Get the first buffer in the queue
 | 
					    // Get the first buffer in the queue
 | 
				
			||||||
    SendBuffer &front_buffer = this->tx_buf_.front();
 | 
					    SendBuffer *front_buffer = this->tx_buf_[this->tx_buf_head_].get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Try to send the remaining data in this buffer
 | 
					    // Try to send the remaining data in this buffer
 | 
				
			||||||
    ssize_t sent = this->socket_->write(front_buffer.current_data(), front_buffer.remaining());
 | 
					    ssize_t sent = this->socket_->write(front_buffer->current_data(), front_buffer->remaining());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (sent == -1) {
 | 
					    if (sent == -1) {
 | 
				
			||||||
      return this->handle_socket_write_error_();
 | 
					      return this->handle_socket_write_error_();
 | 
				
			||||||
    } else if (sent == 0) {
 | 
					    } else if (sent == 0) {
 | 
				
			||||||
      // Nothing sent but not an error
 | 
					      // Nothing sent but not an error
 | 
				
			||||||
      return APIError::WOULD_BLOCK;
 | 
					      return APIError::WOULD_BLOCK;
 | 
				
			||||||
    } else if (static_cast<uint16_t>(sent) < front_buffer.remaining()) {
 | 
					    } else if (static_cast<uint16_t>(sent) < front_buffer->remaining()) {
 | 
				
			||||||
      // Partially sent, update offset
 | 
					      // Partially sent, update offset
 | 
				
			||||||
      // Cast to ensure no overflow issues with uint16_t
 | 
					      // Cast to ensure no overflow issues with uint16_t
 | 
				
			||||||
      front_buffer.offset += static_cast<uint16_t>(sent);
 | 
					      front_buffer->offset += static_cast<uint16_t>(sent);
 | 
				
			||||||
      return APIError::WOULD_BLOCK;  // Stop processing more buffers if we couldn't send a complete buffer
 | 
					      return APIError::WOULD_BLOCK;  // Stop processing more buffers if we couldn't send a complete buffer
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // Buffer completely sent, remove it from the queue
 | 
					      // Buffer completely sent, remove it from the queue
 | 
				
			||||||
      this->tx_buf_.pop_front();
 | 
					      this->tx_buf_[this->tx_buf_head_].reset();
 | 
				
			||||||
      // Update empty status for the loop condition
 | 
					      this->tx_buf_head_ = (this->tx_buf_head_ + 1) % API_MAX_SEND_QUEUE;
 | 
				
			||||||
      tx_buf_empty = this->tx_buf_.empty();
 | 
					      this->tx_buf_count_--;
 | 
				
			||||||
      // Continue loop to try sending the next buffer
 | 
					      // Continue loop to try sending the next buffer
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					#include <array>
 | 
				
			||||||
#include <cstdint>
 | 
					#include <cstdint>
 | 
				
			||||||
#include <deque>
 | 
					 | 
				
			||||||
#include <limits>
 | 
					#include <limits>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
#include <span>
 | 
					#include <span>
 | 
				
			||||||
#include <utility>
 | 
					#include <utility>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
@@ -17,6 +18,17 @@ namespace esphome::api {
 | 
				
			|||||||
// uncomment to log raw packets
 | 
					// uncomment to log raw packets
 | 
				
			||||||
//#define HELPER_LOG_PACKETS
 | 
					//#define HELPER_LOG_PACKETS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Maximum message size limits to prevent OOM on constrained devices
 | 
				
			||||||
 | 
					// Handshake messages are limited to a small size for security
 | 
				
			||||||
 | 
					static constexpr uint16_t MAX_HANDSHAKE_SIZE = 128;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Data message limits vary by platform based on available memory
 | 
				
			||||||
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
 | 
					static constexpr uint16_t MAX_MESSAGE_SIZE = 8192;  // 8 KiB for ESP8266
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					static constexpr uint16_t MAX_MESSAGE_SIZE = 32768;  // 32 KiB for ESP32 and other platforms
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Forward declaration
 | 
					// Forward declaration
 | 
				
			||||||
struct ClientInfo;
 | 
					struct ClientInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -66,7 +78,7 @@ enum class APIError : uint16_t {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const char *api_error_to_str(APIError err);
 | 
					const LogString *api_error_to_logstr(APIError err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class APIFrameHelper {
 | 
					class APIFrameHelper {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
@@ -79,7 +91,7 @@ class APIFrameHelper {
 | 
				
			|||||||
  virtual APIError init() = 0;
 | 
					  virtual APIError init() = 0;
 | 
				
			||||||
  virtual APIError loop();
 | 
					  virtual APIError loop();
 | 
				
			||||||
  virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
 | 
					  virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
 | 
				
			||||||
  bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
 | 
					  bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; }
 | 
				
			||||||
  std::string getpeername() { return socket_->getpeername(); }
 | 
					  std::string getpeername() { return socket_->getpeername(); }
 | 
				
			||||||
  int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
 | 
					  int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
 | 
				
			||||||
  APIError close() {
 | 
					  APIError close() {
 | 
				
			||||||
@@ -104,9 +116,9 @@ class APIFrameHelper {
 | 
				
			|||||||
  // The buffer contains all messages with appropriate padding before each
 | 
					  // The buffer contains all messages with appropriate padding before each
 | 
				
			||||||
  virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
 | 
					  virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
 | 
				
			||||||
  // Get the frame header padding required by this protocol
 | 
					  // Get the frame header padding required by this protocol
 | 
				
			||||||
  virtual uint8_t frame_header_padding() = 0;
 | 
					  uint8_t frame_header_padding() const { return frame_header_padding_; }
 | 
				
			||||||
  // Get the frame footer size required by this protocol
 | 
					  // Get the frame footer size required by this protocol
 | 
				
			||||||
  virtual uint8_t frame_footer_size() = 0;
 | 
					  uint8_t frame_footer_size() const { return frame_footer_size_; }
 | 
				
			||||||
  // Check if socket has data ready to read
 | 
					  // Check if socket has data ready to read
 | 
				
			||||||
  bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
 | 
					  bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -161,7 +173,7 @@ class APIFrameHelper {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Containers (size varies, but typically 12+ bytes on 32-bit)
 | 
					  // Containers (size varies, but typically 12+ bytes on 32-bit)
 | 
				
			||||||
  std::deque<SendBuffer> tx_buf_;
 | 
					  std::array<std::unique_ptr<SendBuffer>, API_MAX_SEND_QUEUE> tx_buf_;
 | 
				
			||||||
  std::vector<struct iovec> reusable_iovs_;
 | 
					  std::vector<struct iovec> reusable_iovs_;
 | 
				
			||||||
  std::vector<uint8_t> rx_buf_;
 | 
					  std::vector<uint8_t> rx_buf_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -174,7 +186,10 @@ class APIFrameHelper {
 | 
				
			|||||||
  State state_{State::INITIALIZE};
 | 
					  State state_{State::INITIALIZE};
 | 
				
			||||||
  uint8_t frame_header_padding_{0};
 | 
					  uint8_t frame_header_padding_{0};
 | 
				
			||||||
  uint8_t frame_footer_size_{0};
 | 
					  uint8_t frame_footer_size_{0};
 | 
				
			||||||
  // 5 bytes total, 3 bytes padding
 | 
					  uint8_t tx_buf_head_{0};
 | 
				
			||||||
 | 
					  uint8_t tx_buf_tail_{0};
 | 
				
			||||||
 | 
					  uint8_t tx_buf_count_{0};
 | 
				
			||||||
 | 
					  // 8 bytes total, 0 bytes padding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Common initialization for both plaintext and noise protocols
 | 
					  // Common initialization for both plaintext and noise protocols
 | 
				
			||||||
  APIError init_common_();
 | 
					  APIError init_common_();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,13 +10,22 @@
 | 
				
			|||||||
#include <cstring>
 | 
					#include <cstring>
 | 
				
			||||||
#include <cinttypes>
 | 
					#include <cinttypes>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
 | 
					#include <pgmspace.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome::api {
 | 
					namespace esphome::api {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char *const TAG = "api.noise";
 | 
					static const char *const TAG = "api.noise";
 | 
				
			||||||
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
 | 
					static const char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit";
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
 | 
					static const char *const PROLOGUE_INIT = "NoiseAPIInit";
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
static constexpr size_t PROLOGUE_INIT_LEN = 12;  // strlen("NoiseAPIInit")
 | 
					static constexpr size_t PROLOGUE_INIT_LEN = 12;  // strlen("NoiseAPIInit")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
 | 
					#define HELPER_LOG(msg, ...) \
 | 
				
			||||||
 | 
					  ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef HELPER_LOG_PACKETS
 | 
					#ifdef HELPER_LOG_PACKETS
 | 
				
			||||||
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
 | 
					#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
 | 
				
			||||||
@@ -27,42 +36,42 @@ static constexpr size_t PROLOGUE_INIT_LEN = 12;  // strlen("NoiseAPIInit")
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Convert a noise error code to a readable error
 | 
					/// Convert a noise error code to a readable error
 | 
				
			||||||
std::string noise_err_to_str(int err) {
 | 
					const LogString *noise_err_to_logstr(int err) {
 | 
				
			||||||
  if (err == NOISE_ERROR_NO_MEMORY)
 | 
					  if (err == NOISE_ERROR_NO_MEMORY)
 | 
				
			||||||
    return "NO_MEMORY";
 | 
					    return LOG_STR("NO_MEMORY");
 | 
				
			||||||
  if (err == NOISE_ERROR_UNKNOWN_ID)
 | 
					  if (err == NOISE_ERROR_UNKNOWN_ID)
 | 
				
			||||||
    return "UNKNOWN_ID";
 | 
					    return LOG_STR("UNKNOWN_ID");
 | 
				
			||||||
  if (err == NOISE_ERROR_UNKNOWN_NAME)
 | 
					  if (err == NOISE_ERROR_UNKNOWN_NAME)
 | 
				
			||||||
    return "UNKNOWN_NAME";
 | 
					    return LOG_STR("UNKNOWN_NAME");
 | 
				
			||||||
  if (err == NOISE_ERROR_MAC_FAILURE)
 | 
					  if (err == NOISE_ERROR_MAC_FAILURE)
 | 
				
			||||||
    return "MAC_FAILURE";
 | 
					    return LOG_STR("MAC_FAILURE");
 | 
				
			||||||
  if (err == NOISE_ERROR_NOT_APPLICABLE)
 | 
					  if (err == NOISE_ERROR_NOT_APPLICABLE)
 | 
				
			||||||
    return "NOT_APPLICABLE";
 | 
					    return LOG_STR("NOT_APPLICABLE");
 | 
				
			||||||
  if (err == NOISE_ERROR_SYSTEM)
 | 
					  if (err == NOISE_ERROR_SYSTEM)
 | 
				
			||||||
    return "SYSTEM";
 | 
					    return LOG_STR("SYSTEM");
 | 
				
			||||||
  if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
 | 
					  if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
 | 
				
			||||||
    return "REMOTE_KEY_REQUIRED";
 | 
					    return LOG_STR("REMOTE_KEY_REQUIRED");
 | 
				
			||||||
  if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
 | 
					  if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
 | 
				
			||||||
    return "LOCAL_KEY_REQUIRED";
 | 
					    return LOG_STR("LOCAL_KEY_REQUIRED");
 | 
				
			||||||
  if (err == NOISE_ERROR_PSK_REQUIRED)
 | 
					  if (err == NOISE_ERROR_PSK_REQUIRED)
 | 
				
			||||||
    return "PSK_REQUIRED";
 | 
					    return LOG_STR("PSK_REQUIRED");
 | 
				
			||||||
  if (err == NOISE_ERROR_INVALID_LENGTH)
 | 
					  if (err == NOISE_ERROR_INVALID_LENGTH)
 | 
				
			||||||
    return "INVALID_LENGTH";
 | 
					    return LOG_STR("INVALID_LENGTH");
 | 
				
			||||||
  if (err == NOISE_ERROR_INVALID_PARAM)
 | 
					  if (err == NOISE_ERROR_INVALID_PARAM)
 | 
				
			||||||
    return "INVALID_PARAM";
 | 
					    return LOG_STR("INVALID_PARAM");
 | 
				
			||||||
  if (err == NOISE_ERROR_INVALID_STATE)
 | 
					  if (err == NOISE_ERROR_INVALID_STATE)
 | 
				
			||||||
    return "INVALID_STATE";
 | 
					    return LOG_STR("INVALID_STATE");
 | 
				
			||||||
  if (err == NOISE_ERROR_INVALID_NONCE)
 | 
					  if (err == NOISE_ERROR_INVALID_NONCE)
 | 
				
			||||||
    return "INVALID_NONCE";
 | 
					    return LOG_STR("INVALID_NONCE");
 | 
				
			||||||
  if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
 | 
					  if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
 | 
				
			||||||
    return "INVALID_PRIVATE_KEY";
 | 
					    return LOG_STR("INVALID_PRIVATE_KEY");
 | 
				
			||||||
  if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
 | 
					  if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
 | 
				
			||||||
    return "INVALID_PUBLIC_KEY";
 | 
					    return LOG_STR("INVALID_PUBLIC_KEY");
 | 
				
			||||||
  if (err == NOISE_ERROR_INVALID_FORMAT)
 | 
					  if (err == NOISE_ERROR_INVALID_FORMAT)
 | 
				
			||||||
    return "INVALID_FORMAT";
 | 
					    return LOG_STR("INVALID_FORMAT");
 | 
				
			||||||
  if (err == NOISE_ERROR_INVALID_SIGNATURE)
 | 
					  if (err == NOISE_ERROR_INVALID_SIGNATURE)
 | 
				
			||||||
    return "INVALID_SIGNATURE";
 | 
					    return LOG_STR("INVALID_SIGNATURE");
 | 
				
			||||||
  return to_string(err);
 | 
					  return LOG_STR("UNKNOWN");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Initialize the frame helper, returns OK if successful.
 | 
					/// Initialize the frame helper, returns OK if successful.
 | 
				
			||||||
@@ -75,7 +84,11 @@ APIError APINoiseFrameHelper::init() {
 | 
				
			|||||||
  // init prologue
 | 
					  // init prologue
 | 
				
			||||||
  size_t old_size = prologue_.size();
 | 
					  size_t old_size = prologue_.size();
 | 
				
			||||||
  prologue_.resize(old_size + PROLOGUE_INIT_LEN);
 | 
					  prologue_.resize(old_size + PROLOGUE_INIT_LEN);
 | 
				
			||||||
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
 | 
					  memcpy_P(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
  std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
 | 
					  std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  state_ = State::CLIENT_HELLO;
 | 
					  state_ = State::CLIENT_HELLO;
 | 
				
			||||||
  return APIError::OK;
 | 
					  return APIError::OK;
 | 
				
			||||||
@@ -83,18 +96,18 @@ APIError APINoiseFrameHelper::init() {
 | 
				
			|||||||
// Helper for handling handshake frame errors
 | 
					// Helper for handling handshake frame errors
 | 
				
			||||||
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
 | 
					APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
 | 
				
			||||||
  if (aerr == APIError::BAD_INDICATOR) {
 | 
					  if (aerr == APIError::BAD_INDICATOR) {
 | 
				
			||||||
    send_explicit_handshake_reject_("Bad indicator byte");
 | 
					    send_explicit_handshake_reject_(LOG_STR("Bad indicator byte"));
 | 
				
			||||||
  } else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
 | 
					  } else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
 | 
				
			||||||
    send_explicit_handshake_reject_("Bad handshake packet len");
 | 
					    send_explicit_handshake_reject_(LOG_STR("Bad handshake packet len"));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return aerr;
 | 
					  return aerr;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Helper for handling noise library errors
 | 
					// Helper for handling noise library errors
 | 
				
			||||||
APIError APINoiseFrameHelper::handle_noise_error_(int err, const char *func_name, APIError api_err) {
 | 
					APIError APINoiseFrameHelper::handle_noise_error_(int err, const LogString *func_name, APIError api_err) {
 | 
				
			||||||
  if (err != 0) {
 | 
					  if (err != 0) {
 | 
				
			||||||
    state_ = State::FAILED;
 | 
					    state_ = State::FAILED;
 | 
				
			||||||
    HELPER_LOG("%s failed: %s", func_name, noise_err_to_str(err).c_str());
 | 
					    HELPER_LOG("%s failed: %s", LOG_STR_ARG(func_name), LOG_STR_ARG(noise_err_to_logstr(err)));
 | 
				
			||||||
    return api_err;
 | 
					    return api_err;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return APIError::OK;
 | 
					  return APIError::OK;
 | 
				
			||||||
@@ -119,26 +132,16 @@ APIError APINoiseFrameHelper::loop() {
 | 
				
			|||||||
  return APIFrameHelper::loop();
 | 
					  return APIFrameHelper::loop();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
 | 
					/** Read a packet into the rx_buf_.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @param frame: The struct to hold the frame information in.
 | 
					 * @return APIError::OK if a full packet is in rx_buf_
 | 
				
			||||||
 *   msg_start: points to the start of the payload - this pointer is only valid until the next
 | 
					 | 
				
			||||||
 *     try_receive_raw_ call
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @return 0 if a full packet is in rx_buf_
 | 
					 | 
				
			||||||
 * @return -1 if error, check errno.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * errno EWOULDBLOCK: Packet could not be read without blocking. Try again later.
 | 
					 * errno EWOULDBLOCK: Packet could not be read without blocking. Try again later.
 | 
				
			||||||
 * errno ENOMEM: Not enough memory for reading packet.
 | 
					 * errno ENOMEM: Not enough memory for reading packet.
 | 
				
			||||||
 * errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
 | 
					 * errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
 | 
				
			||||||
 * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
 | 
					 * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
 | 
					APIError APINoiseFrameHelper::try_read_frame_() {
 | 
				
			||||||
  if (frame == nullptr) {
 | 
					 | 
				
			||||||
    HELPER_LOG("Bad argument for try_read_frame_");
 | 
					 | 
				
			||||||
    return APIError::BAD_ARG;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // read header
 | 
					  // read header
 | 
				
			||||||
  if (rx_header_buf_len_ < 3) {
 | 
					  if (rx_header_buf_len_ < 3) {
 | 
				
			||||||
    // no header information yet
 | 
					    // no header information yet
 | 
				
			||||||
@@ -165,16 +168,17 @@ APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
 | 
				
			|||||||
  // read body
 | 
					  // read body
 | 
				
			||||||
  uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
 | 
					  uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (state_ != State::DATA && msg_size > 128) {
 | 
					  // Check against size limits to prevent OOM: MAX_HANDSHAKE_SIZE for handshake, MAX_MESSAGE_SIZE for data
 | 
				
			||||||
    // for handshake message only permit up to 128 bytes
 | 
					  uint16_t limit = (state_ == State::DATA) ? MAX_MESSAGE_SIZE : MAX_HANDSHAKE_SIZE;
 | 
				
			||||||
 | 
					  if (msg_size > limit) {
 | 
				
			||||||
    state_ = State::FAILED;
 | 
					    state_ = State::FAILED;
 | 
				
			||||||
    HELPER_LOG("Bad packet len for handshake: %d", msg_size);
 | 
					    HELPER_LOG("Bad packet: message size %u exceeds maximum %u", msg_size, limit);
 | 
				
			||||||
    return APIError::BAD_HANDSHAKE_PACKET_LEN;
 | 
					    return (state_ == State::DATA) ? APIError::BAD_DATA_PACKET : APIError::BAD_HANDSHAKE_PACKET_LEN;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // reserve space for body
 | 
					  // Reserve space for body
 | 
				
			||||||
  if (rx_buf_.size() != msg_size) {
 | 
					  if (this->rx_buf_.size() != msg_size) {
 | 
				
			||||||
    rx_buf_.resize(msg_size);
 | 
					    this->rx_buf_.resize(msg_size);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (rx_buf_len_ < msg_size) {
 | 
					  if (rx_buf_len_ < msg_size) {
 | 
				
			||||||
@@ -192,12 +196,12 @@ APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  LOG_PACKET_RECEIVED(rx_buf_);
 | 
					  LOG_PACKET_RECEIVED(this->rx_buf_);
 | 
				
			||||||
  *frame = std::move(rx_buf_);
 | 
					
 | 
				
			||||||
  // consume msg
 | 
					  // Clear state for next frame (rx_buf_ still contains data for caller)
 | 
				
			||||||
  rx_buf_ = {};
 | 
					  this->rx_buf_len_ = 0;
 | 
				
			||||||
  rx_buf_len_ = 0;
 | 
					  this->rx_header_buf_len_ = 0;
 | 
				
			||||||
  rx_header_buf_len_ = 0;
 | 
					
 | 
				
			||||||
  return APIError::OK;
 | 
					  return APIError::OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -219,18 +223,17 @@ APIError APINoiseFrameHelper::state_action_() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  if (state_ == State::CLIENT_HELLO) {
 | 
					  if (state_ == State::CLIENT_HELLO) {
 | 
				
			||||||
    // waiting for client hello
 | 
					    // waiting for client hello
 | 
				
			||||||
    std::vector<uint8_t> frame;
 | 
					    aerr = this->try_read_frame_();
 | 
				
			||||||
    aerr = try_read_frame_(&frame);
 | 
					 | 
				
			||||||
    if (aerr != APIError::OK) {
 | 
					    if (aerr != APIError::OK) {
 | 
				
			||||||
      return handle_handshake_frame_error_(aerr);
 | 
					      return handle_handshake_frame_error_(aerr);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // ignore contents, may be used in future for flags
 | 
					    // ignore contents, may be used in future for flags
 | 
				
			||||||
    // Resize for: existing prologue + 2 size bytes + frame data
 | 
					    // Resize for: existing prologue + 2 size bytes + frame data
 | 
				
			||||||
    size_t old_size = prologue_.size();
 | 
					    size_t old_size = this->prologue_.size();
 | 
				
			||||||
    prologue_.resize(old_size + 2 + frame.size());
 | 
					    this->prologue_.resize(old_size + 2 + this->rx_buf_.size());
 | 
				
			||||||
    prologue_[old_size] = (uint8_t) (frame.size() >> 8);
 | 
					    this->prologue_[old_size] = (uint8_t) (this->rx_buf_.size() >> 8);
 | 
				
			||||||
    prologue_[old_size + 1] = (uint8_t) frame.size();
 | 
					    this->prologue_[old_size + 1] = (uint8_t) this->rx_buf_.size();
 | 
				
			||||||
    std::memcpy(prologue_.data() + old_size + 2, frame.data(), frame.size());
 | 
					    std::memcpy(this->prologue_.data() + old_size + 2, this->rx_buf_.data(), this->rx_buf_.size());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    state_ = State::SERVER_HELLO;
 | 
					    state_ = State::SERVER_HELLO;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -272,29 +275,30 @@ APIError APINoiseFrameHelper::state_action_() {
 | 
				
			|||||||
    int action = noise_handshakestate_get_action(handshake_);
 | 
					    int action = noise_handshakestate_get_action(handshake_);
 | 
				
			||||||
    if (action == NOISE_ACTION_READ_MESSAGE) {
 | 
					    if (action == NOISE_ACTION_READ_MESSAGE) {
 | 
				
			||||||
      // waiting for handshake msg
 | 
					      // waiting for handshake msg
 | 
				
			||||||
      std::vector<uint8_t> frame;
 | 
					      aerr = this->try_read_frame_();
 | 
				
			||||||
      aerr = try_read_frame_(&frame);
 | 
					 | 
				
			||||||
      if (aerr != APIError::OK) {
 | 
					      if (aerr != APIError::OK) {
 | 
				
			||||||
        return handle_handshake_frame_error_(aerr);
 | 
					        return handle_handshake_frame_error_(aerr);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (frame.empty()) {
 | 
					      if (this->rx_buf_.empty()) {
 | 
				
			||||||
        send_explicit_handshake_reject_("Empty handshake message");
 | 
					        send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
 | 
				
			||||||
        return APIError::BAD_HANDSHAKE_ERROR_BYTE;
 | 
					        return APIError::BAD_HANDSHAKE_ERROR_BYTE;
 | 
				
			||||||
      } else if (frame[0] != 0x00) {
 | 
					      } else if (this->rx_buf_[0] != 0x00) {
 | 
				
			||||||
        HELPER_LOG("Bad handshake error byte: %u", frame[0]);
 | 
					        HELPER_LOG("Bad handshake error byte: %u", this->rx_buf_[0]);
 | 
				
			||||||
        send_explicit_handshake_reject_("Bad handshake error byte");
 | 
					        send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
 | 
				
			||||||
        return APIError::BAD_HANDSHAKE_ERROR_BYTE;
 | 
					        return APIError::BAD_HANDSHAKE_ERROR_BYTE;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      NoiseBuffer mbuf;
 | 
					      NoiseBuffer mbuf;
 | 
				
			||||||
      noise_buffer_init(mbuf);
 | 
					      noise_buffer_init(mbuf);
 | 
				
			||||||
      noise_buffer_set_input(mbuf, frame.data() + 1, frame.size() - 1);
 | 
					      noise_buffer_set_input(mbuf, this->rx_buf_.data() + 1, this->rx_buf_.size() - 1);
 | 
				
			||||||
      err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
 | 
					      err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
 | 
				
			||||||
      if (err != 0) {
 | 
					      if (err != 0) {
 | 
				
			||||||
        // Special handling for MAC failure
 | 
					        // Special handling for MAC failure
 | 
				
			||||||
        send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? "Handshake MAC failure" : "Handshake error");
 | 
					        send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? LOG_STR("Handshake MAC failure")
 | 
				
			||||||
        return handle_noise_error_(err, "noise_handshakestate_read_message", APIError::HANDSHAKESTATE_READ_FAILED);
 | 
					                                                                       : LOG_STR("Handshake error"));
 | 
				
			||||||
 | 
					        return handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"),
 | 
				
			||||||
 | 
					                                   APIError::HANDSHAKESTATE_READ_FAILED);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      aerr = check_handshake_finished_();
 | 
					      aerr = check_handshake_finished_();
 | 
				
			||||||
@@ -307,8 +311,8 @@ APIError APINoiseFrameHelper::state_action_() {
 | 
				
			|||||||
      noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
 | 
					      noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
 | 
					      err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
 | 
				
			||||||
      APIError aerr_write =
 | 
					      APIError aerr_write = handle_noise_error_(err, LOG_STR("noise_handshakestate_write_message"),
 | 
				
			||||||
          handle_noise_error_(err, "noise_handshakestate_write_message", APIError::HANDSHAKESTATE_WRITE_FAILED);
 | 
					                                                APIError::HANDSHAKESTATE_WRITE_FAILED);
 | 
				
			||||||
      if (aerr_write != APIError::OK)
 | 
					      if (aerr_write != APIError::OK)
 | 
				
			||||||
        return aerr_write;
 | 
					        return aerr_write;
 | 
				
			||||||
      buffer[0] = 0x00;  // success
 | 
					      buffer[0] = 0x00;  // success
 | 
				
			||||||
@@ -331,15 +335,31 @@ APIError APINoiseFrameHelper::state_action_() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  return APIError::OK;
 | 
					  return APIError::OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) {
 | 
					void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) {
 | 
				
			||||||
 | 
					#ifdef USE_STORE_LOG_STR_IN_FLASH
 | 
				
			||||||
 | 
					  // On ESP8266 with flash strings, we need to use PROGMEM-aware functions
 | 
				
			||||||
 | 
					  size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
 | 
				
			||||||
  std::vector<uint8_t> data;
 | 
					  std::vector<uint8_t> data;
 | 
				
			||||||
  data.resize(reason.length() + 1);
 | 
					  data.resize(reason_len + 1);
 | 
				
			||||||
 | 
					  data[0] = 0x01;  // failure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Copy error message from PROGMEM
 | 
				
			||||||
 | 
					  if (reason_len > 0) {
 | 
				
			||||||
 | 
					    memcpy_P(data.data() + 1, reinterpret_cast<PGM_P>(reason), reason_len);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					  // Normal memory access
 | 
				
			||||||
 | 
					  const char *reason_str = LOG_STR_ARG(reason);
 | 
				
			||||||
 | 
					  size_t reason_len = strlen(reason_str);
 | 
				
			||||||
 | 
					  std::vector<uint8_t> data;
 | 
				
			||||||
 | 
					  data.resize(reason_len + 1);
 | 
				
			||||||
  data[0] = 0x01;  // failure
 | 
					  data[0] = 0x01;  // failure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Copy error message in bulk
 | 
					  // Copy error message in bulk
 | 
				
			||||||
  if (!reason.empty()) {
 | 
					  if (reason_len > 0) {
 | 
				
			||||||
    std::memcpy(data.data() + 1, reason.c_str(), reason.length());
 | 
					    std::memcpy(data.data() + 1, reason_str, reason_len);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // temporarily remove failed state
 | 
					  // temporarily remove failed state
 | 
				
			||||||
  auto orig_state = state_;
 | 
					  auto orig_state = state_;
 | 
				
			||||||
@@ -348,34 +368,33 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &rea
 | 
				
			|||||||
  state_ = orig_state;
 | 
					  state_ = orig_state;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
 | 
					APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
 | 
				
			||||||
  int err;
 | 
					  APIError aerr = this->state_action_();
 | 
				
			||||||
  APIError aerr;
 | 
					 | 
				
			||||||
  aerr = state_action_();
 | 
					 | 
				
			||||||
  if (aerr != APIError::OK) {
 | 
					  if (aerr != APIError::OK) {
 | 
				
			||||||
    return aerr;
 | 
					    return aerr;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (state_ != State::DATA) {
 | 
					  if (this->state_ != State::DATA) {
 | 
				
			||||||
    return APIError::WOULD_BLOCK;
 | 
					    return APIError::WOULD_BLOCK;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::vector<uint8_t> frame;
 | 
					  aerr = this->try_read_frame_();
 | 
				
			||||||
  aerr = try_read_frame_(&frame);
 | 
					 | 
				
			||||||
  if (aerr != APIError::OK)
 | 
					  if (aerr != APIError::OK)
 | 
				
			||||||
    return aerr;
 | 
					    return aerr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  NoiseBuffer mbuf;
 | 
					  NoiseBuffer mbuf;
 | 
				
			||||||
  noise_buffer_init(mbuf);
 | 
					  noise_buffer_init(mbuf);
 | 
				
			||||||
  noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
 | 
					  noise_buffer_set_inout(mbuf, this->rx_buf_.data(), this->rx_buf_.size(), this->rx_buf_.size());
 | 
				
			||||||
  err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
 | 
					  int err = noise_cipherstate_decrypt(this->recv_cipher_, &mbuf);
 | 
				
			||||||
  APIError decrypt_err = handle_noise_error_(err, "noise_cipherstate_decrypt", APIError::CIPHERSTATE_DECRYPT_FAILED);
 | 
					  APIError decrypt_err =
 | 
				
			||||||
  if (decrypt_err != APIError::OK)
 | 
					      handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED);
 | 
				
			||||||
 | 
					  if (decrypt_err != APIError::OK) {
 | 
				
			||||||
    return decrypt_err;
 | 
					    return decrypt_err;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uint16_t msg_size = mbuf.size;
 | 
					  uint16_t msg_size = mbuf.size;
 | 
				
			||||||
  uint8_t *msg_data = frame.data();
 | 
					  uint8_t *msg_data = this->rx_buf_.data();
 | 
				
			||||||
  if (msg_size < 4) {
 | 
					  if (msg_size < 4) {
 | 
				
			||||||
    state_ = State::FAILED;
 | 
					    this->state_ = State::FAILED;
 | 
				
			||||||
    HELPER_LOG("Bad data packet: size %d too short", msg_size);
 | 
					    HELPER_LOG("Bad data packet: size %d too short", msg_size);
 | 
				
			||||||
    return APIError::BAD_DATA_PACKET;
 | 
					    return APIError::BAD_DATA_PACKET;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -383,12 +402,12 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
 | 
				
			|||||||
  uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
 | 
					  uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
 | 
				
			||||||
  uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
 | 
					  uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
 | 
				
			||||||
  if (data_len > msg_size - 4) {
 | 
					  if (data_len > msg_size - 4) {
 | 
				
			||||||
    state_ = State::FAILED;
 | 
					    this->state_ = State::FAILED;
 | 
				
			||||||
    HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
 | 
					    HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
 | 
				
			||||||
    return APIError::BAD_DATA_PACKET;
 | 
					    return APIError::BAD_DATA_PACKET;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  buffer->container = std::move(frame);
 | 
					  buffer->container = std::move(this->rx_buf_);
 | 
				
			||||||
  buffer->data_offset = 4;
 | 
					  buffer->data_offset = 4;
 | 
				
			||||||
  buffer->data_len = data_len;
 | 
					  buffer->data_len = data_len;
 | 
				
			||||||
  buffer->type = type;
 | 
					  buffer->type = type;
 | 
				
			||||||
@@ -450,7 +469,8 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
 | 
				
			|||||||
                           4 + packet.payload_size + frame_footer_size_);
 | 
					                           4 + packet.payload_size + frame_footer_size_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
 | 
					    int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
 | 
				
			||||||
    APIError aerr = handle_noise_error_(err, "noise_cipherstate_encrypt", APIError::CIPHERSTATE_ENCRYPT_FAILED);
 | 
					    APIError aerr =
 | 
				
			||||||
 | 
					        handle_noise_error_(err, LOG_STR("noise_cipherstate_encrypt"), APIError::CIPHERSTATE_ENCRYPT_FAILED);
 | 
				
			||||||
    if (aerr != APIError::OK)
 | 
					    if (aerr != APIError::OK)
 | 
				
			||||||
      return aerr;
 | 
					      return aerr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -504,25 +524,27 @@ APIError APINoiseFrameHelper::init_handshake_() {
 | 
				
			|||||||
  nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
 | 
					  nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
 | 
					  err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
 | 
				
			||||||
  APIError aerr = handle_noise_error_(err, "noise_handshakestate_new_by_id", APIError::HANDSHAKESTATE_SETUP_FAILED);
 | 
					  APIError aerr =
 | 
				
			||||||
 | 
					      handle_noise_error_(err, LOG_STR("noise_handshakestate_new_by_id"), APIError::HANDSHAKESTATE_SETUP_FAILED);
 | 
				
			||||||
  if (aerr != APIError::OK)
 | 
					  if (aerr != APIError::OK)
 | 
				
			||||||
    return aerr;
 | 
					    return aerr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const auto &psk = ctx_->get_psk();
 | 
					  const auto &psk = ctx_->get_psk();
 | 
				
			||||||
  err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
 | 
					  err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
 | 
				
			||||||
  aerr = handle_noise_error_(err, "noise_handshakestate_set_pre_shared_key", APIError::HANDSHAKESTATE_SETUP_FAILED);
 | 
					  aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"),
 | 
				
			||||||
 | 
					                             APIError::HANDSHAKESTATE_SETUP_FAILED);
 | 
				
			||||||
  if (aerr != APIError::OK)
 | 
					  if (aerr != APIError::OK)
 | 
				
			||||||
    return aerr;
 | 
					    return aerr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
 | 
					  err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
 | 
				
			||||||
  aerr = handle_noise_error_(err, "noise_handshakestate_set_prologue", APIError::HANDSHAKESTATE_SETUP_FAILED);
 | 
					  aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_prologue"), APIError::HANDSHAKESTATE_SETUP_FAILED);
 | 
				
			||||||
  if (aerr != APIError::OK)
 | 
					  if (aerr != APIError::OK)
 | 
				
			||||||
    return aerr;
 | 
					    return aerr;
 | 
				
			||||||
  // set_prologue copies it into handshakestate, so we can get rid of it now
 | 
					  // set_prologue copies it into handshakestate, so we can get rid of it now
 | 
				
			||||||
  prologue_ = {};
 | 
					  prologue_ = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  err = noise_handshakestate_start(handshake_);
 | 
					  err = noise_handshakestate_start(handshake_);
 | 
				
			||||||
  aerr = handle_noise_error_(err, "noise_handshakestate_start", APIError::HANDSHAKESTATE_SETUP_FAILED);
 | 
					  aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED);
 | 
				
			||||||
  if (aerr != APIError::OK)
 | 
					  if (aerr != APIError::OK)
 | 
				
			||||||
    return aerr;
 | 
					    return aerr;
 | 
				
			||||||
  return APIError::OK;
 | 
					  return APIError::OK;
 | 
				
			||||||
@@ -540,7 +562,8 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
 | 
				
			|||||||
    return APIError::HANDSHAKESTATE_BAD_STATE;
 | 
					    return APIError::HANDSHAKESTATE_BAD_STATE;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
 | 
					  int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
 | 
				
			||||||
  APIError aerr = handle_noise_error_(err, "noise_handshakestate_split", APIError::HANDSHAKESTATE_SPLIT_FAILED);
 | 
					  APIError aerr =
 | 
				
			||||||
 | 
					      handle_noise_error_(err, LOG_STR("noise_handshakestate_split"), APIError::HANDSHAKESTATE_SPLIT_FAILED);
 | 
				
			||||||
  if (aerr != APIError::OK)
 | 
					  if (aerr != APIError::OK)
 | 
				
			||||||
    return aerr;
 | 
					    return aerr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace esphome::api {
 | 
					namespace esphome::api {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class APINoiseFrameHelper : public APIFrameHelper {
 | 
					class APINoiseFrameHelper final : public APIFrameHelper {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
 | 
					  APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
 | 
				
			||||||
                      const ClientInfo *client_info)
 | 
					                      const ClientInfo *client_info)
 | 
				
			||||||
@@ -25,20 +25,16 @@ class APINoiseFrameHelper : public APIFrameHelper {
 | 
				
			|||||||
  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
					  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
				
			||||||
  APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
 | 
					  APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
 | 
				
			||||||
  APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
 | 
					  APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
 | 
				
			||||||
  // Get the frame header padding required by this protocol
 | 
					 | 
				
			||||||
  uint8_t frame_header_padding() override { return frame_header_padding_; }
 | 
					 | 
				
			||||||
  // Get the frame footer size required by this protocol
 | 
					 | 
				
			||||||
  uint8_t frame_footer_size() override { return frame_footer_size_; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  APIError state_action_();
 | 
					  APIError state_action_();
 | 
				
			||||||
  APIError try_read_frame_(std::vector<uint8_t> *frame);
 | 
					  APIError try_read_frame_();
 | 
				
			||||||
  APIError write_frame_(const uint8_t *data, uint16_t len);
 | 
					  APIError write_frame_(const uint8_t *data, uint16_t len);
 | 
				
			||||||
  APIError init_handshake_();
 | 
					  APIError init_handshake_();
 | 
				
			||||||
  APIError check_handshake_finished_();
 | 
					  APIError check_handshake_finished_();
 | 
				
			||||||
  void send_explicit_handshake_reject_(const std::string &reason);
 | 
					  void send_explicit_handshake_reject_(const LogString *reason);
 | 
				
			||||||
  APIError handle_handshake_frame_error_(APIError aerr);
 | 
					  APIError handle_handshake_frame_error_(APIError aerr);
 | 
				
			||||||
  APIError handle_noise_error_(int err, const char *func_name, APIError api_err);
 | 
					  APIError handle_noise_error_(int err, const LogString *func_name, APIError api_err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Pointers first (4 bytes each)
 | 
					  // Pointers first (4 bytes each)
 | 
				
			||||||
  NoiseHandshakeState *handshake_{nullptr};
 | 
					  NoiseHandshakeState *handshake_{nullptr};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,11 +10,16 @@
 | 
				
			|||||||
#include <cstring>
 | 
					#include <cstring>
 | 
				
			||||||
#include <cinttypes>
 | 
					#include <cinttypes>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
 | 
					#include <pgmspace.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome::api {
 | 
					namespace esphome::api {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char *const TAG = "api.plaintext";
 | 
					static const char *const TAG = "api.plaintext";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
 | 
					#define HELPER_LOG(msg, ...) \
 | 
				
			||||||
 | 
					  ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef HELPER_LOG_PACKETS
 | 
					#ifdef HELPER_LOG_PACKETS
 | 
				
			||||||
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
 | 
					#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
 | 
				
			||||||
@@ -42,21 +47,13 @@ APIError APIPlaintextFrameHelper::loop() {
 | 
				
			|||||||
  return APIFrameHelper::loop();
 | 
					  return APIFrameHelper::loop();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
 | 
					/** Read a packet into the rx_buf_.
 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @param frame: The struct to hold the frame information in.
 | 
					 | 
				
			||||||
 *   msg: store the parsed frame in that struct
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @return See APIError
 | 
					 * @return See APIError
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
 | 
					 * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
 | 
					APIError APIPlaintextFrameHelper::try_read_frame_() {
 | 
				
			||||||
  if (frame == nullptr) {
 | 
					 | 
				
			||||||
    HELPER_LOG("Bad argument for try_read_frame_");
 | 
					 | 
				
			||||||
    return APIError::BAD_ARG;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // read header
 | 
					  // read header
 | 
				
			||||||
  while (!rx_header_parsed_) {
 | 
					  while (!rx_header_parsed_) {
 | 
				
			||||||
    // Now that we know when the socket is ready, we can read up to 3 bytes
 | 
					    // Now that we know when the socket is ready, we can read up to 3 bytes
 | 
				
			||||||
@@ -118,10 +115,10 @@ APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
 | 
				
			|||||||
      continue;
 | 
					      continue;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (msg_size_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) {
 | 
					    if (msg_size_varint->as_uint32() > MAX_MESSAGE_SIZE) {
 | 
				
			||||||
      state_ = State::FAILED;
 | 
					      state_ = State::FAILED;
 | 
				
			||||||
      HELPER_LOG("Bad packet: message size %" PRIu32 " exceeds maximum %u", msg_size_varint->as_uint32(),
 | 
					      HELPER_LOG("Bad packet: message size %" PRIu32 " exceeds maximum %u", msg_size_varint->as_uint32(),
 | 
				
			||||||
                 std::numeric_limits<uint16_t>::max());
 | 
					                 MAX_MESSAGE_SIZE);
 | 
				
			||||||
      return APIError::BAD_DATA_PACKET;
 | 
					      return APIError::BAD_DATA_PACKET;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    rx_header_parsed_len_ = msg_size_varint->as_uint16();
 | 
					    rx_header_parsed_len_ = msg_size_varint->as_uint16();
 | 
				
			||||||
@@ -145,9 +142,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  // header reading done
 | 
					  // header reading done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // reserve space for body
 | 
					  // Reserve space for body
 | 
				
			||||||
  if (rx_buf_.size() != rx_header_parsed_len_) {
 | 
					  if (this->rx_buf_.size() != this->rx_header_parsed_len_) {
 | 
				
			||||||
    rx_buf_.resize(rx_header_parsed_len_);
 | 
					    this->rx_buf_.resize(this->rx_header_parsed_len_);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (rx_buf_len_ < rx_header_parsed_len_) {
 | 
					  if (rx_buf_len_ < rx_header_parsed_len_) {
 | 
				
			||||||
@@ -165,24 +162,22 @@ APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  LOG_PACKET_RECEIVED(rx_buf_);
 | 
					  LOG_PACKET_RECEIVED(this->rx_buf_);
 | 
				
			||||||
  *frame = std::move(rx_buf_);
 | 
					
 | 
				
			||||||
  // consume msg
 | 
					  // Clear state for next frame (rx_buf_ still contains data for caller)
 | 
				
			||||||
  rx_buf_ = {};
 | 
					  this->rx_buf_len_ = 0;
 | 
				
			||||||
  rx_buf_len_ = 0;
 | 
					  this->rx_header_buf_pos_ = 0;
 | 
				
			||||||
  rx_header_buf_pos_ = 0;
 | 
					  this->rx_header_parsed_ = false;
 | 
				
			||||||
  rx_header_parsed_ = false;
 | 
					
 | 
				
			||||||
  return APIError::OK;
 | 
					  return APIError::OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
 | 
					 | 
				
			||||||
  APIError aerr;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (state_ != State::DATA) {
 | 
					APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
 | 
				
			||||||
 | 
					  if (this->state_ != State::DATA) {
 | 
				
			||||||
    return APIError::WOULD_BLOCK;
 | 
					    return APIError::WOULD_BLOCK;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::vector<uint8_t> frame;
 | 
					  APIError aerr = this->try_read_frame_();
 | 
				
			||||||
  aerr = try_read_frame_(&frame);
 | 
					 | 
				
			||||||
  if (aerr != APIError::OK) {
 | 
					  if (aerr != APIError::OK) {
 | 
				
			||||||
    if (aerr == APIError::BAD_INDICATOR) {
 | 
					    if (aerr == APIError::BAD_INDICATOR) {
 | 
				
			||||||
      // Make sure to tell the remote that we don't
 | 
					      // Make sure to tell the remote that we don't
 | 
				
			||||||
@@ -197,19 +192,28 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
 | 
				
			|||||||
      // We must send at least 3 bytes to be read, so we add
 | 
					      // We must send at least 3 bytes to be read, so we add
 | 
				
			||||||
      // a message after the indicator byte to ensures its long
 | 
					      // a message after the indicator byte to ensures its long
 | 
				
			||||||
      // enough and can aid in debugging.
 | 
					      // enough and can aid in debugging.
 | 
				
			||||||
      const char msg[] = "\x00"
 | 
					      static constexpr uint8_t INDICATOR_MSG_SIZE = 19;
 | 
				
			||||||
                         "Bad indicator byte";
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
 | 
					      static const char MSG_PROGMEM[] PROGMEM = "\x00"
 | 
				
			||||||
 | 
					                                                "Bad indicator byte";
 | 
				
			||||||
 | 
					      char msg[INDICATOR_MSG_SIZE];
 | 
				
			||||||
 | 
					      memcpy_P(msg, MSG_PROGMEM, INDICATOR_MSG_SIZE);
 | 
				
			||||||
      iov[0].iov_base = (void *) msg;
 | 
					      iov[0].iov_base = (void *) msg;
 | 
				
			||||||
      iov[0].iov_len = 19;
 | 
					#else
 | 
				
			||||||
      this->write_raw_(iov, 1, 19);
 | 
					      static const char MSG[] = "\x00"
 | 
				
			||||||
 | 
					                                "Bad indicator byte";
 | 
				
			||||||
 | 
					      iov[0].iov_base = (void *) MSG;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					      iov[0].iov_len = INDICATOR_MSG_SIZE;
 | 
				
			||||||
 | 
					      this->write_raw_(iov, 1, INDICATOR_MSG_SIZE);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return aerr;
 | 
					    return aerr;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  buffer->container = std::move(frame);
 | 
					  buffer->container = std::move(this->rx_buf_);
 | 
				
			||||||
  buffer->data_offset = 0;
 | 
					  buffer->data_offset = 0;
 | 
				
			||||||
  buffer->data_len = rx_header_parsed_len_;
 | 
					  buffer->data_len = this->rx_header_parsed_len_;
 | 
				
			||||||
  buffer->type = rx_header_parsed_type_;
 | 
					  buffer->type = this->rx_header_parsed_type_;
 | 
				
			||||||
  return APIError::OK;
 | 
					  return APIError::OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
 | 
					APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace esphome::api {
 | 
					namespace esphome::api {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
					class APIPlaintextFrameHelper final : public APIFrameHelper {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
 | 
					  APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
 | 
				
			||||||
      : APIFrameHelper(std::move(socket), client_info) {
 | 
					      : APIFrameHelper(std::move(socket), client_info) {
 | 
				
			||||||
@@ -22,12 +22,9 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
				
			|||||||
  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
					  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
				
			||||||
  APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
 | 
					  APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
 | 
				
			||||||
  APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
 | 
					  APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
 | 
				
			||||||
  uint8_t frame_header_padding() override { return frame_header_padding_; }
 | 
					 | 
				
			||||||
  // Get the frame footer size required by this protocol
 | 
					 | 
				
			||||||
  uint8_t frame_footer_size() override { return frame_footer_size_; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  APIError try_read_frame_(std::vector<uint8_t> *frame);
 | 
					  APIError try_read_frame_();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Group 2-byte aligned types
 | 
					  // Group 2-byte aligned types
 | 
				
			||||||
  uint16_t rx_header_parsed_type_ = 0;
 | 
					  uint16_t rx_header_parsed_type_ = 0;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,6 +32,13 @@ extend google.protobuf.FieldOptions {
 | 
				
			|||||||
    optional string fixed_array_size_define = 50010;
 | 
					    optional string fixed_array_size_define = 50010;
 | 
				
			||||||
    optional string fixed_array_with_length_define = 50011;
 | 
					    optional string fixed_array_with_length_define = 50011;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // pointer_to_buffer: Use pointer instead of array for fixed-size byte fields
 | 
				
			||||||
 | 
					    // When set, the field will be declared as a pointer (const uint8_t *data)
 | 
				
			||||||
 | 
					    // instead of an array (uint8_t data[N]). This allows zero-copy on decode
 | 
				
			||||||
 | 
					    // by pointing directly to the protobuf buffer. The buffer must remain valid
 | 
				
			||||||
 | 
					    // until the message is processed (which is guaranteed for stack-allocated messages).
 | 
				
			||||||
 | 
					    optional bool pointer_to_buffer = 50012 [default=false];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // container_pointer: Zero-copy optimization for repeated fields.
 | 
					    // container_pointer: Zero-copy optimization for repeated fields.
 | 
				
			||||||
    //
 | 
					    //
 | 
				
			||||||
    // When container_pointer is set on a repeated field, the generated message will
 | 
					    // When container_pointer is set on a repeated field, the generated message will
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,9 +22,12 @@ bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
					bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
  switch (field_id) {
 | 
					  switch (field_id) {
 | 
				
			||||||
    case 1:
 | 
					    case 1: {
 | 
				
			||||||
      this->client_info = value.as_string();
 | 
					      // Use raw data directly to avoid allocation
 | 
				
			||||||
 | 
					      this->client_info = value.data();
 | 
				
			||||||
 | 
					      this->client_info_len = value.size();
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -42,18 +45,23 @@ void HelloResponse::calculate_size(ProtoSize &size) const {
 | 
				
			|||||||
  size.add_length(1, this->server_info_ref_.size());
 | 
					  size.add_length(1, this->server_info_ref_.size());
 | 
				
			||||||
  size.add_length(1, this->name_ref_.size());
 | 
					  size.add_length(1, this->name_ref_.size());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
 | 
					bool AuthenticationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
  switch (field_id) {
 | 
					  switch (field_id) {
 | 
				
			||||||
    case 1:
 | 
					    case 1: {
 | 
				
			||||||
      this->password = value.as_string();
 | 
					      // Use raw data directly to avoid allocation
 | 
				
			||||||
 | 
					      this->password = value.data();
 | 
				
			||||||
 | 
					      this->password_len = value.size();
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
 | 
					void AuthenticationResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
 | 
				
			||||||
void ConnectResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->invalid_password); }
 | 
					void AuthenticationResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->invalid_password); }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
#ifdef USE_AREAS
 | 
					#ifdef USE_AREAS
 | 
				
			||||||
void AreaInfo::encode(ProtoWriteBuffer buffer) const {
 | 
					void AreaInfo::encode(ProtoWriteBuffer buffer) const {
 | 
				
			||||||
  buffer.encode_uint32(1, this->area_id);
 | 
					  buffer.encode_uint32(1, this->area_id);
 | 
				
			||||||
@@ -127,6 +135,12 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
 | 
				
			|||||||
#ifdef USE_AREAS
 | 
					#ifdef USE_AREAS
 | 
				
			||||||
  buffer.encode_message(22, this->area);
 | 
					  buffer.encode_message(22, this->area);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  buffer.encode_uint32(23, this->zwave_proxy_feature_flags);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  buffer.encode_uint32(24, this->zwave_home_id);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
 | 
					void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
 | 
				
			||||||
#ifdef USE_API_PASSWORD
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
@@ -179,6 +193,12 @@ void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
 | 
				
			|||||||
#ifdef USE_AREAS
 | 
					#ifdef USE_AREAS
 | 
				
			||||||
  size.add_message_object(2, this->area);
 | 
					  size.add_message_object(2, this->area);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  size.add_uint32(2, this->zwave_proxy_feature_flags);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  size.add_uint32(2, this->zwave_home_id);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#ifdef USE_BINARY_SENSOR
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
 | 
					void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
 | 
				
			||||||
@@ -852,7 +872,7 @@ void HomeassistantServiceMap::calculate_size(ProtoSize &size) const {
 | 
				
			|||||||
  size.add_length(1, this->key_ref_.size());
 | 
					  size.add_length(1, this->key_ref_.size());
 | 
				
			||||||
  size.add_length(1, this->value.size());
 | 
					  size.add_length(1, this->value.size());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const {
 | 
					void HomeassistantActionRequest::encode(ProtoWriteBuffer buffer) const {
 | 
				
			||||||
  buffer.encode_string(1, this->service_ref_);
 | 
					  buffer.encode_string(1, this->service_ref_);
 | 
				
			||||||
  for (auto &it : this->data) {
 | 
					  for (auto &it : this->data) {
 | 
				
			||||||
    buffer.encode_message(2, it, true);
 | 
					    buffer.encode_message(2, it, true);
 | 
				
			||||||
@@ -864,13 +884,64 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const {
 | 
				
			|||||||
    buffer.encode_message(4, it, true);
 | 
					    buffer.encode_message(4, it, true);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  buffer.encode_bool(5, this->is_event);
 | 
					  buffer.encode_bool(5, this->is_event);
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					  buffer.encode_uint32(6, this->call_id);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  buffer.encode_bool(7, this->wants_response);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  buffer.encode_string(8, this->response_template);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void HomeassistantServiceResponse::calculate_size(ProtoSize &size) const {
 | 
					void HomeassistantActionRequest::calculate_size(ProtoSize &size) const {
 | 
				
			||||||
  size.add_length(1, this->service_ref_.size());
 | 
					  size.add_length(1, this->service_ref_.size());
 | 
				
			||||||
  size.add_repeated_message(1, this->data);
 | 
					  size.add_repeated_message(1, this->data);
 | 
				
			||||||
  size.add_repeated_message(1, this->data_template);
 | 
					  size.add_repeated_message(1, this->data_template);
 | 
				
			||||||
  size.add_repeated_message(1, this->variables);
 | 
					  size.add_repeated_message(1, this->variables);
 | 
				
			||||||
  size.add_bool(1, this->is_event);
 | 
					  size.add_bool(1, this->is_event);
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					  size.add_uint32(1, this->call_id);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  size.add_bool(1, this->wants_response);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  size.add_length(1, this->response_template.size());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					bool HomeassistantActionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 1:
 | 
				
			||||||
 | 
					      this->call_id = value.as_uint32();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case 2:
 | 
				
			||||||
 | 
					      this->success = value.as_bool();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 3:
 | 
				
			||||||
 | 
					      this->error_message = value.as_string();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					    case 4: {
 | 
				
			||||||
 | 
					      // Use raw data directly to avoid allocation
 | 
				
			||||||
 | 
					      this->response_data = value.data();
 | 
				
			||||||
 | 
					      this->response_data_len = value.size();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_API_HOMEASSISTANT_STATES
 | 
					#ifdef USE_API_HOMEASSISTANT_STATES
 | 
				
			||||||
@@ -901,6 +972,19 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
 | 
				
			|||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 2: {
 | 
				
			||||||
 | 
					      // Use raw data directly to avoid allocation
 | 
				
			||||||
 | 
					      this->timezone = value.data();
 | 
				
			||||||
 | 
					      this->timezone_len = value.size();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
					bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
				
			||||||
  switch (field_id) {
 | 
					  switch (field_id) {
 | 
				
			||||||
    case 1:
 | 
					    case 1:
 | 
				
			||||||
@@ -911,8 +995,6 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
 | 
					 | 
				
			||||||
void GetTimeResponse::calculate_size(ProtoSize &size) const { size.add_fixed32(1, this->epoch_seconds); }
 | 
					 | 
				
			||||||
#ifdef USE_API_SERVICES
 | 
					#ifdef USE_API_SERVICES
 | 
				
			||||||
void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
 | 
					void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
 | 
				
			||||||
  buffer.encode_string(1, this->name_ref_);
 | 
					  buffer.encode_string(1, this->name_ref_);
 | 
				
			||||||
@@ -2006,9 +2088,12 @@ bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt val
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
					bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
  switch (field_id) {
 | 
					  switch (field_id) {
 | 
				
			||||||
    case 4:
 | 
					    case 4: {
 | 
				
			||||||
      this->data = value.as_string();
 | 
					      // Use raw data directly to avoid allocation
 | 
				
			||||||
 | 
					      this->data = value.data();
 | 
				
			||||||
 | 
					      this->data_len = value.size();
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -2042,9 +2127,12 @@ bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, Proto
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
					bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
  switch (field_id) {
 | 
					  switch (field_id) {
 | 
				
			||||||
    case 3:
 | 
					    case 3: {
 | 
				
			||||||
      this->data = value.as_string();
 | 
					      // Use raw data directly to avoid allocation
 | 
				
			||||||
 | 
					      this->data = value.data();
 | 
				
			||||||
 | 
					      this->data_len = value.size();
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -2153,10 +2241,12 @@ void BluetoothDeviceClearCacheResponse::calculate_size(ProtoSize &size) const {
 | 
				
			|||||||
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
					void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
				
			||||||
  buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
 | 
					  buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
 | 
				
			||||||
  buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
 | 
					  buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
 | 
				
			||||||
 | 
					  buffer.encode_uint32(3, static_cast<uint32_t>(this->configured_mode));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const {
 | 
					void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const {
 | 
				
			||||||
  size.add_uint32(1, static_cast<uint32_t>(this->state));
 | 
					  size.add_uint32(1, static_cast<uint32_t>(this->state));
 | 
				
			||||||
  size.add_uint32(1, static_cast<uint32_t>(this->mode));
 | 
					  size.add_uint32(1, static_cast<uint32_t>(this->mode));
 | 
				
			||||||
 | 
					  size.add_uint32(1, static_cast<uint32_t>(this->configured_mode));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
					bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
				
			||||||
  switch (field_id) {
 | 
					  switch (field_id) {
 | 
				
			||||||
@@ -2358,6 +2448,52 @@ void VoiceAssistantWakeWord::calculate_size(ProtoSize &size) const {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					bool VoiceAssistantExternalWakeWord::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 5:
 | 
				
			||||||
 | 
					      this->model_size = value.as_uint32();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					bool VoiceAssistantExternalWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 1:
 | 
				
			||||||
 | 
					      this->id = value.as_string();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case 2:
 | 
				
			||||||
 | 
					      this->wake_word = value.as_string();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case 3:
 | 
				
			||||||
 | 
					      this->trained_languages.push_back(value.as_string());
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case 4:
 | 
				
			||||||
 | 
					      this->model_type = value.as_string();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case 6:
 | 
				
			||||||
 | 
					      this->model_hash = value.as_string();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case 7:
 | 
				
			||||||
 | 
					      this->url = value.as_string();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					bool VoiceAssistantConfigurationRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 1:
 | 
				
			||||||
 | 
					      this->external_wake_words.emplace_back();
 | 
				
			||||||
 | 
					      value.decode_to_message(this->external_wake_words.back());
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const {
 | 
					void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const {
 | 
				
			||||||
  for (auto &it : this->available_wake_words) {
 | 
					  for (auto &it : this->available_wake_words) {
 | 
				
			||||||
    buffer.encode_message(1, it, true);
 | 
					    buffer.encode_message(1, it, true);
 | 
				
			||||||
@@ -3001,5 +3137,53 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
 | 
				
			|||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 1: {
 | 
				
			||||||
 | 
					      // Use raw data directly to avoid allocation
 | 
				
			||||||
 | 
					      this->data = value.data();
 | 
				
			||||||
 | 
					      this->data_len = value.size();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void ZWaveProxyFrame::encode(ProtoWriteBuffer buffer) const { buffer.encode_bytes(1, this->data, this->data_len); }
 | 
				
			||||||
 | 
					void ZWaveProxyFrame::calculate_size(ProtoSize &size) const { size.add_length(1, this->data_len); }
 | 
				
			||||||
 | 
					bool ZWaveProxyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 1:
 | 
				
			||||||
 | 
					      this->type = static_cast<enums::ZWaveProxyRequestType>(value.as_uint32());
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					bool ZWaveProxyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
 | 
				
			||||||
 | 
					  switch (field_id) {
 | 
				
			||||||
 | 
					    case 2: {
 | 
				
			||||||
 | 
					      // Use raw data directly to avoid allocation
 | 
				
			||||||
 | 
					      this->data = value.data();
 | 
				
			||||||
 | 
					      this->data_len = value.size();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void ZWaveProxyRequest::encode(ProtoWriteBuffer buffer) const {
 | 
				
			||||||
 | 
					  buffer.encode_uint32(1, static_cast<uint32_t>(this->type));
 | 
				
			||||||
 | 
					  buffer.encode_bytes(2, this->data, this->data_len);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void ZWaveProxyRequest::calculate_size(ProtoSize &size) const {
 | 
				
			||||||
 | 
					  size.add_uint32(1, static_cast<uint32_t>(this->type));
 | 
				
			||||||
 | 
					  size.add_length(2, this->data_len);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace esphome::api
 | 
					}  // namespace esphome::api
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -655,10 +655,26 @@ template<> const char *proto_enum_to_string<enums::UpdateCommand>(enums::UpdateC
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums::ZWaveProxyRequestType value) {
 | 
				
			||||||
 | 
					  switch (value) {
 | 
				
			||||||
 | 
					    case enums::ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE:
 | 
				
			||||||
 | 
					      return "ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE";
 | 
				
			||||||
 | 
					    case enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE:
 | 
				
			||||||
 | 
					      return "ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE";
 | 
				
			||||||
 | 
					    case enums::ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE:
 | 
				
			||||||
 | 
					      return "ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE";
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return "UNKNOWN";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void HelloRequest::dump_to(std::string &out) const {
 | 
					void HelloRequest::dump_to(std::string &out) const {
 | 
				
			||||||
  MessageDumpHelper helper(out, "HelloRequest");
 | 
					  MessageDumpHelper helper(out, "HelloRequest");
 | 
				
			||||||
  dump_field(out, "client_info", this->client_info);
 | 
					  out.append("  client_info: ");
 | 
				
			||||||
 | 
					  out.append(format_hex_pretty(this->client_info, this->client_info_len));
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
  dump_field(out, "api_version_major", this->api_version_major);
 | 
					  dump_field(out, "api_version_major", this->api_version_major);
 | 
				
			||||||
  dump_field(out, "api_version_minor", this->api_version_minor);
 | 
					  dump_field(out, "api_version_minor", this->api_version_minor);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -669,8 +685,18 @@ void HelloResponse::dump_to(std::string &out) const {
 | 
				
			|||||||
  dump_field(out, "server_info", this->server_info_ref_);
 | 
					  dump_field(out, "server_info", this->server_info_ref_);
 | 
				
			||||||
  dump_field(out, "name", this->name_ref_);
 | 
					  dump_field(out, "name", this->name_ref_);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void ConnectRequest::dump_to(std::string &out) const { dump_field(out, "password", this->password); }
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
void ConnectResponse::dump_to(std::string &out) const { dump_field(out, "invalid_password", this->invalid_password); }
 | 
					void AuthenticationRequest::dump_to(std::string &out) const {
 | 
				
			||||||
 | 
					  MessageDumpHelper helper(out, "AuthenticationRequest");
 | 
				
			||||||
 | 
					  out.append("  password: ");
 | 
				
			||||||
 | 
					  out.append(format_hex_pretty(this->password, this->password_len));
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void AuthenticationResponse::dump_to(std::string &out) const {
 | 
				
			||||||
 | 
					  MessageDumpHelper helper(out, "AuthenticationResponse");
 | 
				
			||||||
 | 
					  dump_field(out, "invalid_password", this->invalid_password);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); }
 | 
					void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); }
 | 
				
			||||||
void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); }
 | 
					void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); }
 | 
				
			||||||
void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); }
 | 
					void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); }
 | 
				
			||||||
@@ -749,6 +775,12 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
 | 
				
			|||||||
  this->area.dump_to(out);
 | 
					  this->area.dump_to(out);
 | 
				
			||||||
  out.append("\n");
 | 
					  out.append("\n");
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  dump_field(out, "zwave_proxy_feature_flags", this->zwave_proxy_feature_flags);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  dump_field(out, "zwave_home_id", this->zwave_home_id);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); }
 | 
					void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); }
 | 
				
			||||||
void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); }
 | 
					void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); }
 | 
				
			||||||
@@ -1071,8 +1103,8 @@ void HomeassistantServiceMap::dump_to(std::string &out) const {
 | 
				
			|||||||
  dump_field(out, "key", this->key_ref_);
 | 
					  dump_field(out, "key", this->key_ref_);
 | 
				
			||||||
  dump_field(out, "value", this->value);
 | 
					  dump_field(out, "value", this->value);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void HomeassistantServiceResponse::dump_to(std::string &out) const {
 | 
					void HomeassistantActionRequest::dump_to(std::string &out) const {
 | 
				
			||||||
  MessageDumpHelper helper(out, "HomeassistantServiceResponse");
 | 
					  MessageDumpHelper helper(out, "HomeassistantActionRequest");
 | 
				
			||||||
  dump_field(out, "service", this->service_ref_);
 | 
					  dump_field(out, "service", this->service_ref_);
 | 
				
			||||||
  for (const auto &it : this->data) {
 | 
					  for (const auto &it : this->data) {
 | 
				
			||||||
    out.append("  data: ");
 | 
					    out.append("  data: ");
 | 
				
			||||||
@@ -1090,6 +1122,28 @@ void HomeassistantServiceResponse::dump_to(std::string &out) const {
 | 
				
			|||||||
    out.append("\n");
 | 
					    out.append("\n");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  dump_field(out, "is_event", this->is_event);
 | 
					  dump_field(out, "is_event", this->is_event);
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					  dump_field(out, "call_id", this->call_id);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  dump_field(out, "wants_response", this->wants_response);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  dump_field(out, "response_template", this->response_template);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					void HomeassistantActionResponse::dump_to(std::string &out) const {
 | 
				
			||||||
 | 
					  MessageDumpHelper helper(out, "HomeassistantActionResponse");
 | 
				
			||||||
 | 
					  dump_field(out, "call_id", this->call_id);
 | 
				
			||||||
 | 
					  dump_field(out, "success", this->success);
 | 
				
			||||||
 | 
					  dump_field(out, "error_message", this->error_message);
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  out.append("  response_data: ");
 | 
				
			||||||
 | 
					  out.append(format_hex_pretty(this->response_data, this->response_data_len));
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_API_HOMEASSISTANT_STATES
 | 
					#ifdef USE_API_HOMEASSISTANT_STATES
 | 
				
			||||||
@@ -1110,7 +1164,13 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
 | 
					void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); }
 | 
				
			||||||
void GetTimeResponse::dump_to(std::string &out) const { dump_field(out, "epoch_seconds", this->epoch_seconds); }
 | 
					void GetTimeResponse::dump_to(std::string &out) const {
 | 
				
			||||||
 | 
					  MessageDumpHelper helper(out, "GetTimeResponse");
 | 
				
			||||||
 | 
					  dump_field(out, "epoch_seconds", this->epoch_seconds);
 | 
				
			||||||
 | 
					  out.append("  timezone: ");
 | 
				
			||||||
 | 
					  out.append(format_hex_pretty(this->timezone, this->timezone_len));
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#ifdef USE_API_SERVICES
 | 
					#ifdef USE_API_SERVICES
 | 
				
			||||||
void ListEntitiesServicesArgument::dump_to(std::string &out) const {
 | 
					void ListEntitiesServicesArgument::dump_to(std::string &out) const {
 | 
				
			||||||
  MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
 | 
					  MessageDumpHelper helper(out, "ListEntitiesServicesArgument");
 | 
				
			||||||
@@ -1135,7 +1195,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
 | 
				
			|||||||
  dump_field(out, "string_", this->string_);
 | 
					  dump_field(out, "string_", this->string_);
 | 
				
			||||||
  dump_field(out, "int_", this->int_);
 | 
					  dump_field(out, "int_", this->int_);
 | 
				
			||||||
  for (const auto it : this->bool_array) {
 | 
					  for (const auto it : this->bool_array) {
 | 
				
			||||||
    dump_field(out, "bool_array", it, 4);
 | 
					    dump_field(out, "bool_array", static_cast<bool>(it), 4);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  for (const auto &it : this->int_array) {
 | 
					  for (const auto &it : this->int_array) {
 | 
				
			||||||
    dump_field(out, "int_array", it, 4);
 | 
					    dump_field(out, "int_array", it, 4);
 | 
				
			||||||
@@ -1622,7 +1682,7 @@ void BluetoothGATTWriteRequest::dump_to(std::string &out) const {
 | 
				
			|||||||
  dump_field(out, "handle", this->handle);
 | 
					  dump_field(out, "handle", this->handle);
 | 
				
			||||||
  dump_field(out, "response", this->response);
 | 
					  dump_field(out, "response", this->response);
 | 
				
			||||||
  out.append("  data: ");
 | 
					  out.append("  data: ");
 | 
				
			||||||
  out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size()));
 | 
					  out.append(format_hex_pretty(this->data, this->data_len));
 | 
				
			||||||
  out.append("\n");
 | 
					  out.append("\n");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
 | 
					void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
 | 
				
			||||||
@@ -1635,7 +1695,7 @@ void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const {
 | 
				
			|||||||
  dump_field(out, "address", this->address);
 | 
					  dump_field(out, "address", this->address);
 | 
				
			||||||
  dump_field(out, "handle", this->handle);
 | 
					  dump_field(out, "handle", this->handle);
 | 
				
			||||||
  out.append("  data: ");
 | 
					  out.append("  data: ");
 | 
				
			||||||
  out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size()));
 | 
					  out.append(format_hex_pretty(this->data, this->data_len));
 | 
				
			||||||
  out.append("\n");
 | 
					  out.append("\n");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
 | 
					void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
 | 
				
			||||||
@@ -1704,6 +1764,7 @@ void BluetoothScannerStateResponse::dump_to(std::string &out) const {
 | 
				
			|||||||
  MessageDumpHelper helper(out, "BluetoothScannerStateResponse");
 | 
					  MessageDumpHelper helper(out, "BluetoothScannerStateResponse");
 | 
				
			||||||
  dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state));
 | 
					  dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state));
 | 
				
			||||||
  dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode));
 | 
					  dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode));
 | 
				
			||||||
 | 
					  dump_field(out, "configured_mode", static_cast<enums::BluetoothScannerMode>(this->configured_mode));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
 | 
					void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
 | 
				
			||||||
  MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest");
 | 
					  MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest");
 | 
				
			||||||
@@ -1787,8 +1848,25 @@ void VoiceAssistantWakeWord::dump_to(std::string &out) const {
 | 
				
			|||||||
    dump_field(out, "trained_languages", it, 4);
 | 
					    dump_field(out, "trained_languages", it, 4);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					void VoiceAssistantExternalWakeWord::dump_to(std::string &out) const {
 | 
				
			||||||
 | 
					  MessageDumpHelper helper(out, "VoiceAssistantExternalWakeWord");
 | 
				
			||||||
 | 
					  dump_field(out, "id", this->id);
 | 
				
			||||||
 | 
					  dump_field(out, "wake_word", this->wake_word);
 | 
				
			||||||
 | 
					  for (const auto &it : this->trained_languages) {
 | 
				
			||||||
 | 
					    dump_field(out, "trained_languages", it, 4);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  dump_field(out, "model_type", this->model_type);
 | 
				
			||||||
 | 
					  dump_field(out, "model_size", this->model_size);
 | 
				
			||||||
 | 
					  dump_field(out, "model_hash", this->model_hash);
 | 
				
			||||||
 | 
					  dump_field(out, "url", this->url);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const {
 | 
					void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const {
 | 
				
			||||||
  out.append("VoiceAssistantConfigurationRequest {}");
 | 
					  MessageDumpHelper helper(out, "VoiceAssistantConfigurationRequest");
 | 
				
			||||||
 | 
					  for (const auto &it : this->external_wake_words) {
 | 
				
			||||||
 | 
					    out.append("  external_wake_words: ");
 | 
				
			||||||
 | 
					    it.dump_to(out);
 | 
				
			||||||
 | 
					    out.append("\n");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const {
 | 
					void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const {
 | 
				
			||||||
  MessageDumpHelper helper(out, "VoiceAssistantConfigurationResponse");
 | 
					  MessageDumpHelper helper(out, "VoiceAssistantConfigurationResponse");
 | 
				
			||||||
@@ -2097,6 +2175,21 @@ void UpdateCommandRequest::dump_to(std::string &out) const {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					void ZWaveProxyFrame::dump_to(std::string &out) const {
 | 
				
			||||||
 | 
					  MessageDumpHelper helper(out, "ZWaveProxyFrame");
 | 
				
			||||||
 | 
					  out.append("  data: ");
 | 
				
			||||||
 | 
					  out.append(format_hex_pretty(this->data, this->data_len));
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void ZWaveProxyRequest::dump_to(std::string &out) const {
 | 
				
			||||||
 | 
					  MessageDumpHelper helper(out, "ZWaveProxyRequest");
 | 
				
			||||||
 | 
					  dump_field(out, "type", static_cast<enums::ZWaveProxyRequestType>(this->type));
 | 
				
			||||||
 | 
					  out.append("  data: ");
 | 
				
			||||||
 | 
					  out.append(format_hex_pretty(this->data, this->data_len));
 | 
				
			||||||
 | 
					  out.append("\n");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace esphome::api
 | 
					}  // namespace esphome::api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,15 +24,17 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
				
			|||||||
      this->on_hello_request(msg);
 | 
					      this->on_hello_request(msg);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    case ConnectRequest::MESSAGE_TYPE: {
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
      ConnectRequest msg;
 | 
					    case AuthenticationRequest::MESSAGE_TYPE: {
 | 
				
			||||||
 | 
					      AuthenticationRequest msg;
 | 
				
			||||||
      msg.decode(msg_data, msg_size);
 | 
					      msg.decode(msg_data, msg_size);
 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
      ESP_LOGVV(TAG, "on_connect_request: %s", msg.dump().c_str());
 | 
					      ESP_LOGVV(TAG, "on_authentication_request: %s", msg.dump().c_str());
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
      this->on_connect_request(msg);
 | 
					      this->on_authentication_request(msg);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
    case DisconnectRequest::MESSAGE_TYPE: {
 | 
					    case DisconnectRequest::MESSAGE_TYPE: {
 | 
				
			||||||
      DisconnectRequest msg;
 | 
					      DisconnectRequest msg;
 | 
				
			||||||
      // Empty message: no decode needed
 | 
					      // Empty message: no decode needed
 | 
				
			||||||
@@ -160,15 +162,6 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
				
			|||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
    case GetTimeRequest::MESSAGE_TYPE: {
 | 
					 | 
				
			||||||
      GetTimeRequest msg;
 | 
					 | 
				
			||||||
      // Empty message: no decode needed
 | 
					 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					 | 
				
			||||||
      ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str());
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
      this->on_get_time_request(msg);
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    case GetTimeResponse::MESSAGE_TYPE: {
 | 
					    case GetTimeResponse::MESSAGE_TYPE: {
 | 
				
			||||||
      GetTimeResponse msg;
 | 
					      GetTimeResponse msg;
 | 
				
			||||||
      msg.decode(msg_data, msg_size);
 | 
					      msg.decode(msg_data, msg_size);
 | 
				
			||||||
@@ -555,7 +548,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
				
			|||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
    case VoiceAssistantConfigurationRequest::MESSAGE_TYPE: {
 | 
					    case VoiceAssistantConfigurationRequest::MESSAGE_TYPE: {
 | 
				
			||||||
      VoiceAssistantConfigurationRequest msg;
 | 
					      VoiceAssistantConfigurationRequest msg;
 | 
				
			||||||
      // Empty message: no decode needed
 | 
					      msg.decode(msg_data, msg_size);
 | 
				
			||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
      ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str());
 | 
					      ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str());
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -595,6 +588,39 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
 | 
				
			|||||||
      this->on_bluetooth_scanner_set_mode_request(msg);
 | 
					      this->on_bluetooth_scanner_set_mode_request(msg);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					    case ZWaveProxyFrame::MESSAGE_TYPE: {
 | 
				
			||||||
 | 
					      ZWaveProxyFrame msg;
 | 
				
			||||||
 | 
					      msg.decode(msg_data, msg_size);
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					      ESP_LOGVV(TAG, "on_z_wave_proxy_frame: %s", msg.dump().c_str());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					      this->on_z_wave_proxy_frame(msg);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					    case ZWaveProxyRequest::MESSAGE_TYPE: {
 | 
				
			||||||
 | 
					      ZWaveProxyRequest msg;
 | 
				
			||||||
 | 
					      msg.decode(msg_data, msg_size);
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					      ESP_LOGVV(TAG, "on_z_wave_proxy_request: %s", msg.dump().c_str());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					      this->on_z_wave_proxy_request(msg);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					    case HomeassistantActionResponse::MESSAGE_TYPE: {
 | 
				
			||||||
 | 
					      HomeassistantActionResponse msg;
 | 
				
			||||||
 | 
					      msg.decode(msg_data, msg_size);
 | 
				
			||||||
 | 
					#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
				
			||||||
 | 
					      ESP_LOGVV(TAG, "on_homeassistant_action_response: %s", msg.dump().c_str());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					      this->on_homeassistant_action_response(msg);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
@@ -606,11 +632,13 @@ void APIServerConnection::on_hello_request(const HelloRequest &msg) {
 | 
				
			|||||||
    this->on_fatal_error();
 | 
					    this->on_fatal_error();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void APIServerConnection::on_connect_request(const ConnectRequest &msg) {
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
  if (!this->send_connect_response(msg)) {
 | 
					void APIServerConnection::on_authentication_request(const AuthenticationRequest &msg) {
 | 
				
			||||||
 | 
					  if (!this->send_authenticate_response(msg)) {
 | 
				
			||||||
    this->on_fatal_error();
 | 
					    this->on_fatal_error();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
 | 
					void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
 | 
				
			||||||
  if (!this->send_disconnect_response(msg)) {
 | 
					  if (!this->send_disconnect_response(msg)) {
 | 
				
			||||||
    this->on_fatal_error();
 | 
					    this->on_fatal_error();
 | 
				
			||||||
@@ -622,246 +650,139 @@ void APIServerConnection::on_ping_request(const PingRequest &msg) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) {
 | 
					void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) {
 | 
				
			||||||
  if (this->check_connection_setup_() && !this->send_device_info_response(msg)) {
 | 
					  if (!this->send_device_info_response(msg)) {
 | 
				
			||||||
    this->on_fatal_error();
 | 
					    this->on_fatal_error();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) {
 | 
					void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) { this->list_entities(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->list_entities(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) {
 | 
					void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->subscribe_states(msg);
 | 
				
			||||||
    this->subscribe_states(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) {
 | 
					 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->subscribe_logs(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) { this->subscribe_logs(msg); }
 | 
				
			||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
 | 
					#ifdef USE_API_HOMEASSISTANT_SERVICES
 | 
				
			||||||
void APIServerConnection::on_subscribe_homeassistant_services_request(
 | 
					void APIServerConnection::on_subscribe_homeassistant_services_request(
 | 
				
			||||||
    const SubscribeHomeassistantServicesRequest &msg) {
 | 
					    const SubscribeHomeassistantServicesRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->subscribe_homeassistant_services(msg);
 | 
				
			||||||
    this->subscribe_homeassistant_services(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_API_HOMEASSISTANT_STATES
 | 
					#ifdef USE_API_HOMEASSISTANT_STATES
 | 
				
			||||||
void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
 | 
					void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->subscribe_home_assistant_states(msg);
 | 
				
			||||||
    this->subscribe_home_assistant_states(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
 | 
					 | 
				
			||||||
  if (this->check_connection_setup_() && !this->send_get_time_response(msg)) {
 | 
					 | 
				
			||||||
    this->on_fatal_error();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#ifdef USE_API_SERVICES
 | 
					#ifdef USE_API_SERVICES
 | 
				
			||||||
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
 | 
					void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { this->execute_service(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->execute_service(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_API_NOISE
 | 
					#ifdef USE_API_NOISE
 | 
				
			||||||
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
 | 
					void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_() && !this->send_noise_encryption_set_key_response(msg)) {
 | 
					  if (!this->send_noise_encryption_set_key_response(msg)) {
 | 
				
			||||||
    this->on_fatal_error();
 | 
					    this->on_fatal_error();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BUTTON
 | 
					#ifdef USE_BUTTON
 | 
				
			||||||
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
 | 
					void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { this->button_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->button_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_CAMERA
 | 
					#ifdef USE_CAMERA
 | 
				
			||||||
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
 | 
					void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { this->camera_image(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->camera_image(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_CLIMATE
 | 
					#ifdef USE_CLIMATE
 | 
				
			||||||
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
 | 
					void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) { this->climate_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->climate_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_COVER
 | 
					#ifdef USE_COVER
 | 
				
			||||||
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
 | 
					void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) { this->cover_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->cover_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_DATETIME_DATE
 | 
					#ifdef USE_DATETIME_DATE
 | 
				
			||||||
void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) {
 | 
					void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) { this->date_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->date_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_DATETIME_DATETIME
 | 
					#ifdef USE_DATETIME_DATETIME
 | 
				
			||||||
void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
 | 
					void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->datetime_command(msg);
 | 
				
			||||||
    this->datetime_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_FAN
 | 
					#ifdef USE_FAN
 | 
				
			||||||
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) {
 | 
					void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) { this->fan_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->fan_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_LIGHT
 | 
					#ifdef USE_LIGHT
 | 
				
			||||||
void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) {
 | 
					void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) { this->light_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->light_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_LOCK
 | 
					#ifdef USE_LOCK
 | 
				
			||||||
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
 | 
					void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { this->lock_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->lock_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_MEDIA_PLAYER
 | 
					#ifdef USE_MEDIA_PLAYER
 | 
				
			||||||
void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
 | 
					void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->media_player_command(msg);
 | 
				
			||||||
    this->media_player_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_NUMBER
 | 
					#ifdef USE_NUMBER
 | 
				
			||||||
void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) {
 | 
					void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) { this->number_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->number_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_SELECT
 | 
					#ifdef USE_SELECT
 | 
				
			||||||
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) {
 | 
					void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) { this->select_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->select_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_SIREN
 | 
					#ifdef USE_SIREN
 | 
				
			||||||
void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) {
 | 
					void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) { this->siren_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->siren_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_SWITCH
 | 
					#ifdef USE_SWITCH
 | 
				
			||||||
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
 | 
					void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) { this->switch_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->switch_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_TEXT
 | 
					#ifdef USE_TEXT
 | 
				
			||||||
void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) {
 | 
					void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) { this->text_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->text_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_DATETIME_TIME
 | 
					#ifdef USE_DATETIME_TIME
 | 
				
			||||||
void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) {
 | 
					void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) { this->time_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->time_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_UPDATE
 | 
					#ifdef USE_UPDATE
 | 
				
			||||||
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) {
 | 
					void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) { this->update_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->update_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_VALVE
 | 
					#ifdef USE_VALVE
 | 
				
			||||||
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) {
 | 
					void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { this->valve_command(msg); }
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					 | 
				
			||||||
    this->valve_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
 | 
					void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
 | 
				
			||||||
    const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
					    const SubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->subscribe_bluetooth_le_advertisements(msg);
 | 
				
			||||||
    this->subscribe_bluetooth_le_advertisements(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
 | 
					void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->bluetooth_device_request(msg);
 | 
				
			||||||
    this->bluetooth_device_request(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) {
 | 
					void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->bluetooth_gatt_get_services(msg);
 | 
				
			||||||
    this->bluetooth_gatt_get_services(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) {
 | 
					void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->bluetooth_gatt_read(msg);
 | 
				
			||||||
    this->bluetooth_gatt_read(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) {
 | 
					void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->bluetooth_gatt_write(msg);
 | 
				
			||||||
    this->bluetooth_gatt_write(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) {
 | 
					void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->bluetooth_gatt_read_descriptor(msg);
 | 
				
			||||||
    this->bluetooth_gatt_read_descriptor(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) {
 | 
					void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->bluetooth_gatt_write_descriptor(msg);
 | 
				
			||||||
    this->bluetooth_gatt_write_descriptor(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) {
 | 
					void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->bluetooth_gatt_notify(msg);
 | 
				
			||||||
    this->bluetooth_gatt_notify(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
 | 
					void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
 | 
				
			||||||
    const SubscribeBluetoothConnectionsFreeRequest &msg) {
 | 
					    const SubscribeBluetoothConnectionsFreeRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_() && !this->send_subscribe_bluetooth_connections_free_response(msg)) {
 | 
					  if (!this->send_subscribe_bluetooth_connections_free_response(msg)) {
 | 
				
			||||||
    this->on_fatal_error();
 | 
					    this->on_fatal_error();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -869,45 +790,68 @@ void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
 | 
				
			|||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request(
 | 
					void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request(
 | 
				
			||||||
    const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
					    const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->unsubscribe_bluetooth_le_advertisements(msg);
 | 
				
			||||||
    this->unsubscribe_bluetooth_le_advertisements(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_BLUETOOTH_PROXY
 | 
					#ifdef USE_BLUETOOTH_PROXY
 | 
				
			||||||
void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
 | 
					void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->bluetooth_scanner_set_mode(msg);
 | 
				
			||||||
    this->bluetooth_scanner_set_mode(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
 | 
					void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->subscribe_voice_assistant(msg);
 | 
				
			||||||
    this->subscribe_voice_assistant(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
 | 
					void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_() && !this->send_voice_assistant_get_configuration_response(msg)) {
 | 
					  if (!this->send_voice_assistant_get_configuration_response(msg)) {
 | 
				
			||||||
    this->on_fatal_error();
 | 
					    this->on_fatal_error();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_VOICE_ASSISTANT
 | 
					#ifdef USE_VOICE_ASSISTANT
 | 
				
			||||||
void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
 | 
					void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->voice_assistant_set_configuration(msg);
 | 
				
			||||||
    this->voice_assistant_set_configuration(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
 | 
					void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
 | 
				
			||||||
  if (this->check_authenticated_()) {
 | 
					  this->alarm_control_panel_command(msg);
 | 
				
			||||||
    this->alarm_control_panel_command(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { this->zwave_proxy_frame(msg); }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
 | 
				
			||||||
 | 
					  // Check authentication/connection requirements for messages
 | 
				
			||||||
 | 
					  switch (msg_type) {
 | 
				
			||||||
 | 
					    case HelloRequest::MESSAGE_TYPE:  // No setup required
 | 
				
			||||||
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
 | 
					    case AuthenticationRequest::MESSAGE_TYPE:  // No setup required
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    case DisconnectRequest::MESSAGE_TYPE:  // No setup required
 | 
				
			||||||
 | 
					    case PingRequest::MESSAGE_TYPE:        // No setup required
 | 
				
			||||||
 | 
					      break;                               // Skip all checks for these messages
 | 
				
			||||||
 | 
					    case DeviceInfoRequest::MESSAGE_TYPE:  // Connection setup only
 | 
				
			||||||
 | 
					      if (!this->check_connection_setup_()) {
 | 
				
			||||||
 | 
					        return;  // Connection not setup
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      // All other messages require authentication (which includes connection check)
 | 
				
			||||||
 | 
					      if (!this->check_authenticated_()) {
 | 
				
			||||||
 | 
					        return;  // Authentication failed
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Call base implementation to process the message
 | 
				
			||||||
 | 
					  APIServerConnectionBase::read_message(msg_size, msg_type, msg_data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace esphome::api
 | 
					}  // namespace esphome::api
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,7 +26,9 @@ class APIServerConnectionBase : public ProtoService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  virtual void on_hello_request(const HelloRequest &value){};
 | 
					  virtual void on_hello_request(const HelloRequest &value){};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  virtual void on_connect_request(const ConnectRequest &value){};
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
 | 
					  virtual void on_authentication_request(const AuthenticationRequest &value){};
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  virtual void on_disconnect_request(const DisconnectRequest &value){};
 | 
					  virtual void on_disconnect_request(const DisconnectRequest &value){};
 | 
				
			||||||
  virtual void on_disconnect_response(const DisconnectResponse &value){};
 | 
					  virtual void on_disconnect_response(const DisconnectResponse &value){};
 | 
				
			||||||
@@ -64,6 +66,9 @@ class APIServerConnectionBase : public ProtoService {
 | 
				
			|||||||
  virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
 | 
					  virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					  virtual void on_homeassistant_action_response(const HomeassistantActionResponse &value){};
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
#ifdef USE_API_HOMEASSISTANT_STATES
 | 
					#ifdef USE_API_HOMEASSISTANT_STATES
 | 
				
			||||||
  virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
 | 
					  virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -71,7 +76,7 @@ class APIServerConnectionBase : public ProtoService {
 | 
				
			|||||||
#ifdef USE_API_HOMEASSISTANT_STATES
 | 
					#ifdef USE_API_HOMEASSISTANT_STATES
 | 
				
			||||||
  virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
 | 
					  virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  virtual void on_get_time_request(const GetTimeRequest &value){};
 | 
					
 | 
				
			||||||
  virtual void on_get_time_response(const GetTimeResponse &value){};
 | 
					  virtual void on_get_time_response(const GetTimeResponse &value){};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_API_SERVICES
 | 
					#ifdef USE_API_SERVICES
 | 
				
			||||||
@@ -205,6 +210,12 @@ class APIServerConnectionBase : public ProtoService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#ifdef USE_UPDATE
 | 
					#ifdef USE_UPDATE
 | 
				
			||||||
  virtual void on_update_command_request(const UpdateCommandRequest &value){};
 | 
					  virtual void on_update_command_request(const UpdateCommandRequest &value){};
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  virtual void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){};
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
 | 
					  void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
 | 
				
			||||||
@@ -213,7 +224,9 @@ class APIServerConnectionBase : public ProtoService {
 | 
				
			|||||||
class APIServerConnection : public APIServerConnectionBase {
 | 
					class APIServerConnection : public APIServerConnectionBase {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  virtual bool send_hello_response(const HelloRequest &msg) = 0;
 | 
					  virtual bool send_hello_response(const HelloRequest &msg) = 0;
 | 
				
			||||||
  virtual bool send_connect_response(const ConnectRequest &msg) = 0;
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
 | 
					  virtual bool send_authenticate_response(const AuthenticationRequest &msg) = 0;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
  virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0;
 | 
					  virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0;
 | 
				
			||||||
  virtual bool send_ping_response(const PingRequest &msg) = 0;
 | 
					  virtual bool send_ping_response(const PingRequest &msg) = 0;
 | 
				
			||||||
  virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0;
 | 
					  virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0;
 | 
				
			||||||
@@ -226,7 +239,6 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
				
			|||||||
#ifdef USE_API_HOMEASSISTANT_STATES
 | 
					#ifdef USE_API_HOMEASSISTANT_STATES
 | 
				
			||||||
  virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
 | 
					  virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  virtual bool send_get_time_response(const GetTimeRequest &msg) = 0;
 | 
					 | 
				
			||||||
#ifdef USE_API_SERVICES
 | 
					#ifdef USE_API_SERVICES
 | 
				
			||||||
  virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
 | 
					  virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -332,10 +344,18 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
  virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0;
 | 
					  virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  virtual void zwave_proxy_frame(const ZWaveProxyFrame &msg) = 0;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  virtual void zwave_proxy_request(const ZWaveProxyRequest &msg) = 0;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  void on_hello_request(const HelloRequest &msg) override;
 | 
					  void on_hello_request(const HelloRequest &msg) override;
 | 
				
			||||||
  void on_connect_request(const ConnectRequest &msg) override;
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
 | 
					  void on_authentication_request(const AuthenticationRequest &msg) override;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
  void on_disconnect_request(const DisconnectRequest &msg) override;
 | 
					  void on_disconnect_request(const DisconnectRequest &msg) override;
 | 
				
			||||||
  void on_ping_request(const PingRequest &msg) override;
 | 
					  void on_ping_request(const PingRequest &msg) override;
 | 
				
			||||||
  void on_device_info_request(const DeviceInfoRequest &msg) override;
 | 
					  void on_device_info_request(const DeviceInfoRequest &msg) override;
 | 
				
			||||||
@@ -348,7 +368,6 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
				
			|||||||
#ifdef USE_API_HOMEASSISTANT_STATES
 | 
					#ifdef USE_API_HOMEASSISTANT_STATES
 | 
				
			||||||
  void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
 | 
					  void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  void on_get_time_request(const GetTimeRequest &msg) override;
 | 
					 | 
				
			||||||
#ifdef USE_API_SERVICES
 | 
					#ifdef USE_API_SERVICES
 | 
				
			||||||
  void on_execute_service_request(const ExecuteServiceRequest &msg) override;
 | 
					  void on_execute_service_request(const ExecuteServiceRequest &msg) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -455,6 +474,13 @@ class APIServerConnection : public APIServerConnectionBase {
 | 
				
			|||||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
  void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
 | 
					  void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace esphome::api
 | 
					}  // namespace esphome::api
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,12 +9,16 @@
 | 
				
			|||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
#include "esphome/core/util.h"
 | 
					#include "esphome/core/util.h"
 | 
				
			||||||
#include "esphome/core/version.h"
 | 
					#include "esphome/core/version.h"
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_SERVICES
 | 
				
			||||||
 | 
					#include "homeassistant_service.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_LOGGER
 | 
					#ifdef USE_LOGGER
 | 
				
			||||||
#include "esphome/components/logger/logger.h"
 | 
					#include "esphome/components/logger/logger.h"
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <algorithm>
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome::api {
 | 
					namespace esphome::api {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -37,12 +41,14 @@ void APIServer::setup() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true);
 | 
					  this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef USE_API_NOISE_PSK_FROM_YAML
 | 
				
			||||||
 | 
					  // Only load saved PSK if not set from YAML
 | 
				
			||||||
  SavedNoisePsk noise_pref_saved{};
 | 
					  SavedNoisePsk noise_pref_saved{};
 | 
				
			||||||
  if (this->noise_pref_.load(&noise_pref_saved)) {
 | 
					  if (this->noise_pref_.load(&noise_pref_saved)) {
 | 
				
			||||||
    ESP_LOGD(TAG, "Loaded saved Noise PSK");
 | 
					    ESP_LOGD(TAG, "Loaded saved Noise PSK");
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this->set_noise_psk(noise_pref_saved.psk);
 | 
					    this->set_noise_psk(noise_pref_saved.psk);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Schedule reboot if no clients connect within timeout
 | 
					  // Schedule reboot if no clients connect within timeout
 | 
				
			||||||
@@ -85,7 +91,7 @@ void APIServer::setup() {
 | 
				
			|||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  err = this->socket_->listen(4);
 | 
					  err = this->socket_->listen(this->listen_backlog_);
 | 
				
			||||||
  if (err != 0) {
 | 
					  if (err != 0) {
 | 
				
			||||||
    ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
 | 
					    ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
 | 
				
			||||||
    this->mark_failed();
 | 
					    this->mark_failed();
 | 
				
			||||||
@@ -138,9 +144,19 @@ void APIServer::loop() {
 | 
				
			|||||||
    while (true) {
 | 
					    while (true) {
 | 
				
			||||||
      struct sockaddr_storage source_addr;
 | 
					      struct sockaddr_storage source_addr;
 | 
				
			||||||
      socklen_t addr_len = sizeof(source_addr);
 | 
					      socklen_t addr_len = sizeof(source_addr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
 | 
					      auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
 | 
				
			||||||
      if (!sock)
 | 
					      if (!sock)
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Check if we're at the connection limit
 | 
				
			||||||
 | 
					      if (this->clients_.size() >= this->max_connections_) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, sock->getpeername().c_str());
 | 
				
			||||||
 | 
					        // Immediately close - socket destructor will handle cleanup
 | 
				
			||||||
 | 
					        sock.reset();
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
 | 
					      ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      auto *conn = new APIConnection(std::move(sock), this);
 | 
					      auto *conn = new APIConnection(std::move(sock), this);
 | 
				
			||||||
@@ -165,7 +181,8 @@ void APIServer::loop() {
 | 
				
			|||||||
    // Network is down - disconnect all clients
 | 
					    // Network is down - disconnect all clients
 | 
				
			||||||
    for (auto &client : this->clients_) {
 | 
					    for (auto &client : this->clients_) {
 | 
				
			||||||
      client->on_fatal_error();
 | 
					      client->on_fatal_error();
 | 
				
			||||||
      ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str());
 | 
					      ESP_LOGW(TAG, "%s (%s): Network down; disconnect", client->client_info_.name.c_str(),
 | 
				
			||||||
 | 
					               client->client_info_.peername.c_str());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // Continue to process and clean up the clients below
 | 
					    // Continue to process and clean up the clients below
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -204,8 +221,10 @@ void APIServer::loop() {
 | 
				
			|||||||
void APIServer::dump_config() {
 | 
					void APIServer::dump_config() {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG,
 | 
					  ESP_LOGCONFIG(TAG,
 | 
				
			||||||
                "Server:\n"
 | 
					                "Server:\n"
 | 
				
			||||||
                "  Address: %s:%u",
 | 
					                "  Address: %s:%u\n"
 | 
				
			||||||
                network::get_use_address().c_str(), this->port_);
 | 
					                "  Listen backlog: %u\n"
 | 
				
			||||||
 | 
					                "  Max connections: %u",
 | 
				
			||||||
 | 
					                network::get_use_address().c_str(), this->port_, this->listen_backlog_, this->max_connections_);
 | 
				
			||||||
#ifdef USE_API_NOISE
 | 
					#ifdef USE_API_NOISE
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
 | 
					  ESP_LOGCONFIG(TAG, "  Noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
 | 
				
			||||||
  if (!this->noise_ctx_->has_psk()) {
 | 
					  if (!this->noise_ctx_->has_psk()) {
 | 
				
			||||||
@@ -217,12 +236,12 @@ void APIServer::dump_config() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_API_PASSWORD
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
bool APIServer::check_password(const std::string &password) const {
 | 
					bool APIServer::check_password(const uint8_t *password_data, size_t password_len) const {
 | 
				
			||||||
  // depend only on input password length
 | 
					  // depend only on input password length
 | 
				
			||||||
  const char *a = this->password_.c_str();
 | 
					  const char *a = this->password_.c_str();
 | 
				
			||||||
  uint32_t len_a = this->password_.length();
 | 
					  uint32_t len_a = this->password_.length();
 | 
				
			||||||
  const char *b = password.c_str();
 | 
					  const char *b = reinterpret_cast<const char *>(password_data);
 | 
				
			||||||
  uint32_t len_b = password.length();
 | 
					  uint32_t len_b = password_len;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // disable optimization with volatile
 | 
					  // disable optimization with volatile
 | 
				
			||||||
  volatile uint32_t length = len_b;
 | 
					  volatile uint32_t length = len_b;
 | 
				
			||||||
@@ -245,6 +264,7 @@ bool APIServer::check_password(const std::string &password) const {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return result == 0;
 | 
					  return result == 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void APIServer::handle_disconnect(APIConnection *conn) {}
 | 
					void APIServer::handle_disconnect(APIConnection *conn) {}
 | 
				
			||||||
@@ -355,6 +375,15 @@ void APIServer::on_update(update::UpdateEntity *obj) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					void APIServer::on_zwave_proxy_request(const esphome::api::ProtoMessage &msg) {
 | 
				
			||||||
 | 
					  // We could add code to manage a second subscription type, but, since this message type is
 | 
				
			||||||
 | 
					  //  very infrequent and small, we simply send it to all clients
 | 
				
			||||||
 | 
					  for (auto &c : this->clients_)
 | 
				
			||||||
 | 
					    c->send_message(msg, api::ZWaveProxyRequest::MESSAGE_TYPE);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ALARM_CONTROL_PANEL
 | 
					#ifdef USE_ALARM_CONTROL_PANEL
 | 
				
			||||||
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
 | 
					API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -370,12 +399,43 @@ void APIServer::set_password(const std::string &password) { this->password_ = pa
 | 
				
			|||||||
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
 | 
					void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
 | 
					#ifdef USE_API_HOMEASSISTANT_SERVICES
 | 
				
			||||||
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
 | 
					void APIServer::send_homeassistant_action(const HomeassistantActionRequest &call) {
 | 
				
			||||||
  for (auto &client : this->clients_) {
 | 
					  for (auto &client : this->clients_) {
 | 
				
			||||||
    client->send_homeassistant_service_call(call);
 | 
					    client->send_homeassistant_action(call);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					void APIServer::register_action_response_callback(uint32_t call_id, ActionResponseCallback callback) {
 | 
				
			||||||
 | 
					  this->action_response_callbacks_.push_back({call_id, std::move(callback)});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message) {
 | 
				
			||||||
 | 
					  for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
 | 
				
			||||||
 | 
					    if (it->call_id == call_id) {
 | 
				
			||||||
 | 
					      auto callback = std::move(it->callback);
 | 
				
			||||||
 | 
					      this->action_response_callbacks_.erase(it);
 | 
				
			||||||
 | 
					      ActionResponse response(success, error_message);
 | 
				
			||||||
 | 
					      callback(response);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					void APIServer::handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
 | 
				
			||||||
 | 
					                                       const uint8_t *response_data, size_t response_data_len) {
 | 
				
			||||||
 | 
					  for (auto it = this->action_response_callbacks_.begin(); it != this->action_response_callbacks_.end(); ++it) {
 | 
				
			||||||
 | 
					    if (it->call_id == call_id) {
 | 
				
			||||||
 | 
					      auto callback = std::move(it->callback);
 | 
				
			||||||
 | 
					      this->action_response_callbacks_.erase(it);
 | 
				
			||||||
 | 
					      ActionResponse response(success, error_message, response_data, response_data_len);
 | 
				
			||||||
 | 
					      callback(response);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif  // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					#endif  // USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					#endif  // USE_API_HOMEASSISTANT_SERVICES
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_API_HOMEASSISTANT_STATES
 | 
					#ifdef USE_API_HOMEASSISTANT_STATES
 | 
				
			||||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
 | 
					void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
 | 
				
			||||||
@@ -409,6 +469,12 @@ void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#ifdef USE_API_NOISE
 | 
					#ifdef USE_API_NOISE
 | 
				
			||||||
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
 | 
					bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
 | 
				
			||||||
 | 
					#ifdef USE_API_NOISE_PSK_FROM_YAML
 | 
				
			||||||
 | 
					  // When PSK is set from YAML, this function should never be called
 | 
				
			||||||
 | 
					  // but if it is, reject the change
 | 
				
			||||||
 | 
					  ESP_LOGW(TAG, "Key set in YAML");
 | 
				
			||||||
 | 
					  return false;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
  auto &old_psk = this->noise_ctx_->get_psk();
 | 
					  auto &old_psk = this->noise_ctx_->get_psk();
 | 
				
			||||||
  if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
 | 
					  if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
 | 
				
			||||||
    ESP_LOGW(TAG, "New PSK matches old");
 | 
					    ESP_LOGW(TAG, "New PSK matches old");
 | 
				
			||||||
@@ -437,6 +503,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@
 | 
				
			|||||||
#include "user_services.h"
 | 
					#include "user_services.h"
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <map>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome::api {
 | 
					namespace esphome::api {
 | 
				
			||||||
@@ -37,13 +38,15 @@ class APIServer : public Component, public Controller {
 | 
				
			|||||||
  void on_shutdown() override;
 | 
					  void on_shutdown() override;
 | 
				
			||||||
  bool teardown() override;
 | 
					  bool teardown() override;
 | 
				
			||||||
#ifdef USE_API_PASSWORD
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
  bool check_password(const std::string &password) const;
 | 
					  bool check_password(const uint8_t *password_data, size_t password_len) const;
 | 
				
			||||||
  void set_password(const std::string &password);
 | 
					  void set_password(const std::string &password);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  void set_port(uint16_t port);
 | 
					  void set_port(uint16_t port);
 | 
				
			||||||
  void set_reboot_timeout(uint32_t reboot_timeout);
 | 
					  void set_reboot_timeout(uint32_t reboot_timeout);
 | 
				
			||||||
  void set_batch_delay(uint16_t batch_delay);
 | 
					  void set_batch_delay(uint16_t batch_delay);
 | 
				
			||||||
  uint16_t get_batch_delay() const { return batch_delay_; }
 | 
					  uint16_t get_batch_delay() const { return batch_delay_; }
 | 
				
			||||||
 | 
					  void set_listen_backlog(uint8_t listen_backlog) { this->listen_backlog_ = listen_backlog; }
 | 
				
			||||||
 | 
					  void set_max_connections(uint8_t max_connections) { this->max_connections_ = max_connections; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Get reference to shared buffer for API connections
 | 
					  // Get reference to shared buffer for API connections
 | 
				
			||||||
  std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; }
 | 
					  std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; }
 | 
				
			||||||
@@ -107,8 +110,19 @@ class APIServer : public Component, public Controller {
 | 
				
			|||||||
  void on_media_player_update(media_player::MediaPlayer *obj) override;
 | 
					  void on_media_player_update(media_player::MediaPlayer *obj) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
 | 
					#ifdef USE_API_HOMEASSISTANT_SERVICES
 | 
				
			||||||
  void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
 | 
					  void send_homeassistant_action(const HomeassistantActionRequest &call);
 | 
				
			||||||
#endif
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					  // Action response handling
 | 
				
			||||||
 | 
					  using ActionResponseCallback = std::function<void(const class ActionResponse &)>;
 | 
				
			||||||
 | 
					  void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback);
 | 
				
			||||||
 | 
					  void handle_action_response(uint32_t call_id, bool success, const std::string &error_message);
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  void handle_action_response(uint32_t call_id, bool success, const std::string &error_message,
 | 
				
			||||||
 | 
					                              const uint8_t *response_data, size_t response_data_len);
 | 
				
			||||||
 | 
					#endif  // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					#endif  // USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					#endif  // USE_API_HOMEASSISTANT_SERVICES
 | 
				
			||||||
#ifdef USE_API_SERVICES
 | 
					#ifdef USE_API_SERVICES
 | 
				
			||||||
  void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
 | 
					  void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -125,6 +139,9 @@ class APIServer : public Component, public Controller {
 | 
				
			|||||||
#ifdef USE_UPDATE
 | 
					#ifdef USE_UPDATE
 | 
				
			||||||
  void on_update(update::UpdateEntity *obj) override;
 | 
					  void on_update(update::UpdateEntity *obj) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ZWAVE_PROXY
 | 
				
			||||||
 | 
					  void on_zwave_proxy_request(const esphome::api::ProtoMessage &msg);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool is_connected() const;
 | 
					  bool is_connected() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -181,12 +198,23 @@ class APIServer : public Component, public Controller {
 | 
				
			|||||||
#ifdef USE_API_SERVICES
 | 
					#ifdef USE_API_SERVICES
 | 
				
			||||||
  std::vector<UserServiceDescriptor *> user_services_;
 | 
					  std::vector<UserServiceDescriptor *> user_services_;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					  struct PendingActionResponse {
 | 
				
			||||||
 | 
					    uint32_t call_id;
 | 
				
			||||||
 | 
					    ActionResponseCallback callback;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  std::vector<PendingActionResponse> action_response_callbacks_;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Group smaller types together
 | 
					  // Group smaller types together
 | 
				
			||||||
  uint16_t port_{6053};
 | 
					  uint16_t port_{6053};
 | 
				
			||||||
  uint16_t batch_delay_{100};
 | 
					  uint16_t batch_delay_{100};
 | 
				
			||||||
 | 
					  // Connection limits - these defaults will be overridden by config values
 | 
				
			||||||
 | 
					  // from cv.SplitDefault in __init__.py which sets platform-specific defaults
 | 
				
			||||||
 | 
					  uint8_t listen_backlog_{4};
 | 
				
			||||||
 | 
					  uint8_t max_connections_{8};
 | 
				
			||||||
  bool shutting_down_ = false;
 | 
					  bool shutting_down_ = false;
 | 
				
			||||||
  // 5 bytes used, 3 bytes padding
 | 
					  // 7 bytes used, 1 byte padding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_API_NOISE
 | 
					#ifdef USE_API_NOISE
 | 
				
			||||||
  std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
 | 
					  std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,9 +62,11 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
 | 
				
			|||||||
        time_ = datetime.now()
 | 
					        time_ = datetime.now()
 | 
				
			||||||
        message: bytes = msg.message
 | 
					        message: bytes = msg.message
 | 
				
			||||||
        text = message.decode("utf8", "backslashreplace")
 | 
					        text = message.decode("utf8", "backslashreplace")
 | 
				
			||||||
        for parsed_msg in parse_log_message(
 | 
					        nanoseconds = time_.microsecond // 1000
 | 
				
			||||||
            text, f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}]"
 | 
					        timestamp = (
 | 
				
			||||||
        ):
 | 
					            f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        for parsed_msg in parse_log_message(text, timestamp):
 | 
				
			||||||
            print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg)
 | 
					            print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    stop = await async_run(cli, on_log, name=name)
 | 
					    stop = await async_run(cli, on_log, name=name)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -179,9 +179,9 @@ class CustomAPIDevice {
 | 
				
			|||||||
   * @param service_name The service to call.
 | 
					   * @param service_name The service to call.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  void call_homeassistant_service(const std::string &service_name) {
 | 
					  void call_homeassistant_service(const std::string &service_name) {
 | 
				
			||||||
    HomeassistantServiceResponse resp;
 | 
					    HomeassistantActionRequest resp;
 | 
				
			||||||
    resp.set_service(StringRef(service_name));
 | 
					    resp.set_service(StringRef(service_name));
 | 
				
			||||||
    global_api_server->send_homeassistant_service_call(resp);
 | 
					    global_api_server->send_homeassistant_action(resp);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Call a Home Assistant service from ESPHome.
 | 
					  /** Call a Home Assistant service from ESPHome.
 | 
				
			||||||
@@ -199,7 +199,7 @@ class CustomAPIDevice {
 | 
				
			|||||||
   * @param data The data for the service call, mapping from string to string.
 | 
					   * @param data The data for the service call, mapping from string to string.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
 | 
					  void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
 | 
				
			||||||
    HomeassistantServiceResponse resp;
 | 
					    HomeassistantActionRequest resp;
 | 
				
			||||||
    resp.set_service(StringRef(service_name));
 | 
					    resp.set_service(StringRef(service_name));
 | 
				
			||||||
    for (auto &it : data) {
 | 
					    for (auto &it : data) {
 | 
				
			||||||
      resp.data.emplace_back();
 | 
					      resp.data.emplace_back();
 | 
				
			||||||
@@ -207,7 +207,7 @@ class CustomAPIDevice {
 | 
				
			|||||||
      kv.set_key(StringRef(it.first));
 | 
					      kv.set_key(StringRef(it.first));
 | 
				
			||||||
      kv.value = it.second;
 | 
					      kv.value = it.second;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    global_api_server->send_homeassistant_service_call(resp);
 | 
					    global_api_server->send_homeassistant_action(resp);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Fire an ESPHome event in Home Assistant.
 | 
					  /** Fire an ESPHome event in Home Assistant.
 | 
				
			||||||
@@ -221,10 +221,10 @@ class CustomAPIDevice {
 | 
				
			|||||||
   * @param event_name The event to fire.
 | 
					   * @param event_name The event to fire.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  void fire_homeassistant_event(const std::string &event_name) {
 | 
					  void fire_homeassistant_event(const std::string &event_name) {
 | 
				
			||||||
    HomeassistantServiceResponse resp;
 | 
					    HomeassistantActionRequest resp;
 | 
				
			||||||
    resp.set_service(StringRef(event_name));
 | 
					    resp.set_service(StringRef(event_name));
 | 
				
			||||||
    resp.is_event = true;
 | 
					    resp.is_event = true;
 | 
				
			||||||
    global_api_server->send_homeassistant_service_call(resp);
 | 
					    global_api_server->send_homeassistant_action(resp);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Fire an ESPHome event in Home Assistant.
 | 
					  /** Fire an ESPHome event in Home Assistant.
 | 
				
			||||||
@@ -241,7 +241,7 @@ class CustomAPIDevice {
 | 
				
			|||||||
   * @param data The data for the event, mapping from string to string.
 | 
					   * @param data The data for the event, mapping from string to string.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
 | 
					  void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
 | 
				
			||||||
    HomeassistantServiceResponse resp;
 | 
					    HomeassistantActionRequest resp;
 | 
				
			||||||
    resp.set_service(StringRef(service_name));
 | 
					    resp.set_service(StringRef(service_name));
 | 
				
			||||||
    resp.is_event = true;
 | 
					    resp.is_event = true;
 | 
				
			||||||
    for (auto &it : data) {
 | 
					    for (auto &it : data) {
 | 
				
			||||||
@@ -250,7 +250,7 @@ class CustomAPIDevice {
 | 
				
			|||||||
      kv.set_key(StringRef(it.first));
 | 
					      kv.set_key(StringRef(it.first));
 | 
				
			||||||
      kv.value = it.second;
 | 
					      kv.value = it.second;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    global_api_server->send_homeassistant_service_call(resp);
 | 
					    global_api_server->send_homeassistant_action(resp);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
  template<typename T = void> void call_homeassistant_service(const std::string &service_name) {
 | 
					  template<typename T = void> void call_homeassistant_service(const std::string &service_name) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,10 +3,15 @@
 | 
				
			|||||||
#include "api_server.h"
 | 
					#include "api_server.h"
 | 
				
			||||||
#ifdef USE_API
 | 
					#ifdef USE_API
 | 
				
			||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
 | 
					#ifdef USE_API_HOMEASSISTANT_SERVICES
 | 
				
			||||||
 | 
					#include <functional>
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
#include "api_pb2.h"
 | 
					#include "api_pb2.h"
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					#include "esphome/components/json/json_util.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
#include "esphome/core/automation.h"
 | 
					#include "esphome/core/automation.h"
 | 
				
			||||||
#include "esphome/core/helpers.h"
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
#include <vector>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome::api {
 | 
					namespace esphome::api {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -44,9 +49,47 @@ template<typename... Ts> class TemplatableKeyValuePair {
 | 
				
			|||||||
  TemplatableStringValue<Ts...> value;
 | 
					  TemplatableStringValue<Ts...> value;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					// Represents the response data from a Home Assistant action
 | 
				
			||||||
 | 
					class ActionResponse {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  ActionResponse(bool success, std::string error_message = "")
 | 
				
			||||||
 | 
					      : success_(success), error_message_(std::move(error_message)) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  ActionResponse(bool success, std::string error_message, const uint8_t *data, size_t data_len)
 | 
				
			||||||
 | 
					      : success_(success), error_message_(std::move(error_message)) {
 | 
				
			||||||
 | 
					    if (data == nullptr || data_len == 0)
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    this->json_document_ = json::parse_json(data, data_len);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool is_success() const { return this->success_; }
 | 
				
			||||||
 | 
					  const std::string &get_error_message() const { return this->error_message_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  // Get data as parsed JSON object (const version returns read-only view)
 | 
				
			||||||
 | 
					  JsonObjectConst get_json() const { return this->json_document_.as<JsonObjectConst>(); }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  bool success_;
 | 
				
			||||||
 | 
					  std::string error_message_;
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  JsonDocument json_document_;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Callback type for action responses
 | 
				
			||||||
 | 
					template<typename... Ts> using ActionResponseCallback = std::function<void(const ActionResponse &, Ts...)>;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
 | 
					template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts...> {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
 | 
					  explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent) {
 | 
				
			||||||
 | 
					    this->flags_.is_event = is_event;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  template<typename T> void set_service(T service) { this->service_ = service; }
 | 
					  template<typename T> void set_service(T service) { this->service_ = service; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -61,11 +104,29 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
 | 
				
			|||||||
    this->variables_.emplace_back(std::move(key), value);
 | 
					    this->variables_.emplace_back(std::move(key), value);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					  template<typename T> void set_response_template(T response_template) {
 | 
				
			||||||
 | 
					    this->response_template_ = response_template;
 | 
				
			||||||
 | 
					    this->flags_.has_response_template = true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_wants_status() { this->flags_.wants_status = true; }
 | 
				
			||||||
 | 
					  void set_wants_response() { this->flags_.wants_response = true; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  Trigger<JsonObjectConst, Ts...> *get_success_trigger_with_response() const {
 | 
				
			||||||
 | 
					    return this->success_trigger_with_response_;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  Trigger<Ts...> *get_success_trigger() const { return this->success_trigger_; }
 | 
				
			||||||
 | 
					  Trigger<std::string, Ts...> *get_error_trigger() const { return this->error_trigger_; }
 | 
				
			||||||
 | 
					#endif  // USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void play(Ts... x) override {
 | 
					  void play(Ts... x) override {
 | 
				
			||||||
    HomeassistantServiceResponse resp;
 | 
					    HomeassistantActionRequest resp;
 | 
				
			||||||
    std::string service_value = this->service_.value(x...);
 | 
					    std::string service_value = this->service_.value(x...);
 | 
				
			||||||
    resp.set_service(StringRef(service_value));
 | 
					    resp.set_service(StringRef(service_value));
 | 
				
			||||||
    resp.is_event = this->is_event_;
 | 
					    resp.is_event = this->flags_.is_event;
 | 
				
			||||||
    for (auto &it : this->data_) {
 | 
					    for (auto &it : this->data_) {
 | 
				
			||||||
      resp.data.emplace_back();
 | 
					      resp.data.emplace_back();
 | 
				
			||||||
      auto &kv = resp.data.back();
 | 
					      auto &kv = resp.data.back();
 | 
				
			||||||
@@ -84,18 +145,74 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
 | 
				
			|||||||
      kv.set_key(StringRef(it.key));
 | 
					      kv.set_key(StringRef(it.key));
 | 
				
			||||||
      kv.value = it.value.value(x...);
 | 
					      kv.value = it.value.value(x...);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this->parent_->send_homeassistant_service_call(resp);
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					    if (this->flags_.wants_status) {
 | 
				
			||||||
 | 
					      // Generate a unique call ID for this service call
 | 
				
			||||||
 | 
					      static uint32_t call_id_counter = 1;
 | 
				
			||||||
 | 
					      uint32_t call_id = call_id_counter++;
 | 
				
			||||||
 | 
					      resp.call_id = call_id;
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					      if (this->flags_.wants_response) {
 | 
				
			||||||
 | 
					        resp.wants_response = true;
 | 
				
			||||||
 | 
					        // Set response template if provided
 | 
				
			||||||
 | 
					        if (this->flags_.has_response_template) {
 | 
				
			||||||
 | 
					          std::string response_template_value = this->response_template_.value(x...);
 | 
				
			||||||
 | 
					          resp.response_template = response_template_value;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      auto captured_args = std::make_tuple(x...);
 | 
				
			||||||
 | 
					      this->parent_->register_action_response_callback(call_id, [this, captured_args](const ActionResponse &response) {
 | 
				
			||||||
 | 
					        std::apply(
 | 
				
			||||||
 | 
					            [this, &response](auto &&...args) {
 | 
				
			||||||
 | 
					              if (response.is_success()) {
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					                if (this->flags_.wants_response) {
 | 
				
			||||||
 | 
					                  this->success_trigger_with_response_->trigger(response.get_json(), args...);
 | 
				
			||||||
 | 
					                } else
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  this->success_trigger_->trigger(args...);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                this->error_trigger_->trigger(response.get_error_message(), args...);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            captured_args);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this->parent_->send_homeassistant_action(resp);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  APIServer *parent_;
 | 
					  APIServer *parent_;
 | 
				
			||||||
  bool is_event_;
 | 
					 | 
				
			||||||
  TemplatableStringValue<Ts...> service_{};
 | 
					  TemplatableStringValue<Ts...> service_{};
 | 
				
			||||||
  std::vector<TemplatableKeyValuePair<Ts...>> data_;
 | 
					  std::vector<TemplatableKeyValuePair<Ts...>> data_;
 | 
				
			||||||
  std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
 | 
					  std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
 | 
				
			||||||
  std::vector<TemplatableKeyValuePair<Ts...>> variables_;
 | 
					  std::vector<TemplatableKeyValuePair<Ts...>> variables_;
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  TemplatableStringValue<Ts...> response_template_{""};
 | 
				
			||||||
 | 
					  Trigger<JsonObjectConst, Ts...> *success_trigger_with_response_ = new Trigger<JsonObjectConst, Ts...>();
 | 
				
			||||||
 | 
					#endif  // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
 | 
				
			||||||
 | 
					  Trigger<Ts...> *success_trigger_ = new Trigger<Ts...>();
 | 
				
			||||||
 | 
					  Trigger<std::string, Ts...> *error_trigger_ = new Trigger<std::string, Ts...>();
 | 
				
			||||||
 | 
					#endif  // USE_API_HOMEASSISTANT_ACTION_RESPONSES
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  struct Flags {
 | 
				
			||||||
 | 
					    uint8_t is_event : 1;
 | 
				
			||||||
 | 
					    uint8_t wants_status : 1;
 | 
				
			||||||
 | 
					    uint8_t wants_response : 1;
 | 
				
			||||||
 | 
					    uint8_t has_response_template : 1;
 | 
				
			||||||
 | 
					    uint8_t reserved : 5;
 | 
				
			||||||
 | 
					  } flags_{0};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace esphome::api
 | 
					}  // namespace esphome::api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,74 +8,70 @@ namespace esphome::api {
 | 
				
			|||||||
static const char *const TAG = "api.proto";
 | 
					static const char *const TAG = "api.proto";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
 | 
					void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
 | 
				
			||||||
  uint32_t i = 0;
 | 
					  const uint8_t *ptr = buffer;
 | 
				
			||||||
  bool error = false;
 | 
					  const uint8_t *end = buffer + length;
 | 
				
			||||||
  while (i < length) {
 | 
					
 | 
				
			||||||
 | 
					  while (ptr < end) {
 | 
				
			||||||
    uint32_t consumed;
 | 
					    uint32_t consumed;
 | 
				
			||||||
    auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
 | 
					
 | 
				
			||||||
 | 
					    // Parse field header
 | 
				
			||||||
 | 
					    auto res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
 | 
				
			||||||
    if (!res.has_value()) {
 | 
					    if (!res.has_value()) {
 | 
				
			||||||
      ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i);
 | 
					      ESP_LOGV(TAG, "Invalid field start at offset %ld", (long) (ptr - buffer));
 | 
				
			||||||
      break;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    uint32_t field_type = (res->as_uint32()) & 0b111;
 | 
					    uint32_t tag = res->as_uint32();
 | 
				
			||||||
    uint32_t field_id = (res->as_uint32()) >> 3;
 | 
					    uint32_t field_type = tag & 0b111;
 | 
				
			||||||
    i += consumed;
 | 
					    uint32_t field_id = tag >> 3;
 | 
				
			||||||
 | 
					    ptr += consumed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    switch (field_type) {
 | 
					    switch (field_type) {
 | 
				
			||||||
      case 0: {  // VarInt
 | 
					      case 0: {  // VarInt
 | 
				
			||||||
        res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
 | 
					        res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
 | 
				
			||||||
        if (!res.has_value()) {
 | 
					        if (!res.has_value()) {
 | 
				
			||||||
          ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i);
 | 
					          ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
 | 
				
			||||||
          error = true;
 | 
					          return;
 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (!this->decode_varint(field_id, *res)) {
 | 
					        if (!this->decode_varint(field_id, *res)) {
 | 
				
			||||||
          ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
 | 
					          ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        i += consumed;
 | 
					        ptr += consumed;
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      case 2: {  // Length-delimited
 | 
					      case 2: {  // Length-delimited
 | 
				
			||||||
        res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
 | 
					        res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
 | 
				
			||||||
        if (!res.has_value()) {
 | 
					        if (!res.has_value()) {
 | 
				
			||||||
          ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i);
 | 
					          ESP_LOGV(TAG, "Invalid Length Delimited at offset %ld", (long) (ptr - buffer));
 | 
				
			||||||
          error = true;
 | 
					          return;
 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        uint32_t field_length = res->as_uint32();
 | 
					        uint32_t field_length = res->as_uint32();
 | 
				
			||||||
        i += consumed;
 | 
					        ptr += consumed;
 | 
				
			||||||
        if (field_length > length - i) {
 | 
					        if (ptr + field_length > end) {
 | 
				
			||||||
          ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i);
 | 
					          ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
 | 
				
			||||||
          error = true;
 | 
					          return;
 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) {
 | 
					        if (!this->decode_length(field_id, ProtoLengthDelimited(ptr, field_length))) {
 | 
				
			||||||
          ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
 | 
					          ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        i += field_length;
 | 
					        ptr += field_length;
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      case 5: {  // 32-bit
 | 
					      case 5: {  // 32-bit
 | 
				
			||||||
        if (length - i < 4) {
 | 
					        if (ptr + 4 > end) {
 | 
				
			||||||
          ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i);
 | 
					          ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
 | 
				
			||||||
          error = true;
 | 
					          return;
 | 
				
			||||||
          break;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]);
 | 
					        uint32_t val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
 | 
				
			||||||
        if (!this->decode_32bit(field_id, Proto32Bit(val))) {
 | 
					        if (!this->decode_32bit(field_id, Proto32Bit(val))) {
 | 
				
			||||||
          ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
 | 
					          ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        i += 4;
 | 
					        ptr += 4;
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
        ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i);
 | 
					        ESP_LOGV(TAG, "Invalid field type %u at offset %ld", field_type, (long) (ptr - buffer));
 | 
				
			||||||
        error = true;
 | 
					        return;
 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (error) {
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -182,6 +182,10 @@ class ProtoLengthDelimited {
 | 
				
			|||||||
  explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
 | 
					  explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
 | 
				
			||||||
  std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
 | 
					  std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Direct access to raw data without string allocation
 | 
				
			||||||
 | 
					  const uint8_t *data() const { return this->value_; }
 | 
				
			||||||
 | 
					  size_t size() const { return this->length_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Decode the length-delimited data into an existing ProtoDecodableMessage instance.
 | 
					   * Decode the length-delimited data into an existing ProtoDecodableMessage instance.
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
@@ -827,7 +831,7 @@ class ProtoService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Authentication helper methods
 | 
					  // Authentication helper methods
 | 
				
			||||||
  bool check_connection_setup_() {
 | 
					  inline bool check_connection_setup_() {
 | 
				
			||||||
    if (!this->is_connection_setup()) {
 | 
					    if (!this->is_connection_setup()) {
 | 
				
			||||||
      this->on_no_setup_connection();
 | 
					      this->on_no_setup_connection();
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
@@ -835,7 +839,7 @@ class ProtoService {
 | 
				
			|||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool check_authenticated_() {
 | 
					  inline bool check_authenticated_() {
 | 
				
			||||||
#ifdef USE_API_PASSWORD
 | 
					#ifdef USE_API_PASSWORD
 | 
				
			||||||
    if (!this->check_connection_setup_()) {
 | 
					    if (!this->check_connection_setup_()) {
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
 | 
				
			|||||||
    msg.set_name(StringRef(this->name_));
 | 
					    msg.set_name(StringRef(this->name_));
 | 
				
			||||||
    msg.key = this->key_;
 | 
					    msg.key = this->key_;
 | 
				
			||||||
    std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
 | 
					    std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
 | 
				
			||||||
    for (int i = 0; i < sizeof...(Ts); i++) {
 | 
					    for (size_t i = 0; i < sizeof...(Ts); i++) {
 | 
				
			||||||
      msg.args.emplace_back();
 | 
					      msg.args.emplace_back();
 | 
				
			||||||
      auto &arg = msg.args.back();
 | 
					      auto &arg = msg.args.back();
 | 
				
			||||||
      arg.type = arg_types[i];
 | 
					      arg.type = arg_types[i];
 | 
				
			||||||
@@ -55,7 +55,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  virtual void execute(Ts... x) = 0;
 | 
					  virtual void execute(Ts... x) = 0;
 | 
				
			||||||
  template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...> type) {
 | 
					  template<int... S> void execute_(const std::vector<ExecuteServiceArgument> &args, seq<S...> type) {
 | 
				
			||||||
    this->execute((get_execute_arg_value<Ts>(args[S]))...);
 | 
					    this->execute((get_execute_arg_value<Ts>(args[S]))...);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ import esphome.codegen as cg
 | 
				
			|||||||
from esphome.components import i2c, sensor
 | 
					from esphome.components import i2c, sensor
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_CLEAR,
 | 
				
			||||||
    CONF_GAIN,
 | 
					    CONF_GAIN,
 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
    DEVICE_CLASS_ILLUMINANCE,
 | 
					    DEVICE_CLASS_ILLUMINANCE,
 | 
				
			||||||
@@ -29,7 +30,6 @@ CONF_F5 = "f5"
 | 
				
			|||||||
CONF_F6 = "f6"
 | 
					CONF_F6 = "f6"
 | 
				
			||||||
CONF_F7 = "f7"
 | 
					CONF_F7 = "f7"
 | 
				
			||||||
CONF_F8 = "f8"
 | 
					CONF_F8 = "f8"
 | 
				
			||||||
CONF_CLEAR = "clear"
 | 
					 | 
				
			||||||
CONF_NIR = "nir"
 | 
					CONF_NIR = "nir"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
UNIT_COUNTS = "#"
 | 
					UNIT_COUNTS = "#"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ from esphome.const import (
 | 
				
			|||||||
    PLATFORM_LN882X,
 | 
					    PLATFORM_LN882X,
 | 
				
			||||||
    PLATFORM_RTL87XX,
 | 
					    PLATFORM_RTL87XX,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.core import CORE, coroutine_with_priority
 | 
					from esphome.core import CORE, CoroPriority, coroutine_with_priority
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@esphome/core"]
 | 
					CODEOWNERS = ["@esphome/core"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@coroutine_with_priority(200.0)
 | 
					@coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT)
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    if CORE.is_esp32 or CORE.is_libretiny:
 | 
					    if CORE.is_esp32 or CORE.is_libretiny:
 | 
				
			||||||
        # https://github.com/ESP32Async/AsyncTCP
 | 
					        # https://github.com/ESP32Async/AsyncTCP
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@ from esphome.const import (
 | 
				
			|||||||
    DEVICE_CLASS_ENERGY,
 | 
					    DEVICE_CLASS_ENERGY,
 | 
				
			||||||
    DEVICE_CLASS_POWER,
 | 
					    DEVICE_CLASS_POWER,
 | 
				
			||||||
    DEVICE_CLASS_POWER_FACTOR,
 | 
					    DEVICE_CLASS_POWER_FACTOR,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_REACTIVE_POWER,
 | 
				
			||||||
    DEVICE_CLASS_VOLTAGE,
 | 
					    DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
    ICON_CURRENT_AC,
 | 
					    ICON_CURRENT_AC,
 | 
				
			||||||
    ICON_LIGHTBULB,
 | 
					    ICON_LIGHTBULB,
 | 
				
			||||||
@@ -78,6 +79,7 @@ CONFIG_SCHEMA = (
 | 
				
			|||||||
                unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
 | 
					                unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
 | 
				
			||||||
                icon=ICON_LIGHTBULB,
 | 
					                icon=ICON_LIGHTBULB,
 | 
				
			||||||
                accuracy_decimals=2,
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_REACTIVE_POWER,
 | 
				
			||||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
 | 
					            cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -382,20 +382,15 @@ float ATM90E32Component::get_setup_priority() const { return setup_priority::IO;
 | 
				
			|||||||
// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H)
 | 
					// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H)
 | 
				
			||||||
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
 | 
					// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
 | 
				
			||||||
// Default is 143FH (20ms, 63ms)
 | 
					// Default is 143FH (20ms, 63ms)
 | 
				
			||||||
uint16_t ATM90E32Component::read16_transaction_(uint16_t a_register) {
 | 
					uint16_t ATM90E32Component::read16_(uint16_t a_register) {
 | 
				
			||||||
 | 
					  this->enable();
 | 
				
			||||||
 | 
					  delay_microseconds_safe(1);  // min delay between CS low and first SCK is 200ns - 1us is plenty
 | 
				
			||||||
  uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
 | 
					  uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
 | 
				
			||||||
  uint8_t addrl = (a_register & 0xFF);
 | 
					  uint8_t addrl = (a_register & 0xFF);
 | 
				
			||||||
  uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
 | 
					  uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
 | 
				
			||||||
  this->transfer_array(data, 4);
 | 
					  this->transfer_array(data, 4);
 | 
				
			||||||
  uint16_t output = encode_uint16(data[2], data[3]);
 | 
					  uint16_t output = encode_uint16(data[2], data[3]);
 | 
				
			||||||
  ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
 | 
					  ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
 | 
				
			||||||
  return output;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
 | 
					 | 
				
			||||||
  this->enable();
 | 
					 | 
				
			||||||
  delay_microseconds_safe(1);  // min delay between CS low and first SCK is 200ns - 1us is plenty
 | 
					 | 
				
			||||||
  uint16_t output = this->read16_transaction_(a_register);
 | 
					 | 
				
			||||||
  delay_microseconds_safe(1);  // allow the last clock to propagate before releasing CS
 | 
					  delay_microseconds_safe(1);  // allow the last clock to propagate before releasing CS
 | 
				
			||||||
  this->disable();
 | 
					  this->disable();
 | 
				
			||||||
  delay_microseconds_safe(1);  // meet minimum CS high time before next transaction
 | 
					  delay_microseconds_safe(1);  // meet minimum CS high time before next transaction
 | 
				
			||||||
@@ -403,14 +398,8 @@ uint16_t ATM90E32Component::read16_(uint16_t a_register) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
 | 
					int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
 | 
				
			||||||
  this->enable();
 | 
					  const uint16_t val_h = this->read16_(addr_h);
 | 
				
			||||||
  delay_microseconds_safe(1);
 | 
					  const uint16_t val_l = this->read16_(addr_l);
 | 
				
			||||||
  const uint16_t val_h = this->read16_transaction_(addr_h);
 | 
					 | 
				
			||||||
  delay_microseconds_safe(1);
 | 
					 | 
				
			||||||
  const uint16_t val_l = this->read16_transaction_(addr_l);
 | 
					 | 
				
			||||||
  delay_microseconds_safe(1);
 | 
					 | 
				
			||||||
  this->disable();
 | 
					 | 
				
			||||||
  delay_microseconds_safe(1);
 | 
					 | 
				
			||||||
  const int32_t val = (val_h << 16) | val_l;
 | 
					  const int32_t val = (val_h << 16) | val_l;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ESP_LOGVV(TAG,
 | 
					  ESP_LOGVV(TAG,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -140,7 +140,6 @@ class ATM90E32Component : public PollingComponent,
 | 
				
			|||||||
  number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
 | 
					  number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  uint16_t read16_(uint16_t a_register);
 | 
					  uint16_t read16_(uint16_t a_register);
 | 
				
			||||||
  uint16_t read16_transaction_(uint16_t a_register);
 | 
					 | 
				
			||||||
  int read32_(uint16_t addr_h, uint16_t addr_l);
 | 
					  int read32_(uint16_t addr_h, uint16_t addr_l);
 | 
				
			||||||
  void write16_(uint16_t a_register, uint16_t val, bool validate = true);
 | 
					  void write16_(uint16_t a_register, uint16_t val, bool validate = true);
 | 
				
			||||||
  float get_local_phase_voltage_(uint8_t phase);
 | 
					  float get_local_phase_voltage_(uint8_t phase);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,10 +17,12 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_REACTIVE_POWER,
 | 
					    CONF_REACTIVE_POWER,
 | 
				
			||||||
    CONF_REVERSE_ACTIVE_ENERGY,
 | 
					    CONF_REVERSE_ACTIVE_ENERGY,
 | 
				
			||||||
    CONF_VOLTAGE,
 | 
					    CONF_VOLTAGE,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_APPARENT_POWER,
 | 
				
			||||||
    DEVICE_CLASS_CURRENT,
 | 
					    DEVICE_CLASS_CURRENT,
 | 
				
			||||||
    DEVICE_CLASS_ENERGY,
 | 
					    DEVICE_CLASS_ENERGY,
 | 
				
			||||||
    DEVICE_CLASS_POWER,
 | 
					    DEVICE_CLASS_POWER,
 | 
				
			||||||
    DEVICE_CLASS_POWER_FACTOR,
 | 
					    DEVICE_CLASS_POWER_FACTOR,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_REACTIVE_POWER,
 | 
				
			||||||
    DEVICE_CLASS_TEMPERATURE,
 | 
					    DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
    DEVICE_CLASS_VOLTAGE,
 | 
					    DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
					    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
				
			||||||
@@ -100,13 +102,13 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
 | 
				
			|||||||
            unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
 | 
					            unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
 | 
				
			||||||
            icon=ICON_LIGHTBULB,
 | 
					            icon=ICON_LIGHTBULB,
 | 
				
			||||||
            accuracy_decimals=2,
 | 
					            accuracy_decimals=2,
 | 
				
			||||||
            device_class=DEVICE_CLASS_POWER,
 | 
					            device_class=DEVICE_CLASS_REACTIVE_POWER,
 | 
				
			||||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
					            state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
 | 
					        cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
 | 
				
			||||||
            unit_of_measurement=UNIT_VOLT_AMPS,
 | 
					            unit_of_measurement=UNIT_VOLT_AMPS,
 | 
				
			||||||
            accuracy_decimals=2,
 | 
					            accuracy_decimals=2,
 | 
				
			||||||
            device_class=DEVICE_CLASS_POWER,
 | 
					            device_class=DEVICE_CLASS_APPARENT_POWER,
 | 
				
			||||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
					            state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
 | 
					        cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -165,4 +165,4 @@ def final_validate_audio_schema(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    cg.add_library("esphome/esp-audio-libs", "1.1.4")
 | 
					    cg.add_library("esphome/esp-audio-libs", "2.0.1")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,7 +57,7 @@ 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,
 | 
					void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor,
 | 
				
			||||||
                         size_t samples_to_scale) {
 | 
					                         size_t samples_to_scale) {
 | 
				
			||||||
  // Note the assembly dsps_mulc function has audio glitches if the input and output buffers are the same.
 | 
					  // Note the assembly dsps_mulc function has audio glitches if the input and output buffers are the same.
 | 
				
			||||||
  for (int i = 0; i < samples_to_scale; i++) {
 | 
					  for (size_t i = 0; i < samples_to_scale; i++) {
 | 
				
			||||||
    int32_t acc = (int32_t) audio_samples[i] * (int32_t) scale_factor;
 | 
					    int32_t acc = (int32_t) audio_samples[i] * (int32_t) scale_factor;
 | 
				
			||||||
    output_buffer[i] = (int16_t) (acc >> 15);
 | 
					    output_buffer[i] = (int16_t) (acc >> 15);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -229,18 +229,18 @@ FileDecoderState AudioDecoder::decode_flac_() {
 | 
				
			|||||||
    auto result = this->flac_decoder_->read_header(this->input_transfer_buffer_->get_buffer_start(),
 | 
					    auto result = this->flac_decoder_->read_header(this->input_transfer_buffer_->get_buffer_start(),
 | 
				
			||||||
                                                   this->input_transfer_buffer_->available());
 | 
					                                                   this->input_transfer_buffer_->available());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (result == esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) {
 | 
					    if (result > esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) {
 | 
				
			||||||
      return FileDecoderState::POTENTIALLY_FAILED;
 | 
					      // Serrious error reading FLAC header, there is no recovery
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (result != esp_audio_libs::flac::FLAC_DECODER_SUCCESS) {
 | 
					 | 
				
			||||||
      // Couldn't read FLAC header
 | 
					 | 
				
			||||||
      return FileDecoderState::FAILED;
 | 
					      return FileDecoderState::FAILED;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
 | 
					    size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
 | 
				
			||||||
    this->input_transfer_buffer_->decrease_buffer_length(bytes_consumed);
 | 
					    this->input_transfer_buffer_->decrease_buffer_length(bytes_consumed);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (result == esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) {
 | 
				
			||||||
 | 
					      return FileDecoderState::MORE_TO_PROCESS;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Reallocate the output transfer buffer to the smallest necessary size
 | 
					    // Reallocate the output transfer buffer to the smallest necessary size
 | 
				
			||||||
    this->free_buffer_required_ = flac_decoder_->get_output_buffer_size_bytes();
 | 
					    this->free_buffer_required_ = flac_decoder_->get_output_buffer_size_bytes();
 | 
				
			||||||
    if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) {
 | 
					    if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) {
 | 
				
			||||||
@@ -256,9 +256,9 @@ FileDecoderState AudioDecoder::decode_flac_() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uint32_t output_samples = 0;
 | 
					  uint32_t output_samples = 0;
 | 
				
			||||||
  auto result = this->flac_decoder_->decode_frame(
 | 
					  auto result = this->flac_decoder_->decode_frame(this->input_transfer_buffer_->get_buffer_start(),
 | 
				
			||||||
      this->input_transfer_buffer_->get_buffer_start(), this->input_transfer_buffer_->available(),
 | 
					                                                  this->input_transfer_buffer_->available(),
 | 
				
			||||||
      reinterpret_cast<int16_t *>(this->output_transfer_buffer_->get_buffer_end()), &output_samples);
 | 
					                                                  this->output_transfer_buffer_->get_buffer_end(), &output_samples);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (result == esp_audio_libs::flac::FLAC_DECODER_ERROR_OUT_OF_DATA) {
 | 
					  if (result == esp_audio_libs::flac::FLAC_DECODER_ERROR_OUT_OF_DATA) {
 | 
				
			||||||
    // Not an issue, just needs more data that we'll get next time.
 | 
					    // Not an issue, just needs more data that we'll get next time.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ from esphome import automation
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import CONF_ID, CONF_MIC_GAIN
 | 
					from esphome.const import CONF_ID, CONF_MIC_GAIN
 | 
				
			||||||
from esphome.core import coroutine_with_priority
 | 
					from esphome.core import CoroPriority, coroutine_with_priority
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@kbx81"]
 | 
					CODEOWNERS = ["@kbx81"]
 | 
				
			||||||
IS_PLATFORM_COMPONENT = True
 | 
					IS_PLATFORM_COMPONENT = True
 | 
				
			||||||
@@ -35,7 +35,7 @@ async def audio_adc_set_mic_gain_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
    return var
 | 
					    return var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@coroutine_with_priority(100.0)
 | 
					@coroutine_with_priority(CoroPriority.CORE)
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    cg.add_define("USE_AUDIO_ADC")
 | 
					    cg.add_define("USE_AUDIO_ADC")
 | 
				
			||||||
    cg.add_global(audio_adc_ns.using)
 | 
					    cg.add_global(audio_adc_ns.using)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ from esphome.automation import maybe_simple_id
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import CONF_ID, CONF_VOLUME
 | 
					from esphome.const import CONF_ID, CONF_VOLUME
 | 
				
			||||||
from esphome.core import coroutine_with_priority
 | 
					from esphome.core import CoroPriority, coroutine_with_priority
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@kbx81"]
 | 
					CODEOWNERS = ["@kbx81"]
 | 
				
			||||||
IS_PLATFORM_COMPONENT = True
 | 
					IS_PLATFORM_COMPONENT = True
 | 
				
			||||||
@@ -51,7 +51,7 @@ async def audio_dac_set_volume_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
    return var
 | 
					    return var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@coroutine_with_priority(100.0)
 | 
					@coroutine_with_priority(CoroPriority.CORE)
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    cg.add_define("USE_AUDIO_DAC")
 | 
					    cg.add_define("USE_AUDIO_DAC")
 | 
				
			||||||
    cg.add_global(audio_dac_ns.using)
 | 
					    cg.add_global(audio_dac_ns.using)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@ constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#define ERROR_CHECK(err) \
 | 
					#define ERROR_CHECK(err) \
 | 
				
			||||||
  if ((err) != i2c::ERROR_OK) { \
 | 
					  if ((err) != i2c::ERROR_OK) { \
 | 
				
			||||||
    this->status_set_warning("Failed to communicate"); \
 | 
					    this->status_set_warning(LOG_STR("Failed to communicate")); \
 | 
				
			||||||
    return; \
 | 
					    return; \
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -41,7 +41,7 @@ void AXS15231Touchscreen::update_touches() {
 | 
				
			|||||||
  i2c::ErrorCode err;
 | 
					  i2c::ErrorCode err;
 | 
				
			||||||
  uint8_t data[8]{};
 | 
					  uint8_t data[8]{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD), false);
 | 
					  err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD));
 | 
				
			||||||
  ERROR_CHECK(err);
 | 
					  ERROR_CHECK(err);
 | 
				
			||||||
  err = this->read(data, sizeof(data));
 | 
					  err = this->read(data, sizeof(data));
 | 
				
			||||||
  ERROR_CHECK(err);
 | 
					  ERROR_CHECK(err);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -493,7 +493,7 @@ void BedJetHub::dump_config() {
 | 
				
			|||||||
                "  ble_client.app_id: %d\n"
 | 
					                "  ble_client.app_id: %d\n"
 | 
				
			||||||
                "  ble_client.conn_id: %d",
 | 
					                "  ble_client.conn_id: %d",
 | 
				
			||||||
                this->get_name().c_str(), this->parent()->app_id, this->parent()->get_conn_id());
 | 
					                this->get_name().c_str(), this->parent()->app_id, this->parent()->get_conn_id());
 | 
				
			||||||
  LOG_UPDATE_INTERVAL(this)
 | 
					  LOG_UPDATE_INTERVAL(this);
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Child components (%d):", this->children_.size());
 | 
					  ESP_LOGCONFIG(TAG, "  Child components (%d):", this->children_.size());
 | 
				
			||||||
  for (auto *child : this->children_) {
 | 
					  for (auto *child : this->children_) {
 | 
				
			||||||
    ESP_LOGCONFIG(TAG, "    - %s", child->describe().c_str());
 | 
					    ESP_LOGCONFIG(TAG, "    - %s", child->describe().c_str());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,7 +59,7 @@ from esphome.const import (
 | 
				
			|||||||
    DEVICE_CLASS_VIBRATION,
 | 
					    DEVICE_CLASS_VIBRATION,
 | 
				
			||||||
    DEVICE_CLASS_WINDOW,
 | 
					    DEVICE_CLASS_WINDOW,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.core import CORE, coroutine_with_priority
 | 
					from esphome.core import CORE, CoroPriority, coroutine_with_priority
 | 
				
			||||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
 | 
					from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
 | 
				
			||||||
from esphome.cpp_generator import MockObjClass
 | 
					from esphome.cpp_generator import MockObjClass
 | 
				
			||||||
from esphome.util import Registry
 | 
					from esphome.util import Registry
 | 
				
			||||||
@@ -652,7 +652,7 @@ async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args)
 | 
				
			|||||||
    return cg.new_Pvariable(condition_id, template_arg, paren, False)
 | 
					    return cg.new_Pvariable(condition_id, template_arg, paren, False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@coroutine_with_priority(100.0)
 | 
					@coroutine_with_priority(CoroPriority.CORE)
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    cg.add_global(binary_sensor_ns.using)
 | 
					    cg.add_global(binary_sensor_ns.using)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,19 @@ namespace binary_sensor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static const char *const TAG = "binary_sensor";
 | 
					static const char *const TAG = "binary_sensor";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Function implementation of LOG_BINARY_SENSOR macro to reduce code size
 | 
				
			||||||
 | 
					void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj) {
 | 
				
			||||||
 | 
					  if (obj == nullptr) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!obj->get_device_class_ref().empty()) {
 | 
				
			||||||
 | 
					    ESP_LOGCONFIG(tag, "%s  Device Class: '%s'", prefix, obj->get_device_class_ref().c_str());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void BinarySensor::publish_state(bool new_state) {
 | 
					void BinarySensor::publish_state(bool new_state) {
 | 
				
			||||||
  if (this->filter_list_ == nullptr) {
 | 
					  if (this->filter_list_ == nullptr) {
 | 
				
			||||||
    this->send_state_internal(new_state);
 | 
					    this->send_state_internal(new_state);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,13 +10,10 @@ namespace esphome {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace binary_sensor {
 | 
					namespace binary_sensor {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define LOG_BINARY_SENSOR(prefix, type, obj) \
 | 
					class BinarySensor;
 | 
				
			||||||
  if ((obj) != nullptr) { \
 | 
					void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj);
 | 
				
			||||||
    ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
 | 
					
 | 
				
			||||||
    if (!(obj)->get_device_class().empty()) { \
 | 
					#define LOG_BINARY_SENSOR(prefix, type, obj) log_binary_sensor(TAG, prefix, LOG_STR_LITERAL(type), obj)
 | 
				
			||||||
      ESP_LOGCONFIG(TAG, "%s  Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
 | 
					 | 
				
			||||||
    } \
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define SUB_BINARY_SENSOR(name) \
 | 
					#define SUB_BINARY_SENSOR(name) \
 | 
				
			||||||
 protected: \
 | 
					 protected: \
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -97,10 +97,10 @@ void BL0906::handle_actions_() {
 | 
				
			|||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  ActionCallbackFuncPtr ptr_func = nullptr;
 | 
					  ActionCallbackFuncPtr ptr_func = nullptr;
 | 
				
			||||||
  for (int i = 0; i < this->action_queue_.size(); i++) {
 | 
					  for (size_t i = 0; i < this->action_queue_.size(); i++) {
 | 
				
			||||||
    ptr_func = this->action_queue_[i];
 | 
					    ptr_func = this->action_queue_[i];
 | 
				
			||||||
    if (ptr_func) {
 | 
					    if (ptr_func) {
 | 
				
			||||||
      ESP_LOGI(TAG, "HandleActionCallback[%d]", i);
 | 
					      ESP_LOGI(TAG, "HandleActionCallback[%zu]", i);
 | 
				
			||||||
      (this->*ptr_func)();
 | 
					      (this->*ptr_func)();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1 +1,6 @@
 | 
				
			|||||||
CODEOWNERS = ["@tobias-"]
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CODEOWNERS = ["@tobias-", "@dan-s-github"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_BL0940_ID = "bl0940_id"
 | 
				
			||||||
 | 
					bl0940_ns = cg.esphome_ns.namespace("bl0940")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,28 +7,26 @@ namespace bl0940 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static const char *const TAG = "bl0940";
 | 
					static const char *const TAG = "bl0940";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const uint8_t BL0940_READ_COMMAND = 0x50;  // 0x58 according to documentation
 | 
					 | 
				
			||||||
static const uint8_t BL0940_FULL_PACKET = 0xAA;
 | 
					static const uint8_t BL0940_FULL_PACKET = 0xAA;
 | 
				
			||||||
static const uint8_t BL0940_PACKET_HEADER = 0x55;  // 0x58 according to documentation
 | 
					static const uint8_t BL0940_PACKET_HEADER = 0x55;  // 0x58 according to en doc but 0x55 in cn doc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const uint8_t BL0940_WRITE_COMMAND = 0xA0;  // 0xA8 according to documentation
 | 
					 | 
				
			||||||
static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10;
 | 
					static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10;
 | 
				
			||||||
static const uint8_t BL0940_REG_MODE = 0x18;
 | 
					static const uint8_t BL0940_REG_MODE = 0x18;
 | 
				
			||||||
static const uint8_t BL0940_REG_SOFT_RESET = 0x19;
 | 
					static const uint8_t BL0940_REG_SOFT_RESET = 0x19;
 | 
				
			||||||
static const uint8_t BL0940_REG_USR_WRPROT = 0x1A;
 | 
					static const uint8_t BL0940_REG_USR_WRPROT = 0x1A;
 | 
				
			||||||
static const uint8_t BL0940_REG_TPS_CTRL = 0x1B;
 | 
					static const uint8_t BL0940_REG_TPS_CTRL = 0x1B;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const uint8_t BL0940_INIT[5][6] = {
 | 
					static const uint8_t BL0940_INIT[5][5] = {
 | 
				
			||||||
    // Reset to default
 | 
					    // Reset to default
 | 
				
			||||||
    {BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
 | 
					    {BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
 | 
				
			||||||
    // Enable User Operation Write
 | 
					    // Enable User Operation Write
 | 
				
			||||||
    {BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
 | 
					    {BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
 | 
				
			||||||
    // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
 | 
					    // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
 | 
				
			||||||
    {BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
 | 
					    {BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
 | 
				
			||||||
    // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
 | 
					    // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
 | 
				
			||||||
    {BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
 | 
					    {BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
 | 
				
			||||||
    // 0x181C = Half cycle, Fast RMS threshold 6172
 | 
					    // 0x181C = Half cycle, Fast RMS threshold 6172
 | 
				
			||||||
    {BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
 | 
					    {BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void BL0940::loop() {
 | 
					void BL0940::loop() {
 | 
				
			||||||
  DataPacket buffer;
 | 
					  DataPacket buffer;
 | 
				
			||||||
@@ -36,8 +34,8 @@ void BL0940::loop() {
 | 
				
			|||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
 | 
					  if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
 | 
				
			||||||
    if (validate_checksum(&buffer)) {
 | 
					    if (this->validate_checksum_(&buffer)) {
 | 
				
			||||||
      received_package_(&buffer);
 | 
					      this->received_package_(&buffer);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
 | 
					    ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
 | 
				
			||||||
@@ -46,35 +44,151 @@ void BL0940::loop() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool BL0940::validate_checksum(const DataPacket *data) {
 | 
					bool BL0940::validate_checksum_(DataPacket *data) {
 | 
				
			||||||
  uint8_t checksum = BL0940_READ_COMMAND;
 | 
					  uint8_t checksum = this->read_command_;
 | 
				
			||||||
  // Whole package but checksum
 | 
					  // Whole package but checksum
 | 
				
			||||||
  for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) {
 | 
					  uint8_t *raw = (uint8_t *) data;
 | 
				
			||||||
    checksum += data->raw[i];
 | 
					  for (uint32_t i = 0; i < sizeof(*data) - 1; i++) {
 | 
				
			||||||
 | 
					    checksum += raw[i];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  checksum ^= 0xFF;
 | 
					  checksum ^= 0xFF;
 | 
				
			||||||
  if (checksum != data->checksum) {
 | 
					  if (checksum != data->checksum) {
 | 
				
			||||||
    ESP_LOGW(TAG, "BL0940 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
 | 
					    ESP_LOGW(TAG, "Invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return checksum == data->checksum;
 | 
					  return checksum == data->checksum;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void BL0940::update() {
 | 
					void BL0940::update() {
 | 
				
			||||||
  this->flush();
 | 
					  this->flush();
 | 
				
			||||||
  this->write_byte(BL0940_READ_COMMAND);
 | 
					  this->write_byte(this->read_command_);
 | 
				
			||||||
  this->write_byte(BL0940_FULL_PACKET);
 | 
					  this->write_byte(BL0940_FULL_PACKET);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void BL0940::setup() {
 | 
					void BL0940::setup() {
 | 
				
			||||||
 | 
					#ifdef USE_NUMBER
 | 
				
			||||||
 | 
					  // add calibration callbacks
 | 
				
			||||||
 | 
					  if (this->voltage_calibration_number_ != nullptr) {
 | 
				
			||||||
 | 
					    this->voltage_calibration_number_->add_on_state_callback(
 | 
				
			||||||
 | 
					        [this](float state) { this->voltage_calibration_callback_(state); });
 | 
				
			||||||
 | 
					    if (this->voltage_calibration_number_->has_state()) {
 | 
				
			||||||
 | 
					      this->voltage_calibration_callback_(this->voltage_calibration_number_->state);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->current_calibration_number_ != nullptr) {
 | 
				
			||||||
 | 
					    this->current_calibration_number_->add_on_state_callback(
 | 
				
			||||||
 | 
					        [this](float state) { this->current_calibration_callback_(state); });
 | 
				
			||||||
 | 
					    if (this->current_calibration_number_->has_state()) {
 | 
				
			||||||
 | 
					      this->current_calibration_callback_(this->current_calibration_number_->state);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->power_calibration_number_ != nullptr) {
 | 
				
			||||||
 | 
					    this->power_calibration_number_->add_on_state_callback(
 | 
				
			||||||
 | 
					        [this](float state) { this->power_calibration_callback_(state); });
 | 
				
			||||||
 | 
					    if (this->power_calibration_number_->has_state()) {
 | 
				
			||||||
 | 
					      this->power_calibration_callback_(this->power_calibration_number_->state);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->energy_calibration_number_ != nullptr) {
 | 
				
			||||||
 | 
					    this->energy_calibration_number_->add_on_state_callback(
 | 
				
			||||||
 | 
					        [this](float state) { this->energy_calibration_callback_(state); });
 | 
				
			||||||
 | 
					    if (this->energy_calibration_number_->has_state()) {
 | 
				
			||||||
 | 
					      this->energy_calibration_callback_(this->energy_calibration_number_->state);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // calculate calibrated reference values
 | 
				
			||||||
 | 
					  this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_;
 | 
				
			||||||
 | 
					  this->current_reference_cal_ = this->current_reference_ / this->current_cal_;
 | 
				
			||||||
 | 
					  this->power_reference_cal_ = this->power_reference_ / this->power_cal_;
 | 
				
			||||||
 | 
					  this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (auto *i : BL0940_INIT) {
 | 
					  for (auto *i : BL0940_INIT) {
 | 
				
			||||||
    this->write_array(i, 6);
 | 
					    this->write_byte(this->write_command_), this->write_array(i, 5);
 | 
				
			||||||
    delay(1);
 | 
					    delay(1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->flush();
 | 
					  this->flush();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
 | 
					float BL0940::calculate_power_reference_() {
 | 
				
			||||||
  auto tb = (float) (temperature.h << 8 | temperature.l);
 | 
					  // calculate power reference based on voltage and current reference
 | 
				
			||||||
 | 
					  return this->voltage_reference_cal_ * this->current_reference_cal_ * 4046 / 324004 / 79931;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					float BL0940::calculate_energy_reference_() {
 | 
				
			||||||
 | 
					  // formula: 3600000 * 4046 * RL * R1 * 1000 / (1638.4 * 256) / Vref² / (R1 + R2)
 | 
				
			||||||
 | 
					  // or:  power_reference_ * 3600000 / (1638.4 * 256)
 | 
				
			||||||
 | 
					  return this->power_reference_cal_ * 3600000 / (1638.4 * 256);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					float BL0940::calculate_calibration_value_(float state) { return (100 + state) / 100; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BL0940::reset_calibration() {
 | 
				
			||||||
 | 
					#ifdef USE_NUMBER
 | 
				
			||||||
 | 
					  if (this->current_calibration_number_ != nullptr && this->current_cal_ != 1) {
 | 
				
			||||||
 | 
					    this->current_calibration_number_->make_call().set_value(0).perform();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (this->voltage_calibration_number_ != nullptr && this->voltage_cal_ != 1) {
 | 
				
			||||||
 | 
					    this->voltage_calibration_number_->make_call().set_value(0).perform();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (this->power_calibration_number_ != nullptr && this->power_cal_ != 1) {
 | 
				
			||||||
 | 
					    this->power_calibration_number_->make_call().set_value(0).perform();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (this->energy_calibration_number_ != nullptr && this->energy_cal_ != 1) {
 | 
				
			||||||
 | 
					    this->energy_calibration_number_->make_call().set_value(0).perform();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  ESP_LOGD(TAG, "external calibration values restored to initial state");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BL0940::current_calibration_callback_(float state) {
 | 
				
			||||||
 | 
					  this->current_cal_ = this->calculate_calibration_value_(state);
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "update current calibration state: %f", this->current_cal_);
 | 
				
			||||||
 | 
					  this->recalibrate_();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void BL0940::voltage_calibration_callback_(float state) {
 | 
				
			||||||
 | 
					  this->voltage_cal_ = this->calculate_calibration_value_(state);
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "update voltage calibration state: %f", this->voltage_cal_);
 | 
				
			||||||
 | 
					  this->recalibrate_();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void BL0940::power_calibration_callback_(float state) {
 | 
				
			||||||
 | 
					  this->power_cal_ = this->calculate_calibration_value_(state);
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "update power calibration state: %f", this->power_cal_);
 | 
				
			||||||
 | 
					  this->recalibrate_();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					void BL0940::energy_calibration_callback_(float state) {
 | 
				
			||||||
 | 
					  this->energy_cal_ = this->calculate_calibration_value_(state);
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "update energy calibration state: %f", this->energy_cal_);
 | 
				
			||||||
 | 
					  this->recalibrate_();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BL0940::recalibrate_() {
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "Recalibrating reference values");
 | 
				
			||||||
 | 
					  this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_;
 | 
				
			||||||
 | 
					  this->current_reference_cal_ = this->current_reference_ / this->current_cal_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->voltage_cal_ != 1 || this->current_cal_ != 1) {
 | 
				
			||||||
 | 
					    this->power_reference_ = this->calculate_power_reference_();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->power_reference_cal_ = this->power_reference_ / this->power_cal_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->voltage_cal_ != 1 || this->current_cal_ != 1 || this->power_cal_ != 1) {
 | 
				
			||||||
 | 
					    this->energy_reference_ = this->calculate_energy_reference_();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESP_LOGD(TAG,
 | 
				
			||||||
 | 
					           "Recalibrated reference values:\n"
 | 
				
			||||||
 | 
					           "Voltage: %f\n, Current: %f\n, Power: %f\n, Energy: %f\n",
 | 
				
			||||||
 | 
					           this->voltage_reference_cal_, this->current_reference_cal_, this->power_reference_cal_,
 | 
				
			||||||
 | 
					           this->energy_reference_cal_);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					float BL0940::update_temp_(sensor::Sensor *sensor, uint16_le_t temperature) const {
 | 
				
			||||||
 | 
					  auto tb = (float) temperature;
 | 
				
			||||||
  float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
 | 
					  float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
 | 
				
			||||||
  if (sensor != nullptr) {
 | 
					  if (sensor != nullptr) {
 | 
				
			||||||
    if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
 | 
					    if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
 | 
				
			||||||
@@ -87,33 +201,40 @@ float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
 | 
				
			|||||||
  return converted_temp;
 | 
					  return converted_temp;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void BL0940::received_package_(const DataPacket *data) const {
 | 
					void BL0940::received_package_(DataPacket *data) {
 | 
				
			||||||
  // Bad header
 | 
					  // Bad header
 | 
				
			||||||
  if (data->frame_header != BL0940_PACKET_HEADER) {
 | 
					  if (data->frame_header != BL0940_PACKET_HEADER) {
 | 
				
			||||||
    ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
 | 
					    ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_;
 | 
					  // cf_cnt is only 24 bits, so track overflows
 | 
				
			||||||
  float i_rms = (float) to_uint32_t(data->i_rms) / current_reference_;
 | 
					  uint32_t cf_cnt = (uint24_t) data->cf_cnt;
 | 
				
			||||||
  float watt = (float) to_int32_t(data->watt) / power_reference_;
 | 
					  cf_cnt |= this->prev_cf_cnt_ & 0xff000000;
 | 
				
			||||||
  uint32_t cf_cnt = to_uint32_t(data->cf_cnt);
 | 
					  if (cf_cnt < this->prev_cf_cnt_) {
 | 
				
			||||||
  float total_energy_consumption = (float) cf_cnt / energy_reference_;
 | 
					    cf_cnt += 0x1000000;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->prev_cf_cnt_ = cf_cnt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  float tps1 = update_temp_(internal_temperature_sensor_, data->tps1);
 | 
					  float v_rms = (uint24_t) data->v_rms / this->voltage_reference_cal_;
 | 
				
			||||||
  float tps2 = update_temp_(external_temperature_sensor_, data->tps2);
 | 
					  float i_rms = (uint24_t) data->i_rms / this->current_reference_cal_;
 | 
				
			||||||
 | 
					  float watt = (int24_t) data->watt / this->power_reference_cal_;
 | 
				
			||||||
 | 
					  float total_energy_consumption = cf_cnt / this->energy_reference_cal_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (voltage_sensor_ != nullptr) {
 | 
					  float tps1 = update_temp_(this->internal_temperature_sensor_, data->tps1);
 | 
				
			||||||
    voltage_sensor_->publish_state(v_rms);
 | 
					  float tps2 = update_temp_(this->external_temperature_sensor_, data->tps2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->voltage_sensor_ != nullptr) {
 | 
				
			||||||
 | 
					    this->voltage_sensor_->publish_state(v_rms);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (current_sensor_ != nullptr) {
 | 
					  if (this->current_sensor_ != nullptr) {
 | 
				
			||||||
    current_sensor_->publish_state(i_rms);
 | 
					    this->current_sensor_->publish_state(i_rms);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (power_sensor_ != nullptr) {
 | 
					  if (this->power_sensor_ != nullptr) {
 | 
				
			||||||
    power_sensor_->publish_state(watt);
 | 
					    this->power_sensor_->publish_state(watt);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (energy_sensor_ != nullptr) {
 | 
					  if (this->energy_sensor_ != nullptr) {
 | 
				
			||||||
    energy_sensor_->publish_state(total_energy_consumption);
 | 
					    this->energy_sensor_->publish_state(total_energy_consumption);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ESP_LOGV(TAG, "BL0940: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
 | 
					  ESP_LOGV(TAG, "BL0940: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
 | 
				
			||||||
@@ -121,7 +242,27 @@ void BL0940::received_package_(const DataPacket *data) const {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void BL0940::dump_config() {  // NOLINT(readability-function-cognitive-complexity)
 | 
					void BL0940::dump_config() {  // NOLINT(readability-function-cognitive-complexity)
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "BL0940:");
 | 
					  ESP_LOGCONFIG(TAG,
 | 
				
			||||||
 | 
					                "BL0940:\n"
 | 
				
			||||||
 | 
					                "  LEGACY MODE: %s\n"
 | 
				
			||||||
 | 
					                "  READ  CMD: 0x%02X\n"
 | 
				
			||||||
 | 
					                "  WRITE CMD: 0x%02X\n"
 | 
				
			||||||
 | 
					                "  ------------------\n"
 | 
				
			||||||
 | 
					                "  Current reference: %f\n"
 | 
				
			||||||
 | 
					                "  Energy reference: %f\n"
 | 
				
			||||||
 | 
					                "  Power reference: %f\n"
 | 
				
			||||||
 | 
					                "  Voltage reference: %f\n",
 | 
				
			||||||
 | 
					                TRUEFALSE(this->legacy_mode_enabled_), this->read_command_, this->write_command_,
 | 
				
			||||||
 | 
					                this->current_reference_, this->energy_reference_, this->power_reference_, this->voltage_reference_);
 | 
				
			||||||
 | 
					#ifdef USE_NUMBER
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG,
 | 
				
			||||||
 | 
					                "BL0940:\n"
 | 
				
			||||||
 | 
					                "  Current calibration: %f\n"
 | 
				
			||||||
 | 
					                "  Energy calibration: %f\n"
 | 
				
			||||||
 | 
					                "  Power calibration: %f\n"
 | 
				
			||||||
 | 
					                "  Voltage calibration: %f\n",
 | 
				
			||||||
 | 
					                this->current_cal_, this->energy_cal_, this->power_cal_, this->voltage_cal_);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
  LOG_SENSOR("", "Voltage", this->voltage_sensor_);
 | 
					  LOG_SENSOR("", "Voltage", this->voltage_sensor_);
 | 
				
			||||||
  LOG_SENSOR("", "Current", this->current_sensor_);
 | 
					  LOG_SENSOR("", "Current", this->current_sensor_);
 | 
				
			||||||
  LOG_SENSOR("", "Power", this->power_sensor_);
 | 
					  LOG_SENSOR("", "Power", this->power_sensor_);
 | 
				
			||||||
@@ -130,9 +271,5 @@ void BL0940::dump_config() {  // NOLINT(readability-function-cognitive-complexit
 | 
				
			|||||||
  LOG_SENSOR("", "External temperature", this->external_temperature_sensor_);
 | 
					  LOG_SENSOR("", "External temperature", this->external_temperature_sensor_);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uint32_t BL0940::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int32_t BL0940::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}  // namespace bl0940
 | 
					}  // namespace bl0940
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,66 +1,48 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
#include "esphome/components/uart/uart.h"
 | 
					#include "esphome/core/datatypes.h"
 | 
				
			||||||
 | 
					#include "esphome/core/defines.h"
 | 
				
			||||||
 | 
					#ifdef USE_BUTTON
 | 
				
			||||||
 | 
					#include "esphome/components/button/button.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_NUMBER
 | 
				
			||||||
 | 
					#include "esphome/components/number/number.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
#include "esphome/components/sensor/sensor.h"
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
 | 
					#include "esphome/components/uart/uart.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace bl0940 {
 | 
					namespace bl0940 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const float BL0940_PREF = 1430;
 | 
					 | 
				
			||||||
static const float BL0940_UREF = 33000;
 | 
					 | 
				
			||||||
static const float BL0940_IREF = 275000;  // 2750 from tasmota. Seems to generate values 100 times too high
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Measured to 297J  per click according to power consumption of 5 minutes
 | 
					 | 
				
			||||||
// Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
 | 
					 | 
				
			||||||
static const float BL0940_EREF = 3.6e6 / 297;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct ube24_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align)
 | 
					 | 
				
			||||||
  uint8_t l;
 | 
					 | 
				
			||||||
  uint8_t m;
 | 
					 | 
				
			||||||
  uint8_t h;
 | 
					 | 
				
			||||||
} __attribute__((packed));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct ube16_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align)
 | 
					 | 
				
			||||||
  uint8_t l;
 | 
					 | 
				
			||||||
  uint8_t h;
 | 
					 | 
				
			||||||
} __attribute__((packed));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct sbe24_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align)
 | 
					 | 
				
			||||||
  uint8_t l;
 | 
					 | 
				
			||||||
  uint8_t m;
 | 
					 | 
				
			||||||
  int8_t h;
 | 
					 | 
				
			||||||
} __attribute__((packed));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Caveat: All these values are big endian (low - middle - high)
 | 
					// Caveat: All these values are big endian (low - middle - high)
 | 
				
			||||||
 | 
					struct DataPacket {
 | 
				
			||||||
union DataPacket {  // NOLINT(altera-struct-pack-align)
 | 
					  uint8_t frame_header;    // Packet header (0x58 in EN docs, 0x55 in CN docs and Tasmota tests)
 | 
				
			||||||
  uint8_t raw[35];
 | 
					  uint24_le_t i_fast_rms;  // Fast RMS current
 | 
				
			||||||
  struct {
 | 
					  uint24_le_t i_rms;       // RMS current
 | 
				
			||||||
    uint8_t frame_header;  // value of 0x58 according to docs. 0x55 according to Tasmota real world tests. Reality wins.
 | 
					  uint24_t RESERVED0;      // Reserved
 | 
				
			||||||
    ube24_t i_fast_rms;    // 0x00
 | 
					  uint24_le_t v_rms;       // RMS voltage
 | 
				
			||||||
    ube24_t i_rms;         // 0x04
 | 
					  uint24_t RESERVED1;      // Reserved
 | 
				
			||||||
    ube24_t RESERVED0;     // reserved
 | 
					  int24_le_t watt;         // Active power (can be negative for bidirectional measurement)
 | 
				
			||||||
    ube24_t v_rms;         // 0x06
 | 
					  uint24_t RESERVED2;      // Reserved
 | 
				
			||||||
    ube24_t RESERVED1;     // reserved
 | 
					  uint24_le_t cf_cnt;      // Energy pulse count
 | 
				
			||||||
    sbe24_t watt;          // 0x08
 | 
					  uint24_t RESERVED3;      // Reserved
 | 
				
			||||||
    ube24_t RESERVED2;     // reserved
 | 
					  uint16_le_t tps1;        // Internal temperature sensor 1
 | 
				
			||||||
    ube24_t cf_cnt;        // 0x0A
 | 
					  uint8_t RESERVED4;       // Reserved (should be 0x00)
 | 
				
			||||||
    ube24_t RESERVED3;     // reserved
 | 
					  uint16_le_t tps2;        // Internal temperature sensor 2
 | 
				
			||||||
    ube16_t tps1;          // 0x0c
 | 
					  uint8_t RESERVED5;       // Reserved (should be 0x00)
 | 
				
			||||||
    uint8_t RESERVED4;     // value of 0x00
 | 
					  uint8_t checksum;        // Packet checksum
 | 
				
			||||||
    ube16_t tps2;          // 0x0c
 | 
					 | 
				
			||||||
    uint8_t RESERVED5;     // value of 0x00
 | 
					 | 
				
			||||||
    uint8_t checksum;      // checksum
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
} __attribute__((packed));
 | 
					} __attribute__((packed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BL0940 : public PollingComponent, public uart::UARTDevice {
 | 
					class BL0940 : public PollingComponent, public uart::UARTDevice {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
 | 
					  // Sensor setters
 | 
				
			||||||
  void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
 | 
					  void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
 | 
				
			||||||
  void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
 | 
					  void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
 | 
				
			||||||
  void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
 | 
					  void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
 | 
				
			||||||
  void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
 | 
					  void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Temperature sensor setters
 | 
				
			||||||
  void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) {
 | 
					  void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) {
 | 
				
			||||||
    internal_temperature_sensor_ = internal_temperature_sensor;
 | 
					    internal_temperature_sensor_ = internal_temperature_sensor;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -68,42 +50,105 @@ class BL0940 : public PollingComponent, public uart::UARTDevice {
 | 
				
			|||||||
    external_temperature_sensor_ = external_temperature_sensor;
 | 
					    external_temperature_sensor_ = external_temperature_sensor;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void loop() override;
 | 
					  // Configuration setters
 | 
				
			||||||
 | 
					  void set_legacy_mode(bool enable) { this->legacy_mode_enabled_ = enable; }
 | 
				
			||||||
 | 
					  void set_read_command(uint8_t read_command) { this->read_command_ = read_command; }
 | 
				
			||||||
 | 
					  void set_write_command(uint8_t write_command) { this->write_command_ = write_command; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Reference value setters (used for calibration and conversion)
 | 
				
			||||||
 | 
					  void set_current_reference(float current_ref) { this->current_reference_ = current_ref; }
 | 
				
			||||||
 | 
					  void set_energy_reference(float energy_ref) { this->energy_reference_ = energy_ref; }
 | 
				
			||||||
 | 
					  void set_power_reference(float power_ref) { this->power_reference_ = power_ref; }
 | 
				
			||||||
 | 
					  void set_voltage_reference(float voltage_ref) { this->voltage_reference_ = voltage_ref; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_NUMBER
 | 
				
			||||||
 | 
					  // Calibration number setters (for Home Assistant number entities)
 | 
				
			||||||
 | 
					  void set_current_calibration_number(number::Number *num) { this->current_calibration_number_ = num; }
 | 
				
			||||||
 | 
					  void set_voltage_calibration_number(number::Number *num) { this->voltage_calibration_number_ = num; }
 | 
				
			||||||
 | 
					  void set_power_calibration_number(number::Number *num) { this->power_calibration_number_ = num; }
 | 
				
			||||||
 | 
					  void set_energy_calibration_number(number::Number *num) { this->energy_calibration_number_ = num; }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_BUTTON
 | 
				
			||||||
 | 
					  // Resets all calibration values to defaults (can be triggered by a button)
 | 
				
			||||||
 | 
					  void reset_calibration();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Core component methods
 | 
				
			||||||
 | 
					  void loop() override;
 | 
				
			||||||
  void update() override;
 | 
					  void update() override;
 | 
				
			||||||
  void setup() override;
 | 
					  void setup() override;
 | 
				
			||||||
  void dump_config() override;
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  sensor::Sensor *voltage_sensor_{nullptr};
 | 
					  // --- Sensor pointers ---
 | 
				
			||||||
  sensor::Sensor *current_sensor_{nullptr};
 | 
					  sensor::Sensor *voltage_sensor_{nullptr};               // Voltage sensor
 | 
				
			||||||
  // NB This may be negative as the circuits is seemingly able to measure
 | 
					  sensor::Sensor *current_sensor_{nullptr};               // Current sensor
 | 
				
			||||||
  // power in both directions
 | 
					  sensor::Sensor *power_sensor_{nullptr};                 // Power sensor (can be negative for bidirectional)
 | 
				
			||||||
  sensor::Sensor *power_sensor_{nullptr};
 | 
					  sensor::Sensor *energy_sensor_{nullptr};                // Energy sensor
 | 
				
			||||||
  sensor::Sensor *energy_sensor_{nullptr};
 | 
					  sensor::Sensor *internal_temperature_sensor_{nullptr};  // Internal temperature sensor
 | 
				
			||||||
  sensor::Sensor *internal_temperature_sensor_{nullptr};
 | 
					  sensor::Sensor *external_temperature_sensor_{nullptr};  // External temperature sensor
 | 
				
			||||||
  sensor::Sensor *external_temperature_sensor_{nullptr};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Max difference between two measurements of the temperature. Used to avoid noise.
 | 
					#ifdef USE_NUMBER
 | 
				
			||||||
  float max_temperature_diff_{0};
 | 
					  // --- Calibration number entities (for dynamic calibration via HA UI) ---
 | 
				
			||||||
  // Divide by this to turn into Watt
 | 
					  number::Number *voltage_calibration_number_{nullptr};
 | 
				
			||||||
  float power_reference_ = BL0940_PREF;
 | 
					  number::Number *current_calibration_number_{nullptr};
 | 
				
			||||||
  // Divide by this to turn into Volt
 | 
					  number::Number *power_calibration_number_{nullptr};
 | 
				
			||||||
  float voltage_reference_ = BL0940_UREF;
 | 
					  number::Number *energy_calibration_number_{nullptr};
 | 
				
			||||||
  // Divide by this to turn into Ampere
 | 
					#endif
 | 
				
			||||||
  float current_reference_ = BL0940_IREF;
 | 
					 | 
				
			||||||
  // Divide by this to turn into kWh
 | 
					 | 
				
			||||||
  float energy_reference_ = BL0940_EREF;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  float update_temp_(sensor::Sensor *sensor, ube16_t packed_temperature) const;
 | 
					  // --- Internal state ---
 | 
				
			||||||
 | 
					  uint32_t prev_cf_cnt_ = 0;       // Previous energy pulse count (for energy calculation)
 | 
				
			||||||
 | 
					  float max_temperature_diff_{0};  // Max allowed temperature difference between two measurements (noise filter)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static uint32_t to_uint32_t(ube24_t input);
 | 
					  // --- Reference values for conversion ---
 | 
				
			||||||
 | 
					  float power_reference_;        // Divider for raw power to get Watts
 | 
				
			||||||
 | 
					  float power_reference_cal_;    // Calibrated power reference
 | 
				
			||||||
 | 
					  float voltage_reference_;      // Divider for raw voltage to get Volts
 | 
				
			||||||
 | 
					  float voltage_reference_cal_;  // Calibrated voltage reference
 | 
				
			||||||
 | 
					  float current_reference_;      // Divider for raw current to get Amperes
 | 
				
			||||||
 | 
					  float current_reference_cal_;  // Calibrated current reference
 | 
				
			||||||
 | 
					  float energy_reference_;       // Divider for raw energy to get kWh
 | 
				
			||||||
 | 
					  float energy_reference_cal_;   // Calibrated energy reference
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static int32_t to_int32_t(sbe24_t input);
 | 
					  // --- Home Assistant calibration values (multipliers, default 1) ---
 | 
				
			||||||
 | 
					  float current_cal_{1};
 | 
				
			||||||
 | 
					  float voltage_cal_{1};
 | 
				
			||||||
 | 
					  float power_cal_{1};
 | 
				
			||||||
 | 
					  float energy_cal_{1};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static bool validate_checksum(const DataPacket *data);
 | 
					  // --- Protocol commands ---
 | 
				
			||||||
 | 
					  uint8_t read_command_;
 | 
				
			||||||
 | 
					  uint8_t write_command_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void received_package_(const DataPacket *data) const;
 | 
					  // --- Mode flags ---
 | 
				
			||||||
 | 
					  bool legacy_mode_enabled_ = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // --- Methods ---
 | 
				
			||||||
 | 
					  // Converts packed temperature value to float and updates the sensor
 | 
				
			||||||
 | 
					  float update_temp_(sensor::Sensor *sensor, uint16_le_t packed_temperature) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Validates the checksum of a received data packet
 | 
				
			||||||
 | 
					  bool validate_checksum_(DataPacket *data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Handles a received data packet
 | 
				
			||||||
 | 
					  void received_package_(DataPacket *data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Calculates reference values for calibration and conversion
 | 
				
			||||||
 | 
					  float calculate_energy_reference_();
 | 
				
			||||||
 | 
					  float calculate_power_reference_();
 | 
				
			||||||
 | 
					  float calculate_calibration_value_(float state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Calibration update callbacks (used with number entities)
 | 
				
			||||||
 | 
					  void current_calibration_callback_(float state);
 | 
				
			||||||
 | 
					  void voltage_calibration_callback_(float state);
 | 
				
			||||||
 | 
					  void power_calibration_callback_(float state);
 | 
				
			||||||
 | 
					  void energy_calibration_callback_(float state);
 | 
				
			||||||
 | 
					  void reset_calibration_callback_();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Recalculates all reference values after calibration changes
 | 
				
			||||||
 | 
					  void recalibrate_();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace bl0940
 | 
					}  // namespace bl0940
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										27
									
								
								esphome/components/bl0940/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/bl0940/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					from esphome.components import button
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.const import ENTITY_CATEGORY_CONFIG, ICON_RESTART
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .. import CONF_BL0940_ID, bl0940_ns
 | 
				
			||||||
 | 
					from ..sensor import BL0940
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CalibrationResetButton = bl0940_ns.class_(
 | 
				
			||||||
 | 
					    "CalibrationResetButton", button.Button, cg.Component
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.All(
 | 
				
			||||||
 | 
					    button.button_schema(
 | 
				
			||||||
 | 
					        CalibrationResetButton,
 | 
				
			||||||
 | 
					        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
				
			||||||
 | 
					        icon=ICON_RESTART,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .extend({cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940)})
 | 
				
			||||||
 | 
					    .extend(cv.COMPONENT_SCHEMA)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    var = await button.new_button(config)
 | 
				
			||||||
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					    await cg.register_parented(var, config[CONF_BL0940_ID])
 | 
				
			||||||
@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					#include "calibration_reset_button.h"
 | 
				
			||||||
 | 
					#include "../bl0940.h"
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/core/application.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace bl0940 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "bl0940.button.calibration_reset";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void CalibrationResetButton::dump_config() { LOG_BUTTON("", "Calibration Reset Button", this); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void CalibrationResetButton::press_action() {
 | 
				
			||||||
 | 
					  ESP_LOGI(TAG, "Resetting calibration defaults...");
 | 
				
			||||||
 | 
					  this->parent_->reset_calibration();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace bl0940
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										19
									
								
								esphome/components/bl0940/button/calibration_reset_button.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								esphome/components/bl0940/button/calibration_reset_button.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/components/button/button.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace bl0940 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BL0940;  // Forward declaration of BL0940 class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CalibrationResetButton : public button::Button, public Component, public Parented<BL0940> {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void press_action() override;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace bl0940
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										94
									
								
								esphome/components/bl0940/number/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								esphome/components/bl0940/number/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					from esphome.components import number
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_MAX_VALUE,
 | 
				
			||||||
 | 
					    CONF_MIN_VALUE,
 | 
				
			||||||
 | 
					    CONF_MODE,
 | 
				
			||||||
 | 
					    CONF_RESTORE_VALUE,
 | 
				
			||||||
 | 
					    CONF_STEP,
 | 
				
			||||||
 | 
					    ENTITY_CATEGORY_CONFIG,
 | 
				
			||||||
 | 
					    UNIT_PERCENT,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .. import CONF_BL0940_ID, bl0940_ns
 | 
				
			||||||
 | 
					from ..sensor import BL0940
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Define calibration types
 | 
				
			||||||
 | 
					CONF_CURRENT_CALIBRATION = "current_calibration"
 | 
				
			||||||
 | 
					CONF_VOLTAGE_CALIBRATION = "voltage_calibration"
 | 
				
			||||||
 | 
					CONF_POWER_CALIBRATION = "power_calibration"
 | 
				
			||||||
 | 
					CONF_ENERGY_CALIBRATION = "energy_calibration"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BL0940Number = bl0940_ns.class_("BL0940Number")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CalibrationNumber = bl0940_ns.class_(
 | 
				
			||||||
 | 
					    "CalibrationNumber", number.Number, cg.PollingComponent
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def validate_min_max(config):
 | 
				
			||||||
 | 
					    if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]:
 | 
				
			||||||
 | 
					        raise cv.Invalid("max_value must be greater than min_value")
 | 
				
			||||||
 | 
					    return config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CALIBRATION_SCHEMA = cv.All(
 | 
				
			||||||
 | 
					    number.number_schema(
 | 
				
			||||||
 | 
					        CalibrationNumber,
 | 
				
			||||||
 | 
					        entity_category=ENTITY_CATEGORY_CONFIG,
 | 
				
			||||||
 | 
					        unit_of_measurement=UNIT_PERCENT,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .extend(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cv.Optional(CONF_MODE, default="BOX"): cv.enum(number.NUMBER_MODES),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_MAX_VALUE, default=10): cv.All(
 | 
				
			||||||
 | 
					                cv.float_, cv.Range(max=50)
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_MIN_VALUE, default=-10): cv.All(
 | 
				
			||||||
 | 
					                cv.float_, cv.Range(min=-50)
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_STEP, default=0.1): cv.positive_float,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_RESTORE_VALUE): cv.boolean,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .extend(cv.COMPONENT_SCHEMA),
 | 
				
			||||||
 | 
					    validate_min_max,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Configuration schema for BL0940 numbers
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.Schema(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.GenerateID(): cv.declare_id(BL0940Number),
 | 
				
			||||||
 | 
					        cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940),
 | 
				
			||||||
 | 
					        cv.Optional(CONF_CURRENT_CALIBRATION): CALIBRATION_SCHEMA,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_VOLTAGE_CALIBRATION): CALIBRATION_SCHEMA,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_POWER_CALIBRATION): CALIBRATION_SCHEMA,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_ENERGY_CALIBRATION): CALIBRATION_SCHEMA,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    # Get the BL0940 component instance
 | 
				
			||||||
 | 
					    bl0940 = await cg.get_variable(config[CONF_BL0940_ID])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Process all calibration types
 | 
				
			||||||
 | 
					    for cal_type, setter_method in [
 | 
				
			||||||
 | 
					        (CONF_CURRENT_CALIBRATION, "set_current_calibration_number"),
 | 
				
			||||||
 | 
					        (CONF_VOLTAGE_CALIBRATION, "set_voltage_calibration_number"),
 | 
				
			||||||
 | 
					        (CONF_POWER_CALIBRATION, "set_power_calibration_number"),
 | 
				
			||||||
 | 
					        (CONF_ENERGY_CALIBRATION, "set_energy_calibration_number"),
 | 
				
			||||||
 | 
					    ]:
 | 
				
			||||||
 | 
					        if conf := config.get(cal_type):
 | 
				
			||||||
 | 
					            var = await number.new_number(
 | 
				
			||||||
 | 
					                conf,
 | 
				
			||||||
 | 
					                min_value=conf.get(CONF_MIN_VALUE),
 | 
				
			||||||
 | 
					                max_value=conf.get(CONF_MAX_VALUE),
 | 
				
			||||||
 | 
					                step=conf.get(CONF_STEP),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            await cg.register_component(var, conf)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if restore_value := config.get(CONF_RESTORE_VALUE):
 | 
				
			||||||
 | 
					                cg.add(var.set_restore_value(restore_value))
 | 
				
			||||||
 | 
					            cg.add(getattr(bl0940, setter_method)(var))
 | 
				
			||||||
							
								
								
									
										29
									
								
								esphome/components/bl0940/number/calibration_number.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								esphome/components/bl0940/number/calibration_number.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					#include "calibration_number.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace bl0940 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "bl0940.number";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void CalibrationNumber::setup() {
 | 
				
			||||||
 | 
					  float value = 0.0f;
 | 
				
			||||||
 | 
					  if (this->restore_value_) {
 | 
				
			||||||
 | 
					    this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
 | 
				
			||||||
 | 
					    if (!this->pref_.load(&value)) {
 | 
				
			||||||
 | 
					      value = 0.0f;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->publish_state(value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void CalibrationNumber::control(float value) {
 | 
				
			||||||
 | 
					  this->publish_state(value);
 | 
				
			||||||
 | 
					  if (this->restore_value_)
 | 
				
			||||||
 | 
					    this->pref_.save(&value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void CalibrationNumber::dump_config() { LOG_NUMBER("", "Calibration Number", this); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace bl0940
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										26
									
								
								esphome/components/bl0940/number/calibration_number.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/bl0940/number/calibration_number.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/components/number/number.h"
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/core/preferences.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace bl0940 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CalibrationNumber : public number::Number, public Component {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void setup() override;
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					  float get_setup_priority() const override { return setup_priority::HARDWARE; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  void control(float value) override;
 | 
				
			||||||
 | 
					  bool restore_value_{true};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESPPreferenceObject pref_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace bl0940
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
@@ -8,6 +8,7 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
    CONF_INTERNAL_TEMPERATURE,
 | 
					    CONF_INTERNAL_TEMPERATURE,
 | 
				
			||||||
    CONF_POWER,
 | 
					    CONF_POWER,
 | 
				
			||||||
 | 
					    CONF_REFERENCE_VOLTAGE,
 | 
				
			||||||
    CONF_VOLTAGE,
 | 
					    CONF_VOLTAGE,
 | 
				
			||||||
    DEVICE_CLASS_CURRENT,
 | 
					    DEVICE_CLASS_CURRENT,
 | 
				
			||||||
    DEVICE_CLASS_ENERGY,
 | 
					    DEVICE_CLASS_ENERGY,
 | 
				
			||||||
@@ -23,12 +24,133 @@ from esphome.const import (
 | 
				
			|||||||
    UNIT_WATT,
 | 
					    UNIT_WATT,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from . import bl0940_ns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["uart"]
 | 
					DEPENDENCIES = ["uart"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
bl0940_ns = cg.esphome_ns.namespace("bl0940")
 | 
					 | 
				
			||||||
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
 | 
					BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_LEGACY_MODE = "legacy_mode"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_READ_COMMAND = "read_command"
 | 
				
			||||||
 | 
					CONF_WRITE_COMMAND = "write_command"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_RESISTOR_SHUNT = "resistor_shunt"
 | 
				
			||||||
 | 
					CONF_RESISTOR_ONE = "resistor_one"
 | 
				
			||||||
 | 
					CONF_RESISTOR_TWO = "resistor_two"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_CURRENT_REFERENCE = "current_reference"
 | 
				
			||||||
 | 
					CONF_ENERGY_REFERENCE = "energy_reference"
 | 
				
			||||||
 | 
					CONF_POWER_REFERENCE = "power_reference"
 | 
				
			||||||
 | 
					CONF_VOLTAGE_REFERENCE = "voltage_reference"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEFAULT_BL0940_READ_COMMAND = 0x58
 | 
				
			||||||
 | 
					DEFAULT_BL0940_WRITE_COMMAND = 0xA1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Values according to BL0940 application note:
 | 
				
			||||||
 | 
					# https://www.belling.com.cn/media/file_object/bel_product/BL0940/guide/BL0940_APPNote_TSSOP14_V1.04_EN.pdf
 | 
				
			||||||
 | 
					DEFAULT_BL0940_VREF = 1.218  # Vref = 1.218
 | 
				
			||||||
 | 
					DEFAULT_BL0940_RL = 1  # RL = 1 mΩ
 | 
				
			||||||
 | 
					DEFAULT_BL0940_R1 = 0.51  # R1 = 0.51 kΩ
 | 
				
			||||||
 | 
					DEFAULT_BL0940_R2 = 1950  # R2 = 5 x 390 kΩ -> 1950 kΩ
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ----------------------------------------------------
 | 
				
			||||||
 | 
					# values from initial implementation
 | 
				
			||||||
 | 
					DEFAULT_BL0940_LEGACY_READ_COMMAND = 0x50
 | 
				
			||||||
 | 
					DEFAULT_BL0940_LEGACY_WRITE_COMMAND = 0xA0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEFAULT_BL0940_LEGACY_UREF = 33000
 | 
				
			||||||
 | 
					DEFAULT_BL0940_LEGACY_IREF = 275000
 | 
				
			||||||
 | 
					DEFAULT_BL0940_LEGACY_PREF = 1430
 | 
				
			||||||
 | 
					# Measured to 297J  per click according to power consumption of 5 minutes
 | 
				
			||||||
 | 
					# Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
 | 
				
			||||||
 | 
					DEFAULT_BL0940_LEGACY_EREF = 3.6e6 / 297
 | 
				
			||||||
 | 
					# ----------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# methods to calculate voltage and current reference values
 | 
				
			||||||
 | 
					def calculate_voltage_reference(vref, r_one, r_two):
 | 
				
			||||||
 | 
					    # formula: 79931 / Vref * (R1 * 1000) / (R1 + R2)
 | 
				
			||||||
 | 
					    return 79931 / vref * (r_one * 1000) / (r_one + r_two)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def calculate_current_reference(vref, r_shunt):
 | 
				
			||||||
 | 
					    # formula: 324004 * RL / Vref
 | 
				
			||||||
 | 
					    return 324004 * r_shunt / vref
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def calculate_power_reference(voltage_reference, current_reference):
 | 
				
			||||||
 | 
					    # calculate power reference based on voltage and current reference
 | 
				
			||||||
 | 
					    return voltage_reference * current_reference * 4046 / 324004 / 79931
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def calculate_energy_reference(power_reference):
 | 
				
			||||||
 | 
					    # formula: power_reference * 3600000 / (1638.4 * 256)
 | 
				
			||||||
 | 
					    return power_reference * 3600000 / (1638.4 * 256)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def validate_legacy_mode(config):
 | 
				
			||||||
 | 
					    # Only allow schematic calibration options if legacy_mode is False
 | 
				
			||||||
 | 
					    if config.get(CONF_LEGACY_MODE, True):
 | 
				
			||||||
 | 
					        forbidden = [
 | 
				
			||||||
 | 
					            CONF_REFERENCE_VOLTAGE,
 | 
				
			||||||
 | 
					            CONF_RESISTOR_SHUNT,
 | 
				
			||||||
 | 
					            CONF_RESISTOR_ONE,
 | 
				
			||||||
 | 
					            CONF_RESISTOR_TWO,
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        for key in forbidden:
 | 
				
			||||||
 | 
					            if key in config:
 | 
				
			||||||
 | 
					                raise cv.Invalid(
 | 
				
			||||||
 | 
					                    f"Option '{key}' is only allowed when legacy_mode: false"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					    return config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def set_command_defaults(config):
 | 
				
			||||||
 | 
					    # Set defaults for read_command and write_command based on legacy_mode
 | 
				
			||||||
 | 
					    legacy = config.get(CONF_LEGACY_MODE, True)
 | 
				
			||||||
 | 
					    if legacy:
 | 
				
			||||||
 | 
					        config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_LEGACY_READ_COMMAND)
 | 
				
			||||||
 | 
					        config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_LEGACY_WRITE_COMMAND)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_READ_COMMAND)
 | 
				
			||||||
 | 
					        config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_WRITE_COMMAND)
 | 
				
			||||||
 | 
					    return config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def set_reference_values(config):
 | 
				
			||||||
 | 
					    # Set default reference values based on legacy_mode
 | 
				
			||||||
 | 
					    if config.get(CONF_LEGACY_MODE, True):
 | 
				
			||||||
 | 
					        config.setdefault(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_LEGACY_UREF)
 | 
				
			||||||
 | 
					        config.setdefault(CONF_CURRENT_REFERENCE, DEFAULT_BL0940_LEGACY_IREF)
 | 
				
			||||||
 | 
					        config.setdefault(CONF_POWER_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
 | 
				
			||||||
 | 
					        config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        vref = config.get(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_VREF)
 | 
				
			||||||
 | 
					        r_one = config.get(CONF_RESISTOR_ONE, DEFAULT_BL0940_R1)
 | 
				
			||||||
 | 
					        r_two = config.get(CONF_RESISTOR_TWO, DEFAULT_BL0940_R2)
 | 
				
			||||||
 | 
					        r_shunt = config.get(CONF_RESISTOR_SHUNT, DEFAULT_BL0940_RL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        config.setdefault(
 | 
				
			||||||
 | 
					            CONF_VOLTAGE_REFERENCE, calculate_voltage_reference(vref, r_one, r_two)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        config.setdefault(
 | 
				
			||||||
 | 
					            CONF_CURRENT_REFERENCE, calculate_current_reference(vref, r_shunt)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        config.setdefault(
 | 
				
			||||||
 | 
					            CONF_POWER_REFERENCE,
 | 
				
			||||||
 | 
					            calculate_power_reference(
 | 
				
			||||||
 | 
					                config.get(CONF_VOLTAGE_REFERENCE), config.get(CONF_CURRENT_REFERENCE)
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        config.setdefault(
 | 
				
			||||||
 | 
					            CONF_ENERGY_REFERENCE,
 | 
				
			||||||
 | 
					            calculate_energy_reference(config.get(CONF_POWER_REFERENCE)),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = (
 | 
					CONFIG_SCHEMA = (
 | 
				
			||||||
    cv.Schema(
 | 
					    cv.Schema(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -69,10 +191,24 @@ CONFIG_SCHEMA = (
 | 
				
			|||||||
                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
					                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_LEGACY_MODE, default=True): cv.boolean,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_READ_COMMAND): cv.hex_uint8_t,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_WRITE_COMMAND): cv.hex_uint8_t,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_REFERENCE_VOLTAGE): cv.float_,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_RESISTOR_SHUNT): cv.float_,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_RESISTOR_ONE): cv.float_,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_RESISTOR_TWO): cv.float_,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_CURRENT_REFERENCE): cv.float_,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_ENERGY_REFERENCE): cv.float_,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_POWER_REFERENCE): cv.float_,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_VOLTAGE_REFERENCE): cv.float_,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    .extend(cv.polling_component_schema("60s"))
 | 
					    .extend(cv.polling_component_schema("60s"))
 | 
				
			||||||
    .extend(uart.UART_DEVICE_SCHEMA)
 | 
					    .extend(uart.UART_DEVICE_SCHEMA)
 | 
				
			||||||
 | 
					    .add_extra(validate_legacy_mode)
 | 
				
			||||||
 | 
					    .add_extra(set_command_defaults)
 | 
				
			||||||
 | 
					    .add_extra(set_reference_values)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -99,3 +235,16 @@ async def to_code(config):
 | 
				
			|||||||
    if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
 | 
					    if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
 | 
				
			||||||
        sens = await sensor.new_sensor(external_temperature_config)
 | 
					        sens = await sensor.new_sensor(external_temperature_config)
 | 
				
			||||||
        cg.add(var.set_external_temperature_sensor(sens))
 | 
					        cg.add(var.set_external_temperature_sensor(sens))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # enable legacy mode
 | 
				
			||||||
 | 
					    cg.add(var.set_legacy_mode(config.get(CONF_LEGACY_MODE)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Set bl0940 commands after validator has determined which defaults to use if not set
 | 
				
			||||||
 | 
					    cg.add(var.set_read_command(config.get(CONF_READ_COMMAND)))
 | 
				
			||||||
 | 
					    cg.add(var.set_write_command(config.get(CONF_WRITE_COMMAND)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Set reference values after validator has set the values either from defaults or calculated
 | 
				
			||||||
 | 
					    cg.add(var.set_current_reference(config.get(CONF_CURRENT_REFERENCE)))
 | 
				
			||||||
 | 
					    cg.add(var.set_voltage_reference(config.get(CONF_VOLTAGE_REFERENCE)))
 | 
				
			||||||
 | 
					    cg.add(var.set_power_reference(config.get(CONF_POWER_REFERENCE)))
 | 
				
			||||||
 | 
					    cg.add(var.set_energy_reference(config.get(CONF_ENERGY_REFERENCE)))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,7 +51,7 @@ void BL0942::loop() {
 | 
				
			|||||||
  if (!avail) {
 | 
					  if (!avail) {
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (avail < sizeof(buffer)) {
 | 
					  if (static_cast<size_t>(avail) < sizeof(buffer)) {
 | 
				
			||||||
    if (!this->rx_start_) {
 | 
					    if (!this->rx_start_) {
 | 
				
			||||||
      this->rx_start_ = millis();
 | 
					      this->rx_start_ = millis();
 | 
				
			||||||
    } else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) {
 | 
					    } else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) {
 | 
				
			||||||
@@ -148,8 +148,8 @@ void BL0942::setup() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  this->write_reg_(BL0942_REG_USR_WRPROT, 0);
 | 
					  this->write_reg_(BL0942_REG_USR_WRPROT, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (this->read_reg_(BL0942_REG_MODE) != mode)
 | 
					  if (static_cast<uint32_t>(this->read_reg_(BL0942_REG_MODE)) != mode)
 | 
				
			||||||
    this->status_set_warning("BL0942 setup failed!");
 | 
					    this->status_set_warning(LOG_STR("BL0942 setup failed!"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this->flush();
 | 
					  this->flush();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -116,7 +116,7 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
					    .extend(cv.COMPONENT_SCHEMA)
 | 
				
			||||||
    .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA),
 | 
					    .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA),
 | 
				
			||||||
    esp32_ble_tracker.consume_connection_slots(1, "ble_client"),
 | 
					    esp32_ble.consume_connection_slots(1, "ble_client"),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF_BLE_CLIENT_ID = "ble_client_id"
 | 
					CONF_BLE_CLIENT_ID = "ble_client_id"
 | 
				
			||||||
@@ -286,6 +286,7 @@ async def remove_bond_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    # Register the loggers this component needs
 | 
					    # Register the loggers this component needs
 | 
				
			||||||
    esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
 | 
					    esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
 | 
				
			||||||
 | 
					    cg.add_define("USE_ESP32_BLE_UUID")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
    await cg.register_component(var, config)
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
    if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
					    if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
				
			||||||
        cg.add(
 | 
					        cg.add(
 | 
				
			||||||
@@ -63,6 +63,6 @@ def to_code(config):
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
        cg.add(var.set_char_uuid128(uuid128))
 | 
					        cg.add(var.set_char_uuid128(uuid128))
 | 
				
			||||||
    cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
 | 
					    cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
 | 
				
			||||||
    yield output.register_output(var, config)
 | 
					    await output.register_output(var, config)
 | 
				
			||||||
    yield ble_client.register_ble_node(var, config)
 | 
					    await ble_client.register_ble_node(var, config)
 | 
				
			||||||
    yield cg.register_component(var, config)
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,8 +6,6 @@ from esphome.components.esp32 import add_idf_sdkconfig_option
 | 
				
			|||||||
from esphome.components.esp32_ble import BTLoggers
 | 
					from esphome.components.esp32_ble import BTLoggers
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.const import CONF_ACTIVE, CONF_ID
 | 
					from esphome.const import CONF_ACTIVE, CONF_ID
 | 
				
			||||||
from esphome.core import CORE
 | 
					 | 
				
			||||||
from esphome.log import AnsiFore, color
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
 | 
					AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
 | 
				
			||||||
DEPENDENCIES = ["api", "esp32"]
 | 
					DEPENDENCIES = ["api", "esp32"]
 | 
				
			||||||
@@ -44,29 +42,7 @@ def validate_connections(config):
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
    elif config[CONF_ACTIVE]:
 | 
					    elif config[CONF_ACTIVE]:
 | 
				
			||||||
        connection_slots: int = config[CONF_CONNECTION_SLOTS]
 | 
					        connection_slots: int = config[CONF_CONNECTION_SLOTS]
 | 
				
			||||||
        esp32_ble_tracker.consume_connection_slots(connection_slots, "bluetooth_proxy")(
 | 
					        esp32_ble.consume_connection_slots(connection_slots, "bluetooth_proxy")(config)
 | 
				
			||||||
            config
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Warn about connection slot waste when using Arduino framework
 | 
					 | 
				
			||||||
        if CORE.using_arduino and connection_slots:
 | 
					 | 
				
			||||||
            _LOGGER.warning(
 | 
					 | 
				
			||||||
                "Bluetooth Proxy with active connections on Arduino framework has suboptimal performance.\n"
 | 
					 | 
				
			||||||
                "If BLE connections fail, they can waste connection slots for 10 seconds because\n"
 | 
					 | 
				
			||||||
                "Arduino doesn't allow configuring the BLE connection timeout (fixed at 30s).\n"
 | 
					 | 
				
			||||||
                "ESP-IDF framework allows setting it to 20s to match client timeouts.\n"
 | 
					 | 
				
			||||||
                "\n"
 | 
					 | 
				
			||||||
                "To switch to ESP-IDF, add this to your YAML:\n"
 | 
					 | 
				
			||||||
                "  esp32:\n"
 | 
					 | 
				
			||||||
                "    framework:\n"
 | 
					 | 
				
			||||||
                "      type: esp-idf\n"
 | 
					 | 
				
			||||||
                "\n"
 | 
					 | 
				
			||||||
                "For detailed migration instructions, see:\n"
 | 
					 | 
				
			||||||
                "%s",
 | 
					 | 
				
			||||||
                color(
 | 
					 | 
				
			||||||
                    AnsiFore.BLUE, "https://esphome.io/guides/esp32_arduino_to_idf.html"
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            **config,
 | 
					            **config,
 | 
				
			||||||
@@ -80,20 +56,18 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
        cv.Schema(
 | 
					        cv.Schema(
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                cv.GenerateID(): cv.declare_id(BluetoothProxy),
 | 
					                cv.GenerateID(): cv.declare_id(BluetoothProxy),
 | 
				
			||||||
                cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
 | 
					                cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
 | 
				
			||||||
                cv.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All(
 | 
					                cv.Optional(CONF_CACHE_SERVICES, default=True): cv.boolean,
 | 
				
			||||||
                    cv.only_with_esp_idf, cv.boolean
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                cv.Optional(
 | 
					                cv.Optional(
 | 
				
			||||||
                    CONF_CONNECTION_SLOTS,
 | 
					                    CONF_CONNECTION_SLOTS,
 | 
				
			||||||
                    default=DEFAULT_CONNECTION_SLOTS,
 | 
					                    default=DEFAULT_CONNECTION_SLOTS,
 | 
				
			||||||
                ): cv.All(
 | 
					                ): cv.All(
 | 
				
			||||||
                    cv.positive_int,
 | 
					                    cv.positive_int,
 | 
				
			||||||
                    cv.Range(min=1, max=esp32_ble_tracker.max_connections()),
 | 
					                    cv.Range(min=1, max=esp32_ble.IDF_MAX_CONNECTIONS),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                cv.Optional(CONF_CONNECTIONS): cv.All(
 | 
					                cv.Optional(CONF_CONNECTIONS): cv.All(
 | 
				
			||||||
                    cv.ensure_list(CONNECTION_SCHEMA),
 | 
					                    cv.ensure_list(CONNECTION_SCHEMA),
 | 
				
			||||||
                    cv.Length(min=1, max=esp32_ble_tracker.max_connections()),
 | 
					                    cv.Length(min=1, max=esp32_ble.IDF_MAX_CONNECTIONS),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,16 +12,30 @@ namespace esphome::bluetooth_proxy {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
static const char *const TAG = "bluetooth_proxy.connection";
 | 
					static const char *const TAG = "bluetooth_proxy.connection";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// This function is allocation-free and directly packs UUIDs into the output array
 | 
				
			||||||
 | 
					// using precalculated constants for the Bluetooth base UUID
 | 
				
			||||||
static void fill_128bit_uuid_array(std::array<uint64_t, 2> &out, esp_bt_uuid_t uuid_source) {
 | 
					static void fill_128bit_uuid_array(std::array<uint64_t, 2> &out, esp_bt_uuid_t uuid_source) {
 | 
				
			||||||
  esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid();
 | 
					  // Bluetooth base UUID: 00000000-0000-1000-8000-00805F9B34FB
 | 
				
			||||||
  out[0] = ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
 | 
					  // out[0] = bytes 8-15 (big-endian)
 | 
				
			||||||
           ((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
 | 
					  // - For 128-bit UUIDs: use bytes 8-15 as-is
 | 
				
			||||||
           ((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
 | 
					  // - For 16/32-bit UUIDs: insert into bytes 12-15, use 0x00001000 for bytes 8-11
 | 
				
			||||||
           ((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]);
 | 
					  out[0] = uuid_source.len == ESP_UUID_LEN_128
 | 
				
			||||||
  out[1] = ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
 | 
					               ? (((uint64_t) uuid_source.uuid.uuid128[15] << 56) | ((uint64_t) uuid_source.uuid.uuid128[14] << 48) |
 | 
				
			||||||
           ((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
 | 
					                  ((uint64_t) uuid_source.uuid.uuid128[13] << 40) | ((uint64_t) uuid_source.uuid.uuid128[12] << 32) |
 | 
				
			||||||
           ((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
 | 
					                  ((uint64_t) uuid_source.uuid.uuid128[11] << 24) | ((uint64_t) uuid_source.uuid.uuid128[10] << 16) |
 | 
				
			||||||
           ((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]);
 | 
					                  ((uint64_t) uuid_source.uuid.uuid128[9] << 8) | ((uint64_t) uuid_source.uuid.uuid128[8]))
 | 
				
			||||||
 | 
					               : (((uint64_t) (uuid_source.len == ESP_UUID_LEN_16 ? uuid_source.uuid.uuid16 : uuid_source.uuid.uuid32)
 | 
				
			||||||
 | 
					                   << 32) |
 | 
				
			||||||
 | 
					                  0x00001000ULL);  // Base UUID bytes 8-11
 | 
				
			||||||
 | 
					  // out[1] = bytes 0-7 (big-endian)
 | 
				
			||||||
 | 
					  // - For 128-bit UUIDs: use bytes 0-7 as-is
 | 
				
			||||||
 | 
					  // - For 16/32-bit UUIDs: use precalculated base UUID constant
 | 
				
			||||||
 | 
					  out[1] = uuid_source.len == ESP_UUID_LEN_128
 | 
				
			||||||
 | 
					               ? ((uint64_t) uuid_source.uuid.uuid128[7] << 56) | ((uint64_t) uuid_source.uuid.uuid128[6] << 48) |
 | 
				
			||||||
 | 
					                     ((uint64_t) uuid_source.uuid.uuid128[5] << 40) | ((uint64_t) uuid_source.uuid.uuid128[4] << 32) |
 | 
				
			||||||
 | 
					                     ((uint64_t) uuid_source.uuid.uuid128[3] << 24) | ((uint64_t) uuid_source.uuid.uuid128[2] << 16) |
 | 
				
			||||||
 | 
					                     ((uint64_t) uuid_source.uuid.uuid128[1] << 8) | ((uint64_t) uuid_source.uuid.uuid128[0])
 | 
				
			||||||
 | 
					               : 0x800000805F9B34FBULL;  // Base UUID bytes 0-7: 80-00-00-80-5F-9B-34-FB
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Helper to fill UUID in the appropriate format based on client support and UUID type
 | 
					// Helper to fill UUID in the appropriate format based on client support and UUID type
 | 
				
			||||||
@@ -119,7 +133,7 @@ void BluetoothConnection::loop() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // Check if we should disable the loop
 | 
					  // Check if we should disable the loop
 | 
				
			||||||
  // - For V3_WITH_CACHE: Services are never sent, disable after INIT state
 | 
					  // - For V3_WITH_CACHE: Services are never sent, disable after INIT state
 | 
				
			||||||
  // - For other connections: Disable only after service discovery is complete
 | 
					  // - For V3_WITHOUT_CACHE: Disable only after service discovery is complete
 | 
				
			||||||
  //   (send_service_ == DONE_SENDING_SERVICES, which is only set after services are sent)
 | 
					  //   (send_service_ == DONE_SENDING_SERVICES, which is only set after services are sent)
 | 
				
			||||||
  if (this->state_ != espbt::ClientState::INIT && (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
 | 
					  if (this->state_ != espbt::ClientState::INIT && (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
 | 
				
			||||||
                                                   this->send_service_ == DONE_SENDING_SERVICES)) {
 | 
					                                                   this->send_service_ == DONE_SENDING_SERVICES)) {
 | 
				
			||||||
@@ -146,10 +160,7 @@ void BluetoothConnection::send_service_for_discovery_() {
 | 
				
			|||||||
  if (this->send_service_ >= this->service_count_) {
 | 
					  if (this->send_service_ >= this->service_count_) {
 | 
				
			||||||
    this->send_service_ = DONE_SENDING_SERVICES;
 | 
					    this->send_service_ = DONE_SENDING_SERVICES;
 | 
				
			||||||
    this->proxy_->send_gatt_services_done(this->address_);
 | 
					    this->proxy_->send_gatt_services_done(this->address_);
 | 
				
			||||||
    if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
 | 
					    this->release_services();
 | 
				
			||||||
        this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
 | 
					 | 
				
			||||||
      this->release_services();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -364,10 +375,19 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  switch (event) {
 | 
					  switch (event) {
 | 
				
			||||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
					    case ESP_GATTC_DISCONNECT_EVT: {
 | 
				
			||||||
      this->reset_connection_(param->disconnect.reason);
 | 
					      // Don't reset connection yet - wait for CLOSE_EVT to ensure controller has freed resources
 | 
				
			||||||
 | 
					      // This prevents race condition where we mark slot as free before controller cleanup is complete
 | 
				
			||||||
 | 
					      ESP_LOGD(TAG, "[%d] [%s] Disconnect, reason=0x%02x", this->connection_index_, this->address_str_.c_str(),
 | 
				
			||||||
 | 
					               param->disconnect.reason);
 | 
				
			||||||
 | 
					      // Send disconnection notification but don't free the slot yet
 | 
				
			||||||
 | 
					      this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    case ESP_GATTC_CLOSE_EVT: {
 | 
					    case ESP_GATTC_CLOSE_EVT: {
 | 
				
			||||||
 | 
					      ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_.c_str(),
 | 
				
			||||||
 | 
					               param->close.reason);
 | 
				
			||||||
 | 
					      // Now the GATT connection is fully closed and controller resources are freed
 | 
				
			||||||
 | 
					      // Safe to mark the connection slot as available
 | 
				
			||||||
      this->reset_connection_(param->close.reason);
 | 
					      this->reset_connection_(param->close.reason);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -494,7 +514,8 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
 | 
				
			|||||||
  return this->check_and_log_error_("esp_ble_gattc_read_char", err);
 | 
					  return this->check_and_log_error_("esp_ble_gattc_read_char", err);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) {
 | 
					esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const uint8_t *data, size_t length,
 | 
				
			||||||
 | 
					                                                    bool response) {
 | 
				
			||||||
  if (!this->connected()) {
 | 
					  if (!this->connected()) {
 | 
				
			||||||
    this->log_gatt_not_connected_("write", "characteristic");
 | 
					    this->log_gatt_not_connected_("write", "characteristic");
 | 
				
			||||||
    return ESP_GATT_NOT_CONNECTED;
 | 
					    return ESP_GATT_NOT_CONNECTED;
 | 
				
			||||||
@@ -502,8 +523,11 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::
 | 
				
			|||||||
  ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
 | 
					  ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
 | 
				
			||||||
           handle);
 | 
					           handle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data
 | 
				
			||||||
 | 
					  // The BTC layer immediately copies the data to its own buffer (see btc_gattc.c)
 | 
				
			||||||
 | 
					  // const_cast is safe here and was previously hidden by a C-style cast
 | 
				
			||||||
  esp_err_t err =
 | 
					  esp_err_t err =
 | 
				
			||||||
      esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
 | 
					      esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, length, const_cast<uint8_t *>(data),
 | 
				
			||||||
                               response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
					                               response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
				
			||||||
  return this->check_and_log_error_("esp_ble_gattc_write_char", err);
 | 
					  return this->check_and_log_error_("esp_ble_gattc_write_char", err);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -520,7 +544,7 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
 | 
				
			|||||||
  return this->check_and_log_error_("esp_ble_gattc_read_char_descr", err);
 | 
					  return this->check_and_log_error_("esp_ble_gattc_read_char_descr", err);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) {
 | 
					esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const uint8_t *data, size_t length, bool response) {
 | 
				
			||||||
  if (!this->connected()) {
 | 
					  if (!this->connected()) {
 | 
				
			||||||
    this->log_gatt_not_connected_("write", "descriptor");
 | 
					    this->log_gatt_not_connected_("write", "descriptor");
 | 
				
			||||||
    return ESP_GATT_NOT_CONNECTED;
 | 
					    return ESP_GATT_NOT_CONNECTED;
 | 
				
			||||||
@@ -528,8 +552,11 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::stri
 | 
				
			|||||||
  ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
 | 
					  ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
 | 
				
			||||||
           handle);
 | 
					           handle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data
 | 
				
			||||||
 | 
					  // The BTC layer immediately copies the data to its own buffer (see btc_gattc.c)
 | 
				
			||||||
 | 
					  // const_cast is safe here and was previously hidden by a C-style cast
 | 
				
			||||||
  esp_err_t err = esp_ble_gattc_write_char_descr(
 | 
					  esp_err_t err = esp_ble_gattc_write_char_descr(
 | 
				
			||||||
      this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
 | 
					      this->gattc_if_, this->conn_id_, handle, length, const_cast<uint8_t *>(data),
 | 
				
			||||||
      response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
					      response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
				
			||||||
  return this->check_and_log_error_("esp_ble_gattc_write_char_descr", err);
 | 
					  return this->check_and_log_error_("esp_ble_gattc_write_char_descr", err);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ namespace esphome::bluetooth_proxy {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class BluetoothProxy;
 | 
					class BluetoothProxy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BluetoothConnection : public esp32_ble_client::BLEClientBase {
 | 
					class BluetoothConnection final : public esp32_ble_client::BLEClientBase {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  void dump_config() override;
 | 
					  void dump_config() override;
 | 
				
			||||||
  void loop() override;
 | 
					  void loop() override;
 | 
				
			||||||
@@ -18,9 +18,9 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
 | 
				
			|||||||
  esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
 | 
					  esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  esp_err_t read_characteristic(uint16_t handle);
 | 
					  esp_err_t read_characteristic(uint16_t handle);
 | 
				
			||||||
  esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);
 | 
					  esp_err_t write_characteristic(uint16_t handle, const uint8_t *data, size_t length, bool response);
 | 
				
			||||||
  esp_err_t read_descriptor(uint16_t handle);
 | 
					  esp_err_t read_descriptor(uint16_t handle);
 | 
				
			||||||
  esp_err_t write_descriptor(uint16_t handle, const std::string &data, bool response);
 | 
					  esp_err_t write_descriptor(uint16_t handle, const uint8_t *data, size_t length, bool response);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  esp_err_t notify_characteristic(uint16_t handle, bool enable);
 | 
					  esp_err_t notify_characteristic(uint16_t handle, bool enable);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,6 +24,9 @@ void BluetoothProxy::setup() {
 | 
				
			|||||||
  this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
 | 
					  this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
 | 
				
			||||||
  this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
 | 
					  this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Capture the configured scan mode from YAML before any API changes
 | 
				
			||||||
 | 
					  this->configured_scan_active_ = this->parent_->get_scan_active();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
 | 
					  this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
 | 
				
			||||||
    if (this->api_connection_ != nullptr) {
 | 
					    if (this->api_connection_ != nullptr) {
 | 
				
			||||||
      this->send_bluetooth_scanner_state_(state);
 | 
					      this->send_bluetooth_scanner_state_(state);
 | 
				
			||||||
@@ -36,6 +39,9 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
 | 
				
			|||||||
  resp.state = static_cast<api::enums::BluetoothScannerState>(state);
 | 
					  resp.state = static_cast<api::enums::BluetoothScannerState>(state);
 | 
				
			||||||
  resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
 | 
					  resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
 | 
				
			||||||
                                               : api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
 | 
					                                               : api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
 | 
				
			||||||
 | 
					  resp.configured_mode = this->configured_scan_active_
 | 
				
			||||||
 | 
					                             ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
 | 
				
			||||||
 | 
					                             : api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
 | 
				
			||||||
  this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
 | 
					  this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -183,6 +189,12 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
 | 
				
			|||||||
        this->send_device_connection(msg.address, false);
 | 
					        this->send_device_connection(msg.address, false);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      if (!msg.has_address_type) {
 | 
				
			||||||
 | 
					        ESP_LOGE(TAG, "[%d] [%s] Missing address type in connect request", connection->get_connection_index(),
 | 
				
			||||||
 | 
					                 connection->address_str().c_str());
 | 
				
			||||||
 | 
					        this->send_device_connection(msg.address, false);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      if (connection->state() == espbt::ClientState::CONNECTED ||
 | 
					      if (connection->state() == espbt::ClientState::CONNECTED ||
 | 
				
			||||||
          connection->state() == espbt::ClientState::ESTABLISHED) {
 | 
					          connection->state() == espbt::ClientState::ESTABLISHED) {
 | 
				
			||||||
        this->log_connection_request_ignored_(connection, connection->state());
 | 
					        this->log_connection_request_ignored_(connection, connection->state());
 | 
				
			||||||
@@ -209,13 +221,9 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
 | 
				
			|||||||
        connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
 | 
					        connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
 | 
				
			||||||
        this->log_connection_info_(connection, "v3 without cache");
 | 
					        this->log_connection_info_(connection, "v3 without cache");
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (msg.has_address_type) {
 | 
					      uint64_to_bd_addr(msg.address, connection->remote_bda_);
 | 
				
			||||||
        uint64_to_bd_addr(msg.address, connection->remote_bda_);
 | 
					      connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
 | 
				
			||||||
        connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
 | 
					      connection->set_state(espbt::ClientState::DISCOVERED);
 | 
				
			||||||
        connection->set_state(espbt::ClientState::DISCOVERED);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        connection->set_state(espbt::ClientState::SEARCHING);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      this->send_connections_free();
 | 
					      this->send_connections_free();
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -297,7 +305,7 @@ void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &
 | 
				
			|||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  auto err = connection->write_characteristic(msg.handle, msg.data, msg.response);
 | 
					  auto err = connection->write_characteristic(msg.handle, msg.data, msg.data_len, msg.response);
 | 
				
			||||||
  if (err != ESP_OK) {
 | 
					  if (err != ESP_OK) {
 | 
				
			||||||
    this->send_gatt_error(msg.address, msg.handle, err);
 | 
					    this->send_gatt_error(msg.address, msg.handle, err);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -323,7 +331,7 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri
 | 
				
			|||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  auto err = connection->write_descriptor(msg.handle, msg.data, true);
 | 
					  auto err = connection->write_descriptor(msg.handle, msg.data, msg.data_len, true);
 | 
				
			||||||
  if (err != ESP_OK) {
 | 
					  if (err != ESP_OK) {
 | 
				
			||||||
    this->send_gatt_error(msg.address, msg.handle, err);
 | 
					    this->send_gatt_error(msg.address, msg.handle, err);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,7 +50,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
 | 
				
			|||||||
  SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
 | 
					  SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
 | 
					class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
 | 
				
			||||||
  friend class BluetoothConnection;  // Allow connection to update connections_free_response_
 | 
					  friend class BluetoothConnection;  // Allow connection to update connections_free_response_
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  BluetoothProxy();
 | 
					  BluetoothProxy();
 | 
				
			||||||
@@ -130,7 +130,9 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  std::string get_bluetooth_mac_address_pretty() {
 | 
					  std::string get_bluetooth_mac_address_pretty() {
 | 
				
			||||||
    const uint8_t *mac = esp_bt_dev_get_address();
 | 
					    const uint8_t *mac = esp_bt_dev_get_address();
 | 
				
			||||||
    return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
 | 
					    char buf[18];
 | 
				
			||||||
 | 
					    format_mac_addr_upper(mac, buf);
 | 
				
			||||||
 | 
					    return std::string(buf);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
@@ -161,7 +163,8 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
				
			|||||||
  // Group 4: 1-byte types grouped together
 | 
					  // Group 4: 1-byte types grouped together
 | 
				
			||||||
  bool active_;
 | 
					  bool active_;
 | 
				
			||||||
  uint8_t connection_count_{0};
 | 
					  uint8_t connection_count_{0};
 | 
				
			||||||
  // 2 bytes used, 2 bytes padding
 | 
					  bool configured_scan_active_{false};  // Configured scan mode from YAML
 | 
				
			||||||
 | 
					  // 3 bytes used, 1 byte padding
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern BluetoothProxy *global_bluetooth_proxy;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
					extern BluetoothProxy *global_bluetooth_proxy;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,8 @@
 | 
				
			|||||||
#include <esphome/components/sensor/sensor.h>
 | 
					#include <esphome/components/sensor/sensor.h>
 | 
				
			||||||
#include <esphome/core/component.h>
 | 
					#include <esphome/core/component.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define BME280_ERROR_WRONG_CHIP_ID "Wrong chip ID"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace bme280_base {
 | 
					namespace bme280_base {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -98,18 +100,18 @@ void BME280Component::setup() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
 | 
					  if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
 | 
				
			||||||
    this->error_code_ = COMMUNICATION_FAILED;
 | 
					    this->error_code_ = COMMUNICATION_FAILED;
 | 
				
			||||||
    this->mark_failed();
 | 
					    this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (chip_id != 0x60) {
 | 
					  if (chip_id != 0x60) {
 | 
				
			||||||
    this->error_code_ = WRONG_CHIP_ID;
 | 
					    this->error_code_ = WRONG_CHIP_ID;
 | 
				
			||||||
    this->mark_failed();
 | 
					    this->mark_failed(BME280_ERROR_WRONG_CHIP_ID);
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Send a soft reset.
 | 
					  // Send a soft reset.
 | 
				
			||||||
  if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
 | 
					  if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
 | 
				
			||||||
    this->mark_failed();
 | 
					    this->mark_failed("Reset failed");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  // Wait until the NVM data has finished loading.
 | 
					  // Wait until the NVM data has finished loading.
 | 
				
			||||||
@@ -118,14 +120,12 @@ void BME280Component::setup() {
 | 
				
			|||||||
  do {  // NOLINT
 | 
					  do {  // NOLINT
 | 
				
			||||||
    delay(2);
 | 
					    delay(2);
 | 
				
			||||||
    if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
 | 
					    if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
 | 
				
			||||||
      ESP_LOGW(TAG, "Error reading status register.");
 | 
					      this->mark_failed("Error reading status register");
 | 
				
			||||||
      this->mark_failed();
 | 
					 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  } while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
 | 
					  } while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
 | 
				
			||||||
  if (status & BME280_STATUS_IM_UPDATE) {
 | 
					  if (status & BME280_STATUS_IM_UPDATE) {
 | 
				
			||||||
    ESP_LOGW(TAG, "Timeout loading NVM.");
 | 
					    this->mark_failed("Timeout loading NVM");
 | 
				
			||||||
    this->mark_failed();
 | 
					 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -153,26 +153,26 @@ void BME280Component::setup() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  uint8_t humid_control_val = 0;
 | 
					  uint8_t humid_control_val = 0;
 | 
				
			||||||
  if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
 | 
					  if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
 | 
				
			||||||
    this->mark_failed();
 | 
					    this->mark_failed("Read humidity control");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  humid_control_val &= ~0b00000111;
 | 
					  humid_control_val &= ~0b00000111;
 | 
				
			||||||
  humid_control_val |= this->humidity_oversampling_ & 0b111;
 | 
					  humid_control_val |= this->humidity_oversampling_ & 0b111;
 | 
				
			||||||
  if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
 | 
					  if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
 | 
				
			||||||
    this->mark_failed();
 | 
					    this->mark_failed("Write humidity control");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uint8_t config_register = 0;
 | 
					  uint8_t config_register = 0;
 | 
				
			||||||
  if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
 | 
					  if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
 | 
				
			||||||
    this->mark_failed();
 | 
					    this->mark_failed("Read config");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  config_register &= ~0b11111100;
 | 
					  config_register &= ~0b11111100;
 | 
				
			||||||
  config_register |= 0b101 << 5;  // 1000 ms standby time
 | 
					  config_register |= 0b101 << 5;  // 1000 ms standby time
 | 
				
			||||||
  config_register |= (this->iir_filter_ & 0b111) << 2;
 | 
					  config_register |= (this->iir_filter_ & 0b111) << 2;
 | 
				
			||||||
  if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
 | 
					  if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
 | 
				
			||||||
    this->mark_failed();
 | 
					    this->mark_failed("Write config");
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -183,7 +183,7 @@ void BME280Component::dump_config() {
 | 
				
			|||||||
      ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
 | 
					      ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    case WRONG_CHIP_ID:
 | 
					    case WRONG_CHIP_ID:
 | 
				
			||||||
      ESP_LOGE(TAG, "BME280 has wrong chip ID! Is it a BME280?");
 | 
					      ESP_LOGE(TAG, BME280_ERROR_WRONG_CHIP_ID);
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    case NONE:
 | 
					    case NONE:
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
@@ -223,21 +223,21 @@ void BME280Component::update() {
 | 
				
			|||||||
  this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
 | 
					  this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
 | 
				
			||||||
    uint8_t data[8];
 | 
					    uint8_t data[8];
 | 
				
			||||||
    if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
 | 
					    if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
 | 
				
			||||||
      ESP_LOGW(TAG, "Error reading registers.");
 | 
					      ESP_LOGW(TAG, "Error reading registers");
 | 
				
			||||||
      this->status_set_warning();
 | 
					      this->status_set_warning();
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    int32_t t_fine = 0;
 | 
					    int32_t t_fine = 0;
 | 
				
			||||||
    float const temperature = this->read_temperature_(data, &t_fine);
 | 
					    float const temperature = this->read_temperature_(data, &t_fine);
 | 
				
			||||||
    if (std::isnan(temperature)) {
 | 
					    if (std::isnan(temperature)) {
 | 
				
			||||||
      ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
 | 
					      ESP_LOGW(TAG, "Invalid temperature");
 | 
				
			||||||
      this->status_set_warning();
 | 
					      this->status_set_warning();
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    float const pressure = this->read_pressure_(data, t_fine);
 | 
					    float const pressure = this->read_pressure_(data, t_fine);
 | 
				
			||||||
    float const humidity = this->read_humidity_(data, t_fine);
 | 
					    float const humidity = this->read_humidity_(data, t_fine);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
 | 
					    ESP_LOGV(TAG, "Temperature=%.1f°C Pressure=%.1fhPa Humidity=%.1f%%", temperature, pressure, humidity);
 | 
				
			||||||
    if (this->temperature_sensor_ != nullptr)
 | 
					    if (this->temperature_sensor_ != nullptr)
 | 
				
			||||||
      this->temperature_sensor_->publish_state(temperature);
 | 
					      this->temperature_sensor_->publish_state(temperature);
 | 
				
			||||||
    if (this->pressure_sensor_ != nullptr)
 | 
					    if (this->pressure_sensor_ != nullptr)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -203,7 +203,7 @@ void BMI160Component::dump_config() {
 | 
				
			|||||||
i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) {
 | 
					i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) {
 | 
				
			||||||
  uint8_t raw_data[len * 2];
 | 
					  uint8_t raw_data[len * 2];
 | 
				
			||||||
  // read using read_register because we have little-endian data, and read_bytes_16 will swap it
 | 
					  // read using read_register because we have little-endian data, and read_bytes_16 will swap it
 | 
				
			||||||
  i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2, true);
 | 
					  i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2);
 | 
				
			||||||
  if (err != i2c::ERROR_OK) {
 | 
					  if (err != i2c::ERROR_OK) {
 | 
				
			||||||
    return err;
 | 
					    return err;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user