mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 09:01:49 +00:00 
			
		
		
		
	Compare commits
	
		
			1455 Commits
		
	
	
		
			v1.19.0b7
			...
			2022.3.0b1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					efa8f0730d | ||
| 
						 | 
					023d26f521 | ||
| 
						 | 
					5068619f1b | ||
| 
						 | 
					5b2457af0b | ||
| 
						 | 
					900b4f1af9 | ||
| 
						 | 
					4c22a98b0b | ||
| 
						 | 
					3b8ca80900 | ||
| 
						 | 
					dc6eff83ea | ||
| 
						 | 
					38ff66debd | ||
| 
						 | 
					1d2e0f74ea | ||
| 
						 | 
					bf60e40d0b | ||
| 
						 | 
					c9094ca537 | ||
| 
						 | 
					68b3fd6b8f | ||
| 
						 | 
					9323b3a248 | ||
| 
						 | 
					b55e9329d9 | ||
| 
						 | 
					a5b4105971 | ||
| 
						 | 
					d1feaa935d | ||
| 
						 | 
					6919930aaa | ||
| 
						 | 
					69633826bb | ||
| 
						 | 
					771162bfb1 | ||
| 
						 | 
					ba785e29e9 | ||
| 
						 | 
					2c7b104f4a | ||
| 
						 | 
					78951c197a | ||
| 
						 | 
					07c1cf7137 | ||
| 
						 | 
					d26141151a | ||
| 
						 | 
					f59dbe4a88 | ||
| 
						 | 
					8dae7f8225 | ||
| 
						 | 
					5811389891 | ||
| 
						 | 
					debcaf6fb7 | ||
| 
						 | 
					b8d10a62c2 | ||
| 
						 | 
					d2b209234f | ||
| 
						 | 
					34c9d8be50 | ||
| 
						 | 
					ae57ad0c81 | ||
| 
						 | 
					0c1520dd9c | ||
| 
						 | 
					d594f43ebd | ||
| 
						 | 
					125c693e3f | ||
| 
						 | 
					ad2f857e15 | ||
| 
						 | 
					e445d6aada | ||
| 
						 | 
					88fbb0ffbb | ||
| 
						 | 
					231908fe9f | ||
| 
						 | 
					f137cc10f4 | ||
| 
						 | 
					1a8f8adc2a | ||
| 
						 | 
					7a242bb4ed | ||
| 
						 | 
					3b8bb09ae3 | ||
| 
						 | 
					140db85d21 | ||
| 
						 | 
					ccce4b19e8 | ||
| 
						 | 
					8cb9be7560 | ||
| 
						 | 
					953f0569fb | ||
| 
						 | 
					34c229fd33 | ||
| 
						 | 
					958ad0d750 | ||
| 
						 | 
					36ddd9dd69 | ||
| 
						 | 
					38259c96c9 | ||
| 
						 | 
					c054fb8a2c | ||
| 
						 | 
					5a0b8328d8 | ||
| 
						 | 
					ffa19426d7 | ||
| 
						 | 
					c123804294 | ||
| 
						 | 
					4e24551b90 | ||
| 
						 | 
					51cb5da7f0 | ||
| 
						 | 
					b528f48417 | ||
| 
						 | 
					ec7a79049a | ||
| 
						 | 
					6ddad6b299 | ||
| 
						 | 
					16dc7762f9 | ||
| 
						 | 
					41f84447cc | ||
| 
						 | 
					ce073a704b | ||
| 
						 | 
					113232ebb6 | ||
| 
						 | 
					dc0ed8857f | ||
| 
						 | 
					bb6b77bd98 | ||
| 
						 | 
					dcc80f9032 | ||
| 
						 | 
					dd554bcdf4 | ||
| 
						 | 
					f376a39e55 | ||
| 
						 | 
					8dcc9d6b66 | ||
| 
						 | 
					a13a1225b7 | ||
| 
						 | 
					0ec84be5da | ||
| 
						 | 
					b1cefb7e3e | ||
| 
						 | 
					cc0c1c08b9 | ||
| 
						 | 
					3a67884451 | ||
| 
						 | 
					72e716cdf1 | ||
| 
						 | 
					40e06c9819 | ||
| 
						 | 
					ad6c5ff11d | ||
| 
						 | 
					335512e232 | ||
| 
						 | 
					2622e59b0b | ||
| 
						 | 
					35e6a13cd1 | ||
| 
						 | 
					a576c9f21f | ||
| 
						 | 
					b48490badc | ||
| 
						 | 
					71a438e2cb | ||
| 
						 | 
					272d6f2a8b | ||
| 
						 | 
					5c22065135 | ||
| 
						 | 
					e7dd6c52ac | ||
| 
						 | 
					3bf042dce9 | ||
| 
						 | 
					64f798d4b2 | ||
| 
						 | 
					f43e04e15a | ||
| 
						 | 
					88d72f8c9a | ||
| 
						 | 
					9826726a72 | ||
| 
						 | 
					c66d0550e8 | ||
| 
						 | 
					4aeacfd16e | ||
| 
						 | 
					58fa63ad88 | ||
| 
						 | 
					94f944dc9c | ||
| 
						 | 
					116ddbdd01 | ||
| 
						 | 
					1c0697b5d4 | ||
| 
						 | 
					434ca47ea0 | ||
| 
						 | 
					397ef72b16 | ||
| 
						 | 
					7ca9245735 | ||
| 
						 | 
					69856286e8 | ||
| 
						 | 
					ad43d6a5bc | ||
| 
						 | 
					1e5004f495 | ||
| 
						 | 
					253161d3d0 | ||
| 
						 | 
					ab47e201c7 | ||
| 
						 | 
					42984fa72a | ||
| 
						 | 
					e7864a28a1 | ||
| 
						 | 
					21803607e7 | ||
| 
						 | 
					62b366a5ec | ||
| 
						 | 
					2b39988707 | ||
| 
						 | 
					f9e7291050 | ||
| 
						 | 
					4de642ff28 | ||
| 
						 | 
					0384efcfc2 | ||
| 
						 | 
					bf91443f38 | ||
| 
						 | 
					4a5970b4af | ||
| 
						 | 
					e3fd68c849 | ||
| 
						 | 
					df0de2fc2d | ||
| 
						 | 
					0c3568fad5 | ||
| 
						 | 
					976f5d91ed | ||
| 
						 | 
					0f3d4d9a47 | ||
| 
						 | 
					ad1f4429c9 | ||
| 
						 | 
					7590d5eacb | ||
| 
						 | 
					c5974b8833 | ||
| 
						 | 
					511c8de6f3 | ||
| 
						 | 
					a718ac7ee0 | ||
| 
						 | 
					ef832becf1 | ||
| 
						 | 
					3a62455948 | ||
| 
						 | 
					297824e2d7 | ||
| 
						 | 
					d92f297bc0 | ||
| 
						 | 
					7a0827e3d0 | ||
| 
						 | 
					ef256a64b8 | ||
| 
						 | 
					1de941e837 | ||
| 
						 | 
					28b65cb810 | ||
| 
						 | 
					6ff3942e8b | ||
| 
						 | 
					ef5d959788 | ||
| 
						 | 
					6a2c58fcc0 | ||
| 
						 | 
					4e6bdb31ac | ||
| 
						 | 
					80d03a631e | ||
| 
						 | 
					6b27f2d2cf | ||
| 
						 | 
					7cb6729fa7 | ||
| 
						 | 
					2f46267994 | ||
| 
						 | 
					cdda648360 | ||
| 
						 | 
					f2d677d51a | ||
| 
						 | 
					c2ee0f0864 | ||
| 
						 | 
					2a84db7f85 | ||
| 
						 | 
					8187a4bce9 | ||
| 
						 | 
					97681d142e | ||
| 
						 | 
					b2430097f2 | ||
| 
						 | 
					7da12a878f | ||
| 
						 | 
					a31700e16f | ||
| 
						 | 
					7854522792 | ||
| 
						 | 
					a6a9ebfde2 | ||
| 
						 | 
					c6cbe2748e | ||
| 
						 | 
					f9a7f00843 | ||
| 
						 | 
					f0b183a552 | ||
| 
						 | 
					338ada5c9f | ||
| 
						 | 
					ef88f9923f | ||
| 
						 | 
					6f8c7d9ec4 | ||
| 
						 | 
					ec769ccf72 | ||
| 
						 | 
					045952939e | ||
| 
						 | 
					1c51cac5ba | ||
| 
						 | 
					ea11462e1e | ||
| 
						 | 
					62f9736b1d | ||
| 
						 | 
					1f8a1f0046 | ||
| 
						 | 
					172507acb5 | ||
| 
						 | 
					434ab65c16 | ||
| 
						 | 
					cb5f793ede | ||
| 
						 | 
					5dc776e55f | ||
| 
						 | 
					72d60f30f7 | ||
| 
						 | 
					869743a742 | ||
| 
						 | 
					7b03e07908 | ||
| 
						 | 
					348f880e15 | ||
| 
						 | 
					737188ae50 | ||
| 
						 | 
					db21731b14 | ||
| 
						 | 
					cdb4fa2487 | ||
| 
						 | 
					514204f0d4 | ||
| 
						 | 
					ead597d0fb | ||
| 
						 | 
					afbf989715 | ||
| 
						 | 
					01b62a16c3 | ||
| 
						 | 
					c5eba04517 | ||
| 
						 | 
					282313ab52 | ||
| 
						 | 
					d274545e77 | ||
| 
						 | 
					45ac577c4d | ||
| 
						 | 
					09402fdb22 | ||
| 
						 | 
					89e7448007 | ||
| 
						 | 
					1ea6f957bc | ||
| 
						 | 
					f44fca0a4b | ||
| 
						 | 
					52d2f62a57 | ||
| 
						 | 
					d3fda37615 | ||
| 
						 | 
					cbe3092404 | ||
| 
						 | 
					6dfe3039d0 | ||
| 
						 | 
					d6009453df | ||
| 
						 | 
					2a8668ea60 | ||
| 
						 | 
					cc0d433621 | ||
| 
						 | 
					c81323ef91 | ||
| 
						 | 
					1fe89fb364 | ||
| 
						 | 
					961c27f1c2 | ||
| 
						 | 
					fe4a14e6cc | ||
| 
						 | 
					ee58ad1ac0 | ||
| 
						 | 
					c0ff899812 | ||
| 
						 | 
					d9c938de33 | ||
| 
						 | 
					56547b3d50 | ||
| 
						 | 
					5026bc7a78 | ||
| 
						 | 
					27364ee72c | ||
| 
						 | 
					ece71a0228 | ||
| 
						 | 
					073828235f | ||
| 
						 | 
					41bcc8c0f4 | ||
| 
						 | 
					a0ea2aae6e | ||
| 
						 | 
					f34b46a621 | ||
| 
						 | 
					7217a4f7a4 | ||
| 
						 | 
					6383eca54a | ||
| 
						 | 
					e55bd1e559 | ||
| 
						 | 
					9e8b701dea | ||
| 
						 | 
					a4431abea8 | ||
| 
						 | 
					5844c1767b | ||
| 
						 | 
					9a70bfa471 | ||
| 
						 | 
					b406c6403c | ||
| 
						 | 
					499625f266 | ||
| 
						 | 
					6b773553fc | ||
| 
						 | 
					15fe049a99 | ||
| 
						 | 
					e4555f6997 | ||
| 
						 | 
					470071e0b0 | ||
| 
						 | 
					ea1be8e7bf | ||
| 
						 | 
					84a830195f | ||
| 
						 | 
					e62c3e00c1 | ||
| 
						 | 
					07e790f900 | ||
| 
						 | 
					640142fc0c | ||
| 
						 | 
					5c339d4597 | ||
| 
						 | 
					a4931f5d78 | ||
| 
						 | 
					5e1e543b06 | ||
| 
						 | 
					df929f9445 | ||
| 
						 | 
					d8e719d1c4 | ||
| 
						 | 
					3067e482fc | ||
| 
						 | 
					ed5930e934 | ||
| 
						 | 
					ffea3597f4 | ||
| 
						 | 
					193d3e0206 | ||
| 
						 | 
					c8f4fbb7dd | ||
| 
						 | 
					c855bc31b4 | ||
| 
						 | 
					b924b179ab | ||
| 
						 | 
					3df0fee3de | ||
| 
						 | 
					b601560e81 | ||
| 
						 | 
					e5775cf812 | ||
| 
						 | 
					26dd1f8532 | ||
| 
						 | 
					5143a5b5c5 | ||
| 
						 | 
					15ce27992e | ||
| 
						 | 
					dbc2812022 | ||
| 
						 | 
					dce3713f12 | ||
| 
						 | 
					f849d45bb6 | ||
| 
						 | 
					8ad06fb9ea | ||
| 
						 | 
					9124d9d6e6 | ||
| 
						 | 
					45ebe51e4f | ||
| 
						 | 
					407661d56b | ||
| 
						 | 
					998d4229af | ||
| 
						 | 
					a02d2e2e11 | ||
| 
						 | 
					72fa68849f | ||
| 
						 | 
					33f17f75a0 | ||
| 
						 | 
					23edb18d7e | ||
| 
						 | 
					07ff3a853f | ||
| 
						 | 
					2cf36bdb46 | ||
| 
						 | 
					50848c2f4d | ||
| 
						 | 
					d32633b3c7 | ||
| 
						 | 
					b37739eec2 | ||
| 
						 | 
					28f87dc804 | ||
| 
						 | 
					41879e41e6 | ||
| 
						 | 
					fc0a6546a2 | ||
| 
						 | 
					ffd4280d6c | ||
| 
						 | 
					f859b346a6 | ||
| 
						 | 
					cb0677cafe | ||
| 
						 | 
					c6956527d1 | ||
| 
						 | 
					72c6bfaa50 | ||
| 
						 | 
					7927b5f624 | ||
| 
						 | 
					b7aad39daf | ||
| 
						 | 
					f48de6dd43 | ||
| 
						 | 
					79d73d8f8b | ||
| 
						 | 
					cc5947467f | ||
| 
						 | 
					e152f128c8 | ||
| 
						 | 
					99bd808ebe | ||
| 
						 | 
					beb5f3dc9d | ||
| 
						 | 
					f5c3b3446f | ||
| 
						 | 
					db3b955b0f | ||
| 
						 | 
					f431c7402f | ||
| 
						 | 
					5516f65971 | ||
| 
						 | 
					9471df0a1b | ||
| 
						 | 
					6d39f64be7 | ||
| 
						 | 
					4907e6f6d7 | ||
| 
						 | 
					1ccee86705 | ||
| 
						 | 
					542fb2175b | ||
| 
						 | 
					6ec9cfb044 | ||
| 
						 | 
					66e0ff8392 | ||
| 
						 | 
					1fb0a7109d | ||
| 
						 | 
					b89d0a9a73 | ||
| 
						 | 
					4bb779d9a5 | ||
| 
						 | 
					386a5b6362 | ||
| 
						 | 
					e32a999cd0 | ||
| 
						 | 
					192eb49589 | ||
| 
						 | 
					5d70ff702b | ||
| 
						 | 
					a7b05db2a1 | ||
| 
						 | 
					45e346cf1b | ||
| 
						 | 
					80e2bfada3 | ||
| 
						 | 
					16e7bd0388 | ||
| 
						 | 
					b3fb35783e | ||
| 
						 | 
					a79c6aa9e0 | ||
| 
						 | 
					4bb58b2de9 | ||
| 
						 | 
					4e10881331 | ||
| 
						 | 
					cec4a81e14 | ||
| 
						 | 
					da45923d05 | ||
| 
						 | 
					31a61b598b | ||
| 
						 | 
					9c0506592b | ||
| 
						 | 
					beeb0c7c5a | ||
| 
						 | 
					b2f05faee0 | ||
| 
						 | 
					bfbc6a4bad | ||
| 
						 | 
					8c9e0e552d | ||
| 
						 | 
					8aaf9fd83f | ||
| 
						 | 
					08057720b8 | ||
| 
						 | 
					bfaa648837 | ||
| 
						 | 
					d504daef91 | ||
| 
						 | 
					8375e1d64d | ||
| 
						 | 
					cf5193d3e5 | ||
| 
						 | 
					b8d3ef2f49 | ||
| 
						 | 
					3bf6320030 | ||
| 
						 | 
					708b928c73 | ||
| 
						 | 
					649366ff44 | ||
| 
						 | 
					e5c9e87fad | ||
| 
						 | 
					c490388e80 | ||
| 
						 | 
					24ec5a6e9d | ||
| 
						 | 
					f3d9d707b6 | ||
| 
						 | 
					090e10730c | ||
| 
						 | 
					fbc84861c7 | ||
| 
						 | 
					e763469af8 | ||
| 
						 | 
					6df1d5222d | ||
| 
						 | 
					58fb7a02f6 | ||
| 
						 | 
					3d51ac8df0 | ||
| 
						 | 
					6fe4ff7f85 | ||
| 
						 | 
					3c0c514e44 | ||
| 
						 | 
					2253d4bc16 | ||
| 
						 | 
					e5cc19de43 | ||
| 
						 | 
					ed5e2dd332 | ||
| 
						 | 
					09b7c6f550 | ||
| 
						 | 
					df315a1f51 | ||
| 
						 | 
					7ee4bb621c | ||
| 
						 | 
					24874f4c3c | ||
| 
						 | 
					c128880033 | ||
| 
						 | 
					a66e94a0b0 | ||
| 
						 | 
					56870ed4a8 | ||
| 
						 | 
					3ac720df47 | ||
| 
						 | 
					1bc757ad06 | ||
| 
						 | 
					f72abc6f3d | ||
| 
						 | 
					5ac88de985 | ||
| 
						 | 
					5404617d43 | ||
| 
						 | 
					12467a18e6 | ||
| 
						 | 
					1db7043a4d | ||
| 
						 | 
					49932747b3 | ||
| 
						 | 
					55db190875 | ||
| 
						 | 
					71fe2f7ed3 | ||
| 
						 | 
					ffc112c9d0 | ||
| 
						 | 
					d3e48e296f | ||
| 
						 | 
					14f6ae75ea | ||
| 
						 | 
					c84efe64d3 | ||
| 
						 | 
					10e89a7dbb | ||
| 
						 | 
					ef44acbf10 | ||
| 
						 | 
					06da540ab0 | ||
| 
						 | 
					0826b367d6 | ||
| 
						 | 
					329bf861d6 | ||
| 
						 | 
					9dcd3d18a0 | ||
| 
						 | 
					40c017fd54 | ||
| 
						 | 
					db66cd88b6 | ||
| 
						 | 
					f0bcf81a98 | ||
| 
						 | 
					86c205fe43 | ||
| 
						 | 
					6a0b343289 | ||
| 
						 | 
					c6414138c7 | ||
| 
						 | 
					36b355eb82 | ||
| 
						 | 
					9ca4e8f32a | ||
| 
						 | 
					1b88b7a166 | ||
| 
						 | 
					caf352ff06 | ||
| 
						 | 
					54106179a1 | ||
| 
						 | 
					607601b3a4 | ||
| 
						 | 
					f58828cb82 | ||
| 
						 | 
					11330af05f | ||
| 
						 | 
					fbe1bca1b9 | ||
| 
						 | 
					24a5325db3 | ||
| 
						 | 
					1ec3140759 | ||
| 
						 | 
					ca8db7696e | ||
| 
						 | 
					c9190574a9 | ||
| 
						 | 
					bfeb0b3639 | ||
| 
						 | 
					cbc1334b8d | ||
| 
						 | 
					08cbb97ec9 | ||
| 
						 | 
					5719cc1a24 | ||
| 
						 | 
					d9513e5ff2 | ||
| 
						 | 
					b5a0e8b2c0 | ||
| 
						 | 
					b32b918936 | ||
| 
						 | 
					0f47ffd908 | ||
| 
						 | 
					cd018ad3a5 | ||
| 
						 | 
					24dfecb6f0 | ||
| 
						 | 
					ab027a6ae2 | ||
| 
						 | 
					556d071e7f | ||
| 
						 | 
					939fb313df | ||
| 
						 | 
					b5639a6472 | ||
| 
						 | 
					f50e40e0b8 | ||
| 
						 | 
					6f07421911 | ||
| 
						 | 
					adf48246a9 | ||
| 
						 | 
					7be9291b13 | ||
| 
						 | 
					ea9e75039b | ||
| 
						 | 
					a5fb036011 | ||
| 
						 | 
					e55506f9db | ||
| 
						 | 
					50ec1d0445 | ||
| 
						 | 
					3d5e1d8d91 | ||
| 
						 | 
					db2128a344 | ||
| 
						 | 
					cae283dc86 | ||
| 
						 | 
					7afcb0fb04 | ||
| 
						 | 
					10f830c3ef | ||
| 
						 | 
					7a5c3aa7ed | ||
| 
						 | 
					2b50406856 | ||
| 
						 | 
					10a2a7e0fc | ||
| 
						 | 
					7a564b222d | ||
| 
						 | 
					21db43db06 | ||
| 
						 | 
					5009b3029f | ||
| 
						 | 
					57a029189c | ||
| 
						 | 
					671d68bc2c | ||
| 
						 | 
					5946c37925 | ||
| 
						 | 
					17a37b1de9 | ||
| 
						 | 
					e7827a6997 | ||
| 
						 | 
					2347e043a9 | ||
| 
						 | 
					00965fe19e | ||
| 
						 | 
					9681dfb458 | ||
| 
						 | 
					5e631bc6ba | ||
| 
						 | 
					b5f660398c | ||
| 
						 | 
					d50bdf619f | ||
| 
						 | 
					4e448b21ff | ||
| 
						 | 
					2a78c2970d | ||
| 
						 | 
					0cb715bb76 | ||
| 
						 | 
					7d03823afd | ||
| 
						 | 
					8e1c9f5042 | ||
| 
						 | 
					980b7cda8f | ||
| 
						 | 
					3a72dd5cb6 | ||
| 
						 | 
					3178243811 | ||
| 
						 | 
					d30e2f2a4f | ||
| 
						 | 
					3637be251e | ||
| 
						 | 
					2aea27d272 | ||
| 
						 | 
					ceb9b1d1ff | ||
| 
						 | 
					ccfa1e23f0 | ||
| 
						 | 
					290da8df2d | ||
| 
						 | 
					4b1d73791d | ||
| 
						 | 
					7e8012c1a0 | ||
| 
						 | 
					15cd602e8b | ||
| 
						 | 
					598f5b241f | ||
| 
						 | 
					335e69e6cd | ||
| 
						 | 
					05fe5db030 | ||
| 
						 | 
					710096b1c6 | ||
| 
						 | 
					07b882c801 | ||
| 
						 | 
					3e5331a263 | ||
| 
						 | 
					897277992b | ||
| 
						 | 
					1424091ee5 | ||
| 
						 | 
					61ec16cdfc | ||
| 
						 | 
					e5cb5756aa | ||
| 
						 | 
					9e1c3e8f01 | ||
| 
						 | 
					448e1690aa | ||
| 
						 | 
					8267f01ccd | ||
| 
						 | 
					6f9439e1bc | ||
| 
						 | 
					06994c0dfc | ||
| 
						 | 
					dee5d639e2 | ||
| 
						 | 
					df6730be55 | ||
| 
						 | 
					6226dae05c | ||
| 
						 | 
					9c6a475a6e | ||
| 
						 | 
					8294d10d5b | ||
| 
						 | 
					67558bec47 | ||
| 
						 | 
					6c1ef398bb | ||
| 
						 | 
					84873d4074 | ||
| 
						 | 
					58a0b28a39 | ||
| 
						 | 
					0469e19f54 | ||
| 
						 | 
					dbcfa7b599 | ||
| 
						 | 
					b37d3a66cc | ||
| 
						 | 
					7e495a5e27 | ||
| 
						 | 
					c41547fd4a | ||
| 
						 | 
					0d47d41c85 | ||
| 
						 | 
					df68403b6d | ||
| 
						 | 
					57bdc2b885 | ||
| 
						 | 
					f565ff5def | ||
| 
						 | 
					8ece639987 | ||
| 
						 | 
					b35f509784 | ||
| 
						 | 
					41a3a17456 | ||
| 
						 | 
					cbbafbcca2 | ||
| 
						 | 
					c75566b374 | ||
| 
						 | 
					f1954df573 | ||
| 
						 | 
					7279f1fcc1 | ||
| 
						 | 
					d7432f7c10 | ||
| 
						 | 
					b0a0a153f3 | ||
| 
						 | 
					9e4fa5dcf1 | ||
| 
						 | 
					024632dbd0 | ||
| 
						 | 
					0a545a28b9 | ||
| 
						 | 
					0f2df59998 | ||
| 
						 | 
					0809673ba9 | ||
| 
						 | 
					b386284180 | ||
| 
						 | 
					515519bc87 | ||
| 
						 | 
					5404163be0 | ||
| 
						 | 
					29a7d32f77 | ||
| 
						 | 
					687a7e9b2f | ||
| 
						 | 
					09e8782318 | ||
| 
						 | 
					0b193eee43 | ||
| 
						 | 
					f2aea02210 | ||
| 
						 | 
					194f922312 | ||
| 
						 | 
					fea3c48098 | ||
| 
						 | 
					c2f57baec2 | ||
| 
						 | 
					f4a140e126 | ||
| 
						 | 
					ab506b09fe | ||
| 
						 | 
					87e1cdeedb | ||
| 
						 | 
					81a36146ef | ||
| 
						 | 
					7333123ba4 | ||
| 
						 | 
					d99c5ed890 | ||
| 
						 | 
					04740fbcbb | ||
| 
						 | 
					6a7440f7d3 | ||
| 
						 | 
					14299bb2cc | ||
| 
						 | 
					66cebfc992 | ||
| 
						 | 
					108b8e6705 | ||
| 
						 | 
					4eaa6afa4d | ||
| 
						 | 
					f643a46bbf | ||
| 
						 | 
					aae63a7ff3 | ||
| 
						 | 
					582567696e | ||
| 
						 | 
					2e0c89409d | ||
| 
						 | 
					7fa4a68a27 | ||
| 
						 | 
					f1c5e2ef81 | ||
| 
						 | 
					b526155cce | ||
| 
						 | 
					62c3f301e7 | ||
| 
						 | 
					38cb988809 | ||
| 
						 | 
					7bb7456a8b | ||
| 
						 | 
					0372e12b81 | ||
| 
						 | 
					a6873c1520 | ||
| 
						 | 
					f11220da3a | ||
| 
						 | 
					b976ac54c8 | ||
| 
						 | 
					78026e766f | ||
| 
						 | 
					b4cd8d21a5 | ||
| 
						 | 
					7552893311 | ||
| 
						 | 
					21c896d8f8 | ||
| 
						 | 
					4b7fe202ec | ||
| 
						 | 
					bb9793d5b7 | ||
| 
						 | 
					e99af991ec | ||
| 
						 | 
					abf3708cc2 | ||
| 
						 | 
					4395d6156d | ||
| 
						 | 
					9f4519210f | ||
| 
						 | 
					b0506afa5b | ||
| 
						 | 
					8cbb379898 | ||
| 
						 | 
					04ba53c870 | ||
| 
						 | 
					b226215593 | ||
| 
						 | 
					19970729a9 | ||
| 
						 | 
					f310cacd41 | ||
| 
						 | 
					5ff7c8418c | ||
| 
						 | 
					0bdb48bcac | ||
| 
						 | 
					99c775d8cb | ||
| 
						 | 
					4d43396835 | ||
| 
						 | 
					92321e219a | ||
| 
						 | 
					c422b2fb0b | ||
| 
						 | 
					8aa72f4c1e | ||
| 
						 | 
					d8e33c5a69 | ||
| 
						 | 
					15f9677d33 | ||
| 
						 | 
					219b225ac0 | ||
| 
						 | 
					2ac232e634 | ||
| 
						 | 
					710866ff4e | ||
| 
						 | 
					662773b075 | ||
| 
						 | 
					875b803483 | ||
| 
						 | 
					6e5cfac927 | ||
| 
						 | 
					97eaf3d4a1 | ||
| 
						 | 
					366552a969 | ||
| 
						 | 
					57b07441a1 | ||
| 
						 | 
					fb57ab0add | ||
| 
						 | 
					d6717c0032 | ||
| 
						 | 
					f72389147d | ||
| 
						 | 
					a509f6ccd2 | ||
| 
						 | 
					add484a2ea | ||
| 
						 | 
					a17a6d5346 | ||
| 
						 | 
					be9439f10d | ||
| 
						 | 
					96a50f5c6b | ||
| 
						 | 
					3c0414c420 | ||
| 
						 | 
					b450d4c734 | ||
| 
						 | 
					d536509a63 | ||
| 
						 | 
					11f1e28139 | ||
| 
						 | 
					379c3e98f5 | ||
| 
						 | 
					5ea77894b7 | ||
| 
						 | 
					d54b4e7c44 | ||
| 
						 | 
					d8b3af3815 | ||
| 
						 | 
					2b04152482 | ||
| 
						 | 
					331a3ac387 | ||
| 
						 | 
					7eee3cdc7f | ||
| 
						 | 
					b12c7432e0 | ||
| 
						 | 
					696643d037 | ||
| 
						 | 
					7e54f97003 | ||
| 
						 | 
					77dbf84e55 | ||
| 
						 | 
					2350c5054c | ||
| 
						 | 
					73accf747f | ||
| 
						 | 
					0d3e6b2c4c | ||
| 
						 | 
					b3d7cc637b | ||
| 
						 | 
					2147bcbc29 | ||
| 
						 | 
					980c2d4cae | ||
| 
						 | 
					d2ebfd2833 | ||
| 
						 | 
					bd782fc828 | ||
| 
						 | 
					23560e608c | ||
| 
						 | 
					f1377b560e | ||
| 
						 | 
					72108684ea | ||
| 
						 | 
					c6adaaea97 | ||
| 
						 | 
					91999a38ca | ||
| 
						 | 
					b34eed125d | ||
| 
						 | 
					2abe09529a | ||
| 
						 | 
					9aaaf4dd4b | ||
| 
						 | 
					cbfbcf7f1b | ||
| 
						 | 
					68316cbcf9 | ||
| 
						 | 
					2f4b9263c3 | ||
| 
						 | 
					c2623a08e3 | ||
| 
						 | 
					9f625ee7d1 | ||
| 
						 | 
					2f85c27a05 | ||
| 
						 | 
					c612a3bf60 | ||
| 
						 | 
					a01f5f5cf1 | ||
| 
						 | 
					87328686a0 | ||
| 
						 | 
					81c11ba1f7 | ||
| 
						 | 
					49b17c5a2d | ||
| 
						 | 
					de06a781ff | ||
| 
						 | 
					8e77e3c685 | ||
| 
						 | 
					a687b083ae | ||
| 
						 | 
					b9e5c7eb35 | ||
| 
						 | 
					1a6a063e04 | ||
| 
						 | 
					d85b7a6bd0 | ||
| 
						 | 
					1c4700f447 | ||
| 
						 | 
					c7651dc40d | ||
| 
						 | 
					eda1c471ad | ||
| 
						 | 
					c7ef18fbc4 | ||
| 
						 | 
					901ec918b1 | ||
| 
						 | 
					6bdae55ee1 | ||
| 
						 | 
					dfb96e4b7f | ||
| 
						 | 
					ff2c316b18 | ||
| 
						 | 
					5be52f71f9 | ||
| 
						 | 
					42873dd37c | ||
| 
						 | 
					f93e7d4e3a | ||
| 
						 | 
					bbcd523967 | ||
| 
						 | 
					68cbe58d00 | ||
| 
						 | 
					115bca98f1 | ||
| 
						 | 
					ed0b34b2fe | ||
| 
						 | 
					ab34401421 | ||
| 
						 | 
					eed0c18d65 | ||
| 
						 | 
					83400d0417 | ||
| 
						 | 
					77a6461c9d | ||
| 
						 | 
					6db9d1122f | ||
| 
						 | 
					83bef85415 | ||
| 
						 | 
					b5b3914bbf | ||
| 
						 | 
					0d90ef94ae | ||
| 
						 | 
					c08b21b7cd | ||
| 
						 | 
					be3cb9ef00 | ||
| 
						 | 
					f7b3f52731 | ||
| 
						 | 
					9220d9fc52 | ||
| 
						 | 
					68c8547067 | ||
| 
						 | 
					1468acfced | ||
| 
						 | 
					b141aea4c0 | ||
| 
						 | 
					a88c022406 | ||
| 
						 | 
					5389382798 | ||
| 
						 | 
					4765173778 | ||
| 
						 | 
					07a9cb910f | ||
| 
						 | 
					f408f074c4 | ||
| 
						 | 
					f1f2640d0e | ||
| 
						 | 
					27d7d7ca69 | ||
| 
						 | 
					c0fc5b48ae | ||
| 
						 | 
					8735d3b83e | ||
| 
						 | 
					ca59dd1302 | ||
| 
						 | 
					eccdef8211 | ||
| 
						 | 
					f2ebfe7aef | ||
| 
						 | 
					cac5b356db | ||
| 
						 | 
					e5a38ce748 | ||
| 
						 | 
					7d9d9fcf36 | ||
| 
						 | 
					156104d5f5 | ||
| 
						 | 
					f0aba6ceb2 | ||
| 
						 | 
					c248ba4043 | ||
| 
						 | 
					ab07ee57c6 | ||
| 
						 | 
					c615dc573a | ||
| 
						 | 
					eae3d72a4d | ||
| 
						 | 
					7b8d826704 | ||
| 
						 | 
					e7baa42e63 | ||
| 
						 | 
					2f32833a22 | ||
| 
						 | 
					f6935a4b4b | ||
| 
						 | 
					332c9e891b | ||
| 
						 | 
					1caabb6419 | ||
| 
						 | 
					f41f7994a3 | ||
| 
						 | 
					e39f314e7a | ||
| 
						 | 
					7f34561e53 | ||
| 
						 | 
					34606b0f1f | ||
| 
						 | 
					c51b509501 | ||
| 
						 | 
					b91ee4847f | ||
| 
						 | 
					625463d871 | ||
| 
						 | 
					6433a01e07 | ||
| 
						 | 
					56cc31e8e7 | ||
| 
						 | 
					3af297aa76 | ||
| 
						 | 
					996ec59d28 | ||
| 
						 | 
					95593eeeab | ||
| 
						 | 
					dad244fb7a | ||
| 
						 | 
					15b5968418 | ||
| 
						 | 
					64a45dc6a6 | ||
| 
						 | 
					7cfede5b83 | ||
| 
						 | 
					e4d17e0b15 | ||
| 
						 | 
					e79f7ce290 | ||
| 
						 | 
					adb5d27d95 | ||
| 
						 | 
					8456a8cecb | ||
| 
						 | 
					b9f66373c1 | ||
| 
						 | 
					9ac365feef | ||
| 
						 | 
					bcc77c73e1 | ||
| 
						 | 
					8b11e5aeb1 | ||
| 
						 | 
					43bbd58a44 | ||
| 
						 | 
					7feffa64f3 | ||
| 
						 | 
					ea0977abb4 | ||
| 
						 | 
					cb48394e8a | ||
| 
						 | 
					4c83dc7c28 | ||
| 
						 | 
					e10ab1da78 | ||
| 
						 | 
					1b0e60374b | ||
| 
						 | 
					3a760fbb44 | ||
| 
						 | 
					f5441a87e3 | ||
| 
						 | 
					e2a812fa4b | ||
| 
						 | 
					5b5ead872b | ||
| 
						 | 
					6ef57a2973 | ||
| 
						 | 
					3e9c7f2e9f | ||
| 
						 | 
					430598b7a1 | ||
| 
						 | 
					03cfd78c59 | ||
| 
						 | 
					91611b09b4 | ||
| 
						 | 
					ecd115851f | ||
| 
						 | 
					4a1e50fed1 | ||
| 
						 | 
					d6d037047b | ||
| 
						 | 
					b5734c2b20 | ||
| 
						 | 
					723fb7eaac | ||
| 
						 | 
					63a9acaa19 | ||
| 
						 | 
					0524f8c677 | ||
| 
						 | 
					70b62f272e | ||
| 
						 | 
					ced11bc707 | ||
| 
						 | 
					6b9c084162 | ||
| 
						 | 
					644ce2a26c | ||
| 
						 | 
					5425e45851 | ||
| 
						 | 
					f0089b7940 | ||
| 
						 | 
					4b44280d53 | ||
| 
						 | 
					f045382d20 | ||
| 
						 | 
					db3fa1ade7 | ||
| 
						 | 
					f83950fd75 | ||
| 
						 | 
					4dd1bf920d | ||
| 
						 | 
					98755f3621 | ||
| 
						 | 
					c3a8a044b9 | ||
| 
						 | 
					15b5ea43a7 | ||
| 
						 | 
					12fce7a08d | ||
| 
						 | 
					0991ab3543 | ||
| 
						 | 
					65d2b37496 | ||
| 
						 | 
					94d518a418 | ||
| 
						 | 
					7cca673902 | ||
| 
						 | 
					384f8d97d8 | ||
| 
						 | 
					c82d5d63e3 | ||
| 
						 | 
					653a3d5d11 | ||
| 
						 | 
					884b7201de | ||
| 
						 | 
					85d2f24447 | ||
| 
						 | 
					935992bcb3 | ||
| 
						 | 
					dc15d1c8ec | ||
| 
						 | 
					6beb9e568a | ||
| 
						 | 
					7178f10bda | ||
| 
						 | 
					ec683fc227 | ||
| 
						 | 
					d4e65eb82a | ||
| 
						 | 
					10c6601b0a | ||
| 
						 | 
					73940bc1bd | ||
| 
						 | 
					9b7fb829f9 | ||
| 
						 | 
					c51d8c9021 | ||
| 
						 | 
					d8a6dfe5ce | ||
| 
						 | 
					5f7cef0b06 | ||
| 
						 | 
					48ff2ffc68 | ||
| 
						 | 
					b3b9ccd314 | ||
| 
						 | 
					1308236429 | ||
| 
						 | 
					63d6b610b8 | ||
| 
						 | 
					8823024509 | ||
| 
						 | 
					4896f870f0 | ||
| 
						 | 
					7e482901d9 | ||
| 
						 | 
					07b309e65d | ||
| 
						 | 
					6bbb5e9b56 | ||
| 
						 | 
					867fecd157 | ||
| 
						 | 
					05388d2dfc | ||
| 
						 | 
					e06b6d7140 | ||
| 
						 | 
					859e508392 | ||
| 
						 | 
					534ce11d54 | ||
| 
						 | 
					e63c7b483b | ||
| 
						 | 
					f57980b069 | ||
| 
						 | 
					7006aa0d2a | ||
| 
						 | 
					bb86db869a | ||
| 
						 | 
					4c4dd23e15 | ||
| 
						 | 
					8051c1ca99 | ||
| 
						 | 
					fe5a6847b5 | ||
| 
						 | 
					a779592414 | ||
| 
						 | 
					112215848d | ||
| 
						 | 
					3dee057826 | ||
| 
						 | 
					4406a08fa7 | ||
| 
						 | 
					c33077bc61 | ||
| 
						 | 
					34db9d9ef2 | ||
| 
						 | 
					1184bbc976 | ||
| 
						 | 
					a3eb2a7ee0 | ||
| 
						 | 
					d13134135b | ||
| 
						 | 
					b4f57972fb | ||
| 
						 | 
					6a5eb43454 | ||
| 
						 | 
					04ec1c8b56 | ||
| 
						 | 
					d7ad155885 | ||
| 
						 | 
					85461a752a | ||
| 
						 | 
					039fbc677d | ||
| 
						 | 
					ea56a39e11 | ||
| 
						 | 
					55e9560e74 | ||
| 
						 | 
					3cb4b4ca03 | ||
| 
						 | 
					11d2866755 | ||
| 
						 | 
					2c517e3e8c | ||
| 
						 | 
					42739f0b22 | ||
| 
						 | 
					a1f9b0d7f2 | ||
| 
						 | 
					c3b8c84131 | ||
| 
						 | 
					471b82f727 | ||
| 
						 | 
					92b85f98e8 | ||
| 
						 | 
					c092d92d45 | ||
| 
						 | 
					e514a1fcd4 | ||
| 
						 | 
					a1b28cb36e | ||
| 
						 | 
					3f2d9abfe6 | ||
| 
						 | 
					f3ec4b514d | ||
| 
						 | 
					fc5798fa71 | ||
| 
						 | 
					95d7ad543f | ||
| 
						 | 
					d9b2903d78 | ||
| 
						 | 
					32a664eedc | ||
| 
						 | 
					e7477890cf | ||
| 
						 | 
					9bf72ff05f | ||
| 
						 | 
					5461f87ff0 | ||
| 
						 | 
					1c58b17235 | ||
| 
						 | 
					d34a1c3ed6 | ||
| 
						 | 
					22e3bc7cfe | ||
| 
						 | 
					955c96731e | ||
| 
						 | 
					54a173dbf1 | ||
| 
						 | 
					9ff8240802 | ||
| 
						 | 
					7bbb5213f3 | ||
| 
						 | 
					b8b30599ee | ||
| 
						 | 
					e083d7f4d0 | ||
| 
						 | 
					a57580b5ab | ||
| 
						 | 
					e22f1fc044 | ||
| 
						 | 
					e09ee8f23d | ||
| 
						 | 
					6ec546a6a4 | ||
| 
						 | 
					877367677b | ||
| 
						 | 
					8be4086224 | ||
| 
						 | 
					871d3b66fb | ||
| 
						 | 
					87358e8843 | ||
| 
						 | 
					5c06cd8eb3 | ||
| 
						 | 
					46b4c970d1 | ||
| 
						 | 
					49f46a7cdd | ||
| 
						 | 
					1627dff166 | ||
| 
						 | 
					cee08debff | ||
| 
						 | 
					912793eddf | ||
| 
						 | 
					eaa5200a35 | ||
| 
						 | 
					a7687c3e17 | ||
| 
						 | 
					932e0469f7 | ||
| 
						 | 
					15ab8918af | ||
| 
						 | 
					d0dfc94a61 | ||
| 
						 | 
					5a2984d03a | ||
| 
						 | 
					c89018a431 | ||
| 
						 | 
					1031ea4313 | ||
| 
						 | 
					5b0fbbaada | ||
| 
						 | 
					0e4f1ac40d | ||
| 
						 | 
					946db3fd50 | ||
| 
						 | 
					3dfc8d4291 | ||
| 
						 | 
					4f5e4f3b86 | ||
| 
						 | 
					505d1d78fb | ||
| 
						 | 
					7af1c04493 | ||
| 
						 | 
					855c98d815 | ||
| 
						 | 
					c26ea7e4e0 | ||
| 
						 | 
					c39ac9edfe | ||
| 
						 | 
					af04f565cf | ||
| 
						 | 
					2b9054d3b2 | ||
| 
						 | 
					c83ecf764d | ||
| 
						 | 
					a2485a18cb | ||
| 
						 | 
					8ef2ad17b5 | ||
| 
						 | 
					4579f78bf9 | ||
| 
						 | 
					1853407645 | ||
| 
						 | 
					cb5efc1c42 | ||
| 
						 | 
					2234f6aacf | ||
| 
						 | 
					be965a60eb | ||
| 
						 | 
					5596751c2c | ||
| 
						 | 
					6417d8132d | ||
| 
						 | 
					5624fafb3a | ||
| 
						 | 
					2eb5f89d82 | ||
| 
						 | 
					e30f17f64f | ||
| 
						 | 
					1ba560dc9e | ||
| 
						 | 
					8c86a18dc6 | ||
| 
						 | 
					b2d516c70a | ||
| 
						 | 
					45940b0514 | ||
| 
						 | 
					97e76d64d6 | ||
| 
						 | 
					756c6721e9 | ||
| 
						 | 
					4c390d9f9f | ||
| 
						 | 
					0d0954d74b | ||
| 
						 | 
					7672ba2c8d | ||
| 
						 | 
					4d28afc153 | ||
| 
						 | 
					7246f42a8e | ||
| 
						 | 
					bdcffc7ba9 | ||
| 
						 | 
					95a6715b2b | ||
| 
						 | 
					5342edf04a | ||
| 
						 | 
					d344b1ca0e | ||
| 
						 | 
					278863d027 | ||
| 
						 | 
					8503e08ee6 | ||
| 
						 | 
					aec02afcdc | ||
| 
						 | 
					52dd79691b | ||
| 
						 | 
					aea2491fa4 | ||
| 
						 | 
					963b28181f | ||
| 
						 | 
					210a9a4162 | ||
| 
						 | 
					a27a884191 | ||
| 
						 | 
					17dcba8f8a | ||
| 
						 | 
					ea6a7a22ff | ||
| 
						 | 
					5ddba719c5 | ||
| 
						 | 
					b398d826c1 | ||
| 
						 | 
					edb557f79e | ||
| 
						 | 
					f463cd98f8 | ||
| 
						 | 
					262d69308d | ||
| 
						 | 
					0406e27100 | ||
| 
						 | 
					ed3ad615d8 | ||
| 
						 | 
					66761ff340 | ||
| 
						 | 
					8bebf138ee | ||
| 
						 | 
					fd836e982e | ||
| 
						 | 
					e32722db70 | ||
| 
						 | 
					b20760c93c | ||
| 
						 | 
					654e31124e | ||
| 
						 | 
					8e36e1b92e | ||
| 
						 | 
					9fe7b08874 | ||
| 
						 | 
					f1364d4af4 | ||
| 
						 | 
					ed593544d8 | ||
| 
						 | 
					0929a0f8aa | ||
| 
						 | 
					13b3412b45 | ||
| 
						 | 
					888e315553 | ||
| 
						 | 
					11daabc9c2 | ||
| 
						 | 
					40e0100c1e | ||
| 
						 | 
					c51352d04d | ||
| 
						 | 
					c8a8acd46e | ||
| 
						 | 
					bbac1534a3 | ||
| 
						 | 
					637b55bfbf | ||
| 
						 | 
					92a24d52be | ||
| 
						 | 
					491f8cc611 | ||
| 
						 | 
					71fc61117b | ||
| 
						 | 
					24f445dade | ||
| 
						 | 
					7c884329eb | ||
| 
						 | 
					bac58bba4d | ||
| 
						 | 
					250bf3f054 | ||
| 
						 | 
					e65a7d887f | ||
| 
						 | 
					ac0d921413 | ||
| 
						 | 
					1e8e471dec | ||
| 
						 | 
					2d7f8b3bdf | ||
| 
						 | 
					7452ef23b1 | ||
| 
						 | 
					9ebe075f9b | ||
| 
						 | 
					3052c64dd7 | ||
| 
						 | 
					5e345783bd | ||
| 
						 | 
					81685573e1 | ||
| 
						 | 
					945ed5d3bd | ||
| 
						 | 
					fff5ba03c2 | ||
| 
						 | 
					82eca13d7b | ||
| 
						 | 
					5f21b925da | ||
| 
						 | 
					272ceadbb0 | ||
| 
						 | 
					d26c2b1a44 | ||
| 
						 | 
					8bda8e5393 | ||
| 
						 | 
					954b8a0cff | ||
| 
						 | 
					7c17e72db4 | ||
| 
						 | 
					d180aee57f | ||
| 
						 | 
					e3ffecefc0 | ||
| 
						 | 
					4c61cf153c | ||
| 
						 | 
					c78fb90e2f | ||
| 
						 | 
					a990898256 | ||
| 
						 | 
					c60c618204 | ||
| 
						 | 
					53bd197c44 | ||
| 
						 | 
					dbb195691b | ||
| 
						 | 
					50da630811 | ||
| 
						 | 
					30eca885c9 | ||
| 
						 | 
					f76685fccf | ||
| 
						 | 
					68d547595e | ||
| 
						 | 
					64341d1d18 | ||
| 
						 | 
					2e49039c01 | ||
| 
						 | 
					321504cf29 | ||
| 
						 | 
					0f4a7bf1f5 | ||
| 
						 | 
					711e74a12b | ||
| 
						 | 
					8f3a739da7 | ||
| 
						 | 
					aed140d802 | ||
| 
						 | 
					c69b88bb55 | ||
| 
						 | 
					c6dc8a11e2 | ||
| 
						 | 
					6366ff6421 | ||
| 
						 | 
					607ddaa632 | ||
| 
						 | 
					d281e59f3a | ||
| 
						 | 
					2db8c42e1d | ||
| 
						 | 
					aa8eb2c92a | ||
| 
						 | 
					b422a63b2a | ||
| 
						 | 
					ad5f2cd748 | ||
| 
						 | 
					efae363739 | ||
| 
						 | 
					2d79d21c50 | ||
| 
						 | 
					3b9d126322 | ||
| 
						 | 
					0ea77de98c | ||
| 
						 | 
					19014331d8 | ||
| 
						 | 
					b276ac0588 | ||
| 
						 | 
					de33cbd7e7 | ||
| 
						 | 
					103ba4c696 | ||
| 
						 | 
					5a90b83f63 | ||
| 
						 | 
					716039e452 | ||
| 
						 | 
					896654aaef | ||
| 
						 | 
					5fad38f65f | ||
| 
						 | 
					89f2ea5725 | ||
| 
						 | 
					a32ad33b4e | ||
| 
						 | 
					a328fff5a7 | ||
| 
						 | 
					233783c76c | ||
| 
						 | 
					39a18fb358 | ||
| 
						 | 
					460a144ca8 | ||
| 
						 | 
					23ead416d5 | ||
| 
						 | 
					1b5f11bbee | ||
| 
						 | 
					4cc2817fcd | ||
| 
						 | 
					d437cc915c | ||
| 
						 | 
					dd3f2f6c7e | ||
| 
						 | 
					5f61897bec | ||
| 
						 | 
					c5d26a5b4a | ||
| 
						 | 
					855112dfc3 | ||
| 
						 | 
					0da97289e6 | ||
| 
						 | 
					b9767bdcbc | ||
| 
						 | 
					91f12a50cf | ||
| 
						 | 
					e92a9d1d9e | ||
| 
						 | 
					4eb51ab4d6 | ||
| 
						 | 
					e6b0a0ca2b | ||
| 
						 | 
					924df1e7de | ||
| 
						 | 
					ed7983af41 | ||
| 
						 | 
					40c474cd83 | ||
| 
						 | 
					a2d2863c72 | ||
| 
						 | 
					133a17d6eb | ||
| 
						 | 
					fe47ddc27a | ||
| 
						 | 
					aad03f1bf5 | ||
| 
						 | 
					a4867a00ea | ||
| 
						 | 
					e0cff214b2 | ||
| 
						 | 
					c6109024aa | ||
| 
						 | 
					4e308f551c | ||
| 
						 | 
					8a2b1d9359 | ||
| 
						 | 
					63a186bdf9 | ||
| 
						 | 
					d594a6fcbc | ||
| 
						 | 
					e18dfdd656 | ||
| 
						 | 
					3aa107142b | ||
| 
						 | 
					0cd24c629a | ||
| 
						 | 
					f31e0532c4 | ||
| 
						 | 
					f0b6aabc96 | ||
| 
						 | 
					97a18717e6 | ||
| 
						 | 
					ede1de9021 | ||
| 
						 | 
					f1a8d957f8 | ||
| 
						 | 
					9821a3442b | ||
| 
						 | 
					87842e097b | ||
| 
						 | 
					7dd40e2014 | ||
| 
						 | 
					3d71e2e189 | ||
| 
						 | 
					affaaf7d2c | ||
| 
						 | 
					a719998220 | ||
| 
						 | 
					bad161a5c1 | ||
| 
						 | 
					09a6fdf1c7 | ||
| 
						 | 
					d2616cbdfc | ||
| 
						 | 
					faf1c8bee8 | ||
| 
						 | 
					f09aca4865 | ||
| 
						 | 
					cc52f37933 | ||
| 
						 | 
					e5051eefbc | ||
| 
						 | 
					9e5cd0da51 | ||
| 
						 | 
					4e120a291e | ||
| 
						 | 
					2790d72bff | ||
| 
						 | 
					e44f447d85 | ||
| 
						 | 
					4356581db0 | ||
| 
						 | 
					f87a701b28 | ||
| 
						 | 
					fa2eb46cd6 | ||
| 
						 | 
					f924e80f43 | ||
| 
						 | 
					6180ee8065 | ||
| 
						 | 
					1be106c0b5 | ||
| 
						 | 
					b0533db2eb | ||
| 
						 | 
					dba502c756 | ||
| 
						 | 
					d9cb64b893 | ||
| 
						 | 
					2d91e6b977 | ||
| 
						 | 
					4a6f1f150a | ||
| 
						 | 
					7f76f3726f | ||
| 
						 | 
					e2d97b6f36 | ||
| 
						 | 
					2a653642f5 | ||
| 
						 | 
					97eba1eecc | ||
| 
						 | 
					ff6bed54c6 | ||
| 
						 | 
					f9b0666adf | ||
| 
						 | 
					ca12b8aa56 | ||
| 
						 | 
					77508f7e44 | ||
| 
						 | 
					54de0ca0da | ||
| 
						 | 
					f364788c03 | ||
| 
						 | 
					00aaf84c37 | ||
| 
						 | 
					b01bc76dc5 | ||
| 
						 | 
					910f812737 | ||
| 
						 | 
					a4d024f43d | ||
| 
						 | 
					9937ad7fa0 | ||
| 
						 | 
					edcd88123d | ||
| 
						 | 
					ea1b5e19f0 | ||
| 
						 | 
					54337befc2 | ||
| 
						 | 
					140ef791aa | ||
| 
						 | 
					58350b6c99 | ||
| 
						 | 
					f186ff8b46 | ||
| 
						 | 
					03190611bb | ||
| 
						 | 
					37f322585e | ||
| 
						 | 
					9218e85bd6 | ||
| 
						 | 
					f923ba87c0 | ||
| 
						 | 
					fac49896df | ||
| 
						 | 
					56225701f9 | ||
| 
						 | 
					b5de43b225 | ||
| 
						 | 
					b955527f6c | ||
| 
						 | 
					94b28102f5 | ||
| 
						 | 
					de871862a8 | ||
| 
						 | 
					3be56fd502 | ||
| 
						 | 
					5086cd716f | ||
| 
						 | 
					4937af0cd9 | ||
| 
						 | 
					877a5fda41 | ||
| 
						 | 
					39cd2838df | ||
| 
						 | 
					565473c90c | ||
| 
						 | 
					1fac91a659 | ||
| 
						 | 
					0a4837c1f0 | ||
| 
						 | 
					e7404183a0 | ||
| 
						 | 
					44f8dcfb6e | ||
| 
						 | 
					481e0e98f8 | ||
| 
						 | 
					9de40c26eb | ||
| 
						 | 
					ad953f02d1 | ||
| 
						 | 
					3869e56521 | ||
| 
						 | 
					63d87b17aa | ||
| 
						 | 
					ed68a0e773 | ||
| 
						 | 
					e2640c8368 | ||
| 
						 | 
					eff626248f | ||
| 
						 | 
					ce29a3b07a | ||
| 
						 | 
					1b89174558 | ||
| 
						 | 
					1c1ad32610 | ||
| 
						 | 
					71237e2f76 | ||
| 
						 | 
					518c271eba | ||
| 
						 | 
					d71996e58d | ||
| 
						 | 
					2f33cd2db5 | ||
| 
						 | 
					5ec9bb0fb5 | ||
| 
						 | 
					8cc3cbb22e | ||
| 
						 | 
					b0fa317302 | ||
| 
						 | 
					5cb56bc677 | ||
| 
						 | 
					21f8fd9fa5 | ||
| 
						 | 
					2100ef63a9 | ||
| 
						 | 
					29db77c9c9 | ||
| 
						 | 
					2e59ad90cc | ||
| 
						 | 
					1b8c9edcde | ||
| 
						 | 
					d4c2a85f9c | ||
| 
						 | 
					8c75b87e94 | ||
| 
						 | 
					409d4b9d47 | ||
| 
						 | 
					4e3b95d120 | ||
| 
						 | 
					61a9c9fa33 | ||
| 
						 | 
					9c605f2d46 | ||
| 
						 | 
					44bb5a89c8 | ||
| 
						 | 
					cbdb96f105 | ||
| 
						 | 
					9ee3463d07 | ||
| 
						 | 
					f0b14055b6 | ||
| 
						 | 
					fbd9e87b51 | ||
| 
						 | 
					edb3b77916 | ||
| 
						 | 
					ebaa84617f | ||
| 
						 | 
					8eb18995cb | ||
| 
						 | 
					ebabf0e7d8 | ||
| 
						 | 
					607e1f823d | ||
| 
						 | 
					3b52a306cd | ||
| 
						 | 
					0c370d5897 | ||
| 
						 | 
					8bf0448f41 | ||
| 
						 | 
					14e04eb231 | ||
| 
						 | 
					1be9bac3a9 | ||
| 
						 | 
					02b5a3efb8 | ||
| 
						 | 
					bd457f64d8 | ||
| 
						 | 
					9efeea14f2 | ||
| 
						 | 
					9b48ff5775 | ||
| 
						 | 
					117b58ebe6 | ||
| 
						 | 
					303b699005 | ||
| 
						 | 
					9173da0416 | ||
| 
						 | 
					d2cd65f5db | ||
| 
						 | 
					2735f96516 | ||
| 
						 | 
					6847645782 | ||
| 
						 | 
					b0bc898278 | ||
| 
						 | 
					c0f6af7213 | ||
| 
						 | 
					5edebaf468 | ||
| 
						 | 
					d436409153 | ||
| 
						 | 
					8c41fc2b1d | ||
| 
						 | 
					46f17bea66 | ||
| 
						 | 
					11477dbc03 | ||
| 
						 | 
					947c104eff | ||
| 
						 | 
					e5366dbbe7 | ||
| 
						 | 
					d3375193a9 | ||
| 
						 | 
					6144ce1fe0 | ||
| 
						 | 
					1771e673d2 | ||
| 
						 | 
					d258e06fd7 | ||
| 
						 | 
					854f4a8896 | ||
| 
						 | 
					f94c221a9a | ||
| 
						 | 
					6a2f0f5143 | ||
| 
						 | 
					183e2a8471 | ||
| 
						 | 
					c6c2842bdb | ||
| 
						 | 
					f26767b65e | ||
| 
						 | 
					98d32876b5 | ||
| 
						 | 
					e5d0f3c036 | ||
| 
						 | 
					cc15aaacbb | ||
| 
						 | 
					553df1d57b | ||
| 
						 | 
					b92311402a | ||
| 
						 | 
					93796491af | ||
| 
						 | 
					c038cf27a7 | ||
| 
						 | 
					1f42d32eb5 | ||
| 
						 | 
					06bde559da | ||
| 
						 | 
					922f7167f5 | ||
| 
						 | 
					90c0d3e12f | ||
| 
						 | 
					bf5f846fc6 | ||
| 
						 | 
					926bcc71ae | ||
| 
						 | 
					ea4a458214 | ||
| 
						 | 
					b3ae3e1feb | ||
| 
						 | 
					fe7af21c91 | ||
| 
						 | 
					29f72037fe | ||
| 
						 | 
					1d6b4bfcef | ||
| 
						 | 
					5bfac5ec09 | ||
| 
						 | 
					dfffaace26 | ||
| 
						 | 
					1d5f628c7a | ||
| 
						 | 
					cb8a6f66fa | ||
| 
						 | 
					cb21c7c18d | ||
| 
						 | 
					0d104776bc | ||
| 
						 | 
					5f27757039 | ||
| 
						 | 
					532907219b | ||
| 
						 | 
					eeaba74553 | ||
| 
						 | 
					dd637582a4 | ||
| 
						 | 
					b0d12aeea1 | ||
| 
						 | 
					bdbd813455 | ||
| 
						 | 
					a6fac2b175 | ||
| 
						 | 
					5ce923ea90 | ||
| 
						 | 
					29f0508dc2 | ||
| 
						 | 
					3ffa59f0cd | ||
| 
						 | 
					790d6ef94c | ||
| 
						 | 
					7828f48b9a | ||
| 
						 | 
					768c71830b | ||
| 
						 | 
					ceb0564ebf | ||
| 
						 | 
					20f7eb7327 | ||
| 
						 | 
					441d5bd44d | ||
| 
						 | 
					9fa19df2ff | ||
| 
						 | 
					39f64f597e | ||
| 
						 | 
					160429eb24 | ||
| 
						 | 
					6516c64e67 | ||
| 
						 | 
					4c8a703084 | ||
| 
						 | 
					335210d788 | ||
| 
						 | 
					9b04e657db | ||
| 
						 | 
					f7311aa025 | ||
| 
						 | 
					fb24e55c8d | ||
| 
						 | 
					b58ca46a46 | ||
| 
						 | 
					76991cdcc4 | ||
| 
						 | 
					69c7cf783e | ||
| 
						 | 
					f751c3828e | ||
| 
						 | 
					5c65f9f9ad | ||
| 
						 | 
					81ae6709e4 | ||
| 
						 | 
					593a3d48fb | ||
| 
						 | 
					a8b90283d8 | ||
| 
						 | 
					80076f935d | ||
| 
						 | 
					9fbb3659a6 | ||
| 
						 | 
					fee446c28a | ||
| 
						 | 
					1d56f0b035 | ||
| 
						 | 
					34e8979d40 | ||
| 
						 | 
					2966a62429 | ||
| 
						 | 
					5983ccc55c | ||
| 
						 | 
					de382b704c | ||
| 
						 | 
					16dbbfabc6 | ||
| 
						 | 
					af8d04818d | ||
| 
						 | 
					ee19ef1aac | ||
| 
						 | 
					5e2d4e332a | ||
| 
						 | 
					c6c857dfff | ||
| 
						 | 
					8dbac20f8b | ||
| 
						 | 
					341fddb9aa | ||
| 
						 | 
					456824669f | ||
| 
						 | 
					62f3039d82 | ||
| 
						 | 
					be4c718859 | ||
| 
						 | 
					c2f9ed7c59 | ||
| 
						 | 
					bfac6607d1 | ||
| 
						 | 
					513066ba52 | ||
| 
						 | 
					316777f757 | ||
| 
						 | 
					246950159d | ||
| 
						 | 
					31d6a54b06 | ||
| 
						 | 
					5c3a6164bb | ||
| 
						 | 
					1652914d39 | ||
| 
						 | 
					618cfd9ec5 | ||
| 
						 | 
					f97cfe9916 | ||
| 
						 | 
					b9259a0238 | ||
| 
						 | 
					5abbe385c5 | ||
| 
						 | 
					e43dcded62 | ||
| 
						 | 
					887081fd71 | ||
| 
						 | 
					71ded24fce | ||
| 
						 | 
					1e2a9e8348 | ||
| 
						 | 
					64a3aa7092 | ||
| 
						 | 
					fda8dd4ce3 | ||
| 
						 | 
					1efabd27d8 | ||
| 
						 | 
					caa651e55b | ||
| 
						 | 
					b0a3891498 | ||
| 
						 | 
					2a9e3d84fd | ||
| 
						 | 
					a3dcac62f9 | ||
| 
						 | 
					6b535b11f8 | ||
| 
						 | 
					d9f09a7523 | ||
| 
						 | 
					159744e09e | ||
| 
						 | 
					c2637a76f7 | ||
| 
						 | 
					237edd75d1 | ||
| 
						 | 
					a34d5e3901 | ||
| 
						 | 
					1dd43a75f2 | ||
| 
						 | 
					1f5cbca509 | ||
| 
						 | 
					3749c11f21 | ||
| 
						 | 
					66cdb761dc | ||
| 
						 | 
					f0d9ad6a4e | ||
| 
						 | 
					03e317d052 | ||
| 
						 | 
					ba461e51a8 | ||
| 
						 | 
					80949521b6 | ||
| 
						 | 
					acbb8e9fd0 | ||
| 
						 | 
					90394a50df | ||
| 
						 | 
					5379794f16 | ||
| 
						 | 
					0a32321c85 | ||
| 
						 | 
					c9062599df | ||
| 
						 | 
					10a6e9b4ee | ||
| 
						 | 
					4b8ec44262 | ||
| 
						 | 
					bd74ed4bc0 | ||
| 
						 | 
					fc42f14448 | ||
| 
						 | 
					d01f296420 | ||
| 
						 | 
					27112e2ace | ||
| 
						 | 
					837930234f | ||
| 
						 | 
					e19aa3bbe0 | ||
| 
						 | 
					35b5c1ed56 | ||
| 
						 | 
					3e65e6c69a | ||
| 
						 | 
					3b3297d269 | ||
| 
						 | 
					fc0deb642a | ||
| 
						 | 
					9f2b2f51ff | ||
| 
						 | 
					c9d93ff685 | ||
| 
						 | 
					fa72990a63 | ||
| 
						 | 
					e5afb1c4ea | ||
| 
						 | 
					73ead5f328 | ||
| 
						 | 
					5c57b51378 | ||
| 
						 | 
					e25935ef21 | ||
| 
						 | 
					c7a52c3894 | ||
| 
						 | 
					01a4b4e82f | ||
| 
						 | 
					766866197b | ||
| 
						 | 
					9b5a3cbcd3 | ||
| 
						 | 
					d2ed3b9bec | ||
| 
						 | 
					99d2db42cd | ||
| 
						 | 
					7619507e6c | ||
| 
						 | 
					53a4689ed1 | ||
| 
						 | 
					0a82e6e792 | ||
| 
						 | 
					98855e4123 | ||
| 
						 | 
					71d9d64a02 | ||
| 
						 | 
					6a09d7c49b | ||
| 
						 | 
					46e50ba53f | ||
| 
						 | 
					f1e3ff2ed2 | ||
| 
						 | 
					7787fa8f29 | ||
| 
						 | 
					70902029f8 | ||
| 
						 | 
					4f9a56c884 | ||
| 
						 | 
					3715ba030b | ||
| 
						 | 
					2e49fd7b48 | ||
| 
						 | 
					06912b492f | ||
| 
						 | 
					442e58b07a | ||
| 
						 | 
					799f04efc0 | ||
| 
						 | 
					cc7dbeada6 | ||
| 
						 | 
					45d368e3a1 | ||
| 
						 | 
					628a94bad3 | ||
| 
						 | 
					0c93be97a9 | ||
| 
						 | 
					54eb6070fb | ||
| 
						 | 
					4dbf1c521e | ||
| 
						 | 
					0651716b96 | ||
| 
						 | 
					f30b8f6b3c | ||
| 
						 | 
					0992609bf4 | ||
| 
						 | 
					18c08f24ad | ||
| 
						 | 
					a7f53aea0e | ||
| 
						 | 
					c399905675 | ||
| 
						 | 
					5cb0c11feb | ||
| 
						 | 
					08b67e7aea | ||
| 
						 | 
					07ae8ec553 | ||
| 
						 | 
					04c3a43c17 | ||
| 
						 | 
					b632344596 | ||
| 
						 | 
					fb8ec79a52 | ||
| 
						 | 
					7dd16df846 | ||
| 
						 | 
					551e9c6111 | ||
| 
						 | 
					cc9f0b3f47 | ||
| 
						 | 
					d77c3abdc0 | ||
| 
						 | 
					dd37a4e04c | ||
| 
						 | 
					1f5c79bd17 | ||
| 
						 | 
					623570a117 | ||
| 
						 | 
					cdbc146e5d | ||
| 
						 | 
					7ae611256a | ||
| 
						 | 
					b62c47fede | ||
| 
						 | 
					99f497c3b8 | ||
| 
						 | 
					4f88c2489b | ||
| 
						 | 
					294ba1fca7 | ||
| 
						 | 
					be61b38a2c | ||
| 
						 | 
					f9797825ad | ||
| 
						 | 
					fd4b7d4588 | ||
| 
						 | 
					062cedc200 | ||
| 
						 | 
					79b9d0579d | ||
| 
						 | 
					ab31117bf3 | ||
| 
						 | 
					d31040f5d8 | ||
| 
						 | 
					52d19fa43d | ||
| 
						 | 
					8ca34f7098 | ||
| 
						 | 
					4c4099966a | ||
| 
						 | 
					86ac7f3a59 | ||
| 
						 | 
					9e400a7857 | ||
| 
						 | 
					d5278351da | ||
| 
						 | 
					36861595f1 | ||
| 
						 | 
					d604321f37 | ||
| 
						 | 
					964ab65497 | ||
| 
						 | 
					3b940b1c04 | ||
| 
						 | 
					a91e6a6bdf | ||
| 
						 | 
					8600620305 | ||
| 
						 | 
					96721f305f | ||
| 
						 | 
					2bf70d7d00 | ||
| 
						 | 
					1d8c170f48 | ||
| 
						 | 
					6009c7edb4 | ||
| 
						 | 
					e3f36c033e | ||
| 
						 | 
					d4eb0f1655 | ||
| 
						 | 
					5fca480921 | ||
| 
						 | 
					7051f897bc | ||
| 
						 | 
					2cb3015a28 | ||
| 
						 | 
					d0859a7d33 | ||
| 
						 | 
					e20ec00071 | ||
| 
						 | 
					150114d774 | ||
| 
						 | 
					89dfa5ea82 | ||
| 
						 | 
					61ebc629f6 | ||
| 
						 | 
					32f2da77f8 | ||
| 
						 | 
					bfca3f242a | ||
| 
						 | 
					3dfff2930a | ||
| 
						 | 
					027e0de48e | ||
| 
						 | 
					c811141a4f | ||
| 
						 | 
					871c0ee2a5 | ||
| 
						 | 
					b8a7741c61 | ||
| 
						 | 
					97aa930ad2 | ||
| 
						 | 
					2a5def10e7 | ||
| 
						 | 
					969834e037 | ||
| 
						 | 
					d73a44c504 | ||
| 
						 | 
					8aec092ab6 | ||
| 
						 | 
					4fa959ba45 | ||
| 
						 | 
					b6011b9353 | ||
| 
						 | 
					40a5005d94 | ||
| 
						 | 
					c5eba21ff6 | ||
| 
						 | 
					4891cfef56 | ||
| 
						 | 
					4395664547 | ||
| 
						 | 
					04d926af39 | ||
| 
						 | 
					f9a31c1abb | ||
| 
						 | 
					b43712d78d | ||
| 
						 | 
					01904a0f10 | ||
| 
						 | 
					dd875e7529 | ||
| 
						 | 
					f1dcf0f0b8 | ||
| 
						 | 
					a045d001bf | ||
| 
						 | 
					066c1022d0 | ||
| 
						 | 
					dca1c0f160 | ||
| 
						 | 
					2419bc3678 | ||
| 
						 | 
					c19b3ecd43 | ||
| 
						 | 
					ef1e91d838 | ||
| 
						 | 
					e5929225eb | ||
| 
						 | 
					59c192becc | ||
| 
						 | 
					a800816750 | ||
| 
						 | 
					607c3ae651 | ||
| 
						 | 
					970563e07b | ||
| 
						 | 
					5591832b50 | ||
| 
						 | 
					25b116048c | ||
| 
						 | 
					24ba9eba46 | ||
| 
						 | 
					424c34225f | ||
| 
						 | 
					d781f3a11b | ||
| 
						 | 
					a80f9ed336 | ||
| 
						 | 
					92bbedfa5a | ||
| 
						 | 
					86710ed483 | ||
| 
						 | 
					da7eb9ac90 | ||
| 
						 | 
					c411043681 | ||
| 
						 | 
					93f8ee7e60 | ||
| 
						 | 
					0efc1f06f2 | ||
| 
						 | 
					9ad9d64ac7 | ||
| 
						 | 
					5a2cfa2798 | ||
| 
						 | 
					eb24da7c82 | ||
| 
						 | 
					f93e261d75 | ||
| 
						 | 
					501f88ca86 | ||
| 
						 | 
					360effcb72 | ||
| 
						 | 
					eb9bd69405 | ||
| 
						 | 
					11b8210e36 | ||
| 
						 | 
					d23376b81e | ||
| 
						 | 
					99d90845b5 | ||
| 
						 | 
					ea0127e42b | 
@@ -49,7 +49,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true
 | 
				
			|||||||
ConstructorInitializerIndentWidth: 4
 | 
					ConstructorInitializerIndentWidth: 4
 | 
				
			||||||
ContinuationIndentWidth: 4
 | 
					ContinuationIndentWidth: 4
 | 
				
			||||||
Cpp11BracedListStyle: true
 | 
					Cpp11BracedListStyle: true
 | 
				
			||||||
DerivePointerAlignment: true
 | 
					DerivePointerAlignment: false
 | 
				
			||||||
DisableFormat:   false
 | 
					DisableFormat:   false
 | 
				
			||||||
ExperimentalAutoDetectBinPacking: false
 | 
					ExperimentalAutoDetectBinPacking: false
 | 
				
			||||||
FixNamespaceComments: true
 | 
					FixNamespaceComments: true
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										75
									
								
								.clang-tidy
									
									
									
									
									
								
							
							
						
						
									
										75
									
								
								.clang-tidy
									
									
									
									
									
								
							@@ -2,38 +2,29 @@
 | 
				
			|||||||
Checks: >-
 | 
					Checks: >-
 | 
				
			||||||
  *,
 | 
					  *,
 | 
				
			||||||
  -abseil-*,
 | 
					  -abseil-*,
 | 
				
			||||||
 | 
					  -altera-*,
 | 
				
			||||||
  -android-*,
 | 
					  -android-*,
 | 
				
			||||||
  -boost-*,
 | 
					  -boost-*,
 | 
				
			||||||
  -bugprone-branch-clone,
 | 
					 | 
				
			||||||
  -bugprone-macro-parentheses,
 | 
					 | 
				
			||||||
  -bugprone-narrowing-conversions,
 | 
					  -bugprone-narrowing-conversions,
 | 
				
			||||||
  -bugprone-reserved-identifier,
 | 
					 | 
				
			||||||
  -bugprone-signed-char-misuse,
 | 
					  -bugprone-signed-char-misuse,
 | 
				
			||||||
  -bugprone-suspicious-include,
 | 
					 | 
				
			||||||
  -bugprone-too-small-loop-variable,
 | 
					 | 
				
			||||||
  -bugprone-unhandled-self-assignment,
 | 
					 | 
				
			||||||
  -cert-dcl37-c,
 | 
					 | 
				
			||||||
  -cert-dcl50-cpp,
 | 
					  -cert-dcl50-cpp,
 | 
				
			||||||
  -cert-dcl51-cpp,
 | 
					 | 
				
			||||||
  -cert-err58-cpp,
 | 
					  -cert-err58-cpp,
 | 
				
			||||||
  -cert-oop54-cpp,
 | 
					 | 
				
			||||||
  -cert-oop57-cpp,
 | 
					  -cert-oop57-cpp,
 | 
				
			||||||
  -cert-str34-c,
 | 
					  -cert-str34-c,
 | 
				
			||||||
  -clang-analyzer-core.CallAndMessage,
 | 
					  -clang-analyzer-optin.cplusplus.UninitializedObject,
 | 
				
			||||||
  -clang-analyzer-optin.*,
 | 
					 | 
				
			||||||
  -clang-analyzer-osx.*,
 | 
					  -clang-analyzer-osx.*,
 | 
				
			||||||
  -clang-analyzer-security.*,
 | 
					  -clang-diagnostic-delete-abstract-non-virtual-dtor,
 | 
				
			||||||
 | 
					  -clang-diagnostic-delete-non-abstract-non-virtual-dtor,
 | 
				
			||||||
  -clang-diagnostic-shadow-field,
 | 
					  -clang-diagnostic-shadow-field,
 | 
				
			||||||
 | 
					  -clang-diagnostic-unused-const-variable,
 | 
				
			||||||
 | 
					  -clang-diagnostic-unused-parameter,
 | 
				
			||||||
 | 
					  -concurrency-*,
 | 
				
			||||||
  -cppcoreguidelines-avoid-c-arrays,
 | 
					  -cppcoreguidelines-avoid-c-arrays,
 | 
				
			||||||
  -cppcoreguidelines-avoid-goto,
 | 
					 | 
				
			||||||
  -cppcoreguidelines-avoid-magic-numbers,
 | 
					  -cppcoreguidelines-avoid-magic-numbers,
 | 
				
			||||||
  -cppcoreguidelines-avoid-non-const-global-variables,
 | 
					 | 
				
			||||||
  -cppcoreguidelines-c-copy-assignment-signature,
 | 
					 | 
				
			||||||
  -cppcoreguidelines-init-variables,
 | 
					  -cppcoreguidelines-init-variables,
 | 
				
			||||||
  -cppcoreguidelines-macro-usage,
 | 
					  -cppcoreguidelines-macro-usage,
 | 
				
			||||||
  -cppcoreguidelines-narrowing-conversions,
 | 
					  -cppcoreguidelines-narrowing-conversions,
 | 
				
			||||||
  -cppcoreguidelines-non-private-member-variables-in-classes,
 | 
					  -cppcoreguidelines-non-private-member-variables-in-classes,
 | 
				
			||||||
  -cppcoreguidelines-owning-memory,
 | 
					 | 
				
			||||||
  -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
 | 
					  -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
 | 
				
			||||||
  -cppcoreguidelines-pro-bounds-constant-array-index,
 | 
					  -cppcoreguidelines-pro-bounds-constant-array-index,
 | 
				
			||||||
  -cppcoreguidelines-pro-bounds-pointer-arithmetic,
 | 
					  -cppcoreguidelines-pro-bounds-pointer-arithmetic,
 | 
				
			||||||
@@ -45,17 +36,17 @@ Checks: >-
 | 
				
			|||||||
  -cppcoreguidelines-pro-type-union-access,
 | 
					  -cppcoreguidelines-pro-type-union-access,
 | 
				
			||||||
  -cppcoreguidelines-pro-type-vararg,
 | 
					  -cppcoreguidelines-pro-type-vararg,
 | 
				
			||||||
  -cppcoreguidelines-special-member-functions,
 | 
					  -cppcoreguidelines-special-member-functions,
 | 
				
			||||||
  -fuchsia-*,
 | 
					 | 
				
			||||||
  -fuchsia-default-arguments,
 | 
					 | 
				
			||||||
  -fuchsia-multiple-inheritance,
 | 
					  -fuchsia-multiple-inheritance,
 | 
				
			||||||
  -fuchsia-overloaded-operator,
 | 
					  -fuchsia-overloaded-operator,
 | 
				
			||||||
  -fuchsia-statically-constructed-objects,
 | 
					  -fuchsia-statically-constructed-objects,
 | 
				
			||||||
 | 
					  -fuchsia-default-arguments-declarations,
 | 
				
			||||||
 | 
					  -fuchsia-default-arguments-calls,
 | 
				
			||||||
  -google-build-using-namespace,
 | 
					  -google-build-using-namespace,
 | 
				
			||||||
  -google-explicit-constructor,
 | 
					  -google-explicit-constructor,
 | 
				
			||||||
  -google-readability-braces-around-statements,
 | 
					  -google-readability-braces-around-statements,
 | 
				
			||||||
  -google-readability-casting,
 | 
					  -google-readability-casting,
 | 
				
			||||||
 | 
					  -google-readability-namespace-comments,
 | 
				
			||||||
  -google-readability-todo,
 | 
					  -google-readability-todo,
 | 
				
			||||||
  -google-runtime-int,
 | 
					 | 
				
			||||||
  -google-runtime-references,
 | 
					  -google-runtime-references,
 | 
				
			||||||
  -hicpp-*,
 | 
					  -hicpp-*,
 | 
				
			||||||
  -llvm-else-after-return,
 | 
					  -llvm-else-after-return,
 | 
				
			||||||
@@ -65,39 +56,29 @@ Checks: >-
 | 
				
			|||||||
  -llvmlibc-*,
 | 
					  -llvmlibc-*,
 | 
				
			||||||
  -misc-non-private-member-variables-in-classes,
 | 
					  -misc-non-private-member-variables-in-classes,
 | 
				
			||||||
  -misc-no-recursion,
 | 
					  -misc-no-recursion,
 | 
				
			||||||
  -misc-unconventional-assign-operator,
 | 
					 | 
				
			||||||
  -misc-unused-parameters,
 | 
					  -misc-unused-parameters,
 | 
				
			||||||
  -modernize-avoid-c-arrays,
 | 
					  -modernize-avoid-c-arrays,
 | 
				
			||||||
  -modernize-deprecated-headers,
 | 
					  -modernize-avoid-bind,
 | 
				
			||||||
  -modernize-pass-by-value,
 | 
					  -modernize-concat-nested-namespaces,
 | 
				
			||||||
  -modernize-pass-by-value,
 | 
					 | 
				
			||||||
  -modernize-return-braced-init-list,
 | 
					  -modernize-return-braced-init-list,
 | 
				
			||||||
  -modernize-use-auto,
 | 
					  -modernize-use-auto,
 | 
				
			||||||
  -modernize-use-default-member-init,
 | 
					  -modernize-use-default-member-init,
 | 
				
			||||||
  -modernize-use-equals-default,
 | 
					  -modernize-use-equals-default,
 | 
				
			||||||
  -modernize-use-trailing-return-type,
 | 
					  -modernize-use-trailing-return-type,
 | 
				
			||||||
 | 
					  -modernize-use-nodiscard,
 | 
				
			||||||
  -mpi-*,
 | 
					  -mpi-*,
 | 
				
			||||||
  -objc-*,
 | 
					  -objc-*,
 | 
				
			||||||
  -performance-unnecessary-value-param,
 | 
					 | 
				
			||||||
  -readability-braces-around-statements,
 | 
					 | 
				
			||||||
  -readability-const-return-type,
 | 
					 | 
				
			||||||
  -readability-convert-member-functions-to-static,
 | 
					  -readability-convert-member-functions-to-static,
 | 
				
			||||||
  -readability-else-after-return,
 | 
					  -readability-else-after-return,
 | 
				
			||||||
 | 
					  -readability-function-cognitive-complexity,
 | 
				
			||||||
  -readability-implicit-bool-conversion,
 | 
					  -readability-implicit-bool-conversion,
 | 
				
			||||||
  -readability-isolate-declaration,
 | 
					  -readability-isolate-declaration,
 | 
				
			||||||
  -readability-magic-numbers,
 | 
					  -readability-magic-numbers,
 | 
				
			||||||
  -readability-make-member-function-const,
 | 
					  -readability-make-member-function-const,
 | 
				
			||||||
  -readability-named-parameter,
 | 
					 | 
				
			||||||
  -readability-qualified-auto,
 | 
					 | 
				
			||||||
  -readability-redundant-access-specifiers,
 | 
					 | 
				
			||||||
  -readability-redundant-member-init,
 | 
					 | 
				
			||||||
  -readability-redundant-string-init,
 | 
					  -readability-redundant-string-init,
 | 
				
			||||||
  -readability-uppercase-literal-suffix,
 | 
					  -readability-uppercase-literal-suffix,
 | 
				
			||||||
  -readability-use-anyofallof,
 | 
					  -readability-use-anyofallof,
 | 
				
			||||||
  -warnings-as-errors,
 | 
					 | 
				
			||||||
  -zircon-*
 | 
					 | 
				
			||||||
WarningsAsErrors: '*'
 | 
					WarningsAsErrors: '*'
 | 
				
			||||||
HeaderFilterRegex: '^.*/src/esphome/.*'
 | 
					 | 
				
			||||||
AnalyzeTemporaryDtors: false
 | 
					AnalyzeTemporaryDtors: false
 | 
				
			||||||
FormatStyle:     google
 | 
					FormatStyle:     google
 | 
				
			||||||
CheckOptions:
 | 
					CheckOptions:
 | 
				
			||||||
@@ -105,9 +86,11 @@ CheckOptions:
 | 
				
			|||||||
    value:           '1'
 | 
					    value:           '1'
 | 
				
			||||||
  - key:             google-readability-function-size.StatementThreshold
 | 
					  - key:             google-readability-function-size.StatementThreshold
 | 
				
			||||||
    value:           '800'
 | 
					    value:           '800'
 | 
				
			||||||
  - key:             google-readability-namespace-comments.ShortNamespaceLines
 | 
					  - key:             google-runtime-int.TypeSuffix
 | 
				
			||||||
 | 
					    value:           '_t'
 | 
				
			||||||
 | 
					  - key:             llvm-namespace-comment.ShortNamespaceLines
 | 
				
			||||||
    value:           '10'
 | 
					    value:           '10'
 | 
				
			||||||
  - key:             google-readability-namespace-comments.SpacesBeforeComments
 | 
					  - key:             llvm-namespace-comment.SpacesBeforeComments
 | 
				
			||||||
    value:           '2'
 | 
					    value:           '2'
 | 
				
			||||||
  - key:             modernize-loop-convert.MaxCopySize
 | 
					  - key:             modernize-loop-convert.MaxCopySize
 | 
				
			||||||
    value:           '16'
 | 
					    value:           '16'
 | 
				
			||||||
@@ -121,6 +104,12 @@ CheckOptions:
 | 
				
			|||||||
    value:           llvm
 | 
					    value:           llvm
 | 
				
			||||||
  - key:             modernize-use-nullptr.NullMacros
 | 
					  - key:             modernize-use-nullptr.NullMacros
 | 
				
			||||||
    value:           'NULL'
 | 
					    value:           'NULL'
 | 
				
			||||||
 | 
					  - key:             modernize-make-unique.MakeSmartPtrFunction
 | 
				
			||||||
 | 
					    value:           'make_unique'
 | 
				
			||||||
 | 
					  - key:             modernize-make-unique.MakeSmartPtrFunctionHeader
 | 
				
			||||||
 | 
					    value:           'esphome/core/helpers.h'
 | 
				
			||||||
 | 
					  - key:             readability-braces-around-statements.ShortStatementLines
 | 
				
			||||||
 | 
					    value:           2
 | 
				
			||||||
  - key:             readability-identifier-naming.LocalVariableCase
 | 
					  - key:             readability-identifier-naming.LocalVariableCase
 | 
				
			||||||
    value:           'lower_case'
 | 
					    value:           'lower_case'
 | 
				
			||||||
  - key:             readability-identifier-naming.ClassCase
 | 
					  - key:             readability-identifier-naming.ClassCase
 | 
				
			||||||
@@ -134,15 +123,19 @@ CheckOptions:
 | 
				
			|||||||
  - key:             readability-identifier-naming.StaticConstantCase
 | 
					  - key:             readability-identifier-naming.StaticConstantCase
 | 
				
			||||||
    value:           'UPPER_CASE'
 | 
					    value:           'UPPER_CASE'
 | 
				
			||||||
  - key:             readability-identifier-naming.StaticVariableCase
 | 
					  - key:             readability-identifier-naming.StaticVariableCase
 | 
				
			||||||
    value:           'UPPER_CASE'
 | 
					    value:           'lower_case'
 | 
				
			||||||
  - key:             readability-identifier-naming.GlobalConstantCase
 | 
					  - key:             readability-identifier-naming.GlobalConstantCase
 | 
				
			||||||
    value:           'UPPER_CASE'
 | 
					    value:           'UPPER_CASE'
 | 
				
			||||||
  - key:             readability-identifier-naming.ParameterCase
 | 
					  - key:             readability-identifier-naming.ParameterCase
 | 
				
			||||||
    value:           'lower_case'
 | 
					    value:           'lower_case'
 | 
				
			||||||
  - key:             readability-identifier-naming.PrivateMemberPrefix
 | 
					  - key:             readability-identifier-naming.PrivateMemberCase
 | 
				
			||||||
    value:           'NO_PRIVATE_MEMBERS_ALWAYS_USE_PROTECTED'
 | 
					    value:           'lower_case'
 | 
				
			||||||
  - key:             readability-identifier-naming.PrivateMethodPrefix
 | 
					  - key:             readability-identifier-naming.PrivateMemberSuffix
 | 
				
			||||||
    value:           'NO_PRIVATE_METHODS_ALWAYS_USE_PROTECTED'
 | 
					    value:           '_'
 | 
				
			||||||
 | 
					  - key:             readability-identifier-naming.PrivateMethodCase
 | 
				
			||||||
 | 
					    value:           'lower_case'
 | 
				
			||||||
 | 
					  - key:             readability-identifier-naming.PrivateMethodSuffix
 | 
				
			||||||
 | 
					    value:           '_'
 | 
				
			||||||
  - key:             readability-identifier-naming.ClassMemberCase
 | 
					  - key:             readability-identifier-naming.ClassMemberCase
 | 
				
			||||||
    value:           'lower_case'
 | 
					    value:           'lower_case'
 | 
				
			||||||
  - key:             readability-identifier-naming.ClassMemberCase
 | 
					  - key:             readability-identifier-naming.ClassMemberCase
 | 
				
			||||||
@@ -163,3 +156,5 @@ CheckOptions:
 | 
				
			|||||||
    value:           'lower_case'
 | 
					    value:           'lower_case'
 | 
				
			||||||
  - key:             readability-identifier-naming.VirtualMethodSuffix
 | 
					  - key:             readability-identifier-naming.VirtualMethodSuffix
 | 
				
			||||||
    value:           ''
 | 
					    value:           ''
 | 
				
			||||||
 | 
					  - key:             readability-qualified-auto.AddConstToQualified
 | 
				
			||||||
 | 
					    value:           0
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,29 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "ESPHome Dev",
 | 
					  "name": "ESPHome Dev",
 | 
				
			||||||
  "context": "..",
 | 
					  "image": "esphome/esphome-lint:dev",
 | 
				
			||||||
  "dockerFile": "../docker/Dockerfile.dev",
 | 
					  "postCreateCommand": [
 | 
				
			||||||
  "postCreateCommand": "mkdir -p config && pip3 install -e .",
 | 
					    "script/devcontainer-post-create"
 | 
				
			||||||
  "runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"],
 | 
					  ],
 | 
				
			||||||
 | 
					  "runArgs": [
 | 
				
			||||||
 | 
					    "--privileged",
 | 
				
			||||||
 | 
					    "-e",
 | 
				
			||||||
 | 
					    "ESPHOME_DASHBOARD_USE_PING=1"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  "appPort": 6052,
 | 
					  "appPort": 6052,
 | 
				
			||||||
  "extensions": [
 | 
					  "extensions": [
 | 
				
			||||||
 | 
					    // python
 | 
				
			||||||
    "ms-python.python",
 | 
					    "ms-python.python",
 | 
				
			||||||
    "visualstudioexptteam.vscodeintellicode",
 | 
					    "visualstudioexptteam.vscodeintellicode",
 | 
				
			||||||
    "redhat.vscode-yaml"
 | 
					    // yaml
 | 
				
			||||||
 | 
					    "redhat.vscode-yaml",
 | 
				
			||||||
 | 
					    // cpp
 | 
				
			||||||
 | 
					    "ms-vscode.cpptools",
 | 
				
			||||||
 | 
					    // editorconfig
 | 
				
			||||||
 | 
					    "editorconfig.editorconfig",
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "settings": {
 | 
					  "settings": {
 | 
				
			||||||
    "python.pythonPath": "/usr/local/bin/python",
 | 
					    "python.languageServer": "Pylance",
 | 
				
			||||||
 | 
					    "python.pythonPath": "/usr/bin/python3",
 | 
				
			||||||
    "python.linting.pylintEnabled": true,
 | 
					    "python.linting.pylintEnabled": true,
 | 
				
			||||||
    "python.linting.enabled": true,
 | 
					    "python.linting.enabled": true,
 | 
				
			||||||
    "python.formatting.provider": "black",
 | 
					    "python.formatting.provider": "black",
 | 
				
			||||||
@@ -19,7 +31,7 @@
 | 
				
			|||||||
    "editor.formatOnSave": true,
 | 
					    "editor.formatOnSave": true,
 | 
				
			||||||
    "editor.formatOnType": true,
 | 
					    "editor.formatOnType": true,
 | 
				
			||||||
    "files.trimTrailingWhitespace": true,
 | 
					    "files.trimTrailingWhitespace": true,
 | 
				
			||||||
    "terminal.integrated.shell.linux": "/bin/bash",
 | 
					    "terminal.integrated.defaultProfile.linux": "bash",
 | 
				
			||||||
    "yaml.customTags": [
 | 
					    "yaml.customTags": [
 | 
				
			||||||
      "!secret scalar",
 | 
					      "!secret scalar",
 | 
				
			||||||
      "!lambda scalar",
 | 
					      "!lambda scalar",
 | 
				
			||||||
@@ -27,6 +39,18 @@
 | 
				
			|||||||
      "!include_dir_list scalar",
 | 
					      "!include_dir_list scalar",
 | 
				
			||||||
      "!include_dir_merge_list scalar",
 | 
					      "!include_dir_merge_list scalar",
 | 
				
			||||||
      "!include_dir_merge_named scalar"
 | 
					      "!include_dir_merge_named scalar"
 | 
				
			||||||
    ]
 | 
					    ],
 | 
				
			||||||
 | 
					    "files.exclude": {
 | 
				
			||||||
 | 
					      "**/.git": true,
 | 
				
			||||||
 | 
					      "**/.DS_Store": true,
 | 
				
			||||||
 | 
					      "**/*.pyc": {
 | 
				
			||||||
 | 
					        "when": "$(basename).py"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "**/__pycache__": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "files.associations": {
 | 
				
			||||||
 | 
					      "**/.vscode/*.json": "jsonc"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "C_Cpp.clang_format_path": "/usr/bin/clang-format-11",
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -103,6 +103,10 @@ venv.bak/
 | 
				
			|||||||
# mypy
 | 
					# mypy
 | 
				
			||||||
.mypy_cache/
 | 
					.mypy_cache/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PlatformIO
 | 
				
			||||||
 | 
					.pio/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ESPHome
 | 
				
			||||||
config/
 | 
					config/
 | 
				
			||||||
examples/
 | 
					examples/
 | 
				
			||||||
Dockerfile
 | 
					Dockerfile
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ insert_final_newline = true
 | 
				
			|||||||
charset = utf-8
 | 
					charset = utf-8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# python
 | 
					# python
 | 
				
			||||||
[*.{py}]
 | 
					[*.py]
 | 
				
			||||||
indent_style = space
 | 
					indent_style = space
 | 
				
			||||||
indent_size = 4
 | 
					indent_size = 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -25,4 +25,10 @@ indent_size = 2
 | 
				
			|||||||
[*.{yaml,yml}]
 | 
					[*.{yaml,yml}]
 | 
				
			||||||
indent_style = space
 | 
					indent_style = space
 | 
				
			||||||
indent_size = 2
 | 
					indent_size = 2
 | 
				
			||||||
quote_type = single
 | 
					quote_type = single
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# JSON
 | 
				
			||||||
 | 
					[*.json]
 | 
				
			||||||
 | 
					indent_style = space
 | 
				
			||||||
 | 
					indent_size = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					# Normalize line endings to LF in the repository
 | 
				
			||||||
 | 
					* text eol=lf
 | 
				
			||||||
							
								
								
									
										1
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							@@ -9,4 +9,3 @@ contact_links:
 | 
				
			|||||||
  - name: Frequently Asked Question
 | 
					  - name: Frequently Asked Question
 | 
				
			||||||
    url: https://esphome.io/guides/faq.html
 | 
					    url: https://esphome.io/guides/faq.html
 | 
				
			||||||
    about: Please view the FAQ for common questions and what to include in a bug report.
 | 
					    about: Please view the FAQ for common questions and what to include in a bug report.
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@@ -1,4 +1,4 @@
 | 
				
			|||||||
# What does this implement/fix? 
 | 
					# What does this implement/fix?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Quick description and explanation of changes
 | 
					Quick description and explanation of changes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,6 +16,7 @@ Quick description and explanation of changes
 | 
				
			|||||||
## Test Environment
 | 
					## Test Environment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- [ ] ESP32
 | 
					- [ ] ESP32
 | 
				
			||||||
 | 
					- [ ] ESP32 IDF
 | 
				
			||||||
- [ ] ESP8266
 | 
					- [ ] ESP8266
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Example entry for `config.yaml`:
 | 
					## Example entry for `config.yaml`:
 | 
				
			||||||
@@ -34,6 +35,6 @@ Quick description and explanation of changes
 | 
				
			|||||||
## Checklist:
 | 
					## Checklist:
 | 
				
			||||||
  - [ ] The code change is tested and works locally.
 | 
					  - [ ] The code change is tested and works locally.
 | 
				
			||||||
  - [ ] Tests have been added to verify that the new code works (under `tests/` folder).
 | 
					  - [ ] Tests have been added to verify that the new code works (under `tests/` folder).
 | 
				
			||||||
  
 | 
					
 | 
				
			||||||
If user exposed functionality or configuration variables are added/changed:
 | 
					If user exposed functionality or configuration variables are added/changed:
 | 
				
			||||||
  - [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs).
 | 
					  - [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs).
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								.github/issue-close-app.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/issue-close-app.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +0,0 @@
 | 
				
			|||||||
comment: >-
 | 
					 | 
				
			||||||
  https://github.com/esphome/esphome/issues/430
 | 
					 | 
				
			||||||
issueConfigs:
 | 
					 | 
				
			||||||
- content:
 | 
					 | 
				
			||||||
  - "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
caseInsensitive: false
 | 
					 | 
				
			||||||
							
								
								
									
										36
									
								
								.github/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								.github/lock.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,36 +0,0 @@
 | 
				
			|||||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Number of days of inactivity before a closed issue or pull request is locked
 | 
					 | 
				
			||||||
daysUntilLock: 7
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
 | 
					 | 
				
			||||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
 | 
					 | 
				
			||||||
skipCreatedBefore: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
 | 
					 | 
				
			||||||
exemptLabels:
 | 
					 | 
				
			||||||
  - keep-open
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
 | 
					 | 
				
			||||||
lockLabel: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Comment to post before locking. Set to `false` to disable
 | 
					 | 
				
			||||||
lockComment: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
 | 
					 | 
				
			||||||
setLockReason: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Limit to only `issues` or `pulls`
 | 
					 | 
				
			||||||
# only: issues
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Optionally, specify configuration settings just for `issues` or `pulls`
 | 
					 | 
				
			||||||
# issues:
 | 
					 | 
				
			||||||
#   exemptLabels:
 | 
					 | 
				
			||||||
#     - help-wanted
 | 
					 | 
				
			||||||
#   lockLabel: outdated
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# pulls:
 | 
					 | 
				
			||||||
#   daysUntilLock: 30
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Repository to extend settings from
 | 
					 | 
				
			||||||
# _extends: repo
 | 
					 | 
				
			||||||
							
								
								
									
										59
									
								
								.github/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										59
									
								
								.github/stale.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,59 +0,0 @@
 | 
				
			|||||||
# Configuration for probot-stale - https://github.com/probot/stale
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
 | 
					 | 
				
			||||||
daysUntilStale: 60
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
 | 
					 | 
				
			||||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
 | 
					 | 
				
			||||||
daysUntilClose: 7
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
 | 
					 | 
				
			||||||
onlyLabels: []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
 | 
					 | 
				
			||||||
exemptLabels:
 | 
					 | 
				
			||||||
  - not-stale
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Set to true to ignore issues in a project (defaults to false)
 | 
					 | 
				
			||||||
exemptProjects: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Set to true to ignore issues in a milestone (defaults to false)
 | 
					 | 
				
			||||||
exemptMilestones: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Set to true to ignore issues with an assignee (defaults to false)
 | 
					 | 
				
			||||||
exemptAssignees: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Label to use when marking as stale
 | 
					 | 
				
			||||||
staleLabel: stale
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Comment to post when marking as stale. Set to `false` to disable
 | 
					 | 
				
			||||||
markComment: >
 | 
					 | 
				
			||||||
  This issue has been automatically marked as stale because it has not had
 | 
					 | 
				
			||||||
  recent activity. It will be closed if no further activity occurs. Thank you
 | 
					 | 
				
			||||||
  for your contributions.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Comment to post when removing the stale label.
 | 
					 | 
				
			||||||
# unmarkComment: >
 | 
					 | 
				
			||||||
#   Your comment here.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Comment to post when closing a stale Issue or Pull Request.
 | 
					 | 
				
			||||||
# closeComment: >
 | 
					 | 
				
			||||||
#   Your comment here.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Limit the number of actions per hour, from 1-30. Default is 30
 | 
					 | 
				
			||||||
limitPerRun: 10
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Limit to only `issues` or `pulls`
 | 
					 | 
				
			||||||
only: pulls
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
 | 
					 | 
				
			||||||
# pulls:
 | 
					 | 
				
			||||||
#   daysUntilStale: 30
 | 
					 | 
				
			||||||
#   markComment: >
 | 
					 | 
				
			||||||
#     This pull request has been automatically marked as stale because it has not had
 | 
					 | 
				
			||||||
#     recent activity. It will be closed if no further activity occurs. Thank you
 | 
					 | 
				
			||||||
#     for your contributions.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# issues:
 | 
					 | 
				
			||||||
#   exemptLabels:
 | 
					 | 
				
			||||||
#     - confirmed
 | 
					 | 
				
			||||||
							
								
								
									
										60
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										60
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -3,53 +3,51 @@ name: CI for docker images
 | 
				
			|||||||
# Only run when docker paths change
 | 
					# Only run when docker paths change
 | 
				
			||||||
on:
 | 
					on:
 | 
				
			||||||
  push:
 | 
					  push:
 | 
				
			||||||
    branches: [dev, beta, master]
 | 
					    branches: [dev, beta, release]
 | 
				
			||||||
    paths:
 | 
					    paths:
 | 
				
			||||||
      - 'docker/**'
 | 
					      - 'docker/**'
 | 
				
			||||||
      - '.github/workflows/**'
 | 
					      - '.github/workflows/**'
 | 
				
			||||||
 | 
					      - 'requirements*.txt'
 | 
				
			||||||
 | 
					      - 'platformio.ini'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
    paths:
 | 
					    paths:
 | 
				
			||||||
      - 'docker/**'
 | 
					      - 'docker/**'
 | 
				
			||||||
      - '.github/workflows/**'
 | 
					      - '.github/workflows/**'
 | 
				
			||||||
 | 
					      - 'requirements*.txt'
 | 
				
			||||||
 | 
					      - 'platformio.ini'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					permissions:
 | 
				
			||||||
 | 
					  contents: read
 | 
				
			||||||
 | 
					  packages: read
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  check-docker:
 | 
					  check-docker:
 | 
				
			||||||
    name: Build docker containers
 | 
					    name: Build docker containers
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    strategy:
 | 
					    strategy:
 | 
				
			||||||
      fail-fast: false
 | 
					 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        arch: [amd64, armv7, aarch64]
 | 
					        arch: [amd64, armv7, aarch64]
 | 
				
			||||||
        build_type: ["hassio", "docker"]
 | 
					        build_type: ["ha-addon", "docker", "lint"]
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					    - uses: actions/checkout@v2
 | 
				
			||||||
      - name: Set up env variables
 | 
					    - name: Set up Python
 | 
				
			||||||
        run: |
 | 
					      uses: actions/setup-python@v2
 | 
				
			||||||
          base_version="3.4.0"
 | 
					      with:
 | 
				
			||||||
 | 
					        python-version: '3.9'
 | 
				
			||||||
 | 
					    - name: Set up Docker Buildx
 | 
				
			||||||
 | 
					      uses: docker/setup-buildx-action@v1
 | 
				
			||||||
 | 
					    - name: Set up QEMU
 | 
				
			||||||
 | 
					      uses: docker/setup-qemu-action@v1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
					    - name: Set TAG
 | 
				
			||||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
					      run: |
 | 
				
			||||||
            build_to="esphome/esphome-hassio-${{ matrix.arch }}"
 | 
					        echo "TAG=check" >> $GITHUB_ENV
 | 
				
			||||||
            dockerfile="docker/Dockerfile.hassio"
 | 
					 | 
				
			||||||
          else
 | 
					 | 
				
			||||||
            build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
 | 
					 | 
				
			||||||
            build_to="esphome/esphome-${{ matrix.arch }}"
 | 
					 | 
				
			||||||
            dockerfile="docker/Dockerfile"
 | 
					 | 
				
			||||||
          fi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
 | 
					    - name: Run build
 | 
				
			||||||
          echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
 | 
					      run: |
 | 
				
			||||||
          echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
 | 
					        docker/build.py \
 | 
				
			||||||
      - name: Pull for cache
 | 
					          --tag "${TAG}" \
 | 
				
			||||||
        run: |
 | 
					          --arch "${{ matrix.arch }}" \
 | 
				
			||||||
          docker pull "${BUILD_TO}:dev" || true
 | 
					          --build-type "${{ matrix.build_type }}" \
 | 
				
			||||||
      - name: Register QEMU binfmt
 | 
					          build
 | 
				
			||||||
        run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
 | 
					 | 
				
			||||||
      - run: |
 | 
					 | 
				
			||||||
          docker build \
 | 
					 | 
				
			||||||
            --build-arg "BUILD_FROM=${BUILD_FROM}" \
 | 
					 | 
				
			||||||
            --build-arg "BUILD_VERSION=ci" \
 | 
					 | 
				
			||||||
            --cache-from "${BUILD_TO}:dev" \
 | 
					 | 
				
			||||||
            --file "${DOCKERFILE}" \
 | 
					 | 
				
			||||||
            .
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										261
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										261
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,161 +1,166 @@
 | 
				
			|||||||
# THESE JOBS ARE COPIED IN release.yml and release-dev.yml
 | 
					 | 
				
			||||||
# PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE
 | 
					 | 
				
			||||||
name: CI
 | 
					name: CI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
on:
 | 
					on:
 | 
				
			||||||
  push:
 | 
					  push:
 | 
				
			||||||
    # On dev branch release-dev already performs CI checks
 | 
					    branches: [dev, beta, release]
 | 
				
			||||||
    # On other branches the `pull_request` trigger will be used
 | 
					 | 
				
			||||||
    branches: [beta, master]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					permissions:
 | 
				
			||||||
 | 
					  contents: read
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					concurrency:
 | 
				
			||||||
 | 
					  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
 | 
				
			||||||
 | 
					  cancel-in-progress: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  lint-clang-format:
 | 
					  ci:
 | 
				
			||||||
 | 
					    name: ${{ matrix.name }}
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
					 | 
				
			||||||
    # doesn't have to be installed
 | 
					 | 
				
			||||||
    container: esphome/esphome-lint:1.1
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					 | 
				
			||||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
					 | 
				
			||||||
      # (build flags, libraries etc)
 | 
					 | 
				
			||||||
      - name: Set up platformio environment
 | 
					 | 
				
			||||||
        run: pio init --ide atom
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Run clang-format
 | 
					 | 
				
			||||||
        run: script/clang-format -i
 | 
					 | 
				
			||||||
      - name: Suggest changes
 | 
					 | 
				
			||||||
        run: script/ci-suggest-changes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lint-clang-tidy:
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
					 | 
				
			||||||
    # doesn't have to be installed
 | 
					 | 
				
			||||||
    container: esphome/esphome-lint:1.1
 | 
					 | 
				
			||||||
    # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
 | 
					 | 
				
			||||||
    strategy:
 | 
					    strategy:
 | 
				
			||||||
      fail-fast: false
 | 
					      fail-fast: false
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        split: [1, 2, 3, 4]
 | 
					        include:
 | 
				
			||||||
    steps:
 | 
					          - id: ci-custom
 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					            name: Run script/ci-custom
 | 
				
			||||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
					          - id: lint-python
 | 
				
			||||||
      # (build flags, libraries etc)
 | 
					            name: Run script/lint-python
 | 
				
			||||||
      - name: Set up platformio environment
 | 
					          - id: test
 | 
				
			||||||
        run: pio init --ide atom
 | 
					            file: tests/test1.yaml
 | 
				
			||||||
 | 
					            name: Test tests/test1.yaml
 | 
				
			||||||
 | 
					            pio_cache_key: test1
 | 
				
			||||||
 | 
					          - id: test
 | 
				
			||||||
 | 
					            file: tests/test2.yaml
 | 
				
			||||||
 | 
					            name: Test tests/test2.yaml
 | 
				
			||||||
 | 
					            pio_cache_key: test2
 | 
				
			||||||
 | 
					          - id: test
 | 
				
			||||||
 | 
					            file: tests/test3.yaml
 | 
				
			||||||
 | 
					            name: Test tests/test3.yaml
 | 
				
			||||||
 | 
					            pio_cache_key: test3
 | 
				
			||||||
 | 
					          - id: test
 | 
				
			||||||
 | 
					            file: tests/test4.yaml
 | 
				
			||||||
 | 
					            name: Test tests/test4.yaml
 | 
				
			||||||
 | 
					            pio_cache_key: test4
 | 
				
			||||||
 | 
					          - id: test
 | 
				
			||||||
 | 
					            file: tests/test5.yaml
 | 
				
			||||||
 | 
					            name: Test tests/test5.yaml
 | 
				
			||||||
 | 
					            pio_cache_key: test5
 | 
				
			||||||
 | 
					          - id: pytest
 | 
				
			||||||
 | 
					            name: Run pytest
 | 
				
			||||||
 | 
					          - id: clang-format
 | 
				
			||||||
 | 
					            name: Run script/clang-format
 | 
				
			||||||
 | 
					          - id: clang-tidy
 | 
				
			||||||
 | 
					            name: Run script/clang-tidy for ESP8266
 | 
				
			||||||
 | 
					            options: --environment esp8266-arduino-tidy --grep USE_ESP8266
 | 
				
			||||||
 | 
					            pio_cache_key: tidyesp8266
 | 
				
			||||||
 | 
					          - id: clang-tidy
 | 
				
			||||||
 | 
					            name: Run script/clang-tidy for ESP32 Arduino 1/4
 | 
				
			||||||
 | 
					            options: --environment esp32-arduino-tidy --split-num 4 --split-at 1
 | 
				
			||||||
 | 
					            pio_cache_key: tidyesp32
 | 
				
			||||||
 | 
					          - id: clang-tidy
 | 
				
			||||||
 | 
					            name: Run script/clang-tidy for ESP32 Arduino 2/4
 | 
				
			||||||
 | 
					            options: --environment esp32-arduino-tidy --split-num 4 --split-at 2
 | 
				
			||||||
 | 
					            pio_cache_key: tidyesp32
 | 
				
			||||||
 | 
					          - id: clang-tidy
 | 
				
			||||||
 | 
					            name: Run script/clang-tidy for ESP32 Arduino 3/4
 | 
				
			||||||
 | 
					            options: --environment esp32-arduino-tidy --split-num 4 --split-at 3
 | 
				
			||||||
 | 
					            pio_cache_key: tidyesp32
 | 
				
			||||||
 | 
					          - id: clang-tidy
 | 
				
			||||||
 | 
					            name: Run script/clang-tidy for ESP32 Arduino 4/4
 | 
				
			||||||
 | 
					            options: --environment esp32-arduino-tidy --split-num 4 --split-at 4
 | 
				
			||||||
 | 
					            pio_cache_key: tidyesp32
 | 
				
			||||||
 | 
					          - id: clang-tidy
 | 
				
			||||||
 | 
					            name: Run script/clang-tidy for ESP32 IDF
 | 
				
			||||||
 | 
					            options: --environment esp32-idf-tidy --grep USE_ESP_IDF
 | 
				
			||||||
 | 
					            pio_cache_key: tidyesp32-idf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Register problem matchers
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
					 | 
				
			||||||
      - name: Run clang-tidy
 | 
					 | 
				
			||||||
        run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
 | 
					 | 
				
			||||||
      - name: Suggest changes
 | 
					 | 
				
			||||||
        run: script/ci-suggest-changes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lint-python:
 | 
					 | 
				
			||||||
    # Don't use the esphome-lint docker image because it may contain outdated requirements.
 | 
					 | 
				
			||||||
    # This way, all dependencies are cached via the cache action.
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					      - uses: actions/checkout@v2
 | 
				
			||||||
      - name: Set up Python
 | 
					      - name: Set up Python
 | 
				
			||||||
        uses: actions/setup-python@v2
 | 
					        uses: actions/setup-python@v2
 | 
				
			||||||
 | 
					        id: python
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          python-version: '3.7'
 | 
					          python-version: '3.8'
 | 
				
			||||||
      - name: Cache pip modules
 | 
					
 | 
				
			||||||
        uses: actions/cache@v1
 | 
					      - name: Cache virtualenv
 | 
				
			||||||
 | 
					        uses: actions/cache@v2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          path: ~/.cache/pip
 | 
					          path: .venv
 | 
				
			||||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
					          key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
 | 
				
			||||||
          restore-keys: |
 | 
					          restore-keys: |
 | 
				
			||||||
            esphome-pip-3.7-
 | 
					            venv-${{ steps.python.outputs.python-version }}-
 | 
				
			||||||
      - name: Set up python environment
 | 
					
 | 
				
			||||||
        run: script/setup
 | 
					      - name: Set up virtualenv
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          python -m venv .venv
 | 
				
			||||||
 | 
					          source .venv/bin/activate
 | 
				
			||||||
 | 
					          pip install -U pip
 | 
				
			||||||
 | 
					          pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
 | 
				
			||||||
 | 
					          pip install -e .
 | 
				
			||||||
 | 
					          echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH
 | 
				
			||||||
 | 
					          echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      # Use per check platformio cache because checks use different parts
 | 
				
			||||||
 | 
					      - name: Cache platformio
 | 
				
			||||||
 | 
					        uses: actions/cache@v2
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          path: ~/.platformio
 | 
				
			||||||
 | 
					          key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
 | 
				
			||||||
 | 
					        if: matrix.id == 'test' || matrix.id == 'clang-tidy'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Install clang tools
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          sudo apt-get install \
 | 
				
			||||||
 | 
					              clang-format-11 \
 | 
				
			||||||
 | 
					              clang-tidy-11
 | 
				
			||||||
 | 
					        if: matrix.id == 'clang-tidy' || matrix.id == 'clang-format'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Register problem matchers
 | 
					      - name: Register problem matchers
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
 | 
					          echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/lint-python.json"
 | 
					          echo "::add-matcher::.github/workflows/matchers/lint-python.json"
 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
					          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
				
			||||||
 | 
					          echo "::add-matcher::.github/workflows/matchers/pytest.json"
 | 
				
			||||||
 | 
					          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
				
			||||||
 | 
					          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Lint Custom
 | 
					      - name: Lint Custom
 | 
				
			||||||
        run: script/ci-custom.py
 | 
					        run: |
 | 
				
			||||||
 | 
					          script/ci-custom.py
 | 
				
			||||||
 | 
					          script/build_codeowners.py --check
 | 
				
			||||||
 | 
					        if: matrix.id == 'ci-custom'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Lint Python
 | 
					      - name: Lint Python
 | 
				
			||||||
        run: script/lint-python
 | 
					        run: script/lint-python
 | 
				
			||||||
      - name: Lint CODEOWNERS
 | 
					        if: matrix.id == 'lint-python'
 | 
				
			||||||
        run: script/build_codeowners.py --check
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test:
 | 
					      - run: esphome compile ${{ matrix.file }}
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					        if: matrix.id == 'test'
 | 
				
			||||||
    strategy:
 | 
					        env:
 | 
				
			||||||
      fail-fast: false
 | 
					          # Also cache libdeps, store them in a ~/.platformio subfolder
 | 
				
			||||||
      matrix:
 | 
					          PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
 | 
				
			||||||
          test:
 | 
					 | 
				
			||||||
          - test1
 | 
					 | 
				
			||||||
          - test2
 | 
					 | 
				
			||||||
          - test3
 | 
					 | 
				
			||||||
          - test4
 | 
					 | 
				
			||||||
          - test5
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					 | 
				
			||||||
      - name: Set up Python
 | 
					 | 
				
			||||||
        uses: actions/setup-python@v2
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          python-version: '3.7'
 | 
					 | 
				
			||||||
      - name: Cache pip modules
 | 
					 | 
				
			||||||
        uses: actions/cache@v1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          path: ~/.cache/pip
 | 
					 | 
				
			||||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
					 | 
				
			||||||
          restore-keys: |
 | 
					 | 
				
			||||||
            esphome-pip-3.7-
 | 
					 | 
				
			||||||
      # Use per test platformio cache because tests have different platform versions
 | 
					 | 
				
			||||||
      - name: Cache ~/.platformio
 | 
					 | 
				
			||||||
        uses: actions/cache@v1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          path: ~/.platformio
 | 
					 | 
				
			||||||
          key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
 | 
					 | 
				
			||||||
          restore-keys: |
 | 
					 | 
				
			||||||
            test-home-platformio-${{ matrix.test }}-
 | 
					 | 
				
			||||||
      - name: Set up environment
 | 
					 | 
				
			||||||
        run: script/setup
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Register problem matchers
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
					 | 
				
			||||||
      - run: esphome compile tests/${{ matrix.test }}.yaml
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  pytest:
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					 | 
				
			||||||
      - name: Set up Python
 | 
					 | 
				
			||||||
        uses: actions/setup-python@v2
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          python-version: '3.7'
 | 
					 | 
				
			||||||
      - name: Cache pip modules
 | 
					 | 
				
			||||||
        uses: actions/cache@v1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          path: ~/.cache/pip
 | 
					 | 
				
			||||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
					 | 
				
			||||||
          restore-keys: |
 | 
					 | 
				
			||||||
            esphome-pip-3.7-
 | 
					 | 
				
			||||||
      - name: Set up environment
 | 
					 | 
				
			||||||
        run: script/setup
 | 
					 | 
				
			||||||
      - name: Install Github Actions annotator
 | 
					 | 
				
			||||||
        run: pip install pytest-github-actions-annotate-failures
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Register problem matchers
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
					 | 
				
			||||||
      - name: Run pytest
 | 
					      - name: Run pytest
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          pytest \
 | 
					          pytest -vv --tb=native tests
 | 
				
			||||||
            -qq \
 | 
					        if: matrix.id == 'pytest'
 | 
				
			||||||
            --durations=10 \
 | 
					
 | 
				
			||||||
            -o console_output_style=count \
 | 
					      # Also run git-diff-index so that the step is marked as failed on formatting errors,
 | 
				
			||||||
            tests
 | 
					      # since clang-format doesn't do anything but change files if -i is passed.
 | 
				
			||||||
 | 
					      - name: Run clang-format
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          script/clang-format -i
 | 
				
			||||||
 | 
					          git diff-index --quiet HEAD --
 | 
				
			||||||
 | 
					        if: matrix.id == 'clang-format'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Run clang-tidy
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          script/clang-tidy --all-headers --fix ${{ matrix.options }}
 | 
				
			||||||
 | 
					        if: matrix.id == 'clang-tidy'
 | 
				
			||||||
 | 
					        env:
 | 
				
			||||||
 | 
					          # Also cache libdeps, store them in a ~/.platformio subfolder
 | 
				
			||||||
 | 
					          PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Suggested changes
 | 
				
			||||||
 | 
					        run: script/ci-suggest-changes
 | 
				
			||||||
 | 
					        if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format')
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										42
									
								
								.github/workflows/docker-lint-build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										42
									
								
								.github/workflows/docker-lint-build.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,42 +0,0 @@
 | 
				
			|||||||
name: Build and publish lint docker image
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Only run when docker paths change
 | 
					 | 
				
			||||||
on:
 | 
					 | 
				
			||||||
  push:
 | 
					 | 
				
			||||||
    branches: [dev]
 | 
					 | 
				
			||||||
    paths:
 | 
					 | 
				
			||||||
      - 'docker/Dockerfile.lint'
 | 
					 | 
				
			||||||
      - 'requirements.txt'
 | 
					 | 
				
			||||||
      - 'requirements_optional.txt'
 | 
					 | 
				
			||||||
      - 'requirements_test.txt'
 | 
					 | 
				
			||||||
      - 'platformio.ini'
 | 
					 | 
				
			||||||
      - '.github/workflows/docker-lint-build.yml'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
jobs:
 | 
					 | 
				
			||||||
  publish-docker-lint-iage:
 | 
					 | 
				
			||||||
    name: Build docker containers
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					 | 
				
			||||||
      - name: Set TAG
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          echo "TAG=1.1" >> $GITHUB_ENV
 | 
					 | 
				
			||||||
      - name: Pull for cache
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          docker pull "esphome/esphome-lint:latest" || true
 | 
					 | 
				
			||||||
      - name: Build
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          docker build \
 | 
					 | 
				
			||||||
            --cache-from "esphome/esphome-lint:latest" \
 | 
					 | 
				
			||||||
            --file "docker/Dockerfile.lint" \
 | 
					 | 
				
			||||||
            --tag "esphome/esphome-lint:latest" \
 | 
					 | 
				
			||||||
            --tag "esphome/esphome-lint:${TAG}" \
 | 
					 | 
				
			||||||
            .
 | 
					 | 
				
			||||||
      - name: Log in to docker hub
 | 
					 | 
				
			||||||
        env:
 | 
					 | 
				
			||||||
          DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
					 | 
				
			||||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
					 | 
				
			||||||
        run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
					 | 
				
			||||||
      - run: |
 | 
					 | 
				
			||||||
          docker push "esphome/esphome-lint:${TAG}"
 | 
					 | 
				
			||||||
          docker push "esphome/esphome-lint:latest"
 | 
					 | 
				
			||||||
							
								
								
									
										27
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					name: Lock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  schedule:
 | 
				
			||||||
 | 
					    - cron: '30 0 * * *'
 | 
				
			||||||
 | 
					  workflow_dispatch:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					permissions:
 | 
				
			||||||
 | 
					  issues: write
 | 
				
			||||||
 | 
					  pull-requests: write
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					concurrency:
 | 
				
			||||||
 | 
					  group: lock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  lock:
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - uses: dessant/lock-threads@v3
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          pr-inactive-days: "1"
 | 
				
			||||||
 | 
					          pr-lock-reason: ""
 | 
				
			||||||
 | 
					          exclude-any-pr-labels: keep-open
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          issue-inactive-days: "7"
 | 
				
			||||||
 | 
					          issue-lock-reason: ""
 | 
				
			||||||
 | 
					          exclude-any-issue-labels: keep-open
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/workflows/matchers/ci-custom.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/matchers/ci-custom.json
									
									
									
									
										vendored
									
									
								
							@@ -4,7 +4,7 @@
 | 
				
			|||||||
            "owner": "ci-custom",
 | 
					            "owner": "ci-custom",
 | 
				
			||||||
            "pattern": [
 | 
					            "pattern": [
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    "regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$",
 | 
					                    "regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$",
 | 
				
			||||||
                    "file": 1,
 | 
					                    "file": 1,
 | 
				
			||||||
                    "line": 2,
 | 
					                    "line": 2,
 | 
				
			||||||
                    "column": 3,
 | 
					                    "column": 3,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/matchers/gcc.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/matchers/gcc.json
									
									
									
									
										vendored
									
									
								
							@@ -5,7 +5,7 @@
 | 
				
			|||||||
      "severity": "error",
 | 
					      "severity": "error",
 | 
				
			||||||
      "pattern": [
 | 
					      "pattern": [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
 | 
					          "regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
 | 
				
			||||||
          "file": 1,
 | 
					          "file": 1,
 | 
				
			||||||
          "line": 2,
 | 
					          "line": 2,
 | 
				
			||||||
          "column": 3,
 | 
					          "column": 3,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										15
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							@@ -1,11 +1,22 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "problemMatcher": [
 | 
					  "problemMatcher": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "owner": "black",
 | 
				
			||||||
 | 
					      "severity": "error",
 | 
				
			||||||
 | 
					      "pattern": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "regexp": "^(.*): (Please format this file with the black formatter)",
 | 
				
			||||||
 | 
					          "file": 1,
 | 
				
			||||||
 | 
					          "message": 2
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "owner": "flake8",
 | 
					      "owner": "flake8",
 | 
				
			||||||
      "severity": "error",
 | 
					      "severity": "error",
 | 
				
			||||||
      "pattern": [
 | 
					      "pattern": [
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
          "regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$",
 | 
					          "regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$",
 | 
				
			||||||
          "file": 1,
 | 
					          "file": 1,
 | 
				
			||||||
          "line": 2,
 | 
					          "line": 2,
 | 
				
			||||||
          "message": 3
 | 
					          "message": 3
 | 
				
			||||||
@@ -17,7 +28,7 @@
 | 
				
			|||||||
      "severity": "error",
 | 
					      "severity": "error",
 | 
				
			||||||
      "pattern": [
 | 
					      "pattern": [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
 | 
					          "regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
 | 
				
			||||||
          "file": 1,
 | 
					          "file": 1,
 | 
				
			||||||
          "line": 2,
 | 
					          "line": 2,
 | 
				
			||||||
          "message": 3
 | 
					          "message": 3
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								.github/workflows/matchers/pytest.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/matchers/pytest.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "problemMatcher": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "owner": "pytest",
 | 
				
			||||||
 | 
					      "fileLocation": "absolute",
 | 
				
			||||||
 | 
					      "pattern": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "regexp": "^\\s+File \"(.*)\", line (\\d+), in (.*)$",
 | 
				
			||||||
 | 
					          "file": 1,
 | 
				
			||||||
 | 
					          "line": 2
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "regexp": "^\\s+(.*)$",
 | 
				
			||||||
 | 
					          "message": 1
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										247
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										247
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,247 +0,0 @@
 | 
				
			|||||||
name: Publish dev releases to docker hub
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
on:
 | 
					 | 
				
			||||||
  push:
 | 
					 | 
				
			||||||
    branches:
 | 
					 | 
				
			||||||
    - dev
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
jobs:
 | 
					 | 
				
			||||||
  # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lint-clang-format:
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
					 | 
				
			||||||
    # doesn't have to be installed
 | 
					 | 
				
			||||||
    container: esphome/esphome-lint:1.1
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					 | 
				
			||||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
					 | 
				
			||||||
      # (build flags, libraries etc)
 | 
					 | 
				
			||||||
      - name: Set up platformio environment
 | 
					 | 
				
			||||||
        run: pio init --ide atom
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Run clang-format
 | 
					 | 
				
			||||||
        run: script/clang-format -i
 | 
					 | 
				
			||||||
      - name: Suggest changes
 | 
					 | 
				
			||||||
        run: script/ci-suggest-changes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lint-clang-tidy:
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
					 | 
				
			||||||
    # doesn't have to be installed
 | 
					 | 
				
			||||||
    container: esphome/esphome-lint:1.1
 | 
					 | 
				
			||||||
    # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
 | 
					 | 
				
			||||||
    strategy:
 | 
					 | 
				
			||||||
      fail-fast: false
 | 
					 | 
				
			||||||
      matrix:
 | 
					 | 
				
			||||||
        split: [1, 2, 3, 4]
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					 | 
				
			||||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
					 | 
				
			||||||
      # (build flags, libraries etc)
 | 
					 | 
				
			||||||
      - name: Set up platformio environment
 | 
					 | 
				
			||||||
        run: pio init --ide atom
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Register problem matchers
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
					 | 
				
			||||||
      - name: Run clang-tidy
 | 
					 | 
				
			||||||
        run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
 | 
					 | 
				
			||||||
      - name: Suggest changes
 | 
					 | 
				
			||||||
        run: script/ci-suggest-changes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lint-python:
 | 
					 | 
				
			||||||
    # Don't use the esphome-lint docker image because it may contain outdated requirements.
 | 
					 | 
				
			||||||
    # This way, all dependencies are cached via the cache action.
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					 | 
				
			||||||
      - name: Set up Python
 | 
					 | 
				
			||||||
        uses: actions/setup-python@v2
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          python-version: '3.7'
 | 
					 | 
				
			||||||
      - name: Cache pip modules
 | 
					 | 
				
			||||||
        uses: actions/cache@v1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          path: ~/.cache/pip
 | 
					 | 
				
			||||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
					 | 
				
			||||||
          restore-keys: |
 | 
					 | 
				
			||||||
            esphome-pip-3.7-
 | 
					 | 
				
			||||||
      - name: Set up python environment
 | 
					 | 
				
			||||||
        run: script/setup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Register problem matchers
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/lint-python.json"
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
					 | 
				
			||||||
      - name: Lint Custom
 | 
					 | 
				
			||||||
        run: script/ci-custom.py
 | 
					 | 
				
			||||||
      - name: Lint Python
 | 
					 | 
				
			||||||
        run: script/lint-python
 | 
					 | 
				
			||||||
      - name: Lint CODEOWNERS
 | 
					 | 
				
			||||||
        run: script/build_codeowners.py --check
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  test:
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    strategy:
 | 
					 | 
				
			||||||
      fail-fast: false
 | 
					 | 
				
			||||||
      matrix:
 | 
					 | 
				
			||||||
          test:
 | 
					 | 
				
			||||||
          - test1
 | 
					 | 
				
			||||||
          - test2
 | 
					 | 
				
			||||||
          - test3
 | 
					 | 
				
			||||||
          - test4
 | 
					 | 
				
			||||||
          - test5
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					 | 
				
			||||||
      - name: Set up Python
 | 
					 | 
				
			||||||
        uses: actions/setup-python@v2
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          python-version: '3.7'
 | 
					 | 
				
			||||||
      - name: Cache pip modules
 | 
					 | 
				
			||||||
        uses: actions/cache@v1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          path: ~/.cache/pip
 | 
					 | 
				
			||||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
					 | 
				
			||||||
          restore-keys: |
 | 
					 | 
				
			||||||
            esphome-pip-3.7-
 | 
					 | 
				
			||||||
      # Use per test platformio cache because tests have different platform versions
 | 
					 | 
				
			||||||
      - name: Cache ~/.platformio
 | 
					 | 
				
			||||||
        uses: actions/cache@v1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          path: ~/.platformio
 | 
					 | 
				
			||||||
          key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
 | 
					 | 
				
			||||||
          restore-keys: |
 | 
					 | 
				
			||||||
            test-home-platformio-${{ matrix.test }}-
 | 
					 | 
				
			||||||
      - name: Set up environment
 | 
					 | 
				
			||||||
        run: script/setup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Register problem matchers
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
					 | 
				
			||||||
      - run: esphome compile tests/${{ matrix.test }}.yaml
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  pytest:
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					 | 
				
			||||||
      - name: Set up Python
 | 
					 | 
				
			||||||
        uses: actions/setup-python@v2
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          python-version: '3.7'
 | 
					 | 
				
			||||||
      - name: Cache pip modules
 | 
					 | 
				
			||||||
        uses: actions/cache@v1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          path: ~/.cache/pip
 | 
					 | 
				
			||||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
					 | 
				
			||||||
          restore-keys: |
 | 
					 | 
				
			||||||
            esphome-pip-3.7-
 | 
					 | 
				
			||||||
      - name: Set up environment
 | 
					 | 
				
			||||||
        run: script/setup
 | 
					 | 
				
			||||||
      - name: Install Github Actions annotator
 | 
					 | 
				
			||||||
        run: pip install pytest-github-actions-annotate-failures
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Register problem matchers
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
					 | 
				
			||||||
      - name: Run pytest
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          pytest \
 | 
					 | 
				
			||||||
            -qq \
 | 
					 | 
				
			||||||
            --durations=10 \
 | 
					 | 
				
			||||||
            -o console_output_style=count \
 | 
					 | 
				
			||||||
            tests
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  deploy-docker:
 | 
					 | 
				
			||||||
    name: Build and publish docker containers
 | 
					 | 
				
			||||||
    if: github.repository == 'esphome/esphome'
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
 | 
					 | 
				
			||||||
    strategy:
 | 
					 | 
				
			||||||
      matrix:
 | 
					 | 
				
			||||||
        arch: [amd64, armv7, aarch64]
 | 
					 | 
				
			||||||
        # Hassio dev image doesn't use esphome/esphome-hassio-$arch and uses base directly
 | 
					 | 
				
			||||||
        build_type: ["docker"]
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					 | 
				
			||||||
      - name: Set TAG
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          TAG="${GITHUB_SHA:0:7}"
 | 
					 | 
				
			||||||
          echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
					 | 
				
			||||||
      - name: Set up env variables
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          base_version="3.4.0"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
					 | 
				
			||||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
					 | 
				
			||||||
            build_to="esphome/esphome-hassio-${{ matrix.arch }}"
 | 
					 | 
				
			||||||
            dockerfile="docker/Dockerfile.hassio"
 | 
					 | 
				
			||||||
          else
 | 
					 | 
				
			||||||
            build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
 | 
					 | 
				
			||||||
            build_to="esphome/esphome-${{ matrix.arch }}"
 | 
					 | 
				
			||||||
            dockerfile="docker/Dockerfile"
 | 
					 | 
				
			||||||
          fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
 | 
					 | 
				
			||||||
          echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
 | 
					 | 
				
			||||||
          echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
 | 
					 | 
				
			||||||
      - name: Pull for cache
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          docker pull "${BUILD_TO}:dev" || true
 | 
					 | 
				
			||||||
      - name: Register QEMU binfmt
 | 
					 | 
				
			||||||
        run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
 | 
					 | 
				
			||||||
      - run: |
 | 
					 | 
				
			||||||
          docker build \
 | 
					 | 
				
			||||||
            --build-arg "BUILD_FROM=${BUILD_FROM}" \
 | 
					 | 
				
			||||||
            --build-arg "BUILD_VERSION=${TAG}" \
 | 
					 | 
				
			||||||
            --tag "${BUILD_TO}:${TAG}" \
 | 
					 | 
				
			||||||
            --tag "${BUILD_TO}:dev" \
 | 
					 | 
				
			||||||
            --cache-from "${BUILD_TO}:dev" \
 | 
					 | 
				
			||||||
            --file "${DOCKERFILE}" \
 | 
					 | 
				
			||||||
            .
 | 
					 | 
				
			||||||
      - name: Log in to docker hub
 | 
					 | 
				
			||||||
        env:
 | 
					 | 
				
			||||||
          DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
					 | 
				
			||||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
					 | 
				
			||||||
        run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
					 | 
				
			||||||
      - run: |
 | 
					 | 
				
			||||||
          docker push "${BUILD_TO}:${TAG}"
 | 
					 | 
				
			||||||
          docker push "${BUILD_TO}:dev"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  deploy-docker-manifest:
 | 
					 | 
				
			||||||
    if: github.repository == 'esphome/esphome'
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    needs: [deploy-docker]
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
    - name: Enable experimental manifest support
 | 
					 | 
				
			||||||
      run: |
 | 
					 | 
				
			||||||
        mkdir -p ~/.docker
 | 
					 | 
				
			||||||
        echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
 | 
					 | 
				
			||||||
    - name: Set TAG
 | 
					 | 
				
			||||||
      run: |
 | 
					 | 
				
			||||||
        TAG="${GITHUB_SHA:0:7}"
 | 
					 | 
				
			||||||
        echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
					 | 
				
			||||||
    - name: Log in to docker hub
 | 
					 | 
				
			||||||
      env:
 | 
					 | 
				
			||||||
        DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
					 | 
				
			||||||
        DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
					 | 
				
			||||||
      run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
					 | 
				
			||||||
    - name: "Create the manifest"
 | 
					 | 
				
			||||||
      run: |
 | 
					 | 
				
			||||||
        docker manifest create esphome/esphome:${TAG} \
 | 
					 | 
				
			||||||
          esphome/esphome-aarch64:${TAG} \
 | 
					 | 
				
			||||||
          esphome/esphome-amd64:${TAG} \
 | 
					 | 
				
			||||||
          esphome/esphome-armv7:${TAG}
 | 
					 | 
				
			||||||
        docker manifest push esphome/esphome:${TAG}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        docker manifest create esphome/esphome:dev \
 | 
					 | 
				
			||||||
          esphome/esphome-aarch64:${TAG} \
 | 
					 | 
				
			||||||
          esphome/esphome-amd64:${TAG} \
 | 
					 | 
				
			||||||
          esphome/esphome-armv7:${TAG}
 | 
					 | 
				
			||||||
        docker manifest push esphome/esphome:dev
 | 
					 | 
				
			||||||
							
								
								
									
										332
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										332
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,164 +1,38 @@
 | 
				
			|||||||
name: Publish Release
 | 
					name: Publish Release
 | 
				
			||||||
 | 
					
 | 
				
			||||||
on:
 | 
					on:
 | 
				
			||||||
 | 
					  workflow_dispatch:
 | 
				
			||||||
  release:
 | 
					  release:
 | 
				
			||||||
    types: [published]
 | 
					    types: [published]
 | 
				
			||||||
 | 
					  schedule:
 | 
				
			||||||
 | 
					    - cron: "0 2 * * *"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					permissions:
 | 
				
			||||||
 | 
					  contents: read
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
 | 
					  init:
 | 
				
			||||||
 | 
					    name: Initialize build
 | 
				
			||||||
  lint-clang-format:
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
					    outputs:
 | 
				
			||||||
    # doesn't have to be installed
 | 
					      tag: ${{ steps.tag.outputs.tag }}
 | 
				
			||||||
    container: esphome/esphome-lint:1.1
 | 
					 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					      - uses: actions/checkout@v2
 | 
				
			||||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
					      - name: Get tag
 | 
				
			||||||
      # (build flags, libraries etc)
 | 
					        id: tag
 | 
				
			||||||
      - name: Set up platformio environment
 | 
					 | 
				
			||||||
        run: pio init --ide atom
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Run clang-format
 | 
					 | 
				
			||||||
        run: script/clang-format -i
 | 
					 | 
				
			||||||
      - name: Suggest changes
 | 
					 | 
				
			||||||
        run: script/ci-suggest-changes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  lint-clang-tidy:
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
					 | 
				
			||||||
    # doesn't have to be installed
 | 
					 | 
				
			||||||
    container: esphome/esphome-lint:1.1
 | 
					 | 
				
			||||||
    # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
 | 
					 | 
				
			||||||
    strategy:
 | 
					 | 
				
			||||||
      fail-fast: false
 | 
					 | 
				
			||||||
      matrix:
 | 
					 | 
				
			||||||
        split: [1, 2, 3, 4]
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					 | 
				
			||||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
					 | 
				
			||||||
      # (build flags, libraries etc)
 | 
					 | 
				
			||||||
      - name: Set up platformio environment
 | 
					 | 
				
			||||||
        run: pio init --ide atom
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Register problem matchers
 | 
					 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
					          if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then
 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
					            TAG="${GITHUB_REF#refs/tags/}"
 | 
				
			||||||
      - name: Run clang-tidy
 | 
					          else
 | 
				
			||||||
        run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
 | 
					            TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
 | 
				
			||||||
      - name: Suggest changes
 | 
					            today="$(date --utc '+%Y%m%d')"
 | 
				
			||||||
        run: script/ci-suggest-changes
 | 
					            TAG="${TAG}${today}"
 | 
				
			||||||
 | 
					          fi
 | 
				
			||||||
  lint-python:
 | 
					          echo "::set-output name=tag::${TAG}"
 | 
				
			||||||
    # Don't use the esphome-lint docker image because it may contain outdated requirements.
 | 
					 | 
				
			||||||
    # This way, all dependencies are cached via the cache action.
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					 | 
				
			||||||
      - name: Set up Python
 | 
					 | 
				
			||||||
        uses: actions/setup-python@v2
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          python-version: '3.7'
 | 
					 | 
				
			||||||
      - name: Cache pip modules
 | 
					 | 
				
			||||||
        uses: actions/cache@v1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          path: ~/.cache/pip
 | 
					 | 
				
			||||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
					 | 
				
			||||||
          restore-keys: |
 | 
					 | 
				
			||||||
            esphome-pip-3.7-
 | 
					 | 
				
			||||||
      - name: Set up python environment
 | 
					 | 
				
			||||||
        run: script/setup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Register problem matchers
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/lint-python.json"
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
					 | 
				
			||||||
      - name: Lint Custom
 | 
					 | 
				
			||||||
        run: script/ci-custom.py
 | 
					 | 
				
			||||||
      - name: Lint Python
 | 
					 | 
				
			||||||
        run: script/lint-python
 | 
					 | 
				
			||||||
      - name: Lint CODEOWNERS
 | 
					 | 
				
			||||||
        run: script/build_codeowners.py --check
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  test:
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    strategy:
 | 
					 | 
				
			||||||
      fail-fast: false
 | 
					 | 
				
			||||||
      matrix:
 | 
					 | 
				
			||||||
          test:
 | 
					 | 
				
			||||||
          - test1
 | 
					 | 
				
			||||||
          - test2
 | 
					 | 
				
			||||||
          - test3
 | 
					 | 
				
			||||||
          - test4
 | 
					 | 
				
			||||||
          - test5
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					 | 
				
			||||||
      - name: Set up Python
 | 
					 | 
				
			||||||
        uses: actions/setup-python@v2
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          python-version: '3.7'
 | 
					 | 
				
			||||||
      - name: Cache pip modules
 | 
					 | 
				
			||||||
        uses: actions/cache@v1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          path: ~/.cache/pip
 | 
					 | 
				
			||||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
					 | 
				
			||||||
          restore-keys: |
 | 
					 | 
				
			||||||
            esphome-pip-3.7-
 | 
					 | 
				
			||||||
      # Use per test platformio cache because tests have different platform versions
 | 
					 | 
				
			||||||
      - name: Cache ~/.platformio
 | 
					 | 
				
			||||||
        uses: actions/cache@v1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          path: ~/.platformio
 | 
					 | 
				
			||||||
          key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
 | 
					 | 
				
			||||||
          restore-keys: |
 | 
					 | 
				
			||||||
            test-home-platformio-${{ matrix.test }}-
 | 
					 | 
				
			||||||
      - name: Set up environment
 | 
					 | 
				
			||||||
        run: script/setup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Register problem matchers
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
					 | 
				
			||||||
      - run: esphome compile tests/${{ matrix.test }}.yaml
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  pytest:
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					 | 
				
			||||||
      - name: Set up Python
 | 
					 | 
				
			||||||
        uses: actions/setup-python@v2
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          python-version: '3.7'
 | 
					 | 
				
			||||||
      - name: Cache pip modules
 | 
					 | 
				
			||||||
        uses: actions/cache@v1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          path: ~/.cache/pip
 | 
					 | 
				
			||||||
          key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
 | 
					 | 
				
			||||||
          restore-keys: |
 | 
					 | 
				
			||||||
            esphome-pip-3.7-
 | 
					 | 
				
			||||||
      - name: Set up environment
 | 
					 | 
				
			||||||
        run: script/setup
 | 
					 | 
				
			||||||
      - name: Install Github Actions annotator
 | 
					 | 
				
			||||||
        run: pip install pytest-github-actions-annotate-failures
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Register problem matchers
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
					 | 
				
			||||||
      - name: Run pytest
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          pytest \
 | 
					 | 
				
			||||||
            -qq \
 | 
					 | 
				
			||||||
            --durations=10 \
 | 
					 | 
				
			||||||
            -o console_output_style=count \
 | 
					 | 
				
			||||||
            tests
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  deploy-pypi:
 | 
					  deploy-pypi:
 | 
				
			||||||
    name: Build and publish to PyPi
 | 
					    name: Build and publish to PyPi
 | 
				
			||||||
    if: github.repository == 'esphome/esphome'
 | 
					    if: github.repository == 'esphome/esphome' && github.event_name == 'release'
 | 
				
			||||||
    needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					      - uses: actions/checkout@v2
 | 
				
			||||||
@@ -181,130 +55,100 @@ jobs:
 | 
				
			|||||||
  deploy-docker:
 | 
					  deploy-docker:
 | 
				
			||||||
    name: Build and publish docker containers
 | 
					    name: Build and publish docker containers
 | 
				
			||||||
    if: github.repository == 'esphome/esphome'
 | 
					    if: github.repository == 'esphome/esphome'
 | 
				
			||||||
 | 
					    permissions:
 | 
				
			||||||
 | 
					      contents: read
 | 
				
			||||||
 | 
					      packages: write
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
 | 
					    needs: [init]
 | 
				
			||||||
    strategy:
 | 
					    strategy:
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        arch: [amd64, armv7, aarch64]
 | 
					        arch: [amd64, armv7, aarch64]
 | 
				
			||||||
        build_type: ["hassio", "docker"]
 | 
					        build_type: ["ha-addon", "docker", "lint"]
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					    - uses: actions/checkout@v2
 | 
				
			||||||
      - name: Set TAG
 | 
					    - name: Set up Python
 | 
				
			||||||
        run: |
 | 
					      uses: actions/setup-python@v2
 | 
				
			||||||
          TAG="${GITHUB_REF#refs/tags/v}"
 | 
					      with:
 | 
				
			||||||
          echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
					        python-version: '3.9'
 | 
				
			||||||
      - name: Set up env variables
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          base_version="3.4.0"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
					    - name: Set up Docker Buildx
 | 
				
			||||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
					      uses: docker/setup-buildx-action@v1
 | 
				
			||||||
            build_to="esphome/esphome-hassio-${{ matrix.arch }}"
 | 
					    - name: Set up QEMU
 | 
				
			||||||
            dockerfile="docker/Dockerfile.hassio"
 | 
					      uses: docker/setup-qemu-action@v1
 | 
				
			||||||
          else
 | 
					 | 
				
			||||||
            build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
 | 
					 | 
				
			||||||
            build_to="esphome/esphome-${{ matrix.arch }}"
 | 
					 | 
				
			||||||
            dockerfile="docker/Dockerfile"
 | 
					 | 
				
			||||||
          fi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then
 | 
					    - name: Log in to docker hub
 | 
				
			||||||
            cache_tag="beta"
 | 
					      uses: docker/login-action@v1
 | 
				
			||||||
          else
 | 
					      with:
 | 
				
			||||||
            cache_tag="latest"
 | 
					        username: ${{ secrets.DOCKER_USER }}
 | 
				
			||||||
          fi
 | 
					        password: ${{ secrets.DOCKER_PASSWORD }}
 | 
				
			||||||
 | 
					    - name: Log in to the GitHub container registry
 | 
				
			||||||
 | 
					      uses: docker/login-action@v1
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					          registry: ghcr.io
 | 
				
			||||||
 | 
					          username: ${{ github.actor }}
 | 
				
			||||||
 | 
					          password: ${{ secrets.GITHUB_TOKEN }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          # Set env variables so these values don't need to be calculated again
 | 
					    - name: Build and push
 | 
				
			||||||
          echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
 | 
					      run: |
 | 
				
			||||||
          echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
 | 
					        docker/build.py \
 | 
				
			||||||
          echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
 | 
					          --tag "${{ needs.init.outputs.tag }}" \
 | 
				
			||||||
          echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV
 | 
					          --arch "${{ matrix.arch }}" \
 | 
				
			||||||
      - name: Pull for cache
 | 
					          --build-type "${{ matrix.build_type }}" \
 | 
				
			||||||
        run: |
 | 
					          build \
 | 
				
			||||||
          docker pull "${BUILD_TO}:${CACHE_TAG}" || true
 | 
					          --push
 | 
				
			||||||
      - name: Register QEMU binfmt
 | 
					 | 
				
			||||||
        run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
 | 
					 | 
				
			||||||
      - run: |
 | 
					 | 
				
			||||||
          docker build \
 | 
					 | 
				
			||||||
            --build-arg "BUILD_FROM=${BUILD_FROM}" \
 | 
					 | 
				
			||||||
            --build-arg "BUILD_VERSION=${TAG}" \
 | 
					 | 
				
			||||||
            --tag "${BUILD_TO}:${TAG}" \
 | 
					 | 
				
			||||||
            --cache-from "${BUILD_TO}:${CACHE_TAG}" \
 | 
					 | 
				
			||||||
            --file "${DOCKERFILE}" \
 | 
					 | 
				
			||||||
            .
 | 
					 | 
				
			||||||
      - name: Log in to docker hub
 | 
					 | 
				
			||||||
        env:
 | 
					 | 
				
			||||||
          DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
					 | 
				
			||||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
					 | 
				
			||||||
        run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
					 | 
				
			||||||
      - run: docker push "${BUILD_TO}:${TAG}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      # Always publish to beta tag (also full releases)
 | 
					 | 
				
			||||||
      - name: Publish docker beta tag
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:beta"
 | 
					 | 
				
			||||||
          docker push "${BUILD_TO}:beta"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - if: ${{ !github.event.release.prerelease }}
 | 
					 | 
				
			||||||
        name: Publish docker latest tag
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:latest"
 | 
					 | 
				
			||||||
          docker push "${BUILD_TO}:latest"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  deploy-docker-manifest:
 | 
					  deploy-docker-manifest:
 | 
				
			||||||
    if: github.repository == 'esphome/esphome'
 | 
					    if: github.repository == 'esphome/esphome'
 | 
				
			||||||
 | 
					    permissions:
 | 
				
			||||||
 | 
					      contents: read
 | 
				
			||||||
 | 
					      packages: write
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs: [deploy-docker]
 | 
					    needs: [init, deploy-docker]
 | 
				
			||||||
 | 
					    strategy:
 | 
				
			||||||
 | 
					      matrix:
 | 
				
			||||||
 | 
					        build_type: ["ha-addon", "docker", "lint"]
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
 | 
					    - uses: actions/checkout@v2
 | 
				
			||||||
 | 
					    - name: Set up Python
 | 
				
			||||||
 | 
					      uses: actions/setup-python@v2
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					        python-version: '3.9'
 | 
				
			||||||
    - name: Enable experimental manifest support
 | 
					    - name: Enable experimental manifest support
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
        mkdir -p ~/.docker
 | 
					        mkdir -p ~/.docker
 | 
				
			||||||
        echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
 | 
					        echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
 | 
				
			||||||
    - name: Set TAG
 | 
					
 | 
				
			||||||
      run: |
 | 
					 | 
				
			||||||
        TAG="${GITHUB_REF#refs/tags/v}"
 | 
					 | 
				
			||||||
        echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
					 | 
				
			||||||
    - name: Log in to docker hub
 | 
					    - name: Log in to docker hub
 | 
				
			||||||
      env:
 | 
					      uses: docker/login-action@v1
 | 
				
			||||||
        DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
					      with:
 | 
				
			||||||
        DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
					        username: ${{ secrets.DOCKER_USER }}
 | 
				
			||||||
      run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
 | 
					        password: ${{ secrets.DOCKER_PASSWORD }}
 | 
				
			||||||
    - name: "Create the manifest"
 | 
					    - name: Log in to the GitHub container registry
 | 
				
			||||||
      run: |
 | 
					      uses: docker/login-action@v1
 | 
				
			||||||
        docker manifest create esphome/esphome:${TAG} \
 | 
					      with:
 | 
				
			||||||
          esphome/esphome-aarch64:${TAG} \
 | 
					          registry: ghcr.io
 | 
				
			||||||
          esphome/esphome-amd64:${TAG} \
 | 
					          username: ${{ github.actor }}
 | 
				
			||||||
          esphome/esphome-armv7:${TAG}
 | 
					          password: ${{ secrets.GITHUB_TOKEN }}
 | 
				
			||||||
        docker manifest push esphome/esphome:${TAG}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Publish docker beta tag
 | 
					    - name: Run manifest
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
        docker manifest create esphome/esphome:beta \
 | 
					        docker/build.py \
 | 
				
			||||||
          esphome/esphome-aarch64:${TAG} \
 | 
					          --tag "${{ needs.init.outputs.tag }}" \
 | 
				
			||||||
          esphome/esphome-amd64:${TAG} \
 | 
					          --build-type "${{ matrix.build_type }}" \
 | 
				
			||||||
          esphome/esphome-armv7:${TAG}
 | 
					          manifest
 | 
				
			||||||
        docker manifest push esphome/esphome:beta
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Publish docker latest tag
 | 
					  deploy-ha-addon-repo:
 | 
				
			||||||
      if: ${{ !github.event.release.prerelease }}
 | 
					    if: github.repository == 'esphome/esphome' && github.event_name == 'release'
 | 
				
			||||||
      run: |
 | 
					 | 
				
			||||||
        docker manifest create esphome/esphome:latest \
 | 
					 | 
				
			||||||
          esphome/esphome-aarch64:${TAG} \
 | 
					 | 
				
			||||||
          esphome/esphome-amd64:${TAG} \
 | 
					 | 
				
			||||||
          esphome/esphome-armv7:${TAG}
 | 
					 | 
				
			||||||
        docker manifest push esphome/esphome:latest
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  deploy-hassio-repo:
 | 
					 | 
				
			||||||
    if: github.repository == 'esphome/esphome'
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    needs: [deploy-docker]
 | 
					    needs: [deploy-docker]
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - env:
 | 
					      - env:
 | 
				
			||||||
          TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }}
 | 
					          TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          TAG="${GITHUB_REF#refs/tags/v}"
 | 
					          TAG="${GITHUB_REF#refs/tags/}"
 | 
				
			||||||
          curl \
 | 
					          curl \
 | 
				
			||||||
            -u ":$TOKEN" \
 | 
					            -u ":$TOKEN" \
 | 
				
			||||||
            -X POST \
 | 
					            -X POST \
 | 
				
			||||||
            -H "Accept: application/vnd.github.v3+json" \
 | 
					            -H "Accept: application/vnd.github.v3+json" \
 | 
				
			||||||
            https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \
 | 
					            https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \
 | 
				
			||||||
            -d "{\"ref\":\"master\",\"inputs\":{\"version\":\"$TAG\"}}"
 | 
					            -d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										48
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					name: Stale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  schedule:
 | 
				
			||||||
 | 
					    - cron: '30 0 * * *'
 | 
				
			||||||
 | 
					  workflow_dispatch:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					permissions:
 | 
				
			||||||
 | 
					  issues: write
 | 
				
			||||||
 | 
					  pull-requests: write
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					concurrency:
 | 
				
			||||||
 | 
					  group: lock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  stale:
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - uses: actions/stale@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          days-before-pr-stale: 90
 | 
				
			||||||
 | 
					          days-before-pr-close: 7
 | 
				
			||||||
 | 
					          days-before-issue-stale: -1
 | 
				
			||||||
 | 
					          days-before-issue-close: -1
 | 
				
			||||||
 | 
					          remove-stale-when-updated: true
 | 
				
			||||||
 | 
					          stale-pr-label: "stale"
 | 
				
			||||||
 | 
					          exempt-pr-labels: "no-stale"
 | 
				
			||||||
 | 
					          stale-pr-message: >
 | 
				
			||||||
 | 
					            There hasn't been any activity on this pull request recently. This
 | 
				
			||||||
 | 
					            pull request has been automatically marked as stale because of that
 | 
				
			||||||
 | 
					            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 reference to the issue tracker
 | 
				
			||||||
 | 
					  close-issues:
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - uses: actions/stale@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          days-before-pr-stale: -1
 | 
				
			||||||
 | 
					          days-before-pr-close: -1
 | 
				
			||||||
 | 
					          days-before-issue-stale: 1
 | 
				
			||||||
 | 
					          days-before-issue-close: 1
 | 
				
			||||||
 | 
					          remove-stale-when-updated: true
 | 
				
			||||||
 | 
					          stale-issue-label: "stale"
 | 
				
			||||||
 | 
					          exempt-issue-labels: "not-stale"
 | 
				
			||||||
 | 
					          stale-issue-message: >
 | 
				
			||||||
 | 
					            https://github.com/esphome/esphome/issues/430
 | 
				
			||||||
							
								
								
									
										13
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -13,6 +13,9 @@ __pycache__/
 | 
				
			|||||||
# Intellij Idea
 | 
					# Intellij Idea
 | 
				
			||||||
.idea
 | 
					.idea
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Vim
 | 
				
			||||||
 | 
					*.swp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Hide some OS X stuff
 | 
					# Hide some OS X stuff
 | 
				
			||||||
.DS_Store
 | 
					.DS_Store
 | 
				
			||||||
.AppleDouble
 | 
					.AppleDouble
 | 
				
			||||||
@@ -74,6 +77,7 @@ venv/
 | 
				
			|||||||
ENV/
 | 
					ENV/
 | 
				
			||||||
env.bak/
 | 
					env.bak/
 | 
				
			||||||
venv.bak/
 | 
					venv.bak/
 | 
				
			||||||
 | 
					venv-*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# mypy
 | 
					# mypy
 | 
				
			||||||
.mypy_cache/
 | 
					.mypy_cache/
 | 
				
			||||||
@@ -99,10 +103,7 @@ CMakeLists.txt
 | 
				
			|||||||
.idea/**/dynamic.xml
 | 
					.idea/**/dynamic.xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# CMake
 | 
					# CMake
 | 
				
			||||||
cmake-build-debug/
 | 
					cmake-build-*/
 | 
				
			||||||
cmake-build-livingroom8266/
 | 
					 | 
				
			||||||
cmake-build-livingroom32/
 | 
					 | 
				
			||||||
cmake-build-release/
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
CMakeCache.txt
 | 
					CMakeCache.txt
 | 
				
			||||||
CMakeFiles
 | 
					CMakeFiles
 | 
				
			||||||
@@ -122,4 +123,8 @@ config/
 | 
				
			|||||||
tests/build/
 | 
					tests/build/
 | 
				
			||||||
tests/.esphome/
 | 
					tests/.esphome/
 | 
				
			||||||
/.temp-clang-tidy.cpp
 | 
					/.temp-clang-tidy.cpp
 | 
				
			||||||
 | 
					/.temp/
 | 
				
			||||||
.pio/
 | 
					.pio/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sdkconfig.*
 | 
				
			||||||
 | 
					!sdkconfig.defaults
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,4 +3,4 @@ ports:
 | 
				
			|||||||
  onOpen: open-preview
 | 
					  onOpen: open-preview
 | 
				
			||||||
tasks:
 | 
					tasks:
 | 
				
			||||||
- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup
 | 
					- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup
 | 
				
			||||||
  command: python -m esphome config dashboard
 | 
					  command: python -m esphome dashboard config
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
					# See https://pre-commit.com/hooks.html for more hooks
 | 
				
			||||||
repos:
 | 
					repos:
 | 
				
			||||||
  - repo: https://github.com/ambv/black
 | 
					  - repo: https://github.com/ambv/black
 | 
				
			||||||
    rev: 20.8b1
 | 
					    rev: 22.1.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
    - id: black
 | 
					    - id: black
 | 
				
			||||||
      args:
 | 
					      args:
 | 
				
			||||||
@@ -10,7 +10,7 @@ repos:
 | 
				
			|||||||
        - --quiet
 | 
					        - --quiet
 | 
				
			||||||
      files: ^((esphome|script|tests)/.+)?[^/]+\.py$
 | 
					      files: ^((esphome|script|tests)/.+)?[^/]+\.py$
 | 
				
			||||||
  - repo: https://gitlab.com/pycqa/flake8
 | 
					  - repo: https://gitlab.com/pycqa/flake8
 | 
				
			||||||
    rev: 3.8.4
 | 
					    rev: 4.0.1
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: flake8
 | 
					      - id: flake8
 | 
				
			||||||
        additional_dependencies:
 | 
					        additional_dependencies:
 | 
				
			||||||
@@ -23,5 +23,10 @@ repos:
 | 
				
			|||||||
      - id: no-commit-to-branch
 | 
					      - id: no-commit-to-branch
 | 
				
			||||||
        args:
 | 
					        args:
 | 
				
			||||||
          - --branch=dev
 | 
					          - --branch=dev
 | 
				
			||||||
          - --branch=master
 | 
					          - --branch=release
 | 
				
			||||||
          - --branch=beta
 | 
					          - --branch=beta
 | 
				
			||||||
 | 
					  - repo: https://github.com/asottile/pyupgrade
 | 
				
			||||||
 | 
					    rev: v2.31.0
 | 
				
			||||||
 | 
					    hooks:
 | 
				
			||||||
 | 
					      - id: pyupgrade
 | 
				
			||||||
 | 
					        args: [--py38-plus]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										35
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							@@ -1,11 +1,32 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "version": "2.0.0",
 | 
					  "version": "2.0.0",
 | 
				
			||||||
    "tasks": [
 | 
					  "tasks": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "label": "run",
 | 
				
			||||||
 | 
					      "type": "shell",
 | 
				
			||||||
 | 
					      "command": "python3 -m esphome dashboard config/",
 | 
				
			||||||
 | 
					      "problemMatcher": []
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "label": "clang-tidy",
 | 
				
			||||||
 | 
					      "type": "shell",
 | 
				
			||||||
 | 
					      "command": "./script/clang-tidy",
 | 
				
			||||||
 | 
					      "problemMatcher": [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            "label": "run",
 | 
					          "owner": "clang-tidy",
 | 
				
			||||||
            "type": "shell",
 | 
					          "fileLocation": "absolute",
 | 
				
			||||||
            "command": "python3 -m esphome dashboard config",
 | 
					          "pattern": [
 | 
				
			||||||
            "problemMatcher": []
 | 
					            {
 | 
				
			||||||
 | 
					              "regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*) \\[([a-z0-9,\\-]+)\\]\\s*$",
 | 
				
			||||||
 | 
					              "file": 1,
 | 
				
			||||||
 | 
					              "line": 2,
 | 
				
			||||||
 | 
					              "column": 3,
 | 
				
			||||||
 | 
					              "severity": 4,
 | 
				
			||||||
 | 
					              "message": 5
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          ]
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ]
 | 
					      ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										103
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								CODEOWNERS
									
									
									
									
									
								
							@@ -14,30 +14,57 @@ esphome/core/* @esphome/core
 | 
				
			|||||||
esphome/components/ac_dimmer/* @glmnet
 | 
					esphome/components/ac_dimmer/* @glmnet
 | 
				
			||||||
esphome/components/adc/* @esphome/core
 | 
					esphome/components/adc/* @esphome/core
 | 
				
			||||||
esphome/components/addressable_light/* @justfalter
 | 
					esphome/components/addressable_light/* @justfalter
 | 
				
			||||||
 | 
					esphome/components/airthings_ble/* @jeromelaban
 | 
				
			||||||
 | 
					esphome/components/airthings_wave_mini/* @ncareau
 | 
				
			||||||
 | 
					esphome/components/airthings_wave_plus/* @jeromelaban
 | 
				
			||||||
 | 
					esphome/components/am43/* @buxtronix
 | 
				
			||||||
 | 
					esphome/components/am43/cover/* @buxtronix
 | 
				
			||||||
 | 
					esphome/components/analog_threshold/* @ianchi
 | 
				
			||||||
esphome/components/animation/* @syndlex
 | 
					esphome/components/animation/* @syndlex
 | 
				
			||||||
 | 
					esphome/components/anova/* @buxtronix
 | 
				
			||||||
esphome/components/api/* @OttoWinter
 | 
					esphome/components/api/* @OttoWinter
 | 
				
			||||||
esphome/components/async_tcp/* @OttoWinter
 | 
					esphome/components/async_tcp/* @OttoWinter
 | 
				
			||||||
esphome/components/atc_mithermometer/* @ahpohl
 | 
					esphome/components/atc_mithermometer/* @ahpohl
 | 
				
			||||||
esphome/components/b_parasite/* @rbaron
 | 
					esphome/components/b_parasite/* @rbaron
 | 
				
			||||||
 | 
					esphome/components/ballu/* @bazuchan
 | 
				
			||||||
esphome/components/bang_bang/* @OttoWinter
 | 
					esphome/components/bang_bang/* @OttoWinter
 | 
				
			||||||
 | 
					esphome/components/bh1750/* @OttoWinter
 | 
				
			||||||
esphome/components/binary_sensor/* @esphome/core
 | 
					esphome/components/binary_sensor/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/bl0940/* @tobias-
 | 
				
			||||||
esphome/components/ble_client/* @buxtronix
 | 
					esphome/components/ble_client/* @buxtronix
 | 
				
			||||||
esphome/components/bme680_bsec/* @trvrnrth
 | 
					esphome/components/bme680_bsec/* @trvrnrth
 | 
				
			||||||
 | 
					esphome/components/bmp3xx/* @martgras
 | 
				
			||||||
 | 
					esphome/components/button/* @esphome/core
 | 
				
			||||||
esphome/components/canbus/* @danielschramm @mvturnho
 | 
					esphome/components/canbus/* @danielschramm @mvturnho
 | 
				
			||||||
 | 
					esphome/components/cap1188/* @MrEditor97
 | 
				
			||||||
esphome/components/captive_portal/* @OttoWinter
 | 
					esphome/components/captive_portal/* @OttoWinter
 | 
				
			||||||
 | 
					esphome/components/ccs811/* @habbie
 | 
				
			||||||
 | 
					esphome/components/cd74hc4067/* @asoehlke
 | 
				
			||||||
esphome/components/climate/* @esphome/core
 | 
					esphome/components/climate/* @esphome/core
 | 
				
			||||||
esphome/components/climate_ir/* @glmnet
 | 
					esphome/components/climate_ir/* @glmnet
 | 
				
			||||||
 | 
					esphome/components/color_temperature/* @jesserockz
 | 
				
			||||||
esphome/components/coolix/* @glmnet
 | 
					esphome/components/coolix/* @glmnet
 | 
				
			||||||
 | 
					esphome/components/copy/* @OttoWinter
 | 
				
			||||||
esphome/components/cover/* @esphome/core
 | 
					esphome/components/cover/* @esphome/core
 | 
				
			||||||
esphome/components/cs5460a/* @balrog-kun
 | 
					esphome/components/cs5460a/* @balrog-kun
 | 
				
			||||||
 | 
					esphome/components/cse7761/* @berfenger
 | 
				
			||||||
esphome/components/ct_clamp/* @jesserockz
 | 
					esphome/components/ct_clamp/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/current_based/* @djwmarcx
 | 
				
			||||||
 | 
					esphome/components/daly_bms/* @s1lvi0
 | 
				
			||||||
 | 
					esphome/components/dashboard_import/* @esphome/core
 | 
				
			||||||
esphome/components/debug/* @OttoWinter
 | 
					esphome/components/debug/* @OttoWinter
 | 
				
			||||||
esphome/components/dfplayer/* @glmnet
 | 
					esphome/components/dfplayer/* @glmnet
 | 
				
			||||||
esphome/components/dht/* @OttoWinter
 | 
					esphome/components/dht/* @OttoWinter
 | 
				
			||||||
esphome/components/ds1307/* @badbadc0ffee
 | 
					esphome/components/ds1307/* @badbadc0ffee
 | 
				
			||||||
 | 
					esphome/components/dsmr/* @glmnet @zuidwijk
 | 
				
			||||||
 | 
					esphome/components/ektf2232/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/esp32/* @esphome/core
 | 
				
			||||||
esphome/components/esp32_ble/* @jesserockz
 | 
					esphome/components/esp32_ble/* @jesserockz
 | 
				
			||||||
esphome/components/esp32_ble_server/* @jesserockz
 | 
					esphome/components/esp32_ble_server/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/esp32_camera_web_server/* @ayufan
 | 
				
			||||||
 | 
					esphome/components/esp32_can/* @Sympatron
 | 
				
			||||||
esphome/components/esp32_improv/* @jesserockz
 | 
					esphome/components/esp32_improv/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/esp8266/* @esphome/core
 | 
				
			||||||
esphome/components/exposure_notifications/* @OttoWinter
 | 
					esphome/components/exposure_notifications/* @OttoWinter
 | 
				
			||||||
esphome/components/ezo/* @ssieb
 | 
					esphome/components/ezo/* @ssieb
 | 
				
			||||||
esphome/components/fastled_base/* @OttoWinter
 | 
					esphome/components/fastled_base/* @OttoWinter
 | 
				
			||||||
@@ -45,18 +72,34 @@ esphome/components/fingerprint_grow/* @OnFreund @loongyh
 | 
				
			|||||||
esphome/components/globals/* @esphome/core
 | 
					esphome/components/globals/* @esphome/core
 | 
				
			||||||
esphome/components/gpio/* @esphome/core
 | 
					esphome/components/gpio/* @esphome/core
 | 
				
			||||||
esphome/components/gps/* @coogle
 | 
					esphome/components/gps/* @coogle
 | 
				
			||||||
 | 
					esphome/components/graph/* @synco
 | 
				
			||||||
 | 
					esphome/components/growatt_solar/* @leeuwte
 | 
				
			||||||
 | 
					esphome/components/havells_solar/* @sourabhjaiswal
 | 
				
			||||||
 | 
					esphome/components/hbridge/fan/* @WeekendWarrior
 | 
				
			||||||
 | 
					esphome/components/hbridge/light/* @DotNetDann
 | 
				
			||||||
 | 
					esphome/components/heatpumpir/* @rob-deutsch
 | 
				
			||||||
 | 
					esphome/components/hitachi_ac424/* @sourabhjaiswal
 | 
				
			||||||
esphome/components/homeassistant/* @OttoWinter
 | 
					esphome/components/homeassistant/* @OttoWinter
 | 
				
			||||||
 | 
					esphome/components/honeywellabp/* @RubyBailey
 | 
				
			||||||
 | 
					esphome/components/hrxl_maxsonar_wr/* @netmikey
 | 
				
			||||||
esphome/components/i2c/* @esphome/core
 | 
					esphome/components/i2c/* @esphome/core
 | 
				
			||||||
esphome/components/improv/* @jesserockz
 | 
					esphome/components/improv_serial/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/ina260/* @MrEditor97
 | 
				
			||||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
 | 
					esphome/components/inkbird_ibsth1_mini/* @fkirill
 | 
				
			||||||
esphome/components/inkplate6/* @jesserockz
 | 
					esphome/components/inkplate6/* @jesserockz
 | 
				
			||||||
esphome/components/integration/* @OttoWinter
 | 
					esphome/components/integration/* @OttoWinter
 | 
				
			||||||
esphome/components/interval/* @esphome/core
 | 
					esphome/components/interval/* @esphome/core
 | 
				
			||||||
esphome/components/json/* @OttoWinter
 | 
					esphome/components/json/* @OttoWinter
 | 
				
			||||||
 | 
					esphome/components/kalman_combinator/* @Cat-Ion
 | 
				
			||||||
esphome/components/ledc/* @OttoWinter
 | 
					esphome/components/ledc/* @OttoWinter
 | 
				
			||||||
esphome/components/light/* @esphome/core
 | 
					esphome/components/light/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/lock/* @esphome/core
 | 
				
			||||||
esphome/components/logger/* @esphome/core
 | 
					esphome/components/logger/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/ltr390/* @sjtrny
 | 
				
			||||||
 | 
					esphome/components/max44009/* @berfenger
 | 
				
			||||||
esphome/components/max7219digit/* @rspaargaren
 | 
					esphome/components/max7219digit/* @rspaargaren
 | 
				
			||||||
 | 
					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/* @SenexCrenshaw @jesserockz
 | 
				
			||||||
@@ -65,34 +108,73 @@ 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
 | 
				
			||||||
esphome/components/mcp2515/* @danielschramm @mvturnho
 | 
					esphome/components/mcp2515/* @danielschramm @mvturnho
 | 
				
			||||||
 | 
					esphome/components/mcp3204/* @rsumner
 | 
				
			||||||
 | 
					esphome/components/mcp4728/* @berfenger
 | 
				
			||||||
 | 
					esphome/components/mcp47a1/* @jesserockz
 | 
				
			||||||
esphome/components/mcp9808/* @k7hpn
 | 
					esphome/components/mcp9808/* @k7hpn
 | 
				
			||||||
esphome/components/midea_ac/* @dudanov
 | 
					esphome/components/md5/* @esphome/core
 | 
				
			||||||
esphome/components/midea_dongle/* @dudanov
 | 
					esphome/components/mdns/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/midea/* @dudanov
 | 
				
			||||||
 | 
					esphome/components/midea_ir/* @dudanov
 | 
				
			||||||
esphome/components/mitsubishi/* @RubyBailey
 | 
					esphome/components/mitsubishi/* @RubyBailey
 | 
				
			||||||
 | 
					esphome/components/mlx90393/* @functionpointer
 | 
				
			||||||
 | 
					esphome/components/modbus_controller/* @martgras
 | 
				
			||||||
 | 
					esphome/components/modbus_controller/binary_sensor/* @martgras
 | 
				
			||||||
 | 
					esphome/components/modbus_controller/number/* @martgras
 | 
				
			||||||
 | 
					esphome/components/modbus_controller/output/* @martgras
 | 
				
			||||||
 | 
					esphome/components/modbus_controller/select/* @martgras @stegm
 | 
				
			||||||
 | 
					esphome/components/modbus_controller/sensor/* @martgras
 | 
				
			||||||
 | 
					esphome/components/modbus_controller/switch/* @martgras
 | 
				
			||||||
 | 
					esphome/components/modbus_controller/text_sensor/* @martgras
 | 
				
			||||||
 | 
					esphome/components/mopeka_ble/* @spbrogan
 | 
				
			||||||
 | 
					esphome/components/mopeka_pro_check/* @spbrogan
 | 
				
			||||||
 | 
					esphome/components/mpu6886/* @fabaff
 | 
				
			||||||
esphome/components/network/* @esphome/core
 | 
					esphome/components/network/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/nextion/* @senexcrenshaw
 | 
				
			||||||
 | 
					esphome/components/nextion/binary_sensor/* @senexcrenshaw
 | 
				
			||||||
 | 
					esphome/components/nextion/sensor/* @senexcrenshaw
 | 
				
			||||||
 | 
					esphome/components/nextion/switch/* @senexcrenshaw
 | 
				
			||||||
 | 
					esphome/components/nextion/text_sensor/* @senexcrenshaw
 | 
				
			||||||
esphome/components/nfc/* @jesserockz
 | 
					esphome/components/nfc/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/number/* @esphome/core
 | 
				
			||||||
esphome/components/ota/* @esphome/core
 | 
					esphome/components/ota/* @esphome/core
 | 
				
			||||||
esphome/components/output/* @esphome/core
 | 
					esphome/components/output/* @esphome/core
 | 
				
			||||||
esphome/components/pid/* @OttoWinter
 | 
					esphome/components/pid/* @OttoWinter
 | 
				
			||||||
 | 
					esphome/components/pipsolar/* @andreashergert1984
 | 
				
			||||||
 | 
					esphome/components/pm1006/* @habbie
 | 
				
			||||||
 | 
					esphome/components/pmsa003i/* @sjtrny
 | 
				
			||||||
esphome/components/pn532/* @OttoWinter @jesserockz
 | 
					esphome/components/pn532/* @OttoWinter @jesserockz
 | 
				
			||||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
 | 
					esphome/components/pn532_i2c/* @OttoWinter @jesserockz
 | 
				
			||||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
 | 
					esphome/components/pn532_spi/* @OttoWinter @jesserockz
 | 
				
			||||||
esphome/components/power_supply/* @esphome/core
 | 
					esphome/components/power_supply/* @esphome/core
 | 
				
			||||||
esphome/components/pulse_meter/* @stevebaxter
 | 
					esphome/components/preferences/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/psram/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/pulse_meter/* @cstaahl @stevebaxter
 | 
				
			||||||
 | 
					esphome/components/pvvx_mithermometer/* @pasiz
 | 
				
			||||||
 | 
					esphome/components/qr_code/* @wjtje
 | 
				
			||||||
 | 
					esphome/components/radon_eye_ble/* @jeffeb3
 | 
				
			||||||
 | 
					esphome/components/radon_eye_rd200/* @jeffeb3
 | 
				
			||||||
esphome/components/rc522/* @glmnet
 | 
					esphome/components/rc522/* @glmnet
 | 
				
			||||||
esphome/components/rc522_i2c/* @glmnet
 | 
					esphome/components/rc522_i2c/* @glmnet
 | 
				
			||||||
esphome/components/rc522_spi/* @glmnet
 | 
					esphome/components/rc522_spi/* @glmnet
 | 
				
			||||||
esphome/components/restart/* @esphome/core
 | 
					esphome/components/restart/* @esphome/core
 | 
				
			||||||
esphome/components/rf_bridge/* @jesserockz
 | 
					esphome/components/rf_bridge/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/rgbct/* @jesserockz
 | 
				
			||||||
esphome/components/rtttl/* @glmnet
 | 
					esphome/components/rtttl/* @glmnet
 | 
				
			||||||
 | 
					esphome/components/safe_mode/* @jsuanet @paulmonigatti
 | 
				
			||||||
 | 
					esphome/components/scd4x/* @sjtrny
 | 
				
			||||||
esphome/components/script/* @esphome/core
 | 
					esphome/components/script/* @esphome/core
 | 
				
			||||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
 | 
					esphome/components/sdm_meter/* @jesserockz @polyfaces
 | 
				
			||||||
 | 
					esphome/components/sdp3x/* @Azimath
 | 
				
			||||||
 | 
					esphome/components/selec_meter/* @sourabhjaiswal
 | 
				
			||||||
 | 
					esphome/components/select/* @esphome/core
 | 
				
			||||||
esphome/components/sensor/* @esphome/core
 | 
					esphome/components/sensor/* @esphome/core
 | 
				
			||||||
esphome/components/sgp40/* @SenexCrenshaw
 | 
					esphome/components/sgp40/* @SenexCrenshaw
 | 
				
			||||||
esphome/components/sht4x/* @sjtrny
 | 
					esphome/components/sht4x/* @sjtrny
 | 
				
			||||||
esphome/components/shutdown/* @esphome/core
 | 
					esphome/components/shutdown/* @esphome/core @jsuanet
 | 
				
			||||||
esphome/components/sim800l/* @glmnet
 | 
					esphome/components/sim800l/* @glmnet
 | 
				
			||||||
esphome/components/sm2135/* @BoukeHaarsma23
 | 
					esphome/components/sm2135/* @BoukeHaarsma23
 | 
				
			||||||
 | 
					esphome/components/socket/* @esphome/core
 | 
				
			||||||
esphome/components/spi/* @esphome/core
 | 
					esphome/components/spi/* @esphome/core
 | 
				
			||||||
esphome/components/ssd1322_base/* @kbx81
 | 
					esphome/components/ssd1322_base/* @kbx81
 | 
				
			||||||
esphome/components/ssd1322_spi/* @kbx81
 | 
					esphome/components/ssd1322_spi/* @kbx81
 | 
				
			||||||
@@ -107,26 +189,37 @@ esphome/components/ssd1351_base/* @kbx81
 | 
				
			|||||||
esphome/components/ssd1351_spi/* @kbx81
 | 
					esphome/components/ssd1351_spi/* @kbx81
 | 
				
			||||||
esphome/components/st7735/* @SenexCrenshaw
 | 
					esphome/components/st7735/* @SenexCrenshaw
 | 
				
			||||||
esphome/components/st7789v/* @kbx81
 | 
					esphome/components/st7789v/* @kbx81
 | 
				
			||||||
 | 
					esphome/components/st7920/* @marsjan155
 | 
				
			||||||
esphome/components/substitutions/* @esphome/core
 | 
					esphome/components/substitutions/* @esphome/core
 | 
				
			||||||
esphome/components/sun/* @OttoWinter
 | 
					esphome/components/sun/* @OttoWinter
 | 
				
			||||||
esphome/components/switch/* @esphome/core
 | 
					esphome/components/switch/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/t6615/* @tylermenezes
 | 
				
			||||||
esphome/components/tca9548a/* @andreashergert1984
 | 
					esphome/components/tca9548a/* @andreashergert1984
 | 
				
			||||||
esphome/components/tcl112/* @glmnet
 | 
					esphome/components/tcl112/* @glmnet
 | 
				
			||||||
esphome/components/teleinfo/* @0hax
 | 
					esphome/components/teleinfo/* @0hax
 | 
				
			||||||
esphome/components/thermostat/* @kbx81
 | 
					esphome/components/thermostat/* @kbx81
 | 
				
			||||||
esphome/components/time/* @OttoWinter
 | 
					esphome/components/time/* @OttoWinter
 | 
				
			||||||
 | 
					esphome/components/tlc5947/* @rnauber
 | 
				
			||||||
esphome/components/tm1637/* @glmnet
 | 
					esphome/components/tm1637/* @glmnet
 | 
				
			||||||
esphome/components/tmp102/* @timsavage
 | 
					esphome/components/tmp102/* @timsavage
 | 
				
			||||||
 | 
					esphome/components/tmp117/* @Azimath
 | 
				
			||||||
esphome/components/tof10120/* @wstrzalka
 | 
					esphome/components/tof10120/* @wstrzalka
 | 
				
			||||||
 | 
					esphome/components/toshiba/* @kbx81
 | 
				
			||||||
 | 
					esphome/components/touchscreen/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/tsl2591/* @wjcarpenter
 | 
				
			||||||
esphome/components/tuya/binary_sensor/* @jesserockz
 | 
					esphome/components/tuya/binary_sensor/* @jesserockz
 | 
				
			||||||
esphome/components/tuya/climate/* @jesserockz
 | 
					esphome/components/tuya/climate/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/tuya/number/* @frankiboy1
 | 
				
			||||||
esphome/components/tuya/sensor/* @jesserockz
 | 
					esphome/components/tuya/sensor/* @jesserockz
 | 
				
			||||||
esphome/components/tuya/switch/* @jesserockz
 | 
					esphome/components/tuya/switch/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/tuya/text_sensor/* @dentra
 | 
				
			||||||
esphome/components/uart/* @esphome/core
 | 
					esphome/components/uart/* @esphome/core
 | 
				
			||||||
esphome/components/ultrasonic/* @OttoWinter
 | 
					esphome/components/ultrasonic/* @OttoWinter
 | 
				
			||||||
esphome/components/version/* @esphome/core
 | 
					esphome/components/version/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/wake_on_lan/* @willwill2will54
 | 
				
			||||||
esphome/components/web_server_base/* @OttoWinter
 | 
					esphome/components/web_server_base/* @OttoWinter
 | 
				
			||||||
esphome/components/whirlpool/* @glmnet
 | 
					esphome/components/whirlpool/* @glmnet
 | 
				
			||||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
 | 
					esphome/components/xiaomi_lywsd03mmc/* @ahpohl
 | 
				
			||||||
 | 
					esphome/components/xiaomi_mhoc303/* @drug123
 | 
				
			||||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
 | 
					esphome/components/xiaomi_mhoc401/* @vevsvevs
 | 
				
			||||||
esphome/components/xpt2046/* @numo68
 | 
					esphome/components/xpt2046/* @numo68
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,11 @@
 | 
				
			|||||||
# Contributing to ESPHome
 | 
					# Contributing to ESPHome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This python project is responsible for reading in YAML configuration files,
 | 
					For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphome
 | 
				
			||||||
converting them to C++ code. This code is then converted to a platformio project and compiled
 | 
					 | 
				
			||||||
with [esphome-core](https://github.com/esphome/esphome-core), the C++ framework behind the project.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For a detailed guide, please see https://esphome.io/guides/contributing.html#contributing-to-esphomeyaml
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Things to note when contributing:
 | 
					Things to note when contributing:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 - Please test your changes :)
 | 
					 - Please test your changes :)
 | 
				
			||||||
 - If a new feature is added or an existing user-facing feature is changed, you should also 
 | 
					 - If a new feature is added or an existing user-facing feature is changed, you should also
 | 
				
			||||||
   update the [docs](https://github.com/esphome/esphome-docs). See [contributing to esphome-docs](https://esphome.io/guides/contributing.html#contributing-to-esphomedocs)
 | 
					   update the [docs](https://github.com/esphome/esphome-docs). See [contributing to esphome-docs](https://esphome.io/guides/contributing.html#contributing-to-esphomedocs)
 | 
				
			||||||
   for more information.
 | 
					   for more information.
 | 
				
			||||||
 - Please also update the tests in the `tests/` folder. You can do so by just adding a line in one of the YAML files
 | 
					 - Please also update the tests in the `tests/` folder. You can do so by just adding a line in one of the YAML files
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,4 +4,5 @@ include requirements.txt
 | 
				
			|||||||
include esphome/dashboard/templates/*.html
 | 
					include esphome/dashboard/templates/*.html
 | 
				
			||||||
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
 | 
					recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
 | 
				
			||||||
recursive-include esphome *.cpp *.h *.tcc
 | 
					recursive-include esphome *.cpp *.h *.tcc
 | 
				
			||||||
 | 
					recursive-include esphome *.py.script
 | 
				
			||||||
recursive-include esphome LICENSE.txt
 | 
					recursive-include esphome LICENSE.txt
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
# ESPHome [](https://travis-ci.org/esphome/esphome) [](https://discord.gg/KhAMKrd) [](https://GitHub.com/esphome/esphome/releases/)
 | 
					# ESPHome [](https://discord.gg/KhAMKrd) [](https://GitHub.com/esphome/esphome/releases/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[](https://esphome.io/)
 | 
					[](https://esphome.io/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,58 @@
 | 
				
			|||||||
ARG BUILD_FROM=esphome/esphome-base-amd64:3.4.0
 | 
					# Build these with the build.py script
 | 
				
			||||||
FROM ${BUILD_FROM}
 | 
					# Example:
 | 
				
			||||||
 | 
					#   python3 docker/build.py --tag dev --arch amd64 --build-type docker build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# One of "docker", "hassio"
 | 
				
			||||||
 | 
					ARG BASEIMGTYPE=docker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# https://github.com/hassio-addons/addon-debian-base/releases
 | 
				
			||||||
 | 
					FROM ghcr.io/hassio-addons/debian-base/amd64:5.2.3 AS base-hassio-amd64
 | 
				
			||||||
 | 
					FROM ghcr.io/hassio-addons/debian-base/aarch64:5.2.3 AS base-hassio-arm64
 | 
				
			||||||
 | 
					FROM ghcr.io/hassio-addons/debian-base/armv7:5.2.3 AS base-hassio-armv7
 | 
				
			||||||
 | 
					# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
 | 
				
			||||||
 | 
					FROM debian:bullseye-20220125-slim AS base-docker-amd64
 | 
				
			||||||
 | 
					FROM debian:bullseye-20220125-slim AS base-docker-arm64
 | 
				
			||||||
 | 
					FROM debian:bullseye-20220125-slim AS base-docker-armv7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Use TARGETARCH/TARGETVARIANT defined by docker
 | 
				
			||||||
 | 
					# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
 | 
				
			||||||
 | 
					FROM base-${BASEIMGTYPE}-${TARGETARCH}${TARGETVARIANT} AS base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN \
 | 
				
			||||||
 | 
					    apt-get update \
 | 
				
			||||||
 | 
					    # Use pinned versions so that we get updates with build caching
 | 
				
			||||||
 | 
					    && apt-get install -y --no-install-recommends \
 | 
				
			||||||
 | 
					        python3=3.9.2-3 \
 | 
				
			||||||
 | 
					        python3-pip=20.3.4-4 \
 | 
				
			||||||
 | 
					        python3-setuptools=52.0.0-4 \
 | 
				
			||||||
 | 
					        python3-pil=8.1.2+dfsg-0.3+deb11u1 \
 | 
				
			||||||
 | 
					        python3-cryptography=3.3.2-1 \
 | 
				
			||||||
 | 
					        iputils-ping=3:20210202-1 \
 | 
				
			||||||
 | 
					        git=1:2.30.2-1 \
 | 
				
			||||||
 | 
					        curl=7.74.0-1.3+deb11u1 \
 | 
				
			||||||
 | 
					    && rm -rf \
 | 
				
			||||||
 | 
					        /tmp/* \
 | 
				
			||||||
 | 
					        /var/{cache,log}/* \
 | 
				
			||||||
 | 
					        /var/lib/apt/lists/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENV \
 | 
				
			||||||
 | 
					  # Fix click python3 lang warning https://click.palletsprojects.com/en/7.x/python3/
 | 
				
			||||||
 | 
					  LANG=C.UTF-8 LC_ALL=C.UTF-8 \
 | 
				
			||||||
 | 
					  # Store globally installed pio libs in /piolibs
 | 
				
			||||||
 | 
					  PLATFORMIO_GLOBALLIB_DIR=/piolibs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN \
 | 
				
			||||||
 | 
					    # Ubuntu python3-pip is missing wheel
 | 
				
			||||||
 | 
					    pip3 install --no-cache-dir \
 | 
				
			||||||
 | 
					        wheel==0.37.1 \
 | 
				
			||||||
 | 
					        platformio==5.2.5 \
 | 
				
			||||||
 | 
					    # Change some platformio settings
 | 
				
			||||||
 | 
					    && platformio settings set enable_telemetry No \
 | 
				
			||||||
 | 
					    && platformio settings set check_libraries_interval 1000000 \
 | 
				
			||||||
 | 
					    && platformio settings set check_platformio_interval 1000000 \
 | 
				
			||||||
 | 
					    && platformio settings set check_platforms_interval 1000000 \
 | 
				
			||||||
 | 
					    && mkdir -p /piolibs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# First install requirements to leverage caching when requirements don't change
 | 
					# First install requirements to leverage caching when requirements don't change
 | 
				
			||||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
 | 
					COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
 | 
				
			||||||
@@ -7,9 +60,13 @@ RUN \
 | 
				
			|||||||
    pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
 | 
					    pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
 | 
				
			||||||
    && /platformio_install_deps.py /platformio.ini
 | 
					    && /platformio_install_deps.py /platformio.ini
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Then copy esphome and install
 | 
					
 | 
				
			||||||
COPY . .
 | 
					# ======================= docker-type image =======================
 | 
				
			||||||
RUN pip3 install --no-cache-dir -e .
 | 
					FROM base AS docker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Copy esphome and install
 | 
				
			||||||
 | 
					COPY . /esphome
 | 
				
			||||||
 | 
					RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Settings for dashboard
 | 
					# Settings for dashboard
 | 
				
			||||||
ENV USERNAME="" PASSWORD=""
 | 
					ENV USERNAME="" PASSWORD=""
 | 
				
			||||||
@@ -17,14 +74,77 @@ ENV USERNAME="" PASSWORD=""
 | 
				
			|||||||
# Expose the dashboard to Docker
 | 
					# Expose the dashboard to Docker
 | 
				
			||||||
EXPOSE 6052
 | 
					EXPOSE 6052
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Run healthcheck (heartbeat)
 | 
					COPY docker/docker_entrypoint.sh /entrypoint.sh
 | 
				
			||||||
HEALTHCHECK --interval=30s --timeout=30s \
 | 
					 | 
				
			||||||
  CMD curl --fail http://localhost:6052 || exit 1
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# The directory the user should mount their configuration files to
 | 
					# The directory the user should mount their configuration files to
 | 
				
			||||||
 | 
					VOLUME /config
 | 
				
			||||||
WORKDIR /config
 | 
					WORKDIR /config
 | 
				
			||||||
# Set entrypoint to esphome so that the user doesn't have to type 'esphome'
 | 
					# Set entrypoint to esphome (via a script) so that the user doesn't have to type 'esphome'
 | 
				
			||||||
# in every docker command twice
 | 
					# in every docker command twice
 | 
				
			||||||
ENTRYPOINT ["esphome"]
 | 
					ENTRYPOINT ["/entrypoint.sh"]
 | 
				
			||||||
# When no arguments given, start the dashboard in the workdir
 | 
					# When no arguments given, start the dashboard in the workdir
 | 
				
			||||||
CMD ["dashboard", "/config"]
 | 
					CMD ["dashboard", "/config"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ======================= hassio-type image =======================
 | 
				
			||||||
 | 
					FROM base AS hassio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN \
 | 
				
			||||||
 | 
					    apt-get update \
 | 
				
			||||||
 | 
					    # Use pinned versions so that we get updates with build caching
 | 
				
			||||||
 | 
					    && apt-get install -y --no-install-recommends \
 | 
				
			||||||
 | 
					        nginx-light=1.18.0-6.1 \
 | 
				
			||||||
 | 
					    && rm -rf \
 | 
				
			||||||
 | 
					        /tmp/* \
 | 
				
			||||||
 | 
					        /var/{cache,log}/* \
 | 
				
			||||||
 | 
					        /var/lib/apt/lists/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ARG BUILD_VERSION=dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Copy root filesystem
 | 
				
			||||||
 | 
					COPY docker/ha-addon-rootfs/ /
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Copy esphome and install
 | 
				
			||||||
 | 
					COPY . /esphome
 | 
				
			||||||
 | 
					RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Labels
 | 
				
			||||||
 | 
					LABEL \
 | 
				
			||||||
 | 
					    io.hass.name="ESPHome" \
 | 
				
			||||||
 | 
					    io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
 | 
				
			||||||
 | 
					    io.hass.type="addon" \
 | 
				
			||||||
 | 
					    io.hass.version="${BUILD_VERSION}"
 | 
				
			||||||
 | 
					    # io.hass.arch is inherited from addon-debian-base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ======================= lint-type image =======================
 | 
				
			||||||
 | 
					FROM base AS lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENV \
 | 
				
			||||||
 | 
					  PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN \
 | 
				
			||||||
 | 
					    apt-get update \
 | 
				
			||||||
 | 
					    # Use pinned versions so that we get updates with build caching
 | 
				
			||||||
 | 
					    && apt-get install -y --no-install-recommends \
 | 
				
			||||||
 | 
					        clang-format-11=1:11.0.1-2 \
 | 
				
			||||||
 | 
					        clang-tidy-11=1:11.0.1-2 \
 | 
				
			||||||
 | 
					        patch=2.7.6-7 \
 | 
				
			||||||
 | 
					        software-properties-common=0.96.20.2-2.1 \
 | 
				
			||||||
 | 
					        nano=5.4-2 \
 | 
				
			||||||
 | 
					        build-essential=12.9 \
 | 
				
			||||||
 | 
					        python3-dev=3.9.2-3 \
 | 
				
			||||||
 | 
					    && rm -rf \
 | 
				
			||||||
 | 
					        /tmp/* \
 | 
				
			||||||
 | 
					        /var/{cache,log}/* \
 | 
				
			||||||
 | 
					        /var/lib/apt/lists/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY requirements_test.txt /
 | 
				
			||||||
 | 
					RUN pip3 install --no-cache-dir -r /requirements_test.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					VOLUME ["/esphome"]
 | 
				
			||||||
 | 
					WORKDIR /esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +0,0 @@
 | 
				
			|||||||
FROM esphome/esphome-base-amd64:3.4.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY . .
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN apt-get update \
 | 
					 | 
				
			||||||
    && apt-get install -y --no-install-recommends \
 | 
					 | 
				
			||||||
        python3-wheel \
 | 
					 | 
				
			||||||
        net-tools \
 | 
					 | 
				
			||||||
    && apt-get clean \
 | 
					 | 
				
			||||||
    && rm -rf /var/lib/apt/lists/*
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
WORKDIR /workspaces
 | 
					 | 
				
			||||||
ENV SHELL /bin/bash
 | 
					 | 
				
			||||||
@@ -1,25 +0,0 @@
 | 
				
			|||||||
ARG BUILD_FROM
 | 
					 | 
				
			||||||
FROM ${BUILD_FROM}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# First install requirements to leverage caching when requirements don't change
 | 
					 | 
				
			||||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
 | 
					 | 
				
			||||||
RUN \
 | 
					 | 
				
			||||||
    pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
 | 
					 | 
				
			||||||
    && /platformio_install_deps.py /platformio.ini
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Copy root filesystem
 | 
					 | 
				
			||||||
COPY docker/rootfs/ /
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Then copy esphome and install
 | 
					 | 
				
			||||||
COPY . /opt/esphome/
 | 
					 | 
				
			||||||
RUN pip3 install --no-cache-dir -e /opt/esphome
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Build arguments
 | 
					 | 
				
			||||||
ARG BUILD_VERSION=dev
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Labels
 | 
					 | 
				
			||||||
LABEL \
 | 
					 | 
				
			||||||
    io.hass.name="ESPHome" \
 | 
					 | 
				
			||||||
    io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
 | 
					 | 
				
			||||||
    io.hass.type="addon" \
 | 
					 | 
				
			||||||
    io.hass.version=${BUILD_VERSION}
 | 
					 | 
				
			||||||
@@ -1,9 +0,0 @@
 | 
				
			|||||||
FROM esphome/esphome-lint-base:3.4.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py  platformio.ini /
 | 
					 | 
				
			||||||
RUN \
 | 
					 | 
				
			||||||
    pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \
 | 
					 | 
				
			||||||
    && /platformio_install_deps.py /platformio.ini
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
VOLUME ["/esphome"]
 | 
					 | 
				
			||||||
WORKDIR /esphome
 | 
					 | 
				
			||||||
							
								
								
									
										162
									
								
								docker/build.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										162
									
								
								docker/build.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,162 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					from dataclasses import dataclass
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import argparse
 | 
				
			||||||
 | 
					from platform import machine
 | 
				
			||||||
 | 
					import shlex
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CHANNEL_DEV = 'dev'
 | 
				
			||||||
 | 
					CHANNEL_BETA = 'beta'
 | 
				
			||||||
 | 
					CHANNEL_RELEASE = 'release'
 | 
				
			||||||
 | 
					CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ARCH_AMD64 = 'amd64'
 | 
				
			||||||
 | 
					ARCH_ARMV7 = 'armv7'
 | 
				
			||||||
 | 
					ARCH_AARCH64 = 'aarch64'
 | 
				
			||||||
 | 
					ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TYPE_DOCKER = 'docker'
 | 
				
			||||||
 | 
					TYPE_HA_ADDON = 'ha-addon'
 | 
				
			||||||
 | 
					TYPE_LINT = 'lint'
 | 
				
			||||||
 | 
					TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					parser = argparse.ArgumentParser()
 | 
				
			||||||
 | 
					parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag")
 | 
				
			||||||
 | 
					parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for")
 | 
				
			||||||
 | 
					parser.add_argument("--build-type", choices=TYPES, required=True, help="The type of build to run")
 | 
				
			||||||
 | 
					parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them")
 | 
				
			||||||
 | 
					subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
 | 
				
			||||||
 | 
					build_parser = subparsers.add_parser("build", help="Build the image")
 | 
				
			||||||
 | 
					build_parser.add_argument("--push", help="Also push the images", action="store_true")
 | 
				
			||||||
 | 
					build_parser.add_argument("--load", help="Load the docker image locally", action="store_true")
 | 
				
			||||||
 | 
					manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@dataclass(frozen=True)
 | 
				
			||||||
 | 
					class DockerParams:
 | 
				
			||||||
 | 
					    build_to: str
 | 
				
			||||||
 | 
					    manifest_to: str
 | 
				
			||||||
 | 
					    baseimgtype: str
 | 
				
			||||||
 | 
					    platform: str
 | 
				
			||||||
 | 
					    target: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def for_type_arch(cls, build_type, arch):
 | 
				
			||||||
 | 
					        prefix = {
 | 
				
			||||||
 | 
					            TYPE_DOCKER: "esphome/esphome",
 | 
				
			||||||
 | 
					            TYPE_HA_ADDON: "esphome/esphome-hassio",
 | 
				
			||||||
 | 
					            TYPE_LINT: "esphome/esphome-lint"
 | 
				
			||||||
 | 
					        }[build_type]
 | 
				
			||||||
 | 
					        build_to = f"{prefix}-{arch}"
 | 
				
			||||||
 | 
					        baseimgtype = {
 | 
				
			||||||
 | 
					            TYPE_DOCKER: "docker",
 | 
				
			||||||
 | 
					            TYPE_HA_ADDON: "hassio",
 | 
				
			||||||
 | 
					            TYPE_LINT: "docker",
 | 
				
			||||||
 | 
					        }[build_type]
 | 
				
			||||||
 | 
					        platform = {
 | 
				
			||||||
 | 
					            ARCH_AMD64: "linux/amd64",
 | 
				
			||||||
 | 
					            ARCH_ARMV7: "linux/arm/v7",
 | 
				
			||||||
 | 
					            ARCH_AARCH64: "linux/arm64",
 | 
				
			||||||
 | 
					        }[arch]
 | 
				
			||||||
 | 
					        target = {
 | 
				
			||||||
 | 
					            TYPE_DOCKER: "docker",
 | 
				
			||||||
 | 
					            TYPE_HA_ADDON: "hassio",
 | 
				
			||||||
 | 
					            TYPE_LINT: "lint",
 | 
				
			||||||
 | 
					        }[build_type]
 | 
				
			||||||
 | 
					        return cls(
 | 
				
			||||||
 | 
					            build_to=build_to,
 | 
				
			||||||
 | 
					            manifest_to=prefix,
 | 
				
			||||||
 | 
					            baseimgtype=baseimgtype,
 | 
				
			||||||
 | 
					            platform=platform,
 | 
				
			||||||
 | 
					            target=target,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					    args = parser.parse_args()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run_command(*cmd, ignore_error: bool = False):
 | 
				
			||||||
 | 
					        print(f"$ {shlex.join(list(cmd))}")
 | 
				
			||||||
 | 
					        if not args.dry_run:
 | 
				
			||||||
 | 
					            rc = subprocess.call(list(cmd))
 | 
				
			||||||
 | 
					            if rc != 0 and not ignore_error:
 | 
				
			||||||
 | 
					                print("Command failed")
 | 
				
			||||||
 | 
					                sys.exit(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # detect channel from tag
 | 
				
			||||||
 | 
					    match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag)
 | 
				
			||||||
 | 
					    if match is None:
 | 
				
			||||||
 | 
					        channel = CHANNEL_DEV
 | 
				
			||||||
 | 
					    elif match.group(1) is None:
 | 
				
			||||||
 | 
					        channel = CHANNEL_RELEASE
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        channel = CHANNEL_BETA
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tags_to_push = [args.tag]
 | 
				
			||||||
 | 
					    if channel == CHANNEL_DEV:
 | 
				
			||||||
 | 
					        tags_to_push.append("dev")
 | 
				
			||||||
 | 
					    elif channel == CHANNEL_BETA:
 | 
				
			||||||
 | 
					        tags_to_push.append("beta")
 | 
				
			||||||
 | 
					    elif channel == CHANNEL_RELEASE:
 | 
				
			||||||
 | 
					        # Additionally push to beta
 | 
				
			||||||
 | 
					        tags_to_push.append("beta")
 | 
				
			||||||
 | 
					        tags_to_push.append("latest")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if args.command == "build":
 | 
				
			||||||
 | 
					        # 1. pull cache image
 | 
				
			||||||
 | 
					        params = DockerParams.for_type_arch(args.build_type, args.arch)
 | 
				
			||||||
 | 
					        cache_tag = {
 | 
				
			||||||
 | 
					            CHANNEL_DEV: "cache-dev",
 | 
				
			||||||
 | 
					            CHANNEL_BETA: "cache-beta",
 | 
				
			||||||
 | 
					            CHANNEL_RELEASE: "cache-latest",
 | 
				
			||||||
 | 
					        }[channel]
 | 
				
			||||||
 | 
					        cache_img = f"ghcr.io/{params.build_to}:{cache_tag}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push]
 | 
				
			||||||
 | 
					        imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 3. build
 | 
				
			||||||
 | 
					        cmd = [
 | 
				
			||||||
 | 
					            "docker", "buildx", "build",
 | 
				
			||||||
 | 
					            "--build-arg", f"BASEIMGTYPE={params.baseimgtype}",
 | 
				
			||||||
 | 
					            "--build-arg", f"BUILD_VERSION={args.tag}",
 | 
				
			||||||
 | 
					            "--cache-from", f"type=registry,ref={cache_img}",
 | 
				
			||||||
 | 
					            "--file", "docker/Dockerfile",
 | 
				
			||||||
 | 
					            "--platform", params.platform,
 | 
				
			||||||
 | 
					            "--target", params.target,
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        for img in imgs:
 | 
				
			||||||
 | 
					            cmd += ["--tag", img]
 | 
				
			||||||
 | 
					        if args.push:
 | 
				
			||||||
 | 
					            cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"]
 | 
				
			||||||
 | 
					        if args.load:
 | 
				
			||||||
 | 
					            cmd += ["--load"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        run_command(*cmd, ".")
 | 
				
			||||||
 | 
					    elif args.command == "manifest":
 | 
				
			||||||
 | 
					        manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        targets = [f"{manifest}:{tag}" for tag in tags_to_push]
 | 
				
			||||||
 | 
					        targets += [f"ghcr.io/{manifest}:{tag}" for tag in tags_to_push]
 | 
				
			||||||
 | 
					        # 1. Create manifests
 | 
				
			||||||
 | 
					        for target in targets:
 | 
				
			||||||
 | 
					            cmd = ["docker", "manifest", "create", target]
 | 
				
			||||||
 | 
					            for arch in ARCHS:
 | 
				
			||||||
 | 
					                src = f"{DockerParams.for_type_arch(args.build_type, arch).build_to}:{args.tag}"
 | 
				
			||||||
 | 
					                if target.startswith("ghcr.io"):
 | 
				
			||||||
 | 
					                    src = f"ghcr.io/{src}"
 | 
				
			||||||
 | 
					                cmd.append(src)
 | 
				
			||||||
 | 
					            run_command(*cmd)
 | 
				
			||||||
 | 
					        # 2. Push manifests
 | 
				
			||||||
 | 
					        for target in targets:
 | 
				
			||||||
 | 
					            run_command(
 | 
				
			||||||
 | 
					                "docker", "manifest", "push", target
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
							
								
								
									
										24
									
								
								docker/docker_entrypoint.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										24
									
								
								docker/docker_entrypoint.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If /cache is mounted, use that as PIO's coredir
 | 
				
			||||||
 | 
					# otherwise use path in /config (so that PIO packages aren't downloaded on each compile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [[ -d /cache ]]; then
 | 
				
			||||||
 | 
					    pio_cache_base=/cache/platformio
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					    pio_cache_base=/config/.esphome/platformio
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [[ ! -d "${pio_cache_base}" ]]; then
 | 
				
			||||||
 | 
					    echo "Creating cache directory ${pio_cache_base}"
 | 
				
			||||||
 | 
					    echo "You can change this behavior by mounting a directory to the container's /cache directory."
 | 
				
			||||||
 | 
					    mkdir -p "${pio_cache_base}"
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
 | 
				
			||||||
 | 
					# setting `core_dir` would therefore prevent pio from accessing
 | 
				
			||||||
 | 
					export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
 | 
				
			||||||
 | 
					export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
 | 
				
			||||||
 | 
					export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec esphome "$@"
 | 
				
			||||||
@@ -7,12 +7,12 @@
 | 
				
			|||||||
# Check SSL requirements, if enabled
 | 
					# Check SSL requirements, if enabled
 | 
				
			||||||
if bashio::config.true 'ssl'; then
 | 
					if bashio::config.true 'ssl'; then
 | 
				
			||||||
    if ! bashio::config.has_value 'certfile'; then
 | 
					    if ! bashio::config.has_value 'certfile'; then
 | 
				
			||||||
        bashio::fatal 'SSL is enabled, but no certfile was specified.'
 | 
					        bashio::log.fatal 'SSL is enabled, but no certfile was specified.'
 | 
				
			||||||
        bashio::exit.nok
 | 
					        bashio::exit.nok
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if ! bashio::config.has_value 'keyfile'; then
 | 
					    if ! bashio::config.has_value 'keyfile'; then
 | 
				
			||||||
        bashio::fatal 'SSL is enabled, but no keyfile was specified'
 | 
					        bashio::log.fatal 'SSL is enabled, but no keyfile was specified'
 | 
				
			||||||
        bashio::exit.nok
 | 
					        bashio::exit.nok
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								docker/ha-addon-rootfs/etc/cont-init.d/30-dirs.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								docker/ha-addon-rootfs/etc/cont-init.d/30-dirs.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/with-contenv bashio
 | 
				
			||||||
 | 
					# ==============================================================================
 | 
				
			||||||
 | 
					# Community Hass.io Add-ons: ESPHome
 | 
				
			||||||
 | 
					# This files creates all directories used by esphome
 | 
				
			||||||
 | 
					# ==============================================================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pio_cache_base=/data/cache/platformio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mkdir -p "${pio_cache_base}"
 | 
				
			||||||
@@ -10,7 +10,7 @@ server {
 | 
				
			|||||||
    ssl_certificate_key /ssl/%%keyfile%%;
 | 
					    ssl_certificate_key /ssl/%%keyfile%%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Clear Hass.io Ingress header
 | 
					    # Clear Hass.io Ingress header
 | 
				
			||||||
    proxy_set_header X-Hassio-Ingress "";
 | 
					    proxy_set_header X-HA-Ingress "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Redirect http requests to https on the same port.
 | 
					    # Redirect http requests to https on the same port.
 | 
				
			||||||
    # https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/
 | 
					    # https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/
 | 
				
			||||||
@@ -4,7 +4,7 @@ server {
 | 
				
			|||||||
    include /etc/nginx/includes/server_params.conf;
 | 
					    include /etc/nginx/includes/server_params.conf;
 | 
				
			||||||
    include /etc/nginx/includes/proxy_params.conf;
 | 
					    include /etc/nginx/includes/proxy_params.conf;
 | 
				
			||||||
    # Clear Hass.io Ingress header
 | 
					    # Clear Hass.io Ingress header
 | 
				
			||||||
    proxy_set_header X-Hassio-Ingress "";
 | 
					    proxy_set_header X-HA-Ingress "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    location / {
 | 
					    location / {
 | 
				
			||||||
        proxy_pass http://esphome;
 | 
					        proxy_pass http://esphome;
 | 
				
			||||||
@@ -3,8 +3,8 @@ server {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    include /etc/nginx/includes/server_params.conf;
 | 
					    include /etc/nginx/includes/server_params.conf;
 | 
				
			||||||
    include /etc/nginx/includes/proxy_params.conf;
 | 
					    include /etc/nginx/includes/proxy_params.conf;
 | 
				
			||||||
    # Set Hass.io Ingress header
 | 
					    # Set Home Assistant Ingress header
 | 
				
			||||||
    proxy_set_header X-Hassio-Ingress "YES";
 | 
					    proxy_set_header X-HA-Ingress "YES";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    location / {
 | 
					    location / {
 | 
				
			||||||
        # Only allow from Hass.io supervisor
 | 
					        # Only allow from Hass.io supervisor
 | 
				
			||||||
@@ -4,7 +4,7 @@
 | 
				
			|||||||
# Runs the ESPHome dashboard
 | 
					# Runs the ESPHome dashboard
 | 
				
			||||||
# ==============================================================================
 | 
					# ==============================================================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export ESPHOME_IS_HASSIO=true
 | 
					export ESPHOME_IS_HA_ADDON=true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if bashio::config.true 'leave_front_door_open'; then
 | 
					if bashio::config.true 'leave_front_door_open'; then
 | 
				
			||||||
    export DISABLE_HA_AUTHENTICATION=true
 | 
					    export DISABLE_HA_AUTHENTICATION=true
 | 
				
			||||||
@@ -22,5 +22,14 @@ if bashio::config.has_value 'relative_url'; then
 | 
				
			|||||||
    export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
 | 
					    export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pio_cache_base=/data/cache/platformio
 | 
				
			||||||
 | 
					# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
 | 
				
			||||||
 | 
					# setting `core_dir` would therefore prevent pio from accessing
 | 
				
			||||||
 | 
					export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
 | 
				
			||||||
 | 
					export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
 | 
				
			||||||
 | 
					export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export PLATFORMIO_GLOBALLIB_DIR=/piolibs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bashio::log.info "Starting ESPHome dashboard..."
 | 
					bashio::log.info "Starting ESPHome dashboard..."
 | 
				
			||||||
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio
 | 
					exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon
 | 
				
			||||||
@@ -3,18 +3,28 @@
 | 
				
			|||||||
# all platformio libraries in the global storage
 | 
					# all platformio libraries in the global storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import configparser
 | 
					import configparser
 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config = configparser.ConfigParser()
 | 
					config = configparser.ConfigParser(inline_comment_prefixes=(';', ))
 | 
				
			||||||
config.read(sys.argv[1])
 | 
					config.read(sys.argv[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
libs = []
 | 
					libs = []
 | 
				
			||||||
for line in config['common']['lib_deps'].splitlines():
 | 
					# Extract from every lib_deps key in all sections
 | 
				
			||||||
    # Format: '1655@1.0.2  ; TinyGPSPlus (has name conflict)' (includes comment)
 | 
					for section in config.sections():
 | 
				
			||||||
    m = re.search(r'([a-zA-Z0-9-_/]+@[0-9\.]+)', line)
 | 
					    conf = config[section]
 | 
				
			||||||
    if m is None:
 | 
					    if "lib_deps" not in conf:
 | 
				
			||||||
        continue
 | 
					        continue
 | 
				
			||||||
    libs.append(m.group(1))
 | 
					    for lib_dep in conf["lib_deps"].splitlines():
 | 
				
			||||||
 | 
					        if not lib_dep:
 | 
				
			||||||
 | 
					            # Empty line or comment
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        if lib_dep.startswith("${"):
 | 
				
			||||||
 | 
					            # Extending from another section
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        if "@" not in lib_dep:
 | 
				
			||||||
 | 
					            # No version pinned, this is an internal lib
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        libs.append(lib_dep)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])
 | 
					subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/with-contenv bashio
 | 
					 | 
				
			||||||
# ==============================================================================
 | 
					 | 
				
			||||||
# Community Hass.io Add-ons: ESPHome
 | 
					 | 
				
			||||||
# This files installs the user ESPHome version if specified
 | 
					 | 
				
			||||||
# ==============================================================================
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
declare esphome_version
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if bashio::config.has_value 'esphome_version'; then
 | 
					 | 
				
			||||||
    esphome_version=$(bashio::config 'esphome_version')
 | 
					 | 
				
			||||||
    if [[ $esphome_version == *":"* ]]; then
 | 
					 | 
				
			||||||
      IFS=':' read -r -a array <<< "$esphome_version"
 | 
					 | 
				
			||||||
      username=${array[0]}
 | 
					 | 
				
			||||||
      ref=${array[1]}
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
      username="esphome"
 | 
					 | 
				
			||||||
      ref=$esphome_version
 | 
					 | 
				
			||||||
    fi
 | 
					 | 
				
			||||||
    full_url="https://github.com/${username}/esphome/archive/${ref}.zip"
 | 
					 | 
				
			||||||
    bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..."
 | 
					 | 
				
			||||||
    pip3 install -U --no-cache-dir "${full_url}" \
 | 
					 | 
				
			||||||
      || bashio::exit.nok "Failed installing esphome pinned version."
 | 
					 | 
				
			||||||
fi
 | 
					 | 
				
			||||||
@@ -1,11 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/with-contenv bashio
 | 
					 | 
				
			||||||
# ==============================================================================
 | 
					 | 
				
			||||||
# Community Hass.io Add-ons: ESPHome
 | 
					 | 
				
			||||||
# This files migrates the esphome config directory from the old path
 | 
					 | 
				
			||||||
# ==============================================================================
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if [[ ! -d /config/esphome && -d /config/esphomeyaml ]]; then
 | 
					 | 
				
			||||||
    echo "Moving config directory from /config/esphomeyaml to /config/esphome"
 | 
					 | 
				
			||||||
    mv /config/esphomeyaml /config/esphome
 | 
					 | 
				
			||||||
    mv /config/esphome/.esphomeyaml /config/esphome/.esphome
 | 
					 | 
				
			||||||
fi
 | 
					 | 
				
			||||||
@@ -11,12 +11,14 @@ from esphome.config import iter_components, read_config, strip_default_ids
 | 
				
			|||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_BAUD_RATE,
 | 
					    CONF_BAUD_RATE,
 | 
				
			||||||
    CONF_BROKER,
 | 
					    CONF_BROKER,
 | 
				
			||||||
 | 
					    CONF_DEASSERT_RTS_DTR,
 | 
				
			||||||
    CONF_LOGGER,
 | 
					    CONF_LOGGER,
 | 
				
			||||||
    CONF_OTA,
 | 
					    CONF_OTA,
 | 
				
			||||||
    CONF_PASSWORD,
 | 
					    CONF_PASSWORD,
 | 
				
			||||||
    CONF_PORT,
 | 
					    CONF_PORT,
 | 
				
			||||||
    CONF_ESPHOME,
 | 
					    CONF_ESPHOME,
 | 
				
			||||||
    CONF_PLATFORMIO_OPTIONS,
 | 
					    CONF_PLATFORMIO_OPTIONS,
 | 
				
			||||||
 | 
					    SECRETS_FILES,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.core import CORE, EsphomeError, coroutine
 | 
					from esphome.core import CORE, EsphomeError, coroutine
 | 
				
			||||||
from esphome.helpers import indent
 | 
					from esphome.helpers import indent
 | 
				
			||||||
@@ -71,7 +73,7 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
 | 
				
			|||||||
        if default == "OTA":
 | 
					        if default == "OTA":
 | 
				
			||||||
            return CORE.address
 | 
					            return CORE.address
 | 
				
			||||||
    if show_mqtt and "mqtt" in CORE.config:
 | 
					    if show_mqtt and "mqtt" in CORE.config:
 | 
				
			||||||
        options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT"))
 | 
					        options.append((f"MQTT ({CORE.config['mqtt'][CONF_BROKER]})", "MQTT"))
 | 
				
			||||||
        if default == "OTA":
 | 
					        if default == "OTA":
 | 
				
			||||||
            return "MQTT"
 | 
					            return "MQTT"
 | 
				
			||||||
    if default is not None:
 | 
					    if default is not None:
 | 
				
			||||||
@@ -99,10 +101,21 @@ def run_miniterm(config, port):
 | 
				
			|||||||
    baud_rate = config["logger"][CONF_BAUD_RATE]
 | 
					    baud_rate = config["logger"][CONF_BAUD_RATE]
 | 
				
			||||||
    if baud_rate == 0:
 | 
					    if baud_rate == 0:
 | 
				
			||||||
        _LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
 | 
					        _LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
    _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
 | 
					    _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    backtrace_state = False
 | 
					    backtrace_state = False
 | 
				
			||||||
    with serial.Serial(port, baudrate=baud_rate) as ser:
 | 
					    ser = serial.Serial()
 | 
				
			||||||
 | 
					    ser.baudrate = baud_rate
 | 
				
			||||||
 | 
					    ser.port = port
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # We can't set to False by default since it leads to toggling and hence
 | 
				
			||||||
 | 
					    # ESP32 resets on some platforms.
 | 
				
			||||||
 | 
					    if config["logger"][CONF_DEASSERT_RTS_DTR]:
 | 
				
			||||||
 | 
					        ser.dtr = False
 | 
				
			||||||
 | 
					        ser.rts = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with ser:
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                raw = ser.readline()
 | 
					                raw = ser.readline()
 | 
				
			||||||
@@ -132,6 +145,8 @@ def wrap_to_code(name, comp):
 | 
				
			|||||||
        if comp.config_schema is not None:
 | 
					        if comp.config_schema is not None:
 | 
				
			||||||
            conf_str = yaml_util.dump(conf)
 | 
					            conf_str = yaml_util.dump(conf)
 | 
				
			||||||
            conf_str = conf_str.replace("//", "")
 | 
					            conf_str = conf_str.replace("//", "")
 | 
				
			||||||
 | 
					            # remove tailing \ to avoid multi-line comment warning
 | 
				
			||||||
 | 
					            conf_str = conf_str.replace("\\\n", "\n")
 | 
				
			||||||
            cg.add(cg.LineComment(indent(conf_str)))
 | 
					            cg.add(cg.LineComment(indent(conf_str)))
 | 
				
			||||||
        await coro(conf)
 | 
					        await coro(conf)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -168,16 +183,37 @@ def compile_program(args, config):
 | 
				
			|||||||
    from esphome import platformio_api
 | 
					    from esphome import platformio_api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _LOGGER.info("Compiling app...")
 | 
					    _LOGGER.info("Compiling app...")
 | 
				
			||||||
    return platformio_api.run_compile(config, CORE.verbose)
 | 
					    rc = platformio_api.run_compile(config, CORE.verbose)
 | 
				
			||||||
 | 
					    if rc != 0:
 | 
				
			||||||
 | 
					        return rc
 | 
				
			||||||
 | 
					    idedata = platformio_api.get_idedata(config)
 | 
				
			||||||
 | 
					    return 0 if idedata is not None else 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def upload_using_esptool(config, port):
 | 
					def upload_using_esptool(config, port):
 | 
				
			||||||
    path = CORE.firmware_bin
 | 
					    from esphome import platformio_api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
 | 
					    first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
 | 
				
			||||||
        "upload_speed", 460800
 | 
					        "upload_speed", 460800
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def run_esptool(baud_rate):
 | 
					    def run_esptool(baud_rate):
 | 
				
			||||||
 | 
					        idedata = platformio_api.get_idedata(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
 | 
				
			||||||
 | 
					        flash_images = [
 | 
				
			||||||
 | 
					            platformio_api.FlashImage(
 | 
				
			||||||
 | 
					                path=idedata.firmware_bin_path, offset=firmware_offset
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            *idedata.extra_flash_images,
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mcu = "esp8266"
 | 
				
			||||||
 | 
					        if CORE.is_esp32:
 | 
				
			||||||
 | 
					            from esphome.components.esp32 import get_esp32_variant
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mcu = get_esp32_variant().lower()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cmd = [
 | 
					        cmd = [
 | 
				
			||||||
            "esptool.py",
 | 
					            "esptool.py",
 | 
				
			||||||
            "--before",
 | 
					            "--before",
 | 
				
			||||||
@@ -186,14 +222,17 @@ def upload_using_esptool(config, port):
 | 
				
			|||||||
            "hard_reset",
 | 
					            "hard_reset",
 | 
				
			||||||
            "--baud",
 | 
					            "--baud",
 | 
				
			||||||
            str(baud_rate),
 | 
					            str(baud_rate),
 | 
				
			||||||
            "--chip",
 | 
					 | 
				
			||||||
            "esp8266",
 | 
					 | 
				
			||||||
            "--port",
 | 
					            "--port",
 | 
				
			||||||
            port,
 | 
					            port,
 | 
				
			||||||
 | 
					            "--chip",
 | 
				
			||||||
 | 
					            mcu,
 | 
				
			||||||
            "write_flash",
 | 
					            "write_flash",
 | 
				
			||||||
            "0x0",
 | 
					            "-z",
 | 
				
			||||||
            path,
 | 
					            "--flash_size",
 | 
				
			||||||
 | 
					            "detect",
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					        for img in flash_images:
 | 
				
			||||||
 | 
					            cmd += [img.offset, img.path]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
 | 
					        if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
 | 
				
			||||||
            import esptool
 | 
					            import esptool
 | 
				
			||||||
@@ -217,11 +256,7 @@ def upload_using_esptool(config, port):
 | 
				
			|||||||
def upload_program(config, args, host):
 | 
					def upload_program(config, args, host):
 | 
				
			||||||
    # if upload is to a serial port use platformio, otherwise assume ota
 | 
					    # if upload is to a serial port use platformio, otherwise assume ota
 | 
				
			||||||
    if get_port_type(host) == "SERIAL":
 | 
					    if get_port_type(host) == "SERIAL":
 | 
				
			||||||
        from esphome import platformio_api
 | 
					        return upload_using_esptool(config, host)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if CORE.is_esp8266:
 | 
					 | 
				
			||||||
            return upload_using_esptool(config, host)
 | 
					 | 
				
			||||||
        return platformio_api.run_upload(config, CORE.verbose, host)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    from esphome import espota2
 | 
					    from esphome import espota2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -233,7 +268,7 @@ def upload_program(config, args, host):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    ota_conf = config[CONF_OTA]
 | 
					    ota_conf = config[CONF_OTA]
 | 
				
			||||||
    remote_port = ota_conf[CONF_PORT]
 | 
					    remote_port = ota_conf[CONF_PORT]
 | 
				
			||||||
    password = ota_conf[CONF_PASSWORD]
 | 
					    password = ota_conf.get(CONF_PASSWORD, "")
 | 
				
			||||||
    return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
 | 
					    return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -244,7 +279,7 @@ def show_logs(config, args, port):
 | 
				
			|||||||
        run_miniterm(config, port)
 | 
					        run_miniterm(config, port)
 | 
				
			||||||
        return 0
 | 
					        return 0
 | 
				
			||||||
    if get_port_type(port) == "NETWORK" and "api" in config:
 | 
					    if get_port_type(port) == "NETWORK" and "api" in config:
 | 
				
			||||||
        from esphome.api.client import run_logs
 | 
					        from esphome.components.api.client import run_logs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return run_logs(config, port)
 | 
					        return run_logs(config, port)
 | 
				
			||||||
    if get_port_type(port) == "MQTT" and "mqtt" in config:
 | 
					    if get_port_type(port) == "MQTT" and "mqtt" in config:
 | 
				
			||||||
@@ -284,7 +319,6 @@ def command_vscode(args):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    logging.disable(logging.INFO)
 | 
					    logging.disable(logging.INFO)
 | 
				
			||||||
    logging.disable(logging.WARNING)
 | 
					    logging.disable(logging.WARNING)
 | 
				
			||||||
    CORE.config_path = args.configuration
 | 
					 | 
				
			||||||
    vscode.read_config(args)
 | 
					    vscode.read_config(args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -394,7 +428,7 @@ def command_update_all(args):
 | 
				
			|||||||
    import click
 | 
					    import click
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    success = {}
 | 
					    success = {}
 | 
				
			||||||
    files = list_yaml_files(args.configuration[0])
 | 
					    files = list_yaml_files(args.configuration)
 | 
				
			||||||
    twidth = 60
 | 
					    twidth = 60
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def print_bar(middle_text):
 | 
					    def print_bar(middle_text):
 | 
				
			||||||
@@ -404,34 +438,49 @@ def command_update_all(args):
 | 
				
			|||||||
        click.echo(f"{half_line}{middle_text}{half_line}")
 | 
					        click.echo(f"{half_line}{middle_text}{half_line}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for f in files:
 | 
					    for f in files:
 | 
				
			||||||
        print("Updating {}".format(color(Fore.CYAN, f)))
 | 
					        print(f"Updating {color(Fore.CYAN, f)}")
 | 
				
			||||||
        print("-" * twidth)
 | 
					        print("-" * twidth)
 | 
				
			||||||
        print()
 | 
					        print()
 | 
				
			||||||
        rc = run_external_process(
 | 
					        rc = run_external_process(
 | 
				
			||||||
            "esphome", "--dashboard", "run", "--no-logs", "--device", "OTA", f
 | 
					            "esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        if rc == 0:
 | 
					        if rc == 0:
 | 
				
			||||||
            print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
 | 
					            print_bar(f"[{color(Fore.BOLD_GREEN, 'SUCCESS')}] {f}")
 | 
				
			||||||
            success[f] = True
 | 
					            success[f] = True
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            print_bar("[{}] {}".format(color(Fore.BOLD_RED, "ERROR"), f))
 | 
					            print_bar(f"[{color(Fore.BOLD_RED, 'ERROR')}] {f}")
 | 
				
			||||||
            success[f] = False
 | 
					            success[f] = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        print()
 | 
					        print()
 | 
				
			||||||
        print()
 | 
					        print()
 | 
				
			||||||
        print()
 | 
					        print()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    print_bar("[{}]".format(color(Fore.BOLD_WHITE, "SUMMARY")))
 | 
					    print_bar(f"[{color(Fore.BOLD_WHITE, 'SUMMARY')}]")
 | 
				
			||||||
    failed = 0
 | 
					    failed = 0
 | 
				
			||||||
    for f in files:
 | 
					    for f in files:
 | 
				
			||||||
        if success[f]:
 | 
					        if success[f]:
 | 
				
			||||||
            print("  - {}: {}".format(f, color(Fore.GREEN, "SUCCESS")))
 | 
					            print(f"  - {f}: {color(Fore.GREEN, 'SUCCESS')}")
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            print("  - {}: {}".format(f, color(Fore.BOLD_RED, "FAILED")))
 | 
					            print(f"  - {f}: {color(Fore.BOLD_RED, 'FAILED')}")
 | 
				
			||||||
            failed += 1
 | 
					            failed += 1
 | 
				
			||||||
    return failed
 | 
					    return failed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def command_idedata(args, config):
 | 
				
			||||||
 | 
					    from esphome import platformio_api
 | 
				
			||||||
 | 
					    import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logging.disable(logging.INFO)
 | 
				
			||||||
 | 
					    logging.disable(logging.WARNING)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    idedata = platformio_api.get_idedata(config)
 | 
				
			||||||
 | 
					    if idedata is None:
 | 
				
			||||||
 | 
					        return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print(json.dumps(idedata.raw, indent=2) + "\n")
 | 
				
			||||||
 | 
					    return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PRE_CONFIG_ACTIONS = {
 | 
					PRE_CONFIG_ACTIONS = {
 | 
				
			||||||
    "wizard": command_wizard,
 | 
					    "wizard": command_wizard,
 | 
				
			||||||
    "version": command_version,
 | 
					    "version": command_version,
 | 
				
			||||||
@@ -449,6 +498,7 @@ POST_CONFIG_ACTIONS = {
 | 
				
			|||||||
    "clean-mqtt": command_clean_mqtt,
 | 
					    "clean-mqtt": command_clean_mqtt,
 | 
				
			||||||
    "mqtt-fingerprint": command_mqtt_fingerprint,
 | 
					    "mqtt-fingerprint": command_mqtt_fingerprint,
 | 
				
			||||||
    "clean": command_clean,
 | 
					    "clean": command_clean,
 | 
				
			||||||
 | 
					    "idedata": command_idedata,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -472,62 +522,9 @@ def parse_args(argv):
 | 
				
			|||||||
        metavar=("key", "value"),
 | 
					        metavar=("key", "value"),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Keep backward compatibility with the old command line format of
 | 
					 | 
				
			||||||
    # esphome <config> <command>.
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    # Unfortunately this can't be done by adding another configuration argument to the
 | 
					 | 
				
			||||||
    # main config parser, as argparse is greedy when parsing arguments, so in regular
 | 
					 | 
				
			||||||
    # usage it'll eat the command as the configuration argument and error out out
 | 
					 | 
				
			||||||
    # because it can't parse the configuration as a command.
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    # Instead, construct an ad-hoc parser for the old format that doesn't actually
 | 
					 | 
				
			||||||
    # process the arguments, but parses them enough to let us figure out if the old
 | 
					 | 
				
			||||||
    # format is used. In that case, swap the command and configuration in the arguments
 | 
					 | 
				
			||||||
    # and continue on with the normal parser (after raising a deprecation warning).
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    # Disable argparse's built-in help option and add it manually to prevent this
 | 
					 | 
				
			||||||
    # parser from printing the help messagefor the old format when invoked with -h.
 | 
					 | 
				
			||||||
    compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False)
 | 
					 | 
				
			||||||
    compat_parser.add_argument("-h", "--help")
 | 
					 | 
				
			||||||
    compat_parser.add_argument("configuration", nargs="*")
 | 
					 | 
				
			||||||
    compat_parser.add_argument(
 | 
					 | 
				
			||||||
        "command",
 | 
					 | 
				
			||||||
        choices=[
 | 
					 | 
				
			||||||
            "config",
 | 
					 | 
				
			||||||
            "compile",
 | 
					 | 
				
			||||||
            "upload",
 | 
					 | 
				
			||||||
            "logs",
 | 
					 | 
				
			||||||
            "run",
 | 
					 | 
				
			||||||
            "clean-mqtt",
 | 
					 | 
				
			||||||
            "wizard",
 | 
					 | 
				
			||||||
            "mqtt-fingerprint",
 | 
					 | 
				
			||||||
            "version",
 | 
					 | 
				
			||||||
            "clean",
 | 
					 | 
				
			||||||
            "dashboard",
 | 
					 | 
				
			||||||
            "vscode",
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # on Python 3.9+ we can simply set exit_on_error=False in the constructor
 | 
					 | 
				
			||||||
    def _raise(x):
 | 
					 | 
				
			||||||
        raise argparse.ArgumentError(None, x)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    compat_parser.error = _raise
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        result, unparsed = compat_parser.parse_known_args(argv[1:])
 | 
					 | 
				
			||||||
        last_option = len(argv) - len(unparsed) - 1 - len(result.configuration)
 | 
					 | 
				
			||||||
        argv = argv[0:last_option] + [result.command] + result.configuration + unparsed
 | 
					 | 
				
			||||||
        deprecated_argv_suggestion = argv
 | 
					 | 
				
			||||||
    except argparse.ArgumentError:
 | 
					 | 
				
			||||||
        # This is not an old-style command line, so we don't have to do anything.
 | 
					 | 
				
			||||||
        deprecated_argv_suggestion = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # And continue on with regular parsing
 | 
					 | 
				
			||||||
    parser = argparse.ArgumentParser(
 | 
					    parser = argparse.ArgumentParser(
 | 
				
			||||||
        description=f"ESPHome v{const.__version__}", parents=[options_parser]
 | 
					        description=f"ESPHome v{const.__version__}", parents=[options_parser]
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    mqtt_options = argparse.ArgumentParser(add_help=False)
 | 
					    mqtt_options = argparse.ArgumentParser(add_help=False)
 | 
				
			||||||
    mqtt_options.add_argument("--topic", help="Manually set the MQTT topic.")
 | 
					    mqtt_options.add_argument("--topic", help="Manually set the MQTT topic.")
 | 
				
			||||||
@@ -612,10 +609,7 @@ def parse_args(argv):
 | 
				
			|||||||
        "wizard",
 | 
					        "wizard",
 | 
				
			||||||
        help="A helpful setup wizard that will guide you through setting up ESPHome.",
 | 
					        help="A helpful setup wizard that will guide you through setting up ESPHome.",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    parser_wizard.add_argument(
 | 
					    parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
 | 
				
			||||||
        "configuration",
 | 
					 | 
				
			||||||
        help="Your YAML configuration file.",
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser_fingerprint = subparsers.add_parser(
 | 
					    parser_fingerprint = subparsers.add_parser(
 | 
				
			||||||
        "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
 | 
					        "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
 | 
				
			||||||
@@ -637,8 +631,7 @@ def parse_args(argv):
 | 
				
			|||||||
        "dashboard", help="Create a simple web server for a dashboard."
 | 
					        "dashboard", help="Create a simple web server for a dashboard."
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    parser_dashboard.add_argument(
 | 
					    parser_dashboard.add_argument(
 | 
				
			||||||
        "configuration",
 | 
					        "configuration", help="Your YAML configuration file directory."
 | 
				
			||||||
        help="Your YAML configuration file directory.",
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    parser_dashboard.add_argument(
 | 
					    parser_dashboard.add_argument(
 | 
				
			||||||
        "--port",
 | 
					        "--port",
 | 
				
			||||||
@@ -646,6 +639,12 @@ def parse_args(argv):
 | 
				
			|||||||
        type=int,
 | 
					        type=int,
 | 
				
			||||||
        default=6052,
 | 
					        default=6052,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    parser_dashboard.add_argument(
 | 
				
			||||||
 | 
					        "--address",
 | 
				
			||||||
 | 
					        help="The address to bind to.",
 | 
				
			||||||
 | 
					        type=str,
 | 
				
			||||||
 | 
					        default="0.0.0.0",
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    parser_dashboard.add_argument(
 | 
					    parser_dashboard.add_argument(
 | 
				
			||||||
        "--username",
 | 
					        "--username",
 | 
				
			||||||
        help="The optional username to require for authentication.",
 | 
					        help="The optional username to require for authentication.",
 | 
				
			||||||
@@ -662,43 +661,127 @@ def parse_args(argv):
 | 
				
			|||||||
        "--open-ui", help="Open the dashboard UI in a browser.", action="store_true"
 | 
					        "--open-ui", help="Open the dashboard UI in a browser.", action="store_true"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    parser_dashboard.add_argument(
 | 
					    parser_dashboard.add_argument(
 | 
				
			||||||
        "--hassio", help=argparse.SUPPRESS, action="store_true"
 | 
					        "--ha-addon", help=argparse.SUPPRESS, action="store_true"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    parser_dashboard.add_argument(
 | 
					    parser_dashboard.add_argument(
 | 
				
			||||||
        "--socket", help="Make the dashboard serve under a unix socket", type=str
 | 
					        "--socket", help="Make the dashboard serve under a unix socket", type=str
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser_vscode = subparsers.add_parser("vscode")
 | 
					    parser_vscode = subparsers.add_parser("vscode")
 | 
				
			||||||
    parser_vscode.add_argument(
 | 
					    parser_vscode.add_argument("configuration", help="Your YAML configuration file.")
 | 
				
			||||||
        "configuration", help="Your YAML configuration file.", nargs=1
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    parser_vscode.add_argument("--ace", action="store_true")
 | 
					    parser_vscode.add_argument("--ace", action="store_true")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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 directory.", nargs=1
 | 
					        "configuration", help="Your YAML configuration file directories.", nargs="+"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return parser.parse_args(argv[1:])
 | 
					    parser_idedata = subparsers.add_parser("idedata")
 | 
				
			||||||
 | 
					    parser_idedata.add_argument(
 | 
				
			||||||
 | 
					        "configuration", help="Your YAML configuration file(s).", nargs=1
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Keep backward compatibility with the old command line format of
 | 
				
			||||||
 | 
					    # esphome <config> <command>.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # Unfortunately this can't be done by adding another configuration argument to the
 | 
				
			||||||
 | 
					    # main config parser, as argparse is greedy when parsing arguments, so in regular
 | 
				
			||||||
 | 
					    # usage it'll eat the command as the configuration argument and error out out
 | 
				
			||||||
 | 
					    # because it can't parse the configuration as a command.
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # Instead, if parsing using the current format fails, construct an ad-hoc parser
 | 
				
			||||||
 | 
					    # that doesn't actually process the arguments, but parses them enough to let us
 | 
				
			||||||
 | 
					    # figure out if the old format is used. In that case, swap the command and
 | 
				
			||||||
 | 
					    # configuration in the arguments and retry with the normal parser (and raise
 | 
				
			||||||
 | 
					    # a deprecation warning).
 | 
				
			||||||
 | 
					    arguments = argv[1:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # On Python 3.9+ we can simply set exit_on_error=False in the constructor
 | 
				
			||||||
 | 
					    def _raise(x):
 | 
				
			||||||
 | 
					        raise argparse.ArgumentError(None, x)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # First, try new-style parsing, but don't exit in case of failure
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        # duplicate parser so that we can use the original one to raise errors later on
 | 
				
			||||||
 | 
					        current_parser = argparse.ArgumentParser(add_help=False, parents=[parser])
 | 
				
			||||||
 | 
					        current_parser.set_defaults(deprecated_argv_suggestion=None)
 | 
				
			||||||
 | 
					        current_parser.error = _raise
 | 
				
			||||||
 | 
					        return current_parser.parse_args(arguments)
 | 
				
			||||||
 | 
					    except argparse.ArgumentError:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Second, try compat parsing and rearrange the command-line if it succeeds
 | 
				
			||||||
 | 
					    # Disable argparse's built-in help option and add it manually to prevent this
 | 
				
			||||||
 | 
					    # parser from printing the help messagefor the old format when invoked with -h.
 | 
				
			||||||
 | 
					    compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False)
 | 
				
			||||||
 | 
					    compat_parser.add_argument("-h", "--help", action="store_true")
 | 
				
			||||||
 | 
					    compat_parser.add_argument("configuration", nargs="*")
 | 
				
			||||||
 | 
					    compat_parser.add_argument(
 | 
				
			||||||
 | 
					        "command",
 | 
				
			||||||
 | 
					        choices=[
 | 
				
			||||||
 | 
					            "config",
 | 
				
			||||||
 | 
					            "compile",
 | 
				
			||||||
 | 
					            "upload",
 | 
				
			||||||
 | 
					            "logs",
 | 
				
			||||||
 | 
					            "run",
 | 
				
			||||||
 | 
					            "clean-mqtt",
 | 
				
			||||||
 | 
					            "wizard",
 | 
				
			||||||
 | 
					            "mqtt-fingerprint",
 | 
				
			||||||
 | 
					            "version",
 | 
				
			||||||
 | 
					            "clean",
 | 
				
			||||||
 | 
					            "dashboard",
 | 
				
			||||||
 | 
					            "vscode",
 | 
				
			||||||
 | 
					            "update-all",
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        compat_parser.error = _raise
 | 
				
			||||||
 | 
					        result, unparsed = compat_parser.parse_known_args(argv[1:])
 | 
				
			||||||
 | 
					        last_option = len(arguments) - len(unparsed) - 1 - len(result.configuration)
 | 
				
			||||||
 | 
					        unparsed = [
 | 
				
			||||||
 | 
					            "--device" if arg in ("--upload-port", "--serial-port") else arg
 | 
				
			||||||
 | 
					            for arg in unparsed
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        arguments = (
 | 
				
			||||||
 | 
					            arguments[0:last_option]
 | 
				
			||||||
 | 
					            + [result.command]
 | 
				
			||||||
 | 
					            + result.configuration
 | 
				
			||||||
 | 
					            + unparsed
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        deprecated_argv_suggestion = arguments
 | 
				
			||||||
 | 
					    except argparse.ArgumentError:
 | 
				
			||||||
 | 
					        # old-style parsing failed, don't suggest any argument
 | 
				
			||||||
 | 
					        deprecated_argv_suggestion = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Finally, run the new-style parser again with the possibly swapped arguments,
 | 
				
			||||||
 | 
					    # and let it error out if the command is unparsable.
 | 
				
			||||||
 | 
					    parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
 | 
				
			||||||
 | 
					    return parser.parse_args(arguments)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def run_esphome(argv):
 | 
					def run_esphome(argv):
 | 
				
			||||||
    args = parse_args(argv)
 | 
					    args = parse_args(argv)
 | 
				
			||||||
    CORE.dashboard = args.dashboard
 | 
					    CORE.dashboard = args.dashboard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setup_log(args.verbose, args.quiet)
 | 
					    setup_log(
 | 
				
			||||||
 | 
					        args.verbose,
 | 
				
			||||||
 | 
					        args.quiet,
 | 
				
			||||||
 | 
					        # Show timestamp for dashboard access logs
 | 
				
			||||||
 | 
					        args.command == "dashboard",
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    if args.deprecated_argv_suggestion is not None and args.command != "vscode":
 | 
					    if args.deprecated_argv_suggestion is not None and args.command != "vscode":
 | 
				
			||||||
        _LOGGER.warning(
 | 
					        _LOGGER.warning(
 | 
				
			||||||
            "Calling ESPHome with the configuration before the command is deprecated "
 | 
					            "Calling ESPHome with the configuration before the command is deprecated "
 | 
				
			||||||
            "and will be removed in the future. "
 | 
					            "and will be removed in the future. "
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        _LOGGER.warning("Please instead use:")
 | 
					        _LOGGER.warning("Please instead use:")
 | 
				
			||||||
        _LOGGER.warning("   esphome %s", " ".join(args.deprecated_argv_suggestion[1:]))
 | 
					        _LOGGER.warning("   esphome %s", " ".join(args.deprecated_argv_suggestion))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if sys.version_info < (3, 7, 0):
 | 
					    if sys.version_info < (3, 8, 0):
 | 
				
			||||||
        _LOGGER.error(
 | 
					        _LOGGER.error(
 | 
				
			||||||
            "You're running ESPHome with Python <3.7. ESPHome is no longer compatible "
 | 
					            "You're running ESPHome with Python <3.8. ESPHome is no longer compatible "
 | 
				
			||||||
            "with this Python version. Please reinstall ESPHome with Python 3.7+"
 | 
					            "with this Python version. Please reinstall ESPHome with Python 3.8+"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        return 1
 | 
					        return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -710,12 +793,16 @@ def run_esphome(argv):
 | 
				
			|||||||
            return 1
 | 
					            return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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):
 | 
				
			||||||
 | 
					            _LOGGER.warning("Skipping secrets file %s", conf_path)
 | 
				
			||||||
 | 
					            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 {})
 | 
					        config = read_config(dict(args.substitution) if args.substitution else {})
 | 
				
			||||||
        if config is None:
 | 
					        if config is None:
 | 
				
			||||||
            return 1
 | 
					            return 2
 | 
				
			||||||
        CORE.config = config
 | 
					        CORE.config = config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if args.command not in POST_CONFIG_ACTIONS:
 | 
					        if args.command not in POST_CONFIG_ACTIONS:
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -1,518 +0,0 @@
 | 
				
			|||||||
from datetime import datetime
 | 
					 | 
				
			||||||
import functools
 | 
					 | 
				
			||||||
import logging
 | 
					 | 
				
			||||||
import socket
 | 
					 | 
				
			||||||
import threading
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# pylint: disable=unused-import
 | 
					 | 
				
			||||||
from typing import Optional  # noqa
 | 
					 | 
				
			||||||
from google.protobuf import message  # noqa
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from esphome import const
 | 
					 | 
				
			||||||
import esphome.api.api_pb2 as pb
 | 
					 | 
				
			||||||
from esphome.const import CONF_PASSWORD, CONF_PORT
 | 
					 | 
				
			||||||
from esphome.core import EsphomeError
 | 
					 | 
				
			||||||
from esphome.helpers import resolve_ip_address, indent
 | 
					 | 
				
			||||||
from esphome.log import color, Fore
 | 
					 | 
				
			||||||
from esphome.util import safe_print
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_LOGGER = logging.getLogger(__name__)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class APIConnectionError(EsphomeError):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
MESSAGE_TYPE_TO_PROTO = {
 | 
					 | 
				
			||||||
    1: pb.HelloRequest,
 | 
					 | 
				
			||||||
    2: pb.HelloResponse,
 | 
					 | 
				
			||||||
    3: pb.ConnectRequest,
 | 
					 | 
				
			||||||
    4: pb.ConnectResponse,
 | 
					 | 
				
			||||||
    5: pb.DisconnectRequest,
 | 
					 | 
				
			||||||
    6: pb.DisconnectResponse,
 | 
					 | 
				
			||||||
    7: pb.PingRequest,
 | 
					 | 
				
			||||||
    8: pb.PingResponse,
 | 
					 | 
				
			||||||
    9: pb.DeviceInfoRequest,
 | 
					 | 
				
			||||||
    10: pb.DeviceInfoResponse,
 | 
					 | 
				
			||||||
    11: pb.ListEntitiesRequest,
 | 
					 | 
				
			||||||
    12: pb.ListEntitiesBinarySensorResponse,
 | 
					 | 
				
			||||||
    13: pb.ListEntitiesCoverResponse,
 | 
					 | 
				
			||||||
    14: pb.ListEntitiesFanResponse,
 | 
					 | 
				
			||||||
    15: pb.ListEntitiesLightResponse,
 | 
					 | 
				
			||||||
    16: pb.ListEntitiesSensorResponse,
 | 
					 | 
				
			||||||
    17: pb.ListEntitiesSwitchResponse,
 | 
					 | 
				
			||||||
    18: pb.ListEntitiesTextSensorResponse,
 | 
					 | 
				
			||||||
    19: pb.ListEntitiesDoneResponse,
 | 
					 | 
				
			||||||
    20: pb.SubscribeStatesRequest,
 | 
					 | 
				
			||||||
    21: pb.BinarySensorStateResponse,
 | 
					 | 
				
			||||||
    22: pb.CoverStateResponse,
 | 
					 | 
				
			||||||
    23: pb.FanStateResponse,
 | 
					 | 
				
			||||||
    24: pb.LightStateResponse,
 | 
					 | 
				
			||||||
    25: pb.SensorStateResponse,
 | 
					 | 
				
			||||||
    26: pb.SwitchStateResponse,
 | 
					 | 
				
			||||||
    27: pb.TextSensorStateResponse,
 | 
					 | 
				
			||||||
    28: pb.SubscribeLogsRequest,
 | 
					 | 
				
			||||||
    29: pb.SubscribeLogsResponse,
 | 
					 | 
				
			||||||
    30: pb.CoverCommandRequest,
 | 
					 | 
				
			||||||
    31: pb.FanCommandRequest,
 | 
					 | 
				
			||||||
    32: pb.LightCommandRequest,
 | 
					 | 
				
			||||||
    33: pb.SwitchCommandRequest,
 | 
					 | 
				
			||||||
    34: pb.SubscribeServiceCallsRequest,
 | 
					 | 
				
			||||||
    35: pb.ServiceCallResponse,
 | 
					 | 
				
			||||||
    36: pb.GetTimeRequest,
 | 
					 | 
				
			||||||
    37: pb.GetTimeResponse,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def _varuint_to_bytes(value):
 | 
					 | 
				
			||||||
    if value <= 0x7F:
 | 
					 | 
				
			||||||
        return bytes([value])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ret = bytes()
 | 
					 | 
				
			||||||
    while value:
 | 
					 | 
				
			||||||
        temp = value & 0x7F
 | 
					 | 
				
			||||||
        value >>= 7
 | 
					 | 
				
			||||||
        if value:
 | 
					 | 
				
			||||||
            ret += bytes([temp | 0x80])
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            ret += bytes([temp])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return ret
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def _bytes_to_varuint(value):
 | 
					 | 
				
			||||||
    result = 0
 | 
					 | 
				
			||||||
    bitpos = 0
 | 
					 | 
				
			||||||
    for val in value:
 | 
					 | 
				
			||||||
        result |= (val & 0x7F) << bitpos
 | 
					 | 
				
			||||||
        bitpos += 7
 | 
					 | 
				
			||||||
        if (val & 0x80) == 0:
 | 
					 | 
				
			||||||
            return result
 | 
					 | 
				
			||||||
    return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# pylint: disable=too-many-instance-attributes,not-callable
 | 
					 | 
				
			||||||
class APIClient(threading.Thread):
 | 
					 | 
				
			||||||
    def __init__(self, address, port, password):
 | 
					 | 
				
			||||||
        threading.Thread.__init__(self)
 | 
					 | 
				
			||||||
        self._address = address  # type: str
 | 
					 | 
				
			||||||
        self._port = port  # type: int
 | 
					 | 
				
			||||||
        self._password = password  # type: Optional[str]
 | 
					 | 
				
			||||||
        self._socket = None  # type: Optional[socket.socket]
 | 
					 | 
				
			||||||
        self._socket_open_event = threading.Event()
 | 
					 | 
				
			||||||
        self._socket_write_lock = threading.Lock()
 | 
					 | 
				
			||||||
        self._connected = False
 | 
					 | 
				
			||||||
        self._authenticated = False
 | 
					 | 
				
			||||||
        self._message_handlers = []
 | 
					 | 
				
			||||||
        self._keepalive = 5
 | 
					 | 
				
			||||||
        self._ping_timer = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.on_disconnect = None
 | 
					 | 
				
			||||||
        self.on_connect = None
 | 
					 | 
				
			||||||
        self.on_login = None
 | 
					 | 
				
			||||||
        self.auto_reconnect = False
 | 
					 | 
				
			||||||
        self._running_event = threading.Event()
 | 
					 | 
				
			||||||
        self._stop_event = threading.Event()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def stopped(self):
 | 
					 | 
				
			||||||
        return self._stop_event.is_set()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _refresh_ping(self):
 | 
					 | 
				
			||||||
        if self._ping_timer is not None:
 | 
					 | 
				
			||||||
            self._ping_timer.cancel()
 | 
					 | 
				
			||||||
            self._ping_timer = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def func():
 | 
					 | 
				
			||||||
            self._ping_timer = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if self._connected:
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    self.ping()
 | 
					 | 
				
			||||||
                except APIConnectionError as err:
 | 
					 | 
				
			||||||
                    self._fatal_error(err)
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    self._refresh_ping()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._ping_timer = threading.Timer(self._keepalive, func)
 | 
					 | 
				
			||||||
        self._ping_timer.start()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _cancel_ping(self):
 | 
					 | 
				
			||||||
        if self._ping_timer is not None:
 | 
					 | 
				
			||||||
            self._ping_timer.cancel()
 | 
					 | 
				
			||||||
            self._ping_timer = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _close_socket(self):
 | 
					 | 
				
			||||||
        self._cancel_ping()
 | 
					 | 
				
			||||||
        if self._socket is not None:
 | 
					 | 
				
			||||||
            self._socket.close()
 | 
					 | 
				
			||||||
            self._socket = None
 | 
					 | 
				
			||||||
        self._socket_open_event.clear()
 | 
					 | 
				
			||||||
        self._connected = False
 | 
					 | 
				
			||||||
        self._authenticated = False
 | 
					 | 
				
			||||||
        self._message_handlers = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def stop(self, force=False):
 | 
					 | 
				
			||||||
        if self.stopped:
 | 
					 | 
				
			||||||
            raise ValueError
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if self._connected and not force:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                self.disconnect()
 | 
					 | 
				
			||||||
            except APIConnectionError:
 | 
					 | 
				
			||||||
                pass
 | 
					 | 
				
			||||||
        self._close_socket()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._stop_event.set()
 | 
					 | 
				
			||||||
        if not force:
 | 
					 | 
				
			||||||
            self.join()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def connect(self):
 | 
					 | 
				
			||||||
        if not self._running_event.wait(0.1):
 | 
					 | 
				
			||||||
            raise APIConnectionError("You need to call start() first!")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if self._connected:
 | 
					 | 
				
			||||||
            self.disconnect(on_disconnect=False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            ip = resolve_ip_address(self._address)
 | 
					 | 
				
			||||||
        except EsphomeError as err:
 | 
					 | 
				
			||||||
            _LOGGER.warning(
 | 
					 | 
				
			||||||
                "Error resolving IP address of %s. Is it connected to WiFi?",
 | 
					 | 
				
			||||||
                self._address,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            _LOGGER.warning(
 | 
					 | 
				
			||||||
                "(If this error persists, please set a static IP address: "
 | 
					 | 
				
			||||||
                "https://esphome.io/components/wifi.html#manual-ips)"
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            raise APIConnectionError(err) from err
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        _LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip)
 | 
					 | 
				
			||||||
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
					 | 
				
			||||||
        self._socket.settimeout(10.0)
 | 
					 | 
				
			||||||
        self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            self._socket.connect((ip, self._port))
 | 
					 | 
				
			||||||
        except OSError as err:
 | 
					 | 
				
			||||||
            err = APIConnectionError(f"Error connecting to {ip}: {err}")
 | 
					 | 
				
			||||||
            self._fatal_error(err)
 | 
					 | 
				
			||||||
            raise err
 | 
					 | 
				
			||||||
        self._socket.settimeout(0.1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._socket_open_event.set()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        hello = pb.HelloRequest()
 | 
					 | 
				
			||||||
        hello.client_info = f"ESPHome v{const.__version__}"
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            resp = self._send_message_await_response(hello, pb.HelloResponse)
 | 
					 | 
				
			||||||
        except APIConnectionError as err:
 | 
					 | 
				
			||||||
            self._fatal_error(err)
 | 
					 | 
				
			||||||
            raise err
 | 
					 | 
				
			||||||
        _LOGGER.debug(
 | 
					 | 
				
			||||||
            "Successfully connected to %s ('%s' API=%s.%s)",
 | 
					 | 
				
			||||||
            self._address,
 | 
					 | 
				
			||||||
            resp.server_info,
 | 
					 | 
				
			||||||
            resp.api_version_major,
 | 
					 | 
				
			||||||
            resp.api_version_minor,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self._connected = True
 | 
					 | 
				
			||||||
        self._refresh_ping()
 | 
					 | 
				
			||||||
        if self.on_connect is not None:
 | 
					 | 
				
			||||||
            self.on_connect()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _check_connected(self):
 | 
					 | 
				
			||||||
        if not self._connected:
 | 
					 | 
				
			||||||
            err = APIConnectionError("Must be connected!")
 | 
					 | 
				
			||||||
            self._fatal_error(err)
 | 
					 | 
				
			||||||
            raise err
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def login(self):
 | 
					 | 
				
			||||||
        self._check_connected()
 | 
					 | 
				
			||||||
        if self._authenticated:
 | 
					 | 
				
			||||||
            raise APIConnectionError("Already logged in!")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        connect = pb.ConnectRequest()
 | 
					 | 
				
			||||||
        if self._password is not None:
 | 
					 | 
				
			||||||
            connect.password = self._password
 | 
					 | 
				
			||||||
        resp = self._send_message_await_response(connect, pb.ConnectResponse)
 | 
					 | 
				
			||||||
        if resp.invalid_password:
 | 
					 | 
				
			||||||
            raise APIConnectionError("Invalid password!")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._authenticated = True
 | 
					 | 
				
			||||||
        if self.on_login is not None:
 | 
					 | 
				
			||||||
            self.on_login()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _fatal_error(self, err):
 | 
					 | 
				
			||||||
        was_connected = self._connected
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._close_socket()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if was_connected and self.on_disconnect is not None:
 | 
					 | 
				
			||||||
            self.on_disconnect(err)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _write(self, data):  # type: (bytes) -> None
 | 
					 | 
				
			||||||
        if self._socket is None:
 | 
					 | 
				
			||||||
            raise APIConnectionError("Socket closed")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # _LOGGER.debug("Write: %s", format_bytes(data))
 | 
					 | 
				
			||||||
        with self._socket_write_lock:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                self._socket.sendall(data)
 | 
					 | 
				
			||||||
            except OSError as err:
 | 
					 | 
				
			||||||
                err = APIConnectionError(f"Error while writing data: {err}")
 | 
					 | 
				
			||||||
                self._fatal_error(err)
 | 
					 | 
				
			||||||
                raise err
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _send_message(self, msg):
 | 
					 | 
				
			||||||
        # type: (message.Message) -> None
 | 
					 | 
				
			||||||
        for message_type, klass in MESSAGE_TYPE_TO_PROTO.items():
 | 
					 | 
				
			||||||
            if isinstance(msg, klass):
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            raise ValueError
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        encoded = msg.SerializeToString()
 | 
					 | 
				
			||||||
        _LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg)))
 | 
					 | 
				
			||||||
        req = bytes([0])
 | 
					 | 
				
			||||||
        req += _varuint_to_bytes(len(encoded))
 | 
					 | 
				
			||||||
        req += _varuint_to_bytes(message_type)
 | 
					 | 
				
			||||||
        req += encoded
 | 
					 | 
				
			||||||
        self._write(req)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _send_message_await_response_complex(
 | 
					 | 
				
			||||||
        self, send_msg, do_append, do_stop, timeout=5
 | 
					 | 
				
			||||||
    ):
 | 
					 | 
				
			||||||
        event = threading.Event()
 | 
					 | 
				
			||||||
        responses = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def on_message(resp):
 | 
					 | 
				
			||||||
            if do_append(resp):
 | 
					 | 
				
			||||||
                responses.append(resp)
 | 
					 | 
				
			||||||
            if do_stop(resp):
 | 
					 | 
				
			||||||
                event.set()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._message_handlers.append(on_message)
 | 
					 | 
				
			||||||
        self._send_message(send_msg)
 | 
					 | 
				
			||||||
        ret = event.wait(timeout)
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            self._message_handlers.remove(on_message)
 | 
					 | 
				
			||||||
        except ValueError:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
        if not ret:
 | 
					 | 
				
			||||||
            raise APIConnectionError("Timeout while waiting for message response!")
 | 
					 | 
				
			||||||
        return responses
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _send_message_await_response(self, send_msg, response_type, timeout=5):
 | 
					 | 
				
			||||||
        def is_response(msg):
 | 
					 | 
				
			||||||
            return isinstance(msg, response_type)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return self._send_message_await_response_complex(
 | 
					 | 
				
			||||||
            send_msg, is_response, is_response, timeout
 | 
					 | 
				
			||||||
        )[0]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def device_info(self):
 | 
					 | 
				
			||||||
        self._check_connected()
 | 
					 | 
				
			||||||
        return self._send_message_await_response(
 | 
					 | 
				
			||||||
            pb.DeviceInfoRequest(), pb.DeviceInfoResponse
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def ping(self):
 | 
					 | 
				
			||||||
        self._check_connected()
 | 
					 | 
				
			||||||
        return self._send_message_await_response(pb.PingRequest(), pb.PingResponse)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def disconnect(self, on_disconnect=True):
 | 
					 | 
				
			||||||
        self._check_connected()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            self._send_message_await_response(
 | 
					 | 
				
			||||||
                pb.DisconnectRequest(), pb.DisconnectResponse
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        except APIConnectionError:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
        self._close_socket()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if self.on_disconnect is not None and on_disconnect:
 | 
					 | 
				
			||||||
            self.on_disconnect(None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _check_authenticated(self):
 | 
					 | 
				
			||||||
        if not self._authenticated:
 | 
					 | 
				
			||||||
            raise APIConnectionError("Must login first!")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def subscribe_logs(self, on_log, log_level=7, dump_config=False):
 | 
					 | 
				
			||||||
        self._check_authenticated()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def on_msg(msg):
 | 
					 | 
				
			||||||
            if isinstance(msg, pb.SubscribeLogsResponse):
 | 
					 | 
				
			||||||
                on_log(msg)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._message_handlers.append(on_msg)
 | 
					 | 
				
			||||||
        req = pb.SubscribeLogsRequest(dump_config=dump_config)
 | 
					 | 
				
			||||||
        req.level = log_level
 | 
					 | 
				
			||||||
        self._send_message(req)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _recv(self, amount):
 | 
					 | 
				
			||||||
        ret = bytes()
 | 
					 | 
				
			||||||
        if amount == 0:
 | 
					 | 
				
			||||||
            return ret
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while len(ret) < amount:
 | 
					 | 
				
			||||||
            if self.stopped:
 | 
					 | 
				
			||||||
                raise APIConnectionError("Stopped!")
 | 
					 | 
				
			||||||
            if not self._socket_open_event.is_set():
 | 
					 | 
				
			||||||
                raise APIConnectionError("No socket!")
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                val = self._socket.recv(amount - len(ret))
 | 
					 | 
				
			||||||
            except AttributeError as err:
 | 
					 | 
				
			||||||
                raise APIConnectionError("Socket was closed") from err
 | 
					 | 
				
			||||||
            except socket.timeout:
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            except OSError as err:
 | 
					 | 
				
			||||||
                raise APIConnectionError(f"Error while receiving data: {err}") from err
 | 
					 | 
				
			||||||
            ret += val
 | 
					 | 
				
			||||||
        return ret
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _recv_varint(self):
 | 
					 | 
				
			||||||
        raw = bytes()
 | 
					 | 
				
			||||||
        while not raw or raw[-1] & 0x80:
 | 
					 | 
				
			||||||
            raw += self._recv(1)
 | 
					 | 
				
			||||||
        return _bytes_to_varuint(raw)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _run_once(self):
 | 
					 | 
				
			||||||
        if not self._socket_open_event.wait(0.1):
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Preamble
 | 
					 | 
				
			||||||
        if self._recv(1)[0] != 0x00:
 | 
					 | 
				
			||||||
            raise APIConnectionError("Invalid preamble")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        length = self._recv_varint()
 | 
					 | 
				
			||||||
        msg_type = self._recv_varint()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        raw_msg = self._recv(length)
 | 
					 | 
				
			||||||
        if msg_type not in MESSAGE_TYPE_TO_PROTO:
 | 
					 | 
				
			||||||
            _LOGGER.debug("Skipping message type %s", msg_type)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        msg = MESSAGE_TYPE_TO_PROTO[msg_type]()
 | 
					 | 
				
			||||||
        msg.ParseFromString(raw_msg)
 | 
					 | 
				
			||||||
        _LOGGER.debug("Got message: %s:\n%s", type(msg), indent(str(msg)))
 | 
					 | 
				
			||||||
        for msg_handler in self._message_handlers[:]:
 | 
					 | 
				
			||||||
            msg_handler(msg)
 | 
					 | 
				
			||||||
        self._handle_internal_messages(msg)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def run(self):
 | 
					 | 
				
			||||||
        self._running_event.set()
 | 
					 | 
				
			||||||
        while not self.stopped:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                self._run_once()
 | 
					 | 
				
			||||||
            except APIConnectionError as err:
 | 
					 | 
				
			||||||
                if self.stopped:
 | 
					 | 
				
			||||||
                    break
 | 
					 | 
				
			||||||
                if self._connected:
 | 
					 | 
				
			||||||
                    _LOGGER.error("Error while reading incoming messages: %s", err)
 | 
					 | 
				
			||||||
                    self._fatal_error(err)
 | 
					 | 
				
			||||||
        self._running_event.clear()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _handle_internal_messages(self, msg):
 | 
					 | 
				
			||||||
        if isinstance(msg, pb.DisconnectRequest):
 | 
					 | 
				
			||||||
            self._send_message(pb.DisconnectResponse())
 | 
					 | 
				
			||||||
            if self._socket is not None:
 | 
					 | 
				
			||||||
                self._socket.close()
 | 
					 | 
				
			||||||
                self._socket = None
 | 
					 | 
				
			||||||
            self._connected = False
 | 
					 | 
				
			||||||
            if self.on_disconnect is not None:
 | 
					 | 
				
			||||||
                self.on_disconnect(None)
 | 
					 | 
				
			||||||
        elif isinstance(msg, pb.PingRequest):
 | 
					 | 
				
			||||||
            self._send_message(pb.PingResponse())
 | 
					 | 
				
			||||||
        elif isinstance(msg, pb.GetTimeRequest):
 | 
					 | 
				
			||||||
            resp = pb.GetTimeResponse()
 | 
					 | 
				
			||||||
            resp.epoch_seconds = int(time.time())
 | 
					 | 
				
			||||||
            self._send_message(resp)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def run_logs(config, address):
 | 
					 | 
				
			||||||
    conf = config["api"]
 | 
					 | 
				
			||||||
    port = conf[CONF_PORT]
 | 
					 | 
				
			||||||
    password = conf[CONF_PASSWORD]
 | 
					 | 
				
			||||||
    _LOGGER.info("Starting log output from %s using esphome API", address)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    cli = APIClient(address, port, password)
 | 
					 | 
				
			||||||
    stopping = False
 | 
					 | 
				
			||||||
    retry_timer = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    has_connects = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def try_connect(err, tries=0):
 | 
					 | 
				
			||||||
        if stopping:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if err:
 | 
					 | 
				
			||||||
            _LOGGER.warning("Disconnected from API: %s", err)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while retry_timer:
 | 
					 | 
				
			||||||
            retry_timer.pop(0).cancel()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        error = None
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            cli.connect()
 | 
					 | 
				
			||||||
            cli.login()
 | 
					 | 
				
			||||||
        except APIConnectionError as err2:  # noqa
 | 
					 | 
				
			||||||
            error = err2
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if error is None:
 | 
					 | 
				
			||||||
            _LOGGER.info("Successfully connected to %s", address)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        wait_time = int(min(1.5 ** min(tries, 100), 30))
 | 
					 | 
				
			||||||
        if not has_connects:
 | 
					 | 
				
			||||||
            _LOGGER.warning(
 | 
					 | 
				
			||||||
                "Initial connection failed. The ESP might not be connected "
 | 
					 | 
				
			||||||
                "to WiFi yet (%s). Re-Trying in %s seconds",
 | 
					 | 
				
			||||||
                error,
 | 
					 | 
				
			||||||
                wait_time,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            _LOGGER.warning(
 | 
					 | 
				
			||||||
                "Couldn't connect to API (%s). Trying to reconnect in %s seconds",
 | 
					 | 
				
			||||||
                error,
 | 
					 | 
				
			||||||
                wait_time,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        timer = threading.Timer(
 | 
					 | 
				
			||||||
            wait_time, functools.partial(try_connect, None, tries + 1)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        timer.start()
 | 
					 | 
				
			||||||
        retry_timer.append(timer)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def on_log(msg):
 | 
					 | 
				
			||||||
        time_ = datetime.now().time().strftime("[%H:%M:%S]")
 | 
					 | 
				
			||||||
        text = msg.message
 | 
					 | 
				
			||||||
        if msg.send_failed:
 | 
					 | 
				
			||||||
            text = color(
 | 
					 | 
				
			||||||
                Fore.WHITE,
 | 
					 | 
				
			||||||
                "(Message skipped because it was too big to fit in "
 | 
					 | 
				
			||||||
                "TCP buffer - This is only cosmetic)",
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        safe_print(time_ + text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def on_login():
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            cli.subscribe_logs(on_log, dump_config=not has_connects)
 | 
					 | 
				
			||||||
            has_connects.append(True)
 | 
					 | 
				
			||||||
        except APIConnectionError:
 | 
					 | 
				
			||||||
            cli.disconnect()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    cli.on_disconnect = try_connect
 | 
					 | 
				
			||||||
    cli.on_login = on_login
 | 
					 | 
				
			||||||
    cli.start()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        try_connect(None)
 | 
					 | 
				
			||||||
        while True:
 | 
					 | 
				
			||||||
            time.sleep(1)
 | 
					 | 
				
			||||||
    except KeyboardInterrupt:
 | 
					 | 
				
			||||||
        stopping = True
 | 
					 | 
				
			||||||
        cli.stop(True)
 | 
					 | 
				
			||||||
        while retry_timer:
 | 
					 | 
				
			||||||
            retry_timer.pop(0).cancel()
 | 
					 | 
				
			||||||
    return 0
 | 
					 | 
				
			||||||
@@ -3,9 +3,11 @@ import esphome.config_validation as cv
 | 
				
			|||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_AUTOMATION_ID,
 | 
					    CONF_AUTOMATION_ID,
 | 
				
			||||||
    CONF_CONDITION,
 | 
					    CONF_CONDITION,
 | 
				
			||||||
 | 
					    CONF_COUNT,
 | 
				
			||||||
    CONF_ELSE,
 | 
					    CONF_ELSE,
 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
    CONF_THEN,
 | 
					    CONF_THEN,
 | 
				
			||||||
 | 
					    CONF_TIMEOUT,
 | 
				
			||||||
    CONF_TRIGGER_ID,
 | 
					    CONF_TRIGGER_ID,
 | 
				
			||||||
    CONF_TYPE_ID,
 | 
					    CONF_TYPE_ID,
 | 
				
			||||||
    CONF_TIME,
 | 
					    CONF_TIME,
 | 
				
			||||||
@@ -65,6 +67,7 @@ DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
 | 
				
			|||||||
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
 | 
					LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
 | 
				
			||||||
IfAction = cg.esphome_ns.class_("IfAction", Action)
 | 
					IfAction = cg.esphome_ns.class_("IfAction", Action)
 | 
				
			||||||
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
 | 
					WhileAction = cg.esphome_ns.class_("WhileAction", Action)
 | 
				
			||||||
 | 
					RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
 | 
				
			||||||
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
 | 
					WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
 | 
				
			||||||
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
 | 
					UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
 | 
				
			||||||
Automation = cg.esphome_ns.class_("Automation")
 | 
					Automation = cg.esphome_ns.class_("Automation")
 | 
				
			||||||
@@ -240,10 +243,32 @@ async def while_action_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
    return var
 | 
					    return var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@register_action(
 | 
				
			||||||
 | 
					    "repeat",
 | 
				
			||||||
 | 
					    RepeatAction,
 | 
				
			||||||
 | 
					    cv.Schema(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cv.Required(CONF_COUNT): cv.templatable(cv.positive_not_null_int),
 | 
				
			||||||
 | 
					            cv.Required(CONF_THEN): validate_action_list,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def repeat_action_to_code(config, action_id, template_arg, args):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(action_id, template_arg)
 | 
				
			||||||
 | 
					    count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
 | 
				
			||||||
 | 
					    cg.add(var.set_count(count_template))
 | 
				
			||||||
 | 
					    actions = await build_action_list(config[CONF_THEN], template_arg, args)
 | 
				
			||||||
 | 
					    cg.add(var.add_then(actions))
 | 
				
			||||||
 | 
					    return var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def validate_wait_until(value):
 | 
					def validate_wait_until(value):
 | 
				
			||||||
    schema = cv.Schema(
 | 
					    schema = cv.Schema(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            cv.Required(CONF_CONDITION): validate_potentially_and_condition,
 | 
					            cv.Required(CONF_CONDITION): validate_potentially_and_condition,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_TIMEOUT): cv.templatable(
 | 
				
			||||||
 | 
					                cv.positive_time_period_milliseconds
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    if isinstance(value, dict) and CONF_CONDITION in value:
 | 
					    if isinstance(value, dict) and CONF_CONDITION in value:
 | 
				
			||||||
@@ -255,6 +280,9 @@ def validate_wait_until(value):
 | 
				
			|||||||
async def wait_until_action_to_code(config, action_id, template_arg, args):
 | 
					async def wait_until_action_to_code(config, action_id, template_arg, args):
 | 
				
			||||||
    conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
 | 
					    conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
 | 
				
			||||||
    var = cg.new_Pvariable(action_id, template_arg, conditions)
 | 
					    var = cg.new_Pvariable(action_id, template_arg, conditions)
 | 
				
			||||||
 | 
					    if CONF_TIMEOUT in config:
 | 
				
			||||||
 | 
					        template_ = await cg.templatable(config[CONF_TIMEOUT], args, cg.uint32)
 | 
				
			||||||
 | 
					        cg.add(var.set_timeout_value(template_))
 | 
				
			||||||
    await cg.register_component(var, {})
 | 
					    await cg.register_component(var, {})
 | 
				
			||||||
    return var
 | 
					    return var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,7 @@ from esphome.cpp_generator import (  # noqa
 | 
				
			|||||||
    add_library,
 | 
					    add_library,
 | 
				
			||||||
    add_build_flag,
 | 
					    add_build_flag,
 | 
				
			||||||
    add_define,
 | 
					    add_define,
 | 
				
			||||||
 | 
					    add_platformio_option,
 | 
				
			||||||
    get_variable,
 | 
					    get_variable,
 | 
				
			||||||
    get_variable_with_full_id,
 | 
					    get_variable_with_full_id,
 | 
				
			||||||
    process_lambda,
 | 
					    process_lambda,
 | 
				
			||||||
@@ -60,12 +61,14 @@ from esphome.cpp_types import (  # noqa
 | 
				
			|||||||
    uint8,
 | 
					    uint8,
 | 
				
			||||||
    uint16,
 | 
					    uint16,
 | 
				
			||||||
    uint32,
 | 
					    uint32,
 | 
				
			||||||
 | 
					    uint64,
 | 
				
			||||||
    int32,
 | 
					    int32,
 | 
				
			||||||
 | 
					    int64,
 | 
				
			||||||
    const_char_ptr,
 | 
					    const_char_ptr,
 | 
				
			||||||
    NAN,
 | 
					    NAN,
 | 
				
			||||||
    esphome_ns,
 | 
					    esphome_ns,
 | 
				
			||||||
    App,
 | 
					    App,
 | 
				
			||||||
    Nameable,
 | 
					    EntityBase,
 | 
				
			||||||
    Component,
 | 
					    Component,
 | 
				
			||||||
    ComponentPtr,
 | 
					    ComponentPtr,
 | 
				
			||||||
    PollingComponent,
 | 
					    PollingComponent,
 | 
				
			||||||
@@ -73,8 +76,11 @@ from esphome.cpp_types import (  # noqa
 | 
				
			|||||||
    optional,
 | 
					    optional,
 | 
				
			||||||
    arduino_json_ns,
 | 
					    arduino_json_ns,
 | 
				
			||||||
    JsonObject,
 | 
					    JsonObject,
 | 
				
			||||||
    JsonObjectRef,
 | 
					    JsonObjectConst,
 | 
				
			||||||
    JsonObjectConstRef,
 | 
					 | 
				
			||||||
    Controller,
 | 
					    Controller,
 | 
				
			||||||
    GPIOPin,
 | 
					    GPIOPin,
 | 
				
			||||||
 | 
					    InternalGPIOPin,
 | 
				
			||||||
 | 
					    gpio_Flags,
 | 
				
			||||||
 | 
					    EntityCategory,
 | 
				
			||||||
 | 
					    Parented,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@
 | 
				
			|||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace a4988 {
 | 
					namespace a4988 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char *TAG = "a4988.stepper";
 | 
					static const char *const TAG = "a4988.stepper";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void A4988::setup() {
 | 
					void A4988::setup() {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "Setting up A4988...");
 | 
					  ESP_LOGCONFIG(TAG, "Setting up A4988...");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
#include "esphome/core/esphal.h"
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
#include "esphome/components/stepper/stepper.h"
 | 
					#include "esphome/components/stepper/stepper.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,28 +1,37 @@
 | 
				
			|||||||
 | 
					#ifdef USE_ARDUINO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "ac_dimmer.h"
 | 
					#include "ac_dimmer.h"
 | 
				
			||||||
#include "esphome/core/helpers.h"
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include <cmath>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP8266
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
#include <core_esp8266_waveform.h>
 | 
					#include <core_esp8266_waveform.h>
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ESP32_FRAMEWORK_ARDUINO
 | 
				
			||||||
 | 
					#include <esp32-hal-timer.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace ac_dimmer {
 | 
					namespace ac_dimmer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char *TAG = "ac_dimmer";
 | 
					static const char *const TAG = "ac_dimmer";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Global array to store dimmer objects
 | 
					// Global array to store dimmer objects
 | 
				
			||||||
static AcDimmerDataStore *all_dimmers[32];
 | 
					static AcDimmerDataStore *all_dimmers[32];  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Time in microseconds the gate should be held high
 | 
					/// Time in microseconds the gate should be held high
 | 
				
			||||||
/// 10µs should be long enough for most triacs
 | 
					/// 10µs should be long enough for most triacs
 | 
				
			||||||
/// For reference: BT136 datasheet says 2µs nominal (page 7)
 | 
					/// For reference: BT136 datasheet says 2µs nominal (page 7)
 | 
				
			||||||
static uint32_t GATE_ENABLE_TIME = 10;
 | 
					/// However other factors like gate driver propagation time
 | 
				
			||||||
 | 
					/// are also considered and a really low value is not important
 | 
				
			||||||
 | 
					/// See also: https://github.com/esphome/issues/issues/1632
 | 
				
			||||||
 | 
					static const uint32_t GATE_ENABLE_TIME = 50;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Function called from timer interrupt
 | 
					/// Function called from timer interrupt
 | 
				
			||||||
/// Input is current time in microseconds (micros())
 | 
					/// Input is current time in microseconds (micros())
 | 
				
			||||||
/// Returns when next "event" is expected in µs, or 0 if no such event known.
 | 
					/// Returns when next "event" is expected in µs, or 0 if no such event known.
 | 
				
			||||||
uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
 | 
					uint32_t IRAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
 | 
				
			||||||
  // If no ZC signal received yet.
 | 
					  // If no ZC signal received yet.
 | 
				
			||||||
  if (this->crossed_zero_at == 0)
 | 
					  if (this->crossed_zero_at == 0)
 | 
				
			||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
@@ -34,19 +43,19 @@ uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  if (this->enable_time_us != 0 && time_since_zc >= this->enable_time_us) {
 | 
					  if (this->enable_time_us != 0 && time_since_zc >= this->enable_time_us) {
 | 
				
			||||||
    this->enable_time_us = 0;
 | 
					    this->enable_time_us = 0;
 | 
				
			||||||
    this->gate_pin->digital_write(true);
 | 
					    this->gate_pin.digital_write(true);
 | 
				
			||||||
    // Prevent too short pulses
 | 
					    // Prevent too short pulses
 | 
				
			||||||
    this->disable_time_us = max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME);
 | 
					    this->disable_time_us = std::max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->disable_time_us != 0 && time_since_zc >= this->disable_time_us) {
 | 
					  if (this->disable_time_us != 0 && time_since_zc >= this->disable_time_us) {
 | 
				
			||||||
    this->disable_time_us = 0;
 | 
					    this->disable_time_us = 0;
 | 
				
			||||||
    this->gate_pin->digital_write(false);
 | 
					    this->gate_pin.digital_write(false);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (time_since_zc < this->enable_time_us)
 | 
					  if (time_since_zc < this->enable_time_us) {
 | 
				
			||||||
    // Next event is enable, return time until that event
 | 
					    // Next event is enable, return time until that event
 | 
				
			||||||
    return this->enable_time_us - time_since_zc;
 | 
					    return this->enable_time_us - time_since_zc;
 | 
				
			||||||
  else if (time_since_zc < disable_time_us) {
 | 
					  } else if (time_since_zc < disable_time_us) {
 | 
				
			||||||
    // Next event is disable, return time until that event
 | 
					    // Next event is disable, return time until that event
 | 
				
			||||||
    return this->disable_time_us - time_since_zc;
 | 
					    return this->disable_time_us - time_since_zc;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -60,14 +69,15 @@ uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Run timer interrupt code and return in how many µs the next event is expected
 | 
					/// Run timer interrupt code and return in how many µs the next event is expected
 | 
				
			||||||
uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() {
 | 
					uint32_t IRAM_ATTR HOT timer_interrupt() {
 | 
				
			||||||
  // run at least with 1kHz
 | 
					  // run at least with 1kHz
 | 
				
			||||||
  uint32_t min_dt_us = 1000;
 | 
					  uint32_t min_dt_us = 1000;
 | 
				
			||||||
  uint32_t now = micros();
 | 
					  uint32_t now = micros();
 | 
				
			||||||
  for (auto *dimmer : all_dimmers) {
 | 
					  for (auto *dimmer : all_dimmers) {
 | 
				
			||||||
    if (dimmer == nullptr)
 | 
					    if (dimmer == nullptr) {
 | 
				
			||||||
      // no more dimmers
 | 
					      // no more dimmers
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    uint32_t res = dimmer->timer_intr(now);
 | 
					    uint32_t res = dimmer->timer_intr(now);
 | 
				
			||||||
    if (res != 0 && res < min_dt_us)
 | 
					    if (res != 0 && res < min_dt_us)
 | 
				
			||||||
      min_dt_us = res;
 | 
					      min_dt_us = res;
 | 
				
			||||||
@@ -77,7 +87,7 @@ uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// GPIO interrupt routine, called when ZC pin triggers
 | 
					/// GPIO interrupt routine, called when ZC pin triggers
 | 
				
			||||||
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
 | 
					void IRAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
 | 
				
			||||||
  uint32_t prev_crossed = this->crossed_zero_at;
 | 
					  uint32_t prev_crossed = this->crossed_zero_at;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // 50Hz mains frequency should give a half cycle of 10ms a 60Hz will give 8.33ms
 | 
					  // 50Hz mains frequency should give a half cycle of 10ms a 60Hz will give 8.33ms
 | 
				
			||||||
@@ -94,7 +104,7 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  if (this->value == 65535) {
 | 
					  if (this->value == 65535) {
 | 
				
			||||||
    // fully on, enable output immediately
 | 
					    // fully on, enable output immediately
 | 
				
			||||||
    this->gate_pin->digital_write(true);
 | 
					    this->gate_pin.digital_write(true);
 | 
				
			||||||
  } else if (this->init_cycle) {
 | 
					  } else if (this->init_cycle) {
 | 
				
			||||||
    // send a full cycle
 | 
					    // send a full cycle
 | 
				
			||||||
    this->init_cycle = false;
 | 
					    this->init_cycle = false;
 | 
				
			||||||
@@ -102,30 +112,30 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
 | 
				
			|||||||
    this->disable_time_us = cycle_time_us;
 | 
					    this->disable_time_us = cycle_time_us;
 | 
				
			||||||
  } else if (this->value == 0) {
 | 
					  } else if (this->value == 0) {
 | 
				
			||||||
    // fully off, disable output immediately
 | 
					    // fully off, disable output immediately
 | 
				
			||||||
    this->gate_pin->digital_write(false);
 | 
					    this->gate_pin.digital_write(false);
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    if (this->method == DIM_METHOD_TRAILING) {
 | 
					    if (this->method == DIM_METHOD_TRAILING) {
 | 
				
			||||||
      this->enable_time_us = 1;  // cannot be 0
 | 
					      this->enable_time_us = 1;  // cannot be 0
 | 
				
			||||||
      this->disable_time_us = max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
 | 
					      this->disable_time_us = std::max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
 | 
					      // calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
 | 
				
			||||||
      // also take into account min_power
 | 
					      // also take into account min_power
 | 
				
			||||||
      auto min_us = this->cycle_time_us * this->min_power / 1000;
 | 
					      auto min_us = this->cycle_time_us * this->min_power / 1000;
 | 
				
			||||||
      this->enable_time_us = max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
 | 
					      this->enable_time_us = std::max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
 | 
				
			||||||
      if (this->method == DIM_METHOD_LEADING_PULSE) {
 | 
					      if (this->method == DIM_METHOD_LEADING_PULSE) {
 | 
				
			||||||
        // Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone
 | 
					        // Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone
 | 
				
			||||||
        // this is for brightness near 99%
 | 
					        // this is for brightness near 99%
 | 
				
			||||||
        this->disable_time_us = max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10);
 | 
					        this->disable_time_us = std::max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this->gate_pin->digital_write(false);
 | 
					        this->gate_pin.digital_write(false);
 | 
				
			||||||
        this->disable_time_us = this->cycle_time_us;
 | 
					        this->disable_time_us = this->cycle_time_us;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
 | 
					void IRAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
 | 
				
			||||||
  // Attaching pin interrupts on the same pin will override the previous interupt
 | 
					  // Attaching pin interrupts on the same pin will override the previous interrupt
 | 
				
			||||||
  // However, the user expects that multiple dimmers sharing the same ZC pin will work.
 | 
					  // However, the user expects that multiple dimmers sharing the same ZC pin will work.
 | 
				
			||||||
  // We solve this in a bit of a hacky way: On each pin interrupt, we check all dimmers
 | 
					  // We solve this in a bit of a hacky way: On each pin interrupt, we check all dimmers
 | 
				
			||||||
  // if any of them are using the same ZC pin, and also trigger the interrupt for *them*.
 | 
					  // if any of them are using the same ZC pin, and also trigger the interrupt for *them*.
 | 
				
			||||||
@@ -138,11 +148,11 @@ void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP32
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
// ESP32 implementation, uses basically the same code but needs to wrap
 | 
					// ESP32 implementation, uses basically the same code but needs to wrap
 | 
				
			||||||
// timer_interrupt() function to auto-reschedule
 | 
					// timer_interrupt() function to auto-reschedule
 | 
				
			||||||
static hw_timer_t *dimmer_timer = nullptr;
 | 
					static hw_timer_t *dimmer_timer = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
				
			||||||
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
 | 
					void IRAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AcDimmer::setup() {
 | 
					void AcDimmer::setup() {
 | 
				
			||||||
@@ -171,15 +181,16 @@ void AcDimmer::setup() {
 | 
				
			|||||||
  if (setup_zero_cross_pin) {
 | 
					  if (setup_zero_cross_pin) {
 | 
				
			||||||
    this->zero_cross_pin_->setup();
 | 
					    this->zero_cross_pin_->setup();
 | 
				
			||||||
    this->store_.zero_cross_pin = this->zero_cross_pin_->to_isr();
 | 
					    this->store_.zero_cross_pin = this->zero_cross_pin_->to_isr();
 | 
				
			||||||
    this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_, FALLING);
 | 
					    this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_,
 | 
				
			||||||
 | 
					                                            gpio::INTERRUPT_FALLING_EDGE);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP8266
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
  // Uses ESP8266 waveform (soft PWM) class
 | 
					  // Uses ESP8266 waveform (soft PWM) class
 | 
				
			||||||
  // PWM and AcDimmer can even run at the same time this way
 | 
					  // PWM and AcDimmer can even run at the same time this way
 | 
				
			||||||
  setTimer1Callback(&timer_interrupt);
 | 
					  setTimer1Callback(&timer_interrupt);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP32
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
  // 80 Divider -> 1 count=1µs
 | 
					  // 80 Divider -> 1 count=1µs
 | 
				
			||||||
  dimmer_timer = timerBegin(0, 80, true);
 | 
					  dimmer_timer = timerBegin(0, 80, true);
 | 
				
			||||||
  timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true);
 | 
					  timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true);
 | 
				
			||||||
@@ -202,12 +213,13 @@ void AcDimmer::dump_config() {
 | 
				
			|||||||
  LOG_PIN("  Zero-Cross Pin: ", this->zero_cross_pin_);
 | 
					  LOG_PIN("  Zero-Cross Pin: ", this->zero_cross_pin_);
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "   Min Power: %.1f%%", this->store_.min_power / 10.0f);
 | 
					  ESP_LOGCONFIG(TAG, "   Min Power: %.1f%%", this->store_.min_power / 10.0f);
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "   Init with half cycle: %s", YESNO(this->init_with_half_cycle_));
 | 
					  ESP_LOGCONFIG(TAG, "   Init with half cycle: %s", YESNO(this->init_with_half_cycle_));
 | 
				
			||||||
  if (method_ == DIM_METHOD_LEADING_PULSE)
 | 
					  if (method_ == DIM_METHOD_LEADING_PULSE) {
 | 
				
			||||||
    ESP_LOGCONFIG(TAG, "   Method: leading pulse");
 | 
					    ESP_LOGCONFIG(TAG, "   Method: leading pulse");
 | 
				
			||||||
  else if (method_ == DIM_METHOD_LEADING)
 | 
					  } else if (method_ == DIM_METHOD_LEADING) {
 | 
				
			||||||
    ESP_LOGCONFIG(TAG, "   Method: leading");
 | 
					    ESP_LOGCONFIG(TAG, "   Method: leading");
 | 
				
			||||||
  else
 | 
					  } else {
 | 
				
			||||||
    ESP_LOGCONFIG(TAG, "   Method: trailing");
 | 
					    ESP_LOGCONFIG(TAG, "   Method: trailing");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  LOG_FLOAT_OUTPUT(this);
 | 
					  LOG_FLOAT_OUTPUT(this);
 | 
				
			||||||
  ESP_LOGV(TAG, "  Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2);
 | 
					  ESP_LOGV(TAG, "  Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2);
 | 
				
			||||||
@@ -215,3 +227,5 @@ void AcDimmer::dump_config() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}  // namespace ac_dimmer
 | 
					}  // namespace ac_dimmer
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_ARDUINO
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ARDUINO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
#include "esphome/core/esphal.h"
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
#include "esphome/components/output/float_output.h"
 | 
					#include "esphome/components/output/float_output.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
@@ -11,11 +13,11 @@ enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TR
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
struct AcDimmerDataStore {
 | 
					struct AcDimmerDataStore {
 | 
				
			||||||
  /// Zero-cross pin
 | 
					  /// Zero-cross pin
 | 
				
			||||||
  ISRInternalGPIOPin *zero_cross_pin;
 | 
					  ISRInternalGPIOPin zero_cross_pin;
 | 
				
			||||||
  /// Zero-cross pin number - used to share ZC pin across multiple dimmers
 | 
					  /// Zero-cross pin number - used to share ZC pin across multiple dimmers
 | 
				
			||||||
  uint8_t zero_cross_pin_number;
 | 
					  uint8_t zero_cross_pin_number;
 | 
				
			||||||
  /// Output pin to write to
 | 
					  /// Output pin to write to
 | 
				
			||||||
  ISRInternalGPIOPin *gate_pin;
 | 
					  ISRInternalGPIOPin gate_pin;
 | 
				
			||||||
  /// Value of the dimmer - 0 to 65535.
 | 
					  /// Value of the dimmer - 0 to 65535.
 | 
				
			||||||
  uint16_t value;
 | 
					  uint16_t value;
 | 
				
			||||||
  /// Minimum power for activation
 | 
					  /// Minimum power for activation
 | 
				
			||||||
@@ -37,7 +39,7 @@ struct AcDimmerDataStore {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  void gpio_intr();
 | 
					  void gpio_intr();
 | 
				
			||||||
  static void s_gpio_intr(AcDimmerDataStore *store);
 | 
					  static void s_gpio_intr(AcDimmerDataStore *store);
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP32
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
  static void s_timer_intr();
 | 
					  static void s_timer_intr();
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -47,16 +49,16 @@ class AcDimmer : public output::FloatOutput, public Component {
 | 
				
			|||||||
  void setup() override;
 | 
					  void setup() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void dump_config() override;
 | 
					  void dump_config() override;
 | 
				
			||||||
  void set_gate_pin(GPIOPin *gate_pin) { gate_pin_ = gate_pin; }
 | 
					  void set_gate_pin(InternalGPIOPin *gate_pin) { gate_pin_ = gate_pin; }
 | 
				
			||||||
  void set_zero_cross_pin(GPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; }
 | 
					  void set_zero_cross_pin(InternalGPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; }
 | 
				
			||||||
  void set_init_with_half_cycle(bool init_with_half_cycle) { init_with_half_cycle_ = init_with_half_cycle; }
 | 
					  void set_init_with_half_cycle(bool init_with_half_cycle) { init_with_half_cycle_ = init_with_half_cycle; }
 | 
				
			||||||
  void set_method(DimMethod method) { method_ = method; }
 | 
					  void set_method(DimMethod method) { method_ = method; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  void write_state(float state) override;
 | 
					  void write_state(float state) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  GPIOPin *gate_pin_;
 | 
					  InternalGPIOPin *gate_pin_;
 | 
				
			||||||
  GPIOPin *zero_cross_pin_;
 | 
					  InternalGPIOPin *zero_cross_pin_;
 | 
				
			||||||
  AcDimmerDataStore store_;
 | 
					  AcDimmerDataStore store_;
 | 
				
			||||||
  bool init_with_half_cycle_;
 | 
					  bool init_with_half_cycle_;
 | 
				
			||||||
  DimMethod method_;
 | 
					  DimMethod method_;
 | 
				
			||||||
@@ -64,3 +66,5 @@ class AcDimmer : public output::FloatOutput, public Component {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}  // namespace ac_dimmer
 | 
					}  // namespace ac_dimmer
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_ARDUINO
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,17 +19,20 @@ DIM_METHODS = {
 | 
				
			|||||||
CONF_GATE_PIN = "gate_pin"
 | 
					CONF_GATE_PIN = "gate_pin"
 | 
				
			||||||
CONF_ZERO_CROSS_PIN = "zero_cross_pin"
 | 
					CONF_ZERO_CROSS_PIN = "zero_cross_pin"
 | 
				
			||||||
CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle"
 | 
					CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle"
 | 
				
			||||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
 | 
					CONFIG_SCHEMA = cv.All(
 | 
				
			||||||
    {
 | 
					    output.FLOAT_OUTPUT_SCHEMA.extend(
 | 
				
			||||||
        cv.Required(CONF_ID): cv.declare_id(AcDimmer),
 | 
					        {
 | 
				
			||||||
        cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
 | 
					            cv.Required(CONF_ID): cv.declare_id(AcDimmer),
 | 
				
			||||||
        cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
 | 
					            cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
 | 
				
			||||||
        cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
 | 
					            cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
 | 
				
			||||||
        cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum(
 | 
					            cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
 | 
				
			||||||
            DIM_METHODS, upper=True, space="_"
 | 
					            cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum(
 | 
				
			||||||
        ),
 | 
					                DIM_METHODS, upper=True, space="_"
 | 
				
			||||||
    }
 | 
					            ),
 | 
				
			||||||
).extend(cv.COMPONENT_SCHEMA)
 | 
					        }
 | 
				
			||||||
 | 
					    ).extend(cv.COMPONENT_SCHEMA),
 | 
				
			||||||
 | 
					    cv.only_with_arduino,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@
 | 
				
			|||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace adalight {
 | 
					namespace adalight {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char *TAG = "adalight_light_effect";
 | 
					static const char *const TAG = "adalight_light_effect";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
 | 
					static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
 | 
				
			||||||
static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
 | 
					static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
 | 
				
			||||||
@@ -25,7 +25,7 @@ void AdalightLightEffect::stop() {
 | 
				
			|||||||
  AddressableLightEffect::stop();
 | 
					  AddressableLightEffect::stop();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int AdalightLightEffect::get_frame_size_(int led_count) const {
 | 
					unsigned int AdalightLightEffect::get_frame_size_(int led_count) const {
 | 
				
			||||||
  // 3 bytes: Ada
 | 
					  // 3 bytes: Ada
 | 
				
			||||||
  // 2 bytes: LED count
 | 
					  // 2 bytes: LED count
 | 
				
			||||||
  // 1 byte: checksum
 | 
					  // 1 byte: checksum
 | 
				
			||||||
@@ -42,8 +42,9 @@ void AdalightLightEffect::reset_frame_(light::AddressableLight &it) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) {
 | 
					void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) {
 | 
				
			||||||
  for (int led = it.size(); led-- > 0;) {
 | 
					  for (int led = it.size(); led-- > 0;) {
 | 
				
			||||||
    it[led].set(COLOR_BLACK);
 | 
					    it[led].set(Color::BLACK);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  it.schedule_show();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AdalightLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) {
 | 
					void AdalightLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) {
 | 
				
			||||||
@@ -133,6 +134,7 @@ AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableL
 | 
				
			|||||||
    it[led].set(Color(led_data[0], led_data[1], led_data[2], white));
 | 
					    it[led].set(Color(led_data[0], led_data[1], led_data[2], white));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it.schedule_show();
 | 
				
			||||||
  return CONSUMED;
 | 
					  return CONSUMED;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,6 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
 | 
				
			|||||||
 public:
 | 
					 public:
 | 
				
			||||||
  AdalightLightEffect(const std::string &name);
 | 
					  AdalightLightEffect(const std::string &name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 public:
 | 
					 | 
				
			||||||
  void start() override;
 | 
					  void start() override;
 | 
				
			||||||
  void stop() override;
 | 
					  void stop() override;
 | 
				
			||||||
  void apply(light::AddressableLight &it, const Color ¤t_color) override;
 | 
					  void apply(light::AddressableLight &it, const Color ¤t_color) override;
 | 
				
			||||||
@@ -25,12 +24,11 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
 | 
				
			|||||||
    CONSUMED,
 | 
					    CONSUMED,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  int get_frame_size_(int led_count) const;
 | 
					  unsigned int get_frame_size_(int led_count) const;
 | 
				
			||||||
  void reset_frame_(light::AddressableLight &it);
 | 
					  void reset_frame_(light::AddressableLight &it);
 | 
				
			||||||
  void blank_all_leds_(light::AddressableLight &it);
 | 
					  void blank_all_leds_(light::AddressableLight &it);
 | 
				
			||||||
  Frame parse_frame_(light::AddressableLight &it);
 | 
					  Frame parse_frame_(light::AddressableLight &it);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 | 
				
			||||||
  uint32_t last_ack_{0};
 | 
					  uint32_t last_ack_{0};
 | 
				
			||||||
  uint32_t last_byte_{0};
 | 
					  uint32_t last_byte_{0};
 | 
				
			||||||
  uint32_t last_reset_{0};
 | 
					  uint32_t last_reset_{0};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,92 +1,174 @@
 | 
				
			|||||||
#include "adc_sensor.h"
 | 
					#include "adc_sensor.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
#ifdef USE_ADC_SENSOR_VCC
 | 
					#ifdef USE_ADC_SENSOR_VCC
 | 
				
			||||||
 | 
					#include <Esp.h>
 | 
				
			||||||
ADC_MODE(ADC_VCC)
 | 
					ADC_MODE(ADC_VCC)
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					#include <Arduino.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace adc {
 | 
					namespace adc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char *TAG = "adc";
 | 
					static const char *const TAG = "adc";
 | 
				
			||||||
 | 
					// 13 bits for S3 / 12 bit for all other esp32 variants
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP32
 | 
					// create a const to avoid the repated cast to enum
 | 
				
			||||||
void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; }
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ADCSensor::setup() {
 | 
					void ADCSensor::setup() {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
 | 
					  ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
 | 
				
			||||||
#ifndef USE_ADC_SENSOR_VCC
 | 
					#ifndef USE_ADC_SENSOR_VCC
 | 
				
			||||||
  GPIOPin(this->pin_, INPUT).setup();
 | 
					  pin_->setup();
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP32
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
  analogSetPinAttenuation(this->pin_, this->attenuation_);
 | 
					  adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
 | 
				
			||||||
 | 
					  if (!autorange_) {
 | 
				
			||||||
 | 
					    adc1_config_channel_atten(channel_, attenuation_);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // load characteristics for each attenuation
 | 
				
			||||||
 | 
					  for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) {
 | 
				
			||||||
 | 
					    auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
 | 
				
			||||||
 | 
					                                              1100,  // default vref
 | 
				
			||||||
 | 
					                                              &cal_characteristics_[i]);
 | 
				
			||||||
 | 
					    switch (cal_value) {
 | 
				
			||||||
 | 
					      case ESP_ADC_CAL_VAL_EFUSE_VREF:
 | 
				
			||||||
 | 
					        ESP_LOGV(TAG, "Using eFuse Vref for calibration");
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case ESP_ADC_CAL_VAL_EFUSE_TP:
 | 
				
			||||||
 | 
					        ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration");
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case ESP_ADC_CAL_VAL_DEFAULT_VREF:
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // adc_gpio_init doesn't exist on ESP32-C3 or ESP32-H2
 | 
				
			||||||
 | 
					#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2)
 | 
				
			||||||
 | 
					  adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ADCSensor::dump_config() {
 | 
					void ADCSensor::dump_config() {
 | 
				
			||||||
  LOG_SENSOR("", "ADC Sensor", this);
 | 
					  LOG_SENSOR("", "ADC Sensor", this);
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP8266
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
#ifdef USE_ADC_SENSOR_VCC
 | 
					#ifdef USE_ADC_SENSOR_VCC
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Pin: VCC");
 | 
					  ESP_LOGCONFIG(TAG, "  Pin: VCC");
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Pin: %u", this->pin_);
 | 
					  LOG_PIN("  Pin: ", pin_);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#endif
 | 
					#endif  // USE_ESP8266
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP32
 | 
					
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Pin: %u", this->pin_);
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
  switch (this->attenuation_) {
 | 
					  LOG_PIN("  Pin: ", pin_);
 | 
				
			||||||
    case ADC_0db:
 | 
					  if (autorange_) {
 | 
				
			||||||
      ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
 | 
					    ESP_LOGCONFIG(TAG, " Attenuation: auto");
 | 
				
			||||||
      break;
 | 
					  } else {
 | 
				
			||||||
    case ADC_2_5db:
 | 
					    switch (this->attenuation_) {
 | 
				
			||||||
      ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
 | 
					      case ADC_ATTEN_DB_0:
 | 
				
			||||||
      break;
 | 
					        ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
 | 
				
			||||||
    case ADC_6db:
 | 
					        break;
 | 
				
			||||||
      ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
 | 
					      case ADC_ATTEN_DB_2_5:
 | 
				
			||||||
      break;
 | 
					        ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
 | 
				
			||||||
    case ADC_11db:
 | 
					        break;
 | 
				
			||||||
      ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
 | 
					      case ADC_ATTEN_DB_6:
 | 
				
			||||||
      break;
 | 
					        ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case ADC_ATTEN_DB_11:
 | 
				
			||||||
 | 
					        ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      default:  // This is to satisfy the unused ADC_ATTEN_MAX
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#endif
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
  LOG_UPDATE_INTERVAL(this);
 | 
					  LOG_UPDATE_INTERVAL(this);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
 | 
					float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
 | 
				
			||||||
void ADCSensor::update() {
 | 
					void ADCSensor::update() {
 | 
				
			||||||
  float value_v = this->sample();
 | 
					  float value_v = this->sample();
 | 
				
			||||||
  ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v);
 | 
					  ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
 | 
				
			||||||
  this->publish_state(value_v);
 | 
					  this->publish_state(value_v);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
float ADCSensor::sample() {
 | 
					float ADCSensor::sample() {
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP32
 | 
					#ifdef USE_ADC_SENSOR_VCC
 | 
				
			||||||
  float value_v = analogRead(this->pin_) / 4095.0f;  // NOLINT
 | 
					  int raw = ESP.getVcc();  // NOLINT(readability-static-accessed-through-instance)
 | 
				
			||||||
  switch (this->attenuation_) {
 | 
					#else
 | 
				
			||||||
    case ADC_0db:
 | 
					  int raw = analogRead(this->pin_->get_pin());  // NOLINT
 | 
				
			||||||
      value_v *= 1.1;
 | 
					#endif
 | 
				
			||||||
      break;
 | 
					  if (output_raw_) {
 | 
				
			||||||
    case ADC_2_5db:
 | 
					    return raw;
 | 
				
			||||||
      value_v *= 1.5;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case ADC_6db:
 | 
					 | 
				
			||||||
      value_v *= 2.2;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case ADC_11db:
 | 
					 | 
				
			||||||
      value_v *= 3.9;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return value_v;
 | 
					  return raw / 1024.0f;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP8266
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
#ifdef USE_ADC_SENSOR_VCC
 | 
					float ADCSensor::sample() {
 | 
				
			||||||
  return ESP.getVcc() / 1024.0f;
 | 
					  if (!autorange_) {
 | 
				
			||||||
#else
 | 
					    int raw = adc1_get_raw(channel_);
 | 
				
			||||||
  return analogRead(this->pin_) / 1024.0f;  // NOLINT
 | 
					    if (raw == -1) {
 | 
				
			||||||
#endif
 | 
					      return NAN;
 | 
				
			||||||
#endif
 | 
					    }
 | 
				
			||||||
 | 
					    if (output_raw_) {
 | 
				
			||||||
 | 
					      return raw;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]);
 | 
				
			||||||
 | 
					    return mv / 1000.0f;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int raw11, raw6 = 4095, raw2 = 4095, raw0 = 4095;
 | 
				
			||||||
 | 
					  adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11);
 | 
				
			||||||
 | 
					  raw11 = adc1_get_raw(channel_);
 | 
				
			||||||
 | 
					  if (raw11 < 4095) {
 | 
				
			||||||
 | 
					    adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6);
 | 
				
			||||||
 | 
					    raw6 = adc1_get_raw(channel_);
 | 
				
			||||||
 | 
					    if (raw6 < 4095) {
 | 
				
			||||||
 | 
					      adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5);
 | 
				
			||||||
 | 
					      raw2 = adc1_get_raw(channel_);
 | 
				
			||||||
 | 
					      if (raw2 < 4095) {
 | 
				
			||||||
 | 
					        adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0);
 | 
				
			||||||
 | 
					        raw0 = adc1_get_raw(channel_);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) {
 | 
				
			||||||
 | 
					    return NAN;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]);
 | 
				
			||||||
 | 
					  uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]);
 | 
				
			||||||
 | 
					  uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]);
 | 
				
			||||||
 | 
					  uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Contribution of each value, in range 0-2048
 | 
				
			||||||
 | 
					  uint32_t c11 = std::min(raw11, 2048);
 | 
				
			||||||
 | 
					  uint32_t c6 = 2048 - std::abs(raw6 - 2048);
 | 
				
			||||||
 | 
					  uint32_t c2 = 2048 - std::abs(raw2 - 2048);
 | 
				
			||||||
 | 
					  uint32_t c0 = std::min(4095 - raw0, 2048);
 | 
				
			||||||
 | 
					  // max theoretical csum value is 2048*4 = 8192
 | 
				
			||||||
 | 
					  uint32_t csum = c11 + c6 + c2 + c0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // each mv is max 3900; so max value is 3900*2048*4, fits in unsigned
 | 
				
			||||||
 | 
					  uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
 | 
				
			||||||
 | 
					  return mv_scaled / (float) (csum * 1000U);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP8266
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
 | 
					std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,26 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
#include "esphome/core/esphal.h"
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
#include "esphome/core/defines.h"
 | 
					#include "esphome/core/defines.h"
 | 
				
			||||||
#include "esphome/components/sensor/sensor.h"
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
 | 
					#include "esphome/components/voltage_sampler/voltage_sampler.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					#include "driver/adc.h"
 | 
				
			||||||
 | 
					#include <esp_adc_cal.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace adc {
 | 
					namespace adc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
 | 
					class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP32
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
  /// Set the attenuation for this pin. Only available on the ESP32.
 | 
					  /// Set the attenuation for this pin. Only available on the ESP32.
 | 
				
			||||||
  void set_attenuation(adc_attenuation_t attenuation);
 | 
					  void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; }
 | 
				
			||||||
 | 
					  void set_channel(adc1_channel_t channel) { channel_ = channel; }
 | 
				
			||||||
 | 
					  void set_autorange(bool autorange) { autorange_ = autorange; }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Update adc values.
 | 
					  /// Update adc values.
 | 
				
			||||||
@@ -23,18 +30,23 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
				
			|||||||
  void dump_config() override;
 | 
					  void dump_config() override;
 | 
				
			||||||
  /// `HARDWARE_LATE` setup priority.
 | 
					  /// `HARDWARE_LATE` setup priority.
 | 
				
			||||||
  float get_setup_priority() const override;
 | 
					  float get_setup_priority() const override;
 | 
				
			||||||
  void set_pin(uint8_t pin) { this->pin_ = pin; }
 | 
					  void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
 | 
				
			||||||
 | 
					  void set_output_raw(bool output_raw) { output_raw_ = output_raw; }
 | 
				
			||||||
  float sample() override;
 | 
					  float sample() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP8266
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
  std::string unique_id() override;
 | 
					  std::string unique_id() override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  uint8_t pin_;
 | 
					  InternalGPIOPin *pin_;
 | 
				
			||||||
 | 
					  bool output_raw_{false};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef ARDUINO_ARCH_ESP32
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
  adc_attenuation_t attenuation_{ADC_0db};
 | 
					  adc_atten_t attenuation_{ADC_ATTEN_DB_0};
 | 
				
			||||||
 | 
					  adc1_channel_t channel_{};
 | 
				
			||||||
 | 
					  bool autorange_{false};
 | 
				
			||||||
 | 
					  esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {};
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,30 +4,126 @@ from esphome import pins
 | 
				
			|||||||
from esphome.components import sensor, voltage_sampler
 | 
					from esphome.components import sensor, voltage_sampler
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
    CONF_ATTENUATION,
 | 
					    CONF_ATTENUATION,
 | 
				
			||||||
 | 
					    CONF_RAW,
 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_INPUT,
 | 
				
			||||||
 | 
					    CONF_NUMBER,
 | 
				
			||||||
    CONF_PIN,
 | 
					    CONF_PIN,
 | 
				
			||||||
    DEVICE_CLASS_VOLTAGE,
 | 
					    DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
    ICON_EMPTY,
 | 
					 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_VOLT,
 | 
					    UNIT_VOLT,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from esphome.core import CORE
 | 
				
			||||||
 | 
					from esphome.components.esp32 import get_esp32_variant
 | 
				
			||||||
 | 
					from esphome.components.esp32.const import (
 | 
				
			||||||
 | 
					    VARIANT_ESP32,
 | 
				
			||||||
 | 
					    VARIANT_ESP32C3,
 | 
				
			||||||
 | 
					    VARIANT_ESP32H2,
 | 
				
			||||||
 | 
					    VARIANT_ESP32S2,
 | 
				
			||||||
 | 
					    VARIANT_ESP32S3,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AUTO_LOAD = ["voltage_sampler"]
 | 
					AUTO_LOAD = ["voltage_sampler"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ATTENUATION_MODES = {
 | 
					ATTENUATION_MODES = {
 | 
				
			||||||
    "0db": cg.global_ns.ADC_0db,
 | 
					    "0db": cg.global_ns.ADC_ATTEN_DB_0,
 | 
				
			||||||
    "2.5db": cg.global_ns.ADC_2_5db,
 | 
					    "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
 | 
				
			||||||
    "6db": cg.global_ns.ADC_6db,
 | 
					    "6db": cg.global_ns.ADC_ATTEN_DB_6,
 | 
				
			||||||
    "11db": cg.global_ns.ADC_11db,
 | 
					    "11db": cg.global_ns.ADC_ATTEN_DB_11,
 | 
				
			||||||
 | 
					    "auto": "auto",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
 | 
				
			||||||
 | 
					# pin to adc1 channel mapping
 | 
				
			||||||
 | 
					ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
				
			||||||
 | 
					    VARIANT_ESP32: {
 | 
				
			||||||
 | 
					        36: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
 | 
					        37: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
 | 
					        38: adc1_channel_t.ADC1_CHANNEL_2,
 | 
				
			||||||
 | 
					        39: adc1_channel_t.ADC1_CHANNEL_3,
 | 
				
			||||||
 | 
					        32: adc1_channel_t.ADC1_CHANNEL_4,
 | 
				
			||||||
 | 
					        33: adc1_channel_t.ADC1_CHANNEL_5,
 | 
				
			||||||
 | 
					        34: adc1_channel_t.ADC1_CHANNEL_6,
 | 
				
			||||||
 | 
					        35: adc1_channel_t.ADC1_CHANNEL_7,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    VARIANT_ESP32S2: {
 | 
				
			||||||
 | 
					        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
 | 
					        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
 | 
					        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
				
			||||||
 | 
					        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
				
			||||||
 | 
					        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
				
			||||||
 | 
					        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
				
			||||||
 | 
					        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
				
			||||||
 | 
					        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
				
			||||||
 | 
					        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
				
			||||||
 | 
					        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    VARIANT_ESP32S3: {
 | 
				
			||||||
 | 
					        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
 | 
					        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
 | 
					        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
				
			||||||
 | 
					        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
				
			||||||
 | 
					        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
				
			||||||
 | 
					        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
				
			||||||
 | 
					        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
				
			||||||
 | 
					        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
				
			||||||
 | 
					        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
				
			||||||
 | 
					        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    VARIANT_ESP32C3: {
 | 
				
			||||||
 | 
					        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
 | 
					        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
 | 
					        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
				
			||||||
 | 
					        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
				
			||||||
 | 
					        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    VARIANT_ESP32H2: {
 | 
				
			||||||
 | 
					        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
				
			||||||
 | 
					        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
				
			||||||
 | 
					        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
				
			||||||
 | 
					        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
				
			||||||
 | 
					        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def validate_adc_pin(value):
 | 
					def validate_adc_pin(value):
 | 
				
			||||||
    vcc = str(value).upper()
 | 
					    if str(value).upper() == "VCC":
 | 
				
			||||||
    if vcc == "VCC":
 | 
					        return cv.only_on_esp8266("VCC")
 | 
				
			||||||
        return cv.only_on_esp8266(vcc)
 | 
					
 | 
				
			||||||
    return pins.analog_pin(value)
 | 
					    if CORE.is_esp32:
 | 
				
			||||||
 | 
					        value = pins.internal_gpio_input_pin_number(value)
 | 
				
			||||||
 | 
					        variant = get_esp32_variant()
 | 
				
			||||||
 | 
					        if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
 | 
				
			||||||
 | 
					            raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
 | 
				
			||||||
 | 
					            raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
 | 
				
			||||||
 | 
					        return pins.internal_gpio_input_pin_schema(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CORE.is_esp8266:
 | 
				
			||||||
 | 
					        from esphome.components.esp8266.gpio import CONF_ANALOG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
 | 
				
			||||||
 | 
					            value
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if value != 17:  # A0
 | 
				
			||||||
 | 
					            raise cv.Invalid("ESP8266: Only pin A0 (GPIO17) supports ADC.")
 | 
				
			||||||
 | 
					        return pins.gpio_pin_schema(
 | 
				
			||||||
 | 
					            {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
 | 
				
			||||||
 | 
					        )(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def validate_config(config):
 | 
				
			||||||
 | 
					    if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
 | 
				
			||||||
 | 
					        raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.")
 | 
				
			||||||
 | 
					    return config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
adc_ns = cg.esphome_ns.namespace("adc")
 | 
					adc_ns = cg.esphome_ns.namespace("adc")
 | 
				
			||||||
@@ -35,20 +131,25 @@ ADCSensor = adc_ns.class_(
 | 
				
			|||||||
    "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
 | 
					    "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = (
 | 
					CONFIG_SCHEMA = cv.All(
 | 
				
			||||||
    sensor.sensor_schema(
 | 
					    sensor.sensor_schema(
 | 
				
			||||||
        UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
 | 
					        ADCSensor,
 | 
				
			||||||
 | 
					        unit_of_measurement=UNIT_VOLT,
 | 
				
			||||||
 | 
					        accuracy_decimals=2,
 | 
				
			||||||
 | 
					        device_class=DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
 | 
					        state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    .extend(
 | 
					    .extend(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            cv.GenerateID(): cv.declare_id(ADCSensor),
 | 
					 | 
				
			||||||
            cv.Required(CONF_PIN): validate_adc_pin,
 | 
					            cv.Required(CONF_PIN): validate_adc_pin,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_RAW, default=False): cv.boolean,
 | 
				
			||||||
            cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
 | 
					            cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
 | 
				
			||||||
                cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)
 | 
					                cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    .extend(cv.polling_component_schema("60s"))
 | 
					    .extend(cv.polling_component_schema("60s")),
 | 
				
			||||||
 | 
					    validate_config,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -60,7 +161,20 @@ async def to_code(config):
 | 
				
			|||||||
    if config[CONF_PIN] == "VCC":
 | 
					    if config[CONF_PIN] == "VCC":
 | 
				
			||||||
        cg.add_define("USE_ADC_SENSOR_VCC")
 | 
					        cg.add_define("USE_ADC_SENSOR_VCC")
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        cg.add(var.set_pin(config[CONF_PIN]))
 | 
					        pin = await cg.gpio_pin_expression(config[CONF_PIN])
 | 
				
			||||||
 | 
					        cg.add(var.set_pin(pin))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_RAW in config:
 | 
				
			||||||
 | 
					        cg.add(var.set_output_raw(config[CONF_RAW]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if CONF_ATTENUATION in config:
 | 
					    if CONF_ATTENUATION in config:
 | 
				
			||||||
        cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
 | 
					        if config[CONF_ATTENUATION] == "auto":
 | 
				
			||||||
 | 
					            cg.add(var.set_autorange(cg.global_ns.true))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CORE.is_esp32:
 | 
				
			||||||
 | 
					        variant = get_esp32_variant()
 | 
				
			||||||
 | 
					        pin_num = config[CONF_PIN][CONF_NUMBER]
 | 
				
			||||||
 | 
					        chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
 | 
				
			||||||
 | 
					        cg.add(var.set_channel(chan))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@
 | 
				
			|||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace addressable_light {
 | 
					namespace addressable_light {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char* TAG = "addressable_light.display";
 | 
					static const char *const TAG = "addressable_light.display";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int AddressableLightDisplay::get_width_internal() { return this->width_; }
 | 
					int AddressableLightDisplay::get_width_internal() { return this->width_; }
 | 
				
			||||||
int AddressableLightDisplay::get_height_internal() { return this->height_; }
 | 
					int AddressableLightDisplay::get_height_internal() { return this->height_; }
 | 
				
			||||||
@@ -24,7 +24,7 @@ void AddressableLightDisplay::update() {
 | 
				
			|||||||
void AddressableLightDisplay::display() {
 | 
					void AddressableLightDisplay::display() {
 | 
				
			||||||
  bool dirty = false;
 | 
					  bool dirty = false;
 | 
				
			||||||
  uint8_t old_r, old_g, old_b, old_w;
 | 
					  uint8_t old_r, old_g, old_b, old_w;
 | 
				
			||||||
  Color* c;
 | 
					  Color *c;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (uint32_t offset = 0; offset < this->addressable_light_buffer_.size(); offset++) {
 | 
					  for (uint32_t offset = 0; offset < this->addressable_light_buffer_.size(); offset++) {
 | 
				
			||||||
    c = &(this->addressable_light_buffer_[offset]);
 | 
					    c = &(this->addressable_light_buffer_[offset]);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,13 +4,11 @@
 | 
				
			|||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace ade7953 {
 | 
					namespace ade7953 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char *TAG = "ade7953";
 | 
					static const char *const TAG = "ade7953";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ADE7953::dump_config() {
 | 
					void ADE7953::dump_config() {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "ADE7953:");
 | 
					  ESP_LOGCONFIG(TAG, "ADE7953:");
 | 
				
			||||||
  if (this->has_irq_) {
 | 
					  LOG_PIN("  IRQ Pin: ", irq_pin_);
 | 
				
			||||||
    ESP_LOGCONFIG(TAG, "  IRQ Pin: GPIO%u", this->irq_pin_number_);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  LOG_I2C_DEVICE(this);
 | 
					  LOG_I2C_DEVICE(this);
 | 
				
			||||||
  LOG_UPDATE_INTERVAL(this);
 | 
					  LOG_UPDATE_INTERVAL(this);
 | 
				
			||||||
  LOG_SENSOR("  ", "Voltage Sensor", this->voltage_sensor_);
 | 
					  LOG_SENSOR("  ", "Voltage Sensor", this->voltage_sensor_);
 | 
				
			||||||
@@ -20,27 +18,28 @@ void ADE7953::dump_config() {
 | 
				
			|||||||
  LOG_SENSOR("  ", "Active Power B Sensor", this->active_power_b_sensor_);
 | 
					  LOG_SENSOR("  ", "Active Power B Sensor", this->active_power_b_sensor_);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define ADE_PUBLISH_(name, factor) \
 | 
					#define ADE_PUBLISH_(name, val, factor) \
 | 
				
			||||||
  if (name && this->name##_sensor_) { \
 | 
					  if (err == i2c::ERROR_OK && this->name##_sensor_) { \
 | 
				
			||||||
    float value = *name / factor; \
 | 
					    float value = (val) / (factor); \
 | 
				
			||||||
    this->name##_sensor_->publish_state(value); \
 | 
					    this->name##_sensor_->publish_state(value); \
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#define ADE_PUBLISH(name, factor) ADE_PUBLISH_(name, factor)
 | 
					#define ADE_PUBLISH(name, val, factor) ADE_PUBLISH_(name, val, factor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ADE7953::update() {
 | 
					void ADE7953::update() {
 | 
				
			||||||
  if (!this->is_setup_)
 | 
					  if (!this->is_setup_)
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  auto active_power_a = this->ade_read_<int32_t>(0x0312);
 | 
					  uint32_t val;
 | 
				
			||||||
  ADE_PUBLISH(active_power_a, 154.0f);
 | 
					  i2c::ErrorCode err = ade_read_32_(0x0312, &val);
 | 
				
			||||||
  auto active_power_b = this->ade_read_<int32_t>(0x0313);
 | 
					  ADE_PUBLISH(active_power_a, (int32_t) val, 154.0f);
 | 
				
			||||||
  ADE_PUBLISH(active_power_b, 154.0f);
 | 
					  err = ade_read_32_(0x0313, &val);
 | 
				
			||||||
  auto current_a = this->ade_read_<uint32_t>(0x031A);
 | 
					  ADE_PUBLISH(active_power_b, (int32_t) val, 154.0f);
 | 
				
			||||||
  ADE_PUBLISH(current_a, 100000.0f);
 | 
					  err = ade_read_32_(0x031A, &val);
 | 
				
			||||||
  auto current_b = this->ade_read_<uint32_t>(0x031B);
 | 
					  ADE_PUBLISH(current_a, (uint32_t) val, 100000.0f);
 | 
				
			||||||
  ADE_PUBLISH(current_b, 100000.0f);
 | 
					  err = ade_read_32_(0x031B, &val);
 | 
				
			||||||
  auto voltage = this->ade_read_<uint32_t>(0x031C);
 | 
					  ADE_PUBLISH(current_b, (uint32_t) val, 100000.0f);
 | 
				
			||||||
  ADE_PUBLISH(voltage, 26000.0f);
 | 
					  err = ade_read_32_(0x031C, &val);
 | 
				
			||||||
 | 
					  ADE_PUBLISH(voltage, (uint32_t) val, 26000.0f);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  //    auto apparent_power_a = this->ade_read_<int32_t>(0x0310);
 | 
					  //    auto apparent_power_a = this->ade_read_<int32_t>(0x0310);
 | 
				
			||||||
  //    auto apparent_power_b = this->ade_read_<int32_t>(0x0311);
 | 
					  //    auto apparent_power_b = this->ade_read_<int32_t>(0x0311);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
#include "esphome/components/i2c/i2c.h"
 | 
					#include "esphome/components/i2c/i2c.h"
 | 
				
			||||||
#include "esphome/components/sensor/sensor.h"
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,10 +10,7 @@ namespace ade7953 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class ADE7953 : public i2c::I2CDevice, public PollingComponent {
 | 
					class ADE7953 : public i2c::I2CDevice, public PollingComponent {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  void set_irq_pin(uint8_t irq_pin) {
 | 
					  void set_irq_pin(InternalGPIOPin *irq_pin) { irq_pin_ = irq_pin; }
 | 
				
			||||||
    has_irq_ = true;
 | 
					 | 
				
			||||||
    irq_pin_number_ = irq_pin;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  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_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; }
 | 
					  void set_current_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; }
 | 
				
			||||||
  void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; }
 | 
					  void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; }
 | 
				
			||||||
@@ -24,15 +22,13 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void setup() override {
 | 
					  void setup() override {
 | 
				
			||||||
    if (this->has_irq_) {
 | 
					    if (this->irq_pin_ != nullptr) {
 | 
				
			||||||
      auto pin = GPIOPin(this->irq_pin_number_, INPUT);
 | 
					 | 
				
			||||||
      this->irq_pin_ = &pin;
 | 
					 | 
				
			||||||
      this->irq_pin_->setup();
 | 
					      this->irq_pin_->setup();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this->set_timeout(100, [this]() {
 | 
					    this->set_timeout(100, [this]() {
 | 
				
			||||||
      this->ade_write_<uint8_t>(0x0010, 0x04);
 | 
					      this->ade_write_8_(0x0010, 0x04);
 | 
				
			||||||
      this->ade_write_<uint8_t>(0x00FE, 0xAD);
 | 
					      this->ade_write_8_(0x00FE, 0xAD);
 | 
				
			||||||
      this->ade_write_<uint16_t>(0x0120, 0x0030);
 | 
					      this->ade_write_16_(0x0120, 0x0030);
 | 
				
			||||||
      this->is_setup_ = true;
 | 
					      this->is_setup_ = true;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -42,31 +38,51 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
 | 
				
			|||||||
  void update() override;
 | 
					  void update() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  template<typename T> bool ade_write_(uint16_t reg, T value) {
 | 
					  i2c::ErrorCode ade_write_8_(uint16_t reg, uint8_t value) {
 | 
				
			||||||
    std::vector<uint8_t> data;
 | 
					    std::vector<uint8_t> data;
 | 
				
			||||||
    data.push_back(reg >> 8);
 | 
					    data.push_back(reg >> 8);
 | 
				
			||||||
    data.push_back(reg >> 0);
 | 
					    data.push_back(reg >> 0);
 | 
				
			||||||
    for (int i = sizeof(T) - 1; i >= 0; i--)
 | 
					    data.push_back(value);
 | 
				
			||||||
      data.push_back(value >> (i * 8));
 | 
					    return write(data.data(), data.size());
 | 
				
			||||||
    return this->write_bytes_raw(data);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  template<typename T> optional<T> ade_read_(uint16_t reg) {
 | 
					  i2c::ErrorCode ade_write_16_(uint16_t reg, uint16_t value) {
 | 
				
			||||||
    uint8_t hi = reg >> 8;
 | 
					    std::vector<uint8_t> data;
 | 
				
			||||||
    uint8_t lo = reg >> 0;
 | 
					    data.push_back(reg >> 8);
 | 
				
			||||||
    if (!this->write_bytes_raw({hi, lo}))
 | 
					    data.push_back(reg >> 0);
 | 
				
			||||||
      return {};
 | 
					    data.push_back(value >> 8);
 | 
				
			||||||
    auto ret = this->read_bytes_raw<sizeof(T)>();
 | 
					    data.push_back(value >> 0);
 | 
				
			||||||
    if (!ret.has_value())
 | 
					    return write(data.data(), data.size());
 | 
				
			||||||
      return {};
 | 
					  }
 | 
				
			||||||
    T result = 0;
 | 
					  i2c::ErrorCode ade_write_32_(uint16_t reg, uint32_t value) {
 | 
				
			||||||
    for (int i = 0, j = sizeof(T) - 1; i < sizeof(T); i++, j--)
 | 
					    std::vector<uint8_t> data;
 | 
				
			||||||
      result |= T((*ret)[i]) << (j * 8);
 | 
					    data.push_back(reg >> 8);
 | 
				
			||||||
    return result;
 | 
					    data.push_back(reg >> 0);
 | 
				
			||||||
 | 
					    data.push_back(value >> 24);
 | 
				
			||||||
 | 
					    data.push_back(value >> 16);
 | 
				
			||||||
 | 
					    data.push_back(value >> 8);
 | 
				
			||||||
 | 
					    data.push_back(value >> 0);
 | 
				
			||||||
 | 
					    return write(data.data(), data.size());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  i2c::ErrorCode ade_read_32_(uint16_t reg, uint32_t *value) {
 | 
				
			||||||
 | 
					    uint8_t reg_data[2];
 | 
				
			||||||
 | 
					    reg_data[0] = reg >> 8;
 | 
				
			||||||
 | 
					    reg_data[1] = reg >> 0;
 | 
				
			||||||
 | 
					    i2c::ErrorCode err = write(reg_data, 2);
 | 
				
			||||||
 | 
					    if (err != i2c::ERROR_OK)
 | 
				
			||||||
 | 
					      return err;
 | 
				
			||||||
 | 
					    uint8_t recv[4];
 | 
				
			||||||
 | 
					    err = read(recv, 4);
 | 
				
			||||||
 | 
					    if (err != i2c::ERROR_OK)
 | 
				
			||||||
 | 
					      return err;
 | 
				
			||||||
 | 
					    *value = 0;
 | 
				
			||||||
 | 
					    *value |= ((uint32_t) recv[0]) << 24;
 | 
				
			||||||
 | 
					    *value |= ((uint32_t) recv[1]) << 16;
 | 
				
			||||||
 | 
					    *value |= ((uint32_t) recv[2]) << 8;
 | 
				
			||||||
 | 
					    *value |= ((uint32_t) recv[3]);
 | 
				
			||||||
 | 
					    return i2c::ERROR_OK;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool has_irq_ = false;
 | 
					  InternalGPIOPin *irq_pin_ = nullptr;
 | 
				
			||||||
  uint8_t irq_pin_number_;
 | 
					 | 
				
			||||||
  GPIOPin *irq_pin_{nullptr};
 | 
					 | 
				
			||||||
  bool is_setup_{false};
 | 
					  bool is_setup_{false};
 | 
				
			||||||
  sensor::Sensor *voltage_sensor_{nullptr};
 | 
					  sensor::Sensor *voltage_sensor_{nullptr};
 | 
				
			||||||
  sensor::Sensor *current_a_sensor_{nullptr};
 | 
					  sensor::Sensor *current_a_sensor_{nullptr};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,6 @@ from esphome.const import (
 | 
				
			|||||||
    DEVICE_CLASS_CURRENT,
 | 
					    DEVICE_CLASS_CURRENT,
 | 
				
			||||||
    DEVICE_CLASS_POWER,
 | 
					    DEVICE_CLASS_POWER,
 | 
				
			||||||
    DEVICE_CLASS_VOLTAGE,
 | 
					    DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
    ICON_EMPTY,
 | 
					 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_VOLT,
 | 
					    UNIT_VOLT,
 | 
				
			||||||
    UNIT_AMPERE,
 | 
					    UNIT_AMPERE,
 | 
				
			||||||
@@ -30,29 +29,36 @@ CONFIG_SCHEMA = (
 | 
				
			|||||||
    cv.Schema(
 | 
					    cv.Schema(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            cv.GenerateID(): cv.declare_id(ADE7953),
 | 
					            cv.GenerateID(): cv.declare_id(ADE7953),
 | 
				
			||||||
            cv.Optional(CONF_IRQ_PIN): pins.input_pin,
 | 
					            cv.Optional(CONF_IRQ_PIN): pins.internal_gpio_input_pin_schema,
 | 
				
			||||||
            cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
 | 
					            cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
 | 
				
			||||||
                UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
 | 
					                unit_of_measurement=UNIT_VOLT,
 | 
				
			||||||
 | 
					                accuracy_decimals=1,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(
 | 
					            cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(
 | 
				
			||||||
                UNIT_AMPERE,
 | 
					                unit_of_measurement=UNIT_AMPERE,
 | 
				
			||||||
                ICON_EMPTY,
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
                2,
 | 
					                device_class=DEVICE_CLASS_CURRENT,
 | 
				
			||||||
                DEVICE_CLASS_CURRENT,
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
                STATE_CLASS_MEASUREMENT,
 | 
					 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(
 | 
					            cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(
 | 
				
			||||||
                UNIT_AMPERE,
 | 
					                unit_of_measurement=UNIT_AMPERE,
 | 
				
			||||||
                ICON_EMPTY,
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
                2,
 | 
					                device_class=DEVICE_CLASS_CURRENT,
 | 
				
			||||||
                DEVICE_CLASS_CURRENT,
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
                STATE_CLASS_MEASUREMENT,
 | 
					 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(
 | 
					            cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(
 | 
				
			||||||
                UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
 | 
					                unit_of_measurement=UNIT_WATT,
 | 
				
			||||||
 | 
					                accuracy_decimals=1,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_POWER,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(
 | 
					            cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(
 | 
				
			||||||
                UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
 | 
					                unit_of_measurement=UNIT_WATT,
 | 
				
			||||||
 | 
					                accuracy_decimals=1,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_POWER,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
@@ -67,7 +73,8 @@ async def to_code(config):
 | 
				
			|||||||
    await i2c.register_i2c_device(var, config)
 | 
					    await i2c.register_i2c_device(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if CONF_IRQ_PIN in config:
 | 
					    if CONF_IRQ_PIN in config:
 | 
				
			||||||
        cg.add(var.set_irq_pin(config[CONF_IRQ_PIN]))
 | 
					        irq_pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
 | 
				
			||||||
 | 
					        cg.add(var.set_irq_pin(irq_pin))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for key in [
 | 
					    for key in [
 | 
				
			||||||
        CONF_VOLTAGE,
 | 
					        CONF_VOLTAGE,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,11 @@
 | 
				
			|||||||
#include "ads1115.h"
 | 
					#include "ads1115.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace ads1115 {
 | 
					namespace ads1115 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char *TAG = "ads1115";
 | 
					static const char *const TAG = "ads1115";
 | 
				
			||||||
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
 | 
					static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
 | 
				
			||||||
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
 | 
					static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -64,11 +65,6 @@ void ADS1115Component::setup() {
 | 
				
			|||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  this->prev_config_ = config;
 | 
					  this->prev_config_ = config;
 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (auto *sensor : this->sensors_) {
 | 
					 | 
				
			||||||
    this->set_interval(sensor->get_name(), sensor->update_interval(),
 | 
					 | 
				
			||||||
                       [this, sensor] { this->request_measurement(sensor); });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void ADS1115Component::dump_config() {
 | 
					void ADS1115Component::dump_config() {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
 | 
					  ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
 | 
				
			||||||
@@ -107,17 +103,22 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    this->prev_config_ = config;
 | 
					    this->prev_config_ = config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // about 1.6 ms with 860 samples per second
 | 
					    // about 1.2 ms with 860 samples per second
 | 
				
			||||||
    delay(2);
 | 
					    delay(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    uint32_t start = millis();
 | 
					    // in continuous mode, conversion will always be running, rely on the delay
 | 
				
			||||||
    while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
 | 
					    // to ensure conversion is taking place with the correct settings
 | 
				
			||||||
      if (millis() - start > 100) {
 | 
					    // can we use the rdy pin to trigger when a conversion is done?
 | 
				
			||||||
        ESP_LOGW(TAG, "Reading ADS1115 timed out");
 | 
					    if (!this->continuous_mode_) {
 | 
				
			||||||
        this->status_set_warning();
 | 
					      uint32_t start = millis();
 | 
				
			||||||
        return NAN;
 | 
					      while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
 | 
				
			||||||
 | 
					        if (millis() - start > 100) {
 | 
				
			||||||
 | 
					          ESP_LOGW(TAG, "Reading ADS1115 timed out");
 | 
				
			||||||
 | 
					          this->status_set_warning();
 | 
				
			||||||
 | 
					          return NAN;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        yield();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      yield();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -159,7 +160,7 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
 | 
				
			|||||||
float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); }
 | 
					float ADS1115Sensor::sample() { return this->parent_->request_measurement(this); }
 | 
				
			||||||
void ADS1115Sensor::update() {
 | 
					void ADS1115Sensor::update() {
 | 
				
			||||||
  float v = this->parent_->request_measurement(this);
 | 
					  float v = this->parent_->request_measurement(this);
 | 
				
			||||||
  if (!isnan(v)) {
 | 
					  if (!std::isnan(v)) {
 | 
				
			||||||
    ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v);
 | 
					    ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v);
 | 
				
			||||||
    this->publish_state(v);
 | 
					    this->publish_state(v);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,6 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_GAIN,
 | 
					    CONF_GAIN,
 | 
				
			||||||
    CONF_MULTIPLEXER,
 | 
					    CONF_MULTIPLEXER,
 | 
				
			||||||
    DEVICE_CLASS_VOLTAGE,
 | 
					    DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
    ICON_EMPTY,
 | 
					 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_VOLT,
 | 
					    UNIT_VOLT,
 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
@@ -53,11 +52,14 @@ ADS1115Sensor = ads1115_ns.class_(
 | 
				
			|||||||
CONF_ADS1115_ID = "ads1115_id"
 | 
					CONF_ADS1115_ID = "ads1115_id"
 | 
				
			||||||
CONFIG_SCHEMA = (
 | 
					CONFIG_SCHEMA = (
 | 
				
			||||||
    sensor.sensor_schema(
 | 
					    sensor.sensor_schema(
 | 
				
			||||||
        UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
 | 
					        ADS1115Sensor,
 | 
				
			||||||
 | 
					        unit_of_measurement=UNIT_VOLT,
 | 
				
			||||||
 | 
					        accuracy_decimals=3,
 | 
				
			||||||
 | 
					        device_class=DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
 | 
					        state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    .extend(
 | 
					    .extend(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            cv.GenerateID(): cv.declare_id(ADS1115Sensor),
 | 
					 | 
				
			||||||
            cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
 | 
					            cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
 | 
				
			||||||
            cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"),
 | 
					            cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"),
 | 
				
			||||||
            cv.Required(CONF_GAIN): validate_gain,
 | 
					            cv.Required(CONF_GAIN): validate_gain,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,20 +10,21 @@
 | 
				
			|||||||
//
 | 
					//
 | 
				
			||||||
// According to the datasheet, the component is supposed to respond in more than 75ms. In fact, it can answer almost
 | 
					// According to the datasheet, the component is supposed to respond in more than 75ms. In fact, it can answer almost
 | 
				
			||||||
// immediately for temperature. But for humidity, it takes >90ms to get a valid data. From experience, we have best
 | 
					// immediately for temperature. But for humidity, it takes >90ms to get a valid data. From experience, we have best
 | 
				
			||||||
// results making successive requests; the current implementation make 3 attemps with a delay of 30ms each time.
 | 
					// results making successive requests; the current implementation makes 3 attempts with a delay of 30ms each time.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "aht10.h"
 | 
					#include "aht10.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace aht10 {
 | 
					namespace aht10 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char *TAG = "aht10";
 | 
					static const char *const TAG = "aht10";
 | 
				
			||||||
static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1};
 | 
					static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1};
 | 
				
			||||||
static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
 | 
					static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
 | 
				
			||||||
static const uint8_t AHT10_DEFAULT_DELAY = 5;    // ms, for calibration and temperature measurement
 | 
					static const uint8_t AHT10_DEFAULT_DELAY = 5;    // ms, for calibration and temperature measurement
 | 
				
			||||||
static const uint8_t AHT10_HUMIDITY_DELAY = 30;  // ms
 | 
					static const uint8_t AHT10_HUMIDITY_DELAY = 30;  // ms
 | 
				
			||||||
static const uint8_t AHT10_ATTEMPS = 3;          // safety margin, normally 3 attemps are enough: 3*30=90ms
 | 
					static const uint8_t AHT10_ATTEMPTS = 3;         // safety margin, normally 3 attempts are enough: 3*30=90ms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AHT10Component::setup() {
 | 
					void AHT10Component::setup() {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "Setting up AHT10...");
 | 
					  ESP_LOGCONFIG(TAG, "Setting up AHT10...");
 | 
				
			||||||
@@ -33,8 +34,19 @@ void AHT10Component::setup() {
 | 
				
			|||||||
    this->mark_failed();
 | 
					    this->mark_failed();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  uint8_t data;
 | 
					  uint8_t data = 0;
 | 
				
			||||||
  if (!this->read_byte(0, &data, AHT10_DEFAULT_DELAY)) {
 | 
					  if (this->write(&data, 1) != i2c::ERROR_OK) {
 | 
				
			||||||
 | 
					    ESP_LOGD(TAG, "Communication with AHT10 failed!");
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  delay(AHT10_DEFAULT_DELAY);
 | 
				
			||||||
 | 
					  if (this->read(&data, 1) != i2c::ERROR_OK) {
 | 
				
			||||||
 | 
					    ESP_LOGD(TAG, "Communication with AHT10 failed!");
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (this->read(&data, 1) != i2c::ERROR_OK) {
 | 
				
			||||||
    ESP_LOGD(TAG, "Communication with AHT10 failed!");
 | 
					    ESP_LOGD(TAG, "Communication with AHT10 failed!");
 | 
				
			||||||
    this->mark_failed();
 | 
					    this->mark_failed();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
@@ -55,15 +67,19 @@ void AHT10Component::update() {
 | 
				
			|||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  uint8_t data[6];
 | 
					  uint8_t data[6];
 | 
				
			||||||
  uint8_t delay = AHT10_DEFAULT_DELAY;
 | 
					  uint8_t delay_ms = AHT10_DEFAULT_DELAY;
 | 
				
			||||||
  if (this->humidity_sensor_ != nullptr)
 | 
					  if (this->humidity_sensor_ != nullptr)
 | 
				
			||||||
    delay = AHT10_HUMIDITY_DELAY;
 | 
					    delay_ms = AHT10_HUMIDITY_DELAY;
 | 
				
			||||||
  for (int i = 0; i < AHT10_ATTEMPS; ++i) {
 | 
					  bool success = false;
 | 
				
			||||||
    ESP_LOGVV(TAG, "Attemps %u at %6ld", i, millis());
 | 
					  for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
 | 
				
			||||||
    delay_microseconds_accurate(4);
 | 
					    ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
 | 
				
			||||||
    if (!this->read_bytes(0, data, 6, delay)) {
 | 
					    delay(delay_ms);
 | 
				
			||||||
 | 
					    if (this->read(data, 6) != i2c::ERROR_OK) {
 | 
				
			||||||
      ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
 | 
					      ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
 | 
				
			||||||
    } else if ((data[0] & 0x80) == 0x80) {  // Bit[7] = 0b1, device is busy
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ((data[0] & 0x80) == 0x80) {  // Bit[7] = 0b1, device is busy
 | 
				
			||||||
      ESP_LOGD(TAG, "AHT10 is busy, waiting...");
 | 
					      ESP_LOGD(TAG, "AHT10 is busy, waiting...");
 | 
				
			||||||
    } else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
 | 
					    } else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
 | 
				
			||||||
      // Unrealistic humidity (0x0)
 | 
					      // Unrealistic humidity (0x0)
 | 
				
			||||||
@@ -80,11 +96,12 @@ void AHT10Component::update() {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // data is valid, we can break the loop
 | 
					      // data is valid, we can break the loop
 | 
				
			||||||
      ESP_LOGVV(TAG, "Answer at %6ld", millis());
 | 
					      ESP_LOGVV(TAG, "Answer at %6u", millis());
 | 
				
			||||||
 | 
					      success = true;
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if ((data[0] & 0x80) == 0x80) {
 | 
					  if (!success || (data[0] & 0x80) == 0x80) {
 | 
				
			||||||
    ESP_LOGE(TAG, "Measurements reading timed-out!");
 | 
					    ESP_LOGE(TAG, "Measurements reading timed-out!");
 | 
				
			||||||
    this->status_set_warning();
 | 
					    this->status_set_warning();
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
@@ -93,19 +110,19 @@ void AHT10Component::update() {
 | 
				
			|||||||
  uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
 | 
					  uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
 | 
				
			||||||
  uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
 | 
					  uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0;
 | 
					  float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
 | 
				
			||||||
  float humidity;
 | 
					  float humidity;
 | 
				
			||||||
  if (raw_humidity == 0) {  // unrealistic value
 | 
					  if (raw_humidity == 0) {  // unrealistic value
 | 
				
			||||||
    humidity = NAN;
 | 
					    humidity = NAN;
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    humidity = (float) raw_humidity * 100.0 / 1048576.0;
 | 
					    humidity = (float) raw_humidity * 100.0f / 1048576.0f;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (this->temperature_sensor_ != nullptr) {
 | 
					  if (this->temperature_sensor_ != nullptr) {
 | 
				
			||||||
    this->temperature_sensor_->publish_state(temperature);
 | 
					    this->temperature_sensor_->publish_state(temperature);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (this->humidity_sensor_ != nullptr) {
 | 
					  if (this->humidity_sensor_ != nullptr) {
 | 
				
			||||||
    if (isnan(humidity))
 | 
					    if (std::isnan(humidity))
 | 
				
			||||||
      ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
 | 
					      ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
 | 
				
			||||||
    this->humidity_sensor_->publish_state(humidity);
 | 
					    this->humidity_sensor_->publish_state(humidity);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,6 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_TEMPERATURE,
 | 
					    CONF_TEMPERATURE,
 | 
				
			||||||
    DEVICE_CLASS_HUMIDITY,
 | 
					    DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
    DEVICE_CLASS_TEMPERATURE,
 | 
					    DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
    ICON_EMPTY,
 | 
					 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_CELSIUS,
 | 
					    UNIT_CELSIUS,
 | 
				
			||||||
    UNIT_PERCENT,
 | 
					    UNIT_PERCENT,
 | 
				
			||||||
@@ -23,18 +22,16 @@ CONFIG_SCHEMA = (
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            cv.GenerateID(): cv.declare_id(AHT10Component),
 | 
					            cv.GenerateID(): cv.declare_id(AHT10Component),
 | 
				
			||||||
            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
					            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
				
			||||||
                UNIT_CELSIUS,
 | 
					                unit_of_measurement=UNIT_CELSIUS,
 | 
				
			||||||
                ICON_EMPTY,
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
                2,
 | 
					                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
                DEVICE_CLASS_TEMPERATURE,
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
                STATE_CLASS_MEASUREMENT,
 | 
					 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
					            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
				
			||||||
                UNIT_PERCENT,
 | 
					                unit_of_measurement=UNIT_PERCENT,
 | 
				
			||||||
                ICON_EMPTY,
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
                2,
 | 
					                device_class=DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
                DEVICE_CLASS_HUMIDITY,
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
                STATE_CLASS_MEASUREMENT,
 | 
					 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										23
									
								
								esphome/components/airthings_ble/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphome/components/airthings_ble/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import esp32_ble_tracker
 | 
				
			||||||
 | 
					from esphome.const import CONF_ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEPENDENCIES = ["esp32_ble_tracker"]
 | 
				
			||||||
 | 
					CODEOWNERS = ["@jeromelaban"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					airthings_ble_ns = cg.esphome_ns.namespace("airthings_ble")
 | 
				
			||||||
 | 
					AirthingsListener = airthings_ble_ns.class_(
 | 
				
			||||||
 | 
					    "AirthingsListener", esp32_ble_tracker.ESPBTDeviceListener
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.Schema(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.GenerateID(): cv.declare_id(AirthingsListener),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					    yield esp32_ble_tracker.register_ble_device(var, config)
 | 
				
			||||||
							
								
								
									
										33
									
								
								esphome/components/airthings_ble/airthings_listener.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								esphome/components/airthings_ble/airthings_listener.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					#include "airthings_listener.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace airthings_ble {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "airthings_ble";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
				
			||||||
 | 
					  for (auto &it : device.get_manufacturer_datas()) {
 | 
				
			||||||
 | 
					    if (it.uuid == esp32_ble_tracker::ESPBTUUID::from_uint32(0x0334)) {
 | 
				
			||||||
 | 
					      if (it.data.size() < 4)
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      uint32_t sn = it.data[0];
 | 
				
			||||||
 | 
					      sn |= ((uint32_t) it.data[1] << 8);
 | 
				
			||||||
 | 
					      sn |= ((uint32_t) it.data[2] << 16);
 | 
				
			||||||
 | 
					      sn |= ((uint32_t) it.data[3] << 24);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ESP_LOGD(TAG, "Found AirThings device Serial:%u (MAC: %s)", sn, device.address_str().c_str());
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace airthings_ble
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										19
									
								
								esphome/components/airthings_ble/airthings_listener.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								esphome/components/airthings_ble/airthings_listener.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace airthings_ble {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AirthingsListener : public esp32_ble_tracker::ESPBTDeviceListener {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace airthings_ble
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										1
									
								
								esphome/components/airthings_wave_mini/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/airthings_wave_mini/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					CODEOWNERS = ["@ncareau"]
 | 
				
			||||||
							
								
								
									
										113
									
								
								esphome/components/airthings_wave_mini/airthings_wave_mini.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								esphome/components/airthings_wave_mini/airthings_wave_mini.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					#include "airthings_wave_mini.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace airthings_wave_mini {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "airthings_wave_mini";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
				
			||||||
 | 
					                                            esp_ble_gattc_cb_param_t *param) {
 | 
				
			||||||
 | 
					  switch (event) {
 | 
				
			||||||
 | 
					    case ESP_GATTC_OPEN_EVT: {
 | 
				
			||||||
 | 
					      if (param->open.status == ESP_GATT_OK) {
 | 
				
			||||||
 | 
					        ESP_LOGI(TAG, "Connected successfully!");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case ESP_GATTC_DISCONNECT_EVT: {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Disconnected!");
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
				
			||||||
 | 
					      this->handle_ = 0;
 | 
				
			||||||
 | 
					      auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
 | 
				
			||||||
 | 
					      if (chr == nullptr) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
 | 
				
			||||||
 | 
					                 sensors_data_characteristic_uuid_.to_string().c_str());
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this->handle_ = chr->handle;
 | 
				
			||||||
 | 
					      this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      request_read_values_();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case ESP_GATTC_READ_CHAR_EVT: {
 | 
				
			||||||
 | 
					      if (param->read.conn_id != this->parent()->conn_id)
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      if (param->read.status != ESP_GATT_OK) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (param->read.handle == this->handle_) {
 | 
				
			||||||
 | 
					        read_sensors_(param->read.value, param->read.value_len);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
 | 
				
			||||||
 | 
					  auto *value = (WaveMiniReadings *) raw_value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (sizeof(WaveMiniReadings) <= value_len) {
 | 
				
			||||||
 | 
					    this->humidity_sensor_->publish_state(value->humidity / 100.0f);
 | 
				
			||||||
 | 
					    this->pressure_sensor_->publish_state(value->pressure / 50.0f);
 | 
				
			||||||
 | 
					    this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f);
 | 
				
			||||||
 | 
					    if (is_valid_voc_value_(value->voc)) {
 | 
				
			||||||
 | 
					      this->tvoc_sensor_->publish_state(value->voc);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // This instance must not stay connected
 | 
				
			||||||
 | 
					    // so other clients can connect to it (e.g. the
 | 
				
			||||||
 | 
					    // mobile app).
 | 
				
			||||||
 | 
					    parent()->set_enabled(false);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool AirthingsWaveMini::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AirthingsWaveMini::update() {
 | 
				
			||||||
 | 
					  if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
 | 
				
			||||||
 | 
					    if (!parent()->enabled) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Reconnecting to device");
 | 
				
			||||||
 | 
					      parent()->set_enabled(true);
 | 
				
			||||||
 | 
					      parent()->connect();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Connection in progress");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AirthingsWaveMini::request_read_values_() {
 | 
				
			||||||
 | 
					  auto status =
 | 
				
			||||||
 | 
					      esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
 | 
				
			||||||
 | 
					  if (status) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AirthingsWaveMini::dump_config() {
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Humidity", this->humidity_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Temperature", this->temperature_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Pressure", this->pressure_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "TVOC", this->tvoc_sensor_);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AirthingsWaveMini::AirthingsWaveMini()
 | 
				
			||||||
 | 
					    : PollingComponent(10000),
 | 
				
			||||||
 | 
					      service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
 | 
				
			||||||
 | 
					      sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace airthings_wave_mini
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
							
								
								
									
										65
									
								
								esphome/components/airthings_wave_mini/airthings_wave_mini.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								esphome/components/airthings_wave_mini/airthings_wave_mini.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <esp_gattc_api.h>
 | 
				
			||||||
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					#include <iterator>
 | 
				
			||||||
 | 
					#include "esphome/components/ble_client/ble_client.h"
 | 
				
			||||||
 | 
					#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
				
			||||||
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace airthings_wave_mini {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba";
 | 
				
			||||||
 | 
					static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientNode {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  AirthingsWaveMini();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					  void update() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
				
			||||||
 | 
					                           esp_ble_gattc_cb_param_t *param) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
 | 
				
			||||||
 | 
					  void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
 | 
				
			||||||
 | 
					  void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
 | 
				
			||||||
 | 
					  void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  bool is_valid_voc_value_(uint16_t voc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void read_sensors_(uint8_t *value, uint16_t value_len);
 | 
				
			||||||
 | 
					  void request_read_values_();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  sensor::Sensor *temperature_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *humidity_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *pressure_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *tvoc_sensor_{nullptr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint16_t handle_;
 | 
				
			||||||
 | 
					  esp32_ble_tracker::ESPBTUUID service_uuid_;
 | 
				
			||||||
 | 
					  esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  struct WaveMiniReadings {
 | 
				
			||||||
 | 
					    uint16_t unused01;
 | 
				
			||||||
 | 
					    uint16_t temperature;
 | 
				
			||||||
 | 
					    uint16_t pressure;
 | 
				
			||||||
 | 
					    uint16_t humidity;
 | 
				
			||||||
 | 
					    uint16_t voc;
 | 
				
			||||||
 | 
					    uint16_t unused02;
 | 
				
			||||||
 | 
					    uint32_t unused03;
 | 
				
			||||||
 | 
					    uint32_t unused04;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace airthings_wave_mini
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
							
								
								
									
										82
									
								
								esphome/components/airthings_wave_mini/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								esphome/components/airthings_wave_mini/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor, ble_client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_PRESSURE,
 | 
				
			||||||
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					    UNIT_PERCENT,
 | 
				
			||||||
 | 
					    UNIT_CELSIUS,
 | 
				
			||||||
 | 
					    UNIT_HECTOPASCAL,
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_HUMIDITY,
 | 
				
			||||||
 | 
					    CONF_TVOC,
 | 
				
			||||||
 | 
					    CONF_PRESSURE,
 | 
				
			||||||
 | 
					    CONF_TEMPERATURE,
 | 
				
			||||||
 | 
					    UNIT_PARTS_PER_BILLION,
 | 
				
			||||||
 | 
					    ICON_RADIATOR,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEPENDENCIES = ["ble_client"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					airthings_wave_mini_ns = cg.esphome_ns.namespace("airthings_wave_mini")
 | 
				
			||||||
 | 
					AirthingsWaveMini = airthings_wave_mini_ns.class_(
 | 
				
			||||||
 | 
					    "AirthingsWaveMini", cg.PollingComponent, ble_client.BLEClientNode
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.All(
 | 
				
			||||||
 | 
					    cv.Schema(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cv.GenerateID(): cv.declare_id(AirthingsWaveMini),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_PERCENT,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_CELSIUS,
 | 
				
			||||||
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_HECTOPASCAL,
 | 
				
			||||||
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_PRESSURE,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_TVOC): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_PARTS_PER_BILLION,
 | 
				
			||||||
 | 
					                icon=ICON_RADIATOR,
 | 
				
			||||||
 | 
					                accuracy_decimals=0,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .extend(cv.polling_component_schema("5min"))
 | 
				
			||||||
 | 
					    .extend(ble_client.BLE_CLIENT_SCHEMA),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await ble_client.register_ble_node(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_HUMIDITY in config:
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(config[CONF_HUMIDITY])
 | 
				
			||||||
 | 
					        cg.add(var.set_humidity(sens))
 | 
				
			||||||
 | 
					    if CONF_TEMPERATURE in config:
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
 | 
				
			||||||
 | 
					        cg.add(var.set_temperature(sens))
 | 
				
			||||||
 | 
					    if CONF_PRESSURE in config:
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(config[CONF_PRESSURE])
 | 
				
			||||||
 | 
					        cg.add(var.set_pressure(sens))
 | 
				
			||||||
 | 
					    if CONF_TVOC in config:
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(config[CONF_TVOC])
 | 
				
			||||||
 | 
					        cg.add(var.set_tvoc(sens))
 | 
				
			||||||
							
								
								
									
										1
									
								
								esphome/components/airthings_wave_plus/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/airthings_wave_plus/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					CODEOWNERS = ["@jeromelaban"]
 | 
				
			||||||
							
								
								
									
										137
									
								
								esphome/components/airthings_wave_plus/airthings_wave_plus.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								esphome/components/airthings_wave_plus/airthings_wave_plus.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
				
			|||||||
 | 
					#include "airthings_wave_plus.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace airthings_wave_plus {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "airthings_wave_plus";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
				
			||||||
 | 
					                                            esp_ble_gattc_cb_param_t *param) {
 | 
				
			||||||
 | 
					  switch (event) {
 | 
				
			||||||
 | 
					    case ESP_GATTC_OPEN_EVT: {
 | 
				
			||||||
 | 
					      if (param->open.status == ESP_GATT_OK) {
 | 
				
			||||||
 | 
					        ESP_LOGI(TAG, "Connected successfully!");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case ESP_GATTC_DISCONNECT_EVT: {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Disconnected!");
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
				
			||||||
 | 
					      this->handle_ = 0;
 | 
				
			||||||
 | 
					      auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_);
 | 
				
			||||||
 | 
					      if (chr == nullptr) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
 | 
				
			||||||
 | 
					                 sensors_data_characteristic_uuid_.to_string().c_str());
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this->handle_ = chr->handle;
 | 
				
			||||||
 | 
					      this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      request_read_values_();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case ESP_GATTC_READ_CHAR_EVT: {
 | 
				
			||||||
 | 
					      if (param->read.conn_id != this->parent()->conn_id)
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      if (param->read.status != ESP_GATT_OK) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (param->read.handle == this->handle_) {
 | 
				
			||||||
 | 
					        read_sensors_(param->read.value, param->read.value_len);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) {
 | 
				
			||||||
 | 
					  auto *value = (WavePlusReadings *) raw_value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (sizeof(WavePlusReadings) <= value_len) {
 | 
				
			||||||
 | 
					    ESP_LOGD(TAG, "version = %d", value->version);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (value->version == 1) {
 | 
				
			||||||
 | 
					      ESP_LOGD(TAG, "ambient light = %d", value->ambientLight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this->humidity_sensor_->publish_state(value->humidity / 2.0f);
 | 
				
			||||||
 | 
					      if (is_valid_radon_value_(value->radon)) {
 | 
				
			||||||
 | 
					        this->radon_sensor_->publish_state(value->radon);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (is_valid_radon_value_(value->radon_lt)) {
 | 
				
			||||||
 | 
					        this->radon_long_term_sensor_->publish_state(value->radon_lt);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this->temperature_sensor_->publish_state(value->temperature / 100.0f);
 | 
				
			||||||
 | 
					      this->pressure_sensor_->publish_state(value->pressure / 50.0f);
 | 
				
			||||||
 | 
					      if (is_valid_co2_value_(value->co2)) {
 | 
				
			||||||
 | 
					        this->co2_sensor_->publish_state(value->co2);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (is_valid_voc_value_(value->voc)) {
 | 
				
			||||||
 | 
					        this->tvoc_sensor_->publish_state(value->voc);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // This instance must not stay connected
 | 
				
			||||||
 | 
					      // so other clients can connect to it (e.g. the
 | 
				
			||||||
 | 
					      // mobile app).
 | 
				
			||||||
 | 
					      parent()->set_enabled(false);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool AirthingsWavePlus::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AirthingsWavePlus::update() {
 | 
				
			||||||
 | 
					  if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
 | 
				
			||||||
 | 
					    if (!parent()->enabled) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Reconnecting to device");
 | 
				
			||||||
 | 
					      parent()->set_enabled(true);
 | 
				
			||||||
 | 
					      parent()->connect();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Connection in progress");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AirthingsWavePlus::request_read_values_() {
 | 
				
			||||||
 | 
					  auto status =
 | 
				
			||||||
 | 
					      esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
 | 
				
			||||||
 | 
					  if (status) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void AirthingsWavePlus::dump_config() {
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Humidity", this->humidity_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Radon", this->radon_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Radon Long Term", this->radon_long_term_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Temperature", this->temperature_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Pressure", this->pressure_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "CO2", this->co2_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "TVOC", this->tvoc_sensor_);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AirthingsWavePlus::AirthingsWavePlus()
 | 
				
			||||||
 | 
					    : PollingComponent(10000),
 | 
				
			||||||
 | 
					      service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
 | 
				
			||||||
 | 
					      sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace airthings_wave_plus
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
							
								
								
									
										75
									
								
								esphome/components/airthings_wave_plus/airthings_wave_plus.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								esphome/components/airthings_wave_plus/airthings_wave_plus.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <esp_gattc_api.h>
 | 
				
			||||||
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					#include <iterator>
 | 
				
			||||||
 | 
					#include "esphome/components/ble_client/ble_client.h"
 | 
				
			||||||
 | 
					#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
				
			||||||
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace airthings_wave_plus {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba";
 | 
				
			||||||
 | 
					static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientNode {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  AirthingsWavePlus();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					  void update() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
				
			||||||
 | 
					                           esp_ble_gattc_cb_param_t *param) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
 | 
				
			||||||
 | 
					  void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; }
 | 
				
			||||||
 | 
					  void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
 | 
				
			||||||
 | 
					  void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
 | 
				
			||||||
 | 
					  void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; }
 | 
				
			||||||
 | 
					  void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
 | 
				
			||||||
 | 
					  void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  bool is_valid_radon_value_(uint16_t radon);
 | 
				
			||||||
 | 
					  bool is_valid_voc_value_(uint16_t voc);
 | 
				
			||||||
 | 
					  bool is_valid_co2_value_(uint16_t co2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void read_sensors_(uint8_t *value, uint16_t value_len);
 | 
				
			||||||
 | 
					  void request_read_values_();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  sensor::Sensor *temperature_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *radon_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *radon_long_term_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *humidity_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *pressure_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *co2_sensor_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *tvoc_sensor_{nullptr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint16_t handle_;
 | 
				
			||||||
 | 
					  esp32_ble_tracker::ESPBTUUID service_uuid_;
 | 
				
			||||||
 | 
					  esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  struct WavePlusReadings {
 | 
				
			||||||
 | 
					    uint8_t version;
 | 
				
			||||||
 | 
					    uint8_t humidity;
 | 
				
			||||||
 | 
					    uint8_t ambientLight;
 | 
				
			||||||
 | 
					    uint8_t unused01;
 | 
				
			||||||
 | 
					    uint16_t radon;
 | 
				
			||||||
 | 
					    uint16_t radon_lt;
 | 
				
			||||||
 | 
					    uint16_t temperature;
 | 
				
			||||||
 | 
					    uint16_t pressure;
 | 
				
			||||||
 | 
					    uint16_t co2;
 | 
				
			||||||
 | 
					    uint16_t voc;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace airthings_wave_plus
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
							
								
								
									
										116
									
								
								esphome/components/airthings_wave_plus/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								esphome/components/airthings_wave_plus/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor, ble_client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    DEVICE_CLASS_CARBON_DIOXIDE,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_PRESSURE,
 | 
				
			||||||
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					    UNIT_PERCENT,
 | 
				
			||||||
 | 
					    UNIT_CELSIUS,
 | 
				
			||||||
 | 
					    UNIT_HECTOPASCAL,
 | 
				
			||||||
 | 
					    ICON_RADIOACTIVE,
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_RADON,
 | 
				
			||||||
 | 
					    CONF_RADON_LONG_TERM,
 | 
				
			||||||
 | 
					    CONF_HUMIDITY,
 | 
				
			||||||
 | 
					    CONF_TVOC,
 | 
				
			||||||
 | 
					    CONF_CO2,
 | 
				
			||||||
 | 
					    CONF_PRESSURE,
 | 
				
			||||||
 | 
					    CONF_TEMPERATURE,
 | 
				
			||||||
 | 
					    UNIT_BECQUEREL_PER_CUBIC_METER,
 | 
				
			||||||
 | 
					    UNIT_PARTS_PER_MILLION,
 | 
				
			||||||
 | 
					    UNIT_PARTS_PER_BILLION,
 | 
				
			||||||
 | 
					    ICON_RADIATOR,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEPENDENCIES = ["ble_client"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					airthings_wave_plus_ns = cg.esphome_ns.namespace("airthings_wave_plus")
 | 
				
			||||||
 | 
					AirthingsWavePlus = airthings_wave_plus_ns.class_(
 | 
				
			||||||
 | 
					    "AirthingsWavePlus", cg.PollingComponent, ble_client.BLEClientNode
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.All(
 | 
				
			||||||
 | 
					    cv.Schema(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cv.GenerateID(): cv.declare_id(AirthingsWavePlus),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_PERCENT,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					                accuracy_decimals=0,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_RADON): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
 | 
				
			||||||
 | 
					                icon=ICON_RADIOACTIVE,
 | 
				
			||||||
 | 
					                accuracy_decimals=0,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
 | 
				
			||||||
 | 
					                icon=ICON_RADIOACTIVE,
 | 
				
			||||||
 | 
					                accuracy_decimals=0,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_CELSIUS,
 | 
				
			||||||
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_HECTOPASCAL,
 | 
				
			||||||
 | 
					                accuracy_decimals=1,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_PRESSURE,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_CO2): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_PARTS_PER_MILLION,
 | 
				
			||||||
 | 
					                accuracy_decimals=0,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_CARBON_DIOXIDE,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_TVOC): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_PARTS_PER_BILLION,
 | 
				
			||||||
 | 
					                icon=ICON_RADIATOR,
 | 
				
			||||||
 | 
					                accuracy_decimals=0,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .extend(cv.polling_component_schema("5min"))
 | 
				
			||||||
 | 
					    .extend(ble_client.BLE_CLIENT_SCHEMA),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await ble_client.register_ble_node(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_HUMIDITY in config:
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(config[CONF_HUMIDITY])
 | 
				
			||||||
 | 
					        cg.add(var.set_humidity(sens))
 | 
				
			||||||
 | 
					    if CONF_RADON in config:
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(config[CONF_RADON])
 | 
				
			||||||
 | 
					        cg.add(var.set_radon(sens))
 | 
				
			||||||
 | 
					    if CONF_RADON_LONG_TERM in config:
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM])
 | 
				
			||||||
 | 
					        cg.add(var.set_radon_long_term(sens))
 | 
				
			||||||
 | 
					    if CONF_TEMPERATURE in config:
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
 | 
				
			||||||
 | 
					        cg.add(var.set_temperature(sens))
 | 
				
			||||||
 | 
					    if CONF_PRESSURE in config:
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(config[CONF_PRESSURE])
 | 
				
			||||||
 | 
					        cg.add(var.set_pressure(sens))
 | 
				
			||||||
 | 
					    if CONF_CO2 in config:
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(config[CONF_CO2])
 | 
				
			||||||
 | 
					        cg.add(var.set_co2(sens))
 | 
				
			||||||
 | 
					    if CONF_TVOC in config:
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(config[CONF_TVOC])
 | 
				
			||||||
 | 
					        cg.add(var.set_tvoc(sens))
 | 
				
			||||||
@@ -5,11 +5,12 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "am2320.h"
 | 
					#include "am2320.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace am2320 {
 | 
					namespace am2320 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const char *TAG = "am2320";
 | 
					static const char *const TAG = "am2320";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ---=== Calc CRC16 ===---
 | 
					// ---=== Calc CRC16 ===---
 | 
				
			||||||
uint16_t crc_16(uint8_t *ptr, uint8_t length) {
 | 
					uint16_t crc_16(uint8_t *ptr, uint8_t length) {
 | 
				
			||||||
@@ -18,12 +19,14 @@ uint16_t crc_16(uint8_t *ptr, uint8_t length) {
 | 
				
			|||||||
  //------------------------------
 | 
					  //------------------------------
 | 
				
			||||||
  while (length--) {
 | 
					  while (length--) {
 | 
				
			||||||
    crc ^= *ptr++;
 | 
					    crc ^= *ptr++;
 | 
				
			||||||
    for (i = 0; i < 8; i++)
 | 
					    for (i = 0; i < 8; i++) {
 | 
				
			||||||
      if ((crc & 0x01) != 0) {
 | 
					      if ((crc & 0x01) != 0) {
 | 
				
			||||||
        crc >>= 1;
 | 
					        crc >>= 1;
 | 
				
			||||||
        crc ^= 0xA001;
 | 
					        crc ^= 0xA001;
 | 
				
			||||||
      } else
 | 
					      } else {
 | 
				
			||||||
        crc >>= 1;
 | 
					        crc >>= 1;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return crc;
 | 
					  return crc;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -37,9 +40,9 @@ void AM2320Component::update() {
 | 
				
			|||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0;
 | 
					  float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0f;
 | 
				
			||||||
  temperature = (data[4] & 0x80) ? -temperature : temperature;
 | 
					  temperature = (data[4] & 0x80) ? -temperature : temperature;
 | 
				
			||||||
  float humidity = ((data[2] << 8) + data[3]) / 10.0;
 | 
					  float humidity = ((data[2] << 8) + data[3]) / 10.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity);
 | 
					  ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity);
 | 
				
			||||||
  if (this->temperature_sensor_ != nullptr)
 | 
					  if (this->temperature_sensor_ != nullptr)
 | 
				
			||||||
@@ -77,7 +80,7 @@ bool AM2320Component::read_bytes_(uint8_t a_register, uint8_t *data, uint8_t len
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  if (conversion > 0)
 | 
					  if (conversion > 0)
 | 
				
			||||||
    delay(conversion);
 | 
					    delay(conversion);
 | 
				
			||||||
  return this->parent_->raw_receive(this->address_, data, len);
 | 
					  return this->read(data, len) == i2c::ERROR_OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool AM2320Component::read_data_(uint8_t *data) {
 | 
					bool AM2320Component::read_data_(uint8_t *data) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,6 @@ from esphome.const import (
 | 
				
			|||||||
    DEVICE_CLASS_TEMPERATURE,
 | 
					    DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
    STATE_CLASS_MEASUREMENT,
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
    UNIT_CELSIUS,
 | 
					    UNIT_CELSIUS,
 | 
				
			||||||
    ICON_EMPTY,
 | 
					 | 
				
			||||||
    UNIT_PERCENT,
 | 
					    UNIT_PERCENT,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -25,18 +24,16 @@ CONFIG_SCHEMA = (
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            cv.GenerateID(): cv.declare_id(AM2320Component),
 | 
					            cv.GenerateID(): cv.declare_id(AM2320Component),
 | 
				
			||||||
            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
					            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
				
			||||||
                UNIT_CELSIUS,
 | 
					                unit_of_measurement=UNIT_CELSIUS,
 | 
				
			||||||
                ICON_EMPTY,
 | 
					                accuracy_decimals=1,
 | 
				
			||||||
                1,
 | 
					                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
                DEVICE_CLASS_TEMPERATURE,
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
                STATE_CLASS_MEASUREMENT,
 | 
					 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
					            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
				
			||||||
                UNIT_PERCENT,
 | 
					                unit_of_measurement=UNIT_PERCENT,
 | 
				
			||||||
                ICON_EMPTY,
 | 
					                accuracy_decimals=1,
 | 
				
			||||||
                1,
 | 
					                device_class=DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
                DEVICE_CLASS_HUMIDITY,
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
                STATE_CLASS_MEASUREMENT,
 | 
					 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										117
									
								
								esphome/components/am43/am43.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								esphome/components/am43/am43.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					#include "am43.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace am43 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "am43";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Am43::dump_config() {
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "AM43");
 | 
				
			||||||
 | 
					  LOG_SENSOR(" ", "Battery", this->battery_);
 | 
				
			||||||
 | 
					  LOG_SENSOR(" ", "Illuminance", this->illuminance_);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Am43::setup() {
 | 
				
			||||||
 | 
					  this->encoder_ = make_unique<Am43Encoder>();
 | 
				
			||||||
 | 
					  this->decoder_ = make_unique<Am43Decoder>();
 | 
				
			||||||
 | 
					  this->logged_in_ = false;
 | 
				
			||||||
 | 
					  this->last_battery_update_ = 0;
 | 
				
			||||||
 | 
					  this->current_sensor_ = 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
 | 
				
			||||||
 | 
					  switch (event) {
 | 
				
			||||||
 | 
					    case ESP_GATTC_OPEN_EVT: {
 | 
				
			||||||
 | 
					      this->logged_in_ = false;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_DISCONNECT_EVT: {
 | 
				
			||||||
 | 
					      this->logged_in_ = false;
 | 
				
			||||||
 | 
					      this->node_state = espbt::ClientState::IDLE;
 | 
				
			||||||
 | 
					      if (this->battery_ != nullptr)
 | 
				
			||||||
 | 
					        this->battery_->publish_state(NAN);
 | 
				
			||||||
 | 
					      if (this->illuminance_ != nullptr)
 | 
				
			||||||
 | 
					        this->illuminance_->publish_state(NAN);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
				
			||||||
 | 
					      auto *chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
 | 
				
			||||||
 | 
					      if (chr == nullptr) {
 | 
				
			||||||
 | 
					        if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
 | 
				
			||||||
 | 
					          ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.",
 | 
				
			||||||
 | 
					                   this->parent_->address_str().c_str());
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?",
 | 
				
			||||||
 | 
					                   this->parent_->address_str().c_str());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this->char_handle_ = chr->handle;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
				
			||||||
 | 
					      this->node_state = espbt::ClientState::ESTABLISHED;
 | 
				
			||||||
 | 
					      this->update();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_NOTIFY_EVT: {
 | 
				
			||||||
 | 
					      if (param->notify.handle != this->char_handle_)
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      this->decoder_->decode(param->notify.value, param->notify.value_len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this->battery_ != nullptr && this->decoder_->has_battery_level() &&
 | 
				
			||||||
 | 
					          millis() - this->last_battery_update_ > 10000) {
 | 
				
			||||||
 | 
					        this->battery_->publish_state(this->decoder_->battery_level_);
 | 
				
			||||||
 | 
					        this->last_battery_update_ = millis();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this->illuminance_ != nullptr && this->decoder_->has_light_level()) {
 | 
				
			||||||
 | 
					        this->illuminance_->publish_state(this->decoder_->light_level_);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this->current_sensor_ > 0) {
 | 
				
			||||||
 | 
					        if (this->illuminance_ != nullptr) {
 | 
				
			||||||
 | 
					          auto *packet = this->encoder_->get_light_level_request();
 | 
				
			||||||
 | 
					          auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
 | 
				
			||||||
 | 
					                                                 packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
 | 
				
			||||||
 | 
					                                                 ESP_GATT_AUTH_REQ_NONE);
 | 
				
			||||||
 | 
					          if (status) {
 | 
				
			||||||
 | 
					            ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
 | 
				
			||||||
 | 
					                     status);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this->current_sensor_ = 0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Am43::update() {
 | 
				
			||||||
 | 
					  if (this->node_state != espbt::ClientState::ESTABLISHED) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str());
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (this->current_sensor_ == 0) {
 | 
				
			||||||
 | 
					    if (this->battery_ != nullptr) {
 | 
				
			||||||
 | 
					      auto *packet = this->encoder_->get_battery_level_request();
 | 
				
			||||||
 | 
					      auto status =
 | 
				
			||||||
 | 
					          esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
 | 
				
			||||||
 | 
					                                   packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
				
			||||||
 | 
					      if (status)
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this->current_sensor_++;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace am43
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										45
									
								
								esphome/components/am43/am43.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								esphome/components/am43/am43.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/components/ble_client/ble_client.h"
 | 
				
			||||||
 | 
					#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
				
			||||||
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
 | 
					#include "esphome/components/am43/am43_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <esp_gattc_api.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace am43 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace espbt = esphome::esp32_ble_tracker;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void setup() override;
 | 
				
			||||||
 | 
					  void update() override;
 | 
				
			||||||
 | 
					  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
				
			||||||
 | 
					                           esp_ble_gattc_cb_param_t *param) override;
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
				
			||||||
 | 
					  void set_battery(sensor::Sensor *battery) { battery_ = battery; }
 | 
				
			||||||
 | 
					  void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  uint16_t char_handle_;
 | 
				
			||||||
 | 
					  std::unique_ptr<Am43Encoder> encoder_;
 | 
				
			||||||
 | 
					  std::unique_ptr<Am43Decoder> decoder_;
 | 
				
			||||||
 | 
					  bool logged_in_;
 | 
				
			||||||
 | 
					  sensor::Sensor *battery_{nullptr};
 | 
				
			||||||
 | 
					  sensor::Sensor *illuminance_{nullptr};
 | 
				
			||||||
 | 
					  uint8_t current_sensor_;
 | 
				
			||||||
 | 
					  // The AM43 often gets into a state where it spams loads of battery update
 | 
				
			||||||
 | 
					  // notifications. Here we will limit to no more than every 10s.
 | 
				
			||||||
 | 
					  uint8_t last_battery_update_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace am43
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										144
									
								
								esphome/components/am43/am43_base.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								esphome/components/am43/am43_base.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
				
			|||||||
 | 
					#include "am43_base.h"
 | 
				
			||||||
 | 
					#include <cstring>
 | 
				
			||||||
 | 
					#include <cstdio>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace am43 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const uint8_t START_PACKET[5] = {0x00, 0xff, 0x00, 0x00, 0x9a};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string pkt_to_hex(const uint8_t *data, uint16_t len) {
 | 
				
			||||||
 | 
					  char buf[64];
 | 
				
			||||||
 | 
					  memset(buf, 0, 64);
 | 
				
			||||||
 | 
					  for (int i = 0; i < len; i++)
 | 
				
			||||||
 | 
					    sprintf(&buf[i * 2], "%02x", data[i]);
 | 
				
			||||||
 | 
					  std::string ret = buf;
 | 
				
			||||||
 | 
					  return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Am43Packet *Am43Encoder::get_battery_level_request() {
 | 
				
			||||||
 | 
					  uint8_t data = 0x1;
 | 
				
			||||||
 | 
					  return this->encode_(0xA2, &data, 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Am43Packet *Am43Encoder::get_light_level_request() {
 | 
				
			||||||
 | 
					  uint8_t data = 0x1;
 | 
				
			||||||
 | 
					  return this->encode_(0xAA, &data, 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Am43Packet *Am43Encoder::get_position_request() {
 | 
				
			||||||
 | 
					  uint8_t data = 0x1;
 | 
				
			||||||
 | 
					  return this->encode_(CMD_GET_POSITION, &data, 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Am43Packet *Am43Encoder::get_send_pin_request(uint16_t pin) {
 | 
				
			||||||
 | 
					  uint8_t data[2];
 | 
				
			||||||
 | 
					  data[0] = (pin & 0xFF00) >> 8;
 | 
				
			||||||
 | 
					  data[1] = pin & 0xFF;
 | 
				
			||||||
 | 
					  return this->encode_(CMD_SEND_PIN, data, 2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Am43Packet *Am43Encoder::get_open_request() {
 | 
				
			||||||
 | 
					  uint8_t data = 0xDD;
 | 
				
			||||||
 | 
					  return this->encode_(CMD_SET_STATE, &data, 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Am43Packet *Am43Encoder::get_close_request() {
 | 
				
			||||||
 | 
					  uint8_t data = 0xEE;
 | 
				
			||||||
 | 
					  return this->encode_(CMD_SET_STATE, &data, 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Am43Packet *Am43Encoder::get_stop_request() {
 | 
				
			||||||
 | 
					  uint8_t data = 0xCC;
 | 
				
			||||||
 | 
					  return this->encode_(CMD_SET_STATE, &data, 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Am43Packet *Am43Encoder::get_set_position_request(uint8_t position) {
 | 
				
			||||||
 | 
					  return this->encode_(CMD_SET_POSITION, &position, 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Am43Encoder::checksum_() {
 | 
				
			||||||
 | 
					  uint8_t checksum = 0;
 | 
				
			||||||
 | 
					  int i = 0;
 | 
				
			||||||
 | 
					  for (i = 0; i < this->packet_.length; i++)
 | 
				
			||||||
 | 
					    checksum = checksum ^ this->packet_.data[i];
 | 
				
			||||||
 | 
					  this->packet_.data[i] = checksum ^ 0xff;
 | 
				
			||||||
 | 
					  this->packet_.length++;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Am43Packet *Am43Encoder::encode_(uint8_t command, uint8_t *data, uint8_t length) {
 | 
				
			||||||
 | 
					  memcpy(this->packet_.data, START_PACKET, 5);
 | 
				
			||||||
 | 
					  this->packet_.data[5] = command;
 | 
				
			||||||
 | 
					  this->packet_.data[6] = length;
 | 
				
			||||||
 | 
					  memcpy(&this->packet_.data[7], data, length);
 | 
				
			||||||
 | 
					  this->packet_.length = length + 7;
 | 
				
			||||||
 | 
					  this->checksum_();
 | 
				
			||||||
 | 
					  ESP_LOGV("am43", "ENC(%d): 0x%s", packet_.length, pkt_to_hex(packet_.data, packet_.length).c_str());
 | 
				
			||||||
 | 
					  return &this->packet_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define VERIFY_MIN_LENGTH(x) \
 | 
				
			||||||
 | 
					  if (length < (x)) \
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Am43Decoder::decode(const uint8_t *data, uint16_t length) {
 | 
				
			||||||
 | 
					  this->has_battery_level_ = false;
 | 
				
			||||||
 | 
					  this->has_light_level_ = false;
 | 
				
			||||||
 | 
					  this->has_set_position_response_ = false;
 | 
				
			||||||
 | 
					  this->has_set_state_response_ = false;
 | 
				
			||||||
 | 
					  this->has_position_ = false;
 | 
				
			||||||
 | 
					  this->has_pin_response_ = false;
 | 
				
			||||||
 | 
					  ESP_LOGV("am43", "DEC(%d): 0x%s", length, pkt_to_hex(data, length).c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (length < 2 || data[0] != 0x9a)
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  switch (data[1]) {
 | 
				
			||||||
 | 
					    case CMD_GET_BATTERY_LEVEL: {
 | 
				
			||||||
 | 
					      VERIFY_MIN_LENGTH(8);
 | 
				
			||||||
 | 
					      this->battery_level_ = data[7];
 | 
				
			||||||
 | 
					      this->has_battery_level_ = true;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case CMD_GET_LIGHT_LEVEL: {
 | 
				
			||||||
 | 
					      VERIFY_MIN_LENGTH(5);
 | 
				
			||||||
 | 
					      this->light_level_ = 100 * ((float) data[4] / 9);
 | 
				
			||||||
 | 
					      this->has_light_level_ = true;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case CMD_GET_POSITION: {
 | 
				
			||||||
 | 
					      VERIFY_MIN_LENGTH(6);
 | 
				
			||||||
 | 
					      this->position_ = data[5];
 | 
				
			||||||
 | 
					      this->has_position_ = true;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case CMD_NOTIFY_POSITION: {
 | 
				
			||||||
 | 
					      VERIFY_MIN_LENGTH(5);
 | 
				
			||||||
 | 
					      this->position_ = data[4];
 | 
				
			||||||
 | 
					      this->has_position_ = true;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case CMD_SEND_PIN: {
 | 
				
			||||||
 | 
					      VERIFY_MIN_LENGTH(4);
 | 
				
			||||||
 | 
					      this->pin_ok_ = data[3] == RESPONSE_ACK;
 | 
				
			||||||
 | 
					      this->has_pin_response_ = true;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case CMD_SET_POSITION: {
 | 
				
			||||||
 | 
					      VERIFY_MIN_LENGTH(4);
 | 
				
			||||||
 | 
					      this->set_position_ok_ = data[3] == RESPONSE_ACK;
 | 
				
			||||||
 | 
					      this->has_set_position_response_ = true;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case CMD_SET_STATE: {
 | 
				
			||||||
 | 
					      VERIFY_MIN_LENGTH(4);
 | 
				
			||||||
 | 
					      this->set_state_ok_ = data[3] == RESPONSE_ACK;
 | 
				
			||||||
 | 
					      this->has_set_state_response_ = true;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace am43
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										78
									
								
								esphome/components/am43/am43_base.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								esphome/components/am43/am43_base.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace am43 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const uint16_t AM43_SERVICE_UUID = 0xFE50;
 | 
				
			||||||
 | 
					static const uint16_t AM43_CHARACTERISTIC_UUID = 0xFE51;
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Tuya identifiers, only to detect and warn users as they are incompatible.
 | 
				
			||||||
 | 
					static const uint16_t AM43_TUYA_SERVICE_UUID = 0x1910;
 | 
				
			||||||
 | 
					static const uint16_t AM43_TUYA_CHARACTERISTIC_UUID = 0x2b11;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Am43Packet {
 | 
				
			||||||
 | 
					  uint8_t length;
 | 
				
			||||||
 | 
					  uint8_t data[24];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const uint8_t CMD_GET_BATTERY_LEVEL = 0xA2;
 | 
				
			||||||
 | 
					static const uint8_t CMD_GET_LIGHT_LEVEL = 0xAA;
 | 
				
			||||||
 | 
					static const uint8_t CMD_GET_POSITION = 0xA7;
 | 
				
			||||||
 | 
					static const uint8_t CMD_SEND_PIN = 0x17;
 | 
				
			||||||
 | 
					static const uint8_t CMD_SET_STATE = 0x0A;
 | 
				
			||||||
 | 
					static const uint8_t CMD_SET_POSITION = 0x0D;
 | 
				
			||||||
 | 
					static const uint8_t CMD_NOTIFY_POSITION = 0xA1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const uint8_t RESPONSE_ACK = 0x5A;
 | 
				
			||||||
 | 
					static const uint8_t RESPONSE_NACK = 0xA5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Am43Encoder {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  Am43Packet *get_battery_level_request();
 | 
				
			||||||
 | 
					  Am43Packet *get_light_level_request();
 | 
				
			||||||
 | 
					  Am43Packet *get_position_request();
 | 
				
			||||||
 | 
					  Am43Packet *get_send_pin_request(uint16_t pin);
 | 
				
			||||||
 | 
					  Am43Packet *get_open_request();
 | 
				
			||||||
 | 
					  Am43Packet *get_close_request();
 | 
				
			||||||
 | 
					  Am43Packet *get_stop_request();
 | 
				
			||||||
 | 
					  Am43Packet *get_set_position_request(uint8_t position);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  void checksum_();
 | 
				
			||||||
 | 
					  Am43Packet *encode_(uint8_t command, uint8_t *data, uint8_t length);
 | 
				
			||||||
 | 
					  Am43Packet packet_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Am43Decoder {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void decode(const uint8_t *data, uint16_t length);
 | 
				
			||||||
 | 
					  bool has_battery_level() { return this->has_battery_level_; }
 | 
				
			||||||
 | 
					  bool has_light_level() { return this->has_light_level_; }
 | 
				
			||||||
 | 
					  bool has_set_position_response() { return this->has_set_position_response_; }
 | 
				
			||||||
 | 
					  bool has_set_state_response() { return this->has_set_state_response_; }
 | 
				
			||||||
 | 
					  bool has_position() { return this->has_position_; }
 | 
				
			||||||
 | 
					  bool has_pin_response() { return this->has_pin_response_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  union {
 | 
				
			||||||
 | 
					    uint8_t position_;
 | 
				
			||||||
 | 
					    uint8_t battery_level_;
 | 
				
			||||||
 | 
					    float light_level_;
 | 
				
			||||||
 | 
					    uint8_t set_position_ok_;
 | 
				
			||||||
 | 
					    uint8_t set_state_ok_;
 | 
				
			||||||
 | 
					    uint8_t pin_ok_;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  bool has_battery_level_;
 | 
				
			||||||
 | 
					  bool has_light_level_;
 | 
				
			||||||
 | 
					  bool has_set_position_response_;
 | 
				
			||||||
 | 
					  bool has_set_state_response_;
 | 
				
			||||||
 | 
					  bool has_position_;
 | 
				
			||||||
 | 
					  bool has_pin_response_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace am43
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										36
									
								
								esphome/components/am43/cover/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								esphome/components/am43/cover/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import cover, ble_client
 | 
				
			||||||
 | 
					from esphome.const import CONF_ID, CONF_PIN
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CODEOWNERS = ["@buxtronix"]
 | 
				
			||||||
 | 
					DEPENDENCIES = ["ble_client"]
 | 
				
			||||||
 | 
					AUTO_LOAD = ["am43", "sensor"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_INVERT_POSITION = "invert_position"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					am43_ns = cg.esphome_ns.namespace("am43")
 | 
				
			||||||
 | 
					Am43Component = am43_ns.class_(
 | 
				
			||||||
 | 
					    "Am43Component", cover.Cover, ble_client.BLEClientNode, cg.Component
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = (
 | 
				
			||||||
 | 
					    cover.COVER_SCHEMA.extend(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cv.GenerateID(): cv.declare_id(Am43Component),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .extend(ble_client.BLE_CLIENT_SCHEMA)
 | 
				
			||||||
 | 
					    .extend(cv.COMPONENT_SCHEMA)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					    cg.add(var.set_pin(config[CONF_PIN]))
 | 
				
			||||||
 | 
					    cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
 | 
				
			||||||
 | 
					    yield cg.register_component(var, config)
 | 
				
			||||||
 | 
					    yield cover.register_cover(var, config)
 | 
				
			||||||
 | 
					    yield ble_client.register_ble_node(var, config)
 | 
				
			||||||
							
								
								
									
										150
									
								
								esphome/components/am43/cover/am43_cover.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								esphome/components/am43/cover/am43_cover.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,150 @@
 | 
				
			|||||||
 | 
					#include "am43_cover.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace am43 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "am43_cover";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace esphome::cover;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Am43Component::dump_config() {
 | 
				
			||||||
 | 
					  LOG_COVER("", "AM43 Cover", this);
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Device Pin: %d", this->pin_);
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Invert Position: %d", (int) this->invert_position_);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Am43Component::setup() {
 | 
				
			||||||
 | 
					  this->position = COVER_OPEN;
 | 
				
			||||||
 | 
					  this->encoder_ = make_unique<Am43Encoder>();
 | 
				
			||||||
 | 
					  this->decoder_ = make_unique<Am43Decoder>();
 | 
				
			||||||
 | 
					  this->logged_in_ = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Am43Component::loop() {
 | 
				
			||||||
 | 
					  if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) {
 | 
				
			||||||
 | 
					    auto *packet = this->encoder_->get_send_pin_request(this->pin_);
 | 
				
			||||||
 | 
					    auto status =
 | 
				
			||||||
 | 
					        esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
 | 
				
			||||||
 | 
					                                 packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
				
			||||||
 | 
					    ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str());
 | 
				
			||||||
 | 
					    if (status) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this->logged_in_ = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CoverTraits Am43Component::get_traits() {
 | 
				
			||||||
 | 
					  auto traits = CoverTraits();
 | 
				
			||||||
 | 
					  traits.set_supports_position(true);
 | 
				
			||||||
 | 
					  traits.set_supports_tilt(false);
 | 
				
			||||||
 | 
					  traits.set_is_assumed_state(false);
 | 
				
			||||||
 | 
					  return traits;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Am43Component::control(const CoverCall &call) {
 | 
				
			||||||
 | 
					  if (this->node_state != espbt::ClientState::ESTABLISHED) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "[%s] Cannot send cover control, not connected", this->get_name().c_str());
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (call.get_stop()) {
 | 
				
			||||||
 | 
					    auto *packet = this->encoder_->get_stop_request();
 | 
				
			||||||
 | 
					    auto status =
 | 
				
			||||||
 | 
					        esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
 | 
				
			||||||
 | 
					                                 packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
				
			||||||
 | 
					    if (status)
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (call.get_position().has_value()) {
 | 
				
			||||||
 | 
					    auto pos = *call.get_position();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this->invert_position_)
 | 
				
			||||||
 | 
					      pos = 1 - pos;
 | 
				
			||||||
 | 
					    auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
 | 
				
			||||||
 | 
					    auto status =
 | 
				
			||||||
 | 
					        esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
 | 
				
			||||||
 | 
					                                 packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
				
			||||||
 | 
					    if (status)
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
				
			||||||
 | 
					                                        esp_ble_gattc_cb_param_t *param) {
 | 
				
			||||||
 | 
					  switch (event) {
 | 
				
			||||||
 | 
					    case ESP_GATTC_DISCONNECT_EVT: {
 | 
				
			||||||
 | 
					      this->logged_in_ = false;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
				
			||||||
 | 
					      auto *chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID);
 | 
				
			||||||
 | 
					      if (chr == nullptr) {
 | 
				
			||||||
 | 
					        if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) {
 | 
				
			||||||
 | 
					          ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", this->get_name().c_str());
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          ESP_LOGE(TAG, "[%s] No control service found at device, not an AM43..?", this->get_name().c_str());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this->char_handle_ = chr->handle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle);
 | 
				
			||||||
 | 
					      if (status) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
				
			||||||
 | 
					      this->node_state = espbt::ClientState::ESTABLISHED;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_NOTIFY_EVT: {
 | 
				
			||||||
 | 
					      if (param->notify.handle != this->char_handle_)
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      this->decoder_->decode(param->notify.value, param->notify.value_len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this->decoder_->has_position()) {
 | 
				
			||||||
 | 
					        this->position = ((float) this->decoder_->position_ / 100.0);
 | 
				
			||||||
 | 
					        if (!this->invert_position_)
 | 
				
			||||||
 | 
					          this->position = 1 - this->position;
 | 
				
			||||||
 | 
					        if (this->position > 0.97)
 | 
				
			||||||
 | 
					          this->position = 1.0;
 | 
				
			||||||
 | 
					        if (this->position < 0.02)
 | 
				
			||||||
 | 
					          this->position = 0.0;
 | 
				
			||||||
 | 
					        this->publish_state();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this->decoder_->has_pin_response()) {
 | 
				
			||||||
 | 
					        if (this->decoder_->pin_ok_) {
 | 
				
			||||||
 | 
					          ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str());
 | 
				
			||||||
 | 
					          auto *packet = this->encoder_->get_position_request();
 | 
				
			||||||
 | 
					          auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
 | 
				
			||||||
 | 
					                                                 packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
 | 
				
			||||||
 | 
					                                                 ESP_GATT_AUTH_REQ_NONE);
 | 
				
			||||||
 | 
					          if (status)
 | 
				
			||||||
 | 
					            ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          ESP_LOGW(TAG, "[%s] AM43 pin rejected!", this->get_name().c_str());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this->decoder_->has_set_position_response() && !this->decoder_->set_position_ok_)
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "[%s] Got nack after sending set_position. Bad pin?", this->get_name().c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this->decoder_->has_set_state_response() && !this->decoder_->set_state_ok_)
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "[%s] Got nack after sending set_state. Bad pin?", this->get_name().c_str());
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace am43
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										45
									
								
								esphome/components/am43/cover/am43_cover.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								esphome/components/am43/cover/am43_cover.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/components/ble_client/ble_client.h"
 | 
				
			||||||
 | 
					#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
				
			||||||
 | 
					#include "esphome/components/cover/cover.h"
 | 
				
			||||||
 | 
					#include "esphome/components/am43/am43_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <esp_gattc_api.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace am43 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace espbt = esphome::esp32_ble_tracker;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Am43Component : public cover::Cover, public esphome::ble_client::BLEClientNode, public Component {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void setup() override;
 | 
				
			||||||
 | 
					  void loop() override;
 | 
				
			||||||
 | 
					  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
				
			||||||
 | 
					                           esp_ble_gattc_cb_param_t *param) override;
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
				
			||||||
 | 
					  cover::CoverTraits get_traits() override;
 | 
				
			||||||
 | 
					  void set_pin(uint16_t pin) { this->pin_ = pin; }
 | 
				
			||||||
 | 
					  void set_invert_position(bool invert_position) { this->invert_position_ = invert_position; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  void control(const cover::CoverCall &call) override;
 | 
				
			||||||
 | 
					  uint16_t char_handle_;
 | 
				
			||||||
 | 
					  uint16_t pin_;
 | 
				
			||||||
 | 
					  bool invert_position_;
 | 
				
			||||||
 | 
					  std::unique_ptr<Am43Encoder> encoder_;
 | 
				
			||||||
 | 
					  std::unique_ptr<Am43Decoder> decoder_;
 | 
				
			||||||
 | 
					  bool logged_in_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  float position_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace am43
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										52
									
								
								esphome/components/am43/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								esphome/components/am43/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor, ble_client
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_BATTERY_LEVEL,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_BATTERY,
 | 
				
			||||||
 | 
					    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
				
			||||||
 | 
					    CONF_ILLUMINANCE,
 | 
				
			||||||
 | 
					    ICON_BRIGHTNESS_5,
 | 
				
			||||||
 | 
					    UNIT_PERCENT,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CODEOWNERS = ["@buxtronix"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					am43_ns = cg.esphome_ns.namespace("am43")
 | 
				
			||||||
 | 
					Am43 = am43_ns.class_("Am43", ble_client.BLEClientNode, cg.PollingComponent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = (
 | 
				
			||||||
 | 
					    cv.Schema(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cv.GenerateID(): cv.declare_id(Am43),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_PERCENT,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_BATTERY,
 | 
				
			||||||
 | 
					                accuracy_decimals=0,
 | 
				
			||||||
 | 
					                entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_PERCENT,
 | 
				
			||||||
 | 
					                icon=ICON_BRIGHTNESS_5,
 | 
				
			||||||
 | 
					                accuracy_decimals=0,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .extend(ble_client.BLE_CLIENT_SCHEMA)
 | 
				
			||||||
 | 
					    .extend(cv.polling_component_schema("120s"))
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					    yield cg.register_component(var, config)
 | 
				
			||||||
 | 
					    yield ble_client.register_ble_node(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_BATTERY_LEVEL in config:
 | 
				
			||||||
 | 
					        sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
 | 
				
			||||||
 | 
					        cg.add(var.set_battery(sens))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_ILLUMINANCE in config:
 | 
				
			||||||
 | 
					        sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE])
 | 
				
			||||||
 | 
					        cg.add(var.set_illuminance(sens))
 | 
				
			||||||
							
								
								
									
										1
									
								
								esphome/components/analog_threshold/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/analog_threshold/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					CODEOWNERS = ["@ianchi"]
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user