mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 09:01:49 +00:00 
			
		
		
		
	Compare commits
	
		
			484 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					b660e5a7fa | ||
| 
						 | 
					3b4ea0ed0a | ||
| 
						 | 
					403b6e32e3 | ||
| 
						 | 
					b91a1aa027 | ||
| 
						 | 
					13dbdd9b16 | ||
| 
						 | 
					37bc0b3b5a | ||
| 
						 | 
					be70a96651 | ||
| 
						 | 
					d83d214497 | ||
| 
						 | 
					6ec0f80b76 | ||
| 
						 | 
					06f566346d | ||
| 
						 | 
					b680649113 | ||
| 
						 | 
					5ab2ef4079 | ||
| 
						 | 
					392ed64375 | ||
| 
						 | 
					566c129435 | ||
| 
						 | 
					c903eb2d01 | ||
| 
						 | 
					69c78651d5 | ||
| 
						 | 
					34487c9de4 | ||
| 
						 | 
					822377be8b | ||
| 
						 | 
					0fe61d9ec7 | ||
| 
						 | 
					6114d331c9 | ||
| 
						 | 
					e2e074a3fd | ||
| 
						 | 
					4d340dc029 | ||
| 
						 | 
					fb6c5ebe9a | ||
| 
						 | 
					af3273d930 | ||
| 
						 | 
					8f1eb77e05 | ||
| 
						 | 
					89d0d41c5a | ||
| 
						 | 
					452ca8e4c6 | ||
| 
						 | 
					e51b0ca15e | ||
| 
						 | 
					5eeb110d74 | ||
| 
						 | 
					60b2d57dc3 | ||
| 
						 | 
					91898cb814 | ||
| 
						 | 
					818a7f1c78 | ||
| 
						 | 
					dedf343bd5 | ||
| 
						 | 
					251240cc90 | ||
| 
						 | 
					e5b45b6b4b | ||
| 
						 | 
					a77784a6da | ||
| 
						 | 
					f63f9168ff | ||
| 
						 | 
					b5b2036971 | ||
| 
						 | 
					a96b6e7002 | ||
| 
						 | 
					f34c9b33fc | ||
| 
						 | 
					faf577a9dd | ||
| 
						 | 
					7708b81ef5 | ||
| 
						 | 
					08998caabc | ||
| 
						 | 
					848a5f1680 | ||
| 
						 | 
					2e7c1d7345 | ||
| 
						 | 
					28a72fa56b | ||
| 
						 | 
					cd1353ae54 | ||
| 
						 | 
					c9ee513fa8 | ||
| 
						 | 
					2a12caa955 | ||
| 
						 | 
					5e5f8d5f9b | ||
| 
						 | 
					2c0fe49b86 | ||
| 
						 | 
					1e227e8051 | ||
| 
						 | 
					d5cf4b7eac | ||
| 
						 | 
					570ec36fe3 | ||
| 
						 | 
					69879920eb | ||
| 
						 | 
					2b60b0f1fa | ||
| 
						 | 
					c17624adab | ||
| 
						 | 
					0f151a8f6b | ||
| 
						 | 
					e62bf333a2 | ||
| 
						 | 
					88b46b7487 | ||
| 
						 | 
					fa290fbce8 | ||
| 
						 | 
					1883ce1876 | ||
| 
						 | 
					f3fe2bde38 | ||
| 
						 | 
					811c13d7d1 | ||
| 
						 | 
					521dfe08f2 | ||
| 
						 | 
					2c77d121da | ||
| 
						 | 
					c5dc736c53 | ||
| 
						 | 
					8e93735861 | ||
| 
						 | 
					ac25b138f5 | ||
| 
						 | 
					b17e0c298e | ||
| 
						 | 
					422f0ad7a9 | ||
| 
						 | 
					34d37961c3 | ||
| 
						 | 
					bdf004867d | ||
| 
						 | 
					08ecca86bc | ||
| 
						 | 
					520c4331e3 | ||
| 
						 | 
					342d5166a0 | ||
| 
						 | 
					69d39ef0cd | ||
| 
						 | 
					6588e9380e | ||
| 
						 | 
					4d6277330b | ||
| 
						 | 
					87154e9b6f | ||
| 
						 | 
					b91723344e | ||
| 
						 | 
					92b36720b6 | ||
| 
						 | 
					3d0310d0e0 | ||
| 
						 | 
					f81cddf22e | ||
| 
						 | 
					808ce6eecb | ||
| 
						 | 
					665d0fd759 | ||
| 
						 | 
					c519c02de8 | ||
| 
						 | 
					eacac78099 | ||
| 
						 | 
					a925036ff8 | ||
| 
						 | 
					1468293f3e | ||
| 
						 | 
					25924ca4e8 | ||
| 
						 | 
					6c8ace0ce8 | ||
| 
						 | 
					acc1af0f51 | ||
| 
						 | 
					c92c439d17 | ||
| 
						 | 
					410fad3b41 | ||
| 
						 | 
					dce20680d7 | ||
| 
						 | 
					f95be6a0df | ||
| 
						 | 
					a342302114 | ||
| 
						 | 
					925a992d1b | ||
| 
						 | 
					61ecbe4273 | ||
| 
						 | 
					65c7e27a43 | ||
| 
						 | 
					57b56010da | ||
| 
						 | 
					ef89249019 | ||
| 
						 | 
					bc64cf3e47 | ||
| 
						 | 
					def70dde72 | ||
| 
						 | 
					b52f7cfe86 | ||
| 
						 | 
					81b512a7b3 | ||
| 
						 | 
					57d6185374 | ||
| 
						 | 
					e288aa07fb | ||
| 
						 | 
					50006e4c42 | ||
| 
						 | 
					23cf120977 | ||
| 
						 | 
					04d8593f38 | ||
| 
						 | 
					28e39f7f76 | ||
| 
						 | 
					de3377132d | ||
| 
						 | 
					b351cd94d7 | ||
| 
						 | 
					d238e06f86 | ||
| 
						 | 
					2fc59ecc30 | ||
| 
						 | 
					0047d24698 | ||
| 
						 | 
					89a89e1785 | ||
| 
						 | 
					1952d275f7 | ||
| 
						 | 
					043095b605 | ||
| 
						 | 
					bccaa78a90 | ||
| 
						 | 
					f402c89539 | ||
| 
						 | 
					431d3578a5 | ||
| 
						 | 
					5f02d86841 | ||
| 
						 | 
					a7ec57d4be | ||
| 
						 | 
					4eeb111fa3 | ||
| 
						 | 
					1ea5cc497f | ||
| 
						 | 
					b601cf6bc6 | ||
| 
						 | 
					5057caa7fc | ||
| 
						 | 
					95bef53d37 | ||
| 
						 | 
					ea019a057b | ||
| 
						 | 
					1d378e416d | ||
| 
						 | 
					d3b758d10a | ||
| 
						 | 
					7b9c2d2978 | ||
| 
						 | 
					9d38543cb0 | ||
| 
						 | 
					b860a317b9 | ||
| 
						 | 
					9591c903f7 | ||
| 
						 | 
					65fbb8bc60 | ||
| 
						 | 
					97428f2ba2 | ||
| 
						 | 
					9ab6a7b7ff | ||
| 
						 | 
					e2ad6fe3d8 | ||
| 
						 | 
					6781d08c9b | ||
| 
						 | 
					36a2ce2c23 | ||
| 
						 | 
					c7c71089ce | ||
| 
						 | 
					52c67d715b | ||
| 
						 | 
					f084ab339b | ||
| 
						 | 
					8352f52fef | ||
| 
						 | 
					b28735d63b | ||
| 
						 | 
					4c105398f7 | ||
| 
						 | 
					828f7946ea | ||
| 
						 | 
					23c663d5d4 | ||
| 
						 | 
					6a99789c92 | ||
| 
						 | 
					52e13164b4 | ||
| 
						 | 
					28f2582256 | ||
| 
						 | 
					652f6058d1 | ||
| 
						 | 
					717aab7c8b | ||
| 
						 | 
					5e799b5284 | ||
| 
						 | 
					9f36b25d4e | ||
| 
						 | 
					d9a2651a5a | ||
| 
						 | 
					5fcd1e391d | ||
| 
						 | 
					36089a1400 | ||
| 
						 | 
					e7b1d2efaa | ||
| 
						 | 
					bf453ad8cd | ||
| 
						 | 
					fbc1b3e316 | ||
| 
						 | 
					400819175d | ||
| 
						 | 
					3c34b539b0 | ||
| 
						 | 
					c8058e9636 | ||
| 
						 | 
					f2474c5c45 | ||
| 
						 | 
					7acc36d39d | ||
| 
						 | 
					b01db991a5 | ||
| 
						 | 
					86385a1c19 | ||
| 
						 | 
					96ab6b51b8 | ||
| 
						 | 
					8c849b9002 | ||
| 
						 | 
					6a0d4cb5a9 | ||
| 
						 | 
					72002ce70e | ||
| 
						 | 
					c67539cf5b | ||
| 
						 | 
					dcd3d2084d | ||
| 
						 | 
					585bb6dac8 | ||
| 
						 | 
					02dc49c272 | ||
| 
						 | 
					5df398ec31 | ||
| 
						 | 
					699696e8d1 | ||
| 
						 | 
					93e35a53ed | ||
| 
						 | 
					a269098a0e | ||
| 
						 | 
					cac3055261 | ||
| 
						 | 
					6f7e6cc944 | ||
| 
						 | 
					0a841fcc50 | ||
| 
						 | 
					3c64c9b0e9 | ||
| 
						 | 
					34df25da39 | ||
| 
						 | 
					9586fb95d1 | ||
| 
						 | 
					3ee6348e41 | ||
| 
						 | 
					543f2c8152 | ||
| 
						 | 
					16d11be213 | ||
| 
						 | 
					e49b568fd4 | ||
| 
						 | 
					22ab830ff3 | ||
| 
						 | 
					095d3181cd | ||
| 
						 | 
					9aa14a2e83 | ||
| 
						 | 
					ac15ce576b | ||
| 
						 | 
					498b59e998 | ||
| 
						 | 
					63c420254a | ||
| 
						 | 
					765e641d08 | ||
| 
						 | 
					be16d10b7d | ||
| 
						 | 
					4b808611e9 | ||
| 
						 | 
					7afe202e20 | ||
| 
						 | 
					039810eef3 | ||
| 
						 | 
					ff43b45113 | ||
| 
						 | 
					7cd4c3bdd3 | ||
| 
						 | 
					c12c9e97c2 | ||
| 
						 | 
					b3169deda7 | ||
| 
						 | 
					0ea41e2f71 | ||
| 
						 | 
					3afb564a48 | ||
| 
						 | 
					7ff3f752e2 | ||
| 
						 | 
					d821ead92a | ||
| 
						 | 
					e42ce64127 | ||
| 
						 | 
					9d2b0b4e03 | ||
| 
						 | 
					b5e6ae0d69 | ||
| 
						 | 
					08f1eac8b2 | ||
| 
						 | 
					6ed3da33a2 | ||
| 
						 | 
					a9a00f139b | ||
| 
						 | 
					63d8071dbd | ||
| 
						 | 
					d20caa9d60 | ||
| 
						 | 
					7e40d4246c | ||
| 
						 | 
					5a2b14cfa4 | ||
| 
						 | 
					f2d218e5ad | ||
| 
						 | 
					b493d5bba5 | ||
| 
						 | 
					c9055f2aef | ||
| 
						 | 
					9fed7cab5f | ||
| 
						 | 
					eb5c4b7c4f | ||
| 
						 | 
					2ab3534a4b | ||
| 
						 | 
					9816e677a6 | ||
| 
						 | 
					fc01a70b65 | ||
| 
						 | 
					7221337442 | ||
| 
						 | 
					051a1e4772 | ||
| 
						 | 
					274741a9d5 | ||
| 
						 | 
					25c01adf51 | ||
| 
						 | 
					bf2d54c3ef | ||
| 
						 | 
					49cb8fd9d3 | ||
| 
						 | 
					e536316e3d | ||
| 
						 | 
					a6c46eb8e5 | ||
| 
						 | 
					1a270374e0 | ||
| 
						 | 
					e73eafbd88 | ||
| 
						 | 
					9fc3e05b76 | ||
| 
						 | 
					31c604331c | ||
| 
						 | 
					3fcdaaefe0 | ||
| 
						 | 
					20dd744680 | ||
| 
						 | 
					e4636b99f7 | ||
| 
						 | 
					10e7abb579 | ||
| 
						 | 
					d3f03b7acb | ||
| 
						 | 
					7e53fc9d6a | ||
| 
						 | 
					0059a6de46 | ||
| 
						 | 
					22e1758d5b | ||
| 
						 | 
					59cdc32970 | ||
| 
						 | 
					adb51cf733 | ||
| 
						 | 
					d97a9bf8e8 | ||
| 
						 | 
					f034472e2a | ||
| 
						 | 
					ed328d2df8 | ||
| 
						 | 
					1520dc8755 | ||
| 
						 | 
					bf601c3126 | ||
| 
						 | 
					ab48e4a466 | ||
| 
						 | 
					8ef0f5b047 | ||
| 
						 | 
					2c14d134be | ||
| 
						 | 
					9cd21bb5a0 | ||
| 
						 | 
					bd061ac2ee | ||
| 
						 | 
					dd3e821857 | ||
| 
						 | 
					b38b7019ea | ||
| 
						 | 
					2c71ee7853 | ||
| 
						 | 
					540c62061d | ||
| 
						 | 
					221ef07c8b | ||
| 
						 | 
					29fc7ea154 | ||
| 
						 | 
					7b157aeff1 | ||
| 
						 | 
					e50644edee | ||
| 
						 | 
					5f619e6f01 | ||
| 
						 | 
					b266fb37a3 | ||
| 
						 | 
					c9caf44c2e | ||
| 
						 | 
					0c87a9ad2c | ||
| 
						 | 
					c680b437f5 | ||
| 
						 | 
					4988349677 | ||
| 
						 | 
					e35d56defe | ||
| 
						 | 
					5c86f332b2 | ||
| 
						 | 
					002861f13b | ||
| 
						 | 
					dbec3d7c50 | ||
| 
						 | 
					89cde158d6 | ||
| 
						 | 
					d7b76aadb2 | ||
| 
						 | 
					e09fefd389 | ||
| 
						 | 
					febc485da6 | ||
| 
						 | 
					2ae709c2ba | ||
| 
						 | 
					0ccfdd4711 | ||
| 
						 | 
					0e59243b83 | ||
| 
						 | 
					01bbd04a5a | ||
| 
						 | 
					a3b2d384f5 | ||
| 
						 | 
					50238f8d72 | ||
| 
						 | 
					704470d606 | ||
| 
						 | 
					f7e6195466 | ||
| 
						 | 
					a0bb7c3ed0 | ||
| 
						 | 
					e3a6c9a6cf | ||
| 
						 | 
					99598d87a9 | ||
| 
						 | 
					b5df50893b | ||
| 
						 | 
					f46b3d15cd | ||
| 
						 | 
					041b4ec66e | ||
| 
						 | 
					703e9673c2 | ||
| 
						 | 
					e7bd93b4b0 | ||
| 
						 | 
					a401c71d3e | ||
| 
						 | 
					5bae233334 | ||
| 
						 | 
					c4edd3047f | ||
| 
						 | 
					c50da1593a | ||
| 
						 | 
					1d06426281 | ||
| 
						 | 
					9c5b693dd5 | ||
| 
						 | 
					5fecc70db1 | ||
| 
						 | 
					ff24023b39 | ||
| 
						 | 
					1a04e2d1b8 | ||
| 
						 | 
					52c4dd0e35 | ||
| 
						 | 
					ff90f6a440 | ||
| 
						 | 
					e24d5c172f | ||
| 
						 | 
					0918f452a0 | ||
| 
						 | 
					839fe49e61 | ||
| 
						 | 
					ff050d634a | ||
| 
						 | 
					228670df78 | ||
| 
						 | 
					cfe4638665 | ||
| 
						 | 
					b7352b1345 | ||
| 
						 | 
					32ae8fc2d0 | ||
| 
						 | 
					86df4c1d8d | ||
| 
						 | 
					dc4a88029c | ||
| 
						 | 
					5da9b2ede7 | ||
| 
						 | 
					29cfcfaf0f | ||
| 
						 | 
					61bfd347ea | ||
| 
						 | 
					b7436c0b22 | ||
| 
						 | 
					8a45dfac5c | ||
| 
						 | 
					69f5d8cd0f | ||
| 
						 | 
					9a57e8fcb0 | ||
| 
						 | 
					a9d75ca4f4 | ||
| 
						 | 
					ccb6fc3010 | ||
| 
						 | 
					4e9a05fe11 | ||
| 
						 | 
					8a294e4134 | ||
| 
						 | 
					aad9a539c1 | ||
| 
						 | 
					fd6ac529fb | ||
| 
						 | 
					009cea1abf | ||
| 
						 | 
					4c3c14ec32 | ||
| 
						 | 
					636c9db1e3 | ||
| 
						 | 
					71f625bbd3 | ||
| 
						 | 
					aea2e9a6bb | ||
| 
						 | 
					3f6f3c14c4 | ||
| 
						 | 
					b1d77b7c03 | ||
| 
						 | 
					cb0ba647ed | ||
| 
						 | 
					f9fceb7ffc | ||
| 
						 | 
					2697c9465b | ||
| 
						 | 
					8d204655be | ||
| 
						 | 
					8414a22356 | ||
| 
						 | 
					36e4a8b444 | ||
| 
						 | 
					949c71dc97 | ||
| 
						 | 
					a6f6b8da7f | ||
| 
						 | 
					a9f123e864 | ||
| 
						 | 
					a64a505817 | ||
| 
						 | 
					fe6621357e | ||
| 
						 | 
					4a0067a2c5 | ||
| 
						 | 
					b270ff335d | ||
| 
						 | 
					7d2fcf59fd | ||
| 
						 | 
					d26c43103d | ||
| 
						 | 
					389889ad70 | ||
| 
						 | 
					8aa73bba10 | ||
| 
						 | 
					52639a0a7c | ||
| 
						 | 
					a1e10f384e | ||
| 
						 | 
					27d4b3b8ad | ||
| 
						 | 
					b9d55fd1ed | ||
| 
						 | 
					4c55b9c58c | ||
| 
						 | 
					51ab0f0b78 | ||
| 
						 | 
					bf0cce4ad8 | ||
| 
						 | 
					60e6366521 | ||
| 
						 | 
					072b2c445c | ||
| 
						 | 
					219fe41831 | ||
| 
						 | 
					dcc8bb83af | ||
| 
						 | 
					a8e3521f3c | ||
| 
						 | 
					706dc6d116 | ||
| 
						 | 
					84accb6df6 | ||
| 
						 | 
					8421570b18 | ||
| 
						 | 
					d355543ac9 | ||
| 
						 | 
					3a597c5aa6 | ||
| 
						 | 
					c7dddaded4 | ||
| 
						 | 
					aae4b2ea5d | ||
| 
						 | 
					310e2a0b20 | ||
| 
						 | 
					0b04d143ac | ||
| 
						 | 
					53c231a7eb | ||
| 
						 | 
					d44ce82aa1 | ||
| 
						 | 
					a055de48e4 | ||
| 
						 | 
					37b8d665fe | ||
| 
						 | 
					dd7c8dabb1 | ||
| 
						 | 
					e41a9875e3 | ||
| 
						 | 
					c5c42c4338 | ||
| 
						 | 
					531428b8b0 | ||
| 
						 | 
					ea8068e001 | ||
| 
						 | 
					7842a55c81 | ||
| 
						 | 
					51d39862b1 | ||
| 
						 | 
					bfea6ca79b | ||
| 
						 | 
					6297395018 | ||
| 
						 | 
					a5b49dbfa6 | ||
| 
						 | 
					7c0d777173 | ||
| 
						 | 
					74878276fc | ||
| 
						 | 
					226e3b1dad | ||
| 
						 | 
					7752794fc5 | ||
| 
						 | 
					b3094d6a53 | ||
| 
						 | 
					e3640e710f | ||
| 
						 | 
					2ef64b55c5 | ||
| 
						 | 
					7f6672bb37 | ||
| 
						 | 
					68a3b31628 | ||
| 
						 | 
					1b35855e68 | ||
| 
						 | 
					1e1837000d | ||
| 
						 | 
					e2d5257632 | ||
| 
						 | 
					387c75793b | ||
| 
						 | 
					4f3a74d08a | ||
| 
						 | 
					fdbc59a159 | ||
| 
						 | 
					0db37bb55c | ||
| 
						 | 
					2ff2750628 | ||
| 
						 | 
					eae5c17b87 | ||
| 
						 | 
					a59cde91ad | ||
| 
						 | 
					1f243ae37e | ||
| 
						 | 
					603f82977e | ||
| 
						 | 
					2d70422a6f | ||
| 
						 | 
					e2c8b21195 | ||
| 
						 | 
					7adaeacd0b | ||
| 
						 | 
					3aaa92fdff | ||
| 
						 | 
					5efd076c08 | ||
| 
						 | 
					1ca241615d | ||
| 
						 | 
					b8aa84002a | ||
| 
						 | 
					10cc0b1d5b | ||
| 
						 | 
					11d9c203c1 | ||
| 
						 | 
					c9ab454c3c | ||
| 
						 | 
					4a55692885 | ||
| 
						 | 
					88c129e705 | ||
| 
						 | 
					80b48f01fb | ||
| 
						 | 
					642bc91a76 | ||
| 
						 | 
					d69926ee56 | ||
| 
						 | 
					4758403d44 | ||
| 
						 | 
					4b0ec5c28a | ||
| 
						 | 
					4b2a9e5e49 | ||
| 
						 | 
					1449c51d49 | ||
| 
						 | 
					a451705e0b | ||
| 
						 | 
					2e6db39173 | ||
| 
						 | 
					373f75253c | ||
| 
						 | 
					724842084e | ||
| 
						 | 
					8f3635b167 | ||
| 
						 | 
					11605a36f7 | ||
| 
						 | 
					533f81d625 | ||
| 
						 | 
					aacb9e44e8 | ||
| 
						 | 
					c6e3f1bca6 | ||
| 
						 | 
					a933d4aeb6 | ||
| 
						 | 
					caa5b20791 | ||
| 
						 | 
					e2ad9ed746 | ||
| 
						 | 
					32c0e7c2ae | ||
| 
						 | 
					6c564c7b7f | ||
| 
						 | 
					c81e3a3be4 | ||
| 
						 | 
					6b1b9ef7ec | ||
| 
						 | 
					c26a8b8718 | ||
| 
						 | 
					4a89a475bd | ||
| 
						 | 
					8cf15c7f5c | ||
| 
						 | 
					adc76ca1b8 | ||
| 
						 | 
					8f8892440c | ||
| 
						 | 
					570843150d | ||
| 
						 | 
					f3fc9e4142 | ||
| 
						 | 
					075fcb77a8 | ||
| 
						 | 
					e5899ff717 | ||
| 
						 | 
					2fb3970027 | ||
| 
						 | 
					1a4efa1b8c | ||
| 
						 | 
					72f656ffef | ||
| 
						 | 
					b60239d5e5 | ||
| 
						 | 
					d02e280c3c | ||
| 
						 | 
					6535b0966e | ||
| 
						 | 
					82dbacbee5 | ||
| 
						 | 
					2432901974 | ||
| 
						 | 
					ebb5d58c14 | ||
| 
						 | 
					605e365405 | ||
| 
						 | 
					5ab995d8ca | ||
| 
						 | 
					4248741b11 | ||
| 
						 | 
					4b8ecc7634 | ||
| 
						 | 
					25d04c759c | ||
| 
						 | 
					b4ec84030e | ||
| 
						 | 
					29e8761373 | ||
| 
						 | 
					a04299c59e | ||
| 
						 | 
					d7bf3c51d9 | ||
| 
						 | 
					ac0b095941 | ||
| 
						 | 
					cda9bad233 | ||
| 
						 | 
					41db8a1264 | ||
| 
						 | 
					e7e785fd60 | ||
| 
						 | 
					300d3a1f46 | ||
| 
						 | 
					356554c08d | ||
| 
						 | 
					ced28ad006 | 
@@ -25,3 +25,4 @@ indent_size = 2
 | 
			
		||||
[*.{yaml,yml}]
 | 
			
		||||
indent_style = space
 | 
			
		||||
indent_size = 2
 | 
			
		||||
quote_type = single
 | 
			
		||||
							
								
								
									
										40
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@@ -1,13 +1,47 @@
 | 
			
		||||
## Description:
 | 
			
		||||
# What does this implement/fix? 
 | 
			
		||||
 | 
			
		||||
Quick description 
 | 
			
		||||
 | 
			
		||||
**Related issue (if applicable):** fixes <link to issue>
 | 
			
		||||
## Types of changes
 | 
			
		||||
 | 
			
		||||
- [ ] Bugfix (non-breaking change which fixes an issue)
 | 
			
		||||
- [ ] New feature (non-breaking change which adds functionality)
 | 
			
		||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
 | 
			
		||||
- [ ] Configuration change (this will require users to update their yaml configuration files to keep working)
 | 
			
		||||
 | 
			
		||||
**Related issue or feature (if applicable):** fixes <link to issue>
 | 
			
		||||
 | 
			
		||||
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here>
 | 
			
		||||
  
 | 
			
		||||
# Test Environment
 | 
			
		||||
 | 
			
		||||
- [ ] ESP32
 | 
			
		||||
- [ ] ESP8266
 | 
			
		||||
- [ ] Windows
 | 
			
		||||
- [ ] Mac OS
 | 
			
		||||
- [ ] Linux
 | 
			
		||||
 | 
			
		||||
## Example entry for `config.yaml`:
 | 
			
		||||
<!--
 | 
			
		||||
  Supplying a configuration snippet, makes it easier for a maintainer to test
 | 
			
		||||
  your PR. Furthermore, for new integrations, it gives an impression of how
 | 
			
		||||
  the configuration would look like.
 | 
			
		||||
  Note: Remove this section if this PR does not have an example entry.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
# Example config.yaml
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# Explain your changes
 | 
			
		||||
 | 
			
		||||
Describe your changes here to communicate to the maintainers **why we should accept this pull request**.
 | 
			
		||||
Very important to fill if no issue linked
 | 
			
		||||
 | 
			
		||||
## Checklist:
 | 
			
		||||
  - [ ] The code change is tested and works locally.
 | 
			
		||||
  - [ ] Tests have been added to verify that the new code works (under `tests/` folder).
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
If user exposed functionality or configuration variables are added/changed:
 | 
			
		||||
  - [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs).
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,6 +18,7 @@ jobs:
 | 
			
		||||
    name: Build docker containers
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        build_type: ["hassio", "docker"]
 | 
			
		||||
@@ -25,7 +26,7 @@ jobs:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up env variables
 | 
			
		||||
        run: |
 | 
			
		||||
          base_version="2.4.1"
 | 
			
		||||
          base_version="3.0.0"
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
			
		||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
@@ -37,14 +38,14 @@ jobs:
 | 
			
		||||
            dockerfile="docker/Dockerfile"
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
          echo "::set-env name=BUILD_FROM::${build_from}"
 | 
			
		||||
          echo "::set-env name=BUILD_TO::${build_to}"
 | 
			
		||||
          echo "::set-env name=DOCKERFILE::${dockerfile}"
 | 
			
		||||
          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.0.0-2 --reset -p yes
 | 
			
		||||
        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}" \
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										59
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										59
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -11,45 +11,6 @@ on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  # A fast overview job that checks only changed files
 | 
			
		||||
  overview:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    container: esphome/esphome-lint:latest
 | 
			
		||||
    steps:
 | 
			
		||||
      # Also fetch history and dev branch so that we can check which files changed
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
      - name: Fetch dev branch
 | 
			
		||||
        run: git fetch origin dev
 | 
			
		||||
 | 
			
		||||
      # Cache the .pio directory with (primarily) library dependencies
 | 
			
		||||
      - name: Cache .pio lib_deps
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: .pio
 | 
			
		||||
          key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            lint-cpp-pio-
 | 
			
		||||
      - name: Set up python environment
 | 
			
		||||
        run: script/setup
 | 
			
		||||
      # 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/ci-custom.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/gcc.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/lint-python.json"
 | 
			
		||||
          echo "::add-matcher::.github/workflows/matchers/python.json"
 | 
			
		||||
      - name: Run a quick lint over all changed files
 | 
			
		||||
        run: script/quicklint
 | 
			
		||||
      - name: Suggest changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 | 
			
		||||
  lint-clang-format:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # cpp lint job runs with esphome-lint docker image so that clang-format-*
 | 
			
		||||
@@ -57,15 +18,6 @@ jobs:
 | 
			
		||||
    container: esphome/esphome-lint:latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Cache platformio intermediary files (like libraries etc)
 | 
			
		||||
      # Note: platformio platform versions should be cached via the esphome-lint image
 | 
			
		||||
      - name: Cache Platformio
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: .pio
 | 
			
		||||
          key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            lint-cpp-pio-
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
@@ -83,19 +35,11 @@ jobs:
 | 
			
		||||
    container: esphome/esphome-lint:latest
 | 
			
		||||
    # 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
 | 
			
		||||
      # Cache platformio intermediary files (like libraries etc)
 | 
			
		||||
      # Note: platformio platform versions should be cached via the esphome-lint image
 | 
			
		||||
      - name: Cache Platformio
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: .pio
 | 
			
		||||
          key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            lint-cpp-pio-
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
@@ -146,6 +90,7 @@ jobs:
 | 
			
		||||
  test:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
          test:
 | 
			
		||||
          - test1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										34
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										34
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							@@ -15,15 +15,6 @@ jobs:
 | 
			
		||||
    container: esphome/esphome-lint:latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Cache platformio intermediary files (like libraries etc)
 | 
			
		||||
      # Note: platformio platform versions should be cached via the esphome-lint image
 | 
			
		||||
      - name: Cache Platformio
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: .pio
 | 
			
		||||
          key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            lint-cpp-pio-
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
@@ -41,19 +32,11 @@ jobs:
 | 
			
		||||
    container: esphome/esphome-lint:latest
 | 
			
		||||
    # 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
 | 
			
		||||
      # Cache platformio intermediary files (like libraries etc)
 | 
			
		||||
      # Note: platformio platform versions should be cached via the esphome-lint image
 | 
			
		||||
      - name: Cache Platformio
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: .pio
 | 
			
		||||
          key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            lint-cpp-pio-
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
@@ -104,6 +87,7 @@ jobs:
 | 
			
		||||
  test:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
          test:
 | 
			
		||||
          - test1
 | 
			
		||||
@@ -187,10 +171,10 @@ jobs:
 | 
			
		||||
      - name: Set TAG
 | 
			
		||||
        run: |
 | 
			
		||||
          TAG="${GITHUB_SHA:0:7}"
 | 
			
		||||
          echo "::set-env name=TAG::${TAG}"
 | 
			
		||||
          echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Set up env variables
 | 
			
		||||
        run: |
 | 
			
		||||
          base_version="2.4.1"
 | 
			
		||||
          base_version="3.0.0"
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
			
		||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
@@ -202,14 +186,14 @@ jobs:
 | 
			
		||||
            dockerfile="docker/Dockerfile"
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
          echo "::set-env name=BUILD_FROM::${build_from}"
 | 
			
		||||
          echo "::set-env name=BUILD_TO::${build_to}"
 | 
			
		||||
          echo "::set-env name=DOCKERFILE::${dockerfile}"
 | 
			
		||||
          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.0.0-2 --reset -p yes
 | 
			
		||||
        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}" \
 | 
			
		||||
@@ -241,7 +225,7 @@ jobs:
 | 
			
		||||
    - name: Set TAG
 | 
			
		||||
      run: |
 | 
			
		||||
        TAG="${GITHUB_SHA:0:7}"
 | 
			
		||||
        echo "::set-env name=TAG::${TAG}"
 | 
			
		||||
        echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
    - name: Log in to docker hub
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										38
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -14,15 +14,6 @@ jobs:
 | 
			
		||||
    container: esphome/esphome-lint:latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      # Cache platformio intermediary files (like libraries etc)
 | 
			
		||||
      # Note: platformio platform versions should be cached via the esphome-lint image
 | 
			
		||||
      - name: Cache Platformio
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: .pio
 | 
			
		||||
          key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            lint-cpp-pio-
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
@@ -40,19 +31,11 @@ jobs:
 | 
			
		||||
    container: esphome/esphome-lint:latest
 | 
			
		||||
    # 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
 | 
			
		||||
      # Cache platformio intermediary files (like libraries etc)
 | 
			
		||||
      # Note: platformio platform versions should be cached via the esphome-lint image
 | 
			
		||||
      - name: Cache Platformio
 | 
			
		||||
        uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: .pio
 | 
			
		||||
          key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            lint-cpp-pio-
 | 
			
		||||
      # Set up the pio project so that the cpp checks know how files are compiled
 | 
			
		||||
      # (build flags, libraries etc)
 | 
			
		||||
      - name: Set up platformio environment
 | 
			
		||||
@@ -103,6 +86,7 @@ jobs:
 | 
			
		||||
  test:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
          test:
 | 
			
		||||
          - test1
 | 
			
		||||
@@ -207,10 +191,10 @@ jobs:
 | 
			
		||||
      - name: Set TAG
 | 
			
		||||
        run: |
 | 
			
		||||
          TAG="${GITHUB_REF#refs/tags/v}"
 | 
			
		||||
          echo "::set-env name=TAG::${TAG}"
 | 
			
		||||
          echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Set up env variables
 | 
			
		||||
        run: |
 | 
			
		||||
          base_version="2.4.1"
 | 
			
		||||
          base_version="3.0.0"
 | 
			
		||||
 | 
			
		||||
          if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
 | 
			
		||||
            build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
 | 
			
		||||
@@ -226,18 +210,18 @@ jobs:
 | 
			
		||||
            cache_tag="beta"
 | 
			
		||||
          else
 | 
			
		||||
            cache_tag="latest"
 | 
			
		||||
          end
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
          # Set env variables so these values don't need to be calculated again
 | 
			
		||||
          echo "::set-env name=BUILD_FROM::${build_from}"
 | 
			
		||||
          echo "::set-env name=BUILD_TO::${build_to}"
 | 
			
		||||
          echo "::set-env name=DOCKERFILE::${dockerfile}"
 | 
			
		||||
          echo "::set-env name=CACHE_TAG::${cache_tag}"
 | 
			
		||||
          echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
 | 
			
		||||
          echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
 | 
			
		||||
          echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
 | 
			
		||||
          echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV
 | 
			
		||||
      - name: Pull for cache
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull "${BUILD_TO}:${CACHE_TAG}" || true
 | 
			
		||||
      - name: Register QEMU binfmt
 | 
			
		||||
        run: docker run --rm --privileged multiarch/qemu-user-static:5.0.0-2 --reset -p yes
 | 
			
		||||
        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}" \
 | 
			
		||||
@@ -277,7 +261,7 @@ jobs:
 | 
			
		||||
    - name: Set TAG
 | 
			
		||||
      run: |
 | 
			
		||||
        TAG="${GITHUB_REF#refs/tags/v}"
 | 
			
		||||
        echo "::set-env name=TAG::${TAG}"
 | 
			
		||||
        echo "TAG=${TAG}" >> $GITHUB_ENV
 | 
			
		||||
    - name: Log in to docker hub
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_USER: ${{ secrets.DOCKER_USER }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -81,7 +81,8 @@ venv.bak/
 | 
			
		||||
.pioenvs
 | 
			
		||||
.piolibdeps
 | 
			
		||||
.pio
 | 
			
		||||
.vscode
 | 
			
		||||
.vscode/
 | 
			
		||||
!.vscode/tasks.json
 | 
			
		||||
CMakeListsPrivate.txt
 | 
			
		||||
CMakeLists.txt
 | 
			
		||||
 | 
			
		||||
@@ -119,3 +120,4 @@ config/
 | 
			
		||||
tests/build/
 | 
			
		||||
tests/.esphome/
 | 
			
		||||
/.temp-clang-tidy.cpp
 | 
			
		||||
.pio/
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,27 @@
 | 
			
		||||
# See https://pre-commit.com for more information
 | 
			
		||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
			
		||||
repos:
 | 
			
		||||
-   repo: https://github.com/pre-commit/pre-commit-hooks
 | 
			
		||||
    rev: v2.4.0
 | 
			
		||||
  - repo: https://github.com/ambv/black
 | 
			
		||||
    rev: 20.8b1
 | 
			
		||||
    hooks:
 | 
			
		||||
    -   id: trailing-whitespace
 | 
			
		||||
    -   id: end-of-file-fixer
 | 
			
		||||
    -   id: check-yaml
 | 
			
		||||
    -   id: check-added-large-files
 | 
			
		||||
    -   id: flake8
 | 
			
		||||
    - id: black
 | 
			
		||||
      args:
 | 
			
		||||
        - --safe
 | 
			
		||||
        - --quiet
 | 
			
		||||
      files: ^((esphome|script|tests)/.+)?[^/]+\.py$
 | 
			
		||||
  - repo: https://gitlab.com/pycqa/flake8
 | 
			
		||||
    rev: 3.8.4
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: flake8
 | 
			
		||||
        additional_dependencies:
 | 
			
		||||
          - flake8-docstrings==1.5.0
 | 
			
		||||
          - pydocstyle==5.1.1
 | 
			
		||||
        files: ^(esphome|tests)/.+\.py$
 | 
			
		||||
  - repo: https://github.com/pre-commit/pre-commit-hooks
 | 
			
		||||
    rev: v3.4.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: no-commit-to-branch
 | 
			
		||||
        args:
 | 
			
		||||
          - --branch=dev
 | 
			
		||||
          - --branch=master
 | 
			
		||||
          - --branch=beta
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
{
 | 
			
		||||
    "version": "2.0.0",
 | 
			
		||||
    "tasks": [
 | 
			
		||||
        {
 | 
			
		||||
            "label": "run",
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "command": "python3 -m esphome config dashboard",
 | 
			
		||||
            "problemMatcher": []
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								CODEOWNERS
									
									
									
									
									
								
							@@ -13,10 +13,14 @@ esphome/core/* @esphome/core
 | 
			
		||||
# Integrations
 | 
			
		||||
esphome/components/ac_dimmer/* @glmnet
 | 
			
		||||
esphome/components/adc/* @esphome/core
 | 
			
		||||
esphome/components/addressable_light/* @justfalter
 | 
			
		||||
esphome/components/animation/* @syndlex
 | 
			
		||||
esphome/components/api/* @OttoWinter
 | 
			
		||||
esphome/components/async_tcp/* @OttoWinter
 | 
			
		||||
esphome/components/atc_mithermometer/* @ahpohl
 | 
			
		||||
esphome/components/bang_bang/* @OttoWinter
 | 
			
		||||
esphome/components/binary_sensor/* @esphome/core
 | 
			
		||||
esphome/components/canbus/* @danielschramm @mvturnho
 | 
			
		||||
esphome/components/captive_portal/* @OttoWinter
 | 
			
		||||
esphome/components/climate/* @esphome/core
 | 
			
		||||
esphome/components/climate_ir/* @glmnet
 | 
			
		||||
@@ -26,24 +30,47 @@ esphome/components/ct_clamp/* @jesserockz
 | 
			
		||||
esphome/components/debug/* @OttoWinter
 | 
			
		||||
esphome/components/dfplayer/* @glmnet
 | 
			
		||||
esphome/components/dht/* @OttoWinter
 | 
			
		||||
esphome/components/ds1307/* @badbadc0ffee
 | 
			
		||||
esphome/components/exposure_notifications/* @OttoWinter
 | 
			
		||||
esphome/components/ezo/* @ssieb
 | 
			
		||||
esphome/components/fastled_base/* @OttoWinter
 | 
			
		||||
esphome/components/globals/* @esphome/core
 | 
			
		||||
esphome/components/gpio/* @esphome/core
 | 
			
		||||
esphome/components/homeassistant/* @OttoWinter
 | 
			
		||||
esphome/components/i2c/* @esphome/core
 | 
			
		||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
 | 
			
		||||
esphome/components/inkplate6/* @jesserockz
 | 
			
		||||
esphome/components/integration/* @OttoWinter
 | 
			
		||||
esphome/components/interval/* @esphome/core
 | 
			
		||||
esphome/components/json/* @OttoWinter
 | 
			
		||||
esphome/components/ledc/* @OttoWinter
 | 
			
		||||
esphome/components/light/* @esphome/core
 | 
			
		||||
esphome/components/logger/* @esphome/core
 | 
			
		||||
esphome/components/max7219digit/* @rspaargaren
 | 
			
		||||
esphome/components/mcp23008/* @jesserockz
 | 
			
		||||
esphome/components/mcp23017/* @jesserockz
 | 
			
		||||
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
 | 
			
		||||
esphome/components/mcp23s17/* @SenexCrenshaw @jesserockz
 | 
			
		||||
esphome/components/mcp23x08_base/* @jesserockz
 | 
			
		||||
esphome/components/mcp23x17_base/* @jesserockz
 | 
			
		||||
esphome/components/mcp23xxx_base/* @jesserockz
 | 
			
		||||
esphome/components/mcp2515/* @danielschramm @mvturnho
 | 
			
		||||
esphome/components/mcp9808/* @k7hpn
 | 
			
		||||
esphome/components/midea_ac/* @dudanov
 | 
			
		||||
esphome/components/midea_dongle/* @dudanov
 | 
			
		||||
esphome/components/network/* @esphome/core
 | 
			
		||||
esphome/components/nfc/* @jesserockz
 | 
			
		||||
esphome/components/ota/* @esphome/core
 | 
			
		||||
esphome/components/output/* @esphome/core
 | 
			
		||||
esphome/components/pid/* @OttoWinter
 | 
			
		||||
esphome/components/pn532/* @OttoWinter
 | 
			
		||||
esphome/components/pn532/* @OttoWinter @jesserockz
 | 
			
		||||
esphome/components/pn532_i2c/* @OttoWinter @jesserockz
 | 
			
		||||
esphome/components/pn532_spi/* @OttoWinter @jesserockz
 | 
			
		||||
esphome/components/power_supply/* @esphome/core
 | 
			
		||||
esphome/components/pulse_meter/* @stevebaxter
 | 
			
		||||
esphome/components/rc522/* @glmnet
 | 
			
		||||
esphome/components/rc522_i2c/* @glmnet
 | 
			
		||||
esphome/components/rc522_spi/* @glmnet
 | 
			
		||||
esphome/components/restart/* @esphome/core
 | 
			
		||||
esphome/components/rf_bridge/* @jesserockz
 | 
			
		||||
esphome/components/rtttl/* @glmnet
 | 
			
		||||
@@ -52,12 +79,28 @@ esphome/components/sensor/* @esphome/core
 | 
			
		||||
esphome/components/shutdown/* @esphome/core
 | 
			
		||||
esphome/components/sim800l/* @glmnet
 | 
			
		||||
esphome/components/spi/* @esphome/core
 | 
			
		||||
esphome/components/ssd1322_base/* @kbx81
 | 
			
		||||
esphome/components/ssd1322_spi/* @kbx81
 | 
			
		||||
esphome/components/ssd1325_base/* @kbx81
 | 
			
		||||
esphome/components/ssd1325_spi/* @kbx81
 | 
			
		||||
esphome/components/ssd1327_base/* @kbx81
 | 
			
		||||
esphome/components/ssd1327_i2c/* @kbx81
 | 
			
		||||
esphome/components/ssd1327_spi/* @kbx81
 | 
			
		||||
esphome/components/ssd1331_base/* @kbx81
 | 
			
		||||
esphome/components/ssd1331_spi/* @kbx81
 | 
			
		||||
esphome/components/ssd1351_base/* @kbx81
 | 
			
		||||
esphome/components/ssd1351_spi/* @kbx81
 | 
			
		||||
esphome/components/st7735/* @SenexCrenshaw
 | 
			
		||||
esphome/components/st7789v/* @kbx81
 | 
			
		||||
esphome/components/substitutions/* @esphome/core
 | 
			
		||||
esphome/components/sun/* @OttoWinter
 | 
			
		||||
esphome/components/switch/* @esphome/core
 | 
			
		||||
esphome/components/tcl112/* @glmnet
 | 
			
		||||
esphome/components/teleinfo/* @0hax
 | 
			
		||||
esphome/components/thermostat/* @kbx81
 | 
			
		||||
esphome/components/time/* @OttoWinter
 | 
			
		||||
esphome/components/tm1637/* @glmnet
 | 
			
		||||
esphome/components/tmp102/* @timsavage
 | 
			
		||||
esphome/components/tuya/binary_sensor/* @jesserockz
 | 
			
		||||
esphome/components/tuya/climate/* @jesserockz
 | 
			
		||||
esphome/components/tuya/sensor/* @jesserockz
 | 
			
		||||
@@ -67,3 +110,5 @@ esphome/components/ultrasonic/* @OttoWinter
 | 
			
		||||
esphome/components/version/* @esphome/core
 | 
			
		||||
esphome/components/web_server_base/* @OttoWinter
 | 
			
		||||
esphome/components/whirlpool/* @glmnet
 | 
			
		||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
 | 
			
		||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
ARG BUILD_FROM=esphome/esphome-base-amd64:2.4.1
 | 
			
		||||
ARG BUILD_FROM=esphome/esphome-base-amd64:3.0.0
 | 
			
		||||
FROM ${BUILD_FROM}
 | 
			
		||||
 | 
			
		||||
# First install requirements to leverage caching when requirements don't change
 | 
			
		||||
COPY requirements.txt /
 | 
			
		||||
RUN pip3 install --no-cache-dir -r /requirements.txt
 | 
			
		||||
COPY requirements.txt docker/platformio_install_deps.py platformio.ini /
 | 
			
		||||
RUN \
 | 
			
		||||
    pip3 install --no-cache-dir -r /requirements.txt \
 | 
			
		||||
    && /platformio_install_deps.py /platformio.ini
 | 
			
		||||
 | 
			
		||||
# Then copy esphome and install
 | 
			
		||||
COPY . .
 | 
			
		||||
@@ -12,6 +14,13 @@ RUN pip3 install --no-cache-dir -e .
 | 
			
		||||
# Settings for dashboard
 | 
			
		||||
ENV USERNAME="" PASSWORD=""
 | 
			
		||||
 | 
			
		||||
# Expose the dashboard to Docker
 | 
			
		||||
EXPOSE 6052
 | 
			
		||||
 | 
			
		||||
# Run healthcheck (heartbeat)
 | 
			
		||||
HEALTHCHECK --interval=30s --timeout=30s \
 | 
			
		||||
  CMD curl --fail http://localhost:6052 || exit 1
 | 
			
		||||
 | 
			
		||||
# The directory the user should mount their configuration files to
 | 
			
		||||
WORKDIR /config
 | 
			
		||||
# Set entrypoint to esphome so that the user doesn't have to type 'esphome'
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
FROM esphome/esphome-base-amd64:2.4.1
 | 
			
		||||
FROM esphome/esphome-base-amd64:3.0.0
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,10 @@ ARG BUILD_FROM
 | 
			
		||||
FROM ${BUILD_FROM}
 | 
			
		||||
 | 
			
		||||
# First install requirements to leverage caching when requirements don't change
 | 
			
		||||
COPY requirements.txt /
 | 
			
		||||
RUN pip3 install --no-cache-dir -r /requirements.txt
 | 
			
		||||
COPY requirements.txt docker/platformio_install_deps.py platformio.ini /
 | 
			
		||||
RUN \
 | 
			
		||||
    pip3 install --no-cache-dir -r /requirements.txt \
 | 
			
		||||
    && /platformio_install_deps.py /platformio.ini
 | 
			
		||||
 | 
			
		||||
# Copy root filesystem
 | 
			
		||||
COPY docker/rootfs/ /
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
FROM esphome/esphome-lint-base:2.4.1
 | 
			
		||||
FROM esphome/esphome-lint-base:3.0.0
 | 
			
		||||
 | 
			
		||||
COPY requirements.txt requirements_test.txt /
 | 
			
		||||
RUN pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt
 | 
			
		||||
COPY requirements.txt requirements_test.txt docker/platformio_install_deps.py  platformio.ini /
 | 
			
		||||
RUN \
 | 
			
		||||
    pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt \
 | 
			
		||||
    && /platformio_install_deps.py /platformio.ini
 | 
			
		||||
 | 
			
		||||
VOLUME ["/esphome"]
 | 
			
		||||
WORKDIR /esphome
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								docker/platformio_install_deps.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										20
									
								
								docker/platformio_install_deps.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
# This script is used in the docker containers to preinstall
 | 
			
		||||
# all platformio libraries in the global storage
 | 
			
		||||
 | 
			
		||||
import configparser
 | 
			
		||||
import re
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
config = configparser.ConfigParser()
 | 
			
		||||
config.read(sys.argv[1])
 | 
			
		||||
libs = []
 | 
			
		||||
for line in config['common']['lib_deps'].splitlines():
 | 
			
		||||
    # Format: '1655@1.0.2  ; TinyGPSPlus (has name conflict)' (includes comment)
 | 
			
		||||
    m = re.search(r'([a-zA-Z0-9-_/]+@[0-9\.]+)', line)
 | 
			
		||||
    if m is None:
 | 
			
		||||
        continue
 | 
			
		||||
    libs.append(m.group(1))
 | 
			
		||||
 | 
			
		||||
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])
 | 
			
		||||
@@ -8,21 +8,37 @@ from datetime import datetime
 | 
			
		||||
from esphome import const, writer, yaml_util
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.config import iter_components, read_config, strip_default_ids
 | 
			
		||||
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
 | 
			
		||||
    CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_BAUD_RATE,
 | 
			
		||||
    CONF_BROKER,
 | 
			
		||||
    CONF_LOGGER,
 | 
			
		||||
    CONF_OTA,
 | 
			
		||||
    CONF_PASSWORD,
 | 
			
		||||
    CONF_PORT,
 | 
			
		||||
    CONF_ESPHOME,
 | 
			
		||||
    CONF_PLATFORMIO_OPTIONS,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
 | 
			
		||||
from esphome.helpers import color, indent
 | 
			
		||||
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files, \
 | 
			
		||||
    get_serial_ports
 | 
			
		||||
from esphome.helpers import indent
 | 
			
		||||
from esphome.util import (
 | 
			
		||||
    run_external_command,
 | 
			
		||||
    run_external_process,
 | 
			
		||||
    safe_print,
 | 
			
		||||
    list_yaml_files,
 | 
			
		||||
    get_serial_ports,
 | 
			
		||||
)
 | 
			
		||||
from esphome.log import color, setup_log, Fore
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def choose_prompt(options):
 | 
			
		||||
    if not options:
 | 
			
		||||
        raise EsphomeError("Found no valid options for upload/logging, please make sure relevant "
 | 
			
		||||
                           "sections (ota, api, mqtt, ...) are in your configuration and/or the "
 | 
			
		||||
                           "device is plugged in.")
 | 
			
		||||
        raise EsphomeError(
 | 
			
		||||
            "Found no valid options for upload/logging, please make sure relevant "
 | 
			
		||||
            "sections (ota, api, mqtt, ...) are in your configuration and/or the "
 | 
			
		||||
            "device is plugged in."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if len(options) == 1:
 | 
			
		||||
        return options[0][1]
 | 
			
		||||
@@ -32,7 +48,7 @@ def choose_prompt(options):
 | 
			
		||||
        safe_print(f"  [{i+1}] {desc}")
 | 
			
		||||
 | 
			
		||||
    while True:
 | 
			
		||||
        opt = input('(number): ')
 | 
			
		||||
        opt = input("(number): ")
 | 
			
		||||
        if opt in options:
 | 
			
		||||
            opt = options.index(opt)
 | 
			
		||||
            break
 | 
			
		||||
@@ -42,7 +58,7 @@ def choose_prompt(options):
 | 
			
		||||
                raise ValueError
 | 
			
		||||
            break
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            safe_print(color('red', f"Invalid option: '{opt}'"))
 | 
			
		||||
            safe_print(color(Fore.RED, f"Invalid option: '{opt}'"))
 | 
			
		||||
    return options[opt - 1][1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -50,14 +66,14 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
 | 
			
		||||
    options = []
 | 
			
		||||
    for port in get_serial_ports():
 | 
			
		||||
        options.append((f"{port.path} ({port.description})", port.path))
 | 
			
		||||
    if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config):
 | 
			
		||||
    if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
 | 
			
		||||
        options.append((f"Over The Air ({CORE.address})", CORE.address))
 | 
			
		||||
        if default == 'OTA':
 | 
			
		||||
        if default == "OTA":
 | 
			
		||||
            return CORE.address
 | 
			
		||||
    if show_mqtt and 'mqtt' in CORE.config:
 | 
			
		||||
        options.append(("MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT'))
 | 
			
		||||
        if default == 'OTA':
 | 
			
		||||
            return 'MQTT'
 | 
			
		||||
    if show_mqtt and "mqtt" in CORE.config:
 | 
			
		||||
        options.append(("MQTT ({})".format(CORE.config["mqtt"][CONF_BROKER]), "MQTT"))
 | 
			
		||||
        if default == "OTA":
 | 
			
		||||
            return "MQTT"
 | 
			
		||||
    if default is not None:
 | 
			
		||||
        return default
 | 
			
		||||
    if check_default is not None and check_default in [opt[1] for opt in options]:
 | 
			
		||||
@@ -66,11 +82,11 @@ def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_port_type(port):
 | 
			
		||||
    if port.startswith('/') or port.startswith('COM'):
 | 
			
		||||
        return 'SERIAL'
 | 
			
		||||
    if port == 'MQTT':
 | 
			
		||||
        return 'MQTT'
 | 
			
		||||
    return 'NETWORK'
 | 
			
		||||
    if port.startswith("/") or port.startswith("COM"):
 | 
			
		||||
        return "SERIAL"
 | 
			
		||||
    if port == "MQTT":
 | 
			
		||||
        return "MQTT"
 | 
			
		||||
    return "NETWORK"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run_miniterm(config, port):
 | 
			
		||||
@@ -80,7 +96,7 @@ def run_miniterm(config, port):
 | 
			
		||||
    if CONF_LOGGER not in config:
 | 
			
		||||
        _LOGGER.info("Logger is not enabled. Not starting UART logs.")
 | 
			
		||||
        return
 | 
			
		||||
    baud_rate = config['logger'][CONF_BAUD_RATE]
 | 
			
		||||
    baud_rate = config["logger"][CONF_BAUD_RATE]
 | 
			
		||||
    if baud_rate == 0:
 | 
			
		||||
        _LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
 | 
			
		||||
    _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
 | 
			
		||||
@@ -93,13 +109,18 @@ def run_miniterm(config, port):
 | 
			
		||||
            except serial.SerialException:
 | 
			
		||||
                _LOGGER.error("Serial port closed!")
 | 
			
		||||
                return
 | 
			
		||||
            line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', 'backslashreplace')
 | 
			
		||||
            time = datetime.now().time().strftime('[%H:%M:%S]')
 | 
			
		||||
            line = (
 | 
			
		||||
                raw.replace(b"\r", b"")
 | 
			
		||||
                .replace(b"\n", b"")
 | 
			
		||||
                .decode("utf8", "backslashreplace")
 | 
			
		||||
            )
 | 
			
		||||
            time = datetime.now().time().strftime("[%H:%M:%S]")
 | 
			
		||||
            message = time + line
 | 
			
		||||
            safe_print(message)
 | 
			
		||||
 | 
			
		||||
            backtrace_state = platformio_api.process_stacktrace(
 | 
			
		||||
                config, line, backtrace_state=backtrace_state)
 | 
			
		||||
                config, line, backtrace_state=backtrace_state
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def wrap_to_code(name, comp):
 | 
			
		||||
@@ -111,7 +132,7 @@ def wrap_to_code(name, comp):
 | 
			
		||||
        cg.add(cg.LineComment(f"{name}:"))
 | 
			
		||||
        if comp.config_schema is not None:
 | 
			
		||||
            conf_str = yaml_util.dump(conf)
 | 
			
		||||
            conf_str = conf_str.replace('//', '')
 | 
			
		||||
            conf_str = conf_str.replace("//", "")
 | 
			
		||||
            cg.add(cg.LineComment(indent(conf_str)))
 | 
			
		||||
        yield coro(conf)
 | 
			
		||||
 | 
			
		||||
@@ -151,15 +172,31 @@ def compile_program(args, config):
 | 
			
		||||
 | 
			
		||||
def upload_using_esptool(config, port):
 | 
			
		||||
    path = CORE.firmware_bin
 | 
			
		||||
    first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get('upload_speed', 460800)
 | 
			
		||||
    first_baudrate = config[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS].get(
 | 
			
		||||
        "upload_speed", 460800
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def run_esptool(baud_rate):
 | 
			
		||||
        cmd = ['esptool.py', '--before', 'default_reset', '--after', 'hard_reset',
 | 
			
		||||
               '--baud', str(baud_rate),
 | 
			
		||||
               '--chip', 'esp8266', '--port', port, 'write_flash', '0x0', path]
 | 
			
		||||
        cmd = [
 | 
			
		||||
            "esptool.py",
 | 
			
		||||
            "--before",
 | 
			
		||||
            "default_reset",
 | 
			
		||||
            "--after",
 | 
			
		||||
            "hard_reset",
 | 
			
		||||
            "--baud",
 | 
			
		||||
            str(baud_rate),
 | 
			
		||||
            "--chip",
 | 
			
		||||
            "esp8266",
 | 
			
		||||
            "--port",
 | 
			
		||||
            port,
 | 
			
		||||
            "write_flash",
 | 
			
		||||
            "0x0",
 | 
			
		||||
            path,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        if os.environ.get('ESPHOME_USE_SUBPROCESS') is None:
 | 
			
		||||
        if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
 | 
			
		||||
            import esptool
 | 
			
		||||
 | 
			
		||||
            # pylint: disable=protected-access
 | 
			
		||||
            return run_external_command(esptool._main, *cmd)
 | 
			
		||||
 | 
			
		||||
@@ -169,14 +206,16 @@ def upload_using_esptool(config, port):
 | 
			
		||||
    if rc == 0 or first_baudrate == 115200:
 | 
			
		||||
        return rc
 | 
			
		||||
    # Try with 115200 baud rate, with some serial chips the faster baud rates do not work well
 | 
			
		||||
    _LOGGER.info("Upload with baud rate %s failed. Trying again with baud rate 115200.",
 | 
			
		||||
                 first_baudrate)
 | 
			
		||||
    _LOGGER.info(
 | 
			
		||||
        "Upload with baud rate %s failed. Trying again with baud rate 115200.",
 | 
			
		||||
        first_baudrate,
 | 
			
		||||
    )
 | 
			
		||||
    return run_esptool(115200)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def upload_program(config, args, host):
 | 
			
		||||
    # 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
 | 
			
		||||
 | 
			
		||||
        if CORE.is_esp8266:
 | 
			
		||||
@@ -186,8 +225,10 @@ def upload_program(config, args, host):
 | 
			
		||||
    from esphome import espota2
 | 
			
		||||
 | 
			
		||||
    if CONF_OTA not in config:
 | 
			
		||||
        raise EsphomeError("Cannot upload Over the Air as the config does not include the ota: "
 | 
			
		||||
                           "component")
 | 
			
		||||
        raise EsphomeError(
 | 
			
		||||
            "Cannot upload Over the Air as the config does not include the ota: "
 | 
			
		||||
            "component"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    ota_conf = config[CONF_OTA]
 | 
			
		||||
    remote_port = ota_conf[CONF_PORT]
 | 
			
		||||
@@ -196,19 +237,21 @@ def upload_program(config, args, host):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def show_logs(config, args, port):
 | 
			
		||||
    if 'logger' not in config:
 | 
			
		||||
    if "logger" not in config:
 | 
			
		||||
        raise EsphomeError("Logger is not configured!")
 | 
			
		||||
    if get_port_type(port) == 'SERIAL':
 | 
			
		||||
    if get_port_type(port) == "SERIAL":
 | 
			
		||||
        run_miniterm(config, port)
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
        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:
 | 
			
		||||
        from esphome import mqtt
 | 
			
		||||
 | 
			
		||||
        return mqtt.show_logs(config, args.topic, args.username, args.password, args.client_id)
 | 
			
		||||
        return mqtt.show_logs(
 | 
			
		||||
            config, args.topic, args.username, args.password, args.client_id
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    raise EsphomeError("No remote or local logging method configured (api/mqtt/logger)")
 | 
			
		||||
 | 
			
		||||
@@ -216,40 +259,9 @@ def show_logs(config, args, port):
 | 
			
		||||
def clean_mqtt(config, args):
 | 
			
		||||
    from esphome import mqtt
 | 
			
		||||
 | 
			
		||||
    return mqtt.clear_topic(config, args.topic, args.username, args.password, args.client_id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def setup_log(debug=False, quiet=False):
 | 
			
		||||
    if debug:
 | 
			
		||||
        log_level = logging.DEBUG
 | 
			
		||||
        CORE.verbose = True
 | 
			
		||||
    elif quiet:
 | 
			
		||||
        log_level = logging.CRITICAL
 | 
			
		||||
    else:
 | 
			
		||||
        log_level = logging.INFO
 | 
			
		||||
    logging.basicConfig(level=log_level)
 | 
			
		||||
    fmt = "%(levelname)s %(message)s"
 | 
			
		||||
    colorfmt = f"%(log_color)s{fmt}%(reset)s"
 | 
			
		||||
    datefmt = '%H:%M:%S'
 | 
			
		||||
 | 
			
		||||
    logging.getLogger('urllib3').setLevel(logging.WARNING)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        from colorlog import ColoredFormatter
 | 
			
		||||
        logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
 | 
			
		||||
            colorfmt,
 | 
			
		||||
            datefmt=datefmt,
 | 
			
		||||
            reset=True,
 | 
			
		||||
            log_colors={
 | 
			
		||||
                'DEBUG': 'cyan',
 | 
			
		||||
                'INFO': 'green',
 | 
			
		||||
                'WARNING': 'yellow',
 | 
			
		||||
                'ERROR': 'red',
 | 
			
		||||
                'CRITICAL': 'red',
 | 
			
		||||
            }
 | 
			
		||||
        ))
 | 
			
		||||
    except ImportError:
 | 
			
		||||
        pass
 | 
			
		||||
    return mqtt.clear_topic(
 | 
			
		||||
        config, args.topic, args.username, args.password, args.client_id
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def command_wizard(args):
 | 
			
		||||
@@ -269,6 +281,8 @@ def command_config(args, config):
 | 
			
		||||
def command_vscode(args):
 | 
			
		||||
    from esphome import vscode
 | 
			
		||||
 | 
			
		||||
    logging.disable(logging.INFO)
 | 
			
		||||
    logging.disable(logging.WARNING)
 | 
			
		||||
    CORE.config_path = args.configuration[0]
 | 
			
		||||
    vscode.read_config(args)
 | 
			
		||||
 | 
			
		||||
@@ -288,8 +302,13 @@ def command_compile(args, config):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def command_upload(args, config):
 | 
			
		||||
    port = choose_upload_log_host(default=args.upload_port, check_default=None,
 | 
			
		||||
                                  show_ota=True, show_mqtt=False, show_api=False)
 | 
			
		||||
    port = choose_upload_log_host(
 | 
			
		||||
        default=args.upload_port,
 | 
			
		||||
        check_default=None,
 | 
			
		||||
        show_ota=True,
 | 
			
		||||
        show_mqtt=False,
 | 
			
		||||
        show_api=False,
 | 
			
		||||
    )
 | 
			
		||||
    exit_code = upload_program(config, args, port)
 | 
			
		||||
    if exit_code != 0:
 | 
			
		||||
        return exit_code
 | 
			
		||||
@@ -298,8 +317,13 @@ def command_upload(args, config):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def command_logs(args, config):
 | 
			
		||||
    port = choose_upload_log_host(default=args.serial_port, check_default=None,
 | 
			
		||||
                                  show_ota=False, show_mqtt=True, show_api=True)
 | 
			
		||||
    port = choose_upload_log_host(
 | 
			
		||||
        default=args.serial_port,
 | 
			
		||||
        check_default=None,
 | 
			
		||||
        show_ota=False,
 | 
			
		||||
        show_mqtt=True,
 | 
			
		||||
        show_api=True,
 | 
			
		||||
    )
 | 
			
		||||
    return show_logs(config, args, port)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -311,16 +335,26 @@ def command_run(args, config):
 | 
			
		||||
    if exit_code != 0:
 | 
			
		||||
        return exit_code
 | 
			
		||||
    _LOGGER.info("Successfully compiled program.")
 | 
			
		||||
    port = choose_upload_log_host(default=args.upload_port, check_default=None,
 | 
			
		||||
                                  show_ota=True, show_mqtt=False, show_api=True)
 | 
			
		||||
    port = choose_upload_log_host(
 | 
			
		||||
        default=args.upload_port,
 | 
			
		||||
        check_default=None,
 | 
			
		||||
        show_ota=True,
 | 
			
		||||
        show_mqtt=False,
 | 
			
		||||
        show_api=True,
 | 
			
		||||
    )
 | 
			
		||||
    exit_code = upload_program(config, args, port)
 | 
			
		||||
    if exit_code != 0:
 | 
			
		||||
        return exit_code
 | 
			
		||||
    _LOGGER.info("Successfully uploaded program.")
 | 
			
		||||
    if args.no_logs:
 | 
			
		||||
        return 0
 | 
			
		||||
    port = choose_upload_log_host(default=args.upload_port, check_default=port,
 | 
			
		||||
                                  show_ota=False, show_mqtt=True, show_api=True)
 | 
			
		||||
    port = choose_upload_log_host(
 | 
			
		||||
        default=args.upload_port,
 | 
			
		||||
        check_default=port,
 | 
			
		||||
        show_ota=False,
 | 
			
		||||
        show_mqtt=True,
 | 
			
		||||
        show_api=True,
 | 
			
		||||
    )
 | 
			
		||||
    return show_logs(config, args, port)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -369,137 +403,189 @@ def command_update_all(args):
 | 
			
		||||
        click.echo(f"{half_line}{middle_text}{half_line}")
 | 
			
		||||
 | 
			
		||||
    for f in files:
 | 
			
		||||
        print("Updating {}".format(color('cyan', f)))
 | 
			
		||||
        print('-' * twidth)
 | 
			
		||||
        print("Updating {}".format(color(Fore.CYAN, f)))
 | 
			
		||||
        print("-" * twidth)
 | 
			
		||||
        print()
 | 
			
		||||
        rc = run_external_process('esphome', '--dashboard', f, 'run', '--no-logs', '--upload-port',
 | 
			
		||||
                                  'OTA')
 | 
			
		||||
        rc = run_external_process(
 | 
			
		||||
            "esphome", "--dashboard", f, "run", "--no-logs", "--upload-port", "OTA"
 | 
			
		||||
        )
 | 
			
		||||
        if rc == 0:
 | 
			
		||||
            print_bar("[{}] {}".format(color('bold_green', 'SUCCESS'), f))
 | 
			
		||||
            print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f))
 | 
			
		||||
            success[f] = True
 | 
			
		||||
        else:
 | 
			
		||||
            print_bar("[{}] {}".format(color('bold_red', 'ERROR'), f))
 | 
			
		||||
            print_bar("[{}] {}".format(color(Fore.BOLD_RED, "ERROR"), f))
 | 
			
		||||
            success[f] = False
 | 
			
		||||
 | 
			
		||||
        print()
 | 
			
		||||
        print()
 | 
			
		||||
        print()
 | 
			
		||||
 | 
			
		||||
    print_bar('[{}]'.format(color('bold_white', 'SUMMARY')))
 | 
			
		||||
    print_bar("[{}]".format(color(Fore.BOLD_WHITE, "SUMMARY")))
 | 
			
		||||
    failed = 0
 | 
			
		||||
    for f in files:
 | 
			
		||||
        if success[f]:
 | 
			
		||||
            print("  - {}: {}".format(f, color('green', 'SUCCESS')))
 | 
			
		||||
            print("  - {}: {}".format(f, color(Fore.GREEN, "SUCCESS")))
 | 
			
		||||
        else:
 | 
			
		||||
            print("  - {}: {}".format(f, color('bold_red', 'FAILED')))
 | 
			
		||||
            print("  - {}: {}".format(f, color(Fore.BOLD_RED, "FAILED")))
 | 
			
		||||
            failed += 1
 | 
			
		||||
    return failed
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
PRE_CONFIG_ACTIONS = {
 | 
			
		||||
    'wizard': command_wizard,
 | 
			
		||||
    'version': command_version,
 | 
			
		||||
    'dashboard': command_dashboard,
 | 
			
		||||
    'vscode': command_vscode,
 | 
			
		||||
    'update-all': command_update_all,
 | 
			
		||||
    "wizard": command_wizard,
 | 
			
		||||
    "version": command_version,
 | 
			
		||||
    "dashboard": command_dashboard,
 | 
			
		||||
    "vscode": command_vscode,
 | 
			
		||||
    "update-all": command_update_all,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
POST_CONFIG_ACTIONS = {
 | 
			
		||||
    'config': command_config,
 | 
			
		||||
    'compile': command_compile,
 | 
			
		||||
    'upload': command_upload,
 | 
			
		||||
    'logs': command_logs,
 | 
			
		||||
    'run': command_run,
 | 
			
		||||
    'clean-mqtt': command_clean_mqtt,
 | 
			
		||||
    'mqtt-fingerprint': command_mqtt_fingerprint,
 | 
			
		||||
    'clean': command_clean,
 | 
			
		||||
    "config": command_config,
 | 
			
		||||
    "compile": command_compile,
 | 
			
		||||
    "upload": command_upload,
 | 
			
		||||
    "logs": command_logs,
 | 
			
		||||
    "run": command_run,
 | 
			
		||||
    "clean-mqtt": command_clean_mqtt,
 | 
			
		||||
    "mqtt-fingerprint": command_mqtt_fingerprint,
 | 
			
		||||
    "clean": command_clean,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def parse_args(argv):
 | 
			
		||||
    parser = argparse.ArgumentParser(description=f'ESPHome v{const.__version__}')
 | 
			
		||||
    parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
 | 
			
		||||
                        action='store_true')
 | 
			
		||||
    parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
 | 
			
		||||
                        action='store_true')
 | 
			
		||||
    parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
 | 
			
		||||
    parser.add_argument('-s', '--substitution', nargs=2, action='append',
 | 
			
		||||
                        help='Add a substitution', metavar=('key', 'value'))
 | 
			
		||||
    parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*')
 | 
			
		||||
    parser = argparse.ArgumentParser(description=f"ESPHome v{const.__version__}")
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        "-v", "--verbose", help="Enable verbose esphome logs.", action="store_true"
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        "-q", "--quiet", help="Disable all esphome logs.", action="store_true"
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument("--dashboard", help=argparse.SUPPRESS, action="store_true")
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        "-s",
 | 
			
		||||
        "--substitution",
 | 
			
		||||
        nargs=2,
 | 
			
		||||
        action="append",
 | 
			
		||||
        help="Add a substitution",
 | 
			
		||||
        metavar=("key", "value"),
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        "configuration", help="Your YAML configuration file.", nargs="*"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    subparsers = parser.add_subparsers(help='Commands', dest='command')
 | 
			
		||||
    subparsers = parser.add_subparsers(help="Commands", dest="command")
 | 
			
		||||
    subparsers.required = True
 | 
			
		||||
    subparsers.add_parser('config', help='Validate the configuration and spit it out.')
 | 
			
		||||
    subparsers.add_parser("config", help="Validate the configuration and spit it out.")
 | 
			
		||||
 | 
			
		||||
    parser_compile = subparsers.add_parser('compile',
 | 
			
		||||
                                           help='Read the configuration and compile a program.')
 | 
			
		||||
    parser_compile.add_argument('--only-generate',
 | 
			
		||||
                                help="Only generate source code, do not compile.",
 | 
			
		||||
                                action='store_true')
 | 
			
		||||
    parser_compile = subparsers.add_parser(
 | 
			
		||||
        "compile", help="Read the configuration and compile a program."
 | 
			
		||||
    )
 | 
			
		||||
    parser_compile.add_argument(
 | 
			
		||||
        "--only-generate",
 | 
			
		||||
        help="Only generate source code, do not compile.",
 | 
			
		||||
        action="store_true",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser_upload = subparsers.add_parser('upload', help='Validate the configuration '
 | 
			
		||||
                                                         'and upload the latest binary.')
 | 
			
		||||
    parser_upload.add_argument('--upload-port', help="Manually specify the upload port to use. "
 | 
			
		||||
                                                     "For example /dev/cu.SLAB_USBtoUART.")
 | 
			
		||||
    parser_upload = subparsers.add_parser(
 | 
			
		||||
        "upload", help="Validate the configuration " "and upload the latest binary."
 | 
			
		||||
    )
 | 
			
		||||
    parser_upload.add_argument(
 | 
			
		||||
        "--upload-port",
 | 
			
		||||
        help="Manually specify the upload port to use. "
 | 
			
		||||
        "For example /dev/cu.SLAB_USBtoUART.",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser_logs = subparsers.add_parser('logs', help='Validate the configuration '
 | 
			
		||||
                                                     'and show all MQTT logs.')
 | 
			
		||||
    parser_logs.add_argument('--topic', help='Manually set the topic to subscribe to.')
 | 
			
		||||
    parser_logs.add_argument('--username', help='Manually set the username.')
 | 
			
		||||
    parser_logs.add_argument('--password', help='Manually set the password.')
 | 
			
		||||
    parser_logs.add_argument('--client-id', help='Manually set the client id.')
 | 
			
		||||
    parser_logs.add_argument('--serial-port', help="Manually specify a serial port to use"
 | 
			
		||||
                                                   "For example /dev/cu.SLAB_USBtoUART.")
 | 
			
		||||
    parser_logs = subparsers.add_parser(
 | 
			
		||||
        "logs", help="Validate the configuration " "and show all MQTT logs."
 | 
			
		||||
    )
 | 
			
		||||
    parser_logs.add_argument("--topic", help="Manually set the topic to subscribe to.")
 | 
			
		||||
    parser_logs.add_argument("--username", help="Manually set the username.")
 | 
			
		||||
    parser_logs.add_argument("--password", help="Manually set the password.")
 | 
			
		||||
    parser_logs.add_argument("--client-id", help="Manually set the client id.")
 | 
			
		||||
    parser_logs.add_argument(
 | 
			
		||||
        "--serial-port",
 | 
			
		||||
        help="Manually specify a serial port to use"
 | 
			
		||||
        "For example /dev/cu.SLAB_USBtoUART.",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser_run = subparsers.add_parser('run', help='Validate the configuration, create a binary, '
 | 
			
		||||
                                                   'upload it, and start MQTT logs.')
 | 
			
		||||
    parser_run.add_argument('--upload-port', help="Manually specify the upload port/ip to use. "
 | 
			
		||||
                                                  "For example /dev/cu.SLAB_USBtoUART.")
 | 
			
		||||
    parser_run.add_argument('--no-logs', help='Disable starting MQTT logs.',
 | 
			
		||||
                            action='store_true')
 | 
			
		||||
    parser_run.add_argument('--topic', help='Manually set the topic to subscribe to for logs.')
 | 
			
		||||
    parser_run.add_argument('--username', help='Manually set the MQTT username for logs.')
 | 
			
		||||
    parser_run.add_argument('--password', help='Manually set the MQTT password for logs.')
 | 
			
		||||
    parser_run.add_argument('--client-id', help='Manually set the client id for logs.')
 | 
			
		||||
    parser_run = subparsers.add_parser(
 | 
			
		||||
        "run",
 | 
			
		||||
        help="Validate the configuration, create a binary, "
 | 
			
		||||
        "upload it, and start MQTT logs.",
 | 
			
		||||
    )
 | 
			
		||||
    parser_run.add_argument(
 | 
			
		||||
        "--upload-port",
 | 
			
		||||
        help="Manually specify the upload port/ip to use. "
 | 
			
		||||
        "For example /dev/cu.SLAB_USBtoUART.",
 | 
			
		||||
    )
 | 
			
		||||
    parser_run.add_argument(
 | 
			
		||||
        "--no-logs", help="Disable starting MQTT logs.", action="store_true"
 | 
			
		||||
    )
 | 
			
		||||
    parser_run.add_argument(
 | 
			
		||||
        "--topic", help="Manually set the topic to subscribe to for logs."
 | 
			
		||||
    )
 | 
			
		||||
    parser_run.add_argument(
 | 
			
		||||
        "--username", help="Manually set the MQTT username for logs."
 | 
			
		||||
    )
 | 
			
		||||
    parser_run.add_argument(
 | 
			
		||||
        "--password", help="Manually set the MQTT password for logs."
 | 
			
		||||
    )
 | 
			
		||||
    parser_run.add_argument("--client-id", help="Manually set the client id for logs.")
 | 
			
		||||
 | 
			
		||||
    parser_clean = subparsers.add_parser('clean-mqtt', help="Helper to clear an MQTT topic from "
 | 
			
		||||
                                                            "retain messages.")
 | 
			
		||||
    parser_clean.add_argument('--topic', help='Manually set the topic to subscribe to.')
 | 
			
		||||
    parser_clean.add_argument('--username', help='Manually set the username.')
 | 
			
		||||
    parser_clean.add_argument('--password', help='Manually set the password.')
 | 
			
		||||
    parser_clean.add_argument('--client-id', help='Manually set the client id.')
 | 
			
		||||
    parser_clean = subparsers.add_parser(
 | 
			
		||||
        "clean-mqtt", help="Helper to clear an MQTT topic from " "retain messages."
 | 
			
		||||
    )
 | 
			
		||||
    parser_clean.add_argument("--topic", help="Manually set the topic to subscribe to.")
 | 
			
		||||
    parser_clean.add_argument("--username", help="Manually set the username.")
 | 
			
		||||
    parser_clean.add_argument("--password", help="Manually set the password.")
 | 
			
		||||
    parser_clean.add_argument("--client-id", help="Manually set the client id.")
 | 
			
		||||
 | 
			
		||||
    subparsers.add_parser('wizard', help="A helpful setup wizard that will guide "
 | 
			
		||||
                                         "you through setting up esphome.")
 | 
			
		||||
    subparsers.add_parser(
 | 
			
		||||
        "wizard",
 | 
			
		||||
        help="A helpful setup wizard that will guide "
 | 
			
		||||
        "you through setting up esphome.",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    subparsers.add_parser('mqtt-fingerprint', help="Get the SSL fingerprint from a MQTT broker.")
 | 
			
		||||
    subparsers.add_parser(
 | 
			
		||||
        "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    subparsers.add_parser('version', help="Print the esphome version and exit.")
 | 
			
		||||
    subparsers.add_parser("version", help="Print the esphome version and exit.")
 | 
			
		||||
 | 
			
		||||
    subparsers.add_parser('clean', help="Delete all temporary build files.")
 | 
			
		||||
    subparsers.add_parser("clean", help="Delete all temporary build files.")
 | 
			
		||||
 | 
			
		||||
    dashboard = subparsers.add_parser('dashboard',
 | 
			
		||||
                                      help="Create a simple web server for a dashboard.")
 | 
			
		||||
    dashboard.add_argument("--port", help="The HTTP port to open connections on. Defaults to 6052.",
 | 
			
		||||
                           type=int, default=6052)
 | 
			
		||||
    dashboard.add_argument("--username", help="The optional username to require "
 | 
			
		||||
                                              "for authentication.",
 | 
			
		||||
                           type=str, default='')
 | 
			
		||||
    dashboard.add_argument("--password", help="The optional password to require "
 | 
			
		||||
                                              "for authentication.",
 | 
			
		||||
                           type=str, default='')
 | 
			
		||||
    dashboard.add_argument("--open-ui", help="Open the dashboard UI in a browser.",
 | 
			
		||||
                           action='store_true')
 | 
			
		||||
    dashboard.add_argument("--hassio",
 | 
			
		||||
                           help=argparse.SUPPRESS,
 | 
			
		||||
                           action="store_true")
 | 
			
		||||
    dashboard.add_argument("--socket",
 | 
			
		||||
                           help="Make the dashboard serve under a unix socket", type=str)
 | 
			
		||||
    dashboard = subparsers.add_parser(
 | 
			
		||||
        "dashboard", help="Create a simple web server for a dashboard."
 | 
			
		||||
    )
 | 
			
		||||
    dashboard.add_argument(
 | 
			
		||||
        "--port",
 | 
			
		||||
        help="The HTTP port to open connections on. Defaults to 6052.",
 | 
			
		||||
        type=int,
 | 
			
		||||
        default=6052,
 | 
			
		||||
    )
 | 
			
		||||
    dashboard.add_argument(
 | 
			
		||||
        "--username",
 | 
			
		||||
        help="The optional username to require " "for authentication.",
 | 
			
		||||
        type=str,
 | 
			
		||||
        default="",
 | 
			
		||||
    )
 | 
			
		||||
    dashboard.add_argument(
 | 
			
		||||
        "--password",
 | 
			
		||||
        help="The optional password to require " "for authentication.",
 | 
			
		||||
        type=str,
 | 
			
		||||
        default="",
 | 
			
		||||
    )
 | 
			
		||||
    dashboard.add_argument(
 | 
			
		||||
        "--open-ui", help="Open the dashboard UI in a browser.", action="store_true"
 | 
			
		||||
    )
 | 
			
		||||
    dashboard.add_argument("--hassio", help=argparse.SUPPRESS, action="store_true")
 | 
			
		||||
    dashboard.add_argument(
 | 
			
		||||
        "--socket", help="Make the dashboard serve under a unix socket", type=str
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
 | 
			
		||||
    vscode.add_argument('--ace', action='store_true')
 | 
			
		||||
    vscode = subparsers.add_parser("vscode", help=argparse.SUPPRESS)
 | 
			
		||||
    vscode.add_argument("--ace", action="store_true")
 | 
			
		||||
 | 
			
		||||
    subparsers.add_parser('update-all', help=argparse.SUPPRESS)
 | 
			
		||||
    subparsers.add_parser("update-all", help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
    return parser.parse_args(argv[1:])
 | 
			
		||||
 | 
			
		||||
@@ -509,13 +595,15 @@ def run_esphome(argv):
 | 
			
		||||
    CORE.dashboard = args.dashboard
 | 
			
		||||
 | 
			
		||||
    setup_log(args.verbose, args.quiet)
 | 
			
		||||
    if args.command != 'version' and not args.configuration:
 | 
			
		||||
    if args.command != "version" and not args.configuration:
 | 
			
		||||
        _LOGGER.error("Missing configuration parameter, see esphome --help.")
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    if sys.version_info < (3, 6, 0):
 | 
			
		||||
        _LOGGER.error("You're running ESPHome with Python <3.6. ESPHome is no longer compatible "
 | 
			
		||||
                      "with this Python version. Please reinstall ESPHome with Python 3.6+")
 | 
			
		||||
        _LOGGER.error(
 | 
			
		||||
            "You're running ESPHome with Python <3.6. ESPHome is no longer compatible "
 | 
			
		||||
            "with this Python version. Please reinstall ESPHome with Python 3.6+"
 | 
			
		||||
        )
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
    if args.command in PRE_CONFIG_ACTIONS:
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -13,7 +13,8 @@ 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, color
 | 
			
		||||
from esphome.helpers import resolve_ip_address, indent
 | 
			
		||||
from esphome.log import color, Fore
 | 
			
		||||
from esphome.util import safe_print
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
@@ -177,11 +178,15 @@ class APIClient(threading.Thread):
 | 
			
		||||
        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)
 | 
			
		||||
            _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)
 | 
			
		||||
@@ -198,14 +203,19 @@ class APIClient(threading.Thread):
 | 
			
		||||
        self._socket_open_event.set()
 | 
			
		||||
 | 
			
		||||
        hello = pb.HelloRequest()
 | 
			
		||||
        hello.client_info = f'ESPHome v{const.__version__}'
 | 
			
		||||
        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)
 | 
			
		||||
        _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:
 | 
			
		||||
@@ -270,7 +280,9 @@ class APIClient(threading.Thread):
 | 
			
		||||
        req += encoded
 | 
			
		||||
        self._write(req)
 | 
			
		||||
 | 
			
		||||
    def _send_message_await_response_complex(self, send_msg, do_append, do_stop, timeout=5):
 | 
			
		||||
    def _send_message_await_response_complex(
 | 
			
		||||
        self, send_msg, do_append, do_stop, timeout=5
 | 
			
		||||
    ):
 | 
			
		||||
        event = threading.Event()
 | 
			
		||||
        responses = []
 | 
			
		||||
 | 
			
		||||
@@ -295,12 +307,15 @@ class APIClient(threading.Thread):
 | 
			
		||||
        def is_response(msg):
 | 
			
		||||
            return isinstance(msg, response_type)
 | 
			
		||||
 | 
			
		||||
        return self._send_message_await_response_complex(send_msg, is_response, is_response,
 | 
			
		||||
                                                         timeout)[0]
 | 
			
		||||
        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)
 | 
			
		||||
        return self._send_message_await_response(
 | 
			
		||||
            pb.DeviceInfoRequest(), pb.DeviceInfoResponse
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def ping(self):
 | 
			
		||||
        self._check_connected()
 | 
			
		||||
@@ -310,7 +325,9 @@ class APIClient(threading.Thread):
 | 
			
		||||
        self._check_connected()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._send_message_await_response(pb.DisconnectRequest(), pb.DisconnectResponse)
 | 
			
		||||
            self._send_message_await_response(
 | 
			
		||||
                pb.DisconnectRequest(), pb.DisconnectResponse
 | 
			
		||||
            )
 | 
			
		||||
        except APIConnectionError:
 | 
			
		||||
            pass
 | 
			
		||||
        self._close_socket()
 | 
			
		||||
@@ -346,12 +363,12 @@ class APIClient(threading.Thread):
 | 
			
		||||
                raise APIConnectionError("No socket!")
 | 
			
		||||
            try:
 | 
			
		||||
                val = self._socket.recv(amount - len(ret))
 | 
			
		||||
            except AttributeError:
 | 
			
		||||
                raise APIConnectionError("Socket was closed")
 | 
			
		||||
            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}")
 | 
			
		||||
                raise APIConnectionError(f"Error while receiving data: {err}") from err
 | 
			
		||||
            ret += val
 | 
			
		||||
        return ret
 | 
			
		||||
 | 
			
		||||
@@ -415,7 +432,7 @@ class APIClient(threading.Thread):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run_logs(config, address):
 | 
			
		||||
    conf = config['api']
 | 
			
		||||
    conf = config["api"]
 | 
			
		||||
    port = conf[CONF_PORT]
 | 
			
		||||
    password = conf[CONF_PASSWORD]
 | 
			
		||||
    _LOGGER.info("Starting log output from %s using esphome API", address)
 | 
			
		||||
@@ -447,24 +464,35 @@ def run_logs(config, address):
 | 
			
		||||
            _LOGGER.info("Successfully connected to %s", address)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        wait_time = int(min(1.5**min(tries, 100), 30))
 | 
			
		||||
        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)
 | 
			
		||||
            _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))
 | 
			
		||||
            _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]')
 | 
			
		||||
        time_ = datetime.now().time().strftime("[%H:%M:%S]")
 | 
			
		||||
        text = msg.message
 | 
			
		||||
        if msg.send_failed:
 | 
			
		||||
            text = color('white', '(Message skipped because it was too big to fit in '
 | 
			
		||||
                                  'TCP buffer - This is only cosmetic)')
 | 
			
		||||
            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():
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,17 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_AUTOMATION_ID, CONF_CONDITION, CONF_ELSE, CONF_ID, CONF_THEN, \
 | 
			
		||||
    CONF_TRIGGER_ID, CONF_TYPE_ID, CONF_TIME
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_AUTOMATION_ID,
 | 
			
		||||
    CONF_CONDITION,
 | 
			
		||||
    CONF_ELSE,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_THEN,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_TYPE_ID,
 | 
			
		||||
    CONF_TIME,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import coroutine
 | 
			
		||||
from esphome.jsonschema import jschema_extractor
 | 
			
		||||
from esphome.util import Registry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -13,7 +22,12 @@ def maybe_simple_id(*validators):
 | 
			
		||||
def maybe_conf(conf, *validators):
 | 
			
		||||
    validator = cv.All(*validators)
 | 
			
		||||
 | 
			
		||||
    @jschema_extractor("maybe")
 | 
			
		||||
    def validate(value):
 | 
			
		||||
        # pylint: disable=comparison-with-callable
 | 
			
		||||
        if value == jschema_extractor:
 | 
			
		||||
            return validator
 | 
			
		||||
 | 
			
		||||
        if isinstance(value, dict):
 | 
			
		||||
            return validator(value)
 | 
			
		||||
        with cv.remove_prepend_path([conf]):
 | 
			
		||||
@@ -30,36 +44,34 @@ def register_condition(name, condition_type, schema):
 | 
			
		||||
    return CONDITION_REGISTRY.register(name, condition_type, schema)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Action = cg.esphome_ns.class_('Action')
 | 
			
		||||
Trigger = cg.esphome_ns.class_('Trigger')
 | 
			
		||||
Action = cg.esphome_ns.class_("Action")
 | 
			
		||||
Trigger = cg.esphome_ns.class_("Trigger")
 | 
			
		||||
ACTION_REGISTRY = Registry()
 | 
			
		||||
Condition = cg.esphome_ns.class_('Condition')
 | 
			
		||||
Condition = cg.esphome_ns.class_("Condition")
 | 
			
		||||
CONDITION_REGISTRY = Registry()
 | 
			
		||||
validate_action = cv.validate_registry_entry('action', ACTION_REGISTRY)
 | 
			
		||||
validate_action_list = cv.validate_registry('action', ACTION_REGISTRY)
 | 
			
		||||
validate_condition = cv.validate_registry_entry('condition', CONDITION_REGISTRY)
 | 
			
		||||
validate_condition_list = cv.validate_registry('condition', CONDITION_REGISTRY)
 | 
			
		||||
validate_action = cv.validate_registry_entry("action", ACTION_REGISTRY)
 | 
			
		||||
validate_action_list = cv.validate_registry("action", ACTION_REGISTRY)
 | 
			
		||||
validate_condition = cv.validate_registry_entry("condition", CONDITION_REGISTRY)
 | 
			
		||||
validate_condition_list = cv.validate_registry("condition", CONDITION_REGISTRY)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_potentially_and_condition(value):
 | 
			
		||||
    if isinstance(value, list):
 | 
			
		||||
        with cv.remove_prepend_path(['and']):
 | 
			
		||||
            return validate_condition({
 | 
			
		||||
                'and': value
 | 
			
		||||
            })
 | 
			
		||||
        with cv.remove_prepend_path(["and"]):
 | 
			
		||||
            return validate_condition({"and": value})
 | 
			
		||||
    return validate_condition(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DelayAction = cg.esphome_ns.class_('DelayAction', Action, cg.Component)
 | 
			
		||||
LambdaAction = cg.esphome_ns.class_('LambdaAction', Action)
 | 
			
		||||
IfAction = cg.esphome_ns.class_('IfAction', Action)
 | 
			
		||||
WhileAction = cg.esphome_ns.class_('WhileAction', Action)
 | 
			
		||||
WaitUntilAction = cg.esphome_ns.class_('WaitUntilAction', Action, cg.Component)
 | 
			
		||||
UpdateComponentAction = cg.esphome_ns.class_('UpdateComponentAction', Action)
 | 
			
		||||
Automation = cg.esphome_ns.class_('Automation')
 | 
			
		||||
DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
 | 
			
		||||
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
 | 
			
		||||
IfAction = cg.esphome_ns.class_("IfAction", Action)
 | 
			
		||||
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
 | 
			
		||||
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
 | 
			
		||||
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
 | 
			
		||||
Automation = cg.esphome_ns.class_("Automation")
 | 
			
		||||
 | 
			
		||||
LambdaCondition = cg.esphome_ns.class_('LambdaCondition', Condition)
 | 
			
		||||
ForCondition = cg.esphome_ns.class_('ForCondition', Condition, cg.Component)
 | 
			
		||||
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
 | 
			
		||||
ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_automation(extra_schema=None, extra_validators=None, single=False):
 | 
			
		||||
@@ -83,9 +95,10 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
 | 
			
		||||
                try:
 | 
			
		||||
                    return cv.Schema([schema])(value)
 | 
			
		||||
                except cv.Invalid as err2:
 | 
			
		||||
                    if 'extra keys not allowed' in str(err2) and len(err2.path) == 2:
 | 
			
		||||
                    if "extra keys not allowed" in str(err2) and len(err2.path) == 2:
 | 
			
		||||
                        # pylint: disable=raise-missing-from
 | 
			
		||||
                        raise err
 | 
			
		||||
                    if 'Unable to find action' in str(err):
 | 
			
		||||
                    if "Unable to find action" in str(err):
 | 
			
		||||
                        raise err2
 | 
			
		||||
                    raise cv.MultipleInvalid([err, err2])
 | 
			
		||||
        elif isinstance(value, dict):
 | 
			
		||||
@@ -96,7 +109,13 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
 | 
			
		||||
        # This should only happen with invalid configs, but let's have a nice error message.
 | 
			
		||||
        return [schema(value)]
 | 
			
		||||
 | 
			
		||||
    @jschema_extractor("automation")
 | 
			
		||||
    def validator(value):
 | 
			
		||||
        # hack to get the schema
 | 
			
		||||
        # pylint: disable=comparison-with-callable
 | 
			
		||||
        if value == jschema_extractor:
 | 
			
		||||
            return schema
 | 
			
		||||
 | 
			
		||||
        value = validator_(value)
 | 
			
		||||
        if extra_validators is not None:
 | 
			
		||||
            value = cv.Schema([extra_validators])(value)
 | 
			
		||||
@@ -109,47 +128,59 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
 | 
			
		||||
    return validator
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
AUTOMATION_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger),
 | 
			
		||||
    cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation),
 | 
			
		||||
    cv.Required(CONF_THEN): validate_action_list,
 | 
			
		||||
})
 | 
			
		||||
AUTOMATION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(Trigger),
 | 
			
		||||
        cv.GenerateID(CONF_AUTOMATION_ID): cv.declare_id(Automation),
 | 
			
		||||
        cv.Required(CONF_THEN): validate_action_list,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
AndCondition = cg.esphome_ns.class_('AndCondition', Condition)
 | 
			
		||||
OrCondition = cg.esphome_ns.class_('OrCondition', Condition)
 | 
			
		||||
NotCondition = cg.esphome_ns.class_('NotCondition', Condition)
 | 
			
		||||
AndCondition = cg.esphome_ns.class_("AndCondition", Condition)
 | 
			
		||||
OrCondition = cg.esphome_ns.class_("OrCondition", Condition)
 | 
			
		||||
NotCondition = cg.esphome_ns.class_("NotCondition", Condition)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_condition('and', AndCondition, validate_condition_list)
 | 
			
		||||
@register_condition("and", AndCondition, validate_condition_list)
 | 
			
		||||
def and_condition_to_code(config, condition_id, template_arg, args):
 | 
			
		||||
    conditions = yield build_condition_list(config, template_arg, args)
 | 
			
		||||
    yield cg.new_Pvariable(condition_id, template_arg, conditions)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_condition('or', OrCondition, validate_condition_list)
 | 
			
		||||
@register_condition("or", OrCondition, validate_condition_list)
 | 
			
		||||
def or_condition_to_code(config, condition_id, template_arg, args):
 | 
			
		||||
    conditions = yield build_condition_list(config, template_arg, args)
 | 
			
		||||
    yield cg.new_Pvariable(condition_id, template_arg, conditions)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_condition('not', NotCondition, validate_potentially_and_condition)
 | 
			
		||||
@register_condition("not", NotCondition, validate_potentially_and_condition)
 | 
			
		||||
def not_condition_to_code(config, condition_id, template_arg, args):
 | 
			
		||||
    condition = yield build_condition(config, template_arg, args)
 | 
			
		||||
    yield cg.new_Pvariable(condition_id, template_arg, condition)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_condition('lambda', LambdaCondition, cv.lambda_)
 | 
			
		||||
@register_condition("lambda", LambdaCondition, cv.lambda_)
 | 
			
		||||
def lambda_condition_to_code(config, condition_id, template_arg, args):
 | 
			
		||||
    lambda_ = yield cg.process_lambda(config, args, return_type=bool)
 | 
			
		||||
    yield cg.new_Pvariable(condition_id, template_arg, lambda_)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_condition('for', ForCondition, cv.Schema({
 | 
			
		||||
    cv.Required(CONF_TIME): cv.templatable(cv.positive_time_period_milliseconds),
 | 
			
		||||
    cv.Required(CONF_CONDITION): validate_potentially_and_condition,
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA))
 | 
			
		||||
@register_condition(
 | 
			
		||||
    "for",
 | 
			
		||||
    ForCondition,
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_TIME): cv.templatable(
 | 
			
		||||
                cv.positive_time_period_milliseconds
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Required(CONF_CONDITION): validate_potentially_and_condition,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
)
 | 
			
		||||
def for_condition_to_code(config, condition_id, template_arg, args):
 | 
			
		||||
    condition = yield build_condition(config[CONF_CONDITION], cg.TemplateArguments(), [])
 | 
			
		||||
    condition = yield build_condition(
 | 
			
		||||
        config[CONF_CONDITION], cg.TemplateArguments(), []
 | 
			
		||||
    )
 | 
			
		||||
    var = cg.new_Pvariable(condition_id, template_arg, condition)
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    templ = yield cg.templatable(config[CONF_TIME], args, cg.uint32)
 | 
			
		||||
@@ -157,7 +188,9 @@ def for_condition_to_code(config, condition_id, template_arg, args):
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_action('delay', DelayAction, cv.templatable(cv.positive_time_period_milliseconds))
 | 
			
		||||
@register_action(
 | 
			
		||||
    "delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
 | 
			
		||||
)
 | 
			
		||||
def delay_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg)
 | 
			
		||||
    yield cg.register_component(var, {})
 | 
			
		||||
@@ -166,11 +199,18 @@ def delay_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_action('if', IfAction, cv.All({
 | 
			
		||||
    cv.Required(CONF_CONDITION): validate_potentially_and_condition,
 | 
			
		||||
    cv.Optional(CONF_THEN): validate_action_list,
 | 
			
		||||
    cv.Optional(CONF_ELSE): validate_action_list,
 | 
			
		||||
}, cv.has_at_least_one_key(CONF_THEN, CONF_ELSE)))
 | 
			
		||||
@register_action(
 | 
			
		||||
    "if",
 | 
			
		||||
    IfAction,
 | 
			
		||||
    cv.All(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_CONDITION): validate_potentially_and_condition,
 | 
			
		||||
            cv.Optional(CONF_THEN): validate_action_list,
 | 
			
		||||
            cv.Optional(CONF_ELSE): validate_action_list,
 | 
			
		||||
        },
 | 
			
		||||
        cv.has_at_least_one_key(CONF_THEN, CONF_ELSE),
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
def if_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, conditions)
 | 
			
		||||
@@ -183,10 +223,16 @@ def if_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_action('while', WhileAction, cv.Schema({
 | 
			
		||||
    cv.Required(CONF_CONDITION): validate_potentially_and_condition,
 | 
			
		||||
    cv.Required(CONF_THEN): validate_action_list,
 | 
			
		||||
}))
 | 
			
		||||
@register_action(
 | 
			
		||||
    "while",
 | 
			
		||||
    WhileAction,
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_CONDITION): validate_potentially_and_condition,
 | 
			
		||||
            cv.Required(CONF_THEN): validate_action_list,
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
def while_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, conditions)
 | 
			
		||||
@@ -196,15 +242,17 @@ def while_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_wait_until(value):
 | 
			
		||||
    schema = cv.Schema({
 | 
			
		||||
        cv.Required(CONF_CONDITION): validate_potentially_and_condition,
 | 
			
		||||
    })
 | 
			
		||||
    schema = cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_CONDITION): validate_potentially_and_condition,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    if isinstance(value, dict) and CONF_CONDITION in value:
 | 
			
		||||
        return schema(value)
 | 
			
		||||
    return validate_wait_until({CONF_CONDITION: value})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_action('wait_until', WaitUntilAction, validate_wait_until)
 | 
			
		||||
@register_action("wait_until", WaitUntilAction, validate_wait_until)
 | 
			
		||||
def wait_until_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    conditions = yield build_condition(config[CONF_CONDITION], template_arg, args)
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, conditions)
 | 
			
		||||
@@ -212,15 +260,21 @@ def wait_until_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_action('lambda', LambdaAction, cv.lambda_)
 | 
			
		||||
@register_action("lambda", LambdaAction, cv.lambda_)
 | 
			
		||||
def lambda_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    lambda_ = yield cg.process_lambda(config, args, return_type=cg.void)
 | 
			
		||||
    yield cg.new_Pvariable(action_id, template_arg, lambda_)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_action('component.update', UpdateComponentAction, maybe_simple_id({
 | 
			
		||||
    cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
 | 
			
		||||
}))
 | 
			
		||||
@register_action(
 | 
			
		||||
    "component.update",
 | 
			
		||||
    UpdateComponentAction,
 | 
			
		||||
    maybe_simple_id(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
def component_update_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    comp = yield cg.get_variable(config[CONF_ID])
 | 
			
		||||
    yield cg.new_Pvariable(action_id, template_arg, comp)
 | 
			
		||||
@@ -228,7 +282,9 @@ def component_update_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
 | 
			
		||||
@coroutine
 | 
			
		||||
def build_action(full_config, template_arg, args):
 | 
			
		||||
    registry_entry, config = cg.extract_registry_entry_config(ACTION_REGISTRY, full_config)
 | 
			
		||||
    registry_entry, config = cg.extract_registry_entry_config(
 | 
			
		||||
        ACTION_REGISTRY, full_config
 | 
			
		||||
    )
 | 
			
		||||
    action_id = full_config[CONF_TYPE_ID]
 | 
			
		||||
    builder = registry_entry.coroutine_fun
 | 
			
		||||
    yield builder(config, action_id, template_arg, args)
 | 
			
		||||
@@ -245,7 +301,9 @@ def build_action_list(config, templ, arg_type):
 | 
			
		||||
 | 
			
		||||
@coroutine
 | 
			
		||||
def build_condition(full_config, template_arg, args):
 | 
			
		||||
    registry_entry, config = cg.extract_registry_entry_config(CONDITION_REGISTRY, full_config)
 | 
			
		||||
    registry_entry, config = cg.extract_registry_entry_config(
 | 
			
		||||
        CONDITION_REGISTRY, full_config
 | 
			
		||||
    )
 | 
			
		||||
    action_id = full_config[CONF_TYPE_ID]
 | 
			
		||||
    builder = registry_entry.coroutine_fun
 | 
			
		||||
    yield builder(config, action_id, template_arg, args)
 | 
			
		||||
 
 | 
			
		||||
@@ -9,18 +9,71 @@
 | 
			
		||||
 | 
			
		||||
# pylint: disable=unused-import
 | 
			
		||||
from esphome.cpp_generator import (  # noqa
 | 
			
		||||
    Expression, RawExpression, RawStatement, TemplateArguments,
 | 
			
		||||
    StructInitializer, ArrayInitializer, safe_exp, Statement, LineComment,
 | 
			
		||||
    progmem_array, statement, variable, Pvariable, new_Pvariable,
 | 
			
		||||
    add, add_global, add_library, add_build_flag, add_define,
 | 
			
		||||
    get_variable, get_variable_with_full_id, process_lambda, is_template, templatable, MockObj,
 | 
			
		||||
    MockObjClass)
 | 
			
		||||
    Expression,
 | 
			
		||||
    RawExpression,
 | 
			
		||||
    RawStatement,
 | 
			
		||||
    TemplateArguments,
 | 
			
		||||
    StructInitializer,
 | 
			
		||||
    ArrayInitializer,
 | 
			
		||||
    safe_exp,
 | 
			
		||||
    Statement,
 | 
			
		||||
    LineComment,
 | 
			
		||||
    progmem_array,
 | 
			
		||||
    statement,
 | 
			
		||||
    variable,
 | 
			
		||||
    new_variable,
 | 
			
		||||
    Pvariable,
 | 
			
		||||
    new_Pvariable,
 | 
			
		||||
    add,
 | 
			
		||||
    add_global,
 | 
			
		||||
    add_library,
 | 
			
		||||
    add_build_flag,
 | 
			
		||||
    add_define,
 | 
			
		||||
    get_variable,
 | 
			
		||||
    get_variable_with_full_id,
 | 
			
		||||
    process_lambda,
 | 
			
		||||
    is_template,
 | 
			
		||||
    templatable,
 | 
			
		||||
    MockObj,
 | 
			
		||||
    MockObjClass,
 | 
			
		||||
)
 | 
			
		||||
from esphome.cpp_helpers import (  # noqa
 | 
			
		||||
    gpio_pin_expression, register_component, build_registry_entry,
 | 
			
		||||
    build_registry_list, extract_registry_entry_config, register_parented)
 | 
			
		||||
    gpio_pin_expression,
 | 
			
		||||
    register_component,
 | 
			
		||||
    build_registry_entry,
 | 
			
		||||
    build_registry_list,
 | 
			
		||||
    extract_registry_entry_config,
 | 
			
		||||
    register_parented,
 | 
			
		||||
)
 | 
			
		||||
from esphome.cpp_types import (  # noqa
 | 
			
		||||
    global_ns, void, nullptr, float_, double, bool_, int_, std_ns, std_string,
 | 
			
		||||
    std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN,
 | 
			
		||||
    esphome_ns, App, Nameable, Component, ComponentPtr,
 | 
			
		||||
    PollingComponent, Application, optional, arduino_json_ns, JsonObject,
 | 
			
		||||
    JsonObjectRef, JsonObjectConstRef, Controller, GPIOPin)
 | 
			
		||||
    global_ns,
 | 
			
		||||
    void,
 | 
			
		||||
    nullptr,
 | 
			
		||||
    float_,
 | 
			
		||||
    double,
 | 
			
		||||
    bool_,
 | 
			
		||||
    int_,
 | 
			
		||||
    std_ns,
 | 
			
		||||
    std_string,
 | 
			
		||||
    std_vector,
 | 
			
		||||
    uint8,
 | 
			
		||||
    uint16,
 | 
			
		||||
    uint32,
 | 
			
		||||
    int32,
 | 
			
		||||
    const_char_ptr,
 | 
			
		||||
    NAN,
 | 
			
		||||
    esphome_ns,
 | 
			
		||||
    App,
 | 
			
		||||
    Nameable,
 | 
			
		||||
    Component,
 | 
			
		||||
    ComponentPtr,
 | 
			
		||||
    PollingComponent,
 | 
			
		||||
    Application,
 | 
			
		||||
    optional,
 | 
			
		||||
    arduino_json_ns,
 | 
			
		||||
    JsonObject,
 | 
			
		||||
    JsonObjectRef,
 | 
			
		||||
    JsonObjectConstRef,
 | 
			
		||||
    Controller,
 | 
			
		||||
    GPIOPin,
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ void A4988::setup() {
 | 
			
		||||
  if (this->sleep_pin_ != nullptr) {
 | 
			
		||||
    this->sleep_pin_->setup();
 | 
			
		||||
    this->sleep_pin_->digital_write(false);
 | 
			
		||||
    this->sleep_pin_state_ = false;
 | 
			
		||||
  }
 | 
			
		||||
  this->step_pin_->setup();
 | 
			
		||||
  this->step_pin_->digital_write(false);
 | 
			
		||||
@@ -27,7 +28,12 @@ void A4988::dump_config() {
 | 
			
		||||
void A4988::loop() {
 | 
			
		||||
  bool at_target = this->has_reached_target();
 | 
			
		||||
  if (this->sleep_pin_ != nullptr) {
 | 
			
		||||
    bool sleep_rising_edge = !sleep_pin_state_ & !at_target;
 | 
			
		||||
    this->sleep_pin_->digital_write(!at_target);
 | 
			
		||||
    this->sleep_pin_state_ = !at_target;
 | 
			
		||||
    if (sleep_rising_edge) {
 | 
			
		||||
      delayMicroseconds(1000);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (at_target) {
 | 
			
		||||
    this->high_freq_.stop();
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ class A4988 : public stepper::Stepper, public Component {
 | 
			
		||||
  GPIOPin *step_pin_;
 | 
			
		||||
  GPIOPin *dir_pin_;
 | 
			
		||||
  GPIOPin *sleep_pin_{nullptr};
 | 
			
		||||
  bool sleep_pin_state_;
 | 
			
		||||
  HighFrequencyLoopRequester high_freq_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,15 +5,17 @@ import esphome.codegen as cg
 | 
			
		||||
from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
a4988_ns = cg.esphome_ns.namespace('a4988')
 | 
			
		||||
A4988 = a4988_ns.class_('A4988', stepper.Stepper, cg.Component)
 | 
			
		||||
a4988_ns = cg.esphome_ns.namespace("a4988")
 | 
			
		||||
A4988 = a4988_ns.class_("A4988", stepper.Stepper, cg.Component)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend({
 | 
			
		||||
    cv.Required(CONF_ID): cv.declare_id(A4988),
 | 
			
		||||
    cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
    cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
    cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.declare_id(A4988),
 | 
			
		||||
        cv.Required(CONF_STEP_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
        cv.Required(CONF_DIR_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
        cv.Optional(CONF_SLEEP_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -4,28 +4,32 @@ from esphome import pins
 | 
			
		||||
from esphome.components import output
 | 
			
		||||
from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ['@glmnet']
 | 
			
		||||
CODEOWNERS = ["@glmnet"]
 | 
			
		||||
 | 
			
		||||
ac_dimmer_ns = cg.esphome_ns.namespace('ac_dimmer')
 | 
			
		||||
AcDimmer = ac_dimmer_ns.class_('AcDimmer', output.FloatOutput, cg.Component)
 | 
			
		||||
ac_dimmer_ns = cg.esphome_ns.namespace("ac_dimmer")
 | 
			
		||||
AcDimmer = ac_dimmer_ns.class_("AcDimmer", output.FloatOutput, cg.Component)
 | 
			
		||||
 | 
			
		||||
DimMethod = ac_dimmer_ns.enum('DimMethod')
 | 
			
		||||
DimMethod = ac_dimmer_ns.enum("DimMethod")
 | 
			
		||||
DIM_METHODS = {
 | 
			
		||||
    'LEADING_PULSE': DimMethod.DIM_METHOD_LEADING_PULSE,
 | 
			
		||||
    'LEADING': DimMethod.DIM_METHOD_LEADING,
 | 
			
		||||
    'TRAILING': DimMethod.DIM_METHOD_TRAILING,
 | 
			
		||||
    "LEADING_PULSE": DimMethod.DIM_METHOD_LEADING_PULSE,
 | 
			
		||||
    "LEADING": DimMethod.DIM_METHOD_LEADING,
 | 
			
		||||
    "TRAILING": DimMethod.DIM_METHOD_TRAILING,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONF_GATE_PIN = 'gate_pin'
 | 
			
		||||
CONF_ZERO_CROSS_PIN = 'zero_cross_pin'
 | 
			
		||||
CONF_INIT_WITH_HALF_CYCLE = 'init_with_half_cycle'
 | 
			
		||||
CONFIG_SCHEMA = 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_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
 | 
			
		||||
    cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
 | 
			
		||||
    cv.Optional(CONF_METHOD, default='leading pulse'): cv.enum(DIM_METHODS, upper=True, space='_'),
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
CONF_GATE_PIN = "gate_pin"
 | 
			
		||||
CONF_ZERO_CROSS_PIN = "zero_cross_pin"
 | 
			
		||||
CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle"
 | 
			
		||||
CONFIG_SCHEMA = 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_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
 | 
			
		||||
        cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum(
 | 
			
		||||
            DIM_METHODS, upper=True, space="_"
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -5,18 +5,22 @@ from esphome.components.light.types import AddressableLightEffect
 | 
			
		||||
from esphome.components.light.effects import register_addressable_effect
 | 
			
		||||
from esphome.const import CONF_NAME, CONF_UART_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['uart']
 | 
			
		||||
DEPENDENCIES = ["uart"]
 | 
			
		||||
 | 
			
		||||
adalight_ns = cg.esphome_ns.namespace('adalight')
 | 
			
		||||
adalight_ns = cg.esphome_ns.namespace("adalight")
 | 
			
		||||
AdalightLightEffect = adalight_ns.class_(
 | 
			
		||||
    'AdalightLightEffect', uart.UARTDevice, AddressableLightEffect)
 | 
			
		||||
    "AdalightLightEffect", uart.UARTDevice, AddressableLightEffect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_addressable_effect('adalight', AdalightLightEffect, "Adalight", {
 | 
			
		||||
    cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)
 | 
			
		||||
})
 | 
			
		||||
@register_addressable_effect(
 | 
			
		||||
    "adalight",
 | 
			
		||||
    AdalightLightEffect,
 | 
			
		||||
    "Adalight",
 | 
			
		||||
    {cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)},
 | 
			
		||||
)
 | 
			
		||||
def adalight_light_effect_to_code(config, effect_id):
 | 
			
		||||
    effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
 | 
			
		||||
    yield uart.register_uart_device(effect, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -42,11 +42,11 @@ void AdalightLightEffect::reset_frame_(light::AddressableLight &it) {
 | 
			
		||||
 | 
			
		||||
void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) {
 | 
			
		||||
  for (int led = it.size(); led-- > 0;) {
 | 
			
		||||
    it[led].set(light::ESPColor::BLACK);
 | 
			
		||||
    it[led].set(COLOR_BLACK);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AdalightLightEffect::apply(light::AddressableLight &it, const light::ESPColor ¤t_color) {
 | 
			
		||||
void AdalightLightEffect::apply(light::AddressableLight &it, const Color ¤t_color) {
 | 
			
		||||
  const uint32_t now = millis();
 | 
			
		||||
 | 
			
		||||
  if (now - this->last_ack_ >= ADALIGHT_ACK_INTERVAL) {
 | 
			
		||||
@@ -130,7 +130,7 @@ AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableL
 | 
			
		||||
  for (int led = 0; led < accepted_led_count; led++, led_data += 3) {
 | 
			
		||||
    auto white = std::min(std::min(led_data[0], led_data[1]), led_data[2]);
 | 
			
		||||
 | 
			
		||||
    it[led].set(light::ESPColor(led_data[0], led_data[1], led_data[2], white));
 | 
			
		||||
    it[led].set(Color(led_data[0], led_data[1], led_data[2], white));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return CONSUMED;
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
 | 
			
		||||
 public:
 | 
			
		||||
  void start() override;
 | 
			
		||||
  void stop() override;
 | 
			
		||||
  void apply(light::AddressableLight &it, const light::ESPColor ¤t_color) override;
 | 
			
		||||
  void apply(light::AddressableLight &it, const Color ¤t_color) override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  enum Frame {
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
CODEOWNERS = ['@esphome/core']
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,9 @@ void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuati
 | 
			
		||||
 | 
			
		||||
void ADCSensor::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
 | 
			
		||||
#ifndef USE_ADC_SENSOR_VCC
 | 
			
		||||
  GPIOPin(this->pin_, INPUT).setup();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
  analogSetPinAttenuation(this->pin_, this->attenuation_);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,36 +2,51 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import sensor, voltage_sampler
 | 
			
		||||
from esphome.const import CONF_ATTENUATION, CONF_ID, CONF_PIN, ICON_FLASH, UNIT_VOLT
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ATTENUATION,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_PIN,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ['voltage_sampler']
 | 
			
		||||
AUTO_LOAD = ["voltage_sampler"]
 | 
			
		||||
 | 
			
		||||
ATTENUATION_MODES = {
 | 
			
		||||
    '0db': cg.global_ns.ADC_0db,
 | 
			
		||||
    '2.5db': cg.global_ns.ADC_2_5db,
 | 
			
		||||
    '6db': cg.global_ns.ADC_6db,
 | 
			
		||||
    '11db': cg.global_ns.ADC_11db,
 | 
			
		||||
    "0db": cg.global_ns.ADC_0db,
 | 
			
		||||
    "2.5db": cg.global_ns.ADC_2_5db,
 | 
			
		||||
    "6db": cg.global_ns.ADC_6db,
 | 
			
		||||
    "11db": cg.global_ns.ADC_11db,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_adc_pin(value):
 | 
			
		||||
    vcc = str(value).upper()
 | 
			
		||||
    if vcc == 'VCC':
 | 
			
		||||
    if vcc == "VCC":
 | 
			
		||||
        return cv.only_on_esp8266(vcc)
 | 
			
		||||
    return pins.analog_pin(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
adc_ns = cg.esphome_ns.namespace('adc')
 | 
			
		||||
ADCSensor = adc_ns.class_('ADCSensor', sensor.Sensor, cg.PollingComponent,
 | 
			
		||||
                          voltage_sampler.VoltageSampler)
 | 
			
		||||
adc_ns = cg.esphome_ns.namespace("adc")
 | 
			
		||||
ADCSensor = adc_ns.class_(
 | 
			
		||||
    "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2).extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(ADCSensor),
 | 
			
		||||
    cv.Required(CONF_PIN): validate_adc_pin,
 | 
			
		||||
    cv.SplitDefault(CONF_ATTENUATION, esp32='0db'):
 | 
			
		||||
        cv.All(cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)),
 | 
			
		||||
}).extend(cv.polling_component_schema('60s'))
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(ADCSensor),
 | 
			
		||||
            cv.Required(CONF_PIN): validate_adc_pin,
 | 
			
		||||
            cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
 | 
			
		||||
                cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
@@ -39,8 +54,8 @@ def to_code(config):
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield sensor.register_sensor(var, config)
 | 
			
		||||
 | 
			
		||||
    if config[CONF_PIN] == 'VCC':
 | 
			
		||||
        cg.add_define('USE_ADC_SENSOR_VCC')
 | 
			
		||||
    if config[CONF_PIN] == "VCC":
 | 
			
		||||
        cg.add_define("USE_ADC_SENSOR_VCC")
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_pin(config[CONF_PIN]))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								esphome/components/addressable_light/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/addressable_light/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
#include "addressable_light_display.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace addressable_light {
 | 
			
		||||
 | 
			
		||||
static const char* TAG = "addressable_light.display";
 | 
			
		||||
 | 
			
		||||
int AddressableLightDisplay::get_width_internal() { return this->width_; }
 | 
			
		||||
int AddressableLightDisplay::get_height_internal() { return this->height_; }
 | 
			
		||||
 | 
			
		||||
void AddressableLightDisplay::setup() {
 | 
			
		||||
  this->addressable_light_buffer_.resize(this->width_ * this->height_, {0, 0, 0, 0});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AddressableLightDisplay::update() {
 | 
			
		||||
  if (!this->enabled_)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  this->do_update_();
 | 
			
		||||
  this->display();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AddressableLightDisplay::display() {
 | 
			
		||||
  bool dirty = false;
 | 
			
		||||
  uint8_t old_r, old_g, old_b, old_w;
 | 
			
		||||
  Color* c;
 | 
			
		||||
 | 
			
		||||
  for (uint32_t offset = 0; offset < this->addressable_light_buffer_.size(); offset++) {
 | 
			
		||||
    c = &(this->addressable_light_buffer_[offset]);
 | 
			
		||||
 | 
			
		||||
    light::ESPColorView pixel = (*this->light_)[offset];
 | 
			
		||||
 | 
			
		||||
    // Track the original values for the pixel view. If it has changed updating, then
 | 
			
		||||
    // we trigger a redraw. Avoiding redraws == avoiding flicker!
 | 
			
		||||
    old_r = pixel.get_red();
 | 
			
		||||
    old_g = pixel.get_green();
 | 
			
		||||
    old_b = pixel.get_blue();
 | 
			
		||||
    old_w = pixel.get_white();
 | 
			
		||||
 | 
			
		||||
    pixel.set_rgbw(c->r, c->g, c->b, c->w);
 | 
			
		||||
 | 
			
		||||
    // If the actual value of the pixel changed, then schedule a redraw.
 | 
			
		||||
    if (pixel.get_red() != old_r || pixel.get_green() != old_g || pixel.get_blue() != old_b ||
 | 
			
		||||
        pixel.get_white() != old_w) {
 | 
			
		||||
      dirty = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (dirty) {
 | 
			
		||||
    this->light_->schedule_show();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HOT AddressableLightDisplay::draw_absolute_pixel_internal(int x, int y, Color color) {
 | 
			
		||||
  if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  if (this->pixel_mapper_f_.has_value()) {
 | 
			
		||||
    // Params are passed by reference, so they may be modified in call.
 | 
			
		||||
    this->addressable_light_buffer_[(*this->pixel_mapper_f_)(x, y)] = color;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->addressable_light_buffer_[y * this->get_width_internal() + x] = color;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}  // namespace addressable_light
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -0,0 +1,59 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/color.h"
 | 
			
		||||
#include "esphome/components/display/display_buffer.h"
 | 
			
		||||
#include "esphome/components/light/addressable_light.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace addressable_light {
 | 
			
		||||
 | 
			
		||||
class AddressableLightDisplay : public display::DisplayBuffer, public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  light::AddressableLight *get_light() const { return this->light_; }
 | 
			
		||||
 | 
			
		||||
  void set_width(int32_t width) { width_ = width; }
 | 
			
		||||
  void set_height(int32_t height) { height_ = height; }
 | 
			
		||||
  void set_light(light::LightState *state) {
 | 
			
		||||
    light_state_ = state;
 | 
			
		||||
    light_ = static_cast<light::AddressableLight *>(state->get_output());
 | 
			
		||||
  }
 | 
			
		||||
  void set_enabled(bool enabled) {
 | 
			
		||||
    if (light_state_) {
 | 
			
		||||
      if (enabled_ && !enabled) {  // enabled -> disabled
 | 
			
		||||
        // - Tell the parent light to refresh, effectively wiping the display. Also
 | 
			
		||||
        //   restores the previous effect (if any).
 | 
			
		||||
        light_state_->make_call().set_effect(this->last_effect_).perform();
 | 
			
		||||
 | 
			
		||||
      } else if (!enabled_ && enabled) {  // disabled -> enabled
 | 
			
		||||
        // - Save the current effect.
 | 
			
		||||
        this->last_effect_ = light_state_->get_effect_name();
 | 
			
		||||
        // - Disable any current effect.
 | 
			
		||||
        light_state_->make_call().set_effect(0).perform();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    enabled_ = enabled;
 | 
			
		||||
  }
 | 
			
		||||
  bool get_enabled() { return enabled_; }
 | 
			
		||||
 | 
			
		||||
  void set_pixel_mapper(std::function<int(int, int)> &&pixel_mapper_f) { this->pixel_mapper_f_ = pixel_mapper_f; }
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void display();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  int get_width_internal() override;
 | 
			
		||||
  int get_height_internal() override;
 | 
			
		||||
  void draw_absolute_pixel_internal(int x, int y, Color color) override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
  light::LightState *light_state_;
 | 
			
		||||
  light::AddressableLight *light_;
 | 
			
		||||
  bool enabled_{true};
 | 
			
		||||
  int32_t width_;
 | 
			
		||||
  int32_t height_;
 | 
			
		||||
  std::vector<Color> addressable_light_buffer_;
 | 
			
		||||
  optional<std::string> last_effect_;
 | 
			
		||||
  optional<std::function<int(int, int)>> pixel_mapper_f_;
 | 
			
		||||
};
 | 
			
		||||
}  // namespace addressable_light
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										63
									
								
								esphome/components/addressable_light/display.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								esphome/components/addressable_light/display.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import display, light
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_LAMBDA,
 | 
			
		||||
    CONF_PAGES,
 | 
			
		||||
    CONF_ADDRESSABLE_LIGHT_ID,
 | 
			
		||||
    CONF_HEIGHT,
 | 
			
		||||
    CONF_WIDTH,
 | 
			
		||||
    CONF_UPDATE_INTERVAL,
 | 
			
		||||
    CONF_PIXEL_MAPPER,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@justfalter"]
 | 
			
		||||
 | 
			
		||||
addressable_light_ns = cg.esphome_ns.namespace("addressable_light")
 | 
			
		||||
AddressableLightDisplay = addressable_light_ns.class_(
 | 
			
		||||
    "AddressableLightDisplay", display.DisplayBuffer, cg.PollingComponent
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    display.FULL_DISPLAY_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(AddressableLightDisplay),
 | 
			
		||||
            cv.Required(CONF_ADDRESSABLE_LIGHT_ID): cv.use_id(
 | 
			
		||||
                light.AddressableLightState
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Required(CONF_WIDTH): cv.positive_int,
 | 
			
		||||
            cv.Required(CONF_HEIGHT): cv.positive_int,
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_UPDATE_INTERVAL, default="16ms"
 | 
			
		||||
            ): cv.positive_time_period_milliseconds,
 | 
			
		||||
            cv.Optional(CONF_PIXEL_MAPPER): cv.returning_lambda,
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    wrapped_light = yield cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID])
 | 
			
		||||
    cg.add(var.set_width(config[CONF_WIDTH]))
 | 
			
		||||
    cg.add(var.set_height(config[CONF_HEIGHT]))
 | 
			
		||||
    cg.add(var.set_light(wrapped_light))
 | 
			
		||||
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield display.register_display(var, config)
 | 
			
		||||
 | 
			
		||||
    if CONF_PIXEL_MAPPER in config:
 | 
			
		||||
        pixel_mapper_template_ = yield cg.process_lambda(
 | 
			
		||||
            config[CONF_PIXEL_MAPPER],
 | 
			
		||||
            [(int, "x"), (int, "y")],
 | 
			
		||||
            return_type=cg.int_,
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_pixel_mapper(pixel_mapper_template_))
 | 
			
		||||
 | 
			
		||||
    if CONF_LAMBDA in config:
 | 
			
		||||
        lambda_ = yield cg.process_lambda(
 | 
			
		||||
            config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_writer(lambda_))
 | 
			
		||||
@@ -8,6 +8,9 @@ static const char *TAG = "ade7953";
 | 
			
		||||
 | 
			
		||||
void ADE7953::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ADE7953:");
 | 
			
		||||
  if (this->has_irq_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  IRQ Pin: GPIO%u", this->irq_pin_number_);
 | 
			
		||||
  }
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
  LOG_SENSOR("  ", "Voltage Sensor", this->voltage_sensor_);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,10 @@ namespace ade7953 {
 | 
			
		||||
 | 
			
		||||
class ADE7953 : public i2c::I2CDevice, public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_irq_pin(uint8_t irq_pin) {
 | 
			
		||||
    has_irq_ = true;
 | 
			
		||||
    irq_pin_number_ = irq_pin;
 | 
			
		||||
  }
 | 
			
		||||
  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_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; }
 | 
			
		||||
@@ -20,6 +24,11 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setup() override {
 | 
			
		||||
    if (this->has_irq_) {
 | 
			
		||||
      auto pin = GPIOPin(this->irq_pin_number_, INPUT);
 | 
			
		||||
      this->irq_pin_ = &pin;
 | 
			
		||||
      this->irq_pin_->setup();
 | 
			
		||||
    }
 | 
			
		||||
    this->set_timeout(100, [this]() {
 | 
			
		||||
      this->ade_write_<uint8_t>(0x0010, 0x04);
 | 
			
		||||
      this->ade_write_<uint8_t>(0x00FE, 0xAD);
 | 
			
		||||
@@ -55,6 +64,9 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool has_irq_ = false;
 | 
			
		||||
  uint8_t irq_pin_number_;
 | 
			
		||||
  GPIOPin *irq_pin_{nullptr};
 | 
			
		||||
  bool is_setup_{false};
 | 
			
		||||
  sensor::Sensor *voltage_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *current_a_sensor_{nullptr};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,55 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, i2c
 | 
			
		||||
from esphome.const import CONF_ID, CONF_VOLTAGE, \
 | 
			
		||||
    UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
ace7953_ns = cg.esphome_ns.namespace('ade7953')
 | 
			
		||||
ADE7953 = ace7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
ade7953_ns = cg.esphome_ns.namespace("ade7953")
 | 
			
		||||
ADE7953 = ade7953_ns.class_("ADE7953", cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
 | 
			
		||||
CONF_CURRENT_A = 'current_a'
 | 
			
		||||
CONF_CURRENT_B = 'current_b'
 | 
			
		||||
CONF_ACTIVE_POWER_A = 'active_power_a'
 | 
			
		||||
CONF_ACTIVE_POWER_B = 'active_power_b'
 | 
			
		||||
CONF_IRQ_PIN = "irq_pin"
 | 
			
		||||
CONF_CURRENT_A = "current_a"
 | 
			
		||||
CONF_CURRENT_B = "current_b"
 | 
			
		||||
CONF_ACTIVE_POWER_A = "active_power_a"
 | 
			
		||||
CONF_ACTIVE_POWER_B = "active_power_b"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(ADE7953),
 | 
			
		||||
 | 
			
		||||
    cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
 | 
			
		||||
    cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
 | 
			
		||||
    cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
 | 
			
		||||
    cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
 | 
			
		||||
    cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
 | 
			
		||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38))
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(ADE7953),
 | 
			
		||||
            cv.Optional(CONF_IRQ_PIN): pins.input_pin,
 | 
			
		||||
            cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(
 | 
			
		||||
                UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(
 | 
			
		||||
                UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(
 | 
			
		||||
                UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(
 | 
			
		||||
                UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x38))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
@@ -30,10 +57,18 @@ def to_code(config):
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield i2c.register_i2c_device(var, config)
 | 
			
		||||
 | 
			
		||||
    for key in [CONF_VOLTAGE, CONF_CURRENT_A, CONF_CURRENT_B, CONF_ACTIVE_POWER_A,
 | 
			
		||||
                CONF_ACTIVE_POWER_B]:
 | 
			
		||||
    if CONF_IRQ_PIN in config:
 | 
			
		||||
        cg.add(var.set_irq_pin(config[CONF_IRQ_PIN]))
 | 
			
		||||
 | 
			
		||||
    for key in [
 | 
			
		||||
        CONF_VOLTAGE,
 | 
			
		||||
        CONF_CURRENT_A,
 | 
			
		||||
        CONF_CURRENT_B,
 | 
			
		||||
        CONF_ACTIVE_POWER_A,
 | 
			
		||||
        CONF_ACTIVE_POWER_B,
 | 
			
		||||
    ]:
 | 
			
		||||
        if key not in config:
 | 
			
		||||
            continue
 | 
			
		||||
        conf = config[key]
 | 
			
		||||
        sens = yield sensor.new_sensor(conf)
 | 
			
		||||
        cg.add(getattr(var, f'set_{key}_sensor')(sens))
 | 
			
		||||
        cg.add(getattr(var, f"set_{key}_sensor")(sens))
 | 
			
		||||
 
 | 
			
		||||
@@ -3,18 +3,24 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
AUTO_LOAD = ['sensor', 'voltage_sampler']
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
AUTO_LOAD = ["sensor", "voltage_sampler"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
ads1115_ns = cg.esphome_ns.namespace('ads1115')
 | 
			
		||||
ADS1115Component = ads1115_ns.class_('ADS1115Component', cg.Component, i2c.I2CDevice)
 | 
			
		||||
ads1115_ns = cg.esphome_ns.namespace("ads1115")
 | 
			
		||||
ADS1115Component = ads1115_ns.class_("ADS1115Component", cg.Component, i2c.I2CDevice)
 | 
			
		||||
 | 
			
		||||
CONF_CONTINUOUS_MODE = 'continuous_mode'
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(ADS1115Component),
 | 
			
		||||
    cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean,
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(None))
 | 
			
		||||
CONF_CONTINUOUS_MODE = "continuous_mode"
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(ADS1115Component),
 | 
			
		||||
            cv.Optional(CONF_CONTINUOUS_MODE, default=False): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(i2c.i2c_device_schema(None))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,53 +1,67 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, voltage_sampler
 | 
			
		||||
from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_GAIN,
 | 
			
		||||
    CONF_MULTIPLEXER,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
)
 | 
			
		||||
from . import ads1115_ns, ADS1115Component
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['ads1115']
 | 
			
		||||
DEPENDENCIES = ["ads1115"]
 | 
			
		||||
 | 
			
		||||
ADS1115Multiplexer = ads1115_ns.enum('ADS1115Multiplexer')
 | 
			
		||||
ADS1115Multiplexer = ads1115_ns.enum("ADS1115Multiplexer")
 | 
			
		||||
MUX = {
 | 
			
		||||
    'A0_A1': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
 | 
			
		||||
    'A0_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
 | 
			
		||||
    'A1_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
 | 
			
		||||
    'A2_A3': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
 | 
			
		||||
    'A0_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
 | 
			
		||||
    'A1_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
 | 
			
		||||
    'A2_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
 | 
			
		||||
    'A3_GND': ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
 | 
			
		||||
    "A0_A1": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N1,
 | 
			
		||||
    "A0_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_N3,
 | 
			
		||||
    "A1_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_N3,
 | 
			
		||||
    "A2_A3": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_N3,
 | 
			
		||||
    "A0_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P0_NG,
 | 
			
		||||
    "A1_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P1_NG,
 | 
			
		||||
    "A2_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P2_NG,
 | 
			
		||||
    "A3_GND": ADS1115Multiplexer.ADS1115_MULTIPLEXER_P3_NG,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ADS1115Gain = ads1115_ns.enum('ADS1115Gain')
 | 
			
		||||
ADS1115Gain = ads1115_ns.enum("ADS1115Gain")
 | 
			
		||||
GAIN = {
 | 
			
		||||
    '6.144': ADS1115Gain.ADS1115_GAIN_6P144,
 | 
			
		||||
    '4.096': ADS1115Gain.ADS1115_GAIN_4P096,
 | 
			
		||||
    '2.048': ADS1115Gain.ADS1115_GAIN_2P048,
 | 
			
		||||
    '1.024': ADS1115Gain.ADS1115_GAIN_1P024,
 | 
			
		||||
    '0.512': ADS1115Gain.ADS1115_GAIN_0P512,
 | 
			
		||||
    '0.256': ADS1115Gain.ADS1115_GAIN_0P256,
 | 
			
		||||
    "6.144": ADS1115Gain.ADS1115_GAIN_6P144,
 | 
			
		||||
    "4.096": ADS1115Gain.ADS1115_GAIN_4P096,
 | 
			
		||||
    "2.048": ADS1115Gain.ADS1115_GAIN_2P048,
 | 
			
		||||
    "1.024": ADS1115Gain.ADS1115_GAIN_1P024,
 | 
			
		||||
    "0.512": ADS1115Gain.ADS1115_GAIN_0P512,
 | 
			
		||||
    "0.256": ADS1115Gain.ADS1115_GAIN_0P256,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_gain(value):
 | 
			
		||||
    if isinstance(value, float):
 | 
			
		||||
        value = f'{value:0.03f}'
 | 
			
		||||
        value = f"{value:0.03f}"
 | 
			
		||||
    elif not isinstance(value, str):
 | 
			
		||||
        raise cv.Invalid(f'invalid gain "{value}"')
 | 
			
		||||
 | 
			
		||||
    return cv.enum(GAIN)(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ADS1115Sensor = ads1115_ns.class_('ADS1115Sensor', sensor.Sensor, cg.PollingComponent,
 | 
			
		||||
                                  voltage_sampler.VoltageSampler)
 | 
			
		||||
ADS1115Sensor = ads1115_ns.class_(
 | 
			
		||||
    "ADS1115Sensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_ADS1115_ID = 'ads1115_id'
 | 
			
		||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 3).extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(ADS1115Sensor),
 | 
			
		||||
    cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
 | 
			
		||||
    cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space='_'),
 | 
			
		||||
    cv.Required(CONF_GAIN): validate_gain,
 | 
			
		||||
}).extend(cv.polling_component_schema('60s'))
 | 
			
		||||
CONF_ADS1115_ID = "ads1115_id"
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(ADS1115Sensor),
 | 
			
		||||
            cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
 | 
			
		||||
            cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"),
 | 
			
		||||
            cv.Required(CONF_GAIN): validate_gain,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,37 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \
 | 
			
		||||
    UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
aht10_ns = cg.esphome_ns.namespace('aht10')
 | 
			
		||||
AHT10Component = aht10_ns.class_('AHT10Component', cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
aht10_ns = cg.esphome_ns.namespace("aht10")
 | 
			
		||||
AHT10Component = aht10_ns.class_("AHT10Component", cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(AHT10Component),
 | 
			
		||||
    cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2),
 | 
			
		||||
    cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 2),
 | 
			
		||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38))
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(AHT10Component),
 | 
			
		||||
            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_CELSIUS, ICON_EMPTY, 2, DEVICE_CLASS_TEMPERATURE
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
			
		||||
                UNIT_PERCENT, ICON_EMPTY, 2, DEVICE_CLASS_HUMIDITY
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x38))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,39 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \
 | 
			
		||||
    UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
am2320_ns = cg.esphome_ns.namespace('am2320')
 | 
			
		||||
AM2320Component = am2320_ns.class_('AM2320Component', cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
am2320_ns = cg.esphome_ns.namespace("am2320")
 | 
			
		||||
AM2320Component = am2320_ns.class_(
 | 
			
		||||
    "AM2320Component", cg.PollingComponent, i2c.I2CDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(AM2320Component),
 | 
			
		||||
    cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
 | 
			
		||||
    cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1),
 | 
			
		||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5C))
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(AM2320Component),
 | 
			
		||||
            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
			
		||||
                UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x5C))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										106
									
								
								esphome/components/animation/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								esphome/components/animation/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from esphome import core
 | 
			
		||||
from esphome.components import display, font
 | 
			
		||||
import esphome.components.image as espImage
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE
 | 
			
		||||
from esphome.core import CORE, HexInt
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["display"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
Animation_ = display.display_ns.class_("Animation")
 | 
			
		||||
 | 
			
		||||
CONF_RAW_DATA_ID = "raw_data_id"
 | 
			
		||||
 | 
			
		||||
ANIMATION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.declare_id(Animation_),
 | 
			
		||||
        cv.Required(CONF_FILE): cv.file_,
 | 
			
		||||
        cv.Optional(CONF_RESIZE): cv.dimensions,
 | 
			
		||||
        cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
 | 
			
		||||
            espImage.IMAGE_TYPE, upper=True
 | 
			
		||||
        ),
 | 
			
		||||
        cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@syndlex"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    from PIL import Image
 | 
			
		||||
 | 
			
		||||
    path = CORE.relative_config_path(config[CONF_FILE])
 | 
			
		||||
    try:
 | 
			
		||||
        image = Image.open(path)
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        raise core.EsphomeError(f"Could not load image file {path}: {e}")
 | 
			
		||||
 | 
			
		||||
    width, height = image.size
 | 
			
		||||
    frames = image.n_frames
 | 
			
		||||
    if CONF_RESIZE in config:
 | 
			
		||||
        image.thumbnail(config[CONF_RESIZE])
 | 
			
		||||
        width, height = image.size
 | 
			
		||||
    else:
 | 
			
		||||
        if width > 500 or height > 500:
 | 
			
		||||
            _LOGGER.warning(
 | 
			
		||||
                "The image you requested is very big. Please consider using"
 | 
			
		||||
                " the resize parameter."
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    if config[CONF_TYPE] == "GRAYSCALE":
 | 
			
		||||
        data = [0 for _ in range(height * width * frames)]
 | 
			
		||||
        pos = 0
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            frame = image.convert("L", dither=Image.NONE)
 | 
			
		||||
            pixels = list(frame.getdata())
 | 
			
		||||
            for pix in pixels:
 | 
			
		||||
                data[pos] = pix
 | 
			
		||||
                pos += 1
 | 
			
		||||
 | 
			
		||||
    elif config[CONF_TYPE] == "RGB24":
 | 
			
		||||
        data = [0 for _ in range(height * width * 3 * frames)]
 | 
			
		||||
        pos = 0
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            frame = image.convert("RGB")
 | 
			
		||||
            pixels = list(frame.getdata())
 | 
			
		||||
            for pix in pixels:
 | 
			
		||||
                data[pos] = pix[0]
 | 
			
		||||
                pos += 1
 | 
			
		||||
                data[pos] = pix[1]
 | 
			
		||||
                pos += 1
 | 
			
		||||
                data[pos] = pix[2]
 | 
			
		||||
                pos += 1
 | 
			
		||||
 | 
			
		||||
    elif config[CONF_TYPE] == "BINARY":
 | 
			
		||||
        width8 = ((width + 7) // 8) * 8
 | 
			
		||||
        data = [0 for _ in range((height * width8 // 8) * frames)]
 | 
			
		||||
        for frameIndex in range(frames):
 | 
			
		||||
            image.seek(frameIndex)
 | 
			
		||||
            frame = image.convert("1", dither=Image.NONE)
 | 
			
		||||
            for y in range(height):
 | 
			
		||||
                for x in range(width):
 | 
			
		||||
                    if frame.getpixel((x, y)):
 | 
			
		||||
                        continue
 | 
			
		||||
                    pos = x + y * width8 + (height * width8 * frameIndex)
 | 
			
		||||
                    data[pos // 8] |= 0x80 >> (pos % 8)
 | 
			
		||||
 | 
			
		||||
    rhs = [HexInt(x) for x in data]
 | 
			
		||||
    prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
 | 
			
		||||
    cg.new_Pvariable(
 | 
			
		||||
        config[CONF_ID],
 | 
			
		||||
        prog_arr,
 | 
			
		||||
        width,
 | 
			
		||||
        height,
 | 
			
		||||
        frames,
 | 
			
		||||
        espImage.IMAGE_TYPE[config[CONF_TYPE]],
 | 
			
		||||
    )
 | 
			
		||||
@@ -3,18 +3,24 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
AUTO_LOAD = ['sensor', 'binary_sensor']
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
AUTO_LOAD = ["sensor", "binary_sensor"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
CONF_APDS9960_ID = 'apds9960_id'
 | 
			
		||||
CONF_APDS9960_ID = "apds9960_id"
 | 
			
		||||
 | 
			
		||||
apds9960_nds = cg.esphome_ns.namespace('apds9960')
 | 
			
		||||
APDS9960 = apds9960_nds.class_('APDS9960', cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
apds9960_nds = cg.esphome_ns.namespace("apds9960")
 | 
			
		||||
APDS9960 = apds9960_nds.class_("APDS9960", cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(APDS9960),
 | 
			
		||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x39))
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(APDS9960),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x39))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -4,20 +4,24 @@ from esphome.components import binary_sensor
 | 
			
		||||
from esphome.const import CONF_DIRECTION, CONF_DEVICE_CLASS, DEVICE_CLASS_MOVING
 | 
			
		||||
from . import APDS9960, CONF_APDS9960_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['apds9960']
 | 
			
		||||
DEPENDENCIES = ["apds9960"]
 | 
			
		||||
 | 
			
		||||
DIRECTIONS = {
 | 
			
		||||
    'UP': 'set_up_direction',
 | 
			
		||||
    'DOWN': 'set_down_direction',
 | 
			
		||||
    'LEFT': 'set_left_direction',
 | 
			
		||||
    'RIGHT': 'set_right_direction',
 | 
			
		||||
    "UP": "set_up_direction",
 | 
			
		||||
    "DOWN": "set_down_direction",
 | 
			
		||||
    "LEFT": "set_left_direction",
 | 
			
		||||
    "RIGHT": "set_right_direction",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
 | 
			
		||||
    cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
 | 
			
		||||
    cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
 | 
			
		||||
    cv.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING): binary_sensor.device_class,
 | 
			
		||||
})
 | 
			
		||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True),
 | 
			
		||||
        cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
 | 
			
		||||
        cv.Optional(
 | 
			
		||||
            CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING
 | 
			
		||||
        ): binary_sensor.device_class,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +1,27 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
from esphome.const import CONF_TYPE, UNIT_PERCENT, ICON_LIGHTBULB
 | 
			
		||||
from esphome.const import CONF_TYPE, DEVICE_CLASS_EMPTY, UNIT_PERCENT, ICON_LIGHTBULB
 | 
			
		||||
from . import APDS9960, CONF_APDS9960_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['apds9960']
 | 
			
		||||
DEPENDENCIES = ["apds9960"]
 | 
			
		||||
 | 
			
		||||
TYPES = {
 | 
			
		||||
    'CLEAR': 'set_clear_channel',
 | 
			
		||||
    'RED': 'set_red_channel',
 | 
			
		||||
    'GREEN': 'set_green_channel',
 | 
			
		||||
    'BLUE': 'set_blue_channel',
 | 
			
		||||
    'PROXIMITY': 'set_proximity',
 | 
			
		||||
    "CLEAR": "set_clear_channel",
 | 
			
		||||
    "RED": "set_red_channel",
 | 
			
		||||
    "GREEN": "set_green_channel",
 | 
			
		||||
    "BLUE": "set_blue_channel",
 | 
			
		||||
    "PROXIMITY": "set_proximity",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PERCENT, ICON_LIGHTBULB, 1).extend({
 | 
			
		||||
    cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
 | 
			
		||||
    cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
 | 
			
		||||
})
 | 
			
		||||
CONFIG_SCHEMA = sensor.sensor_schema(
 | 
			
		||||
    UNIT_PERCENT, ICON_LIGHTBULB, 1, DEVICE_CLASS_EMPTY
 | 
			
		||||
).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_TYPE): cv.one_of(*TYPES, upper=True),
 | 
			
		||||
        cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -2,45 +2,69 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.automation import Condition
 | 
			
		||||
from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \
 | 
			
		||||
    CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DATA,
 | 
			
		||||
    CONF_DATA_TEMPLATE,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_PASSWORD,
 | 
			
		||||
    CONF_PORT,
 | 
			
		||||
    CONF_REBOOT_TIMEOUT,
 | 
			
		||||
    CONF_SERVICE,
 | 
			
		||||
    CONF_VARIABLES,
 | 
			
		||||
    CONF_SERVICES,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_EVENT,
 | 
			
		||||
    CONF_TAG,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import coroutine_with_priority
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['network']
 | 
			
		||||
AUTO_LOAD = ['async_tcp']
 | 
			
		||||
CODEOWNERS = ['@OttoWinter']
 | 
			
		||||
DEPENDENCIES = ["network"]
 | 
			
		||||
AUTO_LOAD = ["async_tcp"]
 | 
			
		||||
CODEOWNERS = ["@OttoWinter"]
 | 
			
		||||
 | 
			
		||||
api_ns = cg.esphome_ns.namespace('api')
 | 
			
		||||
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
 | 
			
		||||
HomeAssistantServiceCallAction = api_ns.class_('HomeAssistantServiceCallAction', automation.Action)
 | 
			
		||||
APIConnectedCondition = api_ns.class_('APIConnectedCondition', Condition)
 | 
			
		||||
api_ns = cg.esphome_ns.namespace("api")
 | 
			
		||||
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
 | 
			
		||||
HomeAssistantServiceCallAction = api_ns.class_(
 | 
			
		||||
    "HomeAssistantServiceCallAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
APIConnectedCondition = api_ns.class_("APIConnectedCondition", Condition)
 | 
			
		||||
 | 
			
		||||
UserServiceTrigger = api_ns.class_('UserServiceTrigger', automation.Trigger)
 | 
			
		||||
ListEntitiesServicesArgument = api_ns.class_('ListEntitiesServicesArgument')
 | 
			
		||||
UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
 | 
			
		||||
ListEntitiesServicesArgument = api_ns.class_("ListEntitiesServicesArgument")
 | 
			
		||||
SERVICE_ARG_NATIVE_TYPES = {
 | 
			
		||||
    'bool': bool,
 | 
			
		||||
    'int': cg.int32,
 | 
			
		||||
    'float': float,
 | 
			
		||||
    'string': cg.std_string,
 | 
			
		||||
    'bool[]': cg.std_vector.template(bool),
 | 
			
		||||
    'int[]': cg.std_vector.template(cg.int32),
 | 
			
		||||
    'float[]': cg.std_vector.template(float),
 | 
			
		||||
    'string[]': cg.std_vector.template(cg.std_string),
 | 
			
		||||
    "bool": bool,
 | 
			
		||||
    "int": cg.int32,
 | 
			
		||||
    "float": float,
 | 
			
		||||
    "string": cg.std_string,
 | 
			
		||||
    "bool[]": cg.std_vector.template(bool),
 | 
			
		||||
    "int[]": cg.std_vector.template(cg.int32),
 | 
			
		||||
    "float[]": cg.std_vector.template(float),
 | 
			
		||||
    "string[]": cg.std_vector.template(cg.std_string),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(APIServer),
 | 
			
		||||
    cv.Optional(CONF_PORT, default=6053): cv.port,
 | 
			
		||||
    cv.Optional(CONF_PASSWORD, default=''): cv.string_strict,
 | 
			
		||||
    cv.Optional(CONF_REBOOT_TIMEOUT, default='15min'): cv.positive_time_period_milliseconds,
 | 
			
		||||
    cv.Optional(CONF_SERVICES): automation.validate_automation({
 | 
			
		||||
        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
 | 
			
		||||
        cv.Required(CONF_SERVICE): cv.valid_name,
 | 
			
		||||
        cv.Optional(CONF_VARIABLES, default={}): cv.Schema({
 | 
			
		||||
            cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True),
 | 
			
		||||
        }),
 | 
			
		||||
    }),
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(APIServer),
 | 
			
		||||
        cv.Optional(CONF_PORT, default=6053): cv.port,
 | 
			
		||||
        cv.Optional(CONF_PASSWORD, default=""): cv.string_strict,
 | 
			
		||||
        cv.Optional(
 | 
			
		||||
            CONF_REBOOT_TIMEOUT, default="15min"
 | 
			
		||||
        ): cv.positive_time_period_milliseconds,
 | 
			
		||||
        cv.Optional(CONF_SERVICES): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
 | 
			
		||||
                cv.Required(CONF_SERVICE): cv.valid_name,
 | 
			
		||||
                cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
 | 
			
		||||
                    {
 | 
			
		||||
                        cv.validate_id_name: cv.one_of(
 | 
			
		||||
                            *SERVICE_ARG_NATIVE_TYPES, lower=True
 | 
			
		||||
                        ),
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(40.0)
 | 
			
		||||
@@ -62,28 +86,36 @@ def to_code(config):
 | 
			
		||||
            func_args.append((native, name))
 | 
			
		||||
            service_arg_names.append(name)
 | 
			
		||||
        templ = cg.TemplateArguments(*template_args)
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], templ,
 | 
			
		||||
                                   conf[CONF_SERVICE], service_arg_names)
 | 
			
		||||
        trigger = cg.new_Pvariable(
 | 
			
		||||
            conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.register_user_service(trigger))
 | 
			
		||||
        yield automation.build_automation(trigger, func_args, conf)
 | 
			
		||||
 | 
			
		||||
    cg.add_define('USE_API')
 | 
			
		||||
    cg.add_define("USE_API")
 | 
			
		||||
    cg.add_global(api_ns.using)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
 | 
			
		||||
 | 
			
		||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.use_id(APIServer),
 | 
			
		||||
    cv.Required(CONF_SERVICE): cv.templatable(cv.string),
 | 
			
		||||
    cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
 | 
			
		||||
    cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
 | 
			
		||||
    cv.Optional(CONF_VARIABLES, default={}): cv.Schema({cv.string: cv.returning_lambda}),
 | 
			
		||||
})
 | 
			
		||||
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(APIServer),
 | 
			
		||||
        cv.Required(CONF_SERVICE): cv.templatable(cv.string),
 | 
			
		||||
        cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
 | 
			
		||||
            {cv.string: cv.returning_lambda}
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action('homeassistant.service', HomeAssistantServiceCallAction,
 | 
			
		||||
                            HOMEASSISTANT_SERVICE_ACTION_SCHEMA)
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "homeassistant.service",
 | 
			
		||||
    HomeAssistantServiceCallAction,
 | 
			
		||||
    HOMEASSISTANT_SERVICE_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
def homeassistant_service_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    serv = yield cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, serv, False)
 | 
			
		||||
@@ -103,23 +135,30 @@ def homeassistant_service_to_code(config, action_id, template_arg, args):
 | 
			
		||||
 | 
			
		||||
def validate_homeassistant_event(value):
 | 
			
		||||
    value = cv.string(value)
 | 
			
		||||
    if not value.startswith('esphome.'):
 | 
			
		||||
        raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with "
 | 
			
		||||
                         "esphome. For example 'esphome.xyz'")
 | 
			
		||||
    if not value.startswith("esphome."):
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "ESPHome can only generate Home Assistant events that begin with "
 | 
			
		||||
            "esphome. For example 'esphome.xyz'"
 | 
			
		||||
        )
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.use_id(APIServer),
 | 
			
		||||
    cv.Required(CONF_EVENT): validate_homeassistant_event,
 | 
			
		||||
    cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
 | 
			
		||||
    cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
 | 
			
		||||
    cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
 | 
			
		||||
})
 | 
			
		||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(APIServer),
 | 
			
		||||
        cv.Required(CONF_EVENT): validate_homeassistant_event,
 | 
			
		||||
        cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
 | 
			
		||||
        cv.Optional(CONF_VARIABLES, default={}): KEY_VALUE_SCHEMA,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action('homeassistant.event', HomeAssistantServiceCallAction,
 | 
			
		||||
                            HOMEASSISTANT_EVENT_ACTION_SCHEMA)
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "homeassistant.event",
 | 
			
		||||
    HomeAssistantServiceCallAction,
 | 
			
		||||
    HOMEASSISTANT_EVENT_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
def homeassistant_event_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    serv = yield cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, serv, True)
 | 
			
		||||
@@ -137,6 +176,29 @@ def homeassistant_event_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_condition('api.connected', APIConnectedCondition, {})
 | 
			
		||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(APIServer),
 | 
			
		||||
        cv.Required(CONF_TAG): cv.templatable(cv.string_strict),
 | 
			
		||||
    },
 | 
			
		||||
    key=CONF_TAG,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "homeassistant.tag_scanned",
 | 
			
		||||
    HomeAssistantServiceCallAction,
 | 
			
		||||
    HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    serv = yield cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, serv, True)
 | 
			
		||||
    cg.add(var.set_service("esphome.tag_scanned"))
 | 
			
		||||
    templ = yield cg.templatable(config[CONF_TAG], args, cg.std_string)
 | 
			
		||||
    cg.add(var.add_data("tag_id", templ))
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_condition("api.connected", APIConnectedCondition, {})
 | 
			
		||||
def api_connected_to_code(config, condition_id, template_arg, args):
 | 
			
		||||
    yield cg.new_Pvariable(condition_id, template_arg)
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ service APIConnection {
 | 
			
		||||
// The Home Assistant protocol is structured as a simple
 | 
			
		||||
// TCP socket with short binary messages encoded in the protocol buffers format
 | 
			
		||||
// First, a message in this protocol has a specific format:
 | 
			
		||||
//  * A zero byte.
 | 
			
		||||
//  * VarInt denoting the size of the message object. (type is not part of this)
 | 
			
		||||
//  * VarInt denoting the type of message.
 | 
			
		||||
//  * The message object encoded as a ProtoBuf message
 | 
			
		||||
@@ -302,6 +303,7 @@ message ListEntitiesFanResponse {
 | 
			
		||||
  bool supports_oscillation = 5;
 | 
			
		||||
  bool supports_speed = 6;
 | 
			
		||||
  bool supports_direction = 7;
 | 
			
		||||
  int32 supported_speed_count = 8;
 | 
			
		||||
}
 | 
			
		||||
enum FanSpeed {
 | 
			
		||||
  FAN_SPEED_LOW = 0;
 | 
			
		||||
@@ -321,8 +323,9 @@ message FanStateResponse {
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  bool state = 2;
 | 
			
		||||
  bool oscillating = 3;
 | 
			
		||||
  FanSpeed speed = 4;
 | 
			
		||||
  FanSpeed speed = 4 [deprecated = true];
 | 
			
		||||
  FanDirection direction = 5;
 | 
			
		||||
  int32 speed_level = 6;
 | 
			
		||||
}
 | 
			
		||||
message FanCommandRequest {
 | 
			
		||||
  option (id) = 31;
 | 
			
		||||
@@ -333,12 +336,14 @@ message FanCommandRequest {
 | 
			
		||||
  fixed32 key = 1;
 | 
			
		||||
  bool has_state = 2;
 | 
			
		||||
  bool state = 3;
 | 
			
		||||
  bool has_speed = 4;
 | 
			
		||||
  FanSpeed speed = 5;
 | 
			
		||||
  bool has_speed = 4 [deprecated = true];
 | 
			
		||||
  FanSpeed speed = 5 [deprecated = true];
 | 
			
		||||
  bool has_oscillating = 6;
 | 
			
		||||
  bool oscillating = 7;
 | 
			
		||||
  bool has_direction = 8;
 | 
			
		||||
  FanDirection direction = 9;
 | 
			
		||||
  bool has_speed_level = 10;
 | 
			
		||||
  int32 speed_level = 11;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== LIGHT ====================
 | 
			
		||||
@@ -418,6 +423,7 @@ message ListEntitiesSensorResponse {
 | 
			
		||||
  string unit_of_measurement = 6;
 | 
			
		||||
  int32 accuracy_decimals = 7;
 | 
			
		||||
  bool force_update = 8;
 | 
			
		||||
  string device_class = 9;
 | 
			
		||||
}
 | 
			
		||||
message SensorStateResponse {
 | 
			
		||||
  option (id) = 25;
 | 
			
		||||
@@ -679,7 +685,7 @@ enum ClimateSwingMode {
 | 
			
		||||
  CLIMATE_SWING_OFF = 0;
 | 
			
		||||
  CLIMATE_SWING_BOTH = 1;
 | 
			
		||||
  CLIMATE_SWING_VERTICAL = 2;
 | 
			
		||||
  CLIMATE_SWINT_HORIZONTAL = 3;
 | 
			
		||||
  CLIMATE_SWING_HORIZONTAL = 3;
 | 
			
		||||
}
 | 
			
		||||
enum ClimateAction {
 | 
			
		||||
  CLIMATE_ACTION_OFF = 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,9 @@
 | 
			
		||||
#ifdef USE_HOMEASSISTANT_TIME
 | 
			
		||||
#include "esphome/components/homeassistant/time/homeassistant_time.h"
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_FAN
 | 
			
		||||
#include "esphome/components/fan/fan_helpers.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
@@ -246,8 +249,10 @@ bool APIConnection::send_fan_state(fan::FanState *fan) {
 | 
			
		||||
  resp.state = fan->state;
 | 
			
		||||
  if (traits.supports_oscillation())
 | 
			
		||||
    resp.oscillating = fan->oscillating;
 | 
			
		||||
  if (traits.supports_speed())
 | 
			
		||||
    resp.speed = static_cast<enums::FanSpeed>(fan->speed);
 | 
			
		||||
  if (traits.supports_speed()) {
 | 
			
		||||
    resp.speed_level = fan->speed;
 | 
			
		||||
    resp.speed = static_cast<enums::FanSpeed>(fan::speed_level_to_enum(fan->speed, traits.supported_speed_count()));
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.supports_direction())
 | 
			
		||||
    resp.direction = static_cast<enums::FanDirection>(fan->direction);
 | 
			
		||||
  return this->send_fan_state_response(resp);
 | 
			
		||||
@@ -262,6 +267,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
 | 
			
		||||
  msg.supports_oscillation = traits.supports_oscillation();
 | 
			
		||||
  msg.supports_speed = traits.supports_speed();
 | 
			
		||||
  msg.supports_direction = traits.supports_direction();
 | 
			
		||||
  msg.supported_speed_count = traits.supported_speed_count();
 | 
			
		||||
  return this->send_list_entities_fan_response(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
 | 
			
		||||
@@ -269,13 +275,19 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
 | 
			
		||||
  if (fan == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  auto traits = fan->get_traits();
 | 
			
		||||
 | 
			
		||||
  auto call = fan->make_call();
 | 
			
		||||
  if (msg.has_state)
 | 
			
		||||
    call.set_state(msg.state);
 | 
			
		||||
  if (msg.has_oscillating)
 | 
			
		||||
    call.set_oscillating(msg.oscillating);
 | 
			
		||||
  if (msg.has_speed)
 | 
			
		||||
    call.set_speed(static_cast<fan::FanSpeed>(msg.speed));
 | 
			
		||||
  if (msg.has_speed_level) {
 | 
			
		||||
    // Prefer level
 | 
			
		||||
    call.set_speed(msg.speed_level);
 | 
			
		||||
  } else if (msg.has_speed) {
 | 
			
		||||
    call.set_speed(fan::speed_enum_to_level(static_cast<fan::FanSpeed>(msg.speed), traits.supported_speed_count()));
 | 
			
		||||
  }
 | 
			
		||||
  if (msg.has_direction)
 | 
			
		||||
    call.set_direction(static_cast<fan::FanDirection>(msg.direction));
 | 
			
		||||
  call.perform();
 | 
			
		||||
@@ -382,6 +394,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
 | 
			
		||||
  msg.unit_of_measurement = sensor->get_unit_of_measurement();
 | 
			
		||||
  msg.accuracy_decimals = sensor->get_accuracy_decimals();
 | 
			
		||||
  msg.force_update = sensor->get_force_update();
 | 
			
		||||
  msg.device_class = sensor->get_device_class();
 | 
			
		||||
  return this->send_list_entities_sensor_response(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -589,7 +602,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
 | 
			
		||||
 | 
			
		||||
  HelloResponse resp;
 | 
			
		||||
  resp.api_version_major = 1;
 | 
			
		||||
  resp.api_version_minor = 3;
 | 
			
		||||
  resp.api_version_minor = 4;
 | 
			
		||||
  resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
 | 
			
		||||
  this->connection_state_ = ConnectionState::CONNECTED;
 | 
			
		||||
  return resp;
 | 
			
		||||
@@ -676,8 +689,10 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->client_->add(reinterpret_cast<char *>(header.data()), header.size());
 | 
			
		||||
  this->client_->add(reinterpret_cast<char *>(buffer.get_buffer()->data()), buffer.get_buffer()->size());
 | 
			
		||||
  this->client_->add(reinterpret_cast<char *>(header.data()), header.size(),
 | 
			
		||||
                     ASYNC_WRITE_FLAG_COPY | ASYNC_WRITE_FLAG_MORE);
 | 
			
		||||
  this->client_->add(reinterpret_cast<char *>(buffer.get_buffer()->data()), buffer.get_buffer()->size(),
 | 
			
		||||
                     ASYNC_WRITE_FLAG_COPY);
 | 
			
		||||
  bool ret = this->client_->send();
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -154,8 +154,8 @@ template<> const char *proto_enum_to_string<enums::ClimateSwingMode>(enums::Clim
 | 
			
		||||
      return "CLIMATE_SWING_BOTH";
 | 
			
		||||
    case enums::CLIMATE_SWING_VERTICAL:
 | 
			
		||||
      return "CLIMATE_SWING_VERTICAL";
 | 
			
		||||
    case enums::CLIMATE_SWINT_HORIZONTAL:
 | 
			
		||||
      return "CLIMATE_SWINT_HORIZONTAL";
 | 
			
		||||
    case enums::CLIMATE_SWING_HORIZONTAL:
 | 
			
		||||
      return "CLIMATE_SWING_HORIZONTAL";
 | 
			
		||||
    default:
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
@@ -774,6 +774,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
 | 
			
		||||
      this->supports_direction = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 8: {
 | 
			
		||||
      this->supported_speed_count = value.as_int32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -814,6 +818,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_bool(5, this->supports_oscillation);
 | 
			
		||||
  buffer.encode_bool(6, this->supports_speed);
 | 
			
		||||
  buffer.encode_bool(7, this->supports_direction);
 | 
			
		||||
  buffer.encode_int32(8, this->supported_speed_count);
 | 
			
		||||
}
 | 
			
		||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
@@ -846,6 +851,11 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  supports_direction: ");
 | 
			
		||||
  out.append(YESNO(this->supports_direction));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  supported_speed_count: ");
 | 
			
		||||
  sprintf(buffer, "%d", this->supported_speed_count);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
@@ -866,6 +876,10 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
      this->direction = value.as_enum<enums::FanDirection>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 6: {
 | 
			
		||||
      this->speed_level = value.as_int32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -886,6 +900,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_bool(3, this->oscillating);
 | 
			
		||||
  buffer.encode_enum<enums::FanSpeed>(4, this->speed);
 | 
			
		||||
  buffer.encode_enum<enums::FanDirection>(5, this->direction);
 | 
			
		||||
  buffer.encode_int32(6, this->speed_level);
 | 
			
		||||
}
 | 
			
		||||
void FanStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
@@ -910,6 +925,11 @@ void FanStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  direction: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  speed_level: ");
 | 
			
		||||
  sprintf(buffer, "%d", this->speed_level);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
@@ -946,6 +966,14 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
      this->direction = value.as_enum<enums::FanDirection>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 10: {
 | 
			
		||||
      this->has_speed_level = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 11: {
 | 
			
		||||
      this->speed_level = value.as_int32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -970,6 +998,8 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_bool(7, this->oscillating);
 | 
			
		||||
  buffer.encode_bool(8, this->has_direction);
 | 
			
		||||
  buffer.encode_enum<enums::FanDirection>(9, this->direction);
 | 
			
		||||
  buffer.encode_bool(10, this->has_speed_level);
 | 
			
		||||
  buffer.encode_int32(11, this->speed_level);
 | 
			
		||||
}
 | 
			
		||||
void FanCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
@@ -1010,6 +1040,15 @@ void FanCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  direction: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_speed_level: ");
 | 
			
		||||
  out.append(YESNO(this->has_speed_level));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  speed_level: ");
 | 
			
		||||
  sprintf(buffer, "%d", this->speed_level);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
@@ -1494,6 +1533,10 @@ bool ListEntitiesSensorResponse::decode_length(uint32_t field_id, ProtoLengthDel
 | 
			
		||||
      this->unit_of_measurement = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 9: {
 | 
			
		||||
      this->device_class = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -1517,6 +1560,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(6, this->unit_of_measurement);
 | 
			
		||||
  buffer.encode_int32(7, this->accuracy_decimals);
 | 
			
		||||
  buffer.encode_bool(8, this->force_update);
 | 
			
		||||
  buffer.encode_string(9, this->device_class);
 | 
			
		||||
}
 | 
			
		||||
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
 | 
			
		||||
  char buffer[64];
 | 
			
		||||
@@ -1554,6 +1598,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  force_update: ");
 | 
			
		||||
  out.append(YESNO(this->force_update));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  device_class: ");
 | 
			
		||||
  out.append("'").append(this->device_class).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ enum ClimateSwingMode : uint32_t {
 | 
			
		||||
  CLIMATE_SWING_OFF = 0,
 | 
			
		||||
  CLIMATE_SWING_BOTH = 1,
 | 
			
		||||
  CLIMATE_SWING_VERTICAL = 2,
 | 
			
		||||
  CLIMATE_SWINT_HORIZONTAL = 3,
 | 
			
		||||
  CLIMATE_SWING_HORIZONTAL = 3,
 | 
			
		||||
};
 | 
			
		||||
enum ClimateAction : uint32_t {
 | 
			
		||||
  CLIMATE_ACTION_OFF = 0,
 | 
			
		||||
@@ -284,6 +284,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
 | 
			
		||||
  bool supports_oscillation{false};  // NOLINT
 | 
			
		||||
  bool supports_speed{false};        // NOLINT
 | 
			
		||||
  bool supports_direction{false};    // NOLINT
 | 
			
		||||
  int32_t supported_speed_count{0};  // NOLINT
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
 | 
			
		||||
@@ -299,6 +300,7 @@ class FanStateResponse : public ProtoMessage {
 | 
			
		||||
  bool oscillating{false};          // NOLINT
 | 
			
		||||
  enums::FanSpeed speed{};          // NOLINT
 | 
			
		||||
  enums::FanDirection direction{};  // NOLINT
 | 
			
		||||
  int32_t speed_level{0};           // NOLINT
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
 | 
			
		||||
@@ -317,6 +319,8 @@ class FanCommandRequest : public ProtoMessage {
 | 
			
		||||
  bool oscillating{false};          // NOLINT
 | 
			
		||||
  bool has_direction{false};        // NOLINT
 | 
			
		||||
  enums::FanDirection direction{};  // NOLINT
 | 
			
		||||
  bool has_speed_level{false};      // NOLINT
 | 
			
		||||
  int32_t speed_level{0};           // NOLINT
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
 | 
			
		||||
@@ -403,6 +407,7 @@ class ListEntitiesSensorResponse : public ProtoMessage {
 | 
			
		||||
  std::string unit_of_measurement{};  // NOLINT
 | 
			
		||||
  int32_t accuracy_decimals{0};       // NOLINT
 | 
			
		||||
  bool force_update{false};           // NOLINT
 | 
			
		||||
  std::string device_class{};         // NOLINT
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -62,8 +62,7 @@ void ProtoMessage::decode(const uint8_t *buffer, size_t length) {
 | 
			
		||||
          error = true;
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        uint32_t val = (uint32_t(buffer[i]) << 0) | (uint32_t(buffer[i + 1]) << 8) | (uint32_t(buffer[i + 2]) << 16) |
 | 
			
		||||
                       (uint32_t(buffer[i + 3]) << 24);
 | 
			
		||||
        uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]);
 | 
			
		||||
        if (!this->decode_32bit(field_id, Proto32Bit(val))) {
 | 
			
		||||
          ESP_LOGV(TAG, "Cannot decode 32-bit field %u with value %u!", field_id, val);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +1,43 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import CONF_INDOOR, CONF_WATCHDOG_THRESHOLD, \
 | 
			
		||||
    CONF_NOISE_LEVEL, CONF_SPIKE_REJECTION, CONF_LIGHTNING_THRESHOLD, \
 | 
			
		||||
    CONF_MASK_DISTURBER, CONF_DIV_RATIO, CONF_CAPACITANCE
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_INDOOR,
 | 
			
		||||
    CONF_WATCHDOG_THRESHOLD,
 | 
			
		||||
    CONF_NOISE_LEVEL,
 | 
			
		||||
    CONF_SPIKE_REJECTION,
 | 
			
		||||
    CONF_LIGHTNING_THRESHOLD,
 | 
			
		||||
    CONF_MASK_DISTURBER,
 | 
			
		||||
    CONF_DIV_RATIO,
 | 
			
		||||
    CONF_CAPACITANCE,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import coroutine
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ['sensor', 'binary_sensor']
 | 
			
		||||
AUTO_LOAD = ["sensor", "binary_sensor"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
CONF_AS3935_ID = 'as3935_id'
 | 
			
		||||
CONF_AS3935_ID = "as3935_id"
 | 
			
		||||
 | 
			
		||||
as3935_ns = cg.esphome_ns.namespace('as3935')
 | 
			
		||||
AS3935 = as3935_ns.class_('AS3935Component', cg.Component)
 | 
			
		||||
as3935_ns = cg.esphome_ns.namespace("as3935")
 | 
			
		||||
AS3935 = as3935_ns.class_("AS3935Component", cg.Component)
 | 
			
		||||
 | 
			
		||||
CONF_IRQ_PIN = 'irq_pin'
 | 
			
		||||
AS3935_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(AS3935),
 | 
			
		||||
    cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema,
 | 
			
		||||
 | 
			
		||||
    cv.Optional(CONF_INDOOR, default=True): cv.boolean,
 | 
			
		||||
    cv.Optional(CONF_NOISE_LEVEL, default=2): cv.int_range(min=1, max=7),
 | 
			
		||||
    cv.Optional(CONF_WATCHDOG_THRESHOLD, default=2): cv.int_range(min=1, max=10),
 | 
			
		||||
    cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11),
 | 
			
		||||
    cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True),
 | 
			
		||||
    cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean,
 | 
			
		||||
    cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True),
 | 
			
		||||
    cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15),
 | 
			
		||||
})
 | 
			
		||||
CONF_IRQ_PIN = "irq_pin"
 | 
			
		||||
AS3935_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(AS3935),
 | 
			
		||||
        cv.Required(CONF_IRQ_PIN): pins.gpio_input_pin_schema,
 | 
			
		||||
        cv.Optional(CONF_INDOOR, default=True): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_NOISE_LEVEL, default=2): cv.int_range(min=1, max=7),
 | 
			
		||||
        cv.Optional(CONF_WATCHDOG_THRESHOLD, default=2): cv.int_range(min=1, max=10),
 | 
			
		||||
        cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11),
 | 
			
		||||
        cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(
 | 
			
		||||
            1, 5, 9, 16, int=True
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True),
 | 
			
		||||
        cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine
 | 
			
		||||
 
 | 
			
		||||
@@ -3,11 +3,13 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import binary_sensor
 | 
			
		||||
from . import AS3935, CONF_AS3935_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['as3935']
 | 
			
		||||
DEPENDENCIES = ["as3935"]
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
 | 
			
		||||
})
 | 
			
		||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,30 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
from esphome.const import CONF_DISTANCE, CONF_LIGHTNING_ENERGY, \
 | 
			
		||||
    UNIT_KILOMETER, UNIT_EMPTY, ICON_SIGNAL_DISTANCE_VARIANT, ICON_FLASH
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DISTANCE,
 | 
			
		||||
    CONF_LIGHTNING_ENERGY,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    UNIT_KILOMETER,
 | 
			
		||||
    UNIT_EMPTY,
 | 
			
		||||
    ICON_SIGNAL_DISTANCE_VARIANT,
 | 
			
		||||
    ICON_FLASH,
 | 
			
		||||
)
 | 
			
		||||
from . import AS3935, CONF_AS3935_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['as3935']
 | 
			
		||||
DEPENDENCIES = ["as3935"]
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
 | 
			
		||||
    cv.Optional(CONF_DISTANCE):
 | 
			
		||||
        sensor.sensor_schema(UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1),
 | 
			
		||||
    cv.Optional(CONF_LIGHTNING_ENERGY):
 | 
			
		||||
        sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 1),
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935),
 | 
			
		||||
        cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
 | 
			
		||||
            UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, 1, DEVICE_CLASS_EMPTY
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema(
 | 
			
		||||
            UNIT_EMPTY, ICON_FLASH, 1, DEVICE_CLASS_EMPTY
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -3,15 +3,21 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import as3935, i2c
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ['as3935']
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
AUTO_LOAD = ["as3935"]
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
as3935_i2c_ns = cg.esphome_ns.namespace('as3935_i2c')
 | 
			
		||||
I2CAS3935 = as3935_i2c_ns.class_('I2CAS3935Component', as3935.AS3935, i2c.I2CDevice)
 | 
			
		||||
as3935_i2c_ns = cg.esphome_ns.namespace("as3935_i2c")
 | 
			
		||||
I2CAS3935 = as3935_i2c_ns.class_("I2CAS3935Component", as3935.AS3935, i2c.I2CDevice)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(I2CAS3935),
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x03)))
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    as3935.AS3935_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(I2CAS3935),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x03))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -3,15 +3,21 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import as3935, spi
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ['as3935']
 | 
			
		||||
DEPENDENCIES = ['spi']
 | 
			
		||||
AUTO_LOAD = ["as3935"]
 | 
			
		||||
DEPENDENCIES = ["spi"]
 | 
			
		||||
 | 
			
		||||
as3935_spi_ns = cg.esphome_ns.namespace('as3935_spi')
 | 
			
		||||
SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice)
 | 
			
		||||
as3935_spi_ns = cg.esphome_ns.namespace("as3935_spi")
 | 
			
		||||
SPIAS3935 = as3935_spi_ns.class_("SPIAS3935Component", as3935.AS3935, spi.SPIDevice)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(SPIAS3935),
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=True)))
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    as3935.AS3935_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(SPIAS3935),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(spi.spi_device_schema(cs_pin_required=True))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,14 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ['@OttoWinter']
 | 
			
		||||
CODEOWNERS = ["@OttoWinter"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(200.0)
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        # https://github.com/OttoWinter/AsyncTCP/blob/master/library.json
 | 
			
		||||
        cg.add_library('AsyncTCP-esphome', '1.1.1')
 | 
			
		||||
        cg.add_library("AsyncTCP-esphome", "1.1.1")
 | 
			
		||||
    elif CORE.is_esp8266:
 | 
			
		||||
        # https://github.com/OttoWinter/ESPAsyncTCP
 | 
			
		||||
        cg.add_library('ESPAsyncTCP-esphome', '1.2.2')
 | 
			
		||||
        cg.add_library("ESPAsyncTCP-esphome", "1.2.3")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								esphome/components/atc_mithermometer/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/atc_mithermometer/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										137
									
								
								esphome/components/atc_mithermometer/atc_mithermometer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								esphome/components/atc_mithermometer/atc_mithermometer.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
			
		||||
#include "atc_mithermometer.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atc_mithermometer {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "atc_mithermometer";
 | 
			
		||||
 | 
			
		||||
void ATCMiThermometer::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ATC MiThermometer");
 | 
			
		||||
  LOG_SENSOR("  ", "Temperature", this->temperature_);
 | 
			
		||||
  LOG_SENSOR("  ", "Humidity", this->humidity_);
 | 
			
		||||
  LOG_SENSOR("  ", "Battery Level", this->battery_level_);
 | 
			
		||||
  LOG_SENSOR("  ", "Battery Voltage", this->battery_voltage_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
			
		||||
  if (device.address_uint64() != this->address_) {
 | 
			
		||||
    ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
 | 
			
		||||
 | 
			
		||||
  bool success = false;
 | 
			
		||||
  for (auto &service_data : device.get_service_datas()) {
 | 
			
		||||
    auto res = parse_header(service_data);
 | 
			
		||||
    if (res->is_duplicate) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    if (!(parse_message(service_data.data, *res))) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    if (!(report_results(res, device.address_str()))) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    if (res->temperature.has_value() && this->temperature_ != nullptr)
 | 
			
		||||
      this->temperature_->publish_state(*res->temperature);
 | 
			
		||||
    if (res->humidity.has_value() && this->humidity_ != nullptr)
 | 
			
		||||
      this->humidity_->publish_state(*res->humidity);
 | 
			
		||||
    if (res->battery_level.has_value() && this->battery_level_ != nullptr)
 | 
			
		||||
      this->battery_level_->publish_state(*res->battery_level);
 | 
			
		||||
    if (res->battery_voltage.has_value() && this->battery_voltage_ != nullptr)
 | 
			
		||||
      this->battery_voltage_->publish_state(*res->battery_voltage);
 | 
			
		||||
    success = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!success) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
optional<ParseResult> ATCMiThermometer::parse_header(const esp32_ble_tracker::ServiceData &service_data) {
 | 
			
		||||
  ParseResult result;
 | 
			
		||||
  if (!service_data.uuid.contains(0x1A, 0x18)) {
 | 
			
		||||
    ESP_LOGVV(TAG, "parse_header(): no service data UUID magic bytes.");
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto raw = service_data.data;
 | 
			
		||||
 | 
			
		||||
  static uint8_t last_frame_count = 0;
 | 
			
		||||
  if (last_frame_count == raw[12]) {
 | 
			
		||||
    ESP_LOGVV(TAG, "parse_header(): duplicate data packet received (%d).", static_cast<int>(last_frame_count));
 | 
			
		||||
    result.is_duplicate = true;
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
  last_frame_count = raw[12];
 | 
			
		||||
  result.is_duplicate = false;
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ATCMiThermometer::parse_message(const std::vector<uint8_t> &message, ParseResult &result) {
 | 
			
		||||
  // Byte 0-5 mac in correct order
 | 
			
		||||
  // Byte 6-7 Temperature in uint16
 | 
			
		||||
  // Byte 8 Humidity in percent
 | 
			
		||||
  // Byte 9 Battery in percent
 | 
			
		||||
  // Byte 10-11 Battery in mV uint16_t
 | 
			
		||||
  // Byte 12 frame packet counter
 | 
			
		||||
 | 
			
		||||
  const uint8_t *data = message.data();
 | 
			
		||||
  const int data_length = 13;
 | 
			
		||||
 | 
			
		||||
  if (message.size() != data_length) {
 | 
			
		||||
    ESP_LOGVV(TAG, "parse_message(): payload has wrong size (%d)!", message.size());
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C
 | 
			
		||||
  const int16_t temperature = uint16_t(data[7]) | (uint16_t(data[6]) << 8);
 | 
			
		||||
  result.temperature = temperature / 10.0f;
 | 
			
		||||
 | 
			
		||||
  // humidity, 1 byte, 8-bit unsigned integer, 1.0 %
 | 
			
		||||
  result.humidity = data[8];
 | 
			
		||||
 | 
			
		||||
  // battery, 1 byte, 8-bit unsigned integer,  1.0 %
 | 
			
		||||
  result.battery_level = data[9];
 | 
			
		||||
 | 
			
		||||
  // battery, 2 bytes, 16-bit unsigned integer,  0.001 V
 | 
			
		||||
  const int16_t battery_voltage = uint16_t(data[11]) | (uint16_t(data[10]) << 8);
 | 
			
		||||
  result.battery_voltage = battery_voltage / 1.0e3f;
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ATCMiThermometer::report_results(const optional<ParseResult> &result, const std::string &address) {
 | 
			
		||||
  if (!result.has_value()) {
 | 
			
		||||
    ESP_LOGVV(TAG, "report_results(): no results available.");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Got ATC MiThermometer (%s):", address.c_str());
 | 
			
		||||
 | 
			
		||||
  if (result->temperature.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, "  Temperature: %.1f °C", *result->temperature);
 | 
			
		||||
  }
 | 
			
		||||
  if (result->humidity.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, "  Humidity: %.0f %%", *result->humidity);
 | 
			
		||||
  }
 | 
			
		||||
  if (result->battery_level.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, "  Battery Level: %.0f %%", *result->battery_level);
 | 
			
		||||
  }
 | 
			
		||||
  if (result->battery_voltage.has_value()) {
 | 
			
		||||
    ESP_LOGD(TAG, "  Battery Voltage: %.3f V", *result->battery_voltage);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace atc_mithermometer
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										48
									
								
								esphome/components/atc_mithermometer/atc_mithermometer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								esphome/components/atc_mithermometer/atc_mithermometer.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
 | 
			
		||||
#ifdef ARDUINO_ARCH_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace atc_mithermometer {
 | 
			
		||||
 | 
			
		||||
struct ParseResult {
 | 
			
		||||
  optional<float> temperature;
 | 
			
		||||
  optional<float> humidity;
 | 
			
		||||
  optional<float> battery_level;
 | 
			
		||||
  optional<float> battery_voltage;
 | 
			
		||||
  bool is_duplicate;
 | 
			
		||||
  int raw_offset;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_address(uint64_t address) { address_ = address; };
 | 
			
		||||
 | 
			
		||||
  bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
			
		||||
  void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
 | 
			
		||||
  void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
 | 
			
		||||
  void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; }
 | 
			
		||||
  void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint64_t address_;
 | 
			
		||||
  sensor::Sensor *temperature_{nullptr};
 | 
			
		||||
  sensor::Sensor *humidity_{nullptr};
 | 
			
		||||
  sensor::Sensor *battery_level_{nullptr};
 | 
			
		||||
  sensor::Sensor *battery_voltage_{nullptr};
 | 
			
		||||
 | 
			
		||||
  optional<ParseResult> parse_header(const esp32_ble_tracker::ServiceData &service_data);
 | 
			
		||||
  bool parse_message(const std::vector<uint8_t> &message, ParseResult &result);
 | 
			
		||||
  bool report_results(const optional<ParseResult> &result, const std::string &address);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace atc_mithermometer
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										72
									
								
								esphome/components/atc_mithermometer/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								esphome/components/atc_mithermometer/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, esp32_ble_tracker
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_BATTERY_LEVEL,
 | 
			
		||||
    CONF_BATTERY_VOLTAGE,
 | 
			
		||||
    CONF_MAC_ADDRESS,
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@ahpohl"]
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32_ble_tracker"]
 | 
			
		||||
 | 
			
		||||
atc_mithermometer_ns = cg.esphome_ns.namespace("atc_mithermometer")
 | 
			
		||||
ATCMiThermometer = atc_mithermometer_ns.class_(
 | 
			
		||||
    "ATCMiThermometer", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(ATCMiThermometer),
 | 
			
		||||
            cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
 | 
			
		||||
            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
			
		||||
                UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_HUMIDITY
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
 | 
			
		||||
                UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_VOLT, ICON_EMPTY, 3, DEVICE_CLASS_VOLTAGE
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield esp32_ble_tracker.register_ble_device(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
 | 
			
		||||
 | 
			
		||||
    if CONF_TEMPERATURE in config:
 | 
			
		||||
        sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
 | 
			
		||||
        cg.add(var.set_temperature(sens))
 | 
			
		||||
    if CONF_HUMIDITY in config:
 | 
			
		||||
        sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
 | 
			
		||||
        cg.add(var.set_humidity(sens))
 | 
			
		||||
    if CONF_BATTERY_LEVEL in config:
 | 
			
		||||
        sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
 | 
			
		||||
        cg.add(var.set_battery_level(sens))
 | 
			
		||||
    if CONF_BATTERY_VOLTAGE in config:
 | 
			
		||||
        sens = yield sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
 | 
			
		||||
        cg.add(var.set_battery_voltage(sens))
 | 
			
		||||
@@ -1,61 +1,106 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, spi
 | 
			
		||||
from esphome.const import \
 | 
			
		||||
    CONF_ID, CONF_VOLTAGE, CONF_CURRENT, CONF_POWER, CONF_POWER_FACTOR, CONF_FREQUENCY, \
 | 
			
		||||
    ICON_FLASH, ICON_LIGHTBULB, ICON_CURRENT_AC, ICON_THERMOMETER, \
 | 
			
		||||
    UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, UNIT_EMPTY, UNIT_CELSIUS, UNIT_VOLT_AMPS_REACTIVE
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_VOLTAGE,
 | 
			
		||||
    CONF_CURRENT,
 | 
			
		||||
    CONF_POWER,
 | 
			
		||||
    CONF_POWER_FACTOR,
 | 
			
		||||
    CONF_FREQUENCY,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_POWER_FACTOR,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    ICON_LIGHTBULB,
 | 
			
		||||
    ICON_CURRENT_AC,
 | 
			
		||||
    UNIT_HERTZ,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
    UNIT_EMPTY,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    UNIT_VOLT_AMPS_REACTIVE,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_PHASE_A = 'phase_a'
 | 
			
		||||
CONF_PHASE_B = 'phase_b'
 | 
			
		||||
CONF_PHASE_C = 'phase_c'
 | 
			
		||||
CONF_PHASE_A = "phase_a"
 | 
			
		||||
CONF_PHASE_B = "phase_b"
 | 
			
		||||
CONF_PHASE_C = "phase_c"
 | 
			
		||||
 | 
			
		||||
CONF_REACTIVE_POWER = 'reactive_power'
 | 
			
		||||
CONF_LINE_FREQUENCY = 'line_frequency'
 | 
			
		||||
CONF_CHIP_TEMPERATURE = 'chip_temperature'
 | 
			
		||||
CONF_GAIN_PGA = 'gain_pga'
 | 
			
		||||
CONF_CURRENT_PHASES = 'current_phases'
 | 
			
		||||
CONF_GAIN_VOLTAGE = 'gain_voltage'
 | 
			
		||||
CONF_GAIN_CT = 'gain_ct'
 | 
			
		||||
CONF_REACTIVE_POWER = "reactive_power"
 | 
			
		||||
CONF_LINE_FREQUENCY = "line_frequency"
 | 
			
		||||
CONF_CHIP_TEMPERATURE = "chip_temperature"
 | 
			
		||||
CONF_GAIN_PGA = "gain_pga"
 | 
			
		||||
CONF_CURRENT_PHASES = "current_phases"
 | 
			
		||||
CONF_GAIN_VOLTAGE = "gain_voltage"
 | 
			
		||||
CONF_GAIN_CT = "gain_ct"
 | 
			
		||||
LINE_FREQS = {
 | 
			
		||||
    '50HZ': 50,
 | 
			
		||||
    '60HZ': 60,
 | 
			
		||||
    "50HZ": 50,
 | 
			
		||||
    "60HZ": 60,
 | 
			
		||||
}
 | 
			
		||||
CURRENT_PHASES = {
 | 
			
		||||
    '2': 2,
 | 
			
		||||
    '3': 3,
 | 
			
		||||
    "2": 2,
 | 
			
		||||
    "3": 3,
 | 
			
		||||
}
 | 
			
		||||
PGA_GAINS = {
 | 
			
		||||
    '1X': 0x0,
 | 
			
		||||
    '2X': 0x15,
 | 
			
		||||
    '4X': 0x2A,
 | 
			
		||||
    "1X": 0x0,
 | 
			
		||||
    "2X": 0x15,
 | 
			
		||||
    "4X": 0x2A,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
atm90e32_ns = cg.esphome_ns.namespace('atm90e32')
 | 
			
		||||
ATM90E32Component = atm90e32_ns.class_('ATM90E32Component', cg.PollingComponent, spi.SPIDevice)
 | 
			
		||||
atm90e32_ns = cg.esphome_ns.namespace("atm90e32")
 | 
			
		||||
ATM90E32Component = atm90e32_ns.class_(
 | 
			
		||||
    "ATM90E32Component", cg.PollingComponent, spi.SPIDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
ATM90E32_PHASE_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2),
 | 
			
		||||
    cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_CURRENT_AC, 2),
 | 
			
		||||
    cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2),
 | 
			
		||||
    cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(UNIT_VOLT_AMPS_REACTIVE,
 | 
			
		||||
                                                           ICON_LIGHTBULB, 2),
 | 
			
		||||
    cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 2),
 | 
			
		||||
    cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
 | 
			
		||||
    cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
 | 
			
		||||
})
 | 
			
		||||
ATM90E32_PHASE_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
 | 
			
		||||
            UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_CURRENT): sensor.sensor_schema(
 | 
			
		||||
            UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_POWER): sensor.sensor_schema(
 | 
			
		||||
            UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
 | 
			
		||||
            UNIT_VOLT_AMPS_REACTIVE, ICON_LIGHTBULB, 2, DEVICE_CLASS_EMPTY
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
 | 
			
		||||
            UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_POWER_FACTOR
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
 | 
			
		||||
        cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(ATM90E32Component),
 | 
			
		||||
    cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_SCHEMA,
 | 
			
		||||
    cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
 | 
			
		||||
    cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
 | 
			
		||||
    cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(UNIT_HERTZ, ICON_CURRENT_AC, 1),
 | 
			
		||||
    cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
 | 
			
		||||
    cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
 | 
			
		||||
    cv.Optional(CONF_CURRENT_PHASES, default='3'): cv.enum(CURRENT_PHASES, upper=True),
 | 
			
		||||
    cv.Optional(CONF_GAIN_PGA, default='2X'): cv.enum(PGA_GAINS, upper=True),
 | 
			
		||||
}).extend(cv.polling_component_schema('60s')).extend(spi.spi_device_schema())
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(ATM90E32Component),
 | 
			
		||||
            cv.Optional(CONF_PHASE_A): ATM90E32_PHASE_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
 | 
			
		||||
                UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
 | 
			
		||||
            cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(
 | 
			
		||||
                CURRENT_PHASES, upper=True
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(spi.spi_device_schema())
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
CODEOWNERS = ['@OttoWinter']
 | 
			
		||||
CODEOWNERS = ["@OttoWinter"]
 | 
			
		||||
 
 | 
			
		||||
@@ -2,27 +2,41 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.components import climate, sensor
 | 
			
		||||
from esphome.const import CONF_AWAY_CONFIG, CONF_COOL_ACTION, \
 | 
			
		||||
    CONF_DEFAULT_TARGET_TEMPERATURE_HIGH, CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION, \
 | 
			
		||||
    CONF_ID, CONF_IDLE_ACTION, CONF_SENSOR
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_AWAY_CONFIG,
 | 
			
		||||
    CONF_COOL_ACTION,
 | 
			
		||||
    CONF_DEFAULT_TARGET_TEMPERATURE_HIGH,
 | 
			
		||||
    CONF_DEFAULT_TARGET_TEMPERATURE_LOW,
 | 
			
		||||
    CONF_HEAT_ACTION,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_IDLE_ACTION,
 | 
			
		||||
    CONF_SENSOR,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
bang_bang_ns = cg.esphome_ns.namespace('bang_bang')
 | 
			
		||||
BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.Climate, cg.Component)
 | 
			
		||||
BangBangClimateTargetTempConfig = bang_bang_ns.struct('BangBangClimateTargetTempConfig')
 | 
			
		||||
bang_bang_ns = cg.esphome_ns.namespace("bang_bang")
 | 
			
		||||
BangBangClimate = bang_bang_ns.class_("BangBangClimate", climate.Climate, cg.Component)
 | 
			
		||||
BangBangClimateTargetTempConfig = bang_bang_ns.struct("BangBangClimateTargetTempConfig")
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(BangBangClimate),
 | 
			
		||||
    cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
 | 
			
		||||
    cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
 | 
			
		||||
    cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
 | 
			
		||||
    cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True),
 | 
			
		||||
    cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True),
 | 
			
		||||
    cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True),
 | 
			
		||||
    cv.Optional(CONF_AWAY_CONFIG): cv.Schema({
 | 
			
		||||
        cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
 | 
			
		||||
        cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
 | 
			
		||||
    }),
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION))
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    climate.CLIMATE_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BangBangClimate),
 | 
			
		||||
            cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
 | 
			
		||||
            cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
 | 
			
		||||
            cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
 | 
			
		||||
            cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True),
 | 
			
		||||
            cv.Optional(CONF_COOL_ACTION): automation.validate_automation(single=True),
 | 
			
		||||
            cv.Optional(CONF_HEAT_ACTION): automation.validate_automation(single=True),
 | 
			
		||||
            cv.Optional(CONF_AWAY_CONFIG): cv.Schema(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
 | 
			
		||||
                    cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_HIGH): cv.temperature,
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
@@ -35,23 +49,29 @@ def to_code(config):
 | 
			
		||||
 | 
			
		||||
    normal_config = BangBangClimateTargetTempConfig(
 | 
			
		||||
        config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
 | 
			
		||||
        config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]
 | 
			
		||||
        config[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],
 | 
			
		||||
    )
 | 
			
		||||
    cg.add(var.set_normal_config(normal_config))
 | 
			
		||||
 | 
			
		||||
    yield automation.build_automation(var.get_idle_trigger(), [], config[CONF_IDLE_ACTION])
 | 
			
		||||
    yield automation.build_automation(
 | 
			
		||||
        var.get_idle_trigger(), [], config[CONF_IDLE_ACTION]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if CONF_COOL_ACTION in config:
 | 
			
		||||
        yield automation.build_automation(var.get_cool_trigger(), [], config[CONF_COOL_ACTION])
 | 
			
		||||
        yield automation.build_automation(
 | 
			
		||||
            var.get_cool_trigger(), [], config[CONF_COOL_ACTION]
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_supports_cool(True))
 | 
			
		||||
    if CONF_HEAT_ACTION in config:
 | 
			
		||||
        yield automation.build_automation(var.get_heat_trigger(), [], config[CONF_HEAT_ACTION])
 | 
			
		||||
        yield automation.build_automation(
 | 
			
		||||
            var.get_heat_trigger(), [], config[CONF_HEAT_ACTION]
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_supports_heat(True))
 | 
			
		||||
 | 
			
		||||
    if CONF_AWAY_CONFIG in config:
 | 
			
		||||
        away = config[CONF_AWAY_CONFIG]
 | 
			
		||||
        away_config = BangBangClimateTargetTempConfig(
 | 
			
		||||
            away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
 | 
			
		||||
            away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH]
 | 
			
		||||
            away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_away_config(away_config))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,45 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
from esphome.const import CONF_ID, CONF_RESOLUTION, UNIT_LUX, ICON_BRIGHTNESS_5
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_RESOLUTION,
 | 
			
		||||
    DEVICE_CLASS_ILLUMINANCE,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    UNIT_LUX,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
bh1750_ns = cg.esphome_ns.namespace('bh1750')
 | 
			
		||||
BH1750Resolution = bh1750_ns.enum('BH1750Resolution')
 | 
			
		||||
bh1750_ns = cg.esphome_ns.namespace("bh1750")
 | 
			
		||||
BH1750Resolution = bh1750_ns.enum("BH1750Resolution")
 | 
			
		||||
BH1750_RESOLUTIONS = {
 | 
			
		||||
    4.0: BH1750Resolution.BH1750_RESOLUTION_4P0_LX,
 | 
			
		||||
    1.0: BH1750Resolution.BH1750_RESOLUTION_1P0_LX,
 | 
			
		||||
    0.5: BH1750Resolution.BH1750_RESOLUTION_0P5_LX,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BH1750Sensor = bh1750_ns.class_('BH1750Sensor', sensor.Sensor, cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
BH1750Sensor = bh1750_ns.class_(
 | 
			
		||||
    "BH1750Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_MEASUREMENT_TIME = 'measurement_time'
 | 
			
		||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(BH1750Sensor),
 | 
			
		||||
    cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(BH1750_RESOLUTIONS, float=True),
 | 
			
		||||
    cv.Optional(CONF_MEASUREMENT_TIME, default=69): cv.int_range(min=31, max=254),
 | 
			
		||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x23))
 | 
			
		||||
CONF_MEASUREMENT_TIME = "measurement_time"
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    sensor.sensor_schema(UNIT_LUX, ICON_EMPTY, 1, DEVICE_CLASS_ILLUMINANCE)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BH1750Sensor),
 | 
			
		||||
            cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(
 | 
			
		||||
                BH1750_RESOLUTIONS, float=True
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_MEASUREMENT_TIME, default=69): cv.int_range(
 | 
			
		||||
                min=31, max=254
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x23))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
binary_ns = cg.esphome_ns.namespace('binary')
 | 
			
		||||
binary_ns = cg.esphome_ns.namespace("binary")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,24 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import fan, output
 | 
			
		||||
from esphome.const import CONF_DIRECTION_OUTPUT, CONF_OSCILLATION_OUTPUT, \
 | 
			
		||||
    CONF_OUTPUT, CONF_OUTPUT_ID
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DIRECTION_OUTPUT,
 | 
			
		||||
    CONF_OSCILLATION_OUTPUT,
 | 
			
		||||
    CONF_OUTPUT,
 | 
			
		||||
    CONF_OUTPUT_ID,
 | 
			
		||||
)
 | 
			
		||||
from .. import binary_ns
 | 
			
		||||
 | 
			
		||||
BinaryFan = binary_ns.class_('BinaryFan', cg.Component)
 | 
			
		||||
BinaryFan = binary_ns.class_("BinaryFan", cg.Component)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan),
 | 
			
		||||
    cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
 | 
			
		||||
    cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
 | 
			
		||||
    cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan),
 | 
			
		||||
        cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
 | 
			
		||||
        cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
 | 
			
		||||
        cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ void binary::BinaryFan::dump_config() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BinaryFan::setup() {
 | 
			
		||||
  auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr);
 | 
			
		||||
  auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0);
 | 
			
		||||
  this->fan_->set_traits(traits);
 | 
			
		||||
  this->fan_->add_on_state_callback([this]() { this->next_update_ = true; });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,12 +4,14 @@ from esphome.components import light, output
 | 
			
		||||
from esphome.const import CONF_OUTPUT_ID, CONF_OUTPUT
 | 
			
		||||
from .. import binary_ns
 | 
			
		||||
 | 
			
		||||
BinaryLightOutput = binary_ns.class_('BinaryLightOutput', light.LightOutput)
 | 
			
		||||
BinaryLightOutput = binary_ns.class_("BinaryLightOutput", light.LightOutput)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryLightOutput),
 | 
			
		||||
    cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
 | 
			
		||||
})
 | 
			
		||||
CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryLightOutput),
 | 
			
		||||
        cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -3,130 +3,214 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation, core
 | 
			
		||||
from esphome.automation import Condition, maybe_simple_id
 | 
			
		||||
from esphome.components import mqtt
 | 
			
		||||
from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \
 | 
			
		||||
    CONF_ID, CONF_INTERNAL, CONF_INVALID_COOLDOWN, CONF_INVERTED, \
 | 
			
		||||
    CONF_MAX_LENGTH, CONF_MIN_LENGTH, CONF_ON_CLICK, \
 | 
			
		||||
    CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \
 | 
			
		||||
    CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DEVICE_CLASS,
 | 
			
		||||
    CONF_FILTERS,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INTERNAL,
 | 
			
		||||
    CONF_INVALID_COOLDOWN,
 | 
			
		||||
    CONF_INVERTED,
 | 
			
		||||
    CONF_MAX_LENGTH,
 | 
			
		||||
    CONF_MIN_LENGTH,
 | 
			
		||||
    CONF_ON_CLICK,
 | 
			
		||||
    CONF_ON_DOUBLE_CLICK,
 | 
			
		||||
    CONF_ON_MULTI_CLICK,
 | 
			
		||||
    CONF_ON_PRESS,
 | 
			
		||||
    CONF_ON_RELEASE,
 | 
			
		||||
    CONF_ON_STATE,
 | 
			
		||||
    CONF_STATE,
 | 
			
		||||
    CONF_TIMING,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_FOR,
 | 
			
		||||
    CONF_NAME,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_BATTERY_CHARGING,
 | 
			
		||||
    DEVICE_CLASS_COLD,
 | 
			
		||||
    DEVICE_CLASS_CONNECTIVITY,
 | 
			
		||||
    DEVICE_CLASS_DOOR,
 | 
			
		||||
    DEVICE_CLASS_GARAGE_DOOR,
 | 
			
		||||
    DEVICE_CLASS_GAS,
 | 
			
		||||
    DEVICE_CLASS_HEAT,
 | 
			
		||||
    DEVICE_CLASS_LIGHT,
 | 
			
		||||
    DEVICE_CLASS_LOCK,
 | 
			
		||||
    DEVICE_CLASS_MOISTURE,
 | 
			
		||||
    DEVICE_CLASS_MOTION,
 | 
			
		||||
    DEVICE_CLASS_MOVING,
 | 
			
		||||
    DEVICE_CLASS_OCCUPANCY,
 | 
			
		||||
    DEVICE_CLASS_OPENING,
 | 
			
		||||
    DEVICE_CLASS_PLUG,
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_PRESENCE,
 | 
			
		||||
    DEVICE_CLASS_PROBLEM,
 | 
			
		||||
    DEVICE_CLASS_SAFETY,
 | 
			
		||||
    DEVICE_CLASS_SMOKE,
 | 
			
		||||
    DEVICE_CLASS_SOUND,
 | 
			
		||||
    DEVICE_CLASS_VIBRATION,
 | 
			
		||||
    DEVICE_CLASS_WINDOW,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine, coroutine_with_priority
 | 
			
		||||
from esphome.util import Registry
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ['@esphome/core']
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
DEVICE_CLASSES = [
 | 
			
		||||
    '', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas',
 | 
			
		||||
    'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy',
 | 
			
		||||
    'opening', 'plug', 'power', 'presence', 'problem', 'safety', 'smoke',
 | 
			
		||||
    'sound', 'vibration', 'window'
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_BATTERY_CHARGING,
 | 
			
		||||
    DEVICE_CLASS_COLD,
 | 
			
		||||
    DEVICE_CLASS_CONNECTIVITY,
 | 
			
		||||
    DEVICE_CLASS_DOOR,
 | 
			
		||||
    DEVICE_CLASS_GARAGE_DOOR,
 | 
			
		||||
    DEVICE_CLASS_GAS,
 | 
			
		||||
    DEVICE_CLASS_HEAT,
 | 
			
		||||
    DEVICE_CLASS_LIGHT,
 | 
			
		||||
    DEVICE_CLASS_LOCK,
 | 
			
		||||
    DEVICE_CLASS_MOISTURE,
 | 
			
		||||
    DEVICE_CLASS_MOTION,
 | 
			
		||||
    DEVICE_CLASS_MOVING,
 | 
			
		||||
    DEVICE_CLASS_OCCUPANCY,
 | 
			
		||||
    DEVICE_CLASS_OPENING,
 | 
			
		||||
    DEVICE_CLASS_PLUG,
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_PRESENCE,
 | 
			
		||||
    DEVICE_CLASS_PROBLEM,
 | 
			
		||||
    DEVICE_CLASS_SAFETY,
 | 
			
		||||
    DEVICE_CLASS_SMOKE,
 | 
			
		||||
    DEVICE_CLASS_SOUND,
 | 
			
		||||
    DEVICE_CLASS_VIBRATION,
 | 
			
		||||
    DEVICE_CLASS_WINDOW,
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
binary_sensor_ns = cg.esphome_ns.namespace('binary_sensor')
 | 
			
		||||
BinarySensor = binary_sensor_ns.class_('BinarySensor', cg.Nameable)
 | 
			
		||||
BinarySensorInitiallyOff = binary_sensor_ns.class_('BinarySensorInitiallyOff', BinarySensor)
 | 
			
		||||
BinarySensorPtr = BinarySensor.operator('ptr')
 | 
			
		||||
binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor")
 | 
			
		||||
BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.Nameable)
 | 
			
		||||
BinarySensorInitiallyOff = binary_sensor_ns.class_(
 | 
			
		||||
    "BinarySensorInitiallyOff", BinarySensor
 | 
			
		||||
)
 | 
			
		||||
BinarySensorPtr = BinarySensor.operator("ptr")
 | 
			
		||||
 | 
			
		||||
# Triggers
 | 
			
		||||
PressTrigger = binary_sensor_ns.class_('PressTrigger', automation.Trigger.template())
 | 
			
		||||
ReleaseTrigger = binary_sensor_ns.class_('ReleaseTrigger', automation.Trigger.template())
 | 
			
		||||
ClickTrigger = binary_sensor_ns.class_('ClickTrigger', automation.Trigger.template())
 | 
			
		||||
DoubleClickTrigger = binary_sensor_ns.class_('DoubleClickTrigger', automation.Trigger.template())
 | 
			
		||||
MultiClickTrigger = binary_sensor_ns.class_('MultiClickTrigger', automation.Trigger.template(),
 | 
			
		||||
                                            cg.Component)
 | 
			
		||||
MultiClickTriggerEvent = binary_sensor_ns.struct('MultiClickTriggerEvent')
 | 
			
		||||
StateTrigger = binary_sensor_ns.class_('StateTrigger', automation.Trigger.template(bool))
 | 
			
		||||
BinarySensorPublishAction = binary_sensor_ns.class_('BinarySensorPublishAction', automation.Action)
 | 
			
		||||
PressTrigger = binary_sensor_ns.class_("PressTrigger", automation.Trigger.template())
 | 
			
		||||
ReleaseTrigger = binary_sensor_ns.class_(
 | 
			
		||||
    "ReleaseTrigger", automation.Trigger.template()
 | 
			
		||||
)
 | 
			
		||||
ClickTrigger = binary_sensor_ns.class_("ClickTrigger", automation.Trigger.template())
 | 
			
		||||
DoubleClickTrigger = binary_sensor_ns.class_(
 | 
			
		||||
    "DoubleClickTrigger", automation.Trigger.template()
 | 
			
		||||
)
 | 
			
		||||
MultiClickTrigger = binary_sensor_ns.class_(
 | 
			
		||||
    "MultiClickTrigger", automation.Trigger.template(), cg.Component
 | 
			
		||||
)
 | 
			
		||||
MultiClickTriggerEvent = binary_sensor_ns.struct("MultiClickTriggerEvent")
 | 
			
		||||
StateTrigger = binary_sensor_ns.class_(
 | 
			
		||||
    "StateTrigger", automation.Trigger.template(bool)
 | 
			
		||||
)
 | 
			
		||||
BinarySensorPublishAction = binary_sensor_ns.class_(
 | 
			
		||||
    "BinarySensorPublishAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Condition
 | 
			
		||||
BinarySensorCondition = binary_sensor_ns.class_('BinarySensorCondition', Condition)
 | 
			
		||||
BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Condition)
 | 
			
		||||
 | 
			
		||||
# Filters
 | 
			
		||||
Filter = binary_sensor_ns.class_('Filter')
 | 
			
		||||
DelayedOnOffFilter = binary_sensor_ns.class_('DelayedOnOffFilter', Filter, cg.Component)
 | 
			
		||||
DelayedOnFilter = binary_sensor_ns.class_('DelayedOnFilter', Filter, cg.Component)
 | 
			
		||||
DelayedOffFilter = binary_sensor_ns.class_('DelayedOffFilter', Filter, cg.Component)
 | 
			
		||||
InvertFilter = binary_sensor_ns.class_('InvertFilter', Filter)
 | 
			
		||||
LambdaFilter = binary_sensor_ns.class_('LambdaFilter', Filter)
 | 
			
		||||
Filter = binary_sensor_ns.class_("Filter")
 | 
			
		||||
DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component)
 | 
			
		||||
DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component)
 | 
			
		||||
DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component)
 | 
			
		||||
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
 | 
			
		||||
LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter)
 | 
			
		||||
 | 
			
		||||
FILTER_REGISTRY = Registry()
 | 
			
		||||
validate_filters = cv.validate_registry('filter', FILTER_REGISTRY)
 | 
			
		||||
validate_filters = cv.validate_registry("filter", FILTER_REGISTRY)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@FILTER_REGISTRY.register('invert', InvertFilter, {})
 | 
			
		||||
@FILTER_REGISTRY.register("invert", InvertFilter, {})
 | 
			
		||||
def invert_filter_to_code(config, filter_id):
 | 
			
		||||
    yield cg.new_Pvariable(filter_id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@FILTER_REGISTRY.register('delayed_on_off', DelayedOnOffFilter,
 | 
			
		||||
                          cv.positive_time_period_milliseconds)
 | 
			
		||||
@FILTER_REGISTRY.register(
 | 
			
		||||
    "delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds
 | 
			
		||||
)
 | 
			
		||||
def delayed_on_off_filter_to_code(config, filter_id):
 | 
			
		||||
    var = cg.new_Pvariable(filter_id, config)
 | 
			
		||||
    yield cg.register_component(var, {})
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@FILTER_REGISTRY.register('delayed_on', DelayedOnFilter,
 | 
			
		||||
                          cv.positive_time_period_milliseconds)
 | 
			
		||||
@FILTER_REGISTRY.register(
 | 
			
		||||
    "delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds
 | 
			
		||||
)
 | 
			
		||||
def delayed_on_filter_to_code(config, filter_id):
 | 
			
		||||
    var = cg.new_Pvariable(filter_id, config)
 | 
			
		||||
    yield cg.register_component(var, {})
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@FILTER_REGISTRY.register('delayed_off', DelayedOffFilter, cv.positive_time_period_milliseconds)
 | 
			
		||||
@FILTER_REGISTRY.register(
 | 
			
		||||
    "delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds
 | 
			
		||||
)
 | 
			
		||||
def delayed_off_filter_to_code(config, filter_id):
 | 
			
		||||
    var = cg.new_Pvariable(filter_id, config)
 | 
			
		||||
    yield cg.register_component(var, {})
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@FILTER_REGISTRY.register('lambda', LambdaFilter, cv.returning_lambda)
 | 
			
		||||
@FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda)
 | 
			
		||||
def lambda_filter_to_code(config, filter_id):
 | 
			
		||||
    lambda_ = yield cg.process_lambda(config, [(bool, 'x')], return_type=cg.optional.template(bool))
 | 
			
		||||
    lambda_ = yield cg.process_lambda(
 | 
			
		||||
        config, [(bool, "x")], return_type=cg.optional.template(bool)
 | 
			
		||||
    )
 | 
			
		||||
    yield cg.new_Pvariable(filter_id, lambda_)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MULTI_CLICK_TIMING_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.Optional(CONF_STATE): cv.boolean,
 | 
			
		||||
    cv.Optional(CONF_MIN_LENGTH): cv.positive_time_period_milliseconds,
 | 
			
		||||
    cv.Optional(CONF_MAX_LENGTH): cv.positive_time_period_milliseconds,
 | 
			
		||||
})
 | 
			
		||||
MULTI_CLICK_TIMING_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_STATE): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_MIN_LENGTH): cv.positive_time_period_milliseconds,
 | 
			
		||||
        cv.Optional(CONF_MAX_LENGTH): cv.positive_time_period_milliseconds,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def parse_multi_click_timing_str(value):
 | 
			
		||||
    if not isinstance(value, str):
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
    parts = value.lower().split(' ')
 | 
			
		||||
    parts = value.lower().split(" ")
 | 
			
		||||
    if len(parts) != 5:
 | 
			
		||||
        raise cv.Invalid("Multi click timing grammar consists of exactly 5 words, not {}"
 | 
			
		||||
                         "".format(len(parts)))
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "Multi click timing grammar consists of exactly 5 words, not {}"
 | 
			
		||||
            "".format(len(parts))
 | 
			
		||||
        )
 | 
			
		||||
    try:
 | 
			
		||||
        state = cv.boolean(parts[0])
 | 
			
		||||
    except cv.Invalid:
 | 
			
		||||
        # pylint: disable=raise-missing-from
 | 
			
		||||
        raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0]))
 | 
			
		||||
 | 
			
		||||
    if parts[1] != 'for':
 | 
			
		||||
    if parts[1] != "for":
 | 
			
		||||
        raise cv.Invalid("Second word must be 'for', got {}".format(parts[1]))
 | 
			
		||||
 | 
			
		||||
    if parts[2] == 'at':
 | 
			
		||||
        if parts[3] == 'least':
 | 
			
		||||
    if parts[2] == "at":
 | 
			
		||||
        if parts[3] == "least":
 | 
			
		||||
            key = CONF_MIN_LENGTH
 | 
			
		||||
        elif parts[3] == 'most':
 | 
			
		||||
        elif parts[3] == "most":
 | 
			
		||||
            key = CONF_MAX_LENGTH
 | 
			
		||||
        else:
 | 
			
		||||
            raise cv.Invalid("Third word after at must either be 'least' or 'most', got {}"
 | 
			
		||||
                             "".format(parts[3]))
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                "Third word after at must either be 'least' or 'most', got {}"
 | 
			
		||||
                "".format(parts[3])
 | 
			
		||||
            )
 | 
			
		||||
        try:
 | 
			
		||||
            length = cv.positive_time_period_milliseconds(parts[4])
 | 
			
		||||
        except cv.Invalid as err:
 | 
			
		||||
            raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {err}")
 | 
			
		||||
        return {
 | 
			
		||||
            CONF_STATE: state,
 | 
			
		||||
            key: str(length)
 | 
			
		||||
        }
 | 
			
		||||
        return {CONF_STATE: state, key: str(length)}
 | 
			
		||||
 | 
			
		||||
    if parts[3] != 'to':
 | 
			
		||||
    if parts[3] != "to":
 | 
			
		||||
        raise cv.Invalid("Multi click grammar: 4th word must be 'to'")
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
@@ -142,7 +226,7 @@ def parse_multi_click_timing_str(value):
 | 
			
		||||
    return {
 | 
			
		||||
        CONF_STATE: state,
 | 
			
		||||
        CONF_MIN_LENGTH: str(min_length),
 | 
			
		||||
        CONF_MAX_LENGTH: str(max_length)
 | 
			
		||||
        CONF_MAX_LENGTH: str(max_length),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -162,11 +246,15 @@ def validate_multi_click_timing(value):
 | 
			
		||||
 | 
			
		||||
        new_state = v_.get(CONF_STATE, not state)
 | 
			
		||||
        if new_state == state:
 | 
			
		||||
            raise cv.Invalid("Timings must have alternating state. Indices {} and {} have "
 | 
			
		||||
                             "the same state {}".format(i, i + 1, state))
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                "Timings must have alternating state. Indices {} and {} have "
 | 
			
		||||
                "the same state {}".format(i, i + 1, state)
 | 
			
		||||
            )
 | 
			
		||||
        if max_length is not None and max_length < min_length:
 | 
			
		||||
            raise cv.Invalid("Max length ({}) must be larger than min length ({})."
 | 
			
		||||
                             "".format(max_length, min_length))
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                "Max length ({}) must be larger than min length ({})."
 | 
			
		||||
                "".format(max_length, min_length)
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        state = new_state
 | 
			
		||||
        tim = {
 | 
			
		||||
@@ -179,46 +267,71 @@ def validate_multi_click_timing(value):
 | 
			
		||||
    return timings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space='_')
 | 
			
		||||
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
 | 
			
		||||
 | 
			
		||||
BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(BinarySensor),
 | 
			
		||||
    cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTBinarySensorComponent),
 | 
			
		||||
 | 
			
		||||
    cv.Optional(CONF_DEVICE_CLASS): device_class,
 | 
			
		||||
    cv.Optional(CONF_FILTERS): validate_filters,
 | 
			
		||||
    cv.Optional(CONF_ON_PRESS): automation.validate_automation({
 | 
			
		||||
        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
 | 
			
		||||
    }),
 | 
			
		||||
    cv.Optional(CONF_ON_RELEASE): automation.validate_automation({
 | 
			
		||||
        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
 | 
			
		||||
    }),
 | 
			
		||||
    cv.Optional(CONF_ON_CLICK): automation.validate_automation({
 | 
			
		||||
        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
 | 
			
		||||
        cv.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds,
 | 
			
		||||
        cv.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds,
 | 
			
		||||
    }),
 | 
			
		||||
    cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation({
 | 
			
		||||
        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
 | 
			
		||||
        cv.Optional(CONF_MIN_LENGTH, default='50ms'): cv.positive_time_period_milliseconds,
 | 
			
		||||
        cv.Optional(CONF_MAX_LENGTH, default='350ms'): cv.positive_time_period_milliseconds,
 | 
			
		||||
    }),
 | 
			
		||||
    cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation({
 | 
			
		||||
        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
 | 
			
		||||
        cv.Required(CONF_TIMING): cv.All([parse_multi_click_timing_str],
 | 
			
		||||
                                         validate_multi_click_timing),
 | 
			
		||||
        cv.Optional(CONF_INVALID_COOLDOWN, default='1s'): cv.positive_time_period_milliseconds,
 | 
			
		||||
    }),
 | 
			
		||||
    cv.Optional(CONF_ON_STATE): automation.validate_automation({
 | 
			
		||||
        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
 | 
			
		||||
    }),
 | 
			
		||||
 | 
			
		||||
    cv.Optional(CONF_INVERTED): cv.invalid(
 | 
			
		||||
        "The inverted binary_sensor property has been replaced by the "
 | 
			
		||||
        "new 'invert' binary  sensor filter. Please see "
 | 
			
		||||
        "https://esphome.io/components/binary_sensor/index.html."
 | 
			
		||||
    ),
 | 
			
		||||
})
 | 
			
		||||
BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(BinarySensor),
 | 
			
		||||
        cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
 | 
			
		||||
            mqtt.MQTTBinarySensorComponent
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_DEVICE_CLASS): device_class,
 | 
			
		||||
        cv.Optional(CONF_FILTERS): validate_filters,
 | 
			
		||||
        cv.Optional(CONF_ON_PRESS): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_CLICK): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_MIN_LENGTH, default="50ms"
 | 
			
		||||
                ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_MAX_LENGTH, default="350ms"
 | 
			
		||||
                ): cv.positive_time_period_milliseconds,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_MIN_LENGTH, default="50ms"
 | 
			
		||||
                ): cv.positive_time_period_milliseconds,
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_MAX_LENGTH, default="350ms"
 | 
			
		||||
                ): cv.positive_time_period_milliseconds,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
 | 
			
		||||
                cv.Required(CONF_TIMING): cv.All(
 | 
			
		||||
                    [parse_multi_click_timing_str], validate_multi_click_timing
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(
 | 
			
		||||
                    CONF_INVALID_COOLDOWN, default="1s"
 | 
			
		||||
                ): cv.positive_time_period_milliseconds,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_STATE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_INVERTED): cv.invalid(
 | 
			
		||||
            "The inverted binary_sensor property has been replaced by the "
 | 
			
		||||
            "new 'invert' binary  sensor filter. Please see "
 | 
			
		||||
            "https://esphome.io/components/binary_sensor/index.html."
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine
 | 
			
		||||
@@ -243,24 +356,28 @@ def setup_binary_sensor_core_(var, config):
 | 
			
		||||
        yield automation.build_automation(trigger, [], conf)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_CLICK, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var,
 | 
			
		||||
                                   conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH])
 | 
			
		||||
        trigger = cg.new_Pvariable(
 | 
			
		||||
            conf[CONF_TRIGGER_ID], var, conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]
 | 
			
		||||
        )
 | 
			
		||||
        yield automation.build_automation(trigger, [], conf)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_DOUBLE_CLICK, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var,
 | 
			
		||||
                                   conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH])
 | 
			
		||||
        trigger = cg.new_Pvariable(
 | 
			
		||||
            conf[CONF_TRIGGER_ID], var, conf[CONF_MIN_LENGTH], conf[CONF_MAX_LENGTH]
 | 
			
		||||
        )
 | 
			
		||||
        yield automation.build_automation(trigger, [], conf)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_MULTI_CLICK, []):
 | 
			
		||||
        timings = []
 | 
			
		||||
        for tim in conf[CONF_TIMING]:
 | 
			
		||||
            timings.append(cg.StructInitializer(
 | 
			
		||||
                MultiClickTriggerEvent,
 | 
			
		||||
                ('state', tim[CONF_STATE]),
 | 
			
		||||
                ('min_length', tim[CONF_MIN_LENGTH]),
 | 
			
		||||
                ('max_length', tim.get(CONF_MAX_LENGTH, 4294967294)),
 | 
			
		||||
            ))
 | 
			
		||||
            timings.append(
 | 
			
		||||
                cg.StructInitializer(
 | 
			
		||||
                    MultiClickTriggerEvent,
 | 
			
		||||
                    ("state", tim[CONF_STATE]),
 | 
			
		||||
                    ("min_length", tim[CONF_MIN_LENGTH]),
 | 
			
		||||
                    ("max_length", tim.get(CONF_MAX_LENGTH, 4294967294)),
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings)
 | 
			
		||||
        if CONF_INVALID_COOLDOWN in conf:
 | 
			
		||||
            cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN]))
 | 
			
		||||
@@ -269,7 +386,7 @@ def setup_binary_sensor_core_(var, config):
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_STATE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        yield automation.build_automation(trigger, [(bool, 'x')], conf)
 | 
			
		||||
        yield automation.build_automation(trigger, [(bool, "x")], conf)
 | 
			
		||||
 | 
			
		||||
    if CONF_MQTT_ID in config:
 | 
			
		||||
        mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
 | 
			
		||||
@@ -291,22 +408,28 @@ def new_binary_sensor(config):
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id({
 | 
			
		||||
    cv.Required(CONF_ID): cv.use_id(BinarySensor),
 | 
			
		||||
    cv.Optional(CONF_FOR): cv.invalid("This option has been removed in 1.13, please use the "
 | 
			
		||||
                                      "'for' condition instead."),
 | 
			
		||||
})
 | 
			
		||||
BINARY_SENSOR_CONDITION_SCHEMA = maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(BinarySensor),
 | 
			
		||||
        cv.Optional(CONF_FOR): cv.invalid(
 | 
			
		||||
            "This option has been removed in 1.13, please use the "
 | 
			
		||||
            "'for' condition instead."
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_condition('binary_sensor.is_on', BinarySensorCondition,
 | 
			
		||||
                               BINARY_SENSOR_CONDITION_SCHEMA)
 | 
			
		||||
@automation.register_condition(
 | 
			
		||||
    "binary_sensor.is_on", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
def binary_sensor_is_on_to_code(config, condition_id, template_arg, args):
 | 
			
		||||
    paren = yield cg.get_variable(config[CONF_ID])
 | 
			
		||||
    yield cg.new_Pvariable(condition_id, template_arg, paren, True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_condition('binary_sensor.is_off', BinarySensorCondition,
 | 
			
		||||
                               BINARY_SENSOR_CONDITION_SCHEMA)
 | 
			
		||||
@automation.register_condition(
 | 
			
		||||
    "binary_sensor.is_off", BinarySensorCondition, BINARY_SENSOR_CONDITION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
 | 
			
		||||
    paren = yield cg.get_variable(config[CONF_ID])
 | 
			
		||||
    yield cg.new_Pvariable(condition_id, template_arg, paren, False)
 | 
			
		||||
@@ -314,5 +437,5 @@ def binary_sensor_is_off_to_code(config, condition_id, template_arg, args):
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(100.0)
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    cg.add_define('USE_BINARY_SENSOR')
 | 
			
		||||
    cg.add_define("USE_BINARY_SENSOR")
 | 
			
		||||
    cg.add_global(binary_sensor_ns.using)
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,25 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
from esphome.components import sensor, binary_sensor
 | 
			
		||||
from esphome.const import CONF_ID, CONF_CHANNELS, CONF_VALUE, CONF_TYPE, UNIT_EMPTY, \
 | 
			
		||||
    ICON_CHECK_CIRCLE_OUTLINE, CONF_BINARY_SENSOR, CONF_GROUP
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_CHANNELS,
 | 
			
		||||
    CONF_VALUE,
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    UNIT_EMPTY,
 | 
			
		||||
    ICON_CHECK_CIRCLE_OUTLINE,
 | 
			
		||||
    CONF_BINARY_SENSOR,
 | 
			
		||||
    CONF_GROUP,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['binary_sensor']
 | 
			
		||||
DEPENDENCIES = ["binary_sensor"]
 | 
			
		||||
 | 
			
		||||
binary_sensor_map_ns = cg.esphome_ns.namespace('binary_sensor_map')
 | 
			
		||||
BinarySensorMap = binary_sensor_map_ns.class_('BinarySensorMap', cg.Component, sensor.Sensor)
 | 
			
		||||
SensorMapType = binary_sensor_map_ns.enum('SensorMapType')
 | 
			
		||||
binary_sensor_map_ns = cg.esphome_ns.namespace("binary_sensor_map")
 | 
			
		||||
BinarySensorMap = binary_sensor_map_ns.class_(
 | 
			
		||||
    "BinarySensorMap", cg.Component, sensor.Sensor
 | 
			
		||||
)
 | 
			
		||||
SensorMapType = binary_sensor_map_ns.enum("SensorMapType")
 | 
			
		||||
 | 
			
		||||
SENSOR_MAP_TYPES = {
 | 
			
		||||
    CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP,
 | 
			
		||||
@@ -20,12 +31,21 @@ entry = {
 | 
			
		||||
    cv.Required(CONF_VALUE): cv.float_,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.typed_schema({
 | 
			
		||||
    CONF_GROUP: sensor.sensor_schema(UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0).extend({
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(BinarySensorMap),
 | 
			
		||||
        cv.Required(CONF_CHANNELS): cv.All(cv.ensure_list(entry), cv.Length(min=1)),
 | 
			
		||||
    }),
 | 
			
		||||
}, lower=True)
 | 
			
		||||
CONFIG_SCHEMA = cv.typed_schema(
 | 
			
		||||
    {
 | 
			
		||||
        CONF_GROUP: sensor.sensor_schema(
 | 
			
		||||
            UNIT_EMPTY, ICON_CHECK_CIRCLE_OUTLINE, 0, DEVICE_CLASS_EMPTY
 | 
			
		||||
        ).extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(): cv.declare_id(BinarySensorMap),
 | 
			
		||||
                cv.Required(CONF_CHANNELS): cv.All(
 | 
			
		||||
                    cv.ensure_list(entry), cv.Length(min=1)
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    lower=True,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -3,18 +3,28 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import binary_sensor, esp32_ble_tracker
 | 
			
		||||
from esphome.const import CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['esp32_ble_tracker']
 | 
			
		||||
DEPENDENCIES = ["esp32_ble_tracker"]
 | 
			
		||||
 | 
			
		||||
ble_presence_ns = cg.esphome_ns.namespace('ble_presence')
 | 
			
		||||
BLEPresenceDevice = ble_presence_ns.class_('BLEPresenceDevice', binary_sensor.BinarySensor,
 | 
			
		||||
                                           cg.Component, esp32_ble_tracker.ESPBTDeviceListener)
 | 
			
		||||
ble_presence_ns = cg.esphome_ns.namespace("ble_presence")
 | 
			
		||||
BLEPresenceDevice = ble_presence_ns.class_(
 | 
			
		||||
    "BLEPresenceDevice",
 | 
			
		||||
    binary_sensor.BinarySensor,
 | 
			
		||||
    cg.Component,
 | 
			
		||||
    esp32_ble_tracker.ESPBTDeviceListener,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(BLEPresenceDevice),
 | 
			
		||||
    cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
 | 
			
		||||
    cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(
 | 
			
		||||
    cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID))
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    binary_sensor.BINARY_SENSOR_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BLEPresenceDevice),
 | 
			
		||||
            cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
 | 
			
		||||
            cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
@@ -28,9 +38,17 @@ def to_code(config):
 | 
			
		||||
 | 
			
		||||
    if CONF_SERVICE_UUID in config:
 | 
			
		||||
        if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
            cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])))
 | 
			
		||||
            cg.add(
 | 
			
		||||
                var.set_service_uuid16(
 | 
			
		||||
                    esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
 | 
			
		||||
            cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])))
 | 
			
		||||
            cg.add(
 | 
			
		||||
                var.set_service_uuid32(
 | 
			
		||||
                    esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
 | 
			
		||||
            uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
 | 
			
		||||
            cg.add(var.set_service_uuid128(uuid128))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,35 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, esp32_ble_tracker
 | 
			
		||||
from esphome.const import CONF_SERVICE_UUID, CONF_MAC_ADDRESS, CONF_ID, UNIT_DECIBEL, ICON_SIGNAL
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_SERVICE_UUID,
 | 
			
		||||
    CONF_MAC_ADDRESS,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    DEVICE_CLASS_SIGNAL_STRENGTH,
 | 
			
		||||
    UNIT_DECIBEL,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['esp32_ble_tracker']
 | 
			
		||||
DEPENDENCIES = ["esp32_ble_tracker"]
 | 
			
		||||
 | 
			
		||||
ble_rssi_ns = cg.esphome_ns.namespace('ble_rssi')
 | 
			
		||||
BLERSSISensor = ble_rssi_ns.class_('BLERSSISensor', sensor.Sensor, cg.Component,
 | 
			
		||||
                                   esp32_ble_tracker.ESPBTDeviceListener)
 | 
			
		||||
ble_rssi_ns = cg.esphome_ns.namespace("ble_rssi")
 | 
			
		||||
BLERSSISensor = ble_rssi_ns.class_(
 | 
			
		||||
    "BLERSSISensor", sensor.Sensor, cg.Component, esp32_ble_tracker.ESPBTDeviceListener
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_DECIBEL, ICON_SIGNAL, 0).extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(BLERSSISensor),
 | 
			
		||||
    cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
 | 
			
		||||
    cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(
 | 
			
		||||
    cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID))
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    sensor.sensor_schema(UNIT_DECIBEL, ICON_EMPTY, 0, DEVICE_CLASS_SIGNAL_STRENGTH)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BLERSSISensor),
 | 
			
		||||
            cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
 | 
			
		||||
            cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
@@ -28,9 +43,17 @@ def to_code(config):
 | 
			
		||||
 | 
			
		||||
    if CONF_SERVICE_UUID in config:
 | 
			
		||||
        if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
            cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])))
 | 
			
		||||
            cg.add(
 | 
			
		||||
                var.set_service_uuid16(
 | 
			
		||||
                    esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
 | 
			
		||||
            cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])))
 | 
			
		||||
            cg.add(
 | 
			
		||||
                var.set_service_uuid32(
 | 
			
		||||
                    esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
 | 
			
		||||
            uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
 | 
			
		||||
            cg.add(var.set_service_uuid128(uuid128))
 | 
			
		||||
 
 | 
			
		||||
@@ -3,16 +3,25 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import text_sensor, esp32_ble_tracker
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['esp32_ble_tracker']
 | 
			
		||||
DEPENDENCIES = ["esp32_ble_tracker"]
 | 
			
		||||
 | 
			
		||||
ble_scanner_ns = cg.esphome_ns.namespace('ble_scanner')
 | 
			
		||||
BLEScanner = ble_scanner_ns.class_('BLEScanner', text_sensor.TextSensor, cg.Component,
 | 
			
		||||
                                   esp32_ble_tracker.ESPBTDeviceListener)
 | 
			
		||||
ble_scanner_ns = cg.esphome_ns.namespace("ble_scanner")
 | 
			
		||||
BLEScanner = ble_scanner_ns.class_(
 | 
			
		||||
    "BLEScanner",
 | 
			
		||||
    text_sensor.TextSensor,
 | 
			
		||||
    cg.Component,
 | 
			
		||||
    esp32_ble_tracker.ESPBTDeviceListener,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(text_sensor.TEXT_SENSOR_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(BLEScanner),
 | 
			
		||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(
 | 
			
		||||
    cv.COMPONENT_SCHEMA))
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    text_sensor.TEXT_SENSOR_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BLEScanner),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,53 +1,87 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_OVERSAMPLING, \
 | 
			
		||||
    CONF_PRESSURE, CONF_TEMPERATURE, ICON_THERMOMETER, \
 | 
			
		||||
    UNIT_CELSIUS, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_IIR_FILTER,
 | 
			
		||||
    CONF_OVERSAMPLING,
 | 
			
		||||
    CONF_PRESSURE,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_PRESSURE,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    UNIT_HECTOPASCAL,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
bme280_ns = cg.esphome_ns.namespace('bme280')
 | 
			
		||||
BME280Oversampling = bme280_ns.enum('BME280Oversampling')
 | 
			
		||||
bme280_ns = cg.esphome_ns.namespace("bme280")
 | 
			
		||||
BME280Oversampling = bme280_ns.enum("BME280Oversampling")
 | 
			
		||||
OVERSAMPLING_OPTIONS = {
 | 
			
		||||
    'NONE': BME280Oversampling.BME280_OVERSAMPLING_NONE,
 | 
			
		||||
    '1X': BME280Oversampling.BME280_OVERSAMPLING_1X,
 | 
			
		||||
    '2X': BME280Oversampling.BME280_OVERSAMPLING_2X,
 | 
			
		||||
    '4X': BME280Oversampling.BME280_OVERSAMPLING_4X,
 | 
			
		||||
    '8X': BME280Oversampling.BME280_OVERSAMPLING_8X,
 | 
			
		||||
    '16X': BME280Oversampling.BME280_OVERSAMPLING_16X,
 | 
			
		||||
    "NONE": BME280Oversampling.BME280_OVERSAMPLING_NONE,
 | 
			
		||||
    "1X": BME280Oversampling.BME280_OVERSAMPLING_1X,
 | 
			
		||||
    "2X": BME280Oversampling.BME280_OVERSAMPLING_2X,
 | 
			
		||||
    "4X": BME280Oversampling.BME280_OVERSAMPLING_4X,
 | 
			
		||||
    "8X": BME280Oversampling.BME280_OVERSAMPLING_8X,
 | 
			
		||||
    "16X": BME280Oversampling.BME280_OVERSAMPLING_16X,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BME280IIRFilter = bme280_ns.enum('BME280IIRFilter')
 | 
			
		||||
BME280IIRFilter = bme280_ns.enum("BME280IIRFilter")
 | 
			
		||||
IIR_FILTER_OPTIONS = {
 | 
			
		||||
    'OFF': BME280IIRFilter.BME280_IIR_FILTER_OFF,
 | 
			
		||||
    '2X': BME280IIRFilter.BME280_IIR_FILTER_2X,
 | 
			
		||||
    '4X': BME280IIRFilter.BME280_IIR_FILTER_4X,
 | 
			
		||||
    '8X': BME280IIRFilter.BME280_IIR_FILTER_8X,
 | 
			
		||||
    '16X': BME280IIRFilter.BME280_IIR_FILTER_16X,
 | 
			
		||||
    "OFF": BME280IIRFilter.BME280_IIR_FILTER_OFF,
 | 
			
		||||
    "2X": BME280IIRFilter.BME280_IIR_FILTER_2X,
 | 
			
		||||
    "4X": BME280IIRFilter.BME280_IIR_FILTER_4X,
 | 
			
		||||
    "8X": BME280IIRFilter.BME280_IIR_FILTER_8X,
 | 
			
		||||
    "16X": BME280IIRFilter.BME280_IIR_FILTER_16X,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BME280Component = bme280_ns.class_('BME280Component', cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
BME280Component = bme280_ns.class_(
 | 
			
		||||
    "BME280Component", cg.PollingComponent, i2c.I2CDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(BME280Component),
 | 
			
		||||
    cv.Optional(CONF_TEMPERATURE):
 | 
			
		||||
        sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
 | 
			
		||||
            cv.Optional(CONF_OVERSAMPLING, default='16X'):
 | 
			
		||||
                cv.enum(OVERSAMPLING_OPTIONS, upper=True),
 | 
			
		||||
        }),
 | 
			
		||||
    cv.Optional(CONF_PRESSURE):
 | 
			
		||||
        sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({
 | 
			
		||||
            cv.Optional(CONF_OVERSAMPLING, default='16X'):
 | 
			
		||||
                cv.enum(OVERSAMPLING_OPTIONS, upper=True),
 | 
			
		||||
        }),
 | 
			
		||||
    cv.Optional(CONF_HUMIDITY):
 | 
			
		||||
        sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).extend({
 | 
			
		||||
            cv.Optional(CONF_OVERSAMPLING, default='16X'):
 | 
			
		||||
                cv.enum(OVERSAMPLING_OPTIONS, upper=True),
 | 
			
		||||
        }),
 | 
			
		||||
    cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True),
 | 
			
		||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77))
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BME280Component),
 | 
			
		||||
            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
 | 
			
		||||
            ).extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
 | 
			
		||||
                        OVERSAMPLING_OPTIONS, upper=True
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
 | 
			
		||||
            ).extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
 | 
			
		||||
                        OVERSAMPLING_OPTIONS, upper=True
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
			
		||||
                UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
 | 
			
		||||
            ).extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
 | 
			
		||||
                        OVERSAMPLING_OPTIONS, upper=True
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
 | 
			
		||||
                IIR_FILTER_OPTIONS, upper=True
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x77))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -2,64 +2,116 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import core
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
from esphome.const import CONF_DURATION, CONF_GAS_RESISTANCE, CONF_HEATER, \
 | 
			
		||||
    CONF_HUMIDITY, CONF_ID, CONF_IIR_FILTER, CONF_OVERSAMPLING, CONF_PRESSURE, \
 | 
			
		||||
    CONF_TEMPERATURE, UNIT_OHM, ICON_GAS_CYLINDER, UNIT_CELSIUS, \
 | 
			
		||||
    ICON_THERMOMETER, UNIT_HECTOPASCAL, ICON_GAUGE, ICON_WATER_PERCENT, UNIT_PERCENT
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DURATION,
 | 
			
		||||
    CONF_GAS_RESISTANCE,
 | 
			
		||||
    CONF_HEATER,
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_IIR_FILTER,
 | 
			
		||||
    CONF_OVERSAMPLING,
 | 
			
		||||
    CONF_PRESSURE,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_PRESSURE,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    UNIT_OHM,
 | 
			
		||||
    ICON_GAS_CYLINDER,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    UNIT_HECTOPASCAL,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
bme680_ns = cg.esphome_ns.namespace('bme680')
 | 
			
		||||
BME680Oversampling = bme680_ns.enum('BME680Oversampling')
 | 
			
		||||
bme680_ns = cg.esphome_ns.namespace("bme680")
 | 
			
		||||
BME680Oversampling = bme680_ns.enum("BME680Oversampling")
 | 
			
		||||
OVERSAMPLING_OPTIONS = {
 | 
			
		||||
    'NONE': BME680Oversampling.BME680_OVERSAMPLING_NONE,
 | 
			
		||||
    '1X': BME680Oversampling.BME680_OVERSAMPLING_1X,
 | 
			
		||||
    '2X': BME680Oversampling.BME680_OVERSAMPLING_2X,
 | 
			
		||||
    '4X': BME680Oversampling.BME680_OVERSAMPLING_4X,
 | 
			
		||||
    '8X': BME680Oversampling.BME680_OVERSAMPLING_8X,
 | 
			
		||||
    '16X': BME680Oversampling.BME680_OVERSAMPLING_16X,
 | 
			
		||||
    "NONE": BME680Oversampling.BME680_OVERSAMPLING_NONE,
 | 
			
		||||
    "1X": BME680Oversampling.BME680_OVERSAMPLING_1X,
 | 
			
		||||
    "2X": BME680Oversampling.BME680_OVERSAMPLING_2X,
 | 
			
		||||
    "4X": BME680Oversampling.BME680_OVERSAMPLING_4X,
 | 
			
		||||
    "8X": BME680Oversampling.BME680_OVERSAMPLING_8X,
 | 
			
		||||
    "16X": BME680Oversampling.BME680_OVERSAMPLING_16X,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BME680IIRFilter = bme680_ns.enum('BME680IIRFilter')
 | 
			
		||||
BME680IIRFilter = bme680_ns.enum("BME680IIRFilter")
 | 
			
		||||
IIR_FILTER_OPTIONS = {
 | 
			
		||||
    'OFF': BME680IIRFilter.BME680_IIR_FILTER_OFF,
 | 
			
		||||
    '1X': BME680IIRFilter.BME680_IIR_FILTER_1X,
 | 
			
		||||
    '3X': BME680IIRFilter.BME680_IIR_FILTER_3X,
 | 
			
		||||
    '7X': BME680IIRFilter.BME680_IIR_FILTER_7X,
 | 
			
		||||
    '15X': BME680IIRFilter.BME680_IIR_FILTER_15X,
 | 
			
		||||
    '31X': BME680IIRFilter.BME680_IIR_FILTER_31X,
 | 
			
		||||
    '63X': BME680IIRFilter.BME680_IIR_FILTER_63X,
 | 
			
		||||
    '127X': BME680IIRFilter.BME680_IIR_FILTER_127X,
 | 
			
		||||
    "OFF": BME680IIRFilter.BME680_IIR_FILTER_OFF,
 | 
			
		||||
    "1X": BME680IIRFilter.BME680_IIR_FILTER_1X,
 | 
			
		||||
    "3X": BME680IIRFilter.BME680_IIR_FILTER_3X,
 | 
			
		||||
    "7X": BME680IIRFilter.BME680_IIR_FILTER_7X,
 | 
			
		||||
    "15X": BME680IIRFilter.BME680_IIR_FILTER_15X,
 | 
			
		||||
    "31X": BME680IIRFilter.BME680_IIR_FILTER_31X,
 | 
			
		||||
    "63X": BME680IIRFilter.BME680_IIR_FILTER_63X,
 | 
			
		||||
    "127X": BME680IIRFilter.BME680_IIR_FILTER_127X,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BME680Component = bme680_ns.class_('BME680Component', cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
BME680Component = bme680_ns.class_(
 | 
			
		||||
    "BME680Component", cg.PollingComponent, i2c.I2CDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(BME680Component),
 | 
			
		||||
    cv.Optional(CONF_TEMPERATURE):
 | 
			
		||||
        sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
 | 
			
		||||
            cv.Optional(CONF_OVERSAMPLING, default='16X'):
 | 
			
		||||
                cv.enum(OVERSAMPLING_OPTIONS, upper=True),
 | 
			
		||||
        }),
 | 
			
		||||
    cv.Optional(CONF_PRESSURE):
 | 
			
		||||
        sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({
 | 
			
		||||
            cv.Optional(CONF_OVERSAMPLING, default='16X'):
 | 
			
		||||
                cv.enum(OVERSAMPLING_OPTIONS, upper=True),
 | 
			
		||||
        }),
 | 
			
		||||
    cv.Optional(CONF_HUMIDITY):
 | 
			
		||||
        sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 1).extend({
 | 
			
		||||
            cv.Optional(CONF_OVERSAMPLING, default='16X'):
 | 
			
		||||
                cv.enum(OVERSAMPLING_OPTIONS, upper=True),
 | 
			
		||||
        }),
 | 
			
		||||
    cv.Optional(CONF_GAS_RESISTANCE):
 | 
			
		||||
        sensor.sensor_schema(UNIT_OHM, ICON_GAS_CYLINDER, 1),
 | 
			
		||||
    cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True),
 | 
			
		||||
    cv.Optional(CONF_HEATER): cv.Any(None, cv.All(cv.Schema({
 | 
			
		||||
        cv.Optional(CONF_TEMPERATURE, default=320): cv.int_range(min=200, max=400),
 | 
			
		||||
        cv.Optional(CONF_DURATION, default='150ms'): cv.All(
 | 
			
		||||
            cv.positive_time_period_milliseconds, cv.Range(max=core.TimePeriod(milliseconds=4032)))
 | 
			
		||||
    }), cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_DURATION))),
 | 
			
		||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x76))
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BME680Component),
 | 
			
		||||
            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
 | 
			
		||||
            ).extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
 | 
			
		||||
                        OVERSAMPLING_OPTIONS, upper=True
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
 | 
			
		||||
            ).extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
 | 
			
		||||
                        OVERSAMPLING_OPTIONS, upper=True
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
			
		||||
                UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_HUMIDITY
 | 
			
		||||
            ).extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
 | 
			
		||||
                        OVERSAMPLING_OPTIONS, upper=True
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_OHM, ICON_GAS_CYLINDER, 1, DEVICE_CLASS_EMPTY
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
 | 
			
		||||
                IIR_FILTER_OPTIONS, upper=True
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_HEATER): cv.Any(
 | 
			
		||||
                None,
 | 
			
		||||
                cv.All(
 | 
			
		||||
                    cv.Schema(
 | 
			
		||||
                        {
 | 
			
		||||
                            cv.Optional(CONF_TEMPERATURE, default=320): cv.int_range(
 | 
			
		||||
                                min=200, max=400
 | 
			
		||||
                            ),
 | 
			
		||||
                            cv.Optional(CONF_DURATION, default="150ms"): cv.All(
 | 
			
		||||
                                cv.positive_time_period_milliseconds,
 | 
			
		||||
                                cv.Range(max=core.TimePeriod(milliseconds=4032)),
 | 
			
		||||
                            ),
 | 
			
		||||
                        }
 | 
			
		||||
                    ),
 | 
			
		||||
                    cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_DURATION),
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x76))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,39 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
from esphome.const import CONF_ID, CONF_PRESSURE, CONF_TEMPERATURE, \
 | 
			
		||||
    UNIT_CELSIUS, ICON_THERMOMETER, ICON_GAUGE, UNIT_HECTOPASCAL
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_PRESSURE,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_PRESSURE,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    UNIT_HECTOPASCAL,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
bmp085_ns = cg.esphome_ns.namespace('bmp085')
 | 
			
		||||
BMP085Component = bmp085_ns.class_('BMP085Component', cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
bmp085_ns = cg.esphome_ns.namespace("bmp085")
 | 
			
		||||
BMP085Component = bmp085_ns.class_(
 | 
			
		||||
    "BMP085Component", cg.PollingComponent, i2c.I2CDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(BMP085Component),
 | 
			
		||||
    cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
 | 
			
		||||
    cv.Optional(CONF_PRESSURE): sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1),
 | 
			
		||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77))
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BMP085Component),
 | 
			
		||||
            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x77))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,44 +1,75 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
from esphome.const import CONF_ID, CONF_PRESSURE, CONF_TEMPERATURE, \
 | 
			
		||||
    UNIT_CELSIUS, ICON_THERMOMETER, ICON_GAUGE, UNIT_HECTOPASCAL, \
 | 
			
		||||
    CONF_IIR_FILTER, CONF_OVERSAMPLING
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_PRESSURE,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_PRESSURE,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    UNIT_HECTOPASCAL,
 | 
			
		||||
    CONF_IIR_FILTER,
 | 
			
		||||
    CONF_OVERSAMPLING,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
bmp280_ns = cg.esphome_ns.namespace('bmp280')
 | 
			
		||||
BMP280Oversampling = bmp280_ns.enum('BMP280Oversampling')
 | 
			
		||||
bmp280_ns = cg.esphome_ns.namespace("bmp280")
 | 
			
		||||
BMP280Oversampling = bmp280_ns.enum("BMP280Oversampling")
 | 
			
		||||
OVERSAMPLING_OPTIONS = {
 | 
			
		||||
    'NONE': BMP280Oversampling.BMP280_OVERSAMPLING_NONE,
 | 
			
		||||
    '1X': BMP280Oversampling.BMP280_OVERSAMPLING_1X,
 | 
			
		||||
    '2X': BMP280Oversampling.BMP280_OVERSAMPLING_2X,
 | 
			
		||||
    '4X': BMP280Oversampling.BMP280_OVERSAMPLING_4X,
 | 
			
		||||
    '8X': BMP280Oversampling.BMP280_OVERSAMPLING_8X,
 | 
			
		||||
    '16X': BMP280Oversampling.BMP280_OVERSAMPLING_16X,
 | 
			
		||||
    "NONE": BMP280Oversampling.BMP280_OVERSAMPLING_NONE,
 | 
			
		||||
    "1X": BMP280Oversampling.BMP280_OVERSAMPLING_1X,
 | 
			
		||||
    "2X": BMP280Oversampling.BMP280_OVERSAMPLING_2X,
 | 
			
		||||
    "4X": BMP280Oversampling.BMP280_OVERSAMPLING_4X,
 | 
			
		||||
    "8X": BMP280Oversampling.BMP280_OVERSAMPLING_8X,
 | 
			
		||||
    "16X": BMP280Oversampling.BMP280_OVERSAMPLING_16X,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BMP280IIRFilter = bmp280_ns.enum('BMP280IIRFilter')
 | 
			
		||||
BMP280IIRFilter = bmp280_ns.enum("BMP280IIRFilter")
 | 
			
		||||
IIR_FILTER_OPTIONS = {
 | 
			
		||||
    'OFF': BMP280IIRFilter.BMP280_IIR_FILTER_OFF,
 | 
			
		||||
    '2X': BMP280IIRFilter.BMP280_IIR_FILTER_2X,
 | 
			
		||||
    '4X': BMP280IIRFilter.BMP280_IIR_FILTER_4X,
 | 
			
		||||
    '8X': BMP280IIRFilter.BMP280_IIR_FILTER_8X,
 | 
			
		||||
    '16X': BMP280IIRFilter.BMP280_IIR_FILTER_16X,
 | 
			
		||||
    "OFF": BMP280IIRFilter.BMP280_IIR_FILTER_OFF,
 | 
			
		||||
    "2X": BMP280IIRFilter.BMP280_IIR_FILTER_2X,
 | 
			
		||||
    "4X": BMP280IIRFilter.BMP280_IIR_FILTER_4X,
 | 
			
		||||
    "8X": BMP280IIRFilter.BMP280_IIR_FILTER_8X,
 | 
			
		||||
    "16X": BMP280IIRFilter.BMP280_IIR_FILTER_16X,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BMP280Component = bmp280_ns.class_('BMP280Component', cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
BMP280Component = bmp280_ns.class_(
 | 
			
		||||
    "BMP280Component", cg.PollingComponent, i2c.I2CDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(BMP280Component),
 | 
			
		||||
    cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
 | 
			
		||||
        cv.Optional(CONF_OVERSAMPLING, default='16X'): cv.enum(OVERSAMPLING_OPTIONS, upper=True),
 | 
			
		||||
    }),
 | 
			
		||||
    cv.Optional(CONF_PRESSURE): sensor.sensor_schema(UNIT_HECTOPASCAL, ICON_GAUGE, 1).extend({
 | 
			
		||||
        cv.Optional(CONF_OVERSAMPLING, default='16X'): cv.enum(OVERSAMPLING_OPTIONS, upper=True),
 | 
			
		||||
    }),
 | 
			
		||||
    cv.Optional(CONF_IIR_FILTER, default='OFF'): cv.enum(IIR_FILTER_OPTIONS, upper=True),
 | 
			
		||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x77))
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BMP280Component),
 | 
			
		||||
            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
 | 
			
		||||
            ).extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
 | 
			
		||||
                        OVERSAMPLING_OPTIONS, upper=True
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_HECTOPASCAL, ICON_EMPTY, 1, DEVICE_CLASS_PRESSURE
 | 
			
		||||
            ).extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
 | 
			
		||||
                        OVERSAMPLING_OPTIONS, upper=True
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
 | 
			
		||||
                IIR_FILTER_OPTIONS, upper=True
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x77))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										147
									
								
								esphome/components/canbus/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								esphome/components/canbus/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.core import CORE, coroutine
 | 
			
		||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_DATA
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@mvturnho", "@danielschramm"]
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
CONF_CAN_ID = "can_id"
 | 
			
		||||
CONF_USE_EXTENDED_ID = "use_extended_id"
 | 
			
		||||
CONF_CANBUS_ID = "canbus_id"
 | 
			
		||||
CONF_BIT_RATE = "bit_rate"
 | 
			
		||||
CONF_ON_FRAME = "on_frame"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_id(id_value, id_ext):
 | 
			
		||||
    if not id_ext:
 | 
			
		||||
        if id_value > 0x7FF:
 | 
			
		||||
            raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_raw_data(value):
 | 
			
		||||
    if isinstance(value, str):
 | 
			
		||||
        return value.encode("utf-8")
 | 
			
		||||
    if isinstance(value, list):
 | 
			
		||||
        return cv.Schema([cv.hex_uint8_t])(value)
 | 
			
		||||
    raise cv.Invalid(
 | 
			
		||||
        "data must either be a string wrapped in quotes or a list of bytes"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
canbus_ns = cg.esphome_ns.namespace("canbus")
 | 
			
		||||
CanbusComponent = canbus_ns.class_("CanbusComponent", cg.Component)
 | 
			
		||||
CanbusTrigger = canbus_ns.class_(
 | 
			
		||||
    "CanbusTrigger",
 | 
			
		||||
    automation.Trigger.template(cg.std_vector.template(cg.uint8)),
 | 
			
		||||
    cg.Component,
 | 
			
		||||
)
 | 
			
		||||
CanSpeed = canbus_ns.enum("CAN_SPEED")
 | 
			
		||||
 | 
			
		||||
CAN_SPEEDS = {
 | 
			
		||||
    "5KBPS": CanSpeed.CAN_5KBPS,
 | 
			
		||||
    "10KBPS": CanSpeed.CAN_10KBPS,
 | 
			
		||||
    "20KBPS": CanSpeed.CAN_20KBPS,
 | 
			
		||||
    "31K25BPS": CanSpeed.CAN_31K25BPS,
 | 
			
		||||
    "33KBPS": CanSpeed.CAN_33KBPS,
 | 
			
		||||
    "40KBPS": CanSpeed.CAN_40KBPS,
 | 
			
		||||
    "50KBPS": CanSpeed.CAN_50KBPS,
 | 
			
		||||
    "80KBPS": CanSpeed.CAN_80KBPS,
 | 
			
		||||
    "83K3BPS": CanSpeed.CAN_83K3BPS,
 | 
			
		||||
    "95KBPS": CanSpeed.CAN_95KBPS,
 | 
			
		||||
    "100KBPS": CanSpeed.CAN_100KBPS,
 | 
			
		||||
    "125KBPS": CanSpeed.CAN_125KBPS,
 | 
			
		||||
    "200KBPS": CanSpeed.CAN_200KBPS,
 | 
			
		||||
    "250KBPS": CanSpeed.CAN_250KBPS,
 | 
			
		||||
    "500KBPS": CanSpeed.CAN_500KBPS,
 | 
			
		||||
    "1000KBPS": CanSpeed.CAN_1000KBPS,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CANBUS_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CanbusComponent),
 | 
			
		||||
        cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
 | 
			
		||||
        cv.Optional(CONF_BIT_RATE, default="125KBPS"): cv.enum(CAN_SPEEDS, upper=True),
 | 
			
		||||
        cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_ON_FRAME): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
 | 
			
		||||
                cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
 | 
			
		||||
                cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_ON_FRAME): automation.validate_automation(
 | 
			
		||||
                    {
 | 
			
		||||
                        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
 | 
			
		||||
                        cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
 | 
			
		||||
                        cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine
 | 
			
		||||
def setup_canbus_core_(var, config):
 | 
			
		||||
    validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    cg.add(var.set_can_id([config[CONF_CAN_ID]]))
 | 
			
		||||
    cg.add(var.set_use_extended_id([config[CONF_USE_EXTENDED_ID]]))
 | 
			
		||||
    cg.add(var.set_bitrate(CAN_SPEEDS[config[CONF_BIT_RATE]]))
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_FRAME, []):
 | 
			
		||||
        can_id = conf[CONF_CAN_ID]
 | 
			
		||||
        ext_id = conf[CONF_USE_EXTENDED_ID]
 | 
			
		||||
        validate_id(can_id, ext_id)
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id)
 | 
			
		||||
        yield cg.register_component(trigger, conf)
 | 
			
		||||
        yield automation.build_automation(
 | 
			
		||||
            trigger, [(cg.std_vector.template(cg.uint8), "x")], conf
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine
 | 
			
		||||
def register_canbus(var, config):
 | 
			
		||||
    if not CORE.has_id(config[CONF_ID]):
 | 
			
		||||
        var = cg.new_Pvariable(config[CONF_ID], var)
 | 
			
		||||
    yield setup_canbus_core_(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Actions
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "canbus.send",
 | 
			
		||||
    canbus_ns.class_("CanbusSendAction", automation.Action),
 | 
			
		||||
    cv.maybe_simple_value(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent),
 | 
			
		||||
            cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
 | 
			
		||||
            cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
 | 
			
		||||
            cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
 | 
			
		||||
        },
 | 
			
		||||
        key=CONF_DATA,
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
def canbus_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg)
 | 
			
		||||
    yield cg.register_parented(var, config[CONF_CANBUS_ID])
 | 
			
		||||
 | 
			
		||||
    if CONF_CAN_ID in config:
 | 
			
		||||
        can_id = yield cg.templatable(config[CONF_CAN_ID], args, cg.uint32)
 | 
			
		||||
        cg.add(var.set_can_id(can_id))
 | 
			
		||||
 | 
			
		||||
    use_extended_id = yield cg.templatable(
 | 
			
		||||
        config[CONF_USE_EXTENDED_ID], args, cg.uint32
 | 
			
		||||
    )
 | 
			
		||||
    cg.add(var.set_use_extended_id(use_extended_id))
 | 
			
		||||
 | 
			
		||||
    data = config[CONF_DATA]
 | 
			
		||||
    if isinstance(data, bytes):
 | 
			
		||||
        data = [int(x) for x in data]
 | 
			
		||||
    if cg.is_template(data):
 | 
			
		||||
        templ = yield cg.templatable(data, args, cg.std_vector.template(cg.uint8))
 | 
			
		||||
        cg.add(var.set_data_template(templ))
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_data_static(data))
 | 
			
		||||
    yield var
 | 
			
		||||
							
								
								
									
										87
									
								
								esphome/components/canbus/canbus.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								esphome/components/canbus/canbus.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
#include "canbus.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace canbus {
 | 
			
		||||
 | 
			
		||||
static const char *TAG = "canbus";
 | 
			
		||||
 | 
			
		||||
void Canbus::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up Canbus...");
 | 
			
		||||
  if (!this->setup_internal()) {
 | 
			
		||||
    ESP_LOGE(TAG, "setup error!");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Canbus::dump_config() {
 | 
			
		||||
  if (this->use_extended_id_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "config extended id=0x%08x", this->can_id_);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "config standard id=0x%03x", this->can_id_);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Canbus::send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) {
 | 
			
		||||
  struct CanFrame can_message;
 | 
			
		||||
 | 
			
		||||
  uint8_t size = static_cast<uint8_t>(data.size());
 | 
			
		||||
  if (use_extended_id) {
 | 
			
		||||
    ESP_LOGD(TAG, "send extended id=0x%08x size=%d", can_id, size);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGD(TAG, "send extended id=0x%03x size=%d", can_id, size);
 | 
			
		||||
  }
 | 
			
		||||
  if (size > CAN_MAX_DATA_LENGTH)
 | 
			
		||||
    size = CAN_MAX_DATA_LENGTH;
 | 
			
		||||
  can_message.can_data_length_code = size;
 | 
			
		||||
  can_message.can_id = can_id;
 | 
			
		||||
  can_message.use_extended_id = use_extended_id;
 | 
			
		||||
 | 
			
		||||
  for (int i = 0; i < size; i++) {
 | 
			
		||||
    can_message.data[i] = data[i];
 | 
			
		||||
    ESP_LOGVV(TAG, "  data[%d]=%02x", i, can_message.data[i]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->send_message(&can_message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Canbus::add_trigger(CanbusTrigger *trigger) {
 | 
			
		||||
  if (trigger->use_extended_id_) {
 | 
			
		||||
    ESP_LOGVV(TAG, "add trigger for extended canid=0x%08x", trigger->can_id_);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGVV(TAG, "add trigger for std canid=0x%03x", trigger->can_id_);
 | 
			
		||||
  }
 | 
			
		||||
  this->triggers_.push_back(trigger);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void Canbus::loop() {
 | 
			
		||||
  struct CanFrame can_message;
 | 
			
		||||
  // readmessage
 | 
			
		||||
  if (this->read_message(&can_message) == canbus::ERROR_OK) {
 | 
			
		||||
    if (can_message.use_extended_id) {
 | 
			
		||||
      ESP_LOGD(TAG, "received can message extended can_id=0x%x size=%d", can_message.can_id,
 | 
			
		||||
               can_message.can_data_length_code);
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGD(TAG, "received can message std can_id=0x%x size=%d", can_message.can_id,
 | 
			
		||||
               can_message.can_data_length_code);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<uint8_t> data;
 | 
			
		||||
 | 
			
		||||
    // show data received
 | 
			
		||||
    for (int i = 0; i < can_message.can_data_length_code; i++) {
 | 
			
		||||
      ESP_LOGV(TAG, "  can_message.data[%d]=%02x", i, can_message.data[i]);
 | 
			
		||||
      data.push_back(can_message.data[i]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // fire all triggers
 | 
			
		||||
    for (auto trigger : this->triggers_) {
 | 
			
		||||
      if ((trigger->can_id_ == can_message.can_id) && (trigger->use_extended_id_ == can_message.use_extended_id)) {
 | 
			
		||||
        trigger->trigger(data);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace canbus
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										134
									
								
								esphome/components/canbus/canbus.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								esphome/components/canbus/canbus.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,134 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/optional.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace canbus {
 | 
			
		||||
 | 
			
		||||
enum Error : uint8_t {
 | 
			
		||||
  ERROR_OK = 0,
 | 
			
		||||
  ERROR_FAIL = 1,
 | 
			
		||||
  ERROR_ALLTXBUSY = 2,
 | 
			
		||||
  ERROR_FAILINIT = 3,
 | 
			
		||||
  ERROR_FAILTX = 4,
 | 
			
		||||
  ERROR_NOMSG = 5
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum CanSpeed : uint8_t {
 | 
			
		||||
  CAN_5KBPS,
 | 
			
		||||
  CAN_10KBPS,
 | 
			
		||||
  CAN_20KBPS,
 | 
			
		||||
  CAN_31K25BPS,
 | 
			
		||||
  CAN_33KBPS,
 | 
			
		||||
  CAN_40KBPS,
 | 
			
		||||
  CAN_50KBPS,
 | 
			
		||||
  CAN_80KBPS,
 | 
			
		||||
  CAN_83K3BPS,
 | 
			
		||||
  CAN_95KBPS,
 | 
			
		||||
  CAN_100KBPS,
 | 
			
		||||
  CAN_125KBPS,
 | 
			
		||||
  CAN_200KBPS,
 | 
			
		||||
  CAN_250KBPS,
 | 
			
		||||
  CAN_500KBPS,
 | 
			
		||||
  CAN_1000KBPS
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class CanbusTrigger;
 | 
			
		||||
template<typename... Ts> class CanbusSendAction;
 | 
			
		||||
 | 
			
		||||
/* CAN payload length definitions according to ISO 11898-1 */
 | 
			
		||||
static const uint8_t CAN_MAX_DATA_LENGTH = 8;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Can Frame describes a normative CAN Frame
 | 
			
		||||
The RTR = Remote Transmission Request is implemented in every CAN controller but rarely used
 | 
			
		||||
So currently the flag is passed to and from the hardware but currently ignored to the user application.
 | 
			
		||||
*/
 | 
			
		||||
struct CanFrame {
 | 
			
		||||
  bool use_extended_id = false;
 | 
			
		||||
  bool remote_transmission_request = false;
 | 
			
		||||
  uint32_t can_id;              /* 29 or 11 bit CAN_ID  */
 | 
			
		||||
  uint8_t can_data_length_code; /* frame payload length in byte (0 .. CAN_MAX_DATA_LENGTH) */
 | 
			
		||||
  uint8_t data[CAN_MAX_DATA_LENGTH] __attribute__((aligned(8)));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Canbus : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  Canbus(){};
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::HARDWARE; }
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
  void send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data);
 | 
			
		||||
  void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
 | 
			
		||||
  void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
 | 
			
		||||
  void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; }
 | 
			
		||||
 | 
			
		||||
  void add_trigger(CanbusTrigger *trigger);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  template<typename... Ts> friend class CanbusSendAction;
 | 
			
		||||
  std::vector<CanbusTrigger *> triggers_{};
 | 
			
		||||
  uint32_t can_id_;
 | 
			
		||||
  bool use_extended_id_;
 | 
			
		||||
  CanSpeed bit_rate_;
 | 
			
		||||
 | 
			
		||||
  virtual bool setup_internal();
 | 
			
		||||
  virtual Error send_message(struct CanFrame *frame);
 | 
			
		||||
  virtual Error read_message(struct CanFrame *frame);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public Parented<Canbus> {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_data_template(const std::function<std::vector<uint8_t>(Ts...)> func) {
 | 
			
		||||
    this->data_func_ = func;
 | 
			
		||||
    this->static_ = false;
 | 
			
		||||
  }
 | 
			
		||||
  void set_data_static(const std::vector<uint8_t> &data) {
 | 
			
		||||
    this->data_static_ = data;
 | 
			
		||||
    this->static_ = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
 | 
			
		||||
 | 
			
		||||
  void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
 | 
			
		||||
    auto use_extended_id =
 | 
			
		||||
        this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_;
 | 
			
		||||
    if (this->static_) {
 | 
			
		||||
      this->parent_->send_data(can_id, use_extended_id, this->data_static_);
 | 
			
		||||
    } else {
 | 
			
		||||
      auto val = this->data_func_(x...);
 | 
			
		||||
      this->parent_->send_data(can_id, use_extended_id, val);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  optional<uint32_t> can_id_{};
 | 
			
		||||
  optional<bool> use_extended_id_{};
 | 
			
		||||
  bool static_{false};
 | 
			
		||||
  std::function<std::vector<uint8_t>(Ts...)> data_func_{};
 | 
			
		||||
  std::vector<uint8_t> data_static_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class CanbusTrigger : public Trigger<std::vector<uint8_t>>, public Component {
 | 
			
		||||
  friend class Canbus;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  explicit CanbusTrigger(Canbus *parent, const std::uint32_t can_id, const bool use_extended_id)
 | 
			
		||||
      : parent_(parent), can_id_(can_id), use_extended_id_(use_extended_id){};
 | 
			
		||||
  void setup() override { this->parent_->add_trigger(this); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Canbus *parent_;
 | 
			
		||||
  uint32_t can_id_;
 | 
			
		||||
  bool use_extended_id_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace canbus
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -5,17 +5,21 @@ from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
from esphome.core import coroutine_with_priority
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ['web_server_base']
 | 
			
		||||
DEPENDENCIES = ['wifi']
 | 
			
		||||
CODEOWNERS = ['@OttoWinter']
 | 
			
		||||
AUTO_LOAD = ["web_server_base"]
 | 
			
		||||
DEPENDENCIES = ["wifi"]
 | 
			
		||||
CODEOWNERS = ["@OttoWinter"]
 | 
			
		||||
 | 
			
		||||
captive_portal_ns = cg.esphome_ns.namespace('captive_portal')
 | 
			
		||||
CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component)
 | 
			
		||||
captive_portal_ns = cg.esphome_ns.namespace("captive_portal")
 | 
			
		||||
CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(CaptivePortal),
 | 
			
		||||
    cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(web_server_base.WebServerBase),
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CaptivePortal),
 | 
			
		||||
        cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
 | 
			
		||||
            web_server_base.WebServerBase
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(64.0)
 | 
			
		||||
@@ -24,4 +28,4 @@ def to_code(config):
 | 
			
		||||
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], paren)
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    cg.add_define('USE_CAPTIVE_PORTAL')
 | 
			
		||||
    cg.add_define("USE_CAPTIVE_PORTAL")
 | 
			
		||||
 
 | 
			
		||||
@@ -102,10 +102,14 @@ void CCS811Component::send_env_data_() {
 | 
			
		||||
  // temperature has a 25° offset to allow negative temperatures
 | 
			
		||||
  temperature += 25;
 | 
			
		||||
 | 
			
		||||
  // only 0.5 fractions are supported (application note)
 | 
			
		||||
  auto hum_value = static_cast<uint8_t>(roundf(humidity * 2));
 | 
			
		||||
  auto temp_value = static_cast<uint8_t>(roundf(temperature * 2));
 | 
			
		||||
  this->write_bytes(0x05, {hum_value, 0x00, temp_value, 0x00});
 | 
			
		||||
  // At page 18 of:
 | 
			
		||||
  // https://cdn.sparkfun.com/datasheets/BreakoutBoards/CCS811_Programming_Guide.pdf
 | 
			
		||||
  // Reference code:
 | 
			
		||||
  // https://github.com/adafruit/Adafruit_CCS811/blob/0990f5c620354d8bc087c4706bec091d8e6e5dfd/Adafruit_CCS811.cpp#L135-L142
 | 
			
		||||
  uint16_t hum_conv = static_cast<uint16_t>(lroundf(humidity * 512.0f + 0.5f));
 | 
			
		||||
  uint16_t temp_conv = static_cast<uint16_t>(lroundf(temperature * 512.0f + 0.5f));
 | 
			
		||||
  this->write_bytes(0x05, {(uint8_t)((hum_conv >> 8) & 0xff), (uint8_t)((hum_conv & 0xff)),
 | 
			
		||||
                           (uint8_t)((temp_conv >> 8) & 0xff), (uint8_t)((temp_conv & 0xff))});
 | 
			
		||||
}
 | 
			
		||||
void CCS811Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "CCS811");
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,46 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \
 | 
			
		||||
    UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_MOLECULE_CO2
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    ICON_RADIATOR,
 | 
			
		||||
    UNIT_PARTS_PER_MILLION,
 | 
			
		||||
    UNIT_PARTS_PER_BILLION,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    CONF_TVOC,
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    ICON_MOLECULE_CO2,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['i2c']
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
ccs811_ns = cg.esphome_ns.namespace('ccs811')
 | 
			
		||||
CCS811Component = ccs811_ns.class_('CCS811Component', cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
ccs811_ns = cg.esphome_ns.namespace("ccs811")
 | 
			
		||||
CCS811Component = ccs811_ns.class_(
 | 
			
		||||
    "CCS811Component", cg.PollingComponent, i2c.I2CDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_ECO2 = 'eco2'
 | 
			
		||||
CONF_TVOC = 'tvoc'
 | 
			
		||||
CONF_BASELINE = 'baseline'
 | 
			
		||||
CONF_ECO2 = "eco2"
 | 
			
		||||
CONF_BASELINE = "baseline"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(CCS811Component),
 | 
			
		||||
    cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2,
 | 
			
		||||
                                                 0),
 | 
			
		||||
    cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0),
 | 
			
		||||
 | 
			
		||||
    cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
 | 
			
		||||
    cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
 | 
			
		||||
    cv.Optional(CONF_HUMIDITY): cv.use_id(sensor.Sensor),
 | 
			
		||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5A))
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(CCS811Component),
 | 
			
		||||
            cv.Required(CONF_ECO2): sensor.sensor_schema(
 | 
			
		||||
                UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2, 0, DEVICE_CLASS_EMPTY
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Required(CONF_TVOC): sensor.sensor_schema(
 | 
			
		||||
                UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0, DEVICE_CLASS_EMPTY
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
 | 
			
		||||
            cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
 | 
			
		||||
            cv.Optional(CONF_HUMIDITY): cv.use_id(sensor.Sensor),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x5A))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -2,70 +2,87 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.components import mqtt
 | 
			
		||||
from esphome.const import CONF_AWAY, CONF_ID, CONF_INTERNAL, CONF_MAX_TEMPERATURE, \
 | 
			
		||||
    CONF_MIN_TEMPERATURE, CONF_MODE, CONF_TARGET_TEMPERATURE, \
 | 
			
		||||
    CONF_TARGET_TEMPERATURE_HIGH, CONF_TARGET_TEMPERATURE_LOW, CONF_TEMPERATURE_STEP, CONF_VISUAL, \
 | 
			
		||||
    CONF_MQTT_ID, CONF_NAME, CONF_FAN_MODE, CONF_SWING_MODE
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_AWAY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INTERNAL,
 | 
			
		||||
    CONF_MAX_TEMPERATURE,
 | 
			
		||||
    CONF_MIN_TEMPERATURE,
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_TARGET_TEMPERATURE,
 | 
			
		||||
    CONF_TARGET_TEMPERATURE_HIGH,
 | 
			
		||||
    CONF_TARGET_TEMPERATURE_LOW,
 | 
			
		||||
    CONF_TEMPERATURE_STEP,
 | 
			
		||||
    CONF_VISUAL,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_NAME,
 | 
			
		||||
    CONF_FAN_MODE,
 | 
			
		||||
    CONF_SWING_MODE,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine, coroutine_with_priority
 | 
			
		||||
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ['@esphome/core']
 | 
			
		||||
climate_ns = cg.esphome_ns.namespace('climate')
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
climate_ns = cg.esphome_ns.namespace("climate")
 | 
			
		||||
 | 
			
		||||
Climate = climate_ns.class_('Climate', cg.Nameable)
 | 
			
		||||
ClimateCall = climate_ns.class_('ClimateCall')
 | 
			
		||||
ClimateTraits = climate_ns.class_('ClimateTraits')
 | 
			
		||||
Climate = climate_ns.class_("Climate", cg.Nameable)
 | 
			
		||||
ClimateCall = climate_ns.class_("ClimateCall")
 | 
			
		||||
ClimateTraits = climate_ns.class_("ClimateTraits")
 | 
			
		||||
 | 
			
		||||
ClimateMode = climate_ns.enum('ClimateMode')
 | 
			
		||||
ClimateMode = climate_ns.enum("ClimateMode")
 | 
			
		||||
CLIMATE_MODES = {
 | 
			
		||||
    'OFF': ClimateMode.CLIMATE_MODE_OFF,
 | 
			
		||||
    'AUTO': ClimateMode.CLIMATE_MODE_AUTO,
 | 
			
		||||
    'COOL': ClimateMode.CLIMATE_MODE_COOL,
 | 
			
		||||
    'HEAT': ClimateMode.CLIMATE_MODE_HEAT,
 | 
			
		||||
    'DRY': ClimateMode.CLIMATE_MODE_DRY,
 | 
			
		||||
    'FAN_ONLY': ClimateMode.CLIMATE_MODE_FAN_ONLY,
 | 
			
		||||
    "OFF": ClimateMode.CLIMATE_MODE_OFF,
 | 
			
		||||
    "AUTO": ClimateMode.CLIMATE_MODE_AUTO,
 | 
			
		||||
    "COOL": ClimateMode.CLIMATE_MODE_COOL,
 | 
			
		||||
    "HEAT": ClimateMode.CLIMATE_MODE_HEAT,
 | 
			
		||||
    "DRY": ClimateMode.CLIMATE_MODE_DRY,
 | 
			
		||||
    "FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY,
 | 
			
		||||
}
 | 
			
		||||
validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True)
 | 
			
		||||
 | 
			
		||||
ClimateFanMode = climate_ns.enum('ClimateFanMode')
 | 
			
		||||
ClimateFanMode = climate_ns.enum("ClimateFanMode")
 | 
			
		||||
CLIMATE_FAN_MODES = {
 | 
			
		||||
    'ON': ClimateFanMode.CLIMATE_FAN_ON,
 | 
			
		||||
    'OFF': ClimateFanMode.CLIMATE_FAN_OFF,
 | 
			
		||||
    'AUTO': ClimateFanMode.CLIMATE_FAN_AUTO,
 | 
			
		||||
    'LOW': ClimateFanMode.CLIMATE_FAN_LOW,
 | 
			
		||||
    'MEDIUM': ClimateFanMode.CLIMATE_FAN_MEDIUM,
 | 
			
		||||
    'HIGH': ClimateFanMode.CLIMATE_FAN_HIGH,
 | 
			
		||||
    'MIDDLE': ClimateFanMode.CLIMATE_FAN_MIDDLE,
 | 
			
		||||
    'FOCUS': ClimateFanMode.CLIMATE_FAN_FOCUS,
 | 
			
		||||
    'DIFFUSE': ClimateFanMode.CLIMATE_FAN_DIFFUSE,
 | 
			
		||||
    "ON": ClimateFanMode.CLIMATE_FAN_ON,
 | 
			
		||||
    "OFF": ClimateFanMode.CLIMATE_FAN_OFF,
 | 
			
		||||
    "AUTO": ClimateFanMode.CLIMATE_FAN_AUTO,
 | 
			
		||||
    "LOW": ClimateFanMode.CLIMATE_FAN_LOW,
 | 
			
		||||
    "MEDIUM": ClimateFanMode.CLIMATE_FAN_MEDIUM,
 | 
			
		||||
    "HIGH": ClimateFanMode.CLIMATE_FAN_HIGH,
 | 
			
		||||
    "MIDDLE": ClimateFanMode.CLIMATE_FAN_MIDDLE,
 | 
			
		||||
    "FOCUS": ClimateFanMode.CLIMATE_FAN_FOCUS,
 | 
			
		||||
    "DIFFUSE": ClimateFanMode.CLIMATE_FAN_DIFFUSE,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True)
 | 
			
		||||
 | 
			
		||||
ClimateSwingMode = climate_ns.enum('ClimateSwingMode')
 | 
			
		||||
ClimateSwingMode = climate_ns.enum("ClimateSwingMode")
 | 
			
		||||
CLIMATE_SWING_MODES = {
 | 
			
		||||
    'OFF': ClimateSwingMode.CLIMATE_SWING_OFF,
 | 
			
		||||
    'BOTH': ClimateSwingMode.CLIMATE_SWING_BOTH,
 | 
			
		||||
    'VERTICAL': ClimateSwingMode.CLIMATE_SWING_VERTICAL,
 | 
			
		||||
    'HORIZONTAL': ClimateSwingMode.CLIMATE_SWING_HORIZONTAL,
 | 
			
		||||
    "OFF": ClimateSwingMode.CLIMATE_SWING_OFF,
 | 
			
		||||
    "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH,
 | 
			
		||||
    "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL,
 | 
			
		||||
    "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
 | 
			
		||||
 | 
			
		||||
# Actions
 | 
			
		||||
ControlAction = climate_ns.class_('ControlAction', automation.Action)
 | 
			
		||||
ControlAction = climate_ns.class_("ControlAction", automation.Action)
 | 
			
		||||
 | 
			
		||||
CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(Climate),
 | 
			
		||||
    cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTClimateComponent),
 | 
			
		||||
    cv.Optional(CONF_VISUAL, default={}): cv.Schema({
 | 
			
		||||
        cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
 | 
			
		||||
        cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
 | 
			
		||||
        cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature,
 | 
			
		||||
    }),
 | 
			
		||||
    # TODO: MQTT topic options
 | 
			
		||||
})
 | 
			
		||||
CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(Climate),
 | 
			
		||||
        cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
 | 
			
		||||
        cv.Optional(CONF_VISUAL, default={}): cv.Schema(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
 | 
			
		||||
                cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
 | 
			
		||||
                cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        # TODO: MQTT topic options
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine
 | 
			
		||||
@@ -94,19 +111,23 @@ def register_climate(var, config):
 | 
			
		||||
    yield setup_climate_core_(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.Required(CONF_ID): cv.use_id(Climate),
 | 
			
		||||
    cv.Optional(CONF_MODE): cv.templatable(validate_climate_mode),
 | 
			
		||||
    cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
 | 
			
		||||
    cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),
 | 
			
		||||
    cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
 | 
			
		||||
    cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
 | 
			
		||||
    cv.Optional(CONF_FAN_MODE): cv.templatable(validate_climate_fan_mode),
 | 
			
		||||
    cv.Optional(CONF_SWING_MODE): cv.templatable(validate_climate_swing_mode),
 | 
			
		||||
})
 | 
			
		||||
CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(Climate),
 | 
			
		||||
        cv.Optional(CONF_MODE): cv.templatable(validate_climate_mode),
 | 
			
		||||
        cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
 | 
			
		||||
        cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),
 | 
			
		||||
        cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
 | 
			
		||||
        cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
 | 
			
		||||
        cv.Optional(CONF_FAN_MODE): cv.templatable(validate_climate_fan_mode),
 | 
			
		||||
        cv.Optional(CONF_SWING_MODE): cv.templatable(validate_climate_swing_mode),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action('climate.control', ControlAction, CLIMATE_CONTROL_ACTION_SCHEMA)
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "climate.control", ControlAction, CLIMATE_CONTROL_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
def climate_control_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = yield cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
@@ -117,10 +138,14 @@ def climate_control_to_code(config, action_id, template_arg, args):
 | 
			
		||||
        template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE], args, float)
 | 
			
		||||
        cg.add(var.set_target_temperature(template_))
 | 
			
		||||
    if CONF_TARGET_TEMPERATURE_LOW in config:
 | 
			
		||||
        template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE_LOW], args, float)
 | 
			
		||||
        template_ = yield cg.templatable(
 | 
			
		||||
            config[CONF_TARGET_TEMPERATURE_LOW], args, float
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_target_temperature_low(template_))
 | 
			
		||||
    if CONF_TARGET_TEMPERATURE_HIGH in config:
 | 
			
		||||
        template_ = yield cg.templatable(config[CONF_TARGET_TEMPERATURE_HIGH], args, float)
 | 
			
		||||
        template_ = yield cg.templatable(
 | 
			
		||||
            config[CONF_TARGET_TEMPERATURE_HIGH], args, float
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_target_temperature_high(template_))
 | 
			
		||||
    if CONF_AWAY in config:
 | 
			
		||||
        template_ = yield cg.templatable(config[CONF_AWAY], args, bool)
 | 
			
		||||
@@ -129,12 +154,14 @@ def climate_control_to_code(config, action_id, template_arg, args):
 | 
			
		||||
        template_ = yield cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode)
 | 
			
		||||
        cg.add(var.set_fan_mode(template_))
 | 
			
		||||
    if CONF_SWING_MODE in config:
 | 
			
		||||
        template_ = yield cg.templatable(config[CONF_SWING_MODE], args, ClimateSwingMode)
 | 
			
		||||
        template_ = yield cg.templatable(
 | 
			
		||||
            config[CONF_SWING_MODE], args, ClimateSwingMode
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_swing_mode(template_))
 | 
			
		||||
    yield var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(100.0)
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    cg.add_define('USE_CLIMATE')
 | 
			
		||||
    cg.add_define("USE_CLIMATE")
 | 
			
		||||
    cg.add_global(climate_ns.using)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,42 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import climate, remote_transmitter, remote_receiver, sensor, remote_base
 | 
			
		||||
from esphome.components import (
 | 
			
		||||
    climate,
 | 
			
		||||
    remote_transmitter,
 | 
			
		||||
    remote_receiver,
 | 
			
		||||
    sensor,
 | 
			
		||||
    remote_base,
 | 
			
		||||
)
 | 
			
		||||
from esphome.components.remote_base import CONF_RECEIVER_ID, CONF_TRANSMITTER_ID
 | 
			
		||||
from esphome.const import CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, CONF_SENSOR
 | 
			
		||||
from esphome.core import coroutine
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ['sensor', 'remote_base']
 | 
			
		||||
CODEOWNERS = ['@glmnet']
 | 
			
		||||
AUTO_LOAD = ["sensor", "remote_base"]
 | 
			
		||||
CODEOWNERS = ["@glmnet"]
 | 
			
		||||
 | 
			
		||||
climate_ir_ns = cg.esphome_ns.namespace('climate_ir')
 | 
			
		||||
ClimateIR = climate_ir_ns.class_('ClimateIR', climate.Climate, cg.Component,
 | 
			
		||||
                                 remote_base.RemoteReceiverListener)
 | 
			
		||||
climate_ir_ns = cg.esphome_ns.namespace("climate_ir")
 | 
			
		||||
ClimateIR = climate_ir_ns.class_(
 | 
			
		||||
    "ClimateIR", climate.Climate, cg.Component, remote_base.RemoteReceiverListener
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CLIMATE_IR_SCHEMA = climate.CLIMATE_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent),
 | 
			
		||||
    cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean,
 | 
			
		||||
    cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean,
 | 
			
		||||
    cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
CLIMATE_IR_SCHEMA = climate.CLIMATE_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(
 | 
			
		||||
            remote_transmitter.RemoteTransmitterComponent
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend({
 | 
			
		||||
    cv.Optional(CONF_RECEIVER_ID): cv.use_id(remote_receiver.RemoteReceiverComponent),
 | 
			
		||||
})
 | 
			
		||||
CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_RECEIVER_ID): cv.use_id(
 | 
			
		||||
            remote_receiver.RemoteReceiverComponent
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine
 | 
			
		||||
 
 | 
			
		||||
@@ -3,16 +3,45 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import climate_ir
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ['climate_ir']
 | 
			
		||||
AUTO_LOAD = ["climate_ir"]
 | 
			
		||||
 | 
			
		||||
climate_ir_lg_ns = cg.esphome_ns.namespace('climate_ir_lg')
 | 
			
		||||
LgIrClimate = climate_ir_lg_ns.class_('LgIrClimate', climate_ir.ClimateIR)
 | 
			
		||||
climate_ir_lg_ns = cg.esphome_ns.namespace("climate_ir_lg")
 | 
			
		||||
LgIrClimate = climate_ir_lg_ns.class_("LgIrClimate", climate_ir.ClimateIR)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(LgIrClimate),
 | 
			
		||||
})
 | 
			
		||||
CONF_HEADER_HIGH = "header_high"
 | 
			
		||||
CONF_HEADER_LOW = "header_low"
 | 
			
		||||
CONF_BIT_HIGH = "bit_high"
 | 
			
		||||
CONF_BIT_ONE_LOW = "bit_one_low"
 | 
			
		||||
CONF_BIT_ZERO_LOW = "bit_zero_low"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(LgIrClimate),
 | 
			
		||||
        cv.Optional(
 | 
			
		||||
            CONF_HEADER_HIGH, default="8000us"
 | 
			
		||||
        ): cv.positive_time_period_microseconds,
 | 
			
		||||
        cv.Optional(
 | 
			
		||||
            CONF_HEADER_LOW, default="4000us"
 | 
			
		||||
        ): cv.positive_time_period_microseconds,
 | 
			
		||||
        cv.Optional(
 | 
			
		||||
            CONF_BIT_HIGH, default="600us"
 | 
			
		||||
        ): cv.positive_time_period_microseconds,
 | 
			
		||||
        cv.Optional(
 | 
			
		||||
            CONF_BIT_ONE_LOW, default="1600us"
 | 
			
		||||
        ): cv.positive_time_period_microseconds,
 | 
			
		||||
        cv.Optional(
 | 
			
		||||
            CONF_BIT_ZERO_LOW, default="550us"
 | 
			
		||||
        ): cv.positive_time_period_microseconds,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    yield climate_ir.register_climate_ir(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_header_high(config[CONF_HEADER_HIGH]))
 | 
			
		||||
    cg.add(var.set_header_low(config[CONF_HEADER_LOW]))
 | 
			
		||||
    cg.add(var.set_bit_high(config[CONF_BIT_HIGH]))
 | 
			
		||||
    cg.add(var.set_bit_one_low(config[CONF_BIT_ONE_LOW]))
 | 
			
		||||
    cg.add(var.set_bit_zero_low(config[CONF_BIT_ZERO_LOW]))
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ static const char *TAG = "climate.climate_ir_lg";
 | 
			
		||||
const uint32_t COMMAND_ON = 0x00000;
 | 
			
		||||
const uint32_t COMMAND_ON_AI = 0x03000;
 | 
			
		||||
const uint32_t COMMAND_COOL = 0x08000;
 | 
			
		||||
const uint32_t COMMAND_HEAT = 0x0C000;
 | 
			
		||||
const uint32_t COMMAND_OFF = 0xC0000;
 | 
			
		||||
const uint32_t COMMAND_SWING = 0x10000;
 | 
			
		||||
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
 | 
			
		||||
@@ -28,13 +29,6 @@ const uint8_t TEMP_RANGE = TEMP_MAX - TEMP_MIN + 1;
 | 
			
		||||
const uint32_t TEMP_MASK = 0XF00;
 | 
			
		||||
const uint32_t TEMP_SHIFT = 8;
 | 
			
		||||
 | 
			
		||||
// Constants
 | 
			
		||||
static const uint32_t HEADER_HIGH_US = 8000;
 | 
			
		||||
static const uint32_t HEADER_LOW_US = 4000;
 | 
			
		||||
static const uint32_t BIT_HIGH_US = 600;
 | 
			
		||||
static const uint32_t BIT_ONE_LOW_US = 1600;
 | 
			
		||||
static const uint32_t BIT_ZERO_LOW_US = 550;
 | 
			
		||||
 | 
			
		||||
const uint16_t BITS = 28;
 | 
			
		||||
 | 
			
		||||
void LgIrClimate::transmit_state() {
 | 
			
		||||
@@ -55,6 +49,9 @@ void LgIrClimate::transmit_state() {
 | 
			
		||||
        case climate::CLIMATE_MODE_COOL:
 | 
			
		||||
          remote_state |= COMMAND_COOL;
 | 
			
		||||
          break;
 | 
			
		||||
        case climate::CLIMATE_MODE_HEAT:
 | 
			
		||||
          remote_state |= COMMAND_HEAT;
 | 
			
		||||
          break;
 | 
			
		||||
        case climate::CLIMATE_MODE_AUTO:
 | 
			
		||||
          remote_state |= COMMAND_AUTO;
 | 
			
		||||
          break;
 | 
			
		||||
@@ -73,7 +70,8 @@ void LgIrClimate::transmit_state() {
 | 
			
		||||
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_OFF) {
 | 
			
		||||
      remote_state |= FAN_AUTO;
 | 
			
		||||
    } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY) {
 | 
			
		||||
    } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY ||
 | 
			
		||||
               this->mode == climate::CLIMATE_MODE_HEAT) {
 | 
			
		||||
      switch (this->fan_mode) {
 | 
			
		||||
        case climate::CLIMATE_FAN_HIGH:
 | 
			
		||||
          remote_state |= FAN_MAX;
 | 
			
		||||
@@ -95,7 +93,7 @@ void LgIrClimate::transmit_state() {
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_AUTO;
 | 
			
		||||
      // remote_state |= FAN_MODE_AUTO_DRY;
 | 
			
		||||
    }
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_COOL) {
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
 | 
			
		||||
      auto temp = (uint8_t) roundf(clamp(this->target_temperature, TEMP_MIN, TEMP_MAX));
 | 
			
		||||
      remote_state |= ((temp - 15) << TEMP_SHIFT);
 | 
			
		||||
    }
 | 
			
		||||
@@ -108,13 +106,13 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
 | 
			
		||||
  uint8_t nbits = 0;
 | 
			
		||||
  uint32_t remote_state = 0;
 | 
			
		||||
 | 
			
		||||
  if (!data.expect_item(HEADER_HIGH_US, HEADER_LOW_US))
 | 
			
		||||
  if (!data.expect_item(this->header_high_, this->header_low_))
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  for (nbits = 0; nbits < 32; nbits++) {
 | 
			
		||||
    if (data.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
 | 
			
		||||
    if (data.expect_item(this->bit_high_, this->bit_one_low_)) {
 | 
			
		||||
      remote_state = (remote_state << 1) | 1;
 | 
			
		||||
    } else if (data.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
 | 
			
		||||
    } else if (data.expect_item(this->bit_high_, this->bit_zero_low_)) {
 | 
			
		||||
      remote_state = (remote_state << 1) | 0;
 | 
			
		||||
    } else if (nbits == BITS) {
 | 
			
		||||
      break;
 | 
			
		||||
@@ -141,29 +139,32 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
 | 
			
		||||
  } else {
 | 
			
		||||
    if ((remote_state & COMMAND_MASK) == COMMAND_AUTO)
 | 
			
		||||
      this->mode = climate::CLIMATE_MODE_AUTO;
 | 
			
		||||
    else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) {
 | 
			
		||||
    else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN)
 | 
			
		||||
      this->mode = climate::CLIMATE_MODE_DRY;
 | 
			
		||||
    else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) {
 | 
			
		||||
      this->mode = climate::CLIMATE_MODE_HEAT;
 | 
			
		||||
    } else {
 | 
			
		||||
      this->mode = climate::CLIMATE_MODE_COOL;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Temperature
 | 
			
		||||
  if (this->mode == climate::CLIMATE_MODE_COOL)
 | 
			
		||||
    this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
 | 
			
		||||
    // Temperature
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT)
 | 
			
		||||
      this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
 | 
			
		||||
 | 
			
		||||
  // Fan Speed
 | 
			
		||||
  if (this->mode == climate::CLIMATE_MODE_AUTO) {
 | 
			
		||||
    this->fan_mode = climate::CLIMATE_FAN_AUTO;
 | 
			
		||||
  } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY) {
 | 
			
		||||
    if ((remote_state & FAN_MASK) == FAN_AUTO)
 | 
			
		||||
    // Fan Speed
 | 
			
		||||
    if (this->mode == climate::CLIMATE_MODE_AUTO) {
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_AUTO;
 | 
			
		||||
    else if ((remote_state & FAN_MASK) == FAN_MIN)
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_LOW;
 | 
			
		||||
    else if ((remote_state & FAN_MASK) == FAN_MED)
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
 | 
			
		||||
    else if ((remote_state & FAN_MASK) == FAN_MAX)
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_HIGH;
 | 
			
		||||
    } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT ||
 | 
			
		||||
               this->mode == climate::CLIMATE_MODE_DRY) {
 | 
			
		||||
      if ((remote_state & FAN_MASK) == FAN_AUTO)
 | 
			
		||||
        this->fan_mode = climate::CLIMATE_FAN_AUTO;
 | 
			
		||||
      else if ((remote_state & FAN_MASK) == FAN_MIN)
 | 
			
		||||
        this->fan_mode = climate::CLIMATE_FAN_LOW;
 | 
			
		||||
      else if ((remote_state & FAN_MASK) == FAN_MED)
 | 
			
		||||
        this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
 | 
			
		||||
      else if ((remote_state & FAN_MASK) == FAN_MAX)
 | 
			
		||||
        this->fan_mode = climate::CLIMATE_FAN_HIGH;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
 | 
			
		||||
@@ -179,15 +180,16 @@ void LgIrClimate::transmit_(uint32_t value) {
 | 
			
		||||
  data->set_carrier_frequency(38000);
 | 
			
		||||
  data->reserve(2 + BITS * 2u);
 | 
			
		||||
 | 
			
		||||
  data->item(HEADER_HIGH_US, HEADER_LOW_US);
 | 
			
		||||
  data->item(this->header_high_, this->header_low_);
 | 
			
		||||
 | 
			
		||||
  for (uint32_t mask = 1UL << (BITS - 1); mask != 0; mask >>= 1) {
 | 
			
		||||
    if (value & mask)
 | 
			
		||||
      data->item(BIT_HIGH_US, BIT_ONE_LOW_US);
 | 
			
		||||
    else
 | 
			
		||||
      data->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
 | 
			
		||||
    if (value & mask) {
 | 
			
		||||
      data->item(this->bit_high_, this->bit_one_low_);
 | 
			
		||||
    } else {
 | 
			
		||||
      data->item(this->bit_high_, this->bit_zero_low_);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  data->mark(BIT_HIGH_US);
 | 
			
		||||
  data->mark(this->bit_high_);
 | 
			
		||||
  transmit.perform();
 | 
			
		||||
}
 | 
			
		||||
void LgIrClimate::calc_checksum_(uint32_t &value) {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,11 @@ class LgIrClimate : public climate_ir::ClimateIR {
 | 
			
		||||
      this->swing_mode = climate::CLIMATE_SWING_OFF;
 | 
			
		||||
    climate_ir::ClimateIR::control(call);
 | 
			
		||||
  }
 | 
			
		||||
  void set_header_high(uint32_t header_high) { this->header_high_ = header_high; }
 | 
			
		||||
  void set_header_low(uint32_t header_low) { this->header_low_ = header_low; }
 | 
			
		||||
  void set_bit_high(uint32_t bit_high) { this->bit_high_ = bit_high; }
 | 
			
		||||
  void set_bit_one_low(uint32_t bit_one_low) { this->bit_one_low_ = bit_one_low; }
 | 
			
		||||
  void set_bit_zero_low(uint32_t bit_zero_low) { this->bit_zero_low_ = bit_zero_low; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  /// Transmit via IR the state of this climate controller.
 | 
			
		||||
@@ -37,6 +42,12 @@ class LgIrClimate : public climate_ir::ClimateIR {
 | 
			
		||||
  void calc_checksum_(uint32_t &value);
 | 
			
		||||
  void transmit_(uint32_t value);
 | 
			
		||||
 | 
			
		||||
  uint32_t header_high_;
 | 
			
		||||
  uint32_t header_low_;
 | 
			
		||||
  uint32_t bit_high_;
 | 
			
		||||
  uint32_t bit_one_low_;
 | 
			
		||||
  uint32_t bit_zero_low_;
 | 
			
		||||
 | 
			
		||||
  climate::ClimateMode mode_before_{climate::CLIMATE_MODE_OFF};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,22 +2,56 @@ from esphome import config_validation as cv
 | 
			
		||||
from esphome import codegen as cg
 | 
			
		||||
from esphome.const import CONF_BLUE, CONF_GREEN, CONF_ID, CONF_RED, CONF_WHITE
 | 
			
		||||
 | 
			
		||||
ColorStruct = cg.esphome_ns.struct('Color')
 | 
			
		||||
ColorStruct = cg.esphome_ns.struct("Color")
 | 
			
		||||
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.Required(CONF_ID): cv.declare_id(ColorStruct),
 | 
			
		||||
    cv.Optional(CONF_RED, default=0.0): cv.percentage,
 | 
			
		||||
    cv.Optional(CONF_GREEN, default=0.0): cv.percentage,
 | 
			
		||||
    cv.Optional(CONF_BLUE, default=0.0): cv.percentage,
 | 
			
		||||
    cv.Optional(CONF_WHITE, default=0.0): cv.percentage,
 | 
			
		||||
}).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
CONF_RED_INT = "red_int"
 | 
			
		||||
CONF_GREEN_INT = "green_int"
 | 
			
		||||
CONF_BLUE_INT = "blue_int"
 | 
			
		||||
CONF_WHITE_INT = "white_int"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.declare_id(ColorStruct),
 | 
			
		||||
        cv.Exclusive(CONF_RED, "red"): cv.percentage,
 | 
			
		||||
        cv.Exclusive(CONF_RED_INT, "red"): cv.uint8_t,
 | 
			
		||||
        cv.Exclusive(CONF_GREEN, "green"): cv.percentage,
 | 
			
		||||
        cv.Exclusive(CONF_GREEN_INT, "green"): cv.uint8_t,
 | 
			
		||||
        cv.Exclusive(CONF_BLUE, "blue"): cv.percentage,
 | 
			
		||||
        cv.Exclusive(CONF_BLUE_INT, "blue"): cv.uint8_t,
 | 
			
		||||
        cv.Exclusive(CONF_WHITE, "white"): cv.percentage,
 | 
			
		||||
        cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t,
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    cg.variable(config[CONF_ID], cg.StructInitializer(
 | 
			
		||||
        ColorStruct,
 | 
			
		||||
        ('r', config[CONF_RED]),
 | 
			
		||||
        ('g', config[CONF_GREEN]),
 | 
			
		||||
        ('b', config[CONF_BLUE]),
 | 
			
		||||
        ('w', config[CONF_WHITE])))
 | 
			
		||||
    r = 0
 | 
			
		||||
    if CONF_RED in config:
 | 
			
		||||
        r = int(config[CONF_RED] * 255)
 | 
			
		||||
    elif CONF_RED_INT in config:
 | 
			
		||||
        r = config[CONF_RED_INT]
 | 
			
		||||
 | 
			
		||||
    g = 0
 | 
			
		||||
    if CONF_GREEN in config:
 | 
			
		||||
        g = int(config[CONF_GREEN] * 255)
 | 
			
		||||
    elif CONF_GREEN_INT in config:
 | 
			
		||||
        g = config[CONF_GREEN_INT]
 | 
			
		||||
 | 
			
		||||
    b = 0
 | 
			
		||||
    if CONF_BLUE in config:
 | 
			
		||||
        b = int(config[CONF_BLUE] * 255)
 | 
			
		||||
    elif CONF_BLUE_INT in config:
 | 
			
		||||
        b = config[CONF_BLUE_INT]
 | 
			
		||||
 | 
			
		||||
    w = 0
 | 
			
		||||
    if CONF_WHITE in config:
 | 
			
		||||
        w = int(config[CONF_WHITE] * 255)
 | 
			
		||||
    elif CONF_WHITE_INT in config:
 | 
			
		||||
        w = config[CONF_WHITE_INT]
 | 
			
		||||
 | 
			
		||||
    cg.new_variable(
 | 
			
		||||
        config[CONF_ID],
 | 
			
		||||
        cg.StructInitializer(ColorStruct, ("r", r), ("g", g), ("b", b), ("w", w)),
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -3,15 +3,17 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import climate_ir
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ['climate_ir']
 | 
			
		||||
CODEOWNERS = ['@glmnet']
 | 
			
		||||
AUTO_LOAD = ["climate_ir"]
 | 
			
		||||
CODEOWNERS = ["@glmnet"]
 | 
			
		||||
 | 
			
		||||
coolix_ns = cg.esphome_ns.namespace('coolix')
 | 
			
		||||
CoolixClimate = coolix_ns.class_('CoolixClimate', climate_ir.ClimateIR)
 | 
			
		||||
coolix_ns = cg.esphome_ns.namespace("coolix")
 | 
			
		||||
CoolixClimate = coolix_ns.class_("CoolixClimate", climate_ir.ClimateIR)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(CoolixClimate),
 | 
			
		||||
})
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CoolixClimate),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -3,54 +3,74 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.automation import maybe_simple_id, Condition
 | 
			
		||||
from esphome.components import mqtt
 | 
			
		||||
from esphome.const import CONF_ID, CONF_INTERNAL, CONF_DEVICE_CLASS, CONF_STATE, \
 | 
			
		||||
    CONF_POSITION, CONF_TILT, CONF_STOP, CONF_MQTT_ID, CONF_NAME
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INTERNAL,
 | 
			
		||||
    CONF_DEVICE_CLASS,
 | 
			
		||||
    CONF_STATE,
 | 
			
		||||
    CONF_POSITION,
 | 
			
		||||
    CONF_TILT,
 | 
			
		||||
    CONF_STOP,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_NAME,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine, coroutine_with_priority
 | 
			
		||||
 | 
			
		||||
IS_PLATFORM_COMPONENT = True
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ['@esphome/core']
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
DEVICE_CLASSES = [
 | 
			
		||||
    '', 'awning', 'blind', 'curtain', 'damper', 'door', 'garage',
 | 
			
		||||
    'gate', 'shade', 'shutter', 'window'
 | 
			
		||||
    "",
 | 
			
		||||
    "awning",
 | 
			
		||||
    "blind",
 | 
			
		||||
    "curtain",
 | 
			
		||||
    "damper",
 | 
			
		||||
    "door",
 | 
			
		||||
    "garage",
 | 
			
		||||
    "gate",
 | 
			
		||||
    "shade",
 | 
			
		||||
    "shutter",
 | 
			
		||||
    "window",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
cover_ns = cg.esphome_ns.namespace('cover')
 | 
			
		||||
cover_ns = cg.esphome_ns.namespace("cover")
 | 
			
		||||
 | 
			
		||||
Cover = cover_ns.class_('Cover', cg.Nameable)
 | 
			
		||||
Cover = cover_ns.class_("Cover", cg.Nameable)
 | 
			
		||||
 | 
			
		||||
COVER_OPEN = cover_ns.COVER_OPEN
 | 
			
		||||
COVER_CLOSED = cover_ns.COVER_CLOSED
 | 
			
		||||
 | 
			
		||||
COVER_STATES = {
 | 
			
		||||
    'OPEN': COVER_OPEN,
 | 
			
		||||
    'CLOSED': COVER_CLOSED,
 | 
			
		||||
    "OPEN": COVER_OPEN,
 | 
			
		||||
    "CLOSED": COVER_CLOSED,
 | 
			
		||||
}
 | 
			
		||||
validate_cover_state = cv.enum(COVER_STATES, upper=True)
 | 
			
		||||
 | 
			
		||||
CoverOperation = cover_ns.enum('CoverOperation')
 | 
			
		||||
CoverOperation = cover_ns.enum("CoverOperation")
 | 
			
		||||
COVER_OPERATIONS = {
 | 
			
		||||
    'IDLE': CoverOperation.COVER_OPERATION_IDLE,
 | 
			
		||||
    'OPENING': CoverOperation.COVER_OPERATION_OPENING,
 | 
			
		||||
    'CLOSING': CoverOperation.COVER_OPERATION_CLOSING,
 | 
			
		||||
    "IDLE": CoverOperation.COVER_OPERATION_IDLE,
 | 
			
		||||
    "OPENING": CoverOperation.COVER_OPERATION_OPENING,
 | 
			
		||||
    "CLOSING": CoverOperation.COVER_OPERATION_CLOSING,
 | 
			
		||||
}
 | 
			
		||||
validate_cover_operation = cv.enum(COVER_OPERATIONS, upper=True)
 | 
			
		||||
 | 
			
		||||
# Actions
 | 
			
		||||
OpenAction = cover_ns.class_('OpenAction', automation.Action)
 | 
			
		||||
CloseAction = cover_ns.class_('CloseAction', automation.Action)
 | 
			
		||||
StopAction = cover_ns.class_('StopAction', automation.Action)
 | 
			
		||||
ControlAction = cover_ns.class_('ControlAction', automation.Action)
 | 
			
		||||
CoverPublishAction = cover_ns.class_('CoverPublishAction', automation.Action)
 | 
			
		||||
CoverIsOpenCondition = cover_ns.class_('CoverIsOpenCondition', Condition)
 | 
			
		||||
CoverIsClosedCondition = cover_ns.class_('CoverIsClosedCondition', Condition)
 | 
			
		||||
OpenAction = cover_ns.class_("OpenAction", automation.Action)
 | 
			
		||||
CloseAction = cover_ns.class_("CloseAction", automation.Action)
 | 
			
		||||
StopAction = cover_ns.class_("StopAction", automation.Action)
 | 
			
		||||
ControlAction = cover_ns.class_("ControlAction", automation.Action)
 | 
			
		||||
CoverPublishAction = cover_ns.class_("CoverPublishAction", automation.Action)
 | 
			
		||||
CoverIsOpenCondition = cover_ns.class_("CoverIsOpenCondition", Condition)
 | 
			
		||||
CoverIsClosedCondition = cover_ns.class_("CoverIsClosedCondition", Condition)
 | 
			
		||||
 | 
			
		||||
COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(Cover),
 | 
			
		||||
    cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTCoverComponent),
 | 
			
		||||
    cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
 | 
			
		||||
    # TODO: MQTT topic options
 | 
			
		||||
})
 | 
			
		||||
COVER_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(Cover),
 | 
			
		||||
        cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
 | 
			
		||||
        cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
 | 
			
		||||
        # TODO: MQTT topic options
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine
 | 
			
		||||
@@ -74,39 +94,43 @@ def register_cover(var, config):
 | 
			
		||||
    yield setup_cover_core_(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
COVER_ACTION_SCHEMA = maybe_simple_id({
 | 
			
		||||
    cv.Required(CONF_ID): cv.use_id(Cover),
 | 
			
		||||
})
 | 
			
		||||
COVER_ACTION_SCHEMA = maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(Cover),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action('cover.open', OpenAction, COVER_ACTION_SCHEMA)
 | 
			
		||||
@automation.register_action("cover.open", OpenAction, COVER_ACTION_SCHEMA)
 | 
			
		||||
def cover_open_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = yield cg.get_variable(config[CONF_ID])
 | 
			
		||||
    yield cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action('cover.close', CloseAction, COVER_ACTION_SCHEMA)
 | 
			
		||||
@automation.register_action("cover.close", CloseAction, COVER_ACTION_SCHEMA)
 | 
			
		||||
def cover_close_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = yield cg.get_variable(config[CONF_ID])
 | 
			
		||||
    yield cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action('cover.stop', StopAction, COVER_ACTION_SCHEMA)
 | 
			
		||||
@automation.register_action("cover.stop", StopAction, COVER_ACTION_SCHEMA)
 | 
			
		||||
def cover_stop_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = yield cg.get_variable(config[CONF_ID])
 | 
			
		||||
    yield cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
COVER_CONTROL_ACTION_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.Required(CONF_ID): cv.use_id(Cover),
 | 
			
		||||
    cv.Optional(CONF_STOP): cv.templatable(cv.boolean),
 | 
			
		||||
    cv.Exclusive(CONF_STATE, 'pos'): cv.templatable(validate_cover_state),
 | 
			
		||||
    cv.Exclusive(CONF_POSITION, 'pos'): cv.templatable(cv.percentage),
 | 
			
		||||
    cv.Optional(CONF_TILT): cv.templatable(cv.percentage),
 | 
			
		||||
})
 | 
			
		||||
COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(Cover),
 | 
			
		||||
        cv.Optional(CONF_STOP): cv.templatable(cv.boolean),
 | 
			
		||||
        cv.Exclusive(CONF_STATE, "pos"): cv.templatable(validate_cover_state),
 | 
			
		||||
        cv.Exclusive(CONF_POSITION, "pos"): cv.templatable(cv.percentage),
 | 
			
		||||
        cv.Optional(CONF_TILT): cv.templatable(cv.percentage),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action('cover.control', ControlAction, COVER_CONTROL_ACTION_SCHEMA)
 | 
			
		||||
@automation.register_action("cover.control", ControlAction, COVER_CONTROL_ACTION_SCHEMA)
 | 
			
		||||
def cover_control_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = yield cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
@@ -127,5 +151,5 @@ def cover_control_to_code(config, action_id, template_arg, args):
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(100.0)
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    cg.add_define('USE_COVER')
 | 
			
		||||
    cg.add_define("USE_COVER")
 | 
			
		||||
    cg.add_global(cover_ns.using)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,45 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, uart
 | 
			
		||||
from esphome.const import CONF_CURRENT, CONF_ID, CONF_POWER, CONF_VOLTAGE, \
 | 
			
		||||
    UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_CURRENT,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_POWER,
 | 
			
		||||
    CONF_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ['uart']
 | 
			
		||||
DEPENDENCIES = ["uart"]
 | 
			
		||||
 | 
			
		||||
cse7766_ns = cg.esphome_ns.namespace('cse7766')
 | 
			
		||||
CSE7766Component = cse7766_ns.class_('CSE7766Component', cg.PollingComponent, uart.UARTDevice)
 | 
			
		||||
cse7766_ns = cg.esphome_ns.namespace("cse7766")
 | 
			
		||||
CSE7766Component = cse7766_ns.class_(
 | 
			
		||||
    "CSE7766Component", cg.PollingComponent, uart.UARTDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(CSE7766Component),
 | 
			
		||||
 | 
			
		||||
    cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
 | 
			
		||||
    cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
 | 
			
		||||
    cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1),
 | 
			
		||||
}).extend(cv.polling_component_schema('60s')).extend(uart.UART_DEVICE_SCHEMA)
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(CSE7766Component),
 | 
			
		||||
            cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CURRENT): sensor.sensor_schema(
 | 
			
		||||
                UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_POWER): sensor.sensor_schema(
 | 
			
		||||
                UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(uart.UART_DEVICE_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,35 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, voltage_sampler
 | 
			
		||||
from esphome.const import CONF_SENSOR, CONF_ID, ICON_FLASH, UNIT_AMPERE
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_SENSOR,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    ICON_EMPTY,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ['voltage_sampler']
 | 
			
		||||
CODEOWNERS = ['@jesserockz']
 | 
			
		||||
AUTO_LOAD = ["voltage_sampler"]
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
 | 
			
		||||
CONF_SAMPLE_DURATION = 'sample_duration'
 | 
			
		||||
CONF_SAMPLE_DURATION = "sample_duration"
 | 
			
		||||
 | 
			
		||||
ct_clamp_ns = cg.esphome_ns.namespace('ct_clamp')
 | 
			
		||||
CTClampSensor = ct_clamp_ns.class_('CTClampSensor', sensor.Sensor, cg.PollingComponent)
 | 
			
		||||
ct_clamp_ns = cg.esphome_ns.namespace("ct_clamp")
 | 
			
		||||
CTClampSensor = ct_clamp_ns.class_("CTClampSensor", sensor.Sensor, cg.PollingComponent)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2).extend({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(CTClampSensor),
 | 
			
		||||
    cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler),
 | 
			
		||||
    cv.Optional(CONF_SAMPLE_DURATION, default='200ms'): cv.positive_time_period_milliseconds,
 | 
			
		||||
}).extend(cv.polling_component_schema('60s'))
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    sensor.sensor_schema(UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT)
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(CTClampSensor),
 | 
			
		||||
            cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler),
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_SAMPLE_DURATION, default="200ms"
 | 
			
		||||
            ): cv.positive_time_period_milliseconds,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
custom_ns = cg.esphome_ns.namespace('custom')
 | 
			
		||||
custom_ns = cg.esphome_ns.namespace("custom")
 | 
			
		||||
 
 | 
			
		||||
@@ -4,18 +4,25 @@ from esphome.components import binary_sensor
 | 
			
		||||
from esphome.const import CONF_BINARY_SENSORS, CONF_ID, CONF_LAMBDA
 | 
			
		||||
from .. import custom_ns
 | 
			
		||||
 | 
			
		||||
CustomBinarySensorConstructor = custom_ns.class_('CustomBinarySensorConstructor')
 | 
			
		||||
CustomBinarySensorConstructor = custom_ns.class_("CustomBinarySensorConstructor")
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(CustomBinarySensorConstructor),
 | 
			
		||||
    cv.Required(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
    cv.Required(CONF_BINARY_SENSORS): cv.ensure_list(binary_sensor.BINARY_SENSOR_SCHEMA),
 | 
			
		||||
})
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CustomBinarySensorConstructor),
 | 
			
		||||
        cv.Required(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
        cv.Required(CONF_BINARY_SENSORS): cv.ensure_list(
 | 
			
		||||
            binary_sensor.BINARY_SENSOR_SCHEMA
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    template_ = yield cg.process_lambda(
 | 
			
		||||
        config[CONF_LAMBDA], [], return_type=cg.std_vector.template(binary_sensor.BinarySensorPtr))
 | 
			
		||||
        config[CONF_LAMBDA],
 | 
			
		||||
        [],
 | 
			
		||||
        return_type=cg.std_vector.template(binary_sensor.BinarySensorPtr),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    rhs = CustomBinarySensorConstructor(template_)
 | 
			
		||||
    custom = cg.variable(config[CONF_ID], rhs)
 | 
			
		||||
 
 | 
			
		||||
@@ -4,20 +4,24 @@ from esphome.components import climate
 | 
			
		||||
from esphome.const import CONF_ID, CONF_LAMBDA
 | 
			
		||||
from .. import custom_ns
 | 
			
		||||
 | 
			
		||||
CustomClimateConstructor = custom_ns.class_('CustomClimateConstructor')
 | 
			
		||||
CONF_CLIMATES = 'climates'
 | 
			
		||||
CustomClimateConstructor = custom_ns.class_("CustomClimateConstructor")
 | 
			
		||||
CONF_CLIMATES = "climates"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(CustomClimateConstructor),
 | 
			
		||||
    cv.Required(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
    cv.Required(CONF_CLIMATES): cv.ensure_list(climate.CLIMATE_SCHEMA),
 | 
			
		||||
})
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CustomClimateConstructor),
 | 
			
		||||
        cv.Required(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
        cv.Required(CONF_CLIMATES): cv.ensure_list(climate.CLIMATE_SCHEMA),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    template_ = yield cg.process_lambda(
 | 
			
		||||
        config[CONF_LAMBDA], [],
 | 
			
		||||
        return_type=cg.std_vector.template(climate.Climate.operator('ptr')))
 | 
			
		||||
        config[CONF_LAMBDA],
 | 
			
		||||
        [],
 | 
			
		||||
        return_type=cg.std_vector.template(climate.Climate.operator("ptr")),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    rhs = CustomClimateConstructor(template_)
 | 
			
		||||
    custom = cg.variable(config[CONF_ID], rhs)
 | 
			
		||||
 
 | 
			
		||||
@@ -4,20 +4,24 @@ from esphome.components import cover
 | 
			
		||||
from esphome.const import CONF_ID, CONF_LAMBDA
 | 
			
		||||
from .. import custom_ns
 | 
			
		||||
 | 
			
		||||
CustomCoverConstructor = custom_ns.class_('CustomCoverConstructor')
 | 
			
		||||
CONF_COVERS = 'covers'
 | 
			
		||||
CustomCoverConstructor = custom_ns.class_("CustomCoverConstructor")
 | 
			
		||||
CONF_COVERS = "covers"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(CustomCoverConstructor),
 | 
			
		||||
    cv.Required(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
    cv.Required(CONF_COVERS): cv.ensure_list(cover.COVER_SCHEMA),
 | 
			
		||||
})
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CustomCoverConstructor),
 | 
			
		||||
        cv.Required(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
        cv.Required(CONF_COVERS): cv.ensure_list(cover.COVER_SCHEMA),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    template_ = yield cg.process_lambda(
 | 
			
		||||
        config[CONF_LAMBDA], [],
 | 
			
		||||
        return_type=cg.std_vector.template(cover.Cover.operator('ptr')))
 | 
			
		||||
        config[CONF_LAMBDA],
 | 
			
		||||
        [],
 | 
			
		||||
        return_type=cg.std_vector.template(cover.Cover.operator("ptr")),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    rhs = CustomCoverConstructor(template_)
 | 
			
		||||
    custom = cg.variable(config[CONF_ID], rhs)
 | 
			
		||||
 
 | 
			
		||||
@@ -4,20 +4,24 @@ from esphome.components import light
 | 
			
		||||
from esphome.const import CONF_ID, CONF_LAMBDA
 | 
			
		||||
from .. import custom_ns
 | 
			
		||||
 | 
			
		||||
CustomLightOutputConstructor = custom_ns.class_('CustomLightOutputConstructor')
 | 
			
		||||
CONF_LIGHTS = 'lights'
 | 
			
		||||
CustomLightOutputConstructor = custom_ns.class_("CustomLightOutputConstructor")
 | 
			
		||||
CONF_LIGHTS = "lights"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema({
 | 
			
		||||
    cv.GenerateID(): cv.declare_id(CustomLightOutputConstructor),
 | 
			
		||||
    cv.Required(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
    cv.Required(CONF_LIGHTS): cv.ensure_list(light.ADDRESSABLE_LIGHT_SCHEMA),
 | 
			
		||||
})
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CustomLightOutputConstructor),
 | 
			
		||||
        cv.Required(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
        cv.Required(CONF_LIGHTS): cv.ensure_list(light.ADDRESSABLE_LIGHT_SCHEMA),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    template_ = yield cg.process_lambda(
 | 
			
		||||
        config[CONF_LAMBDA], [],
 | 
			
		||||
        return_type=cg.std_vector.template(light.LightOutput.operator('ptr')))
 | 
			
		||||
        config[CONF_LAMBDA],
 | 
			
		||||
        [],
 | 
			
		||||
        return_type=cg.std_vector.template(light.LightOutput.operator("ptr")),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    rhs = CustomLightOutputConstructor(template_)
 | 
			
		||||
    custom = cg.variable(config[CONF_ID], rhs)
 | 
			
		||||
 
 | 
			
		||||
@@ -4,41 +4,55 @@ from esphome.components import output
 | 
			
		||||
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_OUTPUTS, CONF_TYPE, CONF_BINARY
 | 
			
		||||
from .. import custom_ns
 | 
			
		||||
 | 
			
		||||
CustomBinaryOutputConstructor = custom_ns.class_('CustomBinaryOutputConstructor')
 | 
			
		||||
CustomFloatOutputConstructor = custom_ns.class_('CustomFloatOutputConstructor')
 | 
			
		||||
CustomBinaryOutputConstructor = custom_ns.class_("CustomBinaryOutputConstructor")
 | 
			
		||||
CustomFloatOutputConstructor = custom_ns.class_("CustomFloatOutputConstructor")
 | 
			
		||||
 | 
			
		||||
CONF_FLOAT = 'float'
 | 
			
		||||
CONF_FLOAT = "float"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.typed_schema({
 | 
			
		||||
    CONF_BINARY: cv.Schema({
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CustomBinaryOutputConstructor),
 | 
			
		||||
        cv.Required(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
        cv.Required(CONF_OUTPUTS):
 | 
			
		||||
            cv.ensure_list(output.BINARY_OUTPUT_SCHEMA.extend({
 | 
			
		||||
                cv.GenerateID(): cv.declare_id(output.BinaryOutput),
 | 
			
		||||
            })),
 | 
			
		||||
    }),
 | 
			
		||||
    CONF_FLOAT: cv.Schema({
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CustomFloatOutputConstructor),
 | 
			
		||||
        cv.Required(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
        cv.Required(CONF_OUTPUTS):
 | 
			
		||||
            cv.ensure_list(output.FLOAT_OUTPUT_SCHEMA.extend({
 | 
			
		||||
                cv.GenerateID(): cv.declare_id(output.FloatOutput),
 | 
			
		||||
            })),
 | 
			
		||||
    })
 | 
			
		||||
}, lower=True)
 | 
			
		||||
CONFIG_SCHEMA = cv.typed_schema(
 | 
			
		||||
    {
 | 
			
		||||
        CONF_BINARY: cv.Schema(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(): cv.declare_id(CustomBinaryOutputConstructor),
 | 
			
		||||
                cv.Required(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
                cv.Required(CONF_OUTPUTS): cv.ensure_list(
 | 
			
		||||
                    output.BINARY_OUTPUT_SCHEMA.extend(
 | 
			
		||||
                        {
 | 
			
		||||
                            cv.GenerateID(): cv.declare_id(output.BinaryOutput),
 | 
			
		||||
                        }
 | 
			
		||||
                    )
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_FLOAT: cv.Schema(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(): cv.declare_id(CustomFloatOutputConstructor),
 | 
			
		||||
                cv.Required(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
                cv.Required(CONF_OUTPUTS): cv.ensure_list(
 | 
			
		||||
                    output.FLOAT_OUTPUT_SCHEMA.extend(
 | 
			
		||||
                        {
 | 
			
		||||
                            cv.GenerateID(): cv.declare_id(output.FloatOutput),
 | 
			
		||||
                        }
 | 
			
		||||
                    )
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    lower=True,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    type = config[CONF_TYPE]
 | 
			
		||||
    if type == 'binary':
 | 
			
		||||
    if type == "binary":
 | 
			
		||||
        ret_type = output.BinaryOutputPtr
 | 
			
		||||
        klass = CustomBinaryOutputConstructor
 | 
			
		||||
    else:
 | 
			
		||||
        ret_type = output.FloatOutputPtr
 | 
			
		||||
        klass = CustomFloatOutputConstructor
 | 
			
		||||
    template_ = yield cg.process_lambda(config[CONF_LAMBDA], [],
 | 
			
		||||
                                        return_type=cg.std_vector.template(ret_type))
 | 
			
		||||
    template_ = yield cg.process_lambda(
 | 
			
		||||
        config[CONF_LAMBDA], [], return_type=cg.std_vector.template(ret_type)
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    rhs = klass(template_)
 | 
			
		||||
    custom = cg.variable(config[CONF_ID], rhs)
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user