mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 09:01:49 +00:00 
			
		
		
		
	Compare commits
	
		
			135 Commits
		
	
	
		
			2022.3.1
			...
			2022.5.0b1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					c2a59cb476 | ||
| 
						 | 
					d6e039a1d1 | ||
| 
						 | 
					0f1a7c2b69 | ||
| 
						 | 
					40ad9f4911 | ||
| 
						 | 
					4116caff6a | ||
| 
						 | 
					0b69f72315 | ||
| 
						 | 
					c569f5ddcf | ||
| 
						 | 
					62f9e181e0 | ||
| 
						 | 
					235a97ea10 | ||
| 
						 | 
					e541ae400c | ||
| 
						 | 
					4822abde86 | ||
| 
						 | 
					b7e52812f8 | ||
| 
						 | 
					69118120d9 | ||
| 
						 | 
					7cba0c6fb0 | ||
| 
						 | 
					5fac67ce15 | ||
| 
						 | 
					98c733108e | ||
| 
						 | 
					782186e13d | ||
| 
						 | 
					4e1f6518e8 | ||
| 
						 | 
					53e0fe8e51 | ||
| 
						 | 
					0e547390da | ||
| 
						 | 
					86b52df839 | ||
| 
						 | 
					d685fdf54a | ||
| 
						 | 
					d9caab4108 | ||
| 
						 | 
					44b68f140e | ||
| 
						 | 
					3a3d97dfa7 | ||
| 
						 | 
					47898b527c | ||
| 
						 | 
					a35f36ad39 | ||
| 
						 | 
					d13a397f8e | ||
| 
						 | 
					df999723f8 | ||
| 
						 | 
					8236e840a7 | ||
| 
						 | 
					e5b3625f73 | ||
| 
						 | 
					2e4645310b | ||
| 
						 | 
					50a32b387e | ||
| 
						 | 
					2059283707 | ||
| 
						 | 
					8e3af515c9 | ||
| 
						 | 
					6f88f0ea3f | ||
| 
						 | 
					d2f37cf3f9 | ||
| 
						 | 
					7c30d6254e | ||
| 
						 | 
					64fb39a653 | ||
| 
						 | 
					91895aa70c | ||
| 
						 | 
					68dfaf238b | ||
| 
						 | 
					ebf13a0ba0 | ||
| 
						 | 
					2bff9937b7 | ||
| 
						 | 
					256395c28d | ||
| 
						 | 
					3346bc8bba | ||
| 
						 | 
					6fe22a7e62 | ||
| 
						 | 
					757b98748b | ||
| 
						 | 
					7a778f3f33 | ||
| 
						 | 
					41d9059a2f | ||
| 
						 | 
					e26e0d7c01 | ||
| 
						 | 
					ad41c07a1f | ||
| 
						 | 
					9576d246ee | ||
| 
						 | 
					988d3ea8ba | ||
| 
						 | 
					0767b92b62 | ||
| 
						 | 
					5732f3b044 | ||
| 
						 | 
					712115b6ce | ||
| 
						 | 
					9283559c6b | ||
| 
						 | 
					6b393438e9 | ||
| 
						 | 
					2064abe16d | ||
| 
						 | 
					b605982f94 | ||
| 
						 | 
					343b9ab455 | ||
| 
						 | 
					dcb226b202 | ||
| 
						 | 
					2243021b58 | ||
| 
						 | 
					d5134e88b1 | ||
| 
						 | 
					c59adf612f | ||
| 
						 | 
					93b628d9a8 | ||
| 
						 | 
					6bac551d9f | ||
| 
						 | 
					70a35656e4 | ||
| 
						 | 
					047c18eac0 | ||
| 
						 | 
					b4a86ce6cf | ||
| 
						 | 
					a82d8ea0c3 | ||
| 
						 | 
					b778eed419 | ||
| 
						 | 
					ad57faa9a9 | ||
| 
						 | 
					a9b5e8d036 | ||
| 
						 | 
					8be704e591 | ||
| 
						 | 
					b622a8fa58 | ||
| 
						 | 
					a519e5c475 | ||
| 
						 | 
					d620b6dd5e | ||
| 
						 | 
					99335d986e | ||
| 
						 | 
					7895cd92cd | ||
| 
						 | 
					8b2c032da6 | ||
| 
						 | 
					da336247eb | ||
| 
						 | 
					dabd27d4be | ||
| 
						 | 
					fdda47db6e | ||
| 
						 | 
					efa6fd03e5 | ||
| 
						 | 
					9e3e34acf5 | ||
| 
						 | 
					a2d0c1bf18 | ||
| 
						 | 
					7663716ae8 | ||
| 
						 | 
					c2cacb3478 | ||
| 
						 | 
					84666b54b9 | ||
| 
						 | 
					2b91c23bf3 | ||
| 
						 | 
					3297267a16 | ||
| 
						 | 
					a9e653724c | ||
| 
						 | 
					5e79a1f500 | ||
| 
						 | 
					d4ff98680a | ||
| 
						 | 
					ba8d255cb4 | ||
| 
						 | 
					06f4ad922c | ||
| 
						 | 
					bff06e448b | ||
| 
						 | 
					d48ffa2913 | ||
| 
						 | 
					d97c3a7e01 | ||
| 
						 | 
					0b1161f7ef | ||
| 
						 | 
					061e1a471d | ||
| 
						 | 
					a39d874600 | ||
| 
						 | 
					de96376565 | ||
| 
						 | 
					c54c20ab3c | ||
| 
						 | 
					70fafa473b | ||
| 
						 | 
					2e436eae6b | ||
| 
						 | 
					fd7e861ff5 | ||
| 
						 | 
					792108686c | ||
| 
						 | 
					fa1b5117fd | ||
| 
						 | 
					b0bd9e0a34 | ||
| 
						 | 
					05dc97099a | ||
| 
						 | 
					9de61fcf58 | ||
| 
						 | 
					7f7175b184 | ||
| 
						 | 
					cf5c640ae4 | ||
| 
						 | 
					6b9371d105 | ||
| 
						 | 
					9a82057303 | ||
| 
						 | 
					48584e94c4 | ||
| 
						 | 
					d8024a5928 | ||
| 
						 | 
					2034ab4f6c | ||
| 
						 | 
					58b70b42dd | ||
| 
						 | 
					1496bc1b07 | ||
| 
						 | 
					bfbf88b2ea | ||
| 
						 | 
					e621b938e3 | ||
| 
						 | 
					0372d17a11 | ||
| 
						 | 
					4525588116 | ||
| 
						 | 
					68e957c147 | ||
| 
						 | 
					99f5ed1461 | ||
| 
						 | 
					59f67796dc | ||
| 
						 | 
					aafdfa933e | ||
| 
						 | 
					3208c8ed1e | ||
| 
						 | 
					6bf733e24e | ||
| 
						 | 
					65d3e8fbfc | ||
| 
						 | 
					a29d65d47c | ||
| 
						 | 
					0af1edefff | 
@@ -2,7 +2,7 @@
 | 
				
			|||||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
					# See https://pre-commit.com/hooks.html for more hooks
 | 
				
			||||||
repos:
 | 
					repos:
 | 
				
			||||||
  - repo: https://github.com/ambv/black
 | 
					  - repo: https://github.com/ambv/black
 | 
				
			||||||
    rev: 22.1.0
 | 
					    rev: 22.3.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
    - id: black
 | 
					    - id: black
 | 
				
			||||||
      args:
 | 
					      args:
 | 
				
			||||||
@@ -26,7 +26,7 @@ repos:
 | 
				
			|||||||
          - --branch=release
 | 
					          - --branch=release
 | 
				
			||||||
          - --branch=beta
 | 
					          - --branch=beta
 | 
				
			||||||
  - repo: https://github.com/asottile/pyupgrade
 | 
					  - repo: https://github.com/asottile/pyupgrade
 | 
				
			||||||
    rev: v2.31.0
 | 
					    rev: v2.31.1
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: pyupgrade
 | 
					      - id: pyupgrade
 | 
				
			||||||
        args: [--py38-plus]
 | 
					        args: [--py38-plus]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										15
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								CODEOWNERS
									
									
									
									
									
								
							@@ -28,8 +28,10 @@ esphome/components/atc_mithermometer/* @ahpohl
 | 
				
			|||||||
esphome/components/b_parasite/* @rbaron
 | 
					esphome/components/b_parasite/* @rbaron
 | 
				
			||||||
esphome/components/ballu/* @bazuchan
 | 
					esphome/components/ballu/* @bazuchan
 | 
				
			||||||
esphome/components/bang_bang/* @OttoWinter
 | 
					esphome/components/bang_bang/* @OttoWinter
 | 
				
			||||||
 | 
					esphome/components/bedjet/* @jhansche
 | 
				
			||||||
esphome/components/bh1750/* @OttoWinter
 | 
					esphome/components/bh1750/* @OttoWinter
 | 
				
			||||||
esphome/components/binary_sensor/* @esphome/core
 | 
					esphome/components/binary_sensor/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/bl0939/* @ziceva
 | 
				
			||||||
esphome/components/bl0940/* @tobias-
 | 
					esphome/components/bl0940/* @tobias-
 | 
				
			||||||
esphome/components/ble_client/* @buxtronix
 | 
					esphome/components/ble_client/* @buxtronix
 | 
				
			||||||
esphome/components/bme680_bsec/* @trvrnrth
 | 
					esphome/components/bme680_bsec/* @trvrnrth
 | 
				
			||||||
@@ -53,11 +55,13 @@ esphome/components/current_based/* @djwmarcx
 | 
				
			|||||||
esphome/components/daly_bms/* @s1lvi0
 | 
					esphome/components/daly_bms/* @s1lvi0
 | 
				
			||||||
esphome/components/dashboard_import/* @esphome/core
 | 
					esphome/components/dashboard_import/* @esphome/core
 | 
				
			||||||
esphome/components/debug/* @OttoWinter
 | 
					esphome/components/debug/* @OttoWinter
 | 
				
			||||||
 | 
					esphome/components/delonghi/* @grob6000
 | 
				
			||||||
esphome/components/dfplayer/* @glmnet
 | 
					esphome/components/dfplayer/* @glmnet
 | 
				
			||||||
esphome/components/dht/* @OttoWinter
 | 
					esphome/components/dht/* @OttoWinter
 | 
				
			||||||
esphome/components/ds1307/* @badbadc0ffee
 | 
					esphome/components/ds1307/* @badbadc0ffee
 | 
				
			||||||
esphome/components/dsmr/* @glmnet @zuidwijk
 | 
					esphome/components/dsmr/* @glmnet @zuidwijk
 | 
				
			||||||
esphome/components/ektf2232/* @jesserockz
 | 
					esphome/components/ektf2232/* @jesserockz
 | 
				
			||||||
 | 
					esphome/components/ens210/* @itn3rd77
 | 
				
			||||||
esphome/components/esp32/* @esphome/core
 | 
					esphome/components/esp32/* @esphome/core
 | 
				
			||||||
esphome/components/esp32_ble/* @jesserockz
 | 
					esphome/components/esp32_ble/* @jesserockz
 | 
				
			||||||
esphome/components/esp32_ble_server/* @jesserockz
 | 
					esphome/components/esp32_ble_server/* @jesserockz
 | 
				
			||||||
@@ -82,6 +86,7 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal
 | 
				
			|||||||
esphome/components/homeassistant/* @OttoWinter
 | 
					esphome/components/homeassistant/* @OttoWinter
 | 
				
			||||||
esphome/components/honeywellabp/* @RubyBailey
 | 
					esphome/components/honeywellabp/* @RubyBailey
 | 
				
			||||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
 | 
					esphome/components/hrxl_maxsonar_wr/* @netmikey
 | 
				
			||||||
 | 
					esphome/components/hydreon_rgxx/* @functionpointer
 | 
				
			||||||
esphome/components/i2c/* @esphome/core
 | 
					esphome/components/i2c/* @esphome/core
 | 
				
			||||||
esphome/components/improv_serial/* @esphome/core
 | 
					esphome/components/improv_serial/* @esphome/core
 | 
				
			||||||
esphome/components/ina260/* @MrEditor97
 | 
					esphome/components/ina260/* @MrEditor97
 | 
				
			||||||
@@ -151,6 +156,7 @@ esphome/components/preferences/* @esphome/core
 | 
				
			|||||||
esphome/components/psram/* @esphome/core
 | 
					esphome/components/psram/* @esphome/core
 | 
				
			||||||
esphome/components/pulse_meter/* @cstaahl @stevebaxter
 | 
					esphome/components/pulse_meter/* @cstaahl @stevebaxter
 | 
				
			||||||
esphome/components/pvvx_mithermometer/* @pasiz
 | 
					esphome/components/pvvx_mithermometer/* @pasiz
 | 
				
			||||||
 | 
					esphome/components/qmp6988/* @andrewpc
 | 
				
			||||||
esphome/components/qr_code/* @wjtje
 | 
					esphome/components/qr_code/* @wjtje
 | 
				
			||||||
esphome/components/radon_eye_ble/* @jeffeb3
 | 
					esphome/components/radon_eye_ble/* @jeffeb3
 | 
				
			||||||
esphome/components/radon_eye_rd200/* @jeffeb3
 | 
					esphome/components/radon_eye_rd200/* @jeffeb3
 | 
				
			||||||
@@ -162,20 +168,26 @@ esphome/components/rf_bridge/* @jesserockz
 | 
				
			|||||||
esphome/components/rgbct/* @jesserockz
 | 
					esphome/components/rgbct/* @jesserockz
 | 
				
			||||||
esphome/components/rtttl/* @glmnet
 | 
					esphome/components/rtttl/* @glmnet
 | 
				
			||||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
 | 
					esphome/components/safe_mode/* @jsuanet @paulmonigatti
 | 
				
			||||||
esphome/components/scd4x/* @sjtrny
 | 
					esphome/components/scd4x/* @martgras @sjtrny
 | 
				
			||||||
esphome/components/script/* @esphome/core
 | 
					esphome/components/script/* @esphome/core
 | 
				
			||||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
 | 
					esphome/components/sdm_meter/* @jesserockz @polyfaces
 | 
				
			||||||
esphome/components/sdp3x/* @Azimath
 | 
					esphome/components/sdp3x/* @Azimath
 | 
				
			||||||
esphome/components/selec_meter/* @sourabhjaiswal
 | 
					esphome/components/selec_meter/* @sourabhjaiswal
 | 
				
			||||||
esphome/components/select/* @esphome/core
 | 
					esphome/components/select/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/sen5x/* @martgras
 | 
				
			||||||
 | 
					esphome/components/sensirion_common/* @martgras
 | 
				
			||||||
esphome/components/sensor/* @esphome/core
 | 
					esphome/components/sensor/* @esphome/core
 | 
				
			||||||
esphome/components/sgp40/* @SenexCrenshaw
 | 
					esphome/components/sgp40/* @SenexCrenshaw
 | 
				
			||||||
 | 
					esphome/components/shelly_dimmer/* @edge90 @rnauber
 | 
				
			||||||
esphome/components/sht4x/* @sjtrny
 | 
					esphome/components/sht4x/* @sjtrny
 | 
				
			||||||
esphome/components/shutdown/* @esphome/core @jsuanet
 | 
					esphome/components/shutdown/* @esphome/core @jsuanet
 | 
				
			||||||
esphome/components/sim800l/* @glmnet
 | 
					esphome/components/sim800l/* @glmnet
 | 
				
			||||||
esphome/components/sm2135/* @BoukeHaarsma23
 | 
					esphome/components/sm2135/* @BoukeHaarsma23
 | 
				
			||||||
 | 
					esphome/components/sml/* @alengwenus
 | 
				
			||||||
esphome/components/socket/* @esphome/core
 | 
					esphome/components/socket/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/sonoff_d1/* @anatoly-savchenkov
 | 
				
			||||||
esphome/components/spi/* @esphome/core
 | 
					esphome/components/spi/* @esphome/core
 | 
				
			||||||
 | 
					esphome/components/sps30/* @martgras
 | 
				
			||||||
esphome/components/ssd1322_base/* @kbx81
 | 
					esphome/components/ssd1322_base/* @kbx81
 | 
				
			||||||
esphome/components/ssd1322_spi/* @kbx81
 | 
					esphome/components/ssd1322_spi/* @kbx81
 | 
				
			||||||
esphome/components/ssd1325_base/* @kbx81
 | 
					esphome/components/ssd1325_base/* @kbx81
 | 
				
			||||||
@@ -222,4 +234,5 @@ esphome/components/whirlpool/* @glmnet
 | 
				
			|||||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
 | 
					esphome/components/xiaomi_lywsd03mmc/* @ahpohl
 | 
				
			||||||
esphome/components/xiaomi_mhoc303/* @drug123
 | 
					esphome/components/xiaomi_mhoc303/* @drug123
 | 
				
			||||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
 | 
					esphome/components/xiaomi_mhoc401/* @vevsvevs
 | 
				
			||||||
 | 
					esphome/components/xiaomi_rtcgq02lm/* @jesserockz
 | 
				
			||||||
esphome/components/xpt2046/* @numo68
 | 
					esphome/components/xpt2046/* @numo68
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,13 +6,13 @@
 | 
				
			|||||||
ARG BASEIMGTYPE=docker
 | 
					ARG BASEIMGTYPE=docker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# https://github.com/hassio-addons/addon-debian-base/releases
 | 
					# https://github.com/hassio-addons/addon-debian-base/releases
 | 
				
			||||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.2.3 AS base-hassio-amd64
 | 
					FROM ghcr.io/hassio-addons/debian-base/amd64:5.3.0 AS base-hassio-amd64
 | 
				
			||||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.2.3 AS base-hassio-arm64
 | 
					FROM ghcr.io/hassio-addons/debian-base/aarch64:5.3.0 AS base-hassio-arm64
 | 
				
			||||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.2.3 AS base-hassio-armv7
 | 
					FROM ghcr.io/hassio-addons/debian-base/armv7:5.3.0 AS base-hassio-armv7
 | 
				
			||||||
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
 | 
					# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
 | 
				
			||||||
FROM debian:bullseye-20220125-slim AS base-docker-amd64
 | 
					FROM debian:bullseye-20220328-slim AS base-docker-amd64
 | 
				
			||||||
FROM debian:bullseye-20220125-slim AS base-docker-arm64
 | 
					FROM debian:bullseye-20220328-slim AS base-docker-arm64
 | 
				
			||||||
FROM debian:bullseye-20220125-slim AS base-docker-armv7
 | 
					FROM debian:bullseye-20220328-slim AS base-docker-armv7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Use TARGETARCH/TARGETVARIANT defined by docker
 | 
					# Use TARGETARCH/TARGETVARIANT defined by docker
 | 
				
			||||||
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
 | 
					# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
 | 
				
			||||||
@@ -23,13 +23,14 @@ RUN \
 | 
				
			|||||||
    # Use pinned versions so that we get updates with build caching
 | 
					    # Use pinned versions so that we get updates with build caching
 | 
				
			||||||
    && apt-get install -y --no-install-recommends \
 | 
					    && apt-get install -y --no-install-recommends \
 | 
				
			||||||
        python3=3.9.2-3 \
 | 
					        python3=3.9.2-3 \
 | 
				
			||||||
        python3-pip=20.3.4-4 \
 | 
					        python3-pip=20.3.4-4+deb11u1 \
 | 
				
			||||||
        python3-setuptools=52.0.0-4 \
 | 
					        python3-setuptools=52.0.0-4 \
 | 
				
			||||||
        python3-pil=8.1.2+dfsg-0.3+deb11u1 \
 | 
					        python3-pil=8.1.2+dfsg-0.3+deb11u1 \
 | 
				
			||||||
        python3-cryptography=3.3.2-1 \
 | 
					        python3-cryptography=3.3.2-1 \
 | 
				
			||||||
        iputils-ping=3:20210202-1 \
 | 
					        iputils-ping=3:20210202-1 \
 | 
				
			||||||
        git=1:2.30.2-1 \
 | 
					        git=1:2.30.2-1 \
 | 
				
			||||||
        curl=7.74.0-1.3+deb11u1 \
 | 
					        curl=7.74.0-1.3+deb11u1 \
 | 
				
			||||||
 | 
					        openssh-client=1:8.4p1-5 \
 | 
				
			||||||
    && rm -rf \
 | 
					    && rm -rf \
 | 
				
			||||||
        /tmp/* \
 | 
					        /tmp/* \
 | 
				
			||||||
        /var/{cache,log}/* \
 | 
					        /var/{cache,log}/* \
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ import argparse
 | 
				
			|||||||
import functools
 | 
					import functools
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,15 +10,18 @@ from esphome import const, writer, yaml_util
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.config import iter_components, read_config, strip_default_ids
 | 
					from esphome.config import iter_components, read_config, strip_default_ids
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    ALLOWED_NAME_CHARS,
 | 
				
			||||||
    CONF_BAUD_RATE,
 | 
					    CONF_BAUD_RATE,
 | 
				
			||||||
    CONF_BROKER,
 | 
					    CONF_BROKER,
 | 
				
			||||||
    CONF_DEASSERT_RTS_DTR,
 | 
					    CONF_DEASSERT_RTS_DTR,
 | 
				
			||||||
    CONF_LOGGER,
 | 
					    CONF_LOGGER,
 | 
				
			||||||
 | 
					    CONF_NAME,
 | 
				
			||||||
    CONF_OTA,
 | 
					    CONF_OTA,
 | 
				
			||||||
    CONF_PASSWORD,
 | 
					    CONF_PASSWORD,
 | 
				
			||||||
    CONF_PORT,
 | 
					    CONF_PORT,
 | 
				
			||||||
    CONF_ESPHOME,
 | 
					    CONF_ESPHOME,
 | 
				
			||||||
    CONF_PLATFORMIO_OPTIONS,
 | 
					    CONF_PLATFORMIO_OPTIONS,
 | 
				
			||||||
 | 
					    CONF_SUBSTITUTIONS,
 | 
				
			||||||
    SECRETS_FILES,
 | 
					    SECRETS_FILES,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.core import CORE, EsphomeError, coroutine
 | 
					from esphome.core import CORE, EsphomeError, coroutine
 | 
				
			||||||
@@ -481,6 +485,98 @@ def command_idedata(args, config):
 | 
				
			|||||||
    return 0
 | 
					    return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def command_rename(args, config):
 | 
				
			||||||
 | 
					    for c in args.name:
 | 
				
			||||||
 | 
					        if c not in ALLOWED_NAME_CHARS:
 | 
				
			||||||
 | 
					            print(
 | 
				
			||||||
 | 
					                color(
 | 
				
			||||||
 | 
					                    Fore.BOLD_RED,
 | 
				
			||||||
 | 
					                    f"'{c}' is an invalid character for names. Valid characters are: "
 | 
				
			||||||
 | 
					                    f"{ALLOWED_NAME_CHARS} (lowercase, no spaces)",
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return 1
 | 
				
			||||||
 | 
					    # Load existing yaml file
 | 
				
			||||||
 | 
					    with open(CORE.config_path, mode="r+", encoding="utf-8") as raw_file:
 | 
				
			||||||
 | 
					        raw_contents = raw_file.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    yaml = yaml_util.load_yaml(CORE.config_path)
 | 
				
			||||||
 | 
					    if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
 | 
				
			||||||
 | 
					        print(
 | 
				
			||||||
 | 
					            color(Fore.BOLD_RED, "Complex YAML files cannot be automatically renamed.")
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return 1
 | 
				
			||||||
 | 
					    old_name = yaml[CONF_ESPHOME][CONF_NAME]
 | 
				
			||||||
 | 
					    match = re.match(r"^\$\{?([a-zA-Z0-9_]+)\}?$", old_name)
 | 
				
			||||||
 | 
					    if match is None:
 | 
				
			||||||
 | 
					        new_raw = re.sub(
 | 
				
			||||||
 | 
					            rf"name:\s+[\"']?{old_name}[\"']?",
 | 
				
			||||||
 | 
					            f'name: "{args.name}"',
 | 
				
			||||||
 | 
					            raw_contents,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        old_name = yaml[CONF_SUBSTITUTIONS][match.group(1)]
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            len(
 | 
				
			||||||
 | 
					                re.findall(
 | 
				
			||||||
 | 
					                    rf"^\s+{match.group(1)}:\s+[\"']?{old_name}[\"']?",
 | 
				
			||||||
 | 
					                    raw_contents,
 | 
				
			||||||
 | 
					                    flags=re.MULTILINE,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            > 1
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					            print(color(Fore.BOLD_RED, "Too many matches in YAML to safely rename"))
 | 
				
			||||||
 | 
					            return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        new_raw = re.sub(
 | 
				
			||||||
 | 
					            rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?",
 | 
				
			||||||
 | 
					            f'\\1: "{args.name}"',
 | 
				
			||||||
 | 
					            raw_contents,
 | 
				
			||||||
 | 
					            flags=re.MULTILINE,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    new_path = os.path.join(CORE.config_dir, args.name + ".yaml")
 | 
				
			||||||
 | 
					    print(
 | 
				
			||||||
 | 
					        f"Updating {color(Fore.CYAN, CORE.config_path)} to {color(Fore.CYAN, new_path)}"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    print()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open(new_path, mode="w", encoding="utf-8") as new_file:
 | 
				
			||||||
 | 
					        new_file.write(new_raw)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rc = run_external_process("esphome", "config", new_path)
 | 
				
			||||||
 | 
					    if rc != 0:
 | 
				
			||||||
 | 
					        print(color(Fore.BOLD_RED, "Rename failed. Reverting changes."))
 | 
				
			||||||
 | 
					        os.remove(new_path)
 | 
				
			||||||
 | 
					        return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cli_args = [
 | 
				
			||||||
 | 
					        "run",
 | 
				
			||||||
 | 
					        new_path,
 | 
				
			||||||
 | 
					        "--no-logs",
 | 
				
			||||||
 | 
					        "--device",
 | 
				
			||||||
 | 
					        CORE.address,
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if args.dashboard:
 | 
				
			||||||
 | 
					        cli_args.insert(0, "--dashboard")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        rc = run_external_process("esphome", *cli_args)
 | 
				
			||||||
 | 
					    except KeyboardInterrupt:
 | 
				
			||||||
 | 
					        rc = 1
 | 
				
			||||||
 | 
					    if rc != 0:
 | 
				
			||||||
 | 
					        os.remove(new_path)
 | 
				
			||||||
 | 
					        return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    os.remove(CORE.config_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print(color(Fore.BOLD_GREEN, "SUCCESS"))
 | 
				
			||||||
 | 
					    print()
 | 
				
			||||||
 | 
					    return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PRE_CONFIG_ACTIONS = {
 | 
					PRE_CONFIG_ACTIONS = {
 | 
				
			||||||
    "wizard": command_wizard,
 | 
					    "wizard": command_wizard,
 | 
				
			||||||
    "version": command_version,
 | 
					    "version": command_version,
 | 
				
			||||||
@@ -499,6 +595,7 @@ POST_CONFIG_ACTIONS = {
 | 
				
			|||||||
    "mqtt-fingerprint": command_mqtt_fingerprint,
 | 
					    "mqtt-fingerprint": command_mqtt_fingerprint,
 | 
				
			||||||
    "clean": command_clean,
 | 
					    "clean": command_clean,
 | 
				
			||||||
    "idedata": command_idedata,
 | 
					    "idedata": command_idedata,
 | 
				
			||||||
 | 
					    "rename": command_rename,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -681,6 +778,15 @@ def parse_args(argv):
 | 
				
			|||||||
        "configuration", help="Your YAML configuration file(s).", nargs=1
 | 
					        "configuration", help="Your YAML configuration file(s).", nargs=1
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parser_rename = subparsers.add_parser(
 | 
				
			||||||
 | 
					        "rename",
 | 
				
			||||||
 | 
					        help="Rename a device in YAML, compile the binary and upload it.",
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    parser_rename.add_argument(
 | 
				
			||||||
 | 
					        "configuration", help="Your YAML configuration file.", nargs=1
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    parser_rename.add_argument("name", help="The new name for the device.", type=str)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Keep backward compatibility with the old command line format of
 | 
					    # Keep backward compatibility with the old command line format of
 | 
				
			||||||
    # esphome <config> <command>.
 | 
					    # esphome <config> <command>.
 | 
				
			||||||
    #
 | 
					    #
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -262,21 +262,16 @@ async def repeat_action_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
    return var
 | 
					    return var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def validate_wait_until(value):
 | 
					_validate_wait_until = cv.maybe_simple_value(
 | 
				
			||||||
    schema = cv.Schema(
 | 
					    {
 | 
				
			||||||
        {
 | 
					        cv.Required(CONF_CONDITION): validate_potentially_and_condition,
 | 
				
			||||||
            cv.Required(CONF_CONDITION): validate_potentially_and_condition,
 | 
					        cv.Optional(CONF_TIMEOUT): cv.templatable(cv.positive_time_period_milliseconds),
 | 
				
			||||||
            cv.Optional(CONF_TIMEOUT): cv.templatable(
 | 
					    },
 | 
				
			||||||
                cv.positive_time_period_milliseconds
 | 
					    key=CONF_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)
 | 
				
			||||||
async def wait_until_action_to_code(config, action_id, template_arg, args):
 | 
					async def wait_until_action_to_code(config, action_id, template_arg, args):
 | 
				
			||||||
    conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
 | 
					    conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
 | 
				
			||||||
    var = cg.new_Pvariable(action_id, template_arg, conditions)
 | 
					    var = cg.new_Pvariable(action_id, template_arg, conditions)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -64,6 +64,7 @@ from esphome.cpp_types import (  # noqa
 | 
				
			|||||||
    uint64,
 | 
					    uint64,
 | 
				
			||||||
    int32,
 | 
					    int32,
 | 
				
			||||||
    int64,
 | 
					    int64,
 | 
				
			||||||
 | 
					    size_t,
 | 
				
			||||||
    const_char_ptr,
 | 
					    const_char_ptr,
 | 
				
			||||||
    NAN,
 | 
					    NAN,
 | 
				
			||||||
    esphome_ns,
 | 
					    esphome_ns,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,8 +51,8 @@ void ADCSensor::setup() {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // adc_gpio_init doesn't exist on ESP32-C3 or ESP32-H2
 | 
					  // adc_gpio_init doesn't exist on ESP32-S2, ESP32-C3 or ESP32-H2
 | 
				
			||||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2)
 | 
					#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2) && !defined(USE_ESP32_VARIANT_ESP32S2)
 | 
				
			||||||
  adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_);
 | 
					  adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#endif  // USE_ESP32
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -94,6 +94,29 @@ async def to_code(config):
 | 
				
			|||||||
                data[pos] = pix[2]
 | 
					                data[pos] = pix[2]
 | 
				
			||||||
                pos += 1
 | 
					                pos += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    elif config[CONF_TYPE] == "RGB565":
 | 
				
			||||||
 | 
					        data = [0 for _ in range(height * width * 2 * frames)]
 | 
				
			||||||
 | 
					        pos = 0
 | 
				
			||||||
 | 
					        for frameIndex in range(frames):
 | 
				
			||||||
 | 
					            image.seek(frameIndex)
 | 
				
			||||||
 | 
					            frame = image.convert("RGB")
 | 
				
			||||||
 | 
					            if CONF_RESIZE in config:
 | 
				
			||||||
 | 
					                frame = frame.resize([width, height])
 | 
				
			||||||
 | 
					            pixels = list(frame.getdata())
 | 
				
			||||||
 | 
					            if len(pixels) != height * width:
 | 
				
			||||||
 | 
					                raise core.EsphomeError(
 | 
				
			||||||
 | 
					                    f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            for pix in pixels:
 | 
				
			||||||
 | 
					                R = pix[0] >> 3
 | 
				
			||||||
 | 
					                G = pix[1] >> 2
 | 
				
			||||||
 | 
					                B = pix[2] >> 3
 | 
				
			||||||
 | 
					                rgb = (R << 11) | (G << 5) | B
 | 
				
			||||||
 | 
					                data[pos] = rgb >> 8
 | 
				
			||||||
 | 
					                pos += 1
 | 
				
			||||||
 | 
					                data[pos] = rgb & 255
 | 
				
			||||||
 | 
					                pos += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    elif config[CONF_TYPE] == "BINARY":
 | 
					    elif config[CONF_TYPE] == "BINARY":
 | 
				
			||||||
        width8 = ((width + 7) // 8) * 8
 | 
					        width8 = ((width + 7) // 8) * 8
 | 
				
			||||||
        data = [0 for _ in range((height * width8 // 8) * frames)]
 | 
					        data = [0 for _ in range((height * width8 // 8) * frames)]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,7 @@ static const char *const TAG = "api.connection";
 | 
				
			|||||||
static const int ESP32_CAMERA_STOP_STREAM = 5000;
 | 
					static const int ESP32_CAMERA_STOP_STREAM = 5000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
 | 
					APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
 | 
				
			||||||
    : parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
 | 
					    : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
 | 
				
			||||||
  this->proto_write_buffer_.reserve(64);
 | 
					  this->proto_write_buffer_.reserve(64);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if defined(USE_API_PLAINTEXT)
 | 
					#if defined(USE_API_PLAINTEXT)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -255,7 +255,7 @@ void APIServer::on_number_update(number::Number *obj, float state) {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_SELECT
 | 
					#ifdef USE_SELECT
 | 
				
			||||||
void APIServer::on_select_update(select::Select *obj, const std::string &state) {
 | 
					void APIServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
 | 
				
			||||||
  if (obj->is_internal())
 | 
					  if (obj->is_internal())
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  for (auto &c : this->clients_)
 | 
					  for (auto &c : this->clients_)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,6 @@
 | 
				
			|||||||
#include "esphome/components/socket/socket.h"
 | 
					#include "esphome/components/socket/socket.h"
 | 
				
			||||||
#include "api_pb2.h"
 | 
					#include "api_pb2.h"
 | 
				
			||||||
#include "api_pb2_service.h"
 | 
					#include "api_pb2_service.h"
 | 
				
			||||||
#include "util.h"
 | 
					 | 
				
			||||||
#include "list_entities.h"
 | 
					#include "list_entities.h"
 | 
				
			||||||
#include "subscribe_state.h"
 | 
					#include "subscribe_state.h"
 | 
				
			||||||
#include "user_services.h"
 | 
					#include "user_services.h"
 | 
				
			||||||
@@ -65,7 +64,7 @@ class APIServer : public Component, public Controller {
 | 
				
			|||||||
  void on_number_update(number::Number *obj, float state) override;
 | 
					  void on_number_update(number::Number *obj, float state) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_SELECT
 | 
					#ifdef USE_SELECT
 | 
				
			||||||
  void on_select_update(select::Select *obj, const std::string &state) override;
 | 
					  void on_select_update(select::Select *obj, const std::string &state, size_t index) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_LOCK
 | 
					#ifdef USE_LOCK
 | 
				
			||||||
  void on_lock_update(lock::Lock *obj) override;
 | 
					  void on_lock_update(lock::Lock *obj) override;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,8 +40,7 @@ bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->s
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
 | 
					bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
 | 
				
			||||||
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)
 | 
					ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {}
 | 
				
			||||||
    : ComponentIterator(server), client_(client) {}
 | 
					 | 
				
			||||||
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
 | 
					bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
 | 
				
			||||||
  auto resp = service->encode_list_service_response();
 | 
					  auto resp = service->encode_list_service_response();
 | 
				
			||||||
  return this->client_->send_list_entities_services_response(resp);
 | 
					  return this->client_->send_list_entities_services_response(resp);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/core/component_iterator.h"
 | 
				
			||||||
#include "esphome/core/defines.h"
 | 
					#include "esphome/core/defines.h"
 | 
				
			||||||
#include "util.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace api {
 | 
					namespace api {
 | 
				
			||||||
@@ -11,7 +11,7 @@ class APIConnection;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class ListEntitiesIterator : public ComponentIterator {
 | 
					class ListEntitiesIterator : public ComponentIterator {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  ListEntitiesIterator(APIServer *server, APIConnection *client);
 | 
					  ListEntitiesIterator(APIConnection *client);
 | 
				
			||||||
#ifdef USE_BINARY_SENSOR
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
  bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
 | 
					  bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -60,5 +60,3 @@ class ListEntitiesIterator : public ComponentIterator {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}  // namespace api
 | 
					}  // namespace api
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "api_server.h"
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,4 @@
 | 
				
			|||||||
#include "proto.h"
 | 
					#include "proto.h"
 | 
				
			||||||
#include "util.h"
 | 
					 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -195,6 +195,20 @@ class ProtoWriteBuffer {
 | 
				
			|||||||
    this->write((value >> 16) & 0xFF);
 | 
					    this->write((value >> 16) & 0xFF);
 | 
				
			||||||
    this->write((value >> 24) & 0xFF);
 | 
					    this->write((value >> 24) & 0xFF);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  void encode_fixed64(uint32_t field_id, uint64_t value, bool force = false) {
 | 
				
			||||||
 | 
					    if (value == 0 && !force)
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this->encode_field_raw(field_id, 5);
 | 
				
			||||||
 | 
					    this->write((value >> 0) & 0xFF);
 | 
				
			||||||
 | 
					    this->write((value >> 8) & 0xFF);
 | 
				
			||||||
 | 
					    this->write((value >> 16) & 0xFF);
 | 
				
			||||||
 | 
					    this->write((value >> 24) & 0xFF);
 | 
				
			||||||
 | 
					    this->write((value >> 32) & 0xFF);
 | 
				
			||||||
 | 
					    this->write((value >> 40) & 0xFF);
 | 
				
			||||||
 | 
					    this->write((value >> 48) & 0xFF);
 | 
				
			||||||
 | 
					    this->write((value >> 56) & 0xFF);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) {
 | 
					  template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) {
 | 
				
			||||||
    this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
 | 
					    this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -229,6 +243,15 @@ class ProtoWriteBuffer {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    this->encode_uint32(field_id, uvalue, force);
 | 
					    this->encode_uint32(field_id, uvalue, force);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
 | 
				
			||||||
 | 
					    uint64_t uvalue;
 | 
				
			||||||
 | 
					    if (value < 0) {
 | 
				
			||||||
 | 
					      uvalue = ~(value << 1);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      uvalue = value << 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this->encode_uint64(field_id, uvalue, force);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
 | 
					  template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
 | 
				
			||||||
    this->encode_field_raw(field_id, 2);
 | 
					    this->encode_field_raw(field_id, 2);
 | 
				
			||||||
    size_t begin = this->buffer_->size();
 | 
					    size_t begin = this->buffer_->size();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,8 +50,7 @@ bool InitialStateIterator::on_select(select::Select *select) {
 | 
				
			|||||||
#ifdef USE_LOCK
 | 
					#ifdef USE_LOCK
 | 
				
			||||||
bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); }
 | 
					bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
 | 
					InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
 | 
				
			||||||
    : ComponentIterator(server), client_(client) {}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace api
 | 
					}  // namespace api
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/core/component_iterator.h"
 | 
				
			||||||
#include "esphome/core/controller.h"
 | 
					#include "esphome/core/controller.h"
 | 
				
			||||||
#include "esphome/core/defines.h"
 | 
					#include "esphome/core/defines.h"
 | 
				
			||||||
#include "util.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace api {
 | 
					namespace api {
 | 
				
			||||||
@@ -12,7 +12,7 @@ class APIConnection;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class InitialStateIterator : public ComponentIterator {
 | 
					class InitialStateIterator : public ComponentIterator {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  InitialStateIterator(APIServer *server, APIConnection *client);
 | 
					  InitialStateIterator(APIConnection *client);
 | 
				
			||||||
#ifdef USE_BINARY_SENSOR
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
  bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
 | 
					  bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -55,5 +55,3 @@ class InitialStateIterator : public ComponentIterator {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}  // namespace api
 | 
					}  // namespace api
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "api_server.h"
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,7 +38,7 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
				
			|||||||
  const auto &data = service_data.data;
 | 
					  const auto &data = service_data.data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const uint8_t protocol_version = data[0] >> 4;
 | 
					  const uint8_t protocol_version = data[0] >> 4;
 | 
				
			||||||
  if (protocol_version != 1) {
 | 
					  if (protocol_version != 1 && protocol_version != 2) {
 | 
				
			||||||
    ESP_LOGE(TAG, "Unsupported protocol version: %u", protocol_version);
 | 
					    ESP_LOGE(TAG, "Unsupported protocol version: %u", protocol_version);
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -57,9 +57,15 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
				
			|||||||
  uint16_t battery_millivolt = data[2] << 8 | data[3];
 | 
					  uint16_t battery_millivolt = data[2] << 8 | data[3];
 | 
				
			||||||
  float battery_voltage = battery_millivolt / 1000.0f;
 | 
					  float battery_voltage = battery_millivolt / 1000.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Temperature in 1000 * Celsius.
 | 
					  // Temperature in 1000 * Celsius (protocol v1) or 100 * Celsius (protocol v2).
 | 
				
			||||||
  uint16_t temp_millicelcius = data[4] << 8 | data[5];
 | 
					  float temp_celsius;
 | 
				
			||||||
  float temp_celcius = temp_millicelcius / 1000.0f;
 | 
					  if (protocol_version == 1) {
 | 
				
			||||||
 | 
					    uint16_t temp_millicelsius = data[4] << 8 | data[5];
 | 
				
			||||||
 | 
					    temp_celsius = temp_millicelsius / 1000.0f;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    int16_t temp_centicelsius = data[4] << 8 | data[5];
 | 
				
			||||||
 | 
					    temp_celsius = temp_centicelsius / 100.0f;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Relative air humidity in the range [0, 2^16).
 | 
					  // Relative air humidity in the range [0, 2^16).
 | 
				
			||||||
  uint16_t humidity = data[6] << 8 | data[7];
 | 
					  uint16_t humidity = data[6] << 8 | data[7];
 | 
				
			||||||
@@ -76,7 +82,7 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
				
			|||||||
    battery_voltage_->publish_state(battery_voltage);
 | 
					    battery_voltage_->publish_state(battery_voltage);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (temperature_ != nullptr) {
 | 
					  if (temperature_ != nullptr) {
 | 
				
			||||||
    temperature_->publish_state(temp_celcius);
 | 
					    temperature_->publish_state(temp_celsius);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (humidity_ != nullptr) {
 | 
					  if (humidity_ != nullptr) {
 | 
				
			||||||
    humidity_->publish_state(humidity_percent);
 | 
					    humidity_->publish_state(humidity_percent);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								esphome/components/bedjet/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/bedjet/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					CODEOWNERS = ["@jhansche"]
 | 
				
			||||||
							
								
								
									
										642
									
								
								esphome/components/bedjet/bedjet.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										642
									
								
								esphome/components/bedjet/bedjet.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,642 @@
 | 
				
			|||||||
 | 
					#include "bedjet.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace bedjet {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace esphome::climate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Converts a BedJet temp step into degrees Celsius.
 | 
				
			||||||
 | 
					float bedjet_temp_to_c(const uint8_t temp) {
 | 
				
			||||||
 | 
					  // BedJet temp is "C*2"; to get C, divide by 2.
 | 
				
			||||||
 | 
					  return temp / 2.0f;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Converts a BedJet fan step to a speed percentage, in the range of 5% to 100%.
 | 
				
			||||||
 | 
					uint8_t bedjet_fan_step_to_speed(const uint8_t fan) {
 | 
				
			||||||
 | 
					  //  0 =  5%
 | 
				
			||||||
 | 
					  // 19 = 100%
 | 
				
			||||||
 | 
					  return 5 * fan + 5;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const std::string *bedjet_fan_step_to_fan_mode(const uint8_t fan_step) {
 | 
				
			||||||
 | 
					  if (fan_step >= 0 && fan_step <= 19)
 | 
				
			||||||
 | 
					    return &BEDJET_FAN_STEP_NAME_STRINGS[fan_step];
 | 
				
			||||||
 | 
					  return nullptr;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static uint8_t bedjet_fan_speed_to_step(const std::string &fan_step_percent) {
 | 
				
			||||||
 | 
					  for (int i = 0; i < sizeof(BEDJET_FAN_STEP_NAME_STRINGS); i++) {
 | 
				
			||||||
 | 
					    if (fan_step_percent == BEDJET_FAN_STEP_NAME_STRINGS[i]) {
 | 
				
			||||||
 | 
					      return i;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Bedjet::upgrade_firmware() {
 | 
				
			||||||
 | 
					  auto *pkt = this->codec_->get_button_request(MAGIC_UPDATE);
 | 
				
			||||||
 | 
					  auto status = this->write_bedjet_packet_(pkt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (status) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Bedjet::dump_config() {
 | 
				
			||||||
 | 
					  LOG_CLIMATE("", "BedJet Climate", this);
 | 
				
			||||||
 | 
					  auto traits = this->get_traits();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Supported modes:");
 | 
				
			||||||
 | 
					  for (auto mode : traits.get_supported_modes()) {
 | 
				
			||||||
 | 
					    ESP_LOGCONFIG(TAG, "   - %s", LOG_STR_ARG(climate_mode_to_string(mode)));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Supported fan modes:");
 | 
				
			||||||
 | 
					  for (const auto &mode : traits.get_supported_fan_modes()) {
 | 
				
			||||||
 | 
					    ESP_LOGCONFIG(TAG, "   - %s", LOG_STR_ARG(climate_fan_mode_to_string(mode)));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  for (const auto &mode : traits.get_supported_custom_fan_modes()) {
 | 
				
			||||||
 | 
					    ESP_LOGCONFIG(TAG, "   - %s (c)", mode.c_str());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "  Supported presets:");
 | 
				
			||||||
 | 
					  for (auto preset : traits.get_supported_presets()) {
 | 
				
			||||||
 | 
					    ESP_LOGCONFIG(TAG, "   - %s", LOG_STR_ARG(climate_preset_to_string(preset)));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  for (const auto &preset : traits.get_supported_custom_presets()) {
 | 
				
			||||||
 | 
					    ESP_LOGCONFIG(TAG, "   - %s (c)", preset.c_str());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Bedjet::setup() {
 | 
				
			||||||
 | 
					  this->codec_ = make_unique<BedjetCodec>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // restore set points
 | 
				
			||||||
 | 
					  auto restore = this->restore_state_();
 | 
				
			||||||
 | 
					  if (restore.has_value()) {
 | 
				
			||||||
 | 
					    ESP_LOGI(TAG, "Restored previous saved state.");
 | 
				
			||||||
 | 
					    restore->apply(this);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    // Initial status is unknown until we connect
 | 
				
			||||||
 | 
					    this->reset_state_();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_TIME
 | 
				
			||||||
 | 
					  this->setup_time_();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Resets states to defaults. */
 | 
				
			||||||
 | 
					void Bedjet::reset_state_() {
 | 
				
			||||||
 | 
					  this->mode = climate::CLIMATE_MODE_OFF;
 | 
				
			||||||
 | 
					  this->action = climate::CLIMATE_ACTION_IDLE;
 | 
				
			||||||
 | 
					  this->target_temperature = NAN;
 | 
				
			||||||
 | 
					  this->current_temperature = NAN;
 | 
				
			||||||
 | 
					  this->preset.reset();
 | 
				
			||||||
 | 
					  this->custom_preset.reset();
 | 
				
			||||||
 | 
					  this->publish_state();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Bedjet::loop() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Bedjet::control(const ClimateCall &call) {
 | 
				
			||||||
 | 
					  ESP_LOGD(TAG, "Received Bedjet::control");
 | 
				
			||||||
 | 
					  if (this->node_state != espbt::ClientState::ESTABLISHED) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "Not connected, cannot handle control call yet.");
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (call.get_mode().has_value()) {
 | 
				
			||||||
 | 
					    ClimateMode mode = *call.get_mode();
 | 
				
			||||||
 | 
					    BedjetPacket *pkt;
 | 
				
			||||||
 | 
					    switch (mode) {
 | 
				
			||||||
 | 
					      case climate::CLIMATE_MODE_OFF:
 | 
				
			||||||
 | 
					        pkt = this->codec_->get_button_request(BTN_OFF);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case climate::CLIMATE_MODE_HEAT:
 | 
				
			||||||
 | 
					        pkt = this->codec_->get_button_request(BTN_EXTHT);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case climate::CLIMATE_MODE_FAN_ONLY:
 | 
				
			||||||
 | 
					        pkt = this->codec_->get_button_request(BTN_COOL);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case climate::CLIMATE_MODE_DRY:
 | 
				
			||||||
 | 
					        pkt = this->codec_->get_button_request(BTN_DRY);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "Unsupported mode: %d", mode);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto status = this->write_bedjet_packet_(pkt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (status) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this->force_refresh_ = true;
 | 
				
			||||||
 | 
					      this->mode = mode;
 | 
				
			||||||
 | 
					      // We're using (custom) preset for Turbo & M1-3 presets, so changing climate mode will clear those
 | 
				
			||||||
 | 
					      this->custom_preset.reset();
 | 
				
			||||||
 | 
					      this->preset.reset();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (call.get_target_temperature().has_value()) {
 | 
				
			||||||
 | 
					    auto target_temp = *call.get_target_temperature();
 | 
				
			||||||
 | 
					    auto *pkt = this->codec_->get_set_target_temp_request(target_temp);
 | 
				
			||||||
 | 
					    auto status = this->write_bedjet_packet_(pkt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (status) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this->target_temperature = target_temp;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (call.get_preset().has_value()) {
 | 
				
			||||||
 | 
					    ClimatePreset preset = *call.get_preset();
 | 
				
			||||||
 | 
					    BedjetPacket *pkt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (preset == climate::CLIMATE_PRESET_BOOST) {
 | 
				
			||||||
 | 
					      pkt = this->codec_->get_button_request(BTN_TURBO);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Unsupported preset: %d", preset);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto status = this->write_bedjet_packet_(pkt);
 | 
				
			||||||
 | 
					    if (status) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      // We use BOOST preset for TURBO mode, which is a short-lived/high-heat mode.
 | 
				
			||||||
 | 
					      this->mode = climate::CLIMATE_MODE_HEAT;
 | 
				
			||||||
 | 
					      this->preset = preset;
 | 
				
			||||||
 | 
					      this->custom_preset.reset();
 | 
				
			||||||
 | 
					      this->force_refresh_ = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else if (call.get_custom_preset().has_value()) {
 | 
				
			||||||
 | 
					    std::string preset = *call.get_custom_preset();
 | 
				
			||||||
 | 
					    BedjetPacket *pkt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (preset == "M1") {
 | 
				
			||||||
 | 
					      pkt = this->codec_->get_button_request(BTN_M1);
 | 
				
			||||||
 | 
					    } else if (preset == "M2") {
 | 
				
			||||||
 | 
					      pkt = this->codec_->get_button_request(BTN_M2);
 | 
				
			||||||
 | 
					    } else if (preset == "M3") {
 | 
				
			||||||
 | 
					      pkt = this->codec_->get_button_request(BTN_M3);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Unsupported preset: %s", preset.c_str());
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto status = this->write_bedjet_packet_(pkt);
 | 
				
			||||||
 | 
					    if (status) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this->force_refresh_ = true;
 | 
				
			||||||
 | 
					      this->custom_preset = preset;
 | 
				
			||||||
 | 
					      this->preset.reset();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (call.get_fan_mode().has_value()) {
 | 
				
			||||||
 | 
					    // Climate fan mode only supports low/med/high, but the BedJet supports 5-100% increments.
 | 
				
			||||||
 | 
					    // We can still support a ClimateCall that requests low/med/high, and just translate it to a step increment here.
 | 
				
			||||||
 | 
					    auto fan_mode = *call.get_fan_mode();
 | 
				
			||||||
 | 
					    BedjetPacket *pkt;
 | 
				
			||||||
 | 
					    if (fan_mode == climate::CLIMATE_FAN_LOW) {
 | 
				
			||||||
 | 
					      pkt = this->codec_->get_set_fan_speed_request(3 /* = 20% */);
 | 
				
			||||||
 | 
					    } else if (fan_mode == climate::CLIMATE_FAN_MEDIUM) {
 | 
				
			||||||
 | 
					      pkt = this->codec_->get_set_fan_speed_request(9 /* = 50% */);
 | 
				
			||||||
 | 
					    } else if (fan_mode == climate::CLIMATE_FAN_HIGH) {
 | 
				
			||||||
 | 
					      pkt = this->codec_->get_set_fan_speed_request(14 /* = 75% */);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] Unsupported fan mode: %s", this->get_name().c_str(),
 | 
				
			||||||
 | 
					               LOG_STR_ARG(climate_fan_mode_to_string(fan_mode)));
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto status = this->write_bedjet_packet_(pkt);
 | 
				
			||||||
 | 
					    if (status) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this->force_refresh_ = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else if (call.get_custom_fan_mode().has_value()) {
 | 
				
			||||||
 | 
					    auto fan_mode = *call.get_custom_fan_mode();
 | 
				
			||||||
 | 
					    auto fan_step = bedjet_fan_speed_to_step(fan_mode);
 | 
				
			||||||
 | 
					    if (fan_step >= 0 && fan_step <= 19) {
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "[%s] Converted fan mode %s to bedjet fan step %d", this->get_name().c_str(), fan_mode.c_str(),
 | 
				
			||||||
 | 
					               fan_step);
 | 
				
			||||||
 | 
					      // The index should represent the fan_step index.
 | 
				
			||||||
 | 
					      BedjetPacket *pkt = this->codec_->get_set_fan_speed_request(fan_step);
 | 
				
			||||||
 | 
					      auto status = this->write_bedjet_packet_(pkt);
 | 
				
			||||||
 | 
					      if (status) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        this->force_refresh_ = true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Bedjet::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
 | 
				
			||||||
 | 
					  switch (event) {
 | 
				
			||||||
 | 
					    case ESP_GATTC_DISCONNECT_EVT: {
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "Disconnected: reason=%d", param->disconnect.reason);
 | 
				
			||||||
 | 
					      this->status_set_warning();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
				
			||||||
 | 
					      auto *chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_COMMAND_UUID);
 | 
				
			||||||
 | 
					      if (chr == nullptr) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "[%s] No control service found at device, not a BedJet..?", this->get_name().c_str());
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this->char_handle_cmd_ = chr->handle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_STATUS_UUID);
 | 
				
			||||||
 | 
					      if (chr == nullptr) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "[%s] No status service found at device, not a BedJet..?", this->get_name().c_str());
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this->char_handle_status_ = chr->handle;
 | 
				
			||||||
 | 
					      // We also need to obtain the config descriptor for this handle.
 | 
				
			||||||
 | 
					      // Otherwise once we set node_state=Established, the parent will flush all handles/descriptors, and we won't be
 | 
				
			||||||
 | 
					      // able to look it up.
 | 
				
			||||||
 | 
					      auto *descr = this->parent_->get_config_descriptor(this->char_handle_status_);
 | 
				
			||||||
 | 
					      if (descr == nullptr) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "No config descriptor for status handle 0x%x. Will not be able to receive status notifications",
 | 
				
			||||||
 | 
					                 this->char_handle_status_);
 | 
				
			||||||
 | 
					      } else if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
 | 
				
			||||||
 | 
					                 descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "Config descriptor 0x%x (uuid %s) is not a client config char uuid", this->char_handle_status_,
 | 
				
			||||||
 | 
					                 descr->uuid.to_string().c_str());
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        this->config_descr_status_ = descr->handle;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      chr = this->parent_->get_characteristic(BEDJET_SERVICE_UUID, BEDJET_NAME_UUID);
 | 
				
			||||||
 | 
					      if (chr != nullptr) {
 | 
				
			||||||
 | 
					        this->char_handle_name_ = chr->handle;
 | 
				
			||||||
 | 
					        auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_name_,
 | 
				
			||||||
 | 
					                                              ESP_GATT_AUTH_REQ_NONE);
 | 
				
			||||||
 | 
					        if (status) {
 | 
				
			||||||
 | 
					          ESP_LOGI(TAG, "[%s] Unable to read name characteristic: %d", this->get_name().c_str(), status);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ESP_LOGD(TAG, "Services complete: obtained char handles.");
 | 
				
			||||||
 | 
					      this->node_state = espbt::ClientState::ESTABLISHED;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this->set_notify_(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_TIME
 | 
				
			||||||
 | 
					      if (this->time_id_.has_value()) {
 | 
				
			||||||
 | 
					        this->send_local_time_();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_WRITE_DESCR_EVT: {
 | 
				
			||||||
 | 
					      if (param->write.status != ESP_GATT_OK) {
 | 
				
			||||||
 | 
					        // ESP_GATT_INVALID_ATTR_LEN
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "Error writing descr at handle 0x%04d, status=%d", param->write.handle, param->write.status);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // [16:44:44][V][bedjet:279]: [JOENJET] Register for notify event success: h=0x002a s=0
 | 
				
			||||||
 | 
					      // This might be the enable-notify descriptor? (or disable-notify)
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "[%s] Write to handle 0x%04x status=%d", this->get_name().c_str(), param->write.handle,
 | 
				
			||||||
 | 
					               param->write.status);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_WRITE_CHAR_EVT: {
 | 
				
			||||||
 | 
					      if (param->write.status != ESP_GATT_OK) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "Error writing char at handle 0x%04d, status=%d", param->write.handle, param->write.status);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (param->write.handle == this->char_handle_cmd_) {
 | 
				
			||||||
 | 
					        if (this->force_refresh_) {
 | 
				
			||||||
 | 
					          // Command write was successful. Publish the pending state, hoping that notify will kick in.
 | 
				
			||||||
 | 
					          this->publish_state();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_READ_CHAR_EVT: {
 | 
				
			||||||
 | 
					      if (param->read.conn_id != this->parent_->conn_id)
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      if (param->read.status != ESP_GATT_OK) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (param->read.handle == this->char_handle_status_) {
 | 
				
			||||||
 | 
					        // This is the additional packet that doesn't fit in the notify packet.
 | 
				
			||||||
 | 
					        this->codec_->decode_extra(param->read.value, param->read.value_len);
 | 
				
			||||||
 | 
					      } else if (param->read.handle == this->char_handle_name_) {
 | 
				
			||||||
 | 
					        // The data should represent the name.
 | 
				
			||||||
 | 
					        if (param->read.status == ESP_GATT_OK && param->read.value_len > 0) {
 | 
				
			||||||
 | 
					          std::string bedjet_name(reinterpret_cast<char const *>(param->read.value), param->read.value_len);
 | 
				
			||||||
 | 
					          // this->set_name(bedjet_name);
 | 
				
			||||||
 | 
					          ESP_LOGV(TAG, "[%s] Got BedJet name: '%s'", this->get_name().c_str(), bedjet_name.c_str());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
				
			||||||
 | 
					      // This event means that ESP received the request to enable notifications on the client side. But we also have to
 | 
				
			||||||
 | 
					      // tell the server that we want it to send notifications. Normally BLEClient parent would handle this
 | 
				
			||||||
 | 
					      // automatically, but as soon as we set our status to Established, the parent is going to purge all the
 | 
				
			||||||
 | 
					      // service/char/descriptor handles, and then get_config_descriptor() won't work anymore. There's no way to disable
 | 
				
			||||||
 | 
					      // the BLEClient parent behavior, so our only option is to write the handle anyway, and hope a double-write
 | 
				
			||||||
 | 
					      // doesn't break anything.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (param->reg_for_notify.handle != this->char_handle_status_) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "[%s] Register for notify on unexpected handle 0x%04x, expecting 0x%04x",
 | 
				
			||||||
 | 
					                 this->get_name().c_str(), param->reg_for_notify.handle, this->char_handle_status_);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this->write_notify_config_descriptor_(true);
 | 
				
			||||||
 | 
					      this->last_notify_ = 0;
 | 
				
			||||||
 | 
					      this->force_refresh_ = true;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
 | 
				
			||||||
 | 
					      // This event is not handled by the parent BLEClient, so we need to do this either way.
 | 
				
			||||||
 | 
					      if (param->unreg_for_notify.handle != this->char_handle_status_) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "[%s] Unregister for notify on unexpected handle 0x%04x, expecting 0x%04x",
 | 
				
			||||||
 | 
					                 this->get_name().c_str(), param->unreg_for_notify.handle, this->char_handle_status_);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this->write_notify_config_descriptor_(false);
 | 
				
			||||||
 | 
					      this->last_notify_ = 0;
 | 
				
			||||||
 | 
					      // Now we wait until the next update() poll to re-register notify...
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_NOTIFY_EVT: {
 | 
				
			||||||
 | 
					      if (param->notify.handle != this->char_handle_status_) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "[%s] Unexpected notify handle, wanted %04X, got %04X", this->get_name().c_str(),
 | 
				
			||||||
 | 
					                 this->char_handle_status_, param->notify.handle);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // FIXME: notify events come in every ~200-300 ms, which is too fast to be helpful. So we
 | 
				
			||||||
 | 
					      //  throttle the updates to once every MIN_NOTIFY_THROTTLE (5 seconds).
 | 
				
			||||||
 | 
					      //  Another idea would be to keep notify off by default, and use update() as an opportunity to turn on
 | 
				
			||||||
 | 
					      //  notify to get enough data to update status, then turn off notify again.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      uint32_t now = millis();
 | 
				
			||||||
 | 
					      auto delta = now - this->last_notify_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this->last_notify_ == 0 || delta > MIN_NOTIFY_THROTTLE || this->force_refresh_) {
 | 
				
			||||||
 | 
					        bool needs_extra = this->codec_->decode_notify(param->notify.value, param->notify.value_len);
 | 
				
			||||||
 | 
					        this->last_notify_ = now;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (needs_extra) {
 | 
				
			||||||
 | 
					          // this means the packet was partial, so read the status characteristic to get the second part.
 | 
				
			||||||
 | 
					          auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id,
 | 
				
			||||||
 | 
					                                                this->char_handle_status_, ESP_GATT_AUTH_REQ_NONE);
 | 
				
			||||||
 | 
					          if (status) {
 | 
				
			||||||
 | 
					            ESP_LOGI(TAG, "[%s] Unable to read extended status packet", this->get_name().c_str());
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this->force_refresh_) {
 | 
				
			||||||
 | 
					          // If we requested an immediate update, do that now.
 | 
				
			||||||
 | 
					          this->update();
 | 
				
			||||||
 | 
					          this->force_refresh_ = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      ESP_LOGVV(TAG, "[%s] gattc unhandled event: enum=%d", this->get_name().c_str(), event);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Reimplementation of BLEClient.gattc_event_handler() for ESP_GATTC_REG_FOR_NOTIFY_EVT.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This is a copy of ble_client's automatic handling of `ESP_GATTC_REG_FOR_NOTIFY_EVT`, in order
 | 
				
			||||||
 | 
					 * to undo the same on unregister. It also allows us to maintain the config descriptor separately,
 | 
				
			||||||
 | 
					 * since the parent BLEClient is going to purge all descriptors once we set our connection status
 | 
				
			||||||
 | 
					 * to `Established`.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					uint8_t Bedjet::write_notify_config_descriptor_(bool enable) {
 | 
				
			||||||
 | 
					  auto handle = this->config_descr_status_;
 | 
				
			||||||
 | 
					  if (handle == 0) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", this->char_handle_status_);
 | 
				
			||||||
 | 
					    return -1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // NOTE: BLEClient uses `uint8_t*` of length 1, but BLE spec requires 16 bits.
 | 
				
			||||||
 | 
					  uint8_t notify_en[] = {0, 0};
 | 
				
			||||||
 | 
					  notify_en[0] = enable;
 | 
				
			||||||
 | 
					  auto status =
 | 
				
			||||||
 | 
					      esp_ble_gattc_write_char_descr(this->parent_->gattc_if, this->parent_->conn_id, handle, sizeof(notify_en),
 | 
				
			||||||
 | 
					                                     ¬ify_en[0], ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
				
			||||||
 | 
					  if (status) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
 | 
				
			||||||
 | 
					    return status;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  ESP_LOGD(TAG, "[%s] wrote notify=%s to status config 0x%04x", this->get_name().c_str(), enable ? "true" : "false",
 | 
				
			||||||
 | 
					           handle);
 | 
				
			||||||
 | 
					  return ESP_GATT_OK;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_TIME
 | 
				
			||||||
 | 
					/** Attempts to sync the local time (via `time_id`) to the BedJet device. */
 | 
				
			||||||
 | 
					void Bedjet::send_local_time_() {
 | 
				
			||||||
 | 
					  if (this->node_state != espbt::ClientState::ESTABLISHED) {
 | 
				
			||||||
 | 
					    ESP_LOGV(TAG, "[%s] Not connected, cannot send time.", this->get_name().c_str());
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  auto *time_id = *this->time_id_;
 | 
				
			||||||
 | 
					  time::ESPTime now = time_id->now();
 | 
				
			||||||
 | 
					  if (now.is_valid()) {
 | 
				
			||||||
 | 
					    uint8_t hour = now.hour;
 | 
				
			||||||
 | 
					    uint8_t minute = now.minute;
 | 
				
			||||||
 | 
					    BedjetPacket *pkt = this->codec_->get_set_time_request(hour, minute);
 | 
				
			||||||
 | 
					    auto status = this->write_bedjet_packet_(pkt);
 | 
				
			||||||
 | 
					    if (status) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Failed setting BedJet clock: %d", status);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ESP_LOGD(TAG, "[%s] BedJet clock set to: %d:%02d", this->get_name().c_str(), hour, minute);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Initializes time sync callbacks to support syncing current time to the BedJet. */
 | 
				
			||||||
 | 
					void Bedjet::setup_time_() {
 | 
				
			||||||
 | 
					  if (this->time_id_.has_value()) {
 | 
				
			||||||
 | 
					    this->send_local_time_();
 | 
				
			||||||
 | 
					    auto *time_id = *this->time_id_;
 | 
				
			||||||
 | 
					    time_id->add_on_time_sync_callback([this] { this->send_local_time_(); });
 | 
				
			||||||
 | 
					    time::ESPTime now = time_id->now();
 | 
				
			||||||
 | 
					    ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    ESP_LOGI(TAG, "`time_id` is not configured: will not sync BedJet clock.");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Writes one BedjetPacket to the BLE client on the BEDJET_COMMAND_UUID. */
 | 
				
			||||||
 | 
					uint8_t Bedjet::write_bedjet_packet_(BedjetPacket *pkt) {
 | 
				
			||||||
 | 
					  if (this->node_state != espbt::ClientState::ESTABLISHED) {
 | 
				
			||||||
 | 
					    if (!this->parent_->enabled) {
 | 
				
			||||||
 | 
					      ESP_LOGI(TAG, "[%s] Cannot write packet: Not connected, enabled=false", this->get_name().c_str());
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] Cannot write packet: Not connected", this->get_name().c_str());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return -1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_cmd_,
 | 
				
			||||||
 | 
					                                         pkt->data_length + 1, (uint8_t *) &pkt->command, ESP_GATT_WRITE_TYPE_NO_RSP,
 | 
				
			||||||
 | 
					                                         ESP_GATT_AUTH_REQ_NONE);
 | 
				
			||||||
 | 
					  return status;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Configures the local ESP BLE client to register (`true`) or unregister (`false`) for status notifications. */
 | 
				
			||||||
 | 
					uint8_t Bedjet::set_notify_(const bool enable) {
 | 
				
			||||||
 | 
					  uint8_t status;
 | 
				
			||||||
 | 
					  if (enable) {
 | 
				
			||||||
 | 
					    status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda,
 | 
				
			||||||
 | 
					                                               this->char_handle_status_);
 | 
				
			||||||
 | 
					    if (status) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    status = esp_ble_gattc_unregister_for_notify(this->parent_->gattc_if, this->parent_->remote_bda,
 | 
				
			||||||
 | 
					                                                 this->char_handle_status_);
 | 
				
			||||||
 | 
					    if (status) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] esp_ble_gattc_unregister_for_notify failed, status=%d", this->get_name().c_str(), status);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "[%s] set_notify: enable=%d; result=%d", this->get_name().c_str(), enable, status);
 | 
				
			||||||
 | 
					  return status;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Attempts to update the climate device from the last received BedjetStatusPacket.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @return `true` if the status has been applied; `false` if there is nothing to apply.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					bool Bedjet::update_status_() {
 | 
				
			||||||
 | 
					  if (!this->codec_->has_status())
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  BedjetStatusPacket status = *this->codec_->get_status_packet();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auto converted_temp = bedjet_temp_to_c(status.target_temp_step);
 | 
				
			||||||
 | 
					  if (converted_temp > 0)
 | 
				
			||||||
 | 
					    this->target_temperature = converted_temp;
 | 
				
			||||||
 | 
					  converted_temp = bedjet_temp_to_c(status.ambient_temp_step);
 | 
				
			||||||
 | 
					  if (converted_temp > 0)
 | 
				
			||||||
 | 
					    this->current_temperature = converted_temp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const auto *fan_mode_name = bedjet_fan_step_to_fan_mode(status.fan_step);
 | 
				
			||||||
 | 
					  if (fan_mode_name != nullptr) {
 | 
				
			||||||
 | 
					    this->custom_fan_mode = *fan_mode_name;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // TODO: Get biorhythm data to determine which preset (M1-3) is running, if any.
 | 
				
			||||||
 | 
					  switch (status.mode) {
 | 
				
			||||||
 | 
					    case MODE_WAIT:  // Biorhythm "wait" step: device is idle
 | 
				
			||||||
 | 
					    case MODE_STANDBY:
 | 
				
			||||||
 | 
					      this->mode = climate::CLIMATE_MODE_OFF;
 | 
				
			||||||
 | 
					      this->action = climate::CLIMATE_ACTION_IDLE;
 | 
				
			||||||
 | 
					      this->fan_mode = climate::CLIMATE_FAN_OFF;
 | 
				
			||||||
 | 
					      this->custom_preset.reset();
 | 
				
			||||||
 | 
					      this->preset.reset();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case MODE_HEAT:
 | 
				
			||||||
 | 
					    case MODE_EXTHT:
 | 
				
			||||||
 | 
					      this->mode = climate::CLIMATE_MODE_HEAT;
 | 
				
			||||||
 | 
					      this->action = climate::CLIMATE_ACTION_HEATING;
 | 
				
			||||||
 | 
					      this->custom_preset.reset();
 | 
				
			||||||
 | 
					      this->preset.reset();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case MODE_COOL:
 | 
				
			||||||
 | 
					      this->mode = climate::CLIMATE_MODE_FAN_ONLY;
 | 
				
			||||||
 | 
					      this->action = climate::CLIMATE_ACTION_COOLING;
 | 
				
			||||||
 | 
					      this->custom_preset.reset();
 | 
				
			||||||
 | 
					      this->preset.reset();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case MODE_DRY:
 | 
				
			||||||
 | 
					      this->mode = climate::CLIMATE_MODE_DRY;
 | 
				
			||||||
 | 
					      this->action = climate::CLIMATE_ACTION_DRYING;
 | 
				
			||||||
 | 
					      this->custom_preset.reset();
 | 
				
			||||||
 | 
					      this->preset.reset();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case MODE_TURBO:
 | 
				
			||||||
 | 
					      this->preset = climate::CLIMATE_PRESET_BOOST;
 | 
				
			||||||
 | 
					      this->custom_preset.reset();
 | 
				
			||||||
 | 
					      this->mode = climate::CLIMATE_MODE_HEAT;
 | 
				
			||||||
 | 
					      this->action = climate::CLIMATE_ACTION_HEATING;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] Unexpected mode: 0x%02X", this->get_name().c_str(), status.mode);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->is_valid_()) {
 | 
				
			||||||
 | 
					    this->publish_state();
 | 
				
			||||||
 | 
					    this->codec_->clear_status();
 | 
				
			||||||
 | 
					    this->status_clear_warning();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Bedjet::update() {
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "[%s] update()", this->get_name().c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (this->node_state != espbt::ClientState::ESTABLISHED) {
 | 
				
			||||||
 | 
					    if (!this->parent()->enabled) {
 | 
				
			||||||
 | 
					      ESP_LOGD(TAG, "[%s] Not connected, because enabled=false", this->get_name().c_str());
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      // Possibly still trying to connect.
 | 
				
			||||||
 | 
					      ESP_LOGD(TAG, "[%s] Not connected, enabled=true", this->get_name().c_str());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auto result = this->update_status_();
 | 
				
			||||||
 | 
					  if (!result) {
 | 
				
			||||||
 | 
					    uint32_t now = millis();
 | 
				
			||||||
 | 
					    uint32_t diff = now - this->last_notify_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this->last_notify_ == 0) {
 | 
				
			||||||
 | 
					      // This means we're connected and haven't received a notification, so it likely means that the BedJet is off.
 | 
				
			||||||
 | 
					      // However, it could also mean that it's running, but failing to send notifications.
 | 
				
			||||||
 | 
					      // We can try to unregister for notifications now, and then re-register, hoping to clear it up...
 | 
				
			||||||
 | 
					      // But how do we know for sure which state we're in, and how do we actually clear out the buggy state?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ESP_LOGI(TAG, "[%s] Still waiting for first GATT notify event.", this->get_name().c_str());
 | 
				
			||||||
 | 
					      this->set_notify_(false);
 | 
				
			||||||
 | 
					    } else if (diff > NOTIFY_WARN_THRESHOLD) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] Last GATT notify was %d seconds ago.", this->get_name().c_str(), diff / 1000);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this->timeout_ > 0 && diff > this->timeout_ && this->parent()->enabled) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "[%s] Timed out after %d sec. Retrying...", this->get_name().c_str(), this->timeout_);
 | 
				
			||||||
 | 
					      this->parent()->set_enabled(false);
 | 
				
			||||||
 | 
					      this->parent()->set_enabled(true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace bedjet
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										121
									
								
								esphome/components/bedjet/bedjet.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								esphome/components/bedjet/bedjet.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/components/ble_client/ble_client.h"
 | 
				
			||||||
 | 
					#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
				
			||||||
 | 
					#include "esphome/components/climate/climate.h"
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					#include "bedjet_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_TIME
 | 
				
			||||||
 | 
					#include "esphome/components/time/real_time_clock.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <esp_gattc_api.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace bedjet {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace espbt = esphome::esp32_ble_tracker;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const espbt::ESPBTUUID BEDJET_SERVICE_UUID = espbt::ESPBTUUID::from_raw("00001000-bed0-0080-aa55-4265644a6574");
 | 
				
			||||||
 | 
					static const espbt::ESPBTUUID BEDJET_STATUS_UUID = espbt::ESPBTUUID::from_raw("00002000-bed0-0080-aa55-4265644a6574");
 | 
				
			||||||
 | 
					static const espbt::ESPBTUUID BEDJET_COMMAND_UUID = espbt::ESPBTUUID::from_raw("00002004-bed0-0080-aa55-4265644a6574");
 | 
				
			||||||
 | 
					static const espbt::ESPBTUUID BEDJET_NAME_UUID = espbt::ESPBTUUID::from_raw("00002001-bed0-0080-aa55-4265644a6574");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Bedjet : public climate::Climate, public esphome::ble_client::BLEClientNode, public PollingComponent {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void setup() override;
 | 
				
			||||||
 | 
					  void loop() override;
 | 
				
			||||||
 | 
					  void update() override;
 | 
				
			||||||
 | 
					  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
				
			||||||
 | 
					                           esp_ble_gattc_cb_param_t *param) override;
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					  float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_TIME
 | 
				
			||||||
 | 
					  void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  void set_status_timeout(uint32_t timeout) { this->timeout_ = timeout; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Attempts to check for and apply firmware updates. */
 | 
				
			||||||
 | 
					  void upgrade_firmware();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  climate::ClimateTraits traits() override {
 | 
				
			||||||
 | 
					    auto traits = climate::ClimateTraits();
 | 
				
			||||||
 | 
					    traits.set_supports_action(true);
 | 
				
			||||||
 | 
					    traits.set_supports_current_temperature(true);
 | 
				
			||||||
 | 
					    traits.set_supported_modes({
 | 
				
			||||||
 | 
					        climate::CLIMATE_MODE_OFF,
 | 
				
			||||||
 | 
					        climate::CLIMATE_MODE_HEAT,
 | 
				
			||||||
 | 
					        // climate::CLIMATE_MODE_TURBO // Not supported by Climate: see presets instead
 | 
				
			||||||
 | 
					        climate::CLIMATE_MODE_FAN_ONLY,
 | 
				
			||||||
 | 
					        climate::CLIMATE_MODE_DRY,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // It would be better if we had a slider for the fan modes.
 | 
				
			||||||
 | 
					    traits.set_supported_custom_fan_modes(BEDJET_FAN_STEP_NAMES_SET);
 | 
				
			||||||
 | 
					    traits.set_supported_presets({
 | 
				
			||||||
 | 
					        // If we support NONE, then have to decide what happens if the user switches to it (turn off?)
 | 
				
			||||||
 | 
					        // climate::CLIMATE_PRESET_NONE,
 | 
				
			||||||
 | 
					        // Climate doesn't have a "TURBO" mode, but we can use the BOOST preset instead.
 | 
				
			||||||
 | 
					        climate::CLIMATE_PRESET_BOOST,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    traits.set_supported_custom_presets({
 | 
				
			||||||
 | 
					        // We could fetch biodata from bedjet and set these names that way.
 | 
				
			||||||
 | 
					        // But then we have to invert the lookup in order to send the right preset.
 | 
				
			||||||
 | 
					        // For now, we can leave them as M1-3 to match the remote buttons.
 | 
				
			||||||
 | 
					        "M1",
 | 
				
			||||||
 | 
					        "M2",
 | 
				
			||||||
 | 
					        "M3",
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    traits.set_visual_min_temperature(19.0);
 | 
				
			||||||
 | 
					    traits.set_visual_max_temperature(43.0);
 | 
				
			||||||
 | 
					    traits.set_visual_temperature_step(1.0);
 | 
				
			||||||
 | 
					    return traits;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  void control(const climate::ClimateCall &call) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_TIME
 | 
				
			||||||
 | 
					  void setup_time_();
 | 
				
			||||||
 | 
					  void send_local_time_();
 | 
				
			||||||
 | 
					  optional<time::RealTimeClock *> time_id_{};
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint32_t timeout_{DEFAULT_STATUS_TIMEOUT};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static const uint32_t MIN_NOTIFY_THROTTLE = 5000;
 | 
				
			||||||
 | 
					  static const uint32_t NOTIFY_WARN_THRESHOLD = 300000;
 | 
				
			||||||
 | 
					  static const uint32_t DEFAULT_STATUS_TIMEOUT = 900000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint8_t set_notify_(bool enable);
 | 
				
			||||||
 | 
					  uint8_t write_bedjet_packet_(BedjetPacket *pkt);
 | 
				
			||||||
 | 
					  void reset_state_();
 | 
				
			||||||
 | 
					  bool update_status_();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool is_valid_() {
 | 
				
			||||||
 | 
					    // FIXME: find a better way to check this?
 | 
				
			||||||
 | 
					    return !std::isnan(this->current_temperature) && !std::isnan(this->target_temperature) &&
 | 
				
			||||||
 | 
					           this->current_temperature > 1 && this->target_temperature > 1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint32_t last_notify_ = 0;
 | 
				
			||||||
 | 
					  bool force_refresh_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  std::unique_ptr<BedjetCodec> codec_;
 | 
				
			||||||
 | 
					  uint16_t char_handle_cmd_;
 | 
				
			||||||
 | 
					  uint16_t char_handle_name_;
 | 
				
			||||||
 | 
					  uint16_t char_handle_status_;
 | 
				
			||||||
 | 
					  uint16_t config_descr_status_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint8_t write_notify_config_descriptor_(bool enable);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace bedjet
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										123
									
								
								esphome/components/bedjet/bedjet_base.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								esphome/components/bedjet/bedjet_base.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
				
			|||||||
 | 
					#include "bedjet_base.h"
 | 
				
			||||||
 | 
					#include <cstdio>
 | 
				
			||||||
 | 
					#include <cstring>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace bedjet {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Converts a BedJet temp step into degrees Fahrenheit.
 | 
				
			||||||
 | 
					float bedjet_temp_to_f(const uint8_t temp) {
 | 
				
			||||||
 | 
					  // BedJet temp is "C*2"; to get F, multiply by 0.9 (half 1.8) and add 32.
 | 
				
			||||||
 | 
					  return 0.9f * temp + 32.0f;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Cleans up the packet before sending. */
 | 
				
			||||||
 | 
					BedjetPacket *BedjetCodec::clean_packet_() {
 | 
				
			||||||
 | 
					  // So far no commands require more than 2 bytes of data.
 | 
				
			||||||
 | 
					  assert(this->packet_.data_length <= 2);
 | 
				
			||||||
 | 
					  for (int i = this->packet_.data_length; i < 2; i++) {
 | 
				
			||||||
 | 
					    this->packet_.data[i] = '\0';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "Created packet: %02X, %02X %02X", this->packet_.command, this->packet_.data[0], this->packet_.data[1]);
 | 
				
			||||||
 | 
					  return &this->packet_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Returns a BedjetPacket that will initiate a BedjetButton press. */
 | 
				
			||||||
 | 
					BedjetPacket *BedjetCodec::get_button_request(BedjetButton button) {
 | 
				
			||||||
 | 
					  this->packet_.command = CMD_BUTTON;
 | 
				
			||||||
 | 
					  this->packet_.data_length = 1;
 | 
				
			||||||
 | 
					  this->packet_.data[0] = button;
 | 
				
			||||||
 | 
					  return this->clean_packet_();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Returns a BedjetPacket that will set the device's target `temperature`. */
 | 
				
			||||||
 | 
					BedjetPacket *BedjetCodec::get_set_target_temp_request(float temperature) {
 | 
				
			||||||
 | 
					  this->packet_.command = CMD_SET_TEMP;
 | 
				
			||||||
 | 
					  this->packet_.data_length = 1;
 | 
				
			||||||
 | 
					  this->packet_.data[0] = temperature * 2;
 | 
				
			||||||
 | 
					  return this->clean_packet_();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Returns a BedjetPacket that will set the device's target fan speed. */
 | 
				
			||||||
 | 
					BedjetPacket *BedjetCodec::get_set_fan_speed_request(const uint8_t fan_step) {
 | 
				
			||||||
 | 
					  this->packet_.command = CMD_SET_FAN;
 | 
				
			||||||
 | 
					  this->packet_.data_length = 1;
 | 
				
			||||||
 | 
					  this->packet_.data[0] = fan_step;
 | 
				
			||||||
 | 
					  return this->clean_packet_();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Returns a BedjetPacket that will set the device's current time. */
 | 
				
			||||||
 | 
					BedjetPacket *BedjetCodec::get_set_time_request(const uint8_t hour, const uint8_t minute) {
 | 
				
			||||||
 | 
					  this->packet_.command = CMD_SET_TIME;
 | 
				
			||||||
 | 
					  this->packet_.data_length = 2;
 | 
				
			||||||
 | 
					  this->packet_.data[0] = hour;
 | 
				
			||||||
 | 
					  this->packet_.data[1] = minute;
 | 
				
			||||||
 | 
					  return this->clean_packet_();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Decodes the extra bytes that were received after being notified with a partial packet. */
 | 
				
			||||||
 | 
					void BedjetCodec::decode_extra(const uint8_t *data, uint16_t length) {
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "Received extra: %d bytes: %d %d %d %d", length, data[1], data[2], data[3], data[4]);
 | 
				
			||||||
 | 
					  uint8_t offset = this->last_buffer_size_;
 | 
				
			||||||
 | 
					  if (offset > 0 && length + offset <= sizeof(BedjetStatusPacket)) {
 | 
				
			||||||
 | 
					    memcpy(((uint8_t *) (&this->buf_)) + offset, data, length);
 | 
				
			||||||
 | 
					    ESP_LOGV(TAG,
 | 
				
			||||||
 | 
					             "Extra bytes: skip1=0x%08x, skip2=0x%04x, skip3=0x%02x; update phase=0x%02x, "
 | 
				
			||||||
 | 
					             "flags=BedjetFlags <conn=%c, leds=%c, units=%c, mute=%c, others=%02x>",
 | 
				
			||||||
 | 
					             this->buf_._skip_1_, this->buf_._skip_2_, this->buf_._skip_3_, this->buf_.update_phase,
 | 
				
			||||||
 | 
					             this->buf_.flags & 0x20 ? '1' : '0', this->buf_.flags & 0x10 ? '1' : '0',
 | 
				
			||||||
 | 
					             this->buf_.flags & 0x04 ? '1' : '0', this->buf_.flags & 0x01 ? '1' : '0',
 | 
				
			||||||
 | 
					             this->buf_.flags & ~(0x20 | 0x10 | 0x04 | 0x01));
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    ESP_LOGI(TAG, "Could not determine where to append to, last offset=%d, max size=%u, new size would be %d", offset,
 | 
				
			||||||
 | 
					             sizeof(BedjetStatusPacket), length + offset);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Decodes the incoming status packet received on the BEDJET_STATUS_UUID.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @return `true` if the packet was decoded and represents a "partial" packet; `false` otherwise.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					bool BedjetCodec::decode_notify(const uint8_t *data, uint16_t length) {
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "Received: %d bytes: %d %d %d %d", length, data[1], data[2], data[3], data[4]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (data[1] == PACKET_FORMAT_V3_HOME && data[3] == PACKET_TYPE_STATUS) {
 | 
				
			||||||
 | 
					    this->status_packet_.reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Clear old buffer
 | 
				
			||||||
 | 
					    memset(&this->buf_, 0, sizeof(BedjetStatusPacket));
 | 
				
			||||||
 | 
					    // Copy new data into buffer
 | 
				
			||||||
 | 
					    memcpy(&this->buf_, data, length);
 | 
				
			||||||
 | 
					    this->last_buffer_size_ = length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: validate the packet checksum?
 | 
				
			||||||
 | 
					    if (this->buf_.mode >= 0 && this->buf_.mode < 7 && this->buf_.target_temp_step >= 38 &&
 | 
				
			||||||
 | 
					        this->buf_.target_temp_step <= 86 && this->buf_.actual_temp_step > 1 && this->buf_.actual_temp_step <= 100 &&
 | 
				
			||||||
 | 
					        this->buf_.ambient_temp_step > 1 && this->buf_.ambient_temp_step <= 100) {
 | 
				
			||||||
 | 
					      // and save it for the update() loop
 | 
				
			||||||
 | 
					      this->status_packet_ = this->buf_;
 | 
				
			||||||
 | 
					      return this->buf_.is_partial == 1;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      // TODO: log a warning if we detect that we connected to a non-V3 device.
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Received potentially invalid packet (len %d):", length);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else if (data[1] == PACKET_FORMAT_DEBUG || data[3] == PACKET_TYPE_DEBUG) {
 | 
				
			||||||
 | 
					    // We don't actually know the packet format for this. Dump packets to log, in case a pattern presents itself.
 | 
				
			||||||
 | 
					    ESP_LOGV(TAG,
 | 
				
			||||||
 | 
					             "received DEBUG packet: set1=%01fF, set2=%01fF, air=%01fF;  [7]=%d, [8]=%d, [9]=%d, [10]=%d, [11]=%d, "
 | 
				
			||||||
 | 
					             "[12]=%d, [-1]=%d",
 | 
				
			||||||
 | 
					             bedjet_temp_to_f(data[4]), bedjet_temp_to_f(data[5]), bedjet_temp_to_f(data[6]), data[7], data[8], data[9],
 | 
				
			||||||
 | 
					             data[10], data[11], data[12], data[length - 1]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this->has_status()) {
 | 
				
			||||||
 | 
					      this->status_packet_->ambient_temp_step = data[6];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    // TODO: log a warning if we detect that we connected to a non-V3 device.
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace bedjet
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										159
									
								
								esphome/components/bedjet/bedjet_base.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								esphome/components/bedjet/bedjet_base.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,159 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "bedjet_const.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace bedjet {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct BedjetPacket {
 | 
				
			||||||
 | 
					  uint8_t data_length;
 | 
				
			||||||
 | 
					  BedjetCommand command;
 | 
				
			||||||
 | 
					  uint8_t data[2];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct BedjetFlags {
 | 
				
			||||||
 | 
					  /* uint8_t */
 | 
				
			||||||
 | 
					  int a_ : 1;                // 0x80
 | 
				
			||||||
 | 
					  int b_ : 1;                // 0x40
 | 
				
			||||||
 | 
					  int conn_test_passed : 1;  ///< (0x20) Bit is set `1` if the last connection test passed.
 | 
				
			||||||
 | 
					  int leds_enabled : 1;      ///< (0x10) Bit is set `1` if the LEDs on the device are enabled.
 | 
				
			||||||
 | 
					  int c_ : 1;                // 0x08
 | 
				
			||||||
 | 
					  int units_setup : 1;       ///< (0x04) Bit is set `1` if the device's units have been configured.
 | 
				
			||||||
 | 
					  int d_ : 1;                // 0x02
 | 
				
			||||||
 | 
					  int beeps_muted : 1;       ///< (0x01) Bit is set `1` if the device's sound output is muted.
 | 
				
			||||||
 | 
					} __attribute__((packed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum BedjetPacketFormat : uint8_t {
 | 
				
			||||||
 | 
					  PACKET_FORMAT_DEBUG = 0x05,    //  5
 | 
				
			||||||
 | 
					  PACKET_FORMAT_V3_HOME = 0x56,  // 86
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum BedjetPacketType : uint8_t {
 | 
				
			||||||
 | 
					  PACKET_TYPE_STATUS = 0x1,
 | 
				
			||||||
 | 
					  PACKET_TYPE_DEBUG = 0x2,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** The format of a BedJet V3 status packet. */
 | 
				
			||||||
 | 
					struct BedjetStatusPacket {
 | 
				
			||||||
 | 
					  // [0]
 | 
				
			||||||
 | 
					  uint8_t is_partial : 8;  ///< `1` indicates that this is a partial packet, and more data can be read directly from the
 | 
				
			||||||
 | 
					                           ///< characteristic.
 | 
				
			||||||
 | 
					  BedjetPacketFormat packet_format : 8;  ///< BedjetPacketFormat::PACKET_FORMAT_V3_HOME for BedJet V3 status packet
 | 
				
			||||||
 | 
					                                         ///< format. BedjetPacketFormat::PACKET_FORMAT_DEBUG for debugging packets.
 | 
				
			||||||
 | 
					  uint8_t
 | 
				
			||||||
 | 
					      expecting_length : 8;  ///< The expected total length of the status packet after merging the additional packet.
 | 
				
			||||||
 | 
					  BedjetPacketType packet_type : 8;  ///< Typically BedjetPacketType::PACKET_TYPE_STATUS for BedJet V3 status packet.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // [4]
 | 
				
			||||||
 | 
					  uint8_t time_remaining_hrs : 8;   ///< Hours remaining in program runtime
 | 
				
			||||||
 | 
					  uint8_t time_remaining_mins : 8;  ///< Minutes remaining in program runtime
 | 
				
			||||||
 | 
					  uint8_t time_remaining_secs : 8;  ///< Seconds remaining in program runtime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // [7]
 | 
				
			||||||
 | 
					  uint8_t actual_temp_step : 8;  ///< Actual temp of the air blown by the BedJet fan; value represents `2 *
 | 
				
			||||||
 | 
					                                 ///< degrees_celsius`. See #bedjet_temp_to_c and #bedjet_temp_to_f
 | 
				
			||||||
 | 
					  uint8_t target_temp_step : 8;  ///< Target temp that the BedJet will try to heat to. See #actual_temp_step.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // [9]
 | 
				
			||||||
 | 
					  BedjetMode mode : 8;  ///< BedJet operating mode.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // [10]
 | 
				
			||||||
 | 
					  uint8_t fan_step : 8;  ///< BedJet fan speed; value is in the 0-19 range, representing 5% increments (5%-100%): `5 + 5
 | 
				
			||||||
 | 
					                         ///< * fan_step`
 | 
				
			||||||
 | 
					  uint8_t max_hrs : 8;   ///< Max hours of mode runtime
 | 
				
			||||||
 | 
					  uint8_t max_mins : 8;  ///< Max minutes of mode runtime
 | 
				
			||||||
 | 
					  uint8_t min_temp_step : 8;  ///< Min temp allowed in mode. See #actual_temp_step.
 | 
				
			||||||
 | 
					  uint8_t max_temp_step : 8;  ///< Max temp allowed in mode. See #actual_temp_step.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // [15-16]
 | 
				
			||||||
 | 
					  uint16_t turbo_time : 16;  ///< Time remaining in BedjetMode::MODE_TURBO.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // [17]
 | 
				
			||||||
 | 
					  uint8_t ambient_temp_step : 8;  ///< Current ambient air temp. This is the coldest air the BedJet can blow. See
 | 
				
			||||||
 | 
					                                  ///< #actual_temp_step.
 | 
				
			||||||
 | 
					  uint8_t shutdown_reason : 8;    ///< The reason for the last device shutdown.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // [19-25]; the initial partial packet cuts off here after [19]
 | 
				
			||||||
 | 
					  // Skip 7 bytes?
 | 
				
			||||||
 | 
					  uint32_t _skip_1_ : 32;  // Unknown 19-22 = 0x01810112
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint16_t _skip_2_ : 16;  // Unknown 23-24 = 0x1310
 | 
				
			||||||
 | 
					  uint8_t _skip_3_ : 8;    // Unknown 25 = 0x00
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // [26]
 | 
				
			||||||
 | 
					  //   0x18(24) = "Connection test has completed OK"
 | 
				
			||||||
 | 
					  //   0x1a(26) = "Firmware update is not needed"
 | 
				
			||||||
 | 
					  uint8_t update_phase : 8;  ///< The current status/phase of a firmware update.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // [27]
 | 
				
			||||||
 | 
					  // FIXME: cannot nest packed struct of matching length here?
 | 
				
			||||||
 | 
					  /* BedjetFlags */ uint8_t flags : 8;  /// See BedjetFlags for the packed byte flags.
 | 
				
			||||||
 | 
					  // [28-31]; 20+11 bytes
 | 
				
			||||||
 | 
					  uint32_t _skip_4_ : 32;  // Unknown
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} __attribute__((packed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** This class is responsible for encoding command packets and decoding status packets.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Status Packets
 | 
				
			||||||
 | 
					 * ==============
 | 
				
			||||||
 | 
					 * The BedJet protocol depends on registering for notifications on the esphome::BedJet::BEDJET_SERVICE_UUID
 | 
				
			||||||
 | 
					 * characteristic. If the BedJet is on, it will send rapid updates as notifications. If it is off,
 | 
				
			||||||
 | 
					 * it generally will not notify of any status.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * As the BedJet V3's BedjetStatusPacket exceeds the buffer size allowed for BLE notification packets,
 | 
				
			||||||
 | 
					 * the notification packet will contain `BedjetStatusPacket::is_partial == 1`. When that happens, an additional
 | 
				
			||||||
 | 
					 * read of the esphome::BedJet::BEDJET_SERVICE_UUID characteristic will contain the second portion of the
 | 
				
			||||||
 | 
					 * full status packet.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Command Packets
 | 
				
			||||||
 | 
					 * ===============
 | 
				
			||||||
 | 
					 * This class supports encoding a number of BedjetPacket commands:
 | 
				
			||||||
 | 
					 * - Button press
 | 
				
			||||||
 | 
					 *   This simulates a press of one of the BedjetButton values.
 | 
				
			||||||
 | 
					 *   - BedjetPacket#command = BedjetCommand::CMD_BUTTON
 | 
				
			||||||
 | 
					 *   - BedjetPacket#data [0] contains the BedjetButton value
 | 
				
			||||||
 | 
					 * - Set target temp
 | 
				
			||||||
 | 
					 *   This sets the BedJet's target temp to a concrete temperature value.
 | 
				
			||||||
 | 
					 *   - BedjetPacket#command = BedjetCommand::CMD_SET_TEMP
 | 
				
			||||||
 | 
					 *   - BedjetPacket#data [0] contains the BedJet temp value; see BedjetStatusPacket#actual_temp_step
 | 
				
			||||||
 | 
					 * - Set fan speed
 | 
				
			||||||
 | 
					 *   This sets the BedJet fan speed.
 | 
				
			||||||
 | 
					 *   - BedjetPacket#command = BedjetCommand::CMD_SET_FAN
 | 
				
			||||||
 | 
					 *   - BedjetPacket#data [0] contains the BedJet fan step in the range 0-19.
 | 
				
			||||||
 | 
					 * - Set current time
 | 
				
			||||||
 | 
					 *   The BedJet needs to have its clock set properly in order to run the biorhythm programs, which might
 | 
				
			||||||
 | 
					 *   contain time-of-day based step rules.
 | 
				
			||||||
 | 
					 *   - BedjetPacket#command = BedjetCommand::CMD_SET_TIME
 | 
				
			||||||
 | 
					 *   - BedjetPacket#data [0] is hours, [1] is minutes
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class BedjetCodec {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  BedjetPacket *get_button_request(BedjetButton button);
 | 
				
			||||||
 | 
					  BedjetPacket *get_set_target_temp_request(float temperature);
 | 
				
			||||||
 | 
					  BedjetPacket *get_set_fan_speed_request(uint8_t fan_step);
 | 
				
			||||||
 | 
					  BedjetPacket *get_set_time_request(uint8_t hour, uint8_t minute);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool decode_notify(const uint8_t *data, uint16_t length);
 | 
				
			||||||
 | 
					  void decode_extra(const uint8_t *data, uint16_t length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  inline bool has_status() { return this->status_packet_.has_value(); }
 | 
				
			||||||
 | 
					  const optional<BedjetStatusPacket> &get_status_packet() const { return this->status_packet_; }
 | 
				
			||||||
 | 
					  void clear_status() { this->status_packet_.reset(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  BedjetPacket *clean_packet_();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  uint8_t last_buffer_size_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  BedjetPacket packet_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  optional<BedjetStatusPacket> status_packet_;
 | 
				
			||||||
 | 
					  BedjetStatusPacket buf_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace bedjet
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										78
									
								
								esphome/components/bedjet/bedjet_const.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								esphome/components/bedjet/bedjet_const.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <set>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace bedjet {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "bedjet";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum BedjetMode : uint8_t {
 | 
				
			||||||
 | 
					  /// BedJet is Off
 | 
				
			||||||
 | 
					  MODE_STANDBY = 0,
 | 
				
			||||||
 | 
					  /// BedJet is in Heat mode (limited to 4 hours)
 | 
				
			||||||
 | 
					  MODE_HEAT = 1,
 | 
				
			||||||
 | 
					  /// BedJet is in Turbo mode (high heat, limited time)
 | 
				
			||||||
 | 
					  MODE_TURBO = 2,
 | 
				
			||||||
 | 
					  /// BedJet is in Extended Heat mode (limited to 10 hours)
 | 
				
			||||||
 | 
					  MODE_EXTHT = 3,
 | 
				
			||||||
 | 
					  /// BedJet is in Cool mode (actually "Fan only" mode)
 | 
				
			||||||
 | 
					  MODE_COOL = 4,
 | 
				
			||||||
 | 
					  /// BedJet is in Dry mode (high speed, no heat)
 | 
				
			||||||
 | 
					  MODE_DRY = 5,
 | 
				
			||||||
 | 
					  /// BedJet is in "wait" mode, a step during a biorhythm program
 | 
				
			||||||
 | 
					  MODE_WAIT = 6,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum BedjetButton : uint8_t {
 | 
				
			||||||
 | 
					  /// Turn BedJet off
 | 
				
			||||||
 | 
					  BTN_OFF = 0x1,
 | 
				
			||||||
 | 
					  /// Enter Cool mode (fan only)
 | 
				
			||||||
 | 
					  BTN_COOL = 0x2,
 | 
				
			||||||
 | 
					  /// Enter Heat mode (limited to 4 hours)
 | 
				
			||||||
 | 
					  BTN_HEAT = 0x3,
 | 
				
			||||||
 | 
					  /// Enter Turbo mode (high heat, limited to 10 minutes)
 | 
				
			||||||
 | 
					  BTN_TURBO = 0x4,
 | 
				
			||||||
 | 
					  /// Enter Dry mode (high speed, no heat)
 | 
				
			||||||
 | 
					  BTN_DRY = 0x5,
 | 
				
			||||||
 | 
					  /// Enter Extended Heat mode (limited to 10 hours)
 | 
				
			||||||
 | 
					  BTN_EXTHT = 0x6,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Start the M1 biorhythm/preset program
 | 
				
			||||||
 | 
					  BTN_M1 = 0x20,
 | 
				
			||||||
 | 
					  /// Start the M2 biorhythm/preset program
 | 
				
			||||||
 | 
					  BTN_M2 = 0x21,
 | 
				
			||||||
 | 
					  /// Start the M3 biorhythm/preset program
 | 
				
			||||||
 | 
					  BTN_M3 = 0x22,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* These are "MAGIC" buttons */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Turn debug mode on/off
 | 
				
			||||||
 | 
					  MAGIC_DEBUG_ON = 0x40,
 | 
				
			||||||
 | 
					  MAGIC_DEBUG_OFF = 0x41,
 | 
				
			||||||
 | 
					  /// Perform a connection test.
 | 
				
			||||||
 | 
					  MAGIC_CONNTEST = 0x42,
 | 
				
			||||||
 | 
					  /// Request a firmware update. This will also restart the Bedjet.
 | 
				
			||||||
 | 
					  MAGIC_UPDATE = 0x43,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum BedjetCommand : uint8_t {
 | 
				
			||||||
 | 
					  CMD_BUTTON = 0x1,
 | 
				
			||||||
 | 
					  CMD_SET_TEMP = 0x3,
 | 
				
			||||||
 | 
					  CMD_STATUS = 0x6,
 | 
				
			||||||
 | 
					  CMD_SET_FAN = 0x7,
 | 
				
			||||||
 | 
					  CMD_SET_TIME = 0x8,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define BEDJET_FAN_STEP_NAMES_ \
 | 
				
			||||||
 | 
					  { \
 | 
				
			||||||
 | 
					    "  5%", " 10%", " 15%", " 20%", " 25%", " 30%", " 35%", " 40%", " 45%", " 50%", " 55%", " 60%", " 65%", " 70%", \
 | 
				
			||||||
 | 
					        " 75%", " 80%", " 85%", " 90%", " 95%", "100%" \
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const BEDJET_FAN_STEP_NAMES[20] = BEDJET_FAN_STEP_NAMES_;
 | 
				
			||||||
 | 
					static const std::string BEDJET_FAN_STEP_NAME_STRINGS[20] = BEDJET_FAN_STEP_NAMES_;
 | 
				
			||||||
 | 
					static const std::set<std::string> BEDJET_FAN_STEP_NAMES_SET BEDJET_FAN_STEP_NAMES_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace bedjet
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										42
									
								
								esphome/components/bedjet/climate.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								esphome/components/bedjet/climate.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import climate, ble_client, time
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_RECEIVE_TIMEOUT,
 | 
				
			||||||
 | 
					    CONF_TIME_ID,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CODEOWNERS = ["@jhansche"]
 | 
				
			||||||
 | 
					DEPENDENCIES = ["ble_client"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bedjet_ns = cg.esphome_ns.namespace("bedjet")
 | 
				
			||||||
 | 
					Bedjet = bedjet_ns.class_(
 | 
				
			||||||
 | 
					    "Bedjet", climate.Climate, ble_client.BLEClientNode, cg.PollingComponent
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = (
 | 
				
			||||||
 | 
					    climate.CLIMATE_SCHEMA.extend(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cv.GenerateID(): cv.declare_id(Bedjet),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
 | 
				
			||||||
 | 
					            cv.Optional(
 | 
				
			||||||
 | 
					                CONF_RECEIVE_TIMEOUT, default="0s"
 | 
				
			||||||
 | 
					            ): cv.positive_time_period_milliseconds,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .extend(ble_client.BLE_CLIENT_SCHEMA)
 | 
				
			||||||
 | 
					    .extend(cv.polling_component_schema("30s"))
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					    await climate.register_climate(var, config)
 | 
				
			||||||
 | 
					    await ble_client.register_ble_node(var, config)
 | 
				
			||||||
 | 
					    if CONF_TIME_ID in config:
 | 
				
			||||||
 | 
					        time_ = await cg.get_variable(config[CONF_TIME_ID])
 | 
				
			||||||
 | 
					        cg.add(var.set_time_id(time_))
 | 
				
			||||||
 | 
					    if CONF_RECEIVE_TIMEOUT in config:
 | 
				
			||||||
 | 
					        cg.add(var.set_status_timeout(config[CONF_RECEIVE_TIMEOUT]))
 | 
				
			||||||
							
								
								
									
										1
									
								
								esphome/components/bl0939/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/bl0939/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					CODEOWNERS = ["@ziceva"]
 | 
				
			||||||
							
								
								
									
										144
									
								
								esphome/components/bl0939/bl0939.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								esphome/components/bl0939/bl0939.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
				
			|||||||
 | 
					#include "bl0939.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace bl0939 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "bl0939";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// https://www.belling.com.cn/media/file_object/bel_product/BL0939/datasheet/BL0939_V1.2_cn.pdf
 | 
				
			||||||
 | 
					// (unfortunatelly chinese, but the protocol can be understood with some translation tool)
 | 
				
			||||||
 | 
					static const uint8_t BL0939_READ_COMMAND = 0x55;  // 0x5{A4,A3,A2,A1}
 | 
				
			||||||
 | 
					static const uint8_t BL0939_FULL_PACKET = 0xAA;
 | 
				
			||||||
 | 
					static const uint8_t BL0939_PACKET_HEADER = 0x55;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const uint8_t BL0939_WRITE_COMMAND = 0xA5;  // 0xA{A4,A3,A2,A1}
 | 
				
			||||||
 | 
					static const uint8_t BL0939_REG_IA_FAST_RMS_CTRL = 0x10;
 | 
				
			||||||
 | 
					static const uint8_t BL0939_REG_IB_FAST_RMS_CTRL = 0x1E;
 | 
				
			||||||
 | 
					static const uint8_t BL0939_REG_MODE = 0x18;
 | 
				
			||||||
 | 
					static const uint8_t BL0939_REG_SOFT_RESET = 0x19;
 | 
				
			||||||
 | 
					static const uint8_t BL0939_REG_USR_WRPROT = 0x1A;
 | 
				
			||||||
 | 
					static const uint8_t BL0939_REG_TPS_CTRL = 0x1B;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const uint8_t BL0939_INIT[6][6] = {
 | 
				
			||||||
 | 
					    // Reset to default
 | 
				
			||||||
 | 
					    {BL0939_WRITE_COMMAND, BL0939_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x33},
 | 
				
			||||||
 | 
					    // Enable User Operation Write
 | 
				
			||||||
 | 
					    {BL0939_WRITE_COMMAND, BL0939_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xEB},
 | 
				
			||||||
 | 
					    // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
 | 
				
			||||||
 | 
					    {BL0939_WRITE_COMMAND, BL0939_REG_MODE, 0x00, 0x10, 0x00, 0x32},
 | 
				
			||||||
 | 
					    // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
 | 
				
			||||||
 | 
					    {BL0939_WRITE_COMMAND, BL0939_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xF9},
 | 
				
			||||||
 | 
					    // 0x181C = Half cycle, Fast RMS threshold 6172
 | 
				
			||||||
 | 
					    {BL0939_WRITE_COMMAND, BL0939_REG_IA_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x16},
 | 
				
			||||||
 | 
					    // 0x181C = Half cycle, Fast RMS threshold 6172
 | 
				
			||||||
 | 
					    {BL0939_WRITE_COMMAND, BL0939_REG_IB_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x08}};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BL0939::loop() {
 | 
				
			||||||
 | 
					  DataPacket buffer;
 | 
				
			||||||
 | 
					  if (!this->available()) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
 | 
				
			||||||
 | 
					    if (validate_checksum(&buffer)) {
 | 
				
			||||||
 | 
					      received_package_(&buffer);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
 | 
				
			||||||
 | 
					    while (read() >= 0)
 | 
				
			||||||
 | 
					      ;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool BL0939::validate_checksum(const DataPacket *data) {
 | 
				
			||||||
 | 
					  uint8_t checksum = BL0939_READ_COMMAND;
 | 
				
			||||||
 | 
					  // Whole package but checksum
 | 
				
			||||||
 | 
					  for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) {
 | 
				
			||||||
 | 
					    checksum += data->raw[i];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  checksum ^= 0xFF;
 | 
				
			||||||
 | 
					  if (checksum != data->checksum) {
 | 
				
			||||||
 | 
					    ESP_LOGW(TAG, "BL0939 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return checksum == data->checksum;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BL0939::update() {
 | 
				
			||||||
 | 
					  this->flush();
 | 
				
			||||||
 | 
					  this->write_byte(BL0939_READ_COMMAND);
 | 
				
			||||||
 | 
					  this->write_byte(BL0939_FULL_PACKET);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BL0939::setup() {
 | 
				
			||||||
 | 
					  for (auto *i : BL0939_INIT) {
 | 
				
			||||||
 | 
					    this->write_array(i, 6);
 | 
				
			||||||
 | 
					    delay(1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->flush();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BL0939::received_package_(const DataPacket *data) const {
 | 
				
			||||||
 | 
					  // Bad header
 | 
				
			||||||
 | 
					  if (data->frame_header != BL0939_PACKET_HEADER) {
 | 
				
			||||||
 | 
					    ESP_LOGI("bl0939", "Invalid data. Header mismatch: %d", data->frame_header);
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_;
 | 
				
			||||||
 | 
					  float ia_rms = (float) to_uint32_t(data->ia_rms) / current_reference_;
 | 
				
			||||||
 | 
					  float ib_rms = (float) to_uint32_t(data->ib_rms) / current_reference_;
 | 
				
			||||||
 | 
					  float a_watt = (float) to_int32_t(data->a_watt) / power_reference_;
 | 
				
			||||||
 | 
					  float b_watt = (float) to_int32_t(data->b_watt) / power_reference_;
 | 
				
			||||||
 | 
					  int32_t cfa_cnt = to_int32_t(data->cfa_cnt);
 | 
				
			||||||
 | 
					  int32_t cfb_cnt = to_int32_t(data->cfb_cnt);
 | 
				
			||||||
 | 
					  float a_energy_consumption = (float) cfa_cnt / energy_reference_;
 | 
				
			||||||
 | 
					  float b_energy_consumption = (float) cfb_cnt / energy_reference_;
 | 
				
			||||||
 | 
					  float total_energy_consumption = a_energy_consumption + b_energy_consumption;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (voltage_sensor_ != nullptr) {
 | 
				
			||||||
 | 
					    voltage_sensor_->publish_state(v_rms);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (current_sensor_1_ != nullptr) {
 | 
				
			||||||
 | 
					    current_sensor_1_->publish_state(ia_rms);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (current_sensor_2_ != nullptr) {
 | 
				
			||||||
 | 
					    current_sensor_2_->publish_state(ib_rms);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (power_sensor_1_ != nullptr) {
 | 
				
			||||||
 | 
					    power_sensor_1_->publish_state(a_watt);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (power_sensor_2_ != nullptr) {
 | 
				
			||||||
 | 
					    power_sensor_2_->publish_state(b_watt);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (energy_sensor_1_ != nullptr) {
 | 
				
			||||||
 | 
					    energy_sensor_1_->publish_state(a_energy_consumption);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (energy_sensor_2_ != nullptr) {
 | 
				
			||||||
 | 
					    energy_sensor_2_->publish_state(b_energy_consumption);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (energy_sensor_sum_ != nullptr) {
 | 
				
			||||||
 | 
					    energy_sensor_sum_->publish_state(total_energy_consumption);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ESP_LOGV("bl0939", "BL0939: U %fV, I1 %fA, I2 %fA, P1 %fW, P2 %fW, CntA %d, CntB %d, ∫P1 %fkWh, ∫P2 %fkWh", v_rms,
 | 
				
			||||||
 | 
					           ia_rms, ib_rms, a_watt, b_watt, cfa_cnt, cfb_cnt, a_energy_consumption, b_energy_consumption);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BL0939::dump_config() {  // NOLINT(readability-function-cognitive-complexity)
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "BL0939:");
 | 
				
			||||||
 | 
					  LOG_SENSOR("", "Voltage", this->voltage_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("", "Current 1", this->current_sensor_1_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("", "Current 2", this->current_sensor_2_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("", "Power 1", this->power_sensor_1_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("", "Power 2", this->power_sensor_2_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("", "Energy 1", this->energy_sensor_1_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("", "Energy 2", this->energy_sensor_2_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("", "Energy sum", this->energy_sensor_sum_);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint32_t BL0939::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int32_t BL0939::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace bl0939
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										107
									
								
								esphome/components/bl0939/bl0939.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								esphome/components/bl0939/bl0939.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/components/uart/uart.h"
 | 
				
			||||||
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace bl0939 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// https://datasheet.lcsc.com/lcsc/2108071830_BL-Shanghai-Belling-BL0939_C2841044.pdf
 | 
				
			||||||
 | 
					// (unfortunatelly chinese, but the formulas can be easily understood)
 | 
				
			||||||
 | 
					// Sonoff Dual R3 V2 has the exact same resistor values for the current shunts (RL=1miliOhm)
 | 
				
			||||||
 | 
					// and for the voltage divider (R1=0.51kOhm, R2=5*390kOhm)
 | 
				
			||||||
 | 
					// as in the manufacturer's reference circuit, so the same formulas were used here (Vref=1.218V)
 | 
				
			||||||
 | 
					static const float BL0939_IREF = 324004 * 1 / 1.218;
 | 
				
			||||||
 | 
					static const float BL0939_UREF = 79931 * 0.51 * 1000 / (1.218 * (5 * 390 + 0.51));
 | 
				
			||||||
 | 
					static const float BL0939_PREF = 4046 * 1 * 0.51 * 1000 / (1.218 * 1.218 * (5 * 390 + 0.51));
 | 
				
			||||||
 | 
					static const float BL0939_EREF = 3.6e6 * 4046 * 1 * 0.51 * 1000 / (1638.4 * 256 * 1.218 * 1.218 * (5 * 390 + 0.51));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ube24_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align)
 | 
				
			||||||
 | 
					  uint8_t l;
 | 
				
			||||||
 | 
					  uint8_t m;
 | 
				
			||||||
 | 
					  uint8_t h;
 | 
				
			||||||
 | 
					} __attribute__((packed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct ube16_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align)
 | 
				
			||||||
 | 
					  uint8_t l;
 | 
				
			||||||
 | 
					  uint8_t h;
 | 
				
			||||||
 | 
					} __attribute__((packed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct sbe24_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align)
 | 
				
			||||||
 | 
					  uint8_t l;
 | 
				
			||||||
 | 
					  uint8_t m;
 | 
				
			||||||
 | 
					  int8_t h;
 | 
				
			||||||
 | 
					} __attribute__((packed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Caveat: All these values are big endian (low - middle - high)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					union DataPacket {  // NOLINT(altera-struct-pack-align)
 | 
				
			||||||
 | 
					  uint8_t raw[35];
 | 
				
			||||||
 | 
					  struct {
 | 
				
			||||||
 | 
					    uint8_t frame_header;  // 0x55 according to docs
 | 
				
			||||||
 | 
					    ube24_t ia_fast_rms;
 | 
				
			||||||
 | 
					    ube24_t ia_rms;
 | 
				
			||||||
 | 
					    ube24_t ib_rms;
 | 
				
			||||||
 | 
					    ube24_t v_rms;
 | 
				
			||||||
 | 
					    ube24_t ib_fast_rms;
 | 
				
			||||||
 | 
					    sbe24_t a_watt;
 | 
				
			||||||
 | 
					    sbe24_t b_watt;
 | 
				
			||||||
 | 
					    sbe24_t cfa_cnt;
 | 
				
			||||||
 | 
					    sbe24_t cfb_cnt;
 | 
				
			||||||
 | 
					    ube16_t tps1;
 | 
				
			||||||
 | 
					    uint8_t RESERVED1;  // value of 0x00
 | 
				
			||||||
 | 
					    ube16_t tps2;
 | 
				
			||||||
 | 
					    uint8_t RESERVED2;  // value of 0x00
 | 
				
			||||||
 | 
					    uint8_t checksum;   // checksum
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					} __attribute__((packed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BL0939 : public PollingComponent, public uart::UARTDevice {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
 | 
				
			||||||
 | 
					  void set_current_sensor_1(sensor::Sensor *current_sensor_1) { current_sensor_1_ = current_sensor_1; }
 | 
				
			||||||
 | 
					  void set_current_sensor_2(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; }
 | 
				
			||||||
 | 
					  void set_power_sensor_1(sensor::Sensor *power_sensor_1) { power_sensor_1_ = power_sensor_1; }
 | 
				
			||||||
 | 
					  void set_power_sensor_2(sensor::Sensor *power_sensor_2) { power_sensor_2_ = power_sensor_2; }
 | 
				
			||||||
 | 
					  void set_energy_sensor_1(sensor::Sensor *energy_sensor_1) { energy_sensor_1_ = energy_sensor_1; }
 | 
				
			||||||
 | 
					  void set_energy_sensor_2(sensor::Sensor *energy_sensor_2) { energy_sensor_2_ = energy_sensor_2; }
 | 
				
			||||||
 | 
					  void set_energy_sensor_sum(sensor::Sensor *energy_sensor_sum) { energy_sensor_sum_ = energy_sensor_sum; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void loop() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void update() override;
 | 
				
			||||||
 | 
					  void setup() override;
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  sensor::Sensor *voltage_sensor_;
 | 
				
			||||||
 | 
					  sensor::Sensor *current_sensor_1_;
 | 
				
			||||||
 | 
					  sensor::Sensor *current_sensor_2_;
 | 
				
			||||||
 | 
					  // NB This may be negative as the circuits is seemingly able to measure
 | 
				
			||||||
 | 
					  // power in both directions
 | 
				
			||||||
 | 
					  sensor::Sensor *power_sensor_1_;
 | 
				
			||||||
 | 
					  sensor::Sensor *power_sensor_2_;
 | 
				
			||||||
 | 
					  sensor::Sensor *energy_sensor_1_;
 | 
				
			||||||
 | 
					  sensor::Sensor *energy_sensor_2_;
 | 
				
			||||||
 | 
					  sensor::Sensor *energy_sensor_sum_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Divide by this to turn into Watt
 | 
				
			||||||
 | 
					  float power_reference_ = BL0939_PREF;
 | 
				
			||||||
 | 
					  // Divide by this to turn into Volt
 | 
				
			||||||
 | 
					  float voltage_reference_ = BL0939_UREF;
 | 
				
			||||||
 | 
					  // Divide by this to turn into Ampere
 | 
				
			||||||
 | 
					  float current_reference_ = BL0939_IREF;
 | 
				
			||||||
 | 
					  // Divide by this to turn into kWh
 | 
				
			||||||
 | 
					  float energy_reference_ = BL0939_EREF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static uint32_t to_uint32_t(ube24_t input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static int32_t to_int32_t(sbe24_t input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static bool validate_checksum(const DataPacket *data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void received_package_(const DataPacket *data) const;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					}  // namespace bl0939
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										123
									
								
								esphome/components/bl0939/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								esphome/components/bl0939/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import sensor, uart
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_VOLTAGE,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_CURRENT,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_ENERGY,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_POWER,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					    UNIT_AMPERE,
 | 
				
			||||||
 | 
					    UNIT_KILOWATT_HOURS,
 | 
				
			||||||
 | 
					    UNIT_VOLT,
 | 
				
			||||||
 | 
					    UNIT_WATT,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEPENDENCIES = ["uart"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_CURRENT_1 = "current_1"
 | 
				
			||||||
 | 
					CONF_CURRENT_2 = "current_2"
 | 
				
			||||||
 | 
					CONF_ACTIVE_POWER_1 = "active_power_1"
 | 
				
			||||||
 | 
					CONF_ACTIVE_POWER_2 = "active_power_2"
 | 
				
			||||||
 | 
					CONF_ENERGY_1 = "energy_1"
 | 
				
			||||||
 | 
					CONF_ENERGY_2 = "energy_2"
 | 
				
			||||||
 | 
					CONF_ENERGY_TOTAL = "energy_total"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bl0939_ns = cg.esphome_ns.namespace("bl0939")
 | 
				
			||||||
 | 
					BL0939 = bl0939_ns.class_("BL0939", cg.PollingComponent, uart.UARTDevice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = (
 | 
				
			||||||
 | 
					    cv.Schema(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cv.GenerateID(): cv.declare_id(BL0939),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_VOLT,
 | 
				
			||||||
 | 
					                accuracy_decimals=1,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_CURRENT_1): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_AMPERE,
 | 
				
			||||||
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_CURRENT,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_CURRENT_2): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_AMPERE,
 | 
				
			||||||
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_CURRENT,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_ACTIVE_POWER_1): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_WATT,
 | 
				
			||||||
 | 
					                accuracy_decimals=0,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_POWER,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_ACTIVE_POWER_2): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_WATT,
 | 
				
			||||||
 | 
					                accuracy_decimals=0,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_POWER,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_ENERGY_1): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_KILOWATT_HOURS,
 | 
				
			||||||
 | 
					                accuracy_decimals=3,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_ENERGY,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_ENERGY_2): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_KILOWATT_HOURS,
 | 
				
			||||||
 | 
					                accuracy_decimals=3,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_ENERGY,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_ENERGY_TOTAL): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_KILOWATT_HOURS,
 | 
				
			||||||
 | 
					                accuracy_decimals=3,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_ENERGY,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .extend(cv.polling_component_schema("60s"))
 | 
				
			||||||
 | 
					    .extend(uart.UART_DEVICE_SCHEMA)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					    await uart.register_uart_device(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_VOLTAGE in config:
 | 
				
			||||||
 | 
					        conf = config[CONF_VOLTAGE]
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(conf)
 | 
				
			||||||
 | 
					        cg.add(var.set_voltage_sensor(sens))
 | 
				
			||||||
 | 
					    if CONF_CURRENT_1 in config:
 | 
				
			||||||
 | 
					        conf = config[CONF_CURRENT_1]
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(conf)
 | 
				
			||||||
 | 
					        cg.add(var.set_current_sensor_1(sens))
 | 
				
			||||||
 | 
					    if CONF_CURRENT_2 in config:
 | 
				
			||||||
 | 
					        conf = config[CONF_CURRENT_2]
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(conf)
 | 
				
			||||||
 | 
					        cg.add(var.set_current_sensor_2(sens))
 | 
				
			||||||
 | 
					    if CONF_ACTIVE_POWER_1 in config:
 | 
				
			||||||
 | 
					        conf = config[CONF_ACTIVE_POWER_1]
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(conf)
 | 
				
			||||||
 | 
					        cg.add(var.set_power_sensor_1(sens))
 | 
				
			||||||
 | 
					    if CONF_ACTIVE_POWER_2 in config:
 | 
				
			||||||
 | 
					        conf = config[CONF_ACTIVE_POWER_2]
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(conf)
 | 
				
			||||||
 | 
					        cg.add(var.set_power_sensor_2(sens))
 | 
				
			||||||
 | 
					    if CONF_ENERGY_1 in config:
 | 
				
			||||||
 | 
					        conf = config[CONF_ENERGY_1]
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(conf)
 | 
				
			||||||
 | 
					        cg.add(var.set_energy_sensor_1(sens))
 | 
				
			||||||
 | 
					    if CONF_ENERGY_2 in config:
 | 
				
			||||||
 | 
					        conf = config[CONF_ENERGY_2]
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(conf)
 | 
				
			||||||
 | 
					        cg.add(var.set_energy_sensor_2(sens))
 | 
				
			||||||
 | 
					    if CONF_ENERGY_TOTAL in config:
 | 
				
			||||||
 | 
					        conf = config[CONF_ENERGY_TOTAL]
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(conf)
 | 
				
			||||||
 | 
					        cg.add(var.set_energy_sensor_sum(sens))
 | 
				
			||||||
@@ -118,16 +118,21 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
 | 
				
			|||||||
        this->set_states_(espbt::ClientState::IDLE);
 | 
					        this->set_states_(espbt::ClientState::IDLE);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this->conn_id = param->open.conn_id;
 | 
					      break;
 | 
				
			||||||
      auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->open.conn_id);
 | 
					    }
 | 
				
			||||||
 | 
					    case ESP_GATTC_CONNECT_EVT: {
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str());
 | 
				
			||||||
 | 
					      this->conn_id = param->connect.conn_id;
 | 
				
			||||||
 | 
					      auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->connect.conn_id);
 | 
				
			||||||
      if (ret) {
 | 
					      if (ret) {
 | 
				
			||||||
        ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%d", ret);
 | 
					        ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    case ESP_GATTC_CFG_MTU_EVT: {
 | 
					    case ESP_GATTC_CFG_MTU_EVT: {
 | 
				
			||||||
      if (param->cfg_mtu.status != ESP_GATT_OK) {
 | 
					      if (param->cfg_mtu.status != ESP_GATT_OK) {
 | 
				
			||||||
        ESP_LOGW(TAG, "cfg_mtu to %s failed, status %d", this->address_str().c_str(), param->cfg_mtu.status);
 | 
					        ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu,
 | 
				
			||||||
 | 
					                 param->cfg_mtu.status);
 | 
				
			||||||
        this->set_states_(espbt::ClientState::IDLE);
 | 
					        this->set_states_(espbt::ClientState::IDLE);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -139,7 +144,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
 | 
				
			|||||||
      if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) {
 | 
					      if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT", this->address_str().c_str());
 | 
					      ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason);
 | 
				
			||||||
      for (auto &svc : this->services_)
 | 
					      for (auto &svc : this->services_)
 | 
				
			||||||
        delete svc;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
					        delete svc;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
				
			||||||
      this->services_.clear();
 | 
					      this->services_.clear();
 | 
				
			||||||
@@ -201,6 +206,32 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
 | 
				
			||||||
 | 
					  switch (event) {
 | 
				
			||||||
 | 
					    // This event is sent by the server when it requests security
 | 
				
			||||||
 | 
					    case ESP_GAP_BLE_SEC_REQ_EVT:
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event);
 | 
				
			||||||
 | 
					      esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    // This event is sent once authentication has completed
 | 
				
			||||||
 | 
					    case ESP_GAP_BLE_AUTH_CMPL_EVT:
 | 
				
			||||||
 | 
					      esp_bd_addr_t bd_addr;
 | 
				
			||||||
 | 
					      memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
 | 
				
			||||||
 | 
					      ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str());
 | 
				
			||||||
 | 
					      if (!param->ble_security.auth_cmpl.success) {
 | 
				
			||||||
 | 
					        ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type,
 | 
				
			||||||
 | 
					                 param->ble_security.auth_cmpl.auth_mode);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    // There are other events we'll want to implement at some point to support things like pass key
 | 
				
			||||||
 | 
					    // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Parse GATT values into a float for a sensor.
 | 
					// Parse GATT values into a float for a sensor.
 | 
				
			||||||
// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
 | 
					// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
 | 
				
			||||||
float BLEClient::parse_char_value(uint8_t *value, uint16_t length) {
 | 
					float BLEClient::parse_char_value(uint8_t *value, uint16_t length) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@
 | 
				
			|||||||
#include <esp_gap_ble_api.h>
 | 
					#include <esp_gap_ble_api.h>
 | 
				
			||||||
#include <esp_gattc_api.h>
 | 
					#include <esp_gattc_api.h>
 | 
				
			||||||
#include <esp_bt_defs.h>
 | 
					#include <esp_bt_defs.h>
 | 
				
			||||||
 | 
					#include <esp_gatt_common_api.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace ble_client {
 | 
					namespace ble_client {
 | 
				
			||||||
@@ -86,6 +87,7 @@ class BLEClient : public espbt::ESPBTClient, public Component {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
					  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
				
			||||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
					                           esp_ble_gattc_cb_param_t *param) override;
 | 
				
			||||||
 | 
					  void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
 | 
				
			||||||
  bool parse_device(const espbt::ESPBTDevice &device) override;
 | 
					  bool parse_device(const espbt::ESPBTDevice &device) override;
 | 
				
			||||||
  void on_scan_end() override {}
 | 
					  void on_scan_end() override {}
 | 
				
			||||||
  void connect() override;
 | 
					  void connect() override;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -81,6 +81,11 @@ static const char *iir_filter_to_str(BME280IIRFilter filter) {
 | 
				
			|||||||
void BME280Component::setup() {
 | 
					void BME280Component::setup() {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "Setting up BME280...");
 | 
					  ESP_LOGCONFIG(TAG, "Setting up BME280...");
 | 
				
			||||||
  uint8_t chip_id = 0;
 | 
					  uint8_t chip_id = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Mark as not failed before initializing. Some devices will turn off sensors to save on batteries
 | 
				
			||||||
 | 
					  // and when they come back on, the COMPONENT_STATE_FAILED bit must be unset on the component.
 | 
				
			||||||
 | 
					  this->component_state_ &= ~COMPONENT_STATE_FAILED;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
 | 
					  if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
 | 
				
			||||||
    this->error_code_ = COMMUNICATION_FAILED;
 | 
					    this->error_code_ = COMMUNICATION_FAILED;
 | 
				
			||||||
    this->mark_failed();
 | 
					    this->mark_failed();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -169,6 +169,14 @@ void BME680BSECComponent::loop() {
 | 
				
			|||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    this->status_clear_warning();
 | 
					    this->status_clear_warning();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Process a single action from the queue. These are primarily sensor state publishes
 | 
				
			||||||
 | 
					  // that in totality take too long to send in a single call.
 | 
				
			||||||
 | 
					  if (this->queue_.size()) {
 | 
				
			||||||
 | 
					    auto action = std::move(this->queue_.front());
 | 
				
			||||||
 | 
					    this->queue_.pop();
 | 
				
			||||||
 | 
					    action();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void BME680BSECComponent::run_() {
 | 
					void BME680BSECComponent::run_() {
 | 
				
			||||||
@@ -306,37 +314,39 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_outputs) {
 | 
					void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_outputs) {
 | 
				
			||||||
  ESP_LOGV(TAG, "Publishing sensor states");
 | 
					  ESP_LOGV(TAG, "Queuing sensor state publish actions");
 | 
				
			||||||
  for (uint8_t i = 0; i < num_outputs; i++) {
 | 
					  for (uint8_t i = 0; i < num_outputs; i++) {
 | 
				
			||||||
 | 
					    float signal = outputs[i].signal;
 | 
				
			||||||
    switch (outputs[i].sensor_id) {
 | 
					    switch (outputs[i].sensor_id) {
 | 
				
			||||||
      case BSEC_OUTPUT_IAQ:
 | 
					      case BSEC_OUTPUT_IAQ:
 | 
				
			||||||
      case BSEC_OUTPUT_STATIC_IAQ:
 | 
					      case BSEC_OUTPUT_STATIC_IAQ: {
 | 
				
			||||||
        uint8_t accuracy;
 | 
					        uint8_t accuracy = outputs[i].accuracy;
 | 
				
			||||||
        accuracy = outputs[i].accuracy;
 | 
					        this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_sensor_, signal); });
 | 
				
			||||||
        this->publish_sensor_state_(this->iaq_sensor_, outputs[i].signal);
 | 
					        this->queue_push_([this, accuracy]() {
 | 
				
			||||||
        this->publish_sensor_state_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[accuracy]);
 | 
					          this->publish_sensor_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[accuracy]);
 | 
				
			||||||
        this->publish_sensor_state_(this->iaq_accuracy_sensor_, accuracy, true);
 | 
					        });
 | 
				
			||||||
 | 
					        this->queue_push_([this, accuracy]() { this->publish_sensor_(this->iaq_accuracy_sensor_, accuracy, true); });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Queue up an opportunity to save state
 | 
					        // Queue up an opportunity to save state
 | 
				
			||||||
        this->defer("save_state", [this, accuracy]() { this->save_state_(accuracy); });
 | 
					        this->queue_push_([this, accuracy]() { this->save_state_(accuracy); });
 | 
				
			||||||
        break;
 | 
					      } break;
 | 
				
			||||||
      case BSEC_OUTPUT_CO2_EQUIVALENT:
 | 
					      case BSEC_OUTPUT_CO2_EQUIVALENT:
 | 
				
			||||||
        this->publish_sensor_state_(this->co2_equivalent_sensor_, outputs[i].signal);
 | 
					        this->queue_push_([this, signal]() { this->publish_sensor_(this->co2_equivalent_sensor_, signal); });
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
 | 
					      case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
 | 
				
			||||||
        this->publish_sensor_state_(this->breath_voc_equivalent_sensor_, outputs[i].signal);
 | 
					        this->queue_push_([this, signal]() { this->publish_sensor_(this->breath_voc_equivalent_sensor_, signal); });
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case BSEC_OUTPUT_RAW_PRESSURE:
 | 
					      case BSEC_OUTPUT_RAW_PRESSURE:
 | 
				
			||||||
        this->publish_sensor_state_(this->pressure_sensor_, outputs[i].signal / 100.0f);
 | 
					        this->queue_push_([this, signal]() { this->publish_sensor_(this->pressure_sensor_, signal / 100.0f); });
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case BSEC_OUTPUT_RAW_GAS:
 | 
					      case BSEC_OUTPUT_RAW_GAS:
 | 
				
			||||||
        this->publish_sensor_state_(this->gas_resistance_sensor_, outputs[i].signal);
 | 
					        this->queue_push_([this, signal]() { this->publish_sensor_(this->gas_resistance_sensor_, signal); });
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
 | 
					      case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
 | 
				
			||||||
        this->publish_sensor_state_(this->temperature_sensor_, outputs[i].signal);
 | 
					        this->queue_push_([this, signal]() { this->publish_sensor_(this->temperature_sensor_, signal); });
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
 | 
					      case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
 | 
				
			||||||
        this->publish_sensor_state_(this->humidity_sensor_, outputs[i].signal);
 | 
					        this->queue_push_([this, signal]() { this->publish_sensor_(this->humidity_sensor_, signal); });
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -352,14 +362,14 @@ int64_t BME680BSECComponent::get_time_ns_() {
 | 
				
			|||||||
  return (time_ms + ((int64_t) this->millis_overflow_counter_ << 32)) * INT64_C(1000000);
 | 
					  return (time_ms + ((int64_t) this->millis_overflow_counter_ << 32)) * INT64_C(1000000);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void BME680BSECComponent::publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only) {
 | 
					void BME680BSECComponent::publish_sensor_(sensor::Sensor *sensor, float value, bool change_only) {
 | 
				
			||||||
  if (!sensor || (change_only && sensor->has_state() && sensor->state == value)) {
 | 
					  if (!sensor || (change_only && sensor->has_state() && sensor->state == value)) {
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  sensor->publish_state(value);
 | 
					  sensor->publish_state(value);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void BME680BSECComponent::publish_sensor_state_(text_sensor::TextSensor *sensor, const std::string &value) {
 | 
					void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) {
 | 
				
			||||||
  if (!sensor || (sensor->has_state() && sensor->state == value)) {
 | 
					  if (!sensor || (sensor->has_state() && sensor->state == value)) {
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -70,12 +70,14 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
 | 
				
			|||||||
  void publish_(const bsec_output_t *outputs, uint8_t num_outputs);
 | 
					  void publish_(const bsec_output_t *outputs, uint8_t num_outputs);
 | 
				
			||||||
  int64_t get_time_ns_();
 | 
					  int64_t get_time_ns_();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void publish_sensor_state_(sensor::Sensor *sensor, float value, bool change_only = false);
 | 
					  void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
 | 
				
			||||||
  void publish_sensor_state_(text_sensor::TextSensor *sensor, const std::string &value);
 | 
					  void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void load_state_();
 | 
					  void load_state_();
 | 
				
			||||||
  void save_state_(uint8_t accuracy);
 | 
					  void save_state_(uint8_t accuracy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void queue_push_(std::function<void()> &&f) { this->queue_.push(std::move(f)); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  struct bme680_dev bme680_;
 | 
					  struct bme680_dev bme680_;
 | 
				
			||||||
  bsec_library_return_t bsec_status_{BSEC_OK};
 | 
					  bsec_library_return_t bsec_status_{BSEC_OK};
 | 
				
			||||||
  int8_t bme680_status_{BME680_OK};
 | 
					  int8_t bme680_status_{BME680_OK};
 | 
				
			||||||
@@ -84,6 +86,8 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
 | 
				
			|||||||
  uint32_t millis_overflow_counter_{0};
 | 
					  uint32_t millis_overflow_counter_{0};
 | 
				
			||||||
  int64_t next_call_ns_{0};
 | 
					  int64_t next_call_ns_{0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  std::queue<std::function<void()>> queue_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ESPPreferenceObject bsec_state_;
 | 
					  ESPPreferenceObject bsec_state_;
 | 
				
			||||||
  uint32_t state_save_interval_ms_{21600000};  // 6 hours - 4 times a day
 | 
					  uint32_t state_save_interval_ms_{21600000};  // 6 hours - 4 times a day
 | 
				
			||||||
  uint32_t last_state_save_ms_ = 0;
 | 
					  uint32_t last_state_save_ms_ = 0;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ IS_PLATFORM_COMPONENT = True
 | 
				
			|||||||
CONF_CAN_ID = "can_id"
 | 
					CONF_CAN_ID = "can_id"
 | 
				
			||||||
CONF_CAN_ID_MASK = "can_id_mask"
 | 
					CONF_CAN_ID_MASK = "can_id_mask"
 | 
				
			||||||
CONF_USE_EXTENDED_ID = "use_extended_id"
 | 
					CONF_USE_EXTENDED_ID = "use_extended_id"
 | 
				
			||||||
 | 
					CONF_REMOTE_TRANSMISSION_REQUEST = "remote_transmission_request"
 | 
				
			||||||
CONF_CANBUS_ID = "canbus_id"
 | 
					CONF_CANBUS_ID = "canbus_id"
 | 
				
			||||||
CONF_BIT_RATE = "bit_rate"
 | 
					CONF_BIT_RATE = "bit_rate"
 | 
				
			||||||
CONF_ON_FRAME = "on_frame"
 | 
					CONF_ON_FRAME = "on_frame"
 | 
				
			||||||
@@ -77,6 +78,7 @@ CANBUS_SCHEMA = cv.Schema(
 | 
				
			|||||||
                    min=0, max=0x1FFFFFFF
 | 
					                    min=0, max=0x1FFFFFFF
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
 | 
					                cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
 | 
				
			||||||
 | 
					                cv.Optional(CONF_REMOTE_TRANSMISSION_REQUEST): cv.boolean,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            validate_id,
 | 
					            validate_id,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
@@ -99,10 +101,20 @@ async def setup_canbus_core_(var, config):
 | 
				
			|||||||
        trigger = cg.new_Pvariable(
 | 
					        trigger = cg.new_Pvariable(
 | 
				
			||||||
            conf[CONF_TRIGGER_ID], var, can_id, can_id_mask, ext_id
 | 
					            conf[CONF_TRIGGER_ID], var, can_id, can_id_mask, ext_id
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        if CONF_REMOTE_TRANSMISSION_REQUEST in conf:
 | 
				
			||||||
 | 
					            cg.add(
 | 
				
			||||||
 | 
					                trigger.set_remote_transmission_request(
 | 
				
			||||||
 | 
					                    conf[CONF_REMOTE_TRANSMISSION_REQUEST]
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        await cg.register_component(trigger, conf)
 | 
					        await cg.register_component(trigger, conf)
 | 
				
			||||||
        await automation.build_automation(
 | 
					        await automation.build_automation(
 | 
				
			||||||
            trigger,
 | 
					            trigger,
 | 
				
			||||||
            [(cg.std_vector.template(cg.uint8), "x"), (cg.uint32, "can_id")],
 | 
					            [
 | 
				
			||||||
 | 
					                (cg.std_vector.template(cg.uint8), "x"),
 | 
				
			||||||
 | 
					                (cg.uint32, "can_id"),
 | 
				
			||||||
 | 
					                (cg.bool_, "remote_transmission_request"),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
            conf,
 | 
					            conf,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -122,6 +134,7 @@ async def register_canbus(var, config):
 | 
				
			|||||||
            cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent),
 | 
					            cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent),
 | 
				
			||||||
            cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
 | 
					            cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
 | 
				
			||||||
            cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
 | 
					            cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_REMOTE_TRANSMISSION_REQUEST, default=False): cv.boolean,
 | 
				
			||||||
            cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
 | 
					            cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        validate_id,
 | 
					        validate_id,
 | 
				
			||||||
@@ -140,6 +153,11 @@ async def canbus_action_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
    cg.add(var.set_use_extended_id(use_extended_id))
 | 
					    cg.add(var.set_use_extended_id(use_extended_id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    remote_transmission_request = await cg.templatable(
 | 
				
			||||||
 | 
					        config[CONF_REMOTE_TRANSMISSION_REQUEST], args, bool
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    cg.add(var.set_remote_transmission_request(remote_transmission_request))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data = config[CONF_DATA]
 | 
					    data = config[CONF_DATA]
 | 
				
			||||||
    if isinstance(data, bytes):
 | 
					    if isinstance(data, bytes):
 | 
				
			||||||
        data = [int(x) for x in data]
 | 
					        data = [int(x) for x in data]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,20 +22,22 @@ void Canbus::dump_config() {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Canbus::send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) {
 | 
					void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
 | 
				
			||||||
 | 
					                       const std::vector<uint8_t> &data) {
 | 
				
			||||||
  struct CanFrame can_message;
 | 
					  struct CanFrame can_message;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  uint8_t size = static_cast<uint8_t>(data.size());
 | 
					  uint8_t size = static_cast<uint8_t>(data.size());
 | 
				
			||||||
  if (use_extended_id) {
 | 
					  if (use_extended_id) {
 | 
				
			||||||
    ESP_LOGD(TAG, "send extended id=0x%08x size=%d", can_id, size);
 | 
					    ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    ESP_LOGD(TAG, "send extended id=0x%03x size=%d", can_id, size);
 | 
					    ESP_LOGD(TAG, "send extended id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (size > CAN_MAX_DATA_LENGTH)
 | 
					  if (size > CAN_MAX_DATA_LENGTH)
 | 
				
			||||||
    size = CAN_MAX_DATA_LENGTH;
 | 
					    size = CAN_MAX_DATA_LENGTH;
 | 
				
			||||||
  can_message.can_data_length_code = size;
 | 
					  can_message.can_data_length_code = size;
 | 
				
			||||||
  can_message.can_id = can_id;
 | 
					  can_message.can_id = can_id;
 | 
				
			||||||
  can_message.use_extended_id = use_extended_id;
 | 
					  can_message.use_extended_id = use_extended_id;
 | 
				
			||||||
 | 
					  can_message.remote_transmission_request = remote_transmission_request;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (int i = 0; i < size; i++) {
 | 
					  for (int i = 0; i < size; i++) {
 | 
				
			||||||
    can_message.data[i] = data[i];
 | 
					    can_message.data[i] = data[i];
 | 
				
			||||||
@@ -79,8 +81,10 @@ void Canbus::loop() {
 | 
				
			|||||||
    // fire all triggers
 | 
					    // fire all triggers
 | 
				
			||||||
    for (auto *trigger : this->triggers_) {
 | 
					    for (auto *trigger : this->triggers_) {
 | 
				
			||||||
      if ((trigger->can_id_ == (can_message.can_id & trigger->can_id_mask_)) &&
 | 
					      if ((trigger->can_id_ == (can_message.can_id & trigger->can_id_mask_)) &&
 | 
				
			||||||
          (trigger->use_extended_id_ == can_message.use_extended_id)) {
 | 
					          (trigger->use_extended_id_ == can_message.use_extended_id) &&
 | 
				
			||||||
        trigger->trigger(data, can_message.can_id);
 | 
					          (!trigger->remote_transmission_request_.has_value() ||
 | 
				
			||||||
 | 
					           trigger->remote_transmission_request_.value() == can_message.remote_transmission_request)) {
 | 
				
			||||||
 | 
					        trigger->trigger(data, can_message.can_id, can_message.remote_transmission_request);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,7 +62,12 @@ class Canbus : public Component {
 | 
				
			|||||||
  float get_setup_priority() const override { return setup_priority::HARDWARE; }
 | 
					  float get_setup_priority() const override { return setup_priority::HARDWARE; }
 | 
				
			||||||
  void loop() override;
 | 
					  void loop() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data);
 | 
					  void send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
 | 
				
			||||||
 | 
					                 const std::vector<uint8_t> &data);
 | 
				
			||||||
 | 
					  void send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) {
 | 
				
			||||||
 | 
					    // for backwards compatibility only
 | 
				
			||||||
 | 
					    this->send_data(can_id, use_extended_id, false, data);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
 | 
					  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_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 set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; }
 | 
				
			||||||
@@ -96,33 +101,43 @@ template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public P
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
 | 
					  void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_remote_transmission_request(bool remote_transmission_request) {
 | 
				
			||||||
 | 
					    this->remote_transmission_request_ = remote_transmission_request;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void play(Ts... x) override {
 | 
					  void play(Ts... x) override {
 | 
				
			||||||
    auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
 | 
					    auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
 | 
				
			||||||
    auto use_extended_id =
 | 
					    auto use_extended_id =
 | 
				
			||||||
        this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_;
 | 
					        this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_;
 | 
				
			||||||
    if (this->static_) {
 | 
					    if (this->static_) {
 | 
				
			||||||
      this->parent_->send_data(can_id, use_extended_id, this->data_static_);
 | 
					      this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, this->data_static_);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      auto val = this->data_func_(x...);
 | 
					      auto val = this->data_func_(x...);
 | 
				
			||||||
      this->parent_->send_data(can_id, use_extended_id, val);
 | 
					      this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, val);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  optional<uint32_t> can_id_{};
 | 
					  optional<uint32_t> can_id_{};
 | 
				
			||||||
  optional<bool> use_extended_id_{};
 | 
					  optional<bool> use_extended_id_{};
 | 
				
			||||||
 | 
					  bool remote_transmission_request_{false};
 | 
				
			||||||
  bool static_{false};
 | 
					  bool static_{false};
 | 
				
			||||||
  std::function<std::vector<uint8_t>(Ts...)> data_func_{};
 | 
					  std::function<std::vector<uint8_t>(Ts...)> data_func_{};
 | 
				
			||||||
  std::vector<uint8_t> data_static_{};
 | 
					  std::vector<uint8_t> data_static_{};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CanbusTrigger : public Trigger<std::vector<uint8_t>, uint32_t>, public Component {
 | 
					class CanbusTrigger : public Trigger<std::vector<uint8_t>, uint32_t, bool>, public Component {
 | 
				
			||||||
  friend class Canbus;
 | 
					  friend class Canbus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  explicit CanbusTrigger(Canbus *parent, const std::uint32_t can_id, const std::uint32_t can_id_mask,
 | 
					  explicit CanbusTrigger(Canbus *parent, const std::uint32_t can_id, const std::uint32_t can_id_mask,
 | 
				
			||||||
                         const bool use_extended_id)
 | 
					                         const bool use_extended_id)
 | 
				
			||||||
      : parent_(parent), can_id_(can_id), can_id_mask_(can_id_mask), use_extended_id_(use_extended_id){};
 | 
					      : parent_(parent), can_id_(can_id), can_id_mask_(can_id_mask), use_extended_id_(use_extended_id){};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_remote_transmission_request(bool remote_transmission_request) {
 | 
				
			||||||
 | 
					    this->remote_transmission_request_ = remote_transmission_request;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void setup() override { this->parent_->add_trigger(this); }
 | 
					  void setup() override { this->parent_->add_trigger(this); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
@@ -130,6 +145,7 @@ class CanbusTrigger : public Trigger<std::vector<uint8_t>, uint32_t>, public Com
 | 
				
			|||||||
  uint32_t can_id_;
 | 
					  uint32_t can_id_;
 | 
				
			||||||
  uint32_t can_id_mask_;
 | 
					  uint32_t can_id_mask_;
 | 
				
			||||||
  bool use_extended_id_;
 | 
					  bool use_extended_id_;
 | 
				
			||||||
 | 
					  optional<bool> remote_transmission_request_{};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace canbus
 | 
					}  // namespace canbus
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -76,7 +76,7 @@ enum ClimateSwingMode : uint8_t {
 | 
				
			|||||||
  CLIMATE_SWING_HORIZONTAL = 3,
 | 
					  CLIMATE_SWING_HORIZONTAL = 3,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Enum for all modes a climate swing can be in
 | 
					/// Enum for all preset modes
 | 
				
			||||||
enum ClimatePreset : uint8_t {
 | 
					enum ClimatePreset : uint8_t {
 | 
				
			||||||
  /// No preset is active
 | 
					  /// No preset is active
 | 
				
			||||||
  CLIMATE_PRESET_NONE = 0,
 | 
					  CLIMATE_PRESET_NONE = 0,
 | 
				
			||||||
@@ -108,7 +108,7 @@ const LogString *climate_fan_mode_to_string(ClimateFanMode mode);
 | 
				
			|||||||
/// Convert the given ClimateSwingMode to a human-readable string.
 | 
					/// Convert the given ClimateSwingMode to a human-readable string.
 | 
				
			||||||
const LogString *climate_swing_mode_to_string(ClimateSwingMode mode);
 | 
					const LogString *climate_swing_mode_to_string(ClimateSwingMode mode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Convert the given ClimateSwingMode to a human-readable string.
 | 
					/// Convert the given PresetMode to a human-readable string.
 | 
				
			||||||
const LogString *climate_preset_to_string(ClimatePreset preset);
 | 
					const LogString *climate_preset_to_string(ClimatePreset preset);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace climate
 | 
					}  // namespace climate
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ namespace copy {
 | 
				
			|||||||
static const char *const TAG = "copy.select";
 | 
					static const char *const TAG = "copy.select";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void CopySelect::setup() {
 | 
					void CopySelect::setup() {
 | 
				
			||||||
  source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); });
 | 
					  source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(value); });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  traits.set_options(source_->traits.get_options());
 | 
					  traits.set_options(source_->traits.get_options());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -142,7 +142,6 @@ void IRAM_ATTR ESPOneWire::select(uint64_t address) {
 | 
				
			|||||||
void IRAM_ATTR ESPOneWire::reset_search() {
 | 
					void IRAM_ATTR ESPOneWire::reset_search() {
 | 
				
			||||||
  this->last_discrepancy_ = 0;
 | 
					  this->last_discrepancy_ = 0;
 | 
				
			||||||
  this->last_device_flag_ = false;
 | 
					  this->last_device_flag_ = false;
 | 
				
			||||||
  this->last_family_discrepancy_ = 0;
 | 
					 | 
				
			||||||
  this->rom_number_ = 0;
 | 
					  this->rom_number_ = 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
uint64_t IRAM_ATTR ESPOneWire::search() {
 | 
					uint64_t IRAM_ATTR ESPOneWire::search() {
 | 
				
			||||||
@@ -195,9 +194,6 @@ uint64_t IRAM_ATTR ESPOneWire::search() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (!branch) {
 | 
					        if (!branch) {
 | 
				
			||||||
          last_zero = id_bit_number;
 | 
					          last_zero = id_bit_number;
 | 
				
			||||||
          if (last_zero < 9) {
 | 
					 | 
				
			||||||
            this->last_discrepancy_ = last_zero;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,7 +60,6 @@ class ESPOneWire {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  ISRInternalGPIOPin pin_;
 | 
					  ISRInternalGPIOPin pin_;
 | 
				
			||||||
  uint8_t last_discrepancy_{0};
 | 
					  uint8_t last_discrepancy_{0};
 | 
				
			||||||
  uint8_t last_family_discrepancy_{0};
 | 
					 | 
				
			||||||
  bool last_device_flag_{false};
 | 
					  bool last_device_flag_{false};
 | 
				
			||||||
  uint64_t rom_number_{0};
 | 
					  uint64_t rom_number_{0};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -98,6 +98,8 @@ CELL_VOLTAGE_SCHEMA = sensor.sensor_schema(
 | 
				
			|||||||
    unit_of_measurement=UNIT_VOLT,
 | 
					    unit_of_measurement=UNIT_VOLT,
 | 
				
			||||||
    device_class=DEVICE_CLASS_VOLTAGE,
 | 
					    device_class=DEVICE_CLASS_VOLTAGE,
 | 
				
			||||||
    state_class=STATE_CLASS_MEASUREMENT,
 | 
					    state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					    icon=ICON_FLASH,
 | 
				
			||||||
 | 
					    accuracy_decimals=3,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = cv.All(
 | 
					CONFIG_SCHEMA = cv.All(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -64,7 +64,10 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
 | 
				
			|||||||
        config = {
 | 
					        config = {
 | 
				
			||||||
            "substitutions": {"name": name},
 | 
					            "substitutions": {"name": name},
 | 
				
			||||||
            "packages": {project_name: import_url},
 | 
					            "packages": {project_name: import_url},
 | 
				
			||||||
            "esphome": {"name_add_mac_suffix": False},
 | 
					            "esphome": {
 | 
				
			||||||
 | 
					                "name": "${name}",
 | 
				
			||||||
 | 
					                "name_add_mac_suffix": False,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        p.write_text(
 | 
					        p.write_text(
 | 
				
			||||||
            dump(config) + WIFI_CONFIG,
 | 
					            dump(config) + WIFI_CONFIG,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,18 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					from esphome.components import time
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome import pins, automation
 | 
					from esphome import pins, automation
 | 
				
			||||||
from esphome.const import (
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_HOUR,
 | 
				
			||||||
    CONF_ID,
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_MINUTE,
 | 
				
			||||||
    CONF_MODE,
 | 
					    CONF_MODE,
 | 
				
			||||||
    CONF_NUMBER,
 | 
					    CONF_NUMBER,
 | 
				
			||||||
    CONF_PINS,
 | 
					    CONF_PINS,
 | 
				
			||||||
    CONF_RUN_DURATION,
 | 
					    CONF_RUN_DURATION,
 | 
				
			||||||
 | 
					    CONF_SECOND,
 | 
				
			||||||
    CONF_SLEEP_DURATION,
 | 
					    CONF_SLEEP_DURATION,
 | 
				
			||||||
 | 
					    CONF_TIME_ID,
 | 
				
			||||||
    CONF_WAKEUP_PIN,
 | 
					    CONF_WAKEUP_PIN,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,6 +20,7 @@ from esphome.components.esp32 import get_esp32_variant
 | 
				
			|||||||
from esphome.components.esp32.const import (
 | 
					from esphome.components.esp32.const import (
 | 
				
			||||||
    VARIANT_ESP32,
 | 
					    VARIANT_ESP32,
 | 
				
			||||||
    VARIANT_ESP32C3,
 | 
					    VARIANT_ESP32C3,
 | 
				
			||||||
 | 
					    VARIANT_ESP32S2,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WAKEUP_PINS = {
 | 
					WAKEUP_PINS = {
 | 
				
			||||||
@@ -39,6 +45,30 @@ WAKEUP_PINS = {
 | 
				
			|||||||
        39,
 | 
					        39,
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5],
 | 
					    VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5],
 | 
				
			||||||
 | 
					    VARIANT_ESP32S2: [
 | 
				
			||||||
 | 
					        0,
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        2,
 | 
				
			||||||
 | 
					        3,
 | 
				
			||||||
 | 
					        4,
 | 
				
			||||||
 | 
					        5,
 | 
				
			||||||
 | 
					        6,
 | 
				
			||||||
 | 
					        7,
 | 
				
			||||||
 | 
					        8,
 | 
				
			||||||
 | 
					        9,
 | 
				
			||||||
 | 
					        10,
 | 
				
			||||||
 | 
					        11,
 | 
				
			||||||
 | 
					        12,
 | 
				
			||||||
 | 
					        13,
 | 
				
			||||||
 | 
					        14,
 | 
				
			||||||
 | 
					        15,
 | 
				
			||||||
 | 
					        16,
 | 
				
			||||||
 | 
					        17,
 | 
				
			||||||
 | 
					        18,
 | 
				
			||||||
 | 
					        19,
 | 
				
			||||||
 | 
					        20,
 | 
				
			||||||
 | 
					        21,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -63,7 +93,14 @@ deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep")
 | 
				
			|||||||
DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component)
 | 
					DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component)
 | 
				
			||||||
EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action)
 | 
					EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action)
 | 
				
			||||||
PreventDeepSleepAction = deep_sleep_ns.class_(
 | 
					PreventDeepSleepAction = deep_sleep_ns.class_(
 | 
				
			||||||
    "PreventDeepSleepAction", automation.Action
 | 
					    "PreventDeepSleepAction",
 | 
				
			||||||
 | 
					    automation.Action,
 | 
				
			||||||
 | 
					    cg.Parented.template(DeepSleepComponent),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					AllowDeepSleepAction = deep_sleep_ns.class_(
 | 
				
			||||||
 | 
					    "AllowDeepSleepAction",
 | 
				
			||||||
 | 
					    automation.Action,
 | 
				
			||||||
 | 
					    cg.Parented.template(DeepSleepComponent),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WakeupPinMode = deep_sleep_ns.enum("WakeupPinMode")
 | 
					WakeupPinMode = deep_sleep_ns.enum("WakeupPinMode")
 | 
				
			||||||
@@ -87,6 +124,7 @@ CONF_TOUCH_WAKEUP = "touch_wakeup"
 | 
				
			|||||||
CONF_DEFAULT = "default"
 | 
					CONF_DEFAULT = "default"
 | 
				
			||||||
CONF_GPIO_WAKEUP_REASON = "gpio_wakeup_reason"
 | 
					CONF_GPIO_WAKEUP_REASON = "gpio_wakeup_reason"
 | 
				
			||||||
CONF_TOUCH_WAKEUP_REASON = "touch_wakeup_reason"
 | 
					CONF_TOUCH_WAKEUP_REASON = "touch_wakeup_reason"
 | 
				
			||||||
 | 
					CONF_UNTIL = "until"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WAKEUP_CAUSES_SCHEMA = cv.Schema(
 | 
					WAKEUP_CAUSES_SCHEMA = cv.Schema(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -177,20 +215,30 @@ async def to_code(config):
 | 
				
			|||||||
    cg.add_define("USE_DEEP_SLEEP")
 | 
					    cg.add_define("USE_DEEP_SLEEP")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEEP_SLEEP_ENTER_SCHEMA = automation.maybe_simple_id(
 | 
					DEEP_SLEEP_ACTION_SCHEMA = cv.Schema(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        cv.GenerateID(): cv.use_id(DeepSleepComponent),
 | 
					        cv.GenerateID(): cv.use_id(DeepSleepComponent),
 | 
				
			||||||
        cv.Optional(CONF_SLEEP_DURATION): cv.templatable(
 | 
					 | 
				
			||||||
            cv.positive_time_period_milliseconds
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEEP_SLEEP_ENTER_SCHEMA = cv.All(
 | 
				
			||||||
DEEP_SLEEP_PREVENT_SCHEMA = automation.maybe_simple_id(
 | 
					    automation.maybe_simple_id(
 | 
				
			||||||
    {
 | 
					        DEEP_SLEEP_ACTION_SCHEMA.extend(
 | 
				
			||||||
        cv.GenerateID(): cv.use_id(DeepSleepComponent),
 | 
					            cv.Schema(
 | 
				
			||||||
    }
 | 
					                {
 | 
				
			||||||
 | 
					                    cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable(
 | 
				
			||||||
 | 
					                        cv.positive_time_period_milliseconds
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    # Only on ESP32 due to how long the RTC on ESP8266 can stay asleep
 | 
				
			||||||
 | 
					                    cv.Exclusive(CONF_UNTIL, "time"): cv.All(
 | 
				
			||||||
 | 
					                        cv.only_on_esp32, cv.time_of_day
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    cv.has_none_or_all_keys(CONF_UNTIL, CONF_TIME_ID),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -203,12 +251,28 @@ async def deep_sleep_enter_to_code(config, action_id, template_arg, args):
 | 
				
			|||||||
    if CONF_SLEEP_DURATION in config:
 | 
					    if CONF_SLEEP_DURATION in config:
 | 
				
			||||||
        template_ = await cg.templatable(config[CONF_SLEEP_DURATION], args, cg.int32)
 | 
					        template_ = await cg.templatable(config[CONF_SLEEP_DURATION], args, cg.int32)
 | 
				
			||||||
        cg.add(var.set_sleep_duration(template_))
 | 
					        cg.add(var.set_sleep_duration(template_))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_UNTIL in config:
 | 
				
			||||||
 | 
					        until = config[CONF_UNTIL]
 | 
				
			||||||
 | 
					        cg.add(var.set_until(until[CONF_HOUR], until[CONF_MINUTE], until[CONF_SECOND]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        time_ = await cg.get_variable(config[CONF_TIME_ID])
 | 
				
			||||||
 | 
					        cg.add(var.set_time(time_))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return var
 | 
					    return var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@automation.register_action(
 | 
					@automation.register_action(
 | 
				
			||||||
    "deep_sleep.prevent", PreventDeepSleepAction, DEEP_SLEEP_PREVENT_SCHEMA
 | 
					    "deep_sleep.prevent",
 | 
				
			||||||
 | 
					    PreventDeepSleepAction,
 | 
				
			||||||
 | 
					    automation.maybe_simple_id(DEEP_SLEEP_ACTION_SCHEMA),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
async def deep_sleep_prevent_to_code(config, action_id, template_arg, args):
 | 
					@automation.register_action(
 | 
				
			||||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
					    "deep_sleep.allow",
 | 
				
			||||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
					    AllowDeepSleepAction,
 | 
				
			||||||
 | 
					    automation.maybe_simple_id(DEEP_SLEEP_ACTION_SCHEMA),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def deep_sleep_action_to_code(config, action_id, template_arg, args):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(action_id, template_arg)
 | 
				
			||||||
 | 
					    await cg.register_parented(var, config[CONF_ID])
 | 
				
			||||||
 | 
					    return var
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
#include "deep_sleep_component.h"
 | 
					#include "deep_sleep_component.h"
 | 
				
			||||||
#include "esphome/core/log.h"
 | 
					#include <cinttypes>
 | 
				
			||||||
#include "esphome/core/application.h"
 | 
					#include "esphome/core/application.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ESP8266
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
#include <Esp.h>
 | 
					#include <Esp.h>
 | 
				
			||||||
@@ -20,6 +21,7 @@ optional<uint32_t> DeepSleepComponent::get_run_duration_() const {
 | 
				
			|||||||
    switch (wakeup_cause) {
 | 
					    switch (wakeup_cause) {
 | 
				
			||||||
      case ESP_SLEEP_WAKEUP_EXT0:
 | 
					      case ESP_SLEEP_WAKEUP_EXT0:
 | 
				
			||||||
      case ESP_SLEEP_WAKEUP_EXT1:
 | 
					      case ESP_SLEEP_WAKEUP_EXT1:
 | 
				
			||||||
 | 
					      case ESP_SLEEP_WAKEUP_GPIO:
 | 
				
			||||||
        return this->wakeup_cause_to_run_duration_->gpio_cause;
 | 
					        return this->wakeup_cause_to_run_duration_->gpio_cause;
 | 
				
			||||||
      case ESP_SLEEP_WAKEUP_TOUCHPAD:
 | 
					      case ESP_SLEEP_WAKEUP_TOUCHPAD:
 | 
				
			||||||
        return this->wakeup_cause_to_run_duration_->touch_cause;
 | 
					        return this->wakeup_cause_to_run_duration_->touch_cause;
 | 
				
			||||||
@@ -71,16 +73,27 @@ float DeepSleepComponent::get_loop_priority() const {
 | 
				
			|||||||
  return -100.0f;  // run after everything else is ready
 | 
					  return -100.0f;  // run after everything else is ready
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
void DeepSleepComponent::set_sleep_duration(uint32_t time_ms) { this->sleep_duration_ = uint64_t(time_ms) * 1000; }
 | 
					void DeepSleepComponent::set_sleep_duration(uint32_t time_ms) { this->sleep_duration_ = uint64_t(time_ms) * 1000; }
 | 
				
			||||||
#ifdef USE_ESP32
 | 
					#if defined(USE_ESP32)
 | 
				
			||||||
void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
 | 
					void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
 | 
				
			||||||
  this->wakeup_pin_mode_ = wakeup_pin_mode;
 | 
					  this->wakeup_pin_mode_ = wakeup_pin_mode;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if defined(USE_ESP32)
 | 
				
			||||||
 | 
					#if !defined(USE_ESP32_VARIANT_ESP32C3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
 | 
					void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
 | 
					void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) {
 | 
					void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) {
 | 
				
			||||||
  wakeup_cause_to_run_duration_ = wakeup_cause_to_run_duration;
 | 
					  wakeup_cause_to_run_duration_ = wakeup_cause_to_run_duration;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; }
 | 
					void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; }
 | 
				
			||||||
void DeepSleepComponent::begin_sleep(bool manual) {
 | 
					void DeepSleepComponent::begin_sleep(bool manual) {
 | 
				
			||||||
  if (this->prevent_ && !manual) {
 | 
					  if (this->prevent_ && !manual) {
 | 
				
			||||||
@@ -101,10 +114,13 @@ void DeepSleepComponent::begin_sleep(bool manual) {
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ESP_LOGI(TAG, "Beginning Deep Sleep");
 | 
					  ESP_LOGI(TAG, "Beginning Deep Sleep");
 | 
				
			||||||
 | 
					  if (this->sleep_duration_.has_value())
 | 
				
			||||||
 | 
					    ESP_LOGI(TAG, "Sleeping for %" PRId64 "us", *this->sleep_duration_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  App.run_safe_shutdown_hooks();
 | 
					  App.run_safe_shutdown_hooks();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
 | 
					#if defined(USE_ESP32)
 | 
				
			||||||
 | 
					#if !defined(USE_ESP32_VARIANT_ESP32C3)
 | 
				
			||||||
  if (this->sleep_duration_.has_value())
 | 
					  if (this->sleep_duration_.has_value())
 | 
				
			||||||
    esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
 | 
					    esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
 | 
				
			||||||
  if (this->wakeup_pin_ != nullptr) {
 | 
					  if (this->wakeup_pin_ != nullptr) {
 | 
				
			||||||
@@ -122,10 +138,7 @@ void DeepSleepComponent::begin_sleep(bool manual) {
 | 
				
			|||||||
    esp_sleep_enable_touchpad_wakeup();
 | 
					    esp_sleep_enable_touchpad_wakeup();
 | 
				
			||||||
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
 | 
					    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  esp_deep_sleep_start();
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef USE_ESP32_VARIANT_ESP32C3
 | 
					#ifdef USE_ESP32_VARIANT_ESP32C3
 | 
				
			||||||
  if (this->sleep_duration_.has_value())
 | 
					  if (this->sleep_duration_.has_value())
 | 
				
			||||||
    esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
 | 
					    esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
 | 
				
			||||||
@@ -134,9 +147,12 @@ void DeepSleepComponent::begin_sleep(bool manual) {
 | 
				
			|||||||
    if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
 | 
					    if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
 | 
				
			||||||
      level = !level;
 | 
					      level = !level;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    esp_deep_sleep_enable_gpio_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level);
 | 
					    esp_deep_sleep_enable_gpio_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()),
 | 
				
			||||||
 | 
					                                      static_cast<esp_deepsleep_gpio_wake_up_mode_t>(level));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					  esp_deep_sleep_start();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ESP8266
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
  ESP.deepSleep(*this->sleep_duration_);  // NOLINT(readability-static-accessed-through-instance)
 | 
					  ESP.deepSleep(*this->sleep_duration_);  // NOLINT(readability-static-accessed-through-instance)
 | 
				
			||||||
@@ -144,6 +160,7 @@ void DeepSleepComponent::begin_sleep(bool manual) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
float DeepSleepComponent::get_setup_priority() const { return setup_priority::LATE; }
 | 
					float DeepSleepComponent::get_setup_priority() const { return setup_priority::LATE; }
 | 
				
			||||||
void DeepSleepComponent::prevent_deep_sleep() { this->prevent_ = true; }
 | 
					void DeepSleepComponent::prevent_deep_sleep() { this->prevent_ = true; }
 | 
				
			||||||
 | 
					void DeepSleepComponent::allow_deep_sleep() { this->prevent_ = false; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace deep_sleep
 | 
					}  // namespace deep_sleep
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,10 @@
 | 
				
			|||||||
#include <esp_sleep.h>
 | 
					#include <esp_sleep.h>
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_TIME
 | 
				
			||||||
 | 
					#include "esphome/components/time/real_time_clock.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace deep_sleep {
 | 
					namespace deep_sleep {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -66,17 +70,19 @@ class DeepSleepComponent : public Component {
 | 
				
			|||||||
  void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode);
 | 
					  void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
 | 
					#if defined(USE_ESP32)
 | 
				
			||||||
 | 
					#if !defined(USE_ESP32_VARIANT_ESP32C3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void set_ext1_wakeup(Ext1Wakeup ext1_wakeup);
 | 
					  void set_ext1_wakeup(Ext1Wakeup ext1_wakeup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void set_touch_wakeup(bool touch_wakeup);
 | 
					  void set_touch_wakeup(bool touch_wakeup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
  // Set the duration in ms for how long the code should run before entering
 | 
					  // Set the duration in ms for how long the code should run before entering
 | 
				
			||||||
  // deep sleep mode, according to the cause the ESP32 has woken.
 | 
					  // deep sleep mode, according to the cause the ESP32 has woken.
 | 
				
			||||||
  void set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration);
 | 
					  void set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration);
 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Set a duration in ms for how long the code should run before entering deep sleep mode.
 | 
					  /// Set a duration in ms for how long the code should run before entering deep sleep mode.
 | 
				
			||||||
  void set_run_duration(uint32_t time_ms);
 | 
					  void set_run_duration(uint32_t time_ms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -90,6 +96,7 @@ class DeepSleepComponent : public Component {
 | 
				
			|||||||
  void begin_sleep(bool manual = false);
 | 
					  void begin_sleep(bool manual = false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void prevent_deep_sleep();
 | 
					  void prevent_deep_sleep();
 | 
				
			||||||
 | 
					  void allow_deep_sleep();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  // Returns nullopt if no run duration is set. Otherwise, returns the run
 | 
					  // Returns nullopt if no run duration is set. Otherwise, returns the run
 | 
				
			||||||
@@ -116,25 +123,81 @@ template<typename... Ts> class EnterDeepSleepAction : public Action<Ts...> {
 | 
				
			|||||||
  EnterDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {}
 | 
					  EnterDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {}
 | 
				
			||||||
  TEMPLATABLE_VALUE(uint32_t, sleep_duration);
 | 
					  TEMPLATABLE_VALUE(uint32_t, sleep_duration);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_TIME
 | 
				
			||||||
 | 
					  void set_until(uint8_t hour, uint8_t minute, uint8_t second) {
 | 
				
			||||||
 | 
					    this->hour_ = hour;
 | 
				
			||||||
 | 
					    this->minute_ = minute;
 | 
				
			||||||
 | 
					    this->second_ = second;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_time(time::RealTimeClock *time) { this->time_ = time; }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void play(Ts... x) override {
 | 
					  void play(Ts... x) override {
 | 
				
			||||||
    if (this->sleep_duration_.has_value()) {
 | 
					    if (this->sleep_duration_.has_value()) {
 | 
				
			||||||
      this->deep_sleep_->set_sleep_duration(this->sleep_duration_.value(x...));
 | 
					      this->deep_sleep_->set_sleep_duration(this->sleep_duration_.value(x...));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					#ifdef USE_TIME
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this->hour_.has_value()) {
 | 
				
			||||||
 | 
					      auto time = this->time_->now();
 | 
				
			||||||
 | 
					      const uint32_t timestamp_now = time.timestamp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      bool after_time = false;
 | 
				
			||||||
 | 
					      if (time.hour > this->hour_) {
 | 
				
			||||||
 | 
					        after_time = true;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        if (time.hour == this->hour_) {
 | 
				
			||||||
 | 
					          if (time.minute > this->minute_) {
 | 
				
			||||||
 | 
					            after_time = true;
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            if (time.minute == this->minute_) {
 | 
				
			||||||
 | 
					              if (time.second > this->second_) {
 | 
				
			||||||
 | 
					                after_time = true;
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      time.hour = *this->hour_;
 | 
				
			||||||
 | 
					      time.minute = *this->minute_;
 | 
				
			||||||
 | 
					      time.second = *this->second_;
 | 
				
			||||||
 | 
					      time.recalc_timestamp_utc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      time_t timestamp = time.timestamp;  // timestamp in local time zone
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (after_time)
 | 
				
			||||||
 | 
					        timestamp += 60 * 60 * 24;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      int32_t offset = time::ESPTime::timezone_offset();
 | 
				
			||||||
 | 
					      timestamp -= offset;  // Change timestamp to utc
 | 
				
			||||||
 | 
					      const uint32_t ms_left = (timestamp - timestamp_now) * 1000;
 | 
				
			||||||
 | 
					      this->deep_sleep_->set_sleep_duration(ms_left);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
    this->deep_sleep_->begin_sleep(true);
 | 
					    this->deep_sleep_->begin_sleep(true);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  DeepSleepComponent *deep_sleep_;
 | 
					  DeepSleepComponent *deep_sleep_;
 | 
				
			||||||
 | 
					#ifdef USE_TIME
 | 
				
			||||||
 | 
					  optional<uint8_t> hour_;
 | 
				
			||||||
 | 
					  optional<uint8_t> minute_;
 | 
				
			||||||
 | 
					  optional<uint8_t> second_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  time::RealTimeClock *time_;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template<typename... Ts> class PreventDeepSleepAction : public Action<Ts...> {
 | 
					template<typename... Ts> class PreventDeepSleepAction : public Action<Ts...>, public Parented<DeepSleepComponent> {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  PreventDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {}
 | 
					  void play(Ts... x) override { this->parent_->prevent_deep_sleep(); }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void play(Ts... x) override { this->deep_sleep_->prevent_deep_sleep(); }
 | 
					template<typename... Ts> class AllowDeepSleepAction : public Action<Ts...>, public Parented<DeepSleepComponent> {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 protected:
 | 
					  void play(Ts... x) override { this->parent_->allow_deep_sleep(); }
 | 
				
			||||||
  DeepSleepComponent *deep_sleep_;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace deep_sleep
 | 
					}  // namespace deep_sleep
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								esphome/components/delonghi/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/delonghi/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					CODEOWNERS = ["@grob6000"]
 | 
				
			||||||
							
								
								
									
										20
									
								
								esphome/components/delonghi/climate.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								esphome/components/delonghi/climate.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import climate_ir
 | 
				
			||||||
 | 
					from esphome.const import CONF_ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AUTO_LOAD = ["climate_ir"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					delonghi_ns = cg.esphome_ns.namespace("delonghi")
 | 
				
			||||||
 | 
					DelonghiClimate = delonghi_ns.class_("DelonghiClimate", climate_ir.ClimateIR)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.GenerateID(): cv.declare_id(DelonghiClimate),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					    await climate_ir.register_climate_ir(var, config)
 | 
				
			||||||
							
								
								
									
										186
									
								
								esphome/components/delonghi/delonghi.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								esphome/components/delonghi/delonghi.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,186 @@
 | 
				
			|||||||
 | 
					#include "delonghi.h"
 | 
				
			||||||
 | 
					#include "esphome/components/remote_base/remote_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace delonghi {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "delonghi.climate";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DelonghiClimate::transmit_state() {
 | 
				
			||||||
 | 
					  uint8_t remote_state[DELONGHI_STATE_FRAME_SIZE] = {0};
 | 
				
			||||||
 | 
					  remote_state[0] = DELONGHI_ADDRESS;
 | 
				
			||||||
 | 
					  remote_state[1] = this->temperature_();
 | 
				
			||||||
 | 
					  remote_state[1] |= (this->fan_speed_()) << 5;
 | 
				
			||||||
 | 
					  remote_state[2] = this->operation_mode_();
 | 
				
			||||||
 | 
					  // Calculate checksum
 | 
				
			||||||
 | 
					  for (int i = 0; i < DELONGHI_STATE_FRAME_SIZE - 1; i++) {
 | 
				
			||||||
 | 
					    remote_state[DELONGHI_STATE_FRAME_SIZE - 1] += remote_state[i];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auto transmit = this->transmitter_->transmit();
 | 
				
			||||||
 | 
					  auto *data = transmit.get_data();
 | 
				
			||||||
 | 
					  data->set_carrier_frequency(DELONGHI_IR_FREQUENCY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  data->mark(DELONGHI_HEADER_MARK);
 | 
				
			||||||
 | 
					  data->space(DELONGHI_HEADER_SPACE);
 | 
				
			||||||
 | 
					  for (unsigned char b : remote_state) {
 | 
				
			||||||
 | 
					    for (uint8_t mask = 1; mask > 0; mask <<= 1) {  // iterate through bit mask
 | 
				
			||||||
 | 
					      data->mark(DELONGHI_BIT_MARK);
 | 
				
			||||||
 | 
					      bool bit = b & mask;
 | 
				
			||||||
 | 
					      data->space(bit ? DELONGHI_ONE_SPACE : DELONGHI_ZERO_SPACE);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  data->mark(DELONGHI_BIT_MARK);
 | 
				
			||||||
 | 
					  data->space(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  transmit.perform();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t DelonghiClimate::operation_mode_() {
 | 
				
			||||||
 | 
					  uint8_t operating_mode = DELONGHI_MODE_ON;
 | 
				
			||||||
 | 
					  switch (this->mode) {
 | 
				
			||||||
 | 
					    case climate::CLIMATE_MODE_COOL:
 | 
				
			||||||
 | 
					      operating_mode |= DELONGHI_MODE_COOL;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case climate::CLIMATE_MODE_DRY:
 | 
				
			||||||
 | 
					      operating_mode |= DELONGHI_MODE_DRY;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case climate::CLIMATE_MODE_HEAT:
 | 
				
			||||||
 | 
					      operating_mode |= DELONGHI_MODE_HEAT;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case climate::CLIMATE_MODE_HEAT_COOL:
 | 
				
			||||||
 | 
					      operating_mode |= DELONGHI_MODE_AUTO;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case climate::CLIMATE_MODE_FAN_ONLY:
 | 
				
			||||||
 | 
					      operating_mode |= DELONGHI_MODE_FAN;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case climate::CLIMATE_MODE_OFF:
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      operating_mode = DELONGHI_MODE_OFF;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return operating_mode;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint16_t DelonghiClimate::fan_speed_() {
 | 
				
			||||||
 | 
					  uint16_t fan_speed;
 | 
				
			||||||
 | 
					  switch (this->fan_mode.value()) {
 | 
				
			||||||
 | 
					    case climate::CLIMATE_FAN_LOW:
 | 
				
			||||||
 | 
					      fan_speed = DELONGHI_FAN_LOW;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case climate::CLIMATE_FAN_MEDIUM:
 | 
				
			||||||
 | 
					      fan_speed = DELONGHI_FAN_MEDIUM;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case climate::CLIMATE_FAN_HIGH:
 | 
				
			||||||
 | 
					      fan_speed = DELONGHI_FAN_HIGH;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case climate::CLIMATE_FAN_AUTO:
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      fan_speed = DELONGHI_FAN_AUTO;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return fan_speed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t DelonghiClimate::temperature_() {
 | 
				
			||||||
 | 
					  // Force special temperatures depending on the mode
 | 
				
			||||||
 | 
					  uint8_t temperature = 0b0001;
 | 
				
			||||||
 | 
					  switch (this->mode) {
 | 
				
			||||||
 | 
					    case climate::CLIMATE_MODE_HEAT:
 | 
				
			||||||
 | 
					      temperature = (uint8_t) roundf(this->target_temperature) - DELONGHI_TEMP_OFFSET_HEAT;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case climate::CLIMATE_MODE_COOL:
 | 
				
			||||||
 | 
					    case climate::CLIMATE_MODE_DRY:
 | 
				
			||||||
 | 
					    case climate::CLIMATE_MODE_HEAT_COOL:
 | 
				
			||||||
 | 
					    case climate::CLIMATE_MODE_FAN_ONLY:
 | 
				
			||||||
 | 
					    case climate::CLIMATE_MODE_OFF:
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      temperature = (uint8_t) roundf(this->target_temperature) - DELONGHI_TEMP_OFFSET_COOL;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (temperature > 0x0F) {
 | 
				
			||||||
 | 
					    temperature = 0x0F;  // clamp maximum
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return temperature;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool DelonghiClimate::parse_state_frame_(const uint8_t frame[]) {
 | 
				
			||||||
 | 
					  uint8_t checksum = 0;
 | 
				
			||||||
 | 
					  for (int i = 0; i < (DELONGHI_STATE_FRAME_SIZE - 1); i++) {
 | 
				
			||||||
 | 
					    checksum += frame[i];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (frame[DELONGHI_STATE_FRAME_SIZE - 1] != checksum) {
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  uint8_t mode = frame[2] & 0x0F;
 | 
				
			||||||
 | 
					  if (mode & DELONGHI_MODE_ON) {
 | 
				
			||||||
 | 
					    switch (mode & 0x0E) {
 | 
				
			||||||
 | 
					      case DELONGHI_MODE_COOL:
 | 
				
			||||||
 | 
					        this->mode = climate::CLIMATE_MODE_COOL;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case DELONGHI_MODE_DRY:
 | 
				
			||||||
 | 
					        this->mode = climate::CLIMATE_MODE_DRY;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case DELONGHI_MODE_HEAT:
 | 
				
			||||||
 | 
					        this->mode = climate::CLIMATE_MODE_HEAT;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case DELONGHI_MODE_AUTO:
 | 
				
			||||||
 | 
					        this->mode = climate::CLIMATE_MODE_HEAT_COOL;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case DELONGHI_MODE_FAN:
 | 
				
			||||||
 | 
					        this->mode = climate::CLIMATE_MODE_FAN_ONLY;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    this->mode = climate::CLIMATE_MODE_OFF;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  uint8_t temperature = frame[1] & 0x0F;
 | 
				
			||||||
 | 
					  if (this->mode == climate::CLIMATE_MODE_HEAT) {
 | 
				
			||||||
 | 
					    this->target_temperature = temperature + DELONGHI_TEMP_OFFSET_HEAT;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    this->target_temperature = temperature + DELONGHI_TEMP_OFFSET_COOL;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  uint8_t fan_mode = frame[1] >> 5;
 | 
				
			||||||
 | 
					  switch (fan_mode) {
 | 
				
			||||||
 | 
					    case DELONGHI_FAN_LOW:
 | 
				
			||||||
 | 
					      this->fan_mode = climate::CLIMATE_FAN_LOW;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case DELONGHI_FAN_MEDIUM:
 | 
				
			||||||
 | 
					      this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case DELONGHI_FAN_HIGH:
 | 
				
			||||||
 | 
					      this->fan_mode = climate::CLIMATE_FAN_HIGH;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case DELONGHI_FAN_AUTO:
 | 
				
			||||||
 | 
					      this->fan_mode = climate::CLIMATE_FAN_AUTO;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->publish_state();
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool DelonghiClimate::on_receive(remote_base::RemoteReceiveData data) {
 | 
				
			||||||
 | 
					  uint8_t state_frame[DELONGHI_STATE_FRAME_SIZE] = {};
 | 
				
			||||||
 | 
					  if (!data.expect_item(DELONGHI_HEADER_MARK, DELONGHI_HEADER_SPACE)) {
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  for (uint8_t pos = 0; pos < DELONGHI_STATE_FRAME_SIZE; pos++) {
 | 
				
			||||||
 | 
					    uint8_t byte = 0;
 | 
				
			||||||
 | 
					    for (int8_t bit = 0; bit < 8; bit++) {
 | 
				
			||||||
 | 
					      if (data.expect_item(DELONGHI_BIT_MARK, DELONGHI_ONE_SPACE)) {
 | 
				
			||||||
 | 
					        byte |= 1 << bit;
 | 
				
			||||||
 | 
					      } else if (!data.expect_item(DELONGHI_BIT_MARK, DELONGHI_ZERO_SPACE)) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    state_frame[pos] = byte;
 | 
				
			||||||
 | 
					    if (pos == 0) {
 | 
				
			||||||
 | 
					      // frame header
 | 
				
			||||||
 | 
					      if (byte != DELONGHI_ADDRESS) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return this->parse_state_frame_(state_frame);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace delonghi
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										64
									
								
								esphome/components/delonghi/delonghi.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								esphome/components/delonghi/delonghi.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/components/climate_ir/climate_ir.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace delonghi {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Values for DELONGHI ARC43XXX IR Controllers
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_ADDRESS = 83;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Temperature
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_TEMP_MIN = 13;          // Celsius
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_TEMP_MAX = 32;          // Celsius
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_TEMP_OFFSET_COOL = 17;  // Celsius
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_TEMP_OFFSET_HEAT = 12;  // Celsius
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Modes
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_MODE_AUTO = 0b1000;
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_MODE_COOL = 0b0000;
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_MODE_HEAT = 0b0110;
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_MODE_DRY = 0b0010;
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_MODE_FAN = 0b0100;
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_MODE_OFF = 0b0000;
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_MODE_ON = 0b0001;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Fan Speed
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_FAN_AUTO = 0b00;
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_FAN_HIGH = 0b01;
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_FAN_MEDIUM = 0b10;
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_FAN_LOW = 0b11;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IR Transmission - similar to NEC1
 | 
				
			||||||
 | 
					const uint32_t DELONGHI_IR_FREQUENCY = 38000;
 | 
				
			||||||
 | 
					const uint32_t DELONGHI_HEADER_MARK = 9000;
 | 
				
			||||||
 | 
					const uint32_t DELONGHI_HEADER_SPACE = 4500;
 | 
				
			||||||
 | 
					const uint32_t DELONGHI_BIT_MARK = 465;
 | 
				
			||||||
 | 
					const uint32_t DELONGHI_ONE_SPACE = 1750;
 | 
				
			||||||
 | 
					const uint32_t DELONGHI_ZERO_SPACE = 670;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// State Frame size
 | 
				
			||||||
 | 
					const uint8_t DELONGHI_STATE_FRAME_SIZE = 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DelonghiClimate : public climate_ir::ClimateIR {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  DelonghiClimate()
 | 
				
			||||||
 | 
					      : climate_ir::ClimateIR(DELONGHI_TEMP_MIN, DELONGHI_TEMP_MAX, 1.0f, true, true,
 | 
				
			||||||
 | 
					                              {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
 | 
				
			||||||
 | 
					                               climate::CLIMATE_FAN_HIGH},
 | 
				
			||||||
 | 
					                              {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
 | 
				
			||||||
 | 
					                               climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  // Transmit via IR the state of this climate controller.
 | 
				
			||||||
 | 
					  void transmit_state() override;
 | 
				
			||||||
 | 
					  uint8_t operation_mode_();
 | 
				
			||||||
 | 
					  uint16_t fan_speed_();
 | 
				
			||||||
 | 
					  uint8_t temperature_();
 | 
				
			||||||
 | 
					  // Handle received IR Buffer
 | 
				
			||||||
 | 
					  bool on_receive(remote_base::RemoteReceiveData data) override;
 | 
				
			||||||
 | 
					  bool parse_state_frame_(const uint8_t frame[]);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace delonghi
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
@@ -242,6 +242,13 @@ void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color colo
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
 | 
					    case IMAGE_TYPE_RGB565:
 | 
				
			||||||
 | 
					      for (int img_x = 0; img_x < image->get_width(); img_x++) {
 | 
				
			||||||
 | 
					        for (int img_y = 0; img_y < image->get_height(); img_y++) {
 | 
				
			||||||
 | 
					          this->draw_pixel_at(x + img_x, y + img_y, image->get_rgb565_pixel(img_x, img_y));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -497,6 +504,17 @@ Color Image::get_color_pixel(int x, int y) const {
 | 
				
			|||||||
                           (progmem_read_byte(this->data_start_ + pos + 0) << 16);
 | 
					                           (progmem_read_byte(this->data_start_ + pos + 0) << 16);
 | 
				
			||||||
  return Color(color32);
 | 
					  return Color(color32);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					Color Image::get_rgb565_pixel(int x, int y) const {
 | 
				
			||||||
 | 
					  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
				
			||||||
 | 
					    return Color::BLACK;
 | 
				
			||||||
 | 
					  const uint32_t pos = (x + y * this->width_) * 2;
 | 
				
			||||||
 | 
					  uint16_t rgb565 =
 | 
				
			||||||
 | 
					      progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1);
 | 
				
			||||||
 | 
					  auto r = (rgb565 & 0xF800) >> 11;
 | 
				
			||||||
 | 
					  auto g = (rgb565 & 0x07E0) >> 5;
 | 
				
			||||||
 | 
					  auto b = rgb565 & 0x001F;
 | 
				
			||||||
 | 
					  return Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
Color Image::get_grayscale_pixel(int x, int y) const {
 | 
					Color Image::get_grayscale_pixel(int x, int y) const {
 | 
				
			||||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
					  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
				
			||||||
    return Color::BLACK;
 | 
					    return Color::BLACK;
 | 
				
			||||||
@@ -532,6 +550,20 @@ Color Animation::get_color_pixel(int x, int y) const {
 | 
				
			|||||||
                           (progmem_read_byte(this->data_start_ + pos + 0) << 16);
 | 
					                           (progmem_read_byte(this->data_start_ + pos + 0) << 16);
 | 
				
			||||||
  return Color(color32);
 | 
					  return Color(color32);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					Color Animation::get_rgb565_pixel(int x, int y) const {
 | 
				
			||||||
 | 
					  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
				
			||||||
 | 
					    return Color::BLACK;
 | 
				
			||||||
 | 
					  const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
 | 
				
			||||||
 | 
					  if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
 | 
				
			||||||
 | 
					    return Color::BLACK;
 | 
				
			||||||
 | 
					  const uint32_t pos = (x + y * this->width_ + frame_index) * 2;
 | 
				
			||||||
 | 
					  uint16_t rgb565 =
 | 
				
			||||||
 | 
					      progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1);
 | 
				
			||||||
 | 
					  auto r = (rgb565 & 0xF800) >> 11;
 | 
				
			||||||
 | 
					  auto g = (rgb565 & 0x07E0) >> 5;
 | 
				
			||||||
 | 
					  auto b = rgb565 & 0x001F;
 | 
				
			||||||
 | 
					  return Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
Color Animation::get_grayscale_pixel(int x, int y) const {
 | 
					Color Animation::get_grayscale_pixel(int x, int y) const {
 | 
				
			||||||
  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
					  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
 | 
				
			||||||
    return Color::BLACK;
 | 
					    return Color::BLACK;
 | 
				
			||||||
@@ -552,6 +584,12 @@ void Animation::next_frame() {
 | 
				
			|||||||
    this->current_frame_ = 0;
 | 
					    this->current_frame_ = 0;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					void Animation::prev_frame() {
 | 
				
			||||||
 | 
					  this->current_frame_--;
 | 
				
			||||||
 | 
					  if (this->current_frame_ < 0) {
 | 
				
			||||||
 | 
					    this->current_frame_ = this->animation_frame_count_ - 1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
 | 
					DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
 | 
				
			||||||
void DisplayPage::show() { this->parent_->show_page(this); }
 | 
					void DisplayPage::show() { this->parent_->show_page(this); }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -82,6 +82,7 @@ enum ImageType {
 | 
				
			|||||||
  IMAGE_TYPE_GRAYSCALE = 1,
 | 
					  IMAGE_TYPE_GRAYSCALE = 1,
 | 
				
			||||||
  IMAGE_TYPE_RGB24 = 2,
 | 
					  IMAGE_TYPE_RGB24 = 2,
 | 
				
			||||||
  IMAGE_TYPE_TRANSPARENT_BINARY = 3,
 | 
					  IMAGE_TYPE_TRANSPARENT_BINARY = 3,
 | 
				
			||||||
 | 
					  IMAGE_TYPE_RGB565 = 4,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum DisplayRotation {
 | 
					enum DisplayRotation {
 | 
				
			||||||
@@ -453,6 +454,7 @@ class Image {
 | 
				
			|||||||
  Image(const uint8_t *data_start, int width, int height, ImageType type);
 | 
					  Image(const uint8_t *data_start, int width, int height, ImageType type);
 | 
				
			||||||
  virtual bool get_pixel(int x, int y) const;
 | 
					  virtual bool get_pixel(int x, int y) const;
 | 
				
			||||||
  virtual Color get_color_pixel(int x, int y) const;
 | 
					  virtual Color get_color_pixel(int x, int y) const;
 | 
				
			||||||
 | 
					  virtual Color get_rgb565_pixel(int x, int y) const;
 | 
				
			||||||
  virtual Color get_grayscale_pixel(int x, int y) const;
 | 
					  virtual Color get_grayscale_pixel(int x, int y) const;
 | 
				
			||||||
  int get_width() const;
 | 
					  int get_width() const;
 | 
				
			||||||
  int get_height() const;
 | 
					  int get_height() const;
 | 
				
			||||||
@@ -470,11 +472,13 @@ class Animation : public Image {
 | 
				
			|||||||
  Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type);
 | 
					  Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type);
 | 
				
			||||||
  bool get_pixel(int x, int y) const override;
 | 
					  bool get_pixel(int x, int y) const override;
 | 
				
			||||||
  Color get_color_pixel(int x, int y) const override;
 | 
					  Color get_color_pixel(int x, int y) const override;
 | 
				
			||||||
 | 
					  Color get_rgb565_pixel(int x, int y) const override;
 | 
				
			||||||
  Color get_grayscale_pixel(int x, int y) const override;
 | 
					  Color get_grayscale_pixel(int x, int y) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  int get_animation_frame_count() const;
 | 
					  int get_animation_frame_count() const;
 | 
				
			||||||
  int get_current_frame() const;
 | 
					  int get_current_frame() const;
 | 
				
			||||||
  void next_frame();
 | 
					  void next_frame();
 | 
				
			||||||
 | 
					  void prev_frame();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  int current_frame_;
 | 
					  int current_frame_;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -143,37 +143,37 @@ CONFIG_SCHEMA = cv.Schema(
 | 
				
			|||||||
        cv.Optional("power_delivered_l1"): sensor.sensor_schema(
 | 
					        cv.Optional("power_delivered_l1"): sensor.sensor_schema(
 | 
				
			||||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
					            unit_of_measurement=UNIT_KILOWATT,
 | 
				
			||||||
            accuracy_decimals=3,
 | 
					            accuracy_decimals=3,
 | 
				
			||||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
					            device_class=DEVICE_CLASS_POWER,
 | 
				
			||||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
					            state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        cv.Optional("power_delivered_l2"): sensor.sensor_schema(
 | 
					        cv.Optional("power_delivered_l2"): sensor.sensor_schema(
 | 
				
			||||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
					            unit_of_measurement=UNIT_KILOWATT,
 | 
				
			||||||
            accuracy_decimals=3,
 | 
					            accuracy_decimals=3,
 | 
				
			||||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
					            device_class=DEVICE_CLASS_POWER,
 | 
				
			||||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
					            state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        cv.Optional("power_delivered_l3"): sensor.sensor_schema(
 | 
					        cv.Optional("power_delivered_l3"): sensor.sensor_schema(
 | 
				
			||||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
					            unit_of_measurement=UNIT_KILOWATT,
 | 
				
			||||||
            accuracy_decimals=3,
 | 
					            accuracy_decimals=3,
 | 
				
			||||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
					            device_class=DEVICE_CLASS_POWER,
 | 
				
			||||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
					            state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        cv.Optional("power_returned_l1"): sensor.sensor_schema(
 | 
					        cv.Optional("power_returned_l1"): sensor.sensor_schema(
 | 
				
			||||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
					            unit_of_measurement=UNIT_KILOWATT,
 | 
				
			||||||
            accuracy_decimals=3,
 | 
					            accuracy_decimals=3,
 | 
				
			||||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
					            device_class=DEVICE_CLASS_POWER,
 | 
				
			||||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
					            state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        cv.Optional("power_returned_l2"): sensor.sensor_schema(
 | 
					        cv.Optional("power_returned_l2"): sensor.sensor_schema(
 | 
				
			||||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
					            unit_of_measurement=UNIT_KILOWATT,
 | 
				
			||||||
            accuracy_decimals=3,
 | 
					            accuracy_decimals=3,
 | 
				
			||||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
					            device_class=DEVICE_CLASS_POWER,
 | 
				
			||||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
					            state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        cv.Optional("power_returned_l3"): sensor.sensor_schema(
 | 
					        cv.Optional("power_returned_l3"): sensor.sensor_schema(
 | 
				
			||||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
					            unit_of_measurement=UNIT_KILOWATT,
 | 
				
			||||||
            accuracy_decimals=3,
 | 
					            accuracy_decimals=3,
 | 
				
			||||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
					            device_class=DEVICE_CLASS_POWER,
 | 
				
			||||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
					            state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema(
 | 
					        cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ using namespace esphome::cover;
 | 
				
			|||||||
CoverTraits EndstopCover::get_traits() {
 | 
					CoverTraits EndstopCover::get_traits() {
 | 
				
			||||||
  auto traits = CoverTraits();
 | 
					  auto traits = CoverTraits();
 | 
				
			||||||
  traits.set_supports_position(true);
 | 
					  traits.set_supports_position(true);
 | 
				
			||||||
 | 
					  traits.set_supports_toggle(true);
 | 
				
			||||||
  traits.set_is_assumed_state(false);
 | 
					  traits.set_is_assumed_state(false);
 | 
				
			||||||
  return traits;
 | 
					  return traits;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -20,6 +21,20 @@ void EndstopCover::control(const CoverCall &call) {
 | 
				
			|||||||
    this->start_direction_(COVER_OPERATION_IDLE);
 | 
					    this->start_direction_(COVER_OPERATION_IDLE);
 | 
				
			||||||
    this->publish_state();
 | 
					    this->publish_state();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  if (call.get_toggle().has_value()) {
 | 
				
			||||||
 | 
					    if (this->current_operation != COVER_OPERATION_IDLE) {
 | 
				
			||||||
 | 
					      this->start_direction_(COVER_OPERATION_IDLE);
 | 
				
			||||||
 | 
					      this->publish_state();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      if (this->position == COVER_CLOSED || this->last_operation_ == COVER_OPERATION_CLOSING) {
 | 
				
			||||||
 | 
					        this->target_position_ = COVER_OPEN;
 | 
				
			||||||
 | 
					        this->start_direction_(COVER_OPERATION_OPENING);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        this->target_position_ = COVER_CLOSED;
 | 
				
			||||||
 | 
					        this->start_direction_(COVER_OPERATION_CLOSING);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  if (call.get_position().has_value()) {
 | 
					  if (call.get_position().has_value()) {
 | 
				
			||||||
    auto pos = *call.get_position();
 | 
					    auto pos = *call.get_position();
 | 
				
			||||||
    if (pos == this->position) {
 | 
					    if (pos == this->position) {
 | 
				
			||||||
@@ -125,9 +140,11 @@ void EndstopCover::start_direction_(CoverOperation dir) {
 | 
				
			|||||||
      trig = this->stop_trigger_;
 | 
					      trig = this->stop_trigger_;
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    case COVER_OPERATION_OPENING:
 | 
					    case COVER_OPERATION_OPENING:
 | 
				
			||||||
 | 
					      this->last_operation_ = dir;
 | 
				
			||||||
      trig = this->open_trigger_;
 | 
					      trig = this->open_trigger_;
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    case COVER_OPERATION_CLOSING:
 | 
					    case COVER_OPERATION_CLOSING:
 | 
				
			||||||
 | 
					      this->last_operation_ = dir;
 | 
				
			||||||
      trig = this->close_trigger_;
 | 
					      trig = this->close_trigger_;
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,6 +51,7 @@ class EndstopCover : public cover::Cover, public Component {
 | 
				
			|||||||
  uint32_t start_dir_time_{0};
 | 
					  uint32_t start_dir_time_{0};
 | 
				
			||||||
  uint32_t last_publish_time_{0};
 | 
					  uint32_t last_publish_time_{0};
 | 
				
			||||||
  float target_position_{0};
 | 
					  float target_position_{0};
 | 
				
			||||||
 | 
					  cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace endstop
 | 
					}  // namespace endstop
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										0
									
								
								esphome/components/ens210/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/ens210/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										230
									
								
								esphome/components/ens210/ens210.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								esphome/components/ens210/ens210.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,230 @@
 | 
				
			|||||||
 | 
					// ENS210 relative humidity and temperature sensor with I2C interface from ScioSense
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Datasheet: https://www.sciosense.com/wp-content/uploads/2021/01/ENS210.pdf
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Implementation based on:
 | 
				
			||||||
 | 
					//   https://github.com/maarten-pennings/ENS210
 | 
				
			||||||
 | 
					//   https://github.com/sciosense/ENS210_driver
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "ens210.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace ens210 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "ens210";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ENS210 chip constants
 | 
				
			||||||
 | 
					static const uint8_t ENS210_BOOTING_MS = 2;  // Booting time in ms (also after reset, or going to high power)
 | 
				
			||||||
 | 
					static const uint8_t ENS210_SINGLE_MEASURMENT_CONVERSION_TIME_MS =
 | 
				
			||||||
 | 
					    130;                                        // Conversion time in ms for single shot T/H measurement
 | 
				
			||||||
 | 
					static const uint16_t ENS210_PART_ID = 0x0210;  // The expected part id of the ENS210
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Addresses of the ENS210 registers
 | 
				
			||||||
 | 
					static const uint8_t ENS210_REGISTER_PART_ID = 0x00;
 | 
				
			||||||
 | 
					static const uint8_t ENS210_REGISTER_UID = 0x04;
 | 
				
			||||||
 | 
					static const uint8_t ENS210_REGISTER_SYS_CTRL = 0x10;
 | 
				
			||||||
 | 
					static const uint8_t ENS210_REGISTER_SYS_STAT = 0x11;
 | 
				
			||||||
 | 
					static const uint8_t ENS210_REGISTER_SENS_RUN = 0x21;
 | 
				
			||||||
 | 
					static const uint8_t ENS210_REGISTER_SENS_START = 0x22;
 | 
				
			||||||
 | 
					static const uint8_t ENS210_REGISTER_SENS_STOP = 0x23;
 | 
				
			||||||
 | 
					static const uint8_t ENS210_REGISTER_SENS_STAT = 0x24;
 | 
				
			||||||
 | 
					static const uint8_t ENS210_REGISTER_T_VAL = 0x30;
 | 
				
			||||||
 | 
					static const uint8_t ENS210_REGISTER_H_VAL = 0x33;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CRC-7 constants
 | 
				
			||||||
 | 
					static const uint8_t CRC7_WIDTH = 7;    // A 7 bits CRC has polynomial of 7th order, which has 8 terms
 | 
				
			||||||
 | 
					static const uint8_t CRC7_POLY = 0x89;  // The 8 coefficients of the polynomial
 | 
				
			||||||
 | 
					static const uint8_t CRC7_IVEC = 0x7F;  // Initial vector has all 7 bits high
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Payload data constants
 | 
				
			||||||
 | 
					static const uint8_t DATA7_WIDTH = 17;
 | 
				
			||||||
 | 
					static const uint32_t DATA7_MASK = ((1UL << DATA7_WIDTH) - 1);  // 0b 0 1111 1111 1111 1111
 | 
				
			||||||
 | 
					static const uint32_t DATA7_MSB = (1UL << (DATA7_WIDTH - 1));   // 0b 1 0000 0000 0000 0000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Converts a status to a human readable string
 | 
				
			||||||
 | 
					static const LogString *ens210_status_to_human(int status) {
 | 
				
			||||||
 | 
					  switch (status) {
 | 
				
			||||||
 | 
					    case ENS210Component::ENS210_STATUS_I2C_ERROR:
 | 
				
			||||||
 | 
					      return LOG_STR("I2C error - communication with ENS210 failed!");
 | 
				
			||||||
 | 
					    case ENS210Component::ENS210_STATUS_CRC_ERROR:
 | 
				
			||||||
 | 
					      return LOG_STR("CRC error");
 | 
				
			||||||
 | 
					    case ENS210Component::ENS210_STATUS_INVALID:
 | 
				
			||||||
 | 
					      return LOG_STR("Invalid data");
 | 
				
			||||||
 | 
					    case ENS210Component::ENS210_STATUS_OK:
 | 
				
			||||||
 | 
					      return LOG_STR("Status OK");
 | 
				
			||||||
 | 
					    case ENS210Component::ENS210_WRONG_CHIP_ID:
 | 
				
			||||||
 | 
					      return LOG_STR("ENS210 has wrong chip ID! Is it a ENS210?");
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return LOG_STR("Unknown");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Compute the CRC-7 of 'value' (should only have 17 bits)
 | 
				
			||||||
 | 
					// https://en.wikipedia.org/wiki/Cyclic_redundancy_check#Computation
 | 
				
			||||||
 | 
					static uint32_t crc7(uint32_t value) {
 | 
				
			||||||
 | 
					  // Setup polynomial
 | 
				
			||||||
 | 
					  uint32_t polynomial = CRC7_POLY;
 | 
				
			||||||
 | 
					  // Align polynomial with data
 | 
				
			||||||
 | 
					  polynomial = polynomial << (DATA7_WIDTH - CRC7_WIDTH - 1);
 | 
				
			||||||
 | 
					  // Loop variable (indicates which bit to test, start with highest)
 | 
				
			||||||
 | 
					  uint32_t bit = DATA7_MSB;
 | 
				
			||||||
 | 
					  // Make room for CRC value
 | 
				
			||||||
 | 
					  value = value << CRC7_WIDTH;
 | 
				
			||||||
 | 
					  bit = bit << CRC7_WIDTH;
 | 
				
			||||||
 | 
					  polynomial = polynomial << CRC7_WIDTH;
 | 
				
			||||||
 | 
					  // Insert initial vector
 | 
				
			||||||
 | 
					  value |= CRC7_IVEC;
 | 
				
			||||||
 | 
					  // Apply division until all bits done
 | 
				
			||||||
 | 
					  while (bit & (DATA7_MASK << CRC7_WIDTH)) {
 | 
				
			||||||
 | 
					    if (bit & value)
 | 
				
			||||||
 | 
					      value ^= polynomial;
 | 
				
			||||||
 | 
					    bit >>= 1;
 | 
				
			||||||
 | 
					    polynomial >>= 1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return value;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ENS210Component::setup() {
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "Setting up ENS210...");
 | 
				
			||||||
 | 
					  uint8_t data[2];
 | 
				
			||||||
 | 
					  uint16_t part_id = 0;
 | 
				
			||||||
 | 
					  // Reset
 | 
				
			||||||
 | 
					  if (!this->write_byte(ENS210_REGISTER_SYS_CTRL, 0x80)) {
 | 
				
			||||||
 | 
					    this->write_byte(ENS210_REGISTER_SYS_CTRL, 0x80);
 | 
				
			||||||
 | 
					    this->error_code_ = ENS210_STATUS_I2C_ERROR;
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  // Wait to boot after reset
 | 
				
			||||||
 | 
					  delay(ENS210_BOOTING_MS);
 | 
				
			||||||
 | 
					  // Must disable low power to read PART_ID
 | 
				
			||||||
 | 
					  if (!set_low_power_(false)) {
 | 
				
			||||||
 | 
					    // Try to go back to default mode (low power enabled)
 | 
				
			||||||
 | 
					    set_low_power_(true);
 | 
				
			||||||
 | 
					    this->error_code_ = ENS210_STATUS_I2C_ERROR;
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  // Read the PART_ID
 | 
				
			||||||
 | 
					  if (!this->read_bytes(ENS210_REGISTER_PART_ID, data, 2)) {
 | 
				
			||||||
 | 
					    // Try to go back to default mode (low power enabled)
 | 
				
			||||||
 | 
					    set_low_power_(true);
 | 
				
			||||||
 | 
					    this->error_code_ = ENS210_STATUS_I2C_ERROR;
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  // Pack bytes into partid
 | 
				
			||||||
 | 
					  part_id = data[1] * 256U + data[0] * 1U;
 | 
				
			||||||
 | 
					  // Check expected part id of the ENS210
 | 
				
			||||||
 | 
					  if (part_id != ENS210_PART_ID) {
 | 
				
			||||||
 | 
					    this->error_code_ = ENS210_WRONG_CHIP_ID;
 | 
				
			||||||
 | 
					    this->mark_failed();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  // Set default power mode (low power enabled)
 | 
				
			||||||
 | 
					  set_low_power_(true);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ENS210Component::dump_config() {
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "ENS210:");
 | 
				
			||||||
 | 
					  LOG_I2C_DEVICE(this);
 | 
				
			||||||
 | 
					  if (this->is_failed()) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "%s", LOG_STR_ARG(ens210_status_to_human(this->error_code_)));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  LOG_UPDATE_INTERVAL(this);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Temperature", this->temperature_sensor_);
 | 
				
			||||||
 | 
					  LOG_SENSOR("  ", "Humidity", this->humidity_sensor_);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					float ENS210Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ENS210Component::update() {
 | 
				
			||||||
 | 
					  // Execute a single measurement
 | 
				
			||||||
 | 
					  if (!this->write_byte(ENS210_REGISTER_SENS_RUN, 0x00)) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "Starting single measurement failed!");
 | 
				
			||||||
 | 
					    this->status_set_warning();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  // Trigger measurement
 | 
				
			||||||
 | 
					  if (!this->write_byte(ENS210_REGISTER_SENS_START, 0x03)) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "Trigger of measurement failed!");
 | 
				
			||||||
 | 
					    this->status_set_warning();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  // Wait for measurement to complete
 | 
				
			||||||
 | 
					  this->set_timeout("data", uint32_t(ENS210_SINGLE_MEASURMENT_CONVERSION_TIME_MS), [this]() {
 | 
				
			||||||
 | 
					    int temperature_data, temperature_status, humidity_data, humidity_status;
 | 
				
			||||||
 | 
					    uint8_t data[6];
 | 
				
			||||||
 | 
					    uint32_t h_val_data, t_val_data;
 | 
				
			||||||
 | 
					    // Set default status for early bail out
 | 
				
			||||||
 | 
					    temperature_status = ENS210_STATUS_I2C_ERROR;
 | 
				
			||||||
 | 
					    humidity_status = ENS210_STATUS_I2C_ERROR;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Read T_VAL and H_VAL
 | 
				
			||||||
 | 
					    if (!this->read_bytes(ENS210_REGISTER_T_VAL, data, 6)) {
 | 
				
			||||||
 | 
					      ESP_LOGE(TAG, "Communication with ENS210 failed!");
 | 
				
			||||||
 | 
					      this->status_set_warning();
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // Pack bytes for humidity
 | 
				
			||||||
 | 
					    h_val_data = (uint32_t)((uint32_t) data[5] << 16 | (uint32_t) data[4] << 8 | (uint32_t) data[3]);
 | 
				
			||||||
 | 
					    // Extract humidity data and update the status
 | 
				
			||||||
 | 
					    extract_measurement_(h_val_data, &humidity_data, &humidity_status);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (humidity_status == ENS210_STATUS_OK) {
 | 
				
			||||||
 | 
					      if (this->humidity_sensor_ != nullptr) {
 | 
				
			||||||
 | 
					        float humidity = (humidity_data & 0xFFFF) / 512.0;
 | 
				
			||||||
 | 
					        this->humidity_sensor_->publish_state(humidity);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Humidity status failure: %s", LOG_STR_ARG(ens210_status_to_human(humidity_status)));
 | 
				
			||||||
 | 
					      this->status_set_warning();
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // Pack bytes for temperature
 | 
				
			||||||
 | 
					    t_val_data = (uint32_t)((uint32_t) data[2] << 16 | (uint32_t) data[1] << 8 | (uint32_t) data[0]);
 | 
				
			||||||
 | 
					    // Extract temperature data and update the status
 | 
				
			||||||
 | 
					    extract_measurement_(t_val_data, &temperature_data, &temperature_status);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (temperature_status == ENS210_STATUS_OK) {
 | 
				
			||||||
 | 
					      if (this->temperature_sensor_ != nullptr) {
 | 
				
			||||||
 | 
					        // Temperature in Celsius
 | 
				
			||||||
 | 
					        float temperature = (temperature_data & 0xFFFF) / 64.0 - 27315L / 100.0;
 | 
				
			||||||
 | 
					        this->temperature_sensor_->publish_state(temperature);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "Temperature status failure: %s", LOG_STR_ARG(ens210_status_to_human(temperature_status)));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Extracts measurement 'data' and 'status' from a 'val' obtained from measurment.
 | 
				
			||||||
 | 
					void ENS210Component::extract_measurement_(uint32_t val, int *data, int *status) {
 | 
				
			||||||
 | 
					  *data = (val >> 0) & 0xffff;
 | 
				
			||||||
 | 
					  int valid = (val >> 16) & 0x1;
 | 
				
			||||||
 | 
					  uint32_t crc = (val >> 17) & 0x7f;
 | 
				
			||||||
 | 
					  uint32_t payload = (val >> 0) & 0x1ffff;
 | 
				
			||||||
 | 
					  // Check CRC
 | 
				
			||||||
 | 
					  uint8_t crc_ok = crc7(payload) == crc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!crc_ok) {
 | 
				
			||||||
 | 
					    *status = ENS210_STATUS_CRC_ERROR;
 | 
				
			||||||
 | 
					  } else if (!valid) {
 | 
				
			||||||
 | 
					    *status = ENS210_STATUS_INVALID;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    *status = ENS210_STATUS_OK;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Sets ENS210 to low (true) or high (false) power. Returns false on I2C problems.
 | 
				
			||||||
 | 
					bool ENS210Component::set_low_power_(bool enable) {
 | 
				
			||||||
 | 
					  uint8_t low_power_cmd = enable ? 0x01 : 0x00;
 | 
				
			||||||
 | 
					  ESP_LOGD(TAG, "Enable low power: %s", enable ? "true" : "false");
 | 
				
			||||||
 | 
					  bool result = this->write_byte(ENS210_REGISTER_SYS_CTRL, low_power_cmd);
 | 
				
			||||||
 | 
					  delay(ENS210_BOOTING_MS);
 | 
				
			||||||
 | 
					  return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace ens210
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										39
									
								
								esphome/components/ens210/ens210.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								esphome/components/ens210/ens210.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
 | 
					#include "esphome/components/i2c/i2c.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace ens210 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// This class implements support for the ENS210 relative humidity and temperature i2c sensor.
 | 
				
			||||||
 | 
					class ENS210Component : public PollingComponent, public i2c::I2CDevice {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  float get_setup_priority() const override;
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					  void setup() override;
 | 
				
			||||||
 | 
					  void update() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
 | 
				
			||||||
 | 
					  void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  enum ErrorCode {
 | 
				
			||||||
 | 
					    ENS210_STATUS_OK = 0,     // The value was read, the CRC matches, and data is valid
 | 
				
			||||||
 | 
					    ENS210_STATUS_INVALID,    // The value was read, the CRC matches, but the data is invalid (e.g. the measurement was
 | 
				
			||||||
 | 
					                              // not yet finished)
 | 
				
			||||||
 | 
					    ENS210_STATUS_CRC_ERROR,  // The value was read, but the CRC over the payload (valid and data) does not match
 | 
				
			||||||
 | 
					    ENS210_STATUS_I2C_ERROR,  // There was an I2C communication error
 | 
				
			||||||
 | 
					    ENS210_WRONG_CHIP_ID      // The read PART_ID is not the expected part id of the ENS210
 | 
				
			||||||
 | 
					  } error_code_{ENS210_STATUS_OK};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  bool set_low_power_(bool enable);
 | 
				
			||||||
 | 
					  void extract_measurement_(uint32_t val, int *data, int *status);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  sensor::Sensor *temperature_sensor_;
 | 
				
			||||||
 | 
					  sensor::Sensor *humidity_sensor_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace ens210
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										58
									
								
								esphome/components/ens210/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								esphome/components/ens210/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					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,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					    UNIT_CELSIUS,
 | 
				
			||||||
 | 
					    UNIT_PERCENT,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CODEOWNERS = ["@itn3rd77"]
 | 
				
			||||||
 | 
					DEPENDENCIES = ["i2c"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ens210_ns = cg.esphome_ns.namespace("ens210")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENS210Component = ens210_ns.class_(
 | 
				
			||||||
 | 
					    "ENS210Component", cg.PollingComponent, i2c.I2CDevice
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = (
 | 
				
			||||||
 | 
					    cv.Schema(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cv.GenerateID(): cv.declare_id(ENS210Component),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_CELSIUS,
 | 
				
			||||||
 | 
					                accuracy_decimals=1,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_PERCENT,
 | 
				
			||||||
 | 
					                accuracy_decimals=1,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .extend(cv.polling_component_schema("60s"))
 | 
				
			||||||
 | 
					    .extend(i2c.i2c_device_schema(0x43))
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					    await i2c.register_i2c_device(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_TEMPERATURE in config:
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
 | 
				
			||||||
 | 
					        cg.add(var.set_temperature_sensor(sens))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CONF_HUMIDITY in config:
 | 
				
			||||||
 | 
					        sens = await sensor.new_sensor(config[CONF_HUMIDITY])
 | 
				
			||||||
 | 
					        cg.add(var.set_humidity_sensor(sens))
 | 
				
			||||||
@@ -107,7 +107,7 @@ def validate_gpio_pin(value):
 | 
				
			|||||||
    value = _translate_pin(value)
 | 
					    value = _translate_pin(value)
 | 
				
			||||||
    variant = CORE.data[KEY_ESP32][KEY_VARIANT]
 | 
					    variant = CORE.data[KEY_ESP32][KEY_VARIANT]
 | 
				
			||||||
    if variant not in _esp32_validations:
 | 
					    if variant not in _esp32_validations:
 | 
				
			||||||
        raise cv.Invalid("Unsupported ESP32 variant {variant}")
 | 
					        raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return _esp32_validations[variant].pin_validation(value)
 | 
					    return _esp32_validations[variant].pin_validation(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -121,7 +121,7 @@ def validate_supports(value):
 | 
				
			|||||||
    is_pulldown = mode[CONF_PULLDOWN]
 | 
					    is_pulldown = mode[CONF_PULLDOWN]
 | 
				
			||||||
    variant = CORE.data[KEY_ESP32][KEY_VARIANT]
 | 
					    variant = CORE.data[KEY_ESP32][KEY_VARIANT]
 | 
				
			||||||
    if variant not in _esp32_validations:
 | 
					    if variant not in _esp32_validations:
 | 
				
			||||||
        raise cv.Invalid("Unsupported ESP32 variant {variant}")
 | 
					        raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if is_open_drain and not is_output:
 | 
					    if is_open_drain and not is_output:
 | 
				
			||||||
        raise cv.Invalid(
 | 
					        raise cv.Invalid(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -262,6 +262,9 @@ void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_
 | 
				
			|||||||
    default:
 | 
					    default:
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  for (auto *client : global_esp32_ble_tracker->clients_) {
 | 
				
			||||||
 | 
					    client->gap_event_handler(event, param);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) {
 | 
					void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -155,6 +155,7 @@ class ESPBTClient : public ESPBTDeviceListener {
 | 
				
			|||||||
 public:
 | 
					 public:
 | 
				
			||||||
  virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
					  virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
				
			||||||
                                   esp_ble_gattc_cb_param_t *param) = 0;
 | 
					                                   esp_ble_gattc_cb_param_t *param) = 0;
 | 
				
			||||||
 | 
					  virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
 | 
				
			||||||
  virtual void connect() = 0;
 | 
					  virtual void connect() = 0;
 | 
				
			||||||
  void set_state(ClientState st) { this->state_ = st; }
 | 
					  void set_state(ClientState st) { this->state_ = st; }
 | 
				
			||||||
  ClientState state() const { return state_; }
 | 
					  ClientState state() const { return state_; }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@ from esphome.helpers import copy_file_if_changed
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from .const import (
 | 
					from .const import (
 | 
				
			||||||
    CONF_RESTORE_FROM_FLASH,
 | 
					    CONF_RESTORE_FROM_FLASH,
 | 
				
			||||||
 | 
					    CONF_EARLY_PIN_INIT,
 | 
				
			||||||
    KEY_BOARD,
 | 
					    KEY_BOARD,
 | 
				
			||||||
    KEY_ESP8266,
 | 
					    KEY_ESP8266,
 | 
				
			||||||
    KEY_PIN_INITIAL_STATES,
 | 
					    KEY_PIN_INITIAL_STATES,
 | 
				
			||||||
@@ -148,6 +149,7 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
            cv.Required(CONF_BOARD): cv.string_strict,
 | 
					            cv.Required(CONF_BOARD): cv.string_strict,
 | 
				
			||||||
            cv.Optional(CONF_FRAMEWORK, default={}): ARDUINO_FRAMEWORK_SCHEMA,
 | 
					            cv.Optional(CONF_FRAMEWORK, default={}): ARDUINO_FRAMEWORK_SCHEMA,
 | 
				
			||||||
            cv.Optional(CONF_RESTORE_FROM_FLASH, default=False): cv.boolean,
 | 
					            cv.Optional(CONF_RESTORE_FROM_FLASH, default=False): cv.boolean,
 | 
				
			||||||
 | 
					            cv.Optional(CONF_EARLY_PIN_INIT, default=True): cv.boolean,
 | 
				
			||||||
            cv.Optional(CONF_BOARD_FLASH_MODE, default="dout"): cv.one_of(
 | 
					            cv.Optional(CONF_BOARD_FLASH_MODE, default="dout"): cv.one_of(
 | 
				
			||||||
                *BUILD_FLASH_MODES, lower=True
 | 
					                *BUILD_FLASH_MODES, lower=True
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
@@ -197,6 +199,9 @@ async def to_code(config):
 | 
				
			|||||||
    if config[CONF_RESTORE_FROM_FLASH]:
 | 
					    if config[CONF_RESTORE_FROM_FLASH]:
 | 
				
			||||||
        cg.add_define("USE_ESP8266_PREFERENCES_FLASH")
 | 
					        cg.add_define("USE_ESP8266_PREFERENCES_FLASH")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if config[CONF_EARLY_PIN_INIT]:
 | 
				
			||||||
 | 
					        cg.add_define("USE_ESP8266_EARLY_PIN_INIT")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when
 | 
					    # Arduino 2 has a non-standards conformant new that returns a nullptr instead of failing when
 | 
				
			||||||
    # out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make
 | 
					    # out of memory and exceptions are disabled. Since Arduino 2.6.0, this flag can be used to make
 | 
				
			||||||
    # new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of
 | 
					    # new abort instead. Use it so that OOM fails early (on allocation) instead of on dereference of
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ KEY_ESP8266 = "esp8266"
 | 
				
			|||||||
KEY_BOARD = "board"
 | 
					KEY_BOARD = "board"
 | 
				
			||||||
KEY_PIN_INITIAL_STATES = "pin_initial_states"
 | 
					KEY_PIN_INITIAL_STATES = "pin_initial_states"
 | 
				
			||||||
CONF_RESTORE_FROM_FLASH = "restore_from_flash"
 | 
					CONF_RESTORE_FROM_FLASH = "restore_from_flash"
 | 
				
			||||||
 | 
					CONF_EARLY_PIN_INIT = "early_pin_init"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# esp8266 namespace is already defined by arduino, manually prefix esphome
 | 
					# esp8266 namespace is already defined by arduino, manually prefix esphome
 | 
				
			||||||
esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266")
 | 
					esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
#ifdef USE_ESP8266
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "core.h"
 | 
					#include "core.h"
 | 
				
			||||||
 | 
					#include "esphome/core/defines.h"
 | 
				
			||||||
#include "esphome/core/hal.h"
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
#include "esphome/core/helpers.h"
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
#include "preferences.h"
 | 
					#include "preferences.h"
 | 
				
			||||||
@@ -55,6 +56,7 @@ extern "C" void resetPins() {  // NOLINT
 | 
				
			|||||||
  // ourselves and this causes pins to toggle during reboot.
 | 
					  // ourselves and this causes pins to toggle during reboot.
 | 
				
			||||||
  force_link_symbols();
 | 
					  force_link_symbols();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef USE_ESP8266_EARLY_PIN_INIT
 | 
				
			||||||
  for (int i = 0; i < 16; i++) {
 | 
					  for (int i = 0; i < 16; i++) {
 | 
				
			||||||
    uint8_t mode = ESPHOME_ESP8266_GPIO_INITIAL_MODE[i];
 | 
					    uint8_t mode = ESPHOME_ESP8266_GPIO_INITIAL_MODE[i];
 | 
				
			||||||
    uint8_t level = ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[i];
 | 
					    uint8_t level = ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[i];
 | 
				
			||||||
@@ -63,6 +65,7 @@ extern "C" void resetPins() {  // NOLINT
 | 
				
			|||||||
    if (level != 255)
 | 
					    if (level != 255)
 | 
				
			||||||
      digitalWrite(i, level);  // NOLINT
 | 
					      digitalWrite(i, level);  // NOLINT
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,29 @@
 | 
				
			|||||||
import functools
 | 
					import functools
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					import hashlib
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from esphome import core
 | 
					from esphome import core
 | 
				
			||||||
from esphome.components import display
 | 
					from esphome.components import display
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_RAW_DATA_ID, CONF_SIZE
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_FAMILY,
 | 
				
			||||||
 | 
					    CONF_FILE,
 | 
				
			||||||
 | 
					    CONF_GLYPHS,
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_RAW_DATA_ID,
 | 
				
			||||||
 | 
					    CONF_TYPE,
 | 
				
			||||||
 | 
					    CONF_SIZE,
 | 
				
			||||||
 | 
					    CONF_PATH,
 | 
				
			||||||
 | 
					    CONF_WEIGHT,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from esphome.core import CORE, HexInt
 | 
					from esphome.core import CORE, HexInt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DOMAIN = "font"
 | 
				
			||||||
DEPENDENCIES = ["display"]
 | 
					DEPENDENCIES = ["display"]
 | 
				
			||||||
MULTI_CONF = True
 | 
					MULTI_CONF = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -71,6 +88,128 @@ def validate_truetype_file(value):
 | 
				
			|||||||
    return cv.file_(value)
 | 
					    return cv.file_(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _compute_gfonts_local_path(value) -> Path:
 | 
				
			||||||
 | 
					    name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1"
 | 
				
			||||||
 | 
					    base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN
 | 
				
			||||||
 | 
					    h = hashlib.new("sha256")
 | 
				
			||||||
 | 
					    h.update(name.encode())
 | 
				
			||||||
 | 
					    return base_dir / h.hexdigest()[:8] / "font.ttf"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TYPE_LOCAL = "local"
 | 
				
			||||||
 | 
					TYPE_GFONTS = "gfonts"
 | 
				
			||||||
 | 
					LOCAL_SCHEMA = cv.Schema(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.Required(CONF_PATH): validate_truetype_file,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					CONF_ITALIC = "italic"
 | 
				
			||||||
 | 
					FONT_WEIGHTS = {
 | 
				
			||||||
 | 
					    "thin": 100,
 | 
				
			||||||
 | 
					    "extra-light": 200,
 | 
				
			||||||
 | 
					    "light": 300,
 | 
				
			||||||
 | 
					    "regular": 400,
 | 
				
			||||||
 | 
					    "medium": 500,
 | 
				
			||||||
 | 
					    "semi-bold": 600,
 | 
				
			||||||
 | 
					    "bold": 700,
 | 
				
			||||||
 | 
					    "extra-bold": 800,
 | 
				
			||||||
 | 
					    "black": 900,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def validate_weight_name(value):
 | 
				
			||||||
 | 
					    return FONT_WEIGHTS[cv.one_of(*FONT_WEIGHTS, lower=True, space="-")(value)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def download_gfonts(value):
 | 
				
			||||||
 | 
					    wght = value[CONF_WEIGHT]
 | 
				
			||||||
 | 
					    if value[CONF_ITALIC]:
 | 
				
			||||||
 | 
					        wght = f"1,{wght}"
 | 
				
			||||||
 | 
					    name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}"
 | 
				
			||||||
 | 
					    url = f"https://fonts.googleapis.com/css2?family={value[CONF_FAMILY]}:wght@{wght}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    path = _compute_gfonts_local_path(value)
 | 
				
			||||||
 | 
					    if path.is_file():
 | 
				
			||||||
 | 
					        return value
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        req = requests.get(url)
 | 
				
			||||||
 | 
					        req.raise_for_status()
 | 
				
			||||||
 | 
					    except requests.exceptions.RequestException as e:
 | 
				
			||||||
 | 
					        raise cv.Invalid(
 | 
				
			||||||
 | 
					            f"Could not download font for {name}, please check the fonts exists "
 | 
				
			||||||
 | 
					            f"at google fonts ({e})"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    match = re.search(r"src:\s+url\((.+)\)\s+format\('truetype'\);", req.text)
 | 
				
			||||||
 | 
					    if match is None:
 | 
				
			||||||
 | 
					        raise cv.Invalid(
 | 
				
			||||||
 | 
					            f"Could not extract ttf file from gfonts response for {name}, "
 | 
				
			||||||
 | 
					            f"please report this."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ttf_url = match.group(1)
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        req = requests.get(ttf_url)
 | 
				
			||||||
 | 
					        req.raise_for_status()
 | 
				
			||||||
 | 
					    except requests.exceptions.RequestException as e:
 | 
				
			||||||
 | 
					        raise cv.Invalid(f"Could not download ttf file for {name} ({ttf_url}): {e}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    path.parent.mkdir(exist_ok=True, parents=True)
 | 
				
			||||||
 | 
					    path.write_bytes(req.content)
 | 
				
			||||||
 | 
					    return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GFONTS_SCHEMA = cv.All(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.Required(CONF_FAMILY): cv.string_strict,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_WEIGHT, default="regular"): cv.Any(
 | 
				
			||||||
 | 
					            cv.int_, validate_weight_name
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        cv.Optional(CONF_ITALIC, default=False): cv.boolean,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    download_gfonts,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def validate_file_shorthand(value):
 | 
				
			||||||
 | 
					    value = cv.string_strict(value)
 | 
				
			||||||
 | 
					    if value.startswith("gfonts://"):
 | 
				
			||||||
 | 
					        match = re.match(r"^gfonts://([^@]+)(@.+)?$", value)
 | 
				
			||||||
 | 
					        if match is None:
 | 
				
			||||||
 | 
					            raise cv.Invalid("Could not parse gfonts shorthand syntax, please check it")
 | 
				
			||||||
 | 
					        family = match.group(1)
 | 
				
			||||||
 | 
					        weight = match.group(2)
 | 
				
			||||||
 | 
					        data = {
 | 
				
			||||||
 | 
					            CONF_TYPE: TYPE_GFONTS,
 | 
				
			||||||
 | 
					            CONF_FAMILY: family,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if weight is not None:
 | 
				
			||||||
 | 
					            data[CONF_WEIGHT] = weight[1:]
 | 
				
			||||||
 | 
					        return FILE_SCHEMA(data)
 | 
				
			||||||
 | 
					    return FILE_SCHEMA(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            CONF_TYPE: TYPE_LOCAL,
 | 
				
			||||||
 | 
					            CONF_PATH: value,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TYPED_FILE_SCHEMA = cv.typed_schema(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        TYPE_LOCAL: LOCAL_SCHEMA,
 | 
				
			||||||
 | 
					        TYPE_GFONTS: GFONTS_SCHEMA,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _file_schema(value):
 | 
				
			||||||
 | 
					    if isinstance(value, str):
 | 
				
			||||||
 | 
					        return validate_file_shorthand(value)
 | 
				
			||||||
 | 
					    return TYPED_FILE_SCHEMA(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FILE_SCHEMA = cv.Schema(_file_schema)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEFAULT_GLYPHS = (
 | 
					DEFAULT_GLYPHS = (
 | 
				
			||||||
    ' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
 | 
					    ' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -79,7 +218,7 @@ CONF_RAW_GLYPH_ID = "raw_glyph_id"
 | 
				
			|||||||
FONT_SCHEMA = cv.Schema(
 | 
					FONT_SCHEMA = cv.Schema(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        cv.Required(CONF_ID): cv.declare_id(Font),
 | 
					        cv.Required(CONF_ID): cv.declare_id(Font),
 | 
				
			||||||
        cv.Required(CONF_FILE): validate_truetype_file,
 | 
					        cv.Required(CONF_FILE): FILE_SCHEMA,
 | 
				
			||||||
        cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs,
 | 
					        cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs,
 | 
				
			||||||
        cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1),
 | 
					        cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1),
 | 
				
			||||||
        cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
 | 
					        cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
 | 
				
			||||||
@@ -93,9 +232,13 @@ CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA)
 | 
				
			|||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    from PIL import ImageFont
 | 
					    from PIL import ImageFont
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    path = CORE.relative_config_path(config[CONF_FILE])
 | 
					    conf = config[CONF_FILE]
 | 
				
			||||||
 | 
					    if conf[CONF_TYPE] == TYPE_LOCAL:
 | 
				
			||||||
 | 
					        path = CORE.relative_config_path(conf[CONF_PATH])
 | 
				
			||||||
 | 
					    elif conf[CONF_TYPE] == TYPE_GFONTS:
 | 
				
			||||||
 | 
					        path = _compute_gfonts_local_path(conf)
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        font = ImageFont.truetype(path, config[CONF_SIZE])
 | 
					        font = ImageFont.truetype(str(path), config[CONF_SIZE])
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        raise core.EsphomeError(f"Could not load truetype file {path}: {e}")
 | 
					        raise core.EsphomeError(f"Could not load truetype file {path}: {e}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,9 +7,11 @@ namespace growatt_solar {
 | 
				
			|||||||
static const char *const TAG = "growatt_solar";
 | 
					static const char *const TAG = "growatt_solar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04;
 | 
					static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04;
 | 
				
			||||||
static const uint8_t MODBUS_REGISTER_COUNT = 33;
 | 
					static const uint8_t MODBUS_REGISTER_COUNT[] = {33, 95};  // indexed with enum GrowattProtocolVersion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void GrowattSolar::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); }
 | 
					void GrowattSolar::update() {
 | 
				
			||||||
 | 
					  this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT[this->protocol_version_]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) {
 | 
					void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) {
 | 
				
			||||||
  auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void {
 | 
					  auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void {
 | 
				
			||||||
@@ -27,37 +29,76 @@ void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) {
 | 
				
			|||||||
      sensor->publish_state(value);
 | 
					      sensor->publish_state(value);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  publish_1_reg_sensor_state(this->inverter_status_, 0, 1);
 | 
					  switch (this->protocol_version_) {
 | 
				
			||||||
 | 
					    case RTU: {
 | 
				
			||||||
 | 
					      publish_1_reg_sensor_state(this->inverter_status_, 0, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT);
 | 
					      publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT);
 | 
					      publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT);
 | 
				
			||||||
  publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT);
 | 
					      publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT);
 | 
				
			||||||
  publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT);
 | 
					      publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT);
 | 
					      publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT);
 | 
				
			||||||
  publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT);
 | 
					      publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT);
 | 
				
			||||||
  publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT);
 | 
					      publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  publish_2_reg_sensor_state(this->grid_active_power_sensor_, 11, 12, ONE_DEC_UNIT);
 | 
					      publish_2_reg_sensor_state(this->grid_active_power_sensor_, 11, 12, ONE_DEC_UNIT);
 | 
				
			||||||
  publish_1_reg_sensor_state(this->grid_frequency_sensor_, 13, TWO_DEC_UNIT);
 | 
					      publish_1_reg_sensor_state(this->grid_frequency_sensor_, 13, TWO_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 14, ONE_DEC_UNIT);
 | 
					      publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 14, ONE_DEC_UNIT);
 | 
				
			||||||
  publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 15, ONE_DEC_UNIT);
 | 
					      publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 15, ONE_DEC_UNIT);
 | 
				
			||||||
  publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 16, 17, ONE_DEC_UNIT);
 | 
					      publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 16, 17, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 18, ONE_DEC_UNIT);
 | 
					      publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 18, ONE_DEC_UNIT);
 | 
				
			||||||
  publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 19, ONE_DEC_UNIT);
 | 
					      publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 19, ONE_DEC_UNIT);
 | 
				
			||||||
  publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 20, 21, ONE_DEC_UNIT);
 | 
					      publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 20, 21, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 22, ONE_DEC_UNIT);
 | 
					      publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 22, ONE_DEC_UNIT);
 | 
				
			||||||
  publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 23, ONE_DEC_UNIT);
 | 
					      publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 23, ONE_DEC_UNIT);
 | 
				
			||||||
  publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 24, 25, ONE_DEC_UNIT);
 | 
					      publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 24, 25, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  publish_2_reg_sensor_state(this->today_production_, 26, 27, ONE_DEC_UNIT);
 | 
					      publish_2_reg_sensor_state(this->today_production_, 26, 27, ONE_DEC_UNIT);
 | 
				
			||||||
  publish_2_reg_sensor_state(this->total_energy_production_, 28, 29, ONE_DEC_UNIT);
 | 
					      publish_2_reg_sensor_state(this->total_energy_production_, 28, 29, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  publish_1_reg_sensor_state(this->inverter_module_temp_, 32, ONE_DEC_UNIT);
 | 
					      publish_1_reg_sensor_state(this->inverter_module_temp_, 32, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case RTU2: {
 | 
				
			||||||
 | 
					      publish_1_reg_sensor_state(this->inverter_status_, 0, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					      publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					      publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					      publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					      publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      publish_2_reg_sensor_state(this->grid_active_power_sensor_, 35, 36, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					      publish_1_reg_sensor_state(this->grid_frequency_sensor_, 37, TWO_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 38, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					      publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 39, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					      publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 40, 41, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 42, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					      publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 43, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					      publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 44, 45, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 46, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					      publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 47, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					      publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 48, 49, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      publish_2_reg_sensor_state(this->today_production_, 53, 54, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					      publish_2_reg_sensor_state(this->total_energy_production_, 55, 56, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      publish_1_reg_sensor_state(this->inverter_module_temp_, 93, ONE_DEC_UNIT);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void GrowattSolar::dump_config() {
 | 
					void GrowattSolar::dump_config() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,12 +10,19 @@ namespace growatt_solar {
 | 
				
			|||||||
static const float TWO_DEC_UNIT = 0.01;
 | 
					static const float TWO_DEC_UNIT = 0.01;
 | 
				
			||||||
static const float ONE_DEC_UNIT = 0.1;
 | 
					static const float ONE_DEC_UNIT = 0.1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum GrowattProtocolVersion {
 | 
				
			||||||
 | 
					  RTU = 0,
 | 
				
			||||||
 | 
					  RTU2,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GrowattSolar : public PollingComponent, public modbus::ModbusDevice {
 | 
					class GrowattSolar : public PollingComponent, public modbus::ModbusDevice {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  void update() override;
 | 
					  void update() override;
 | 
				
			||||||
  void on_modbus_data(const std::vector<uint8_t> &data) override;
 | 
					  void on_modbus_data(const std::vector<uint8_t> &data) override;
 | 
				
			||||||
  void dump_config() override;
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_protocol_version(GrowattProtocolVersion protocol_version) { this->protocol_version_ = protocol_version; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void set_inverter_status_sensor(sensor::Sensor *sensor) { this->inverter_status_ = sensor; }
 | 
					  void set_inverter_status_sensor(sensor::Sensor *sensor) { this->inverter_status_ = sensor; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void set_grid_frequency_sensor(sensor::Sensor *sensor) { this->grid_frequency_sensor_ = sensor; }
 | 
					  void set_grid_frequency_sensor(sensor::Sensor *sensor) { this->grid_frequency_sensor_ = sensor; }
 | 
				
			||||||
@@ -67,6 +74,7 @@ class GrowattSolar : public PollingComponent, public modbus::ModbusDevice {
 | 
				
			|||||||
  sensor::Sensor *today_production_{nullptr};
 | 
					  sensor::Sensor *today_production_{nullptr};
 | 
				
			||||||
  sensor::Sensor *total_energy_production_{nullptr};
 | 
					  sensor::Sensor *total_energy_production_{nullptr};
 | 
				
			||||||
  sensor::Sensor *inverter_module_temp_{nullptr};
 | 
					  sensor::Sensor *inverter_module_temp_{nullptr};
 | 
				
			||||||
 | 
					  GrowattProtocolVersion protocol_version_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace growatt_solar
 | 
					}  // namespace growatt_solar
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,7 +39,7 @@ UNIT_MILLIAMPERE = "mA"
 | 
				
			|||||||
CONF_INVERTER_STATUS = "inverter_status"
 | 
					CONF_INVERTER_STATUS = "inverter_status"
 | 
				
			||||||
CONF_PV_ACTIVE_POWER = "pv_active_power"
 | 
					CONF_PV_ACTIVE_POWER = "pv_active_power"
 | 
				
			||||||
CONF_INVERTER_MODULE_TEMP = "inverter_module_temp"
 | 
					CONF_INVERTER_MODULE_TEMP = "inverter_module_temp"
 | 
				
			||||||
 | 
					CONF_PROTOCOL_VERSION = "protocol_version"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AUTO_LOAD = ["modbus"]
 | 
					AUTO_LOAD = ["modbus"]
 | 
				
			||||||
CODEOWNERS = ["@leeuwte"]
 | 
					CODEOWNERS = ["@leeuwte"]
 | 
				
			||||||
@@ -95,10 +95,20 @@ PV_SCHEMA = cv.Schema(
 | 
				
			|||||||
    {cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()}
 | 
					    {cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()}
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GrowattProtocolVersion = growatt_solar_ns.enum("GrowattProtocolVersion")
 | 
				
			||||||
 | 
					PROTOCOL_VERSIONS = {
 | 
				
			||||||
 | 
					    "RTU": GrowattProtocolVersion.RTU,
 | 
				
			||||||
 | 
					    "RTU2": GrowattProtocolVersion.RTU2,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = (
 | 
					CONFIG_SCHEMA = (
 | 
				
			||||||
    cv.Schema(
 | 
					    cv.Schema(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            cv.GenerateID(): cv.declare_id(GrowattSolar),
 | 
					            cv.GenerateID(): cv.declare_id(GrowattSolar),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_PROTOCOL_VERSION, default="RTU"): cv.enum(
 | 
				
			||||||
 | 
					                PROTOCOL_VERSIONS, upper=True
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
            cv.Optional(CONF_PHASE_A): PHASE_SCHEMA,
 | 
					            cv.Optional(CONF_PHASE_A): PHASE_SCHEMA,
 | 
				
			||||||
            cv.Optional(CONF_PHASE_B): PHASE_SCHEMA,
 | 
					            cv.Optional(CONF_PHASE_B): PHASE_SCHEMA,
 | 
				
			||||||
            cv.Optional(CONF_PHASE_C): PHASE_SCHEMA,
 | 
					            cv.Optional(CONF_PHASE_C): PHASE_SCHEMA,
 | 
				
			||||||
@@ -152,6 +162,8 @@ async def to_code(config):
 | 
				
			|||||||
    await cg.register_component(var, config)
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
    await modbus.register_modbus_device(var, config)
 | 
					    await modbus.register_modbus_device(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cg.add(var.set_protocol_version(config[CONF_PROTOCOL_VERSION]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if CONF_INVERTER_STATUS in config:
 | 
					    if CONF_INVERTER_STATUS in config:
 | 
				
			||||||
        sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS])
 | 
					        sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS])
 | 
				
			||||||
        cg.add(var.set_inverter_status_sensor(sens))
 | 
					        cg.add(var.set_inverter_status_sensor(sens))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,7 @@ PROTOCOLS = {
 | 
				
			|||||||
    "daikin_arc417": Protocol.PROTOCOL_DAIKIN_ARC417,
 | 
					    "daikin_arc417": Protocol.PROTOCOL_DAIKIN_ARC417,
 | 
				
			||||||
    "daikin_arc480": Protocol.PROTOCOL_DAIKIN_ARC480,
 | 
					    "daikin_arc480": Protocol.PROTOCOL_DAIKIN_ARC480,
 | 
				
			||||||
    "daikin": Protocol.PROTOCOL_DAIKIN,
 | 
					    "daikin": Protocol.PROTOCOL_DAIKIN,
 | 
				
			||||||
 | 
					    "electroluxyal": Protocol.PROTOCOL_ELECTROLUXYAL,
 | 
				
			||||||
    "fuego": Protocol.PROTOCOL_FUEGO,
 | 
					    "fuego": Protocol.PROTOCOL_FUEGO,
 | 
				
			||||||
    "fujitsu_awyz": Protocol.PROTOCOL_FUJITSU_AWYZ,
 | 
					    "fujitsu_awyz": Protocol.PROTOCOL_FUJITSU_AWYZ,
 | 
				
			||||||
    "gree": Protocol.PROTOCOL_GREE,
 | 
					    "gree": Protocol.PROTOCOL_GREE,
 | 
				
			||||||
@@ -112,6 +113,4 @@ def to_code(config):
 | 
				
			|||||||
    cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
 | 
					    cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
 | 
				
			||||||
    cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
 | 
					    cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # PIO isn't updating releases, so referencing the release tag directly. See:
 | 
					    cg.add_library("tonia/HeatpumpIR", "1.0.20")
 | 
				
			||||||
    # https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
 | 
					 | 
				
			||||||
    cg.add_library("", "", "https://github.com/ToniA/arduino-heatpumpir.git#1.0.18")
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
 | 
				
			|||||||
    {PROTOCOL_DAIKIN_ARC417, []() { return new DaikinHeatpumpARC417IR(); }},                 // NOLINT
 | 
					    {PROTOCOL_DAIKIN_ARC417, []() { return new DaikinHeatpumpARC417IR(); }},                 // NOLINT
 | 
				
			||||||
    {PROTOCOL_DAIKIN_ARC480, []() { return new DaikinHeatpumpARC480A14IR(); }},              // NOLINT
 | 
					    {PROTOCOL_DAIKIN_ARC480, []() { return new DaikinHeatpumpARC480A14IR(); }},              // NOLINT
 | 
				
			||||||
    {PROTOCOL_DAIKIN, []() { return new DaikinHeatpumpIR(); }},                              // NOLINT
 | 
					    {PROTOCOL_DAIKIN, []() { return new DaikinHeatpumpIR(); }},                              // NOLINT
 | 
				
			||||||
 | 
					    {PROTOCOL_ELECTROLUXYAL, []() { return new ElectroluxYALHeatpumpIR(); }},                // NOLINT
 | 
				
			||||||
    {PROTOCOL_FUEGO, []() { return new FuegoHeatpumpIR(); }},                                // NOLINT
 | 
					    {PROTOCOL_FUEGO, []() { return new FuegoHeatpumpIR(); }},                                // NOLINT
 | 
				
			||||||
    {PROTOCOL_FUJITSU_AWYZ, []() { return new FujitsuHeatpumpIR(); }},                       // NOLINT
 | 
					    {PROTOCOL_FUJITSU_AWYZ, []() { return new FujitsuHeatpumpIR(); }},                       // NOLINT
 | 
				
			||||||
    {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }},                           // NOLINT
 | 
					    {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }},                           // NOLINT
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ enum Protocol {
 | 
				
			|||||||
  PROTOCOL_DAIKIN_ARC417,
 | 
					  PROTOCOL_DAIKIN_ARC417,
 | 
				
			||||||
  PROTOCOL_DAIKIN_ARC480,
 | 
					  PROTOCOL_DAIKIN_ARC480,
 | 
				
			||||||
  PROTOCOL_DAIKIN,
 | 
					  PROTOCOL_DAIKIN,
 | 
				
			||||||
 | 
					  PROTOCOL_ELECTROLUXYAL,
 | 
				
			||||||
  PROTOCOL_FUEGO,
 | 
					  PROTOCOL_FUEGO,
 | 
				
			||||||
  PROTOCOL_FUJITSU_AWYZ,
 | 
					  PROTOCOL_FUJITSU_AWYZ,
 | 
				
			||||||
  PROTOCOL_GREE,
 | 
					  PROTOCOL_GREE,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ namespace hm3301 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class AbstractAQICalculator {
 | 
					class AbstractAQICalculator {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  virtual uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0;
 | 
					  virtual uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace hm3301
 | 
					}  // namespace hm3301
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ namespace hm3301 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class AQICalculator : public AbstractAQICalculator {
 | 
					class AQICalculator : public AbstractAQICalculator {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
 | 
					  uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
 | 
				
			||||||
    int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_);
 | 
					    int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_);
 | 
				
			||||||
    int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_);
 | 
					    int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ namespace hm3301 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class CAQICalculator : public AbstractAQICalculator {
 | 
					class CAQICalculator : public AbstractAQICalculator {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
 | 
					  uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
 | 
				
			||||||
    int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_);
 | 
					    int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_);
 | 
				
			||||||
    int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_);
 | 
					    int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,7 +62,7 @@ void HM3301Component::update() {
 | 
				
			|||||||
    pm_10_0_value = get_sensor_value_(data_buffer_, PM_10_0_VALUE_INDEX);
 | 
					    pm_10_0_value = get_sensor_value_(data_buffer_, PM_10_0_VALUE_INDEX);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  int8_t aqi_value = -1;
 | 
					  int16_t aqi_value = -1;
 | 
				
			||||||
  if (this->aqi_sensor_ != nullptr && pm_2_5_value != -1 && pm_10_0_value != -1) {
 | 
					  if (this->aqi_sensor_ != nullptr && pm_2_5_value != -1 && pm_10_0_value != -1) {
 | 
				
			||||||
    AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_);
 | 
					    AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_);
 | 
				
			||||||
    aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value);
 | 
					    aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,20 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_INTERNAL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@OttoWinter"]
 | 
					CODEOWNERS = ["@OttoWinter"]
 | 
				
			||||||
homeassistant_ns = cg.esphome_ns.namespace("homeassistant")
 | 
					homeassistant_ns = cg.esphome_ns.namespace("homeassistant")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.Required(CONF_ENTITY_ID): cv.entity_id,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_ATTRIBUTE): cv.string,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_INTERNAL, default=True): cv.boolean,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def setup_home_assistant_entity(var, config):
 | 
				
			||||||
 | 
					    cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
 | 
				
			||||||
 | 
					    if CONF_ATTRIBUTE in config:
 | 
				
			||||||
 | 
					        cg.add(var.set_attribute(config[CONF_ATTRIBUTE]))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,30 +1,24 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					 | 
				
			||||||
from esphome.components import binary_sensor
 | 
					from esphome.components import binary_sensor
 | 
				
			||||||
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID
 | 
					
 | 
				
			||||||
from .. import homeassistant_ns
 | 
					from .. import (
 | 
				
			||||||
 | 
					    HOME_ASSISTANT_IMPORT_SCHEMA,
 | 
				
			||||||
 | 
					    homeassistant_ns,
 | 
				
			||||||
 | 
					    setup_home_assistant_entity,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["api"]
 | 
					DEPENDENCIES = ["api"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
HomeassistantBinarySensor = homeassistant_ns.class_(
 | 
					HomeassistantBinarySensor = homeassistant_ns.class_(
 | 
				
			||||||
    "HomeassistantBinarySensor", binary_sensor.BinarySensor, cg.Component
 | 
					    "HomeassistantBinarySensor", binary_sensor.BinarySensor, cg.Component
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = (
 | 
					CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(HomeassistantBinarySensor).extend(
 | 
				
			||||||
    binary_sensor.binary_sensor_schema(HomeassistantBinarySensor)
 | 
					    HOME_ASSISTANT_IMPORT_SCHEMA
 | 
				
			||||||
    .extend(
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            cv.Required(CONF_ENTITY_ID): cv.entity_id,
 | 
					 | 
				
			||||||
            cv.Optional(CONF_ATTRIBUTE): cv.string,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    var = await binary_sensor.new_binary_sensor(config)
 | 
					    var = await binary_sensor.new_binary_sensor(config)
 | 
				
			||||||
    await cg.register_component(var, config)
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					    setup_home_assistant_entity(var, config)
 | 
				
			||||||
    cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
 | 
					 | 
				
			||||||
    if CONF_ATTRIBUTE in config:
 | 
					 | 
				
			||||||
        cg.add(var.set_attribute(config[CONF_ATTRIBUTE]))
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,11 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					 | 
				
			||||||
from esphome.components import sensor
 | 
					from esphome.components import sensor
 | 
				
			||||||
from esphome.const import (
 | 
					
 | 
				
			||||||
    CONF_ATTRIBUTE,
 | 
					from .. import (
 | 
				
			||||||
    CONF_ENTITY_ID,
 | 
					    HOME_ASSISTANT_IMPORT_SCHEMA,
 | 
				
			||||||
    CONF_ID,
 | 
					    homeassistant_ns,
 | 
				
			||||||
 | 
					    setup_home_assistant_entity,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .. import homeassistant_ns
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["api"]
 | 
					DEPENDENCIES = ["api"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -14,19 +13,12 @@ HomeassistantSensor = homeassistant_ns.class_(
 | 
				
			|||||||
    "HomeassistantSensor", sensor.Sensor, cg.Component
 | 
					    "HomeassistantSensor", sensor.Sensor, cg.Component
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1,).extend(
 | 
					CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1).extend(
 | 
				
			||||||
    {
 | 
					    HOME_ASSISTANT_IMPORT_SCHEMA
 | 
				
			||||||
        cv.Required(CONF_ENTITY_ID): cv.entity_id,
 | 
					 | 
				
			||||||
        cv.Optional(CONF_ATTRIBUTE): cv.string,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
					    var = await sensor.new_sensor(config)
 | 
				
			||||||
    await cg.register_component(var, config)
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
    await sensor.register_sensor(var, config)
 | 
					    setup_home_assistant_entity(var, config)
 | 
				
			||||||
 | 
					 | 
				
			||||||
    cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
 | 
					 | 
				
			||||||
    if CONF_ATTRIBUTE in config:
 | 
					 | 
				
			||||||
        cg.add(var.set_attribute(config[CONF_ATTRIBUTE]))
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,11 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					 | 
				
			||||||
from esphome.components import text_sensor
 | 
					from esphome.components import text_sensor
 | 
				
			||||||
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .. import homeassistant_ns
 | 
					from .. import (
 | 
				
			||||||
 | 
					    HOME_ASSISTANT_IMPORT_SCHEMA,
 | 
				
			||||||
 | 
					    homeassistant_ns,
 | 
				
			||||||
 | 
					    setup_home_assistant_entity,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["api"]
 | 
					DEPENDENCIES = ["api"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -11,19 +13,12 @@ HomeassistantTextSensor = homeassistant_ns.class_(
 | 
				
			|||||||
    "HomeassistantTextSensor", text_sensor.TextSensor, cg.Component
 | 
					    "HomeassistantTextSensor", text_sensor.TextSensor, cg.Component
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend(
 | 
					CONFIG_SCHEMA = text_sensor.text_sensor_schema(HomeassistantTextSensor).extend(
 | 
				
			||||||
    {
 | 
					    HOME_ASSISTANT_IMPORT_SCHEMA
 | 
				
			||||||
        cv.GenerateID(): cv.declare_id(HomeassistantTextSensor),
 | 
					 | 
				
			||||||
        cv.Required(CONF_ENTITY_ID): cv.entity_id,
 | 
					 | 
				
			||||||
        cv.Optional(CONF_ATTRIBUTE): cv.string,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def to_code(config):
 | 
					async def to_code(config):
 | 
				
			||||||
    var = await text_sensor.new_text_sensor(config)
 | 
					    var = await text_sensor.new_text_sensor(config)
 | 
				
			||||||
    await cg.register_component(var, config)
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					    setup_home_assistant_entity(var, config)
 | 
				
			||||||
    cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
 | 
					 | 
				
			||||||
    if CONF_ATTRIBUTE in config:
 | 
					 | 
				
			||||||
        cg.add(var.set_attribute(config[CONF_ATTRIBUTE]))
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								esphome/components/hydreon_rgxx/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								esphome/components/hydreon_rgxx/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					from esphome.components import uart
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CODEOWNERS = ["@functionpointer"]
 | 
				
			||||||
 | 
					DEPENDENCIES = ["uart"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					hydreon_rgxx_ns = cg.esphome_ns.namespace("hydreon_rgxx")
 | 
				
			||||||
 | 
					RGModel = hydreon_rgxx_ns.enum("RGModel")
 | 
				
			||||||
 | 
					HydreonRGxxComponent = hydreon_rgxx_ns.class_(
 | 
				
			||||||
 | 
					    "HydreonRGxxComponent", cg.PollingComponent, uart.UARTDevice
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										36
									
								
								esphome/components/hydreon_rgxx/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								esphome/components/hydreon_rgxx/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import binary_sensor
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_COLD,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from . import hydreon_rgxx_ns, HydreonRGxxComponent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_HYDREON_RGXX_ID = "hydreon_rgxx_id"
 | 
				
			||||||
 | 
					CONF_TOO_COLD = "too_cold"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HydreonRGxxBinarySensor = hydreon_rgxx_ns.class_(
 | 
				
			||||||
 | 
					    "HydreonRGxxBinaryComponent", cg.Component
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.Schema(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        cv.GenerateID(): cv.declare_id(HydreonRGxxBinarySensor),
 | 
				
			||||||
 | 
					        cv.GenerateID(CONF_HYDREON_RGXX_ID): cv.use_id(HydreonRGxxComponent),
 | 
				
			||||||
 | 
					        cv.Optional(CONF_TOO_COLD): binary_sensor.binary_sensor_schema(
 | 
				
			||||||
 | 
					            device_class=DEVICE_CLASS_COLD
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    main_sensor = await cg.get_variable(config[CONF_HYDREON_RGXX_ID])
 | 
				
			||||||
 | 
					    bin_component = cg.new_Pvariable(config[CONF_ID], main_sensor)
 | 
				
			||||||
 | 
					    await cg.register_component(bin_component, config)
 | 
				
			||||||
 | 
					    if CONF_TOO_COLD in config:
 | 
				
			||||||
 | 
					        tc = await binary_sensor.new_binary_sensor(config[CONF_TOO_COLD])
 | 
				
			||||||
 | 
					        cg.add(main_sensor.set_too_cold_sensor(tc))
 | 
				
			||||||
							
								
								
									
										211
									
								
								esphome/components/hydreon_rgxx/hydreon_rgxx.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								esphome/components/hydreon_rgxx/hydreon_rgxx.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,211 @@
 | 
				
			|||||||
 | 
					#include "hydreon_rgxx.h"
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace hydreon_rgxx {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char *const TAG = "hydreon_rgxx.sensor";
 | 
				
			||||||
 | 
					static const int MAX_DATA_LENGTH_BYTES = 80;
 | 
				
			||||||
 | 
					static const uint8_t ASCII_LF = 0x0A;
 | 
				
			||||||
 | 
					#define HYDREON_RGXX_COMMA ,
 | 
				
			||||||
 | 
					static const char *const PROTOCOL_NAMES[] = {HYDREON_RGXX_PROTOCOL_LIST(, HYDREON_RGXX_COMMA)};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HydreonRGxxComponent::dump_config() {
 | 
				
			||||||
 | 
					  this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8);
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "hydreon_rgxx:");
 | 
				
			||||||
 | 
					  if (this->is_failed()) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "Connection with hydreon_rgxx failed!");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  LOG_UPDATE_INTERVAL(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int i = 0;
 | 
				
			||||||
 | 
					#define HYDREON_RGXX_LOG_SENSOR(s) \
 | 
				
			||||||
 | 
					  if (this->sensors_[i++] != nullptr) { \
 | 
				
			||||||
 | 
					    LOG_SENSOR("  ", #s, this->sensors_[i - 1]); \
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  HYDREON_RGXX_PROTOCOL_LIST(HYDREON_RGXX_LOG_SENSOR, );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HydreonRGxxComponent::setup() {
 | 
				
			||||||
 | 
					  ESP_LOGCONFIG(TAG, "Setting up hydreon_rgxx...");
 | 
				
			||||||
 | 
					  while (this->available() != 0) {
 | 
				
			||||||
 | 
					    this->read();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  this->schedule_reboot_();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool HydreonRGxxComponent::sensor_missing_() {
 | 
				
			||||||
 | 
					  if (this->sensors_received_ == -1) {
 | 
				
			||||||
 | 
					    // no request sent yet, don't check
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    if (this->sensors_received_ == 0) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "No data at all");
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (int i = 0; i < NUM_SENSORS; i++) {
 | 
				
			||||||
 | 
					      if (this->sensors_[i] == nullptr) {
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if ((this->sensors_received_ >> i & 1) == 0) {
 | 
				
			||||||
 | 
					        ESP_LOGW(TAG, "Missing %s", PROTOCOL_NAMES[i]);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HydreonRGxxComponent::update() {
 | 
				
			||||||
 | 
					  if (this->boot_count_ > 0) {
 | 
				
			||||||
 | 
					    if (this->sensor_missing_()) {
 | 
				
			||||||
 | 
					      this->no_response_count_++;
 | 
				
			||||||
 | 
					      ESP_LOGE(TAG, "data missing %d times", this->no_response_count_);
 | 
				
			||||||
 | 
					      if (this->no_response_count_ > 15) {
 | 
				
			||||||
 | 
					        ESP_LOGE(TAG, "asking sensor to reboot");
 | 
				
			||||||
 | 
					        for (auto &sensor : this->sensors_) {
 | 
				
			||||||
 | 
					          if (sensor != nullptr) {
 | 
				
			||||||
 | 
					            sensor->publish_state(NAN);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this->schedule_reboot_();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this->no_response_count_ = 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this->write_str("R\n");
 | 
				
			||||||
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
 | 
					    if (this->too_cold_sensor_ != nullptr) {
 | 
				
			||||||
 | 
					      this->too_cold_sensor_->publish_state(this->too_cold_);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    this->too_cold_ = false;
 | 
				
			||||||
 | 
					    this->sensors_received_ = 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HydreonRGxxComponent::loop() {
 | 
				
			||||||
 | 
					  uint8_t data;
 | 
				
			||||||
 | 
					  while (this->available() > 0) {
 | 
				
			||||||
 | 
					    if (this->read_byte(&data)) {
 | 
				
			||||||
 | 
					      buffer_ += (char) data;
 | 
				
			||||||
 | 
					      if (this->buffer_.back() == static_cast<char>(ASCII_LF) || this->buffer_.length() >= MAX_DATA_LENGTH_BYTES) {
 | 
				
			||||||
 | 
					        // complete line received
 | 
				
			||||||
 | 
					        this->process_line_();
 | 
				
			||||||
 | 
					        this->buffer_.clear();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Communication with the sensor is asynchronous.
 | 
				
			||||||
 | 
					 * We send requests and let esphome continue doing its thing.
 | 
				
			||||||
 | 
					 * Once we have received a complete line, we process it.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Catching communication failures is done in two layers:
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 1. We check if all requested data has been received
 | 
				
			||||||
 | 
					 *    before we send out the next request. If data keeps
 | 
				
			||||||
 | 
					 *    missing, we escalate.
 | 
				
			||||||
 | 
					 * 2. Request the sensor to reboot. We retry based on
 | 
				
			||||||
 | 
					 *    a timeout. If the sensor does not respond after
 | 
				
			||||||
 | 
					 *    several boot attempts, we give up.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void HydreonRGxxComponent::schedule_reboot_() {
 | 
				
			||||||
 | 
					  this->boot_count_ = 0;
 | 
				
			||||||
 | 
					  this->set_interval("reboot", 5000, [this]() {
 | 
				
			||||||
 | 
					    if (this->boot_count_ < 0) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "hydreon_rgxx failed to boot %d times", -this->boot_count_);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this->boot_count_--;
 | 
				
			||||||
 | 
					    this->write_str("K\n");
 | 
				
			||||||
 | 
					    if (this->boot_count_ < -5) {
 | 
				
			||||||
 | 
					      ESP_LOGE(TAG, "hydreon_rgxx can't boot, giving up");
 | 
				
			||||||
 | 
					      for (auto &sensor : this->sensors_) {
 | 
				
			||||||
 | 
					        if (sensor != nullptr) {
 | 
				
			||||||
 | 
					          sensor->publish_state(NAN);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this->mark_failed();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool HydreonRGxxComponent::buffer_starts_with_(const std::string &prefix) {
 | 
				
			||||||
 | 
					  return this->buffer_starts_with_(prefix.c_str());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool HydreonRGxxComponent::buffer_starts_with_(const char *prefix) { return buffer_.rfind(prefix, 0) == 0; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void HydreonRGxxComponent::process_line_() {
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "Read from serial: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (buffer_[0] == ';') {
 | 
				
			||||||
 | 
					    ESP_LOGI(TAG, "Comment: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (this->buffer_starts_with_("PwrDays")) {
 | 
				
			||||||
 | 
					    if (this->boot_count_ <= 0) {
 | 
				
			||||||
 | 
					      this->boot_count_ = 1;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this->boot_count_++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this->cancel_interval("reboot");
 | 
				
			||||||
 | 
					    this->no_response_count_ = 0;
 | 
				
			||||||
 | 
					    ESP_LOGI(TAG, "Boot detected: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
 | 
				
			||||||
 | 
					    this->write_str("P\nH\nM\n");  // set sensor to polling mode, high res mode, metric mode
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (this->buffer_starts_with_("SW")) {
 | 
				
			||||||
 | 
					    std::string::size_type majend = this->buffer_.find('.');
 | 
				
			||||||
 | 
					    std::string::size_type endversion = this->buffer_.find(' ', 3);
 | 
				
			||||||
 | 
					    if (majend == std::string::npos || endversion == std::string::npos || majend > endversion) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "invalid version string: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    int major = strtol(this->buffer_.substr(3, majend - 3).c_str(), nullptr, 10);
 | 
				
			||||||
 | 
					    int minor = strtol(this->buffer_.substr(majend + 1, endversion - (majend + 1)).c_str(), nullptr, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (major > 10 || minor >= 1000 || minor < 0 || major < 0) {
 | 
				
			||||||
 | 
					      ESP_LOGW(TAG, "invalid version: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this->sw_version_ = major * 1000 + minor;
 | 
				
			||||||
 | 
					    ESP_LOGI(TAG, "detected sw version %i", this->sw_version_);
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  bool is_data_line = false;
 | 
				
			||||||
 | 
					  for (int i = 0; i < NUM_SENSORS; i++) {
 | 
				
			||||||
 | 
					    if (this->sensors_[i] != nullptr && this->buffer_starts_with_(PROTOCOL_NAMES[i])) {
 | 
				
			||||||
 | 
					      is_data_line = true;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (is_data_line) {
 | 
				
			||||||
 | 
					    std::string::size_type tc = this->buffer_.find("TooCold");
 | 
				
			||||||
 | 
					    this->too_cold_ |= tc != std::string::npos;
 | 
				
			||||||
 | 
					    if (this->too_cold_) {
 | 
				
			||||||
 | 
					      ESP_LOGD(TAG, "Received TooCold");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for (int i = 0; i < NUM_SENSORS; i++) {
 | 
				
			||||||
 | 
					      if (this->sensors_[i] == nullptr) {
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      std::string::size_type n = this->buffer_.find(PROTOCOL_NAMES[i]);
 | 
				
			||||||
 | 
					      if (n == std::string::npos) {
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      int data = strtol(this->buffer_.substr(n + strlen(PROTOCOL_NAMES[i])).c_str(), nullptr, 10);
 | 
				
			||||||
 | 
					      this->sensors_[i]->publish_state(data);
 | 
				
			||||||
 | 
					      ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state());
 | 
				
			||||||
 | 
					      this->sensors_received_ |= (1 << i);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    ESP_LOGI(TAG, "Got unknown line: %s", this->buffer_.c_str());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					float HydreonRGxxComponent::get_setup_priority() const { return setup_priority::DATA; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace hydreon_rgxx
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										76
									
								
								esphome/components/hydreon_rgxx/hydreon_rgxx.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								esphome/components/hydreon_rgxx/hydreon_rgxx.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/core/defines.h"
 | 
				
			||||||
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
 | 
					#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#include "esphome/components/uart/uart.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome {
 | 
				
			||||||
 | 
					namespace hydreon_rgxx {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum RGModel {
 | 
				
			||||||
 | 
					  RG9 = 1,
 | 
				
			||||||
 | 
					  RG15 = 2,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef HYDREON_RGXX_NUM_SENSORS
 | 
				
			||||||
 | 
					static const uint8_t NUM_SENSORS = HYDREON_RGXX_NUM_SENSORS;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					static const uint8_t NUM_SENSORS = 1;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef HYDREON_RGXX_PROTOCOL_LIST
 | 
				
			||||||
 | 
					#define HYDREON_RGXX_PROTOCOL_LIST(F, SEP) F("")
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  void set_sensor(sensor::Sensor *sensor, int index) { this->sensors_[index] = sensor; }
 | 
				
			||||||
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
 | 
					  void set_too_cold_sensor(binary_sensor::BinarySensor *sensor) { this->too_cold_sensor_ = sensor; }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					  void set_model(RGModel model) { model_ = model; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Schedule data readings.
 | 
				
			||||||
 | 
					  void update() override;
 | 
				
			||||||
 | 
					  /// Read data once available
 | 
				
			||||||
 | 
					  void loop() override;
 | 
				
			||||||
 | 
					  /// Setup the sensor and test for a connection.
 | 
				
			||||||
 | 
					  void setup() override;
 | 
				
			||||||
 | 
					  void dump_config() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  float get_setup_priority() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 protected:
 | 
				
			||||||
 | 
					  void process_line_();
 | 
				
			||||||
 | 
					  void schedule_reboot_();
 | 
				
			||||||
 | 
					  bool buffer_starts_with_(const std::string &prefix);
 | 
				
			||||||
 | 
					  bool buffer_starts_with_(const char *prefix);
 | 
				
			||||||
 | 
					  bool sensor_missing_();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  sensor::Sensor *sensors_[NUM_SENSORS] = {nullptr};
 | 
				
			||||||
 | 
					#ifdef USE_BINARY_SENSOR
 | 
				
			||||||
 | 
					  binary_sensor::BinarySensor *too_cold_sensor_ = nullptr;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  int16_t boot_count_ = 0;
 | 
				
			||||||
 | 
					  int16_t no_response_count_ = 0;
 | 
				
			||||||
 | 
					  std::string buffer_;
 | 
				
			||||||
 | 
					  RGModel model_ = RG9;
 | 
				
			||||||
 | 
					  int sw_version_ = 0;
 | 
				
			||||||
 | 
					  bool too_cold_ = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // bit field showing which sensors we have received data for
 | 
				
			||||||
 | 
					  int sensors_received_ = -1;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HydreonRGxxBinaryComponent : public Component {
 | 
				
			||||||
 | 
					 public:
 | 
				
			||||||
 | 
					  HydreonRGxxBinaryComponent(HydreonRGxxComponent *parent) {}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace hydreon_rgxx
 | 
				
			||||||
 | 
					}  // namespace esphome
 | 
				
			||||||
							
								
								
									
										119
									
								
								esphome/components/hydreon_rgxx/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								esphome/components/hydreon_rgxx/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import uart, sensor
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    CONF_ID,
 | 
				
			||||||
 | 
					    CONF_MODEL,
 | 
				
			||||||
 | 
					    CONF_MOISTURE,
 | 
				
			||||||
 | 
					    DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from . import RGModel, HydreonRGxxComponent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					UNIT_INTENSITY = "intensity"
 | 
				
			||||||
 | 
					UNIT_MILLIMETERS = "mm"
 | 
				
			||||||
 | 
					UNIT_MILLIMETERS_PER_HOUR = "mm/h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_ACC = "acc"
 | 
				
			||||||
 | 
					CONF_EVENT_ACC = "event_acc"
 | 
				
			||||||
 | 
					CONF_TOTAL_ACC = "total_acc"
 | 
				
			||||||
 | 
					CONF_R_INT = "r_int"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RG_MODELS = {
 | 
				
			||||||
 | 
					    "RG_9": RGModel.RG9,
 | 
				
			||||||
 | 
					    "RG_15": RGModel.RG15,
 | 
				
			||||||
 | 
					    # https://rainsensors.com/wp-content/uploads/sites/3/2020/07/rg-15_instructions_sw_1.000.pdf
 | 
				
			||||||
 | 
					    # https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2020.08.25-rg-9_instructions.pdf
 | 
				
			||||||
 | 
					    # https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2021.03.11-rg-9_instructions.pdf
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					SUPPORTED_SENSORS = {
 | 
				
			||||||
 | 
					    CONF_ACC: ["RG_15"],
 | 
				
			||||||
 | 
					    CONF_EVENT_ACC: ["RG_15"],
 | 
				
			||||||
 | 
					    CONF_TOTAL_ACC: ["RG_15"],
 | 
				
			||||||
 | 
					    CONF_R_INT: ["RG_15"],
 | 
				
			||||||
 | 
					    CONF_MOISTURE: ["RG_9"],
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					PROTOCOL_NAMES = {
 | 
				
			||||||
 | 
					    CONF_MOISTURE: "R",
 | 
				
			||||||
 | 
					    CONF_ACC: "Acc",
 | 
				
			||||||
 | 
					    CONF_R_INT: "Rint",
 | 
				
			||||||
 | 
					    CONF_EVENT_ACC: "EventAcc",
 | 
				
			||||||
 | 
					    CONF_TOTAL_ACC: "TotalAcc",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _validate(config):
 | 
				
			||||||
 | 
					    for conf, models in SUPPORTED_SENSORS.items():
 | 
				
			||||||
 | 
					        if conf in config:
 | 
				
			||||||
 | 
					            if config[CONF_MODEL] not in models:
 | 
				
			||||||
 | 
					                raise cv.Invalid(
 | 
				
			||||||
 | 
					                    f"{conf} is only available on {' and '.join(models)}, not {config[CONF_MODEL]}"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					    return config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.All(
 | 
				
			||||||
 | 
					    cv.Schema(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            cv.GenerateID(): cv.declare_id(HydreonRGxxComponent),
 | 
				
			||||||
 | 
					            cv.Required(CONF_MODEL): cv.enum(
 | 
				
			||||||
 | 
					                RG_MODELS,
 | 
				
			||||||
 | 
					                upper=True,
 | 
				
			||||||
 | 
					                space="_",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_ACC): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_MILLIMETERS,
 | 
				
			||||||
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_EVENT_ACC): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_MILLIMETERS,
 | 
				
			||||||
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_TOTAL_ACC): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_MILLIMETERS,
 | 
				
			||||||
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_R_INT): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_MILLIMETERS_PER_HOUR,
 | 
				
			||||||
 | 
					                accuracy_decimals=2,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
 | 
				
			||||||
 | 
					                unit_of_measurement=UNIT_INTENSITY,
 | 
				
			||||||
 | 
					                accuracy_decimals=0,
 | 
				
			||||||
 | 
					                device_class=DEVICE_CLASS_HUMIDITY,
 | 
				
			||||||
 | 
					                state_class=STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .extend(cv.polling_component_schema("60s"))
 | 
				
			||||||
 | 
					    .extend(uart.UART_DEVICE_SCHEMA),
 | 
				
			||||||
 | 
					    _validate,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					    var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
 | 
					    await uart.register_uart_device(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cg.add_define(
 | 
				
			||||||
 | 
					        "HYDREON_RGXX_PROTOCOL_LIST(F, sep)",
 | 
				
			||||||
 | 
					        cg.RawExpression(
 | 
				
			||||||
 | 
					            " sep ".join([f'F("{name}")' for name in PROTOCOL_NAMES.values()])
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    cg.add_define("HYDREON_RGXX_NUM_SENSORS", len(PROTOCOL_NAMES))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for i, conf in enumerate(PROTOCOL_NAMES):
 | 
				
			||||||
 | 
					        if conf in config:
 | 
				
			||||||
 | 
					            sens = await sensor.new_sensor(config[conf])
 | 
				
			||||||
 | 
					            cg.add(var.set_sensor(sens, i))
 | 
				
			||||||
@@ -46,21 +46,21 @@ class I2CDevice {
 | 
				
			|||||||
  I2CRegister reg(uint8_t a_register) { return {this, a_register}; }
 | 
					  I2CRegister reg(uint8_t a_register) { return {this, a_register}; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ErrorCode read(uint8_t *data, size_t len) { return bus_->read(address_, data, len); }
 | 
					  ErrorCode read(uint8_t *data, size_t len) { return bus_->read(address_, data, len); }
 | 
				
			||||||
  ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len) {
 | 
					  ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop = true) {
 | 
				
			||||||
    ErrorCode err = this->write(&a_register, 1);
 | 
					    ErrorCode err = this->write(&a_register, 1, stop);
 | 
				
			||||||
    if (err != ERROR_OK)
 | 
					    if (err != ERROR_OK)
 | 
				
			||||||
      return err;
 | 
					      return err;
 | 
				
			||||||
    return this->read(data, len);
 | 
					    return this->read(data, len);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ErrorCode write(const uint8_t *data, uint8_t len) { return bus_->write(address_, data, len); }
 | 
					  ErrorCode write(const uint8_t *data, uint8_t len, bool stop = true) { return bus_->write(address_, data, len, stop); }
 | 
				
			||||||
  ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len) {
 | 
					  ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop = true) {
 | 
				
			||||||
    WriteBuffer buffers[2];
 | 
					    WriteBuffer buffers[2];
 | 
				
			||||||
    buffers[0].data = &a_register;
 | 
					    buffers[0].data = &a_register;
 | 
				
			||||||
    buffers[0].len = 1;
 | 
					    buffers[0].len = 1;
 | 
				
			||||||
    buffers[1].data = data;
 | 
					    buffers[1].data = data;
 | 
				
			||||||
    buffers[1].len = len;
 | 
					    buffers[1].len = len;
 | 
				
			||||||
    return bus_->writev(address_, buffers, 2);
 | 
					    return bus_->writev(address_, buffers, 2, stop);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Compat APIs
 | 
					  // Compat APIs
 | 
				
			||||||
@@ -93,7 +93,9 @@ class I2CDevice {
 | 
				
			|||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool read_byte(uint8_t a_register, uint8_t *data) { return read_register(a_register, data, 1) == ERROR_OK; }
 | 
					  bool read_byte(uint8_t a_register, uint8_t *data, bool stop = true) {
 | 
				
			||||||
 | 
					    return read_register(a_register, data, 1, stop) == ERROR_OK;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  optional<uint8_t> read_byte(uint8_t a_register) {
 | 
					  optional<uint8_t> read_byte(uint8_t a_register) {
 | 
				
			||||||
    uint8_t data;
 | 
					    uint8_t data;
 | 
				
			||||||
@@ -104,8 +106,8 @@ class I2CDevice {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  bool read_byte_16(uint8_t a_register, uint16_t *data) { return read_bytes_16(a_register, data, 1); }
 | 
					  bool read_byte_16(uint8_t a_register, uint16_t *data) { return read_bytes_16(a_register, data, 1); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) {
 | 
					  bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len, bool stop = true) {
 | 
				
			||||||
    return write_register(a_register, data, len) == ERROR_OK;
 | 
					    return write_register(a_register, data, len, stop) == ERROR_OK;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) {
 | 
					  bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) {
 | 
				
			||||||
@@ -118,7 +120,9 @@ class I2CDevice {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len);
 | 
					  bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool write_byte(uint8_t a_register, uint8_t data) { return write_bytes(a_register, &data, 1); }
 | 
					  bool write_byte(uint8_t a_register, uint8_t data, bool stop = true) {
 | 
				
			||||||
 | 
					    return write_bytes(a_register, &data, 1, stop);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool write_byte_16(uint8_t a_register, uint16_t data) { return write_bytes_16(a_register, &data, 1); }
 | 
					  bool write_byte_16(uint8_t a_register, uint16_t data) { return write_bytes_16(a_register, &data, 1); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@ enum ErrorCode {
 | 
				
			|||||||
  ERROR_NOT_INITIALIZED = 4,
 | 
					  ERROR_NOT_INITIALIZED = 4,
 | 
				
			||||||
  ERROR_TOO_LARGE = 5,
 | 
					  ERROR_TOO_LARGE = 5,
 | 
				
			||||||
  ERROR_UNKNOWN = 6,
 | 
					  ERROR_UNKNOWN = 6,
 | 
				
			||||||
 | 
					  ERROR_CRC = 7,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct ReadBuffer {
 | 
					struct ReadBuffer {
 | 
				
			||||||
@@ -36,12 +37,18 @@ class I2CBus {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  virtual ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) = 0;
 | 
					  virtual ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) = 0;
 | 
				
			||||||
  virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len) {
 | 
					  virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len) {
 | 
				
			||||||
 | 
					    return write(address, buffer, len, true);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len, bool stop) {
 | 
				
			||||||
    WriteBuffer buf;
 | 
					    WriteBuffer buf;
 | 
				
			||||||
    buf.data = buffer;
 | 
					    buf.data = buffer;
 | 
				
			||||||
    buf.len = len;
 | 
					    buf.len = len;
 | 
				
			||||||
    return writev(address, &buf, 1);
 | 
					    return writev(address, &buf, 1, stop);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) = 0;
 | 
					  virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) {
 | 
				
			||||||
 | 
					    return writev(address, buffers, cnt, true);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  void i2c_scan_() {
 | 
					  void i2c_scan_() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -104,7 +104,7 @@ ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return ERROR_OK;
 | 
					  return ERROR_OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) {
 | 
					ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) {
 | 
				
			||||||
  // logging is only enabled with vv level, if warnings are shown the caller
 | 
					  // logging is only enabled with vv level, if warnings are shown the caller
 | 
				
			||||||
  // should log them
 | 
					  // should log them
 | 
				
			||||||
  if (!initialized_) {
 | 
					  if (!initialized_) {
 | 
				
			||||||
@@ -139,7 +139,7 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn
 | 
				
			|||||||
      return ERROR_UNKNOWN;
 | 
					      return ERROR_UNKNOWN;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  uint8_t status = wire_->endTransmission(true);
 | 
					  uint8_t status = wire_->endTransmission(stop);
 | 
				
			||||||
  if (status == 0) {
 | 
					  if (status == 0) {
 | 
				
			||||||
    return ERROR_OK;
 | 
					    return ERROR_OK;
 | 
				
			||||||
  } else if (status == 1) {
 | 
					  } else if (status == 1) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,7 @@ class ArduinoI2CBus : public I2CBus, public Component {
 | 
				
			|||||||
  void setup() override;
 | 
					  void setup() override;
 | 
				
			||||||
  void dump_config() override;
 | 
					  void dump_config() override;
 | 
				
			||||||
  ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override;
 | 
					  ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override;
 | 
				
			||||||
  ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override;
 | 
					  ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) override;
 | 
				
			||||||
  float get_setup_priority() const override { return setup_priority::BUS; }
 | 
					  float get_setup_priority() const override { return setup_priority::BUS; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void set_scan(bool scan) { scan_ = scan; }
 | 
					  void set_scan(bool scan) { scan_ = scan; }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -142,7 +142,7 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return ERROR_OK;
 | 
					  return ERROR_OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) {
 | 
					ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) {
 | 
				
			||||||
  // logging is only enabled with vv level, if warnings are shown the caller
 | 
					  // logging is only enabled with vv level, if warnings are shown the caller
 | 
				
			||||||
  // should log them
 | 
					  // should log them
 | 
				
			||||||
  if (!initialized_) {
 | 
					  if (!initialized_) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,7 @@ class IDFI2CBus : public I2CBus, public Component {
 | 
				
			|||||||
  void setup() override;
 | 
					  void setup() override;
 | 
				
			||||||
  void dump_config() override;
 | 
					  void dump_config() override;
 | 
				
			||||||
  ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override;
 | 
					  ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override;
 | 
				
			||||||
  ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override;
 | 
					  ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) override;
 | 
				
			||||||
  float get_setup_priority() const override { return setup_priority::BUS; }
 | 
					  float get_setup_priority() const override { return setup_priority::BUS; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void set_scan(bool scan) { scan_ = scan; }
 | 
					  void set_scan(bool scan) { scan_ = scan; }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,7 @@ IMAGE_TYPE = {
 | 
				
			|||||||
    "GRAYSCALE": ImageType.IMAGE_TYPE_GRAYSCALE,
 | 
					    "GRAYSCALE": ImageType.IMAGE_TYPE_GRAYSCALE,
 | 
				
			||||||
    "RGB24": ImageType.IMAGE_TYPE_RGB24,
 | 
					    "RGB24": ImageType.IMAGE_TYPE_RGB24,
 | 
				
			||||||
    "TRANSPARENT_BINARY": ImageType.IMAGE_TYPE_TRANSPARENT_BINARY,
 | 
					    "TRANSPARENT_BINARY": ImageType.IMAGE_TYPE_TRANSPARENT_BINARY,
 | 
				
			||||||
 | 
					    "RGB565": ImageType.IMAGE_TYPE_RGB565,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Image_ = display.display_ns.class_("Image")
 | 
					Image_ = display.display_ns.class_("Image")
 | 
				
			||||||
@@ -89,6 +90,21 @@ async def to_code(config):
 | 
				
			|||||||
            data[pos] = pix[2]
 | 
					            data[pos] = pix[2]
 | 
				
			||||||
            pos += 1
 | 
					            pos += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    elif config[CONF_TYPE] == "RGB565":
 | 
				
			||||||
 | 
					        image = image.convert("RGB")
 | 
				
			||||||
 | 
					        pixels = list(image.getdata())
 | 
				
			||||||
 | 
					        data = [0 for _ in range(height * width * 3)]
 | 
				
			||||||
 | 
					        pos = 0
 | 
				
			||||||
 | 
					        for pix in pixels:
 | 
				
			||||||
 | 
					            R = pix[0] >> 3
 | 
				
			||||||
 | 
					            G = pix[1] >> 2
 | 
				
			||||||
 | 
					            B = pix[2] >> 3
 | 
				
			||||||
 | 
					            rgb = (R << 11) | (G << 5) | B
 | 
				
			||||||
 | 
					            data[pos] = rgb >> 8
 | 
				
			||||||
 | 
					            pos += 1
 | 
				
			||||||
 | 
					            data[pos] = rgb & 255
 | 
				
			||||||
 | 
					            pos += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    elif config[CONF_TYPE] == "BINARY":
 | 
					    elif config[CONF_TYPE] == "BINARY":
 | 
				
			||||||
        image = image.convert("1", dither=dither)
 | 
					        image = image.convert("1", dither=dither)
 | 
				
			||||||
        width8 = ((width + 7) // 8) * 8
 | 
					        width8 = ((width + 7) // 8) * 8
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,7 +51,7 @@ class ImprovSerialComponent : public Component {
 | 
				
			|||||||
  void write_data_(std::vector<uint8_t> &data);
 | 
					  void write_data_(std::vector<uint8_t> &data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_ARDUINO
 | 
					#ifdef USE_ARDUINO
 | 
				
			||||||
  HardwareSerial *hw_serial_{nullptr};
 | 
					  Stream *hw_serial_{nullptr};
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_ESP_IDF
 | 
					#ifdef USE_ESP_IDF
 | 
				
			||||||
  uart_port_t uart_num_;
 | 
					  uart_port_t uart_num_;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,47 +16,73 @@ static const char *const TAG = "json";
 | 
				
			|||||||
static std::vector<char> global_json_build_buffer;  // NOLINT
 | 
					static std::vector<char> global_json_build_buffer;  // NOLINT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::string build_json(const json_build_t &f) {
 | 
					std::string build_json(const json_build_t &f) {
 | 
				
			||||||
  // Here we are allocating as much heap memory as available minus 2kb to be safe
 | 
					  // Here we are allocating up to 5kb of memory,
 | 
				
			||||||
 | 
					  // with the heap size minus 2kb to be safe if less than 5kb
 | 
				
			||||||
  // as we can not have a true dynamic sized document.
 | 
					  // as we can not have a true dynamic sized document.
 | 
				
			||||||
  // The excess memory is freed below with `shrinkToFit()`
 | 
					  // The excess memory is freed below with `shrinkToFit()`
 | 
				
			||||||
#ifdef USE_ESP8266
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
  const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048;  // NOLINT(readability-static-accessed-through-instance)
 | 
					  const size_t free_heap = ESP.getMaxFreeBlockSize();  // NOLINT(readability-static-accessed-through-instance)
 | 
				
			||||||
#elif defined(USE_ESP32)
 | 
					#elif defined(USE_ESP32)
 | 
				
			||||||
  const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048;
 | 
					  const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  DynamicJsonDocument json_document(free_heap);
 | 
					  const size_t request_size = std::min(free_heap, (size_t) 512);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  DynamicJsonDocument json_document(request_size);
 | 
				
			||||||
 | 
					  if (json_document.capacity() == 0) {
 | 
				
			||||||
 | 
					    ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, largest free heap block: %u bytes",
 | 
				
			||||||
 | 
					             request_size, free_heap);
 | 
				
			||||||
 | 
					    return "{}";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  JsonObject root = json_document.to<JsonObject>();
 | 
					  JsonObject root = json_document.to<JsonObject>();
 | 
				
			||||||
  f(root);
 | 
					  f(root);
 | 
				
			||||||
  json_document.shrinkToFit();
 | 
					  json_document.shrinkToFit();
 | 
				
			||||||
 | 
					  ESP_LOGV(TAG, "Size after shrink %u bytes", json_document.capacity());
 | 
				
			||||||
  std::string output;
 | 
					  std::string output;
 | 
				
			||||||
  serializeJson(json_document, output);
 | 
					  serializeJson(json_document, output);
 | 
				
			||||||
  return output;
 | 
					  return output;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void parse_json(const std::string &data, const json_parse_t &f) {
 | 
					void parse_json(const std::string &data, const json_parse_t &f) {
 | 
				
			||||||
  // Here we are allocating as much heap memory as available minus 2kb to be safe
 | 
					  // Here we are allocating 1.5 times the data size,
 | 
				
			||||||
 | 
					  // with the heap size minus 2kb to be safe if less than that
 | 
				
			||||||
  // as we can not have a true dynamic sized document.
 | 
					  // as we can not have a true dynamic sized document.
 | 
				
			||||||
  // The excess memory is freed below with `shrinkToFit()`
 | 
					  // The excess memory is freed below with `shrinkToFit()`
 | 
				
			||||||
#ifdef USE_ESP8266
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
  const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048;  // NOLINT(readability-static-accessed-through-instance)
 | 
					  const size_t free_heap = ESP.getMaxFreeBlockSize();  // NOLINT(readability-static-accessed-through-instance)
 | 
				
			||||||
#elif defined(USE_ESP32)
 | 
					#elif defined(USE_ESP32)
 | 
				
			||||||
  const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048;
 | 
					  const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					  bool pass = false;
 | 
				
			||||||
 | 
					  size_t request_size = std::min(free_heap, (size_t)(data.size() * 1.5));
 | 
				
			||||||
 | 
					  do {
 | 
				
			||||||
 | 
					    DynamicJsonDocument json_document(request_size);
 | 
				
			||||||
 | 
					    if (json_document.capacity() == 0) {
 | 
				
			||||||
 | 
					      ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size,
 | 
				
			||||||
 | 
					               free_heap);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    DeserializationError err = deserializeJson(json_document, data);
 | 
				
			||||||
 | 
					    json_document.shrinkToFit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  DynamicJsonDocument json_document(free_heap);
 | 
					    JsonObject root = json_document.as<JsonObject>();
 | 
				
			||||||
  DeserializationError err = deserializeJson(json_document, data);
 | 
					 | 
				
			||||||
  json_document.shrinkToFit();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  JsonObject root = json_document.as<JsonObject>();
 | 
					    if (err == DeserializationError::Ok) {
 | 
				
			||||||
 | 
					      pass = true;
 | 
				
			||||||
  if (err) {
 | 
					      f(root);
 | 
				
			||||||
    ESP_LOGW(TAG, "Parsing JSON failed.");
 | 
					    } else if (err == DeserializationError::NoMemory) {
 | 
				
			||||||
    return;
 | 
					      if (request_size * 2 >= free_heap) {
 | 
				
			||||||
  }
 | 
					        ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller");
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
  f(root);
 | 
					      }
 | 
				
			||||||
 | 
					      ESP_LOGV(TAG, "Increasing memory allocation.");
 | 
				
			||||||
 | 
					      request_size *= 2;
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      ESP_LOGE(TAG, "JSON parse error: %s", err.c_str());
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } while (!pass);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace json
 | 
					}  // namespace json
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
import esphome.codegen as cg
 | 
					import esphome.codegen as cg
 | 
				
			||||||
import esphome.config_validation as cv
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
from esphome.components import display
 | 
					from esphome.components import display
 | 
				
			||||||
from esphome.const import CONF_DIMENSIONS
 | 
					from esphome.const import CONF_DIMENSIONS, CONF_POSITION, CONF_DATA
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_USER_CHARACTERS = "user_characters"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
lcd_base_ns = cg.esphome_ns.namespace("lcd_base")
 | 
					lcd_base_ns = cg.esphome_ns.namespace("lcd_base")
 | 
				
			||||||
LCDDisplay = lcd_base_ns.class_("LCDDisplay", cg.PollingComponent)
 | 
					LCDDisplay = lcd_base_ns.class_("LCDDisplay", cg.PollingComponent)
 | 
				
			||||||
@@ -16,9 +18,35 @@ def validate_lcd_dimensions(value):
 | 
				
			|||||||
    return value
 | 
					    return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def validate_user_characters(value):
 | 
				
			||||||
 | 
					    positions = set()
 | 
				
			||||||
 | 
					    for conf in value:
 | 
				
			||||||
 | 
					        if conf[CONF_POSITION] in positions:
 | 
				
			||||||
 | 
					            raise cv.Invalid(
 | 
				
			||||||
 | 
					                f"Duplicate user defined character at position {conf[CONF_POSITION]}"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        positions.add(conf[CONF_POSITION])
 | 
				
			||||||
 | 
					    return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LCD_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend(
 | 
					LCD_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        cv.Required(CONF_DIMENSIONS): validate_lcd_dimensions,
 | 
					        cv.Required(CONF_DIMENSIONS): validate_lcd_dimensions,
 | 
				
			||||||
 | 
					        cv.Optional(CONF_USER_CHARACTERS): cv.All(
 | 
				
			||||||
 | 
					            cv.ensure_list(
 | 
				
			||||||
 | 
					                cv.Schema(
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        cv.Required(CONF_POSITION): cv.int_range(min=0, max=7),
 | 
				
			||||||
 | 
					                        cv.Required(CONF_DATA): cv.All(
 | 
				
			||||||
 | 
					                            cv.ensure_list(cv.int_range(min=0, max=31)),
 | 
				
			||||||
 | 
					                            cv.Length(min=8, max=8),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            cv.Length(max=8),
 | 
				
			||||||
 | 
					            validate_user_characters,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
).extend(cv.polling_component_schema("1s"))
 | 
					).extend(cv.polling_component_schema("1s"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -27,3 +55,6 @@ async def setup_lcd_display(var, config):
 | 
				
			|||||||
    await cg.register_component(var, config)
 | 
					    await cg.register_component(var, config)
 | 
				
			||||||
    await display.register_display(var, config)
 | 
					    await display.register_display(var, config)
 | 
				
			||||||
    cg.add(var.set_dimensions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1]))
 | 
					    cg.add(var.set_dimensions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1]))
 | 
				
			||||||
 | 
					    if CONF_USER_CHARACTERS in config:
 | 
				
			||||||
 | 
					        for usr in config[CONF_USER_CHARACTERS]:
 | 
				
			||||||
 | 
					            cg.add(var.set_user_defined_char(usr[CONF_POSITION], usr[CONF_DATA]))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,6 +65,13 @@ void LCDDisplay::setup() {
 | 
				
			|||||||
    this->command_(LCD_DISPLAY_COMMAND_FUNCTION_SET | display_function);
 | 
					    this->command_(LCD_DISPLAY_COMMAND_FUNCTION_SET | display_function);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // store user defined characters
 | 
				
			||||||
 | 
					  for (auto &user_defined_char : this->user_defined_chars_) {
 | 
				
			||||||
 | 
					    this->command_(LCD_DISPLAY_COMMAND_SET_CGRAM_ADDR | (user_defined_char.first << 3));
 | 
				
			||||||
 | 
					    for (auto data : user_defined_char.second)
 | 
				
			||||||
 | 
					      this->send(data, true);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  this->command_(LCD_DISPLAY_COMMAND_FUNCTION_SET | display_function);
 | 
					  this->command_(LCD_DISPLAY_COMMAND_FUNCTION_SET | display_function);
 | 
				
			||||||
  uint8_t display_control = LCD_DISPLAY_DISPLAY_ON;
 | 
					  uint8_t display_control = LCD_DISPLAY_DISPLAY_ON;
 | 
				
			||||||
  this->command_(LCD_DISPLAY_COMMAND_DISPLAY_CONTROL | display_control);
 | 
					  this->command_(LCD_DISPLAY_COMMAND_DISPLAY_CONTROL | display_control);
 | 
				
			||||||
@@ -160,6 +167,13 @@ void LCDDisplay::strftime(uint8_t column, uint8_t row, const char *format, time:
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
void LCDDisplay::strftime(const char *format, time::ESPTime time) { this->strftime(0, 0, format, time); }
 | 
					void LCDDisplay::strftime(const char *format, time::ESPTime time) { this->strftime(0, 0, format, time); }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					void LCDDisplay::loadchar(uint8_t location, uint8_t charmap[]) {
 | 
				
			||||||
 | 
					  location &= 0x7;  // we only have 8 locations 0-7
 | 
				
			||||||
 | 
					  this->command_(LCD_DISPLAY_COMMAND_SET_CGRAM_ADDR | (location << 3));
 | 
				
			||||||
 | 
					  for (int i = 0; i < 8; i++) {
 | 
				
			||||||
 | 
					    this->send(charmap[i], true);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace lcd_base
 | 
					}  // namespace lcd_base
 | 
				
			||||||
}  // namespace esphome
 | 
					}  // namespace esphome
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,8 @@
 | 
				
			|||||||
#include "esphome/components/time/real_time_clock.h"
 | 
					#include "esphome/components/time/real_time_clock.h"
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <map>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome {
 | 
					namespace esphome {
 | 
				
			||||||
namespace lcd_base {
 | 
					namespace lcd_base {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,6 +21,8 @@ class LCDDisplay : public PollingComponent {
 | 
				
			|||||||
    this->rows_ = rows;
 | 
					    this->rows_ = rows;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void set_user_defined_char(uint8_t pos, const std::vector<uint8_t> &data) { this->user_defined_chars_[pos] = data; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void setup() override;
 | 
					  void setup() override;
 | 
				
			||||||
  float get_setup_priority() const override;
 | 
					  float get_setup_priority() const override;
 | 
				
			||||||
  void update() override;
 | 
					  void update() override;
 | 
				
			||||||
@@ -47,6 +51,9 @@ class LCDDisplay : public PollingComponent {
 | 
				
			|||||||
  void strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0)));
 | 
					  void strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0)));
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Load custom char to given location
 | 
				
			||||||
 | 
					  void loadchar(uint8_t location, uint8_t charmap[]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 protected:
 | 
					 protected:
 | 
				
			||||||
  virtual bool is_four_bit_mode() = 0;
 | 
					  virtual bool is_four_bit_mode() = 0;
 | 
				
			||||||
  virtual void write_n_bits(uint8_t value, uint8_t n) = 0;
 | 
					  virtual void write_n_bits(uint8_t value, uint8_t n) = 0;
 | 
				
			||||||
@@ -58,6 +65,7 @@ class LCDDisplay : public PollingComponent {
 | 
				
			|||||||
  uint8_t columns_;
 | 
					  uint8_t columns_;
 | 
				
			||||||
  uint8_t rows_;
 | 
					  uint8_t rows_;
 | 
				
			||||||
  uint8_t *buffer_{nullptr};
 | 
					  uint8_t *buffer_{nullptr};
 | 
				
			||||||
 | 
					  std::map<uint8_t, std::vector<uint8_t> > user_defined_chars_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}  // namespace lcd_base
 | 
					}  // namespace lcd_base
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,8 +19,13 @@ from esphome.const import (
 | 
				
			|||||||
    CONF_TX_BUFFER_SIZE,
 | 
					    CONF_TX_BUFFER_SIZE,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority
 | 
					from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority
 | 
				
			||||||
from esphome.components.esp32 import get_esp32_variant
 | 
					from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
 | 
				
			||||||
from esphome.components.esp32.const import VARIANT_ESP32S2, VARIANT_ESP32C3
 | 
					from esphome.components.esp32.const import (
 | 
				
			||||||
 | 
					    VARIANT_ESP32,
 | 
				
			||||||
 | 
					    VARIANT_ESP32S2,
 | 
				
			||||||
 | 
					    VARIANT_ESP32C3,
 | 
				
			||||||
 | 
					    VARIANT_ESP32S3,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CODEOWNERS = ["@esphome/core"]
 | 
					CODEOWNERS = ["@esphome/core"]
 | 
				
			||||||
logger_ns = cg.esphome_ns.namespace("logger")
 | 
					logger_ns = cg.esphome_ns.namespace("logger")
 | 
				
			||||||
@@ -54,36 +59,51 @@ LOG_LEVEL_SEVERITY = [
 | 
				
			|||||||
    "VERY_VERBOSE",
 | 
					    "VERY_VERBOSE",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ESP32_REDUCED_VARIANTS = [VARIANT_ESP32C3, VARIANT_ESP32S2]
 | 
					UART0 = "UART0"
 | 
				
			||||||
 | 
					UART1 = "UART1"
 | 
				
			||||||
 | 
					UART2 = "UART2"
 | 
				
			||||||
 | 
					UART0_SWAP = "UART0_SWAP"
 | 
				
			||||||
 | 
					USB_SERIAL_JTAG = "USB_SERIAL_JTAG"
 | 
				
			||||||
 | 
					USB_CDC = "USB_CDC"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
UART_SELECTION_ESP32_REDUCED = ["UART0", "UART1"]
 | 
					UART_SELECTION_ESP32 = {
 | 
				
			||||||
 | 
					    VARIANT_ESP32: [UART0, UART1, UART2],
 | 
				
			||||||
 | 
					    VARIANT_ESP32S2: [UART0, UART1, USB_CDC],
 | 
				
			||||||
 | 
					    VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
 | 
				
			||||||
 | 
					    VARIANT_ESP32C3: [UART0, UART1, USB_SERIAL_JTAG],
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
UART_SELECTION_ESP32 = ["UART0", "UART1", "UART2"]
 | 
					UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
UART_SELECTION_ESP8266 = ["UART0", "UART0_SWAP", "UART1"]
 | 
					ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
HARDWARE_UART_TO_UART_SELECTION = {
 | 
					HARDWARE_UART_TO_UART_SELECTION = {
 | 
				
			||||||
    "UART0": logger_ns.UART_SELECTION_UART0,
 | 
					    UART0: logger_ns.UART_SELECTION_UART0,
 | 
				
			||||||
    "UART0_SWAP": logger_ns.UART_SELECTION_UART0_SWAP,
 | 
					    UART0_SWAP: logger_ns.UART_SELECTION_UART0_SWAP,
 | 
				
			||||||
    "UART1": logger_ns.UART_SELECTION_UART1,
 | 
					    UART1: logger_ns.UART_SELECTION_UART1,
 | 
				
			||||||
    "UART2": logger_ns.UART_SELECTION_UART2,
 | 
					    UART2: logger_ns.UART_SELECTION_UART2,
 | 
				
			||||||
 | 
					    USB_CDC: logger_ns.UART_SELECTION_USB_CDC,
 | 
				
			||||||
 | 
					    USB_SERIAL_JTAG: logger_ns.UART_SELECTION_USB_SERIAL_JTAG,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
HARDWARE_UART_TO_SERIAL = {
 | 
					HARDWARE_UART_TO_SERIAL = {
 | 
				
			||||||
    "UART0": cg.global_ns.Serial,
 | 
					    UART0: cg.global_ns.Serial,
 | 
				
			||||||
    "UART0_SWAP": cg.global_ns.Serial,
 | 
					    UART0_SWAP: cg.global_ns.Serial,
 | 
				
			||||||
    "UART1": cg.global_ns.Serial1,
 | 
					    UART1: cg.global_ns.Serial1,
 | 
				
			||||||
    "UART2": cg.global_ns.Serial2,
 | 
					    UART2: cg.global_ns.Serial2,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
 | 
					is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def uart_selection(value):
 | 
					def uart_selection(value):
 | 
				
			||||||
 | 
					    if value.upper() in ESP_IDF_UARTS:
 | 
				
			||||||
 | 
					        if not CORE.using_esp_idf:
 | 
				
			||||||
 | 
					            raise cv.Invalid(f"Only esp-idf framework supports {value}.")
 | 
				
			||||||
    if CORE.is_esp32:
 | 
					    if CORE.is_esp32:
 | 
				
			||||||
        if get_esp32_variant() in ESP32_REDUCED_VARIANTS:
 | 
					        variant = get_esp32_variant()
 | 
				
			||||||
            return cv.one_of(*UART_SELECTION_ESP32_REDUCED, upper=True)(value)
 | 
					        if variant in UART_SELECTION_ESP32:
 | 
				
			||||||
        return cv.one_of(*UART_SELECTION_ESP32, upper=True)(value)
 | 
					            return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
 | 
				
			||||||
    if CORE.is_esp8266:
 | 
					    if CORE.is_esp8266:
 | 
				
			||||||
        return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
 | 
					        return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
 | 
				
			||||||
    raise NotImplementedError
 | 
					    raise NotImplementedError
 | 
				
			||||||
@@ -113,7 +133,7 @@ CONFIG_SCHEMA = cv.All(
 | 
				
			|||||||
            cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
 | 
					            cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
 | 
				
			||||||
            cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes,
 | 
					            cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes,
 | 
				
			||||||
            cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
 | 
					            cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
 | 
				
			||||||
            cv.Optional(CONF_HARDWARE_UART, default="UART0"): uart_selection,
 | 
					            cv.Optional(CONF_HARDWARE_UART, default=UART0): uart_selection,
 | 
				
			||||||
            cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level,
 | 
					            cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level,
 | 
				
			||||||
            cv.Optional(CONF_LOGS, default={}): cv.Schema(
 | 
					            cv.Optional(CONF_LOGS, default={}): cv.Schema(
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@@ -185,6 +205,12 @@ async def to_code(config):
 | 
				
			|||||||
    if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH):
 | 
					    if config.get(CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH):
 | 
				
			||||||
        cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH")
 | 
					        cg.add_build_flag("-DUSE_STORE_LOG_STR_IN_FLASH")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if CORE.using_esp_idf:
 | 
				
			||||||
 | 
					        if config[CONF_HARDWARE_UART] == USB_CDC:
 | 
				
			||||||
 | 
					            add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
 | 
				
			||||||
 | 
					        elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG:
 | 
				
			||||||
 | 
					            add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Register at end for safe mode
 | 
					    # Register at end for safe mode
 | 
				
			||||||
    await cg.register_component(log, config)
 | 
					    await cg.register_component(log, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -203,15 +229,6 @@ async def to_code(config):
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def maybe_simple_message(schema):
 | 
					 | 
				
			||||||
    def validator(value):
 | 
					 | 
				
			||||||
        if isinstance(value, dict):
 | 
					 | 
				
			||||||
            return cv.Schema(schema)(value)
 | 
					 | 
				
			||||||
        return cv.Schema(schema)({CONF_FORMAT: value})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return validator
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def validate_printf(value):
 | 
					def validate_printf(value):
 | 
				
			||||||
    # https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python
 | 
					    # https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python
 | 
				
			||||||
    cfmt = r"""
 | 
					    cfmt = r"""
 | 
				
			||||||
@@ -234,7 +251,7 @@ def validate_printf(value):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
CONF_LOGGER_LOG = "logger.log"
 | 
					CONF_LOGGER_LOG = "logger.log"
 | 
				
			||||||
LOGGER_LOG_ACTION_SCHEMA = cv.All(
 | 
					LOGGER_LOG_ACTION_SCHEMA = cv.All(
 | 
				
			||||||
    maybe_simple_message(
 | 
					    cv.maybe_simple_value(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            cv.Required(CONF_FORMAT): cv.string,
 | 
					            cv.Required(CONF_FORMAT): cv.string,
 | 
				
			||||||
            cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
 | 
					            cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
 | 
				
			||||||
@@ -242,9 +259,10 @@ LOGGER_LOG_ACTION_SCHEMA = cv.All(
 | 
				
			|||||||
                *LOG_LEVEL_TO_ESP_LOG, upper=True
 | 
					                *LOG_LEVEL_TO_ESP_LOG, upper=True
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            cv.Optional(CONF_TAG, default="main"): cv.string,
 | 
					            cv.Optional(CONF_TAG, default="main"): cv.string,
 | 
				
			||||||
        }
 | 
					        },
 | 
				
			||||||
    ),
 | 
					        validate_printf,
 | 
				
			||||||
    validate_printf,
 | 
					        key=CONF_FORMAT,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -116,8 +116,22 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
 | 
				
			|||||||
    this->hw_serial_->println(msg);
 | 
					    this->hw_serial_->println(msg);
 | 
				
			||||||
#endif  // USE_ARDUINO
 | 
					#endif  // USE_ARDUINO
 | 
				
			||||||
#ifdef USE_ESP_IDF
 | 
					#ifdef USE_ESP_IDF
 | 
				
			||||||
    uart_write_bytes(uart_num_, msg, strlen(msg));
 | 
					    if (
 | 
				
			||||||
    uart_write_bytes(uart_num_, "\n", 1);
 | 
					#if defined(USE_ESP32_VARIANT_ESP32S2)
 | 
				
			||||||
 | 
					        uart_ == UART_SELECTION_USB_CDC
 | 
				
			||||||
 | 
					#elif defined(USE_ESP32_VARIANT_ESP32C3)
 | 
				
			||||||
 | 
					        uart_ == UART_SELECTION_USB_SERIAL_JTAG
 | 
				
			||||||
 | 
					#elif defined(USE_ESP32_VARIANT_ESP32S3)
 | 
				
			||||||
 | 
					        uart_ == UART_SELECTION_USB_CDC || uart_ == UART_SELECTION_USB_SERIAL_JTAG
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					        /* DISABLES CODE */ (false)
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      puts(msg);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      uart_write_bytes(uart_num_, msg, strlen(msg));
 | 
				
			||||||
 | 
					      uart_write_bytes(uart_num_, "\n", 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -149,13 +163,25 @@ void Logger::pre_setup() {
 | 
				
			|||||||
      case UART_SELECTION_UART0_SWAP:
 | 
					      case UART_SELECTION_UART0_SWAP:
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
        this->hw_serial_ = &Serial;
 | 
					        this->hw_serial_ = &Serial;
 | 
				
			||||||
 | 
					        Serial.begin(this->baud_rate_);
 | 
				
			||||||
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
 | 
					        if (this->uart_ == UART_SELECTION_UART0_SWAP) {
 | 
				
			||||||
 | 
					          Serial.swap();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case UART_SELECTION_UART1:
 | 
					      case UART_SELECTION_UART1:
 | 
				
			||||||
        this->hw_serial_ = &Serial1;
 | 
					        this->hw_serial_ = &Serial1;
 | 
				
			||||||
 | 
					        Serial1.begin(this->baud_rate_);
 | 
				
			||||||
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
 | 
					        Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2)
 | 
					#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2)
 | 
				
			||||||
      case UART_SELECTION_UART2:
 | 
					      case UART_SELECTION_UART2:
 | 
				
			||||||
        this->hw_serial_ = &Serial2;
 | 
					        this->hw_serial_ = &Serial2;
 | 
				
			||||||
 | 
					        Serial2.begin(this->baud_rate_);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -169,39 +195,41 @@ void Logger::pre_setup() {
 | 
				
			|||||||
      case UART_SELECTION_UART1:
 | 
					      case UART_SELECTION_UART1:
 | 
				
			||||||
        uart_num_ = UART_NUM_1;
 | 
					        uart_num_ = UART_NUM_1;
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2)
 | 
					#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
 | 
				
			||||||
      case UART_SELECTION_UART2:
 | 
					      case UART_SELECTION_UART2:
 | 
				
			||||||
        uart_num_ = UART_NUM_2;
 | 
					        uart_num_ = UART_NUM_2;
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
#endif
 | 
					#endif  // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
 | 
				
			||||||
 | 
					#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
				
			||||||
 | 
					      case UART_SELECTION_USB_CDC:
 | 
				
			||||||
 | 
					        uart_num_ = -1;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					#endif  // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
 | 
				
			||||||
 | 
					#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
				
			||||||
 | 
					      case UART_SELECTION_USB_SERIAL_JTAG:
 | 
				
			||||||
 | 
					        uart_num_ = -1;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					#endif  // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    uart_config_t uart_config{};
 | 
					    if (uart_num_ >= 0) {
 | 
				
			||||||
    uart_config.baud_rate = (int) baud_rate_;
 | 
					      uart_config_t uart_config{};
 | 
				
			||||||
    uart_config.data_bits = UART_DATA_8_BITS;
 | 
					      uart_config.baud_rate = (int) baud_rate_;
 | 
				
			||||||
    uart_config.parity = UART_PARITY_DISABLE;
 | 
					      uart_config.data_bits = UART_DATA_8_BITS;
 | 
				
			||||||
    uart_config.stop_bits = UART_STOP_BITS_1;
 | 
					      uart_config.parity = UART_PARITY_DISABLE;
 | 
				
			||||||
    uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
 | 
					      uart_config.stop_bits = UART_STOP_BITS_1;
 | 
				
			||||||
    uart_param_config(uart_num_, &uart_config);
 | 
					      uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
 | 
				
			||||||
    const int uart_buffer_size = tx_buffer_size_;
 | 
					      uart_param_config(uart_num_, &uart_config);
 | 
				
			||||||
    // Install UART driver using an event queue here
 | 
					      const int uart_buffer_size = tx_buffer_size_;
 | 
				
			||||||
    uart_driver_install(uart_num_, uart_buffer_size, uart_buffer_size, 10, nullptr, 0);
 | 
					      // Install UART driver using an event queue here
 | 
				
			||||||
#endif
 | 
					      uart_driver_install(uart_num_, uart_buffer_size, uart_buffer_size, 10, nullptr, 0);
 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef USE_ARDUINO
 | 
					 | 
				
			||||||
    this->hw_serial_->begin(this->baud_rate_);
 | 
					 | 
				
			||||||
#ifdef USE_ESP8266
 | 
					 | 
				
			||||||
    if (this->uart_ == UART_SELECTION_UART0_SWAP) {
 | 
					 | 
				
			||||||
      this->hw_serial_->swap();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this->hw_serial_->setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
 | 
					#endif  // USE_ESP_IDF
 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#endif  // USE_ARDUINO
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#ifdef USE_ESP8266
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
  else {
 | 
					  else {
 | 
				
			||||||
    uart_set_debug(UART_NO);
 | 
					    uart_set_debug(UART_NO);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#endif
 | 
					#endif  // USE_ESP8266
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  global_logger = this;
 | 
					  global_logger = this;
 | 
				
			||||||
#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO)
 | 
					#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO)
 | 
				
			||||||
@@ -209,7 +237,7 @@ void Logger::pre_setup() {
 | 
				
			|||||||
  if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) {
 | 
					  if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) {
 | 
				
			||||||
    esp_log_level_set("*", ESP_LOG_VERBOSE);
 | 
					    esp_log_level_set("*", ESP_LOG_VERBOSE);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
#endif
 | 
					#endif  // USE_ESP_IDF || USE_ESP32_FRAMEWORK_ARDUINO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ESP_LOGI(TAG, "Log initialized");
 | 
					  ESP_LOGI(TAG, "Log initialized");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -224,11 +252,24 @@ void Logger::add_on_log_callback(std::function<void(int, const char *, const cha
 | 
				
			|||||||
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
 | 
					float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
 | 
				
			||||||
const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
 | 
					const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
 | 
				
			||||||
#ifdef USE_ESP32
 | 
					#ifdef USE_ESP32
 | 
				
			||||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART2"};
 | 
					const char *const UART_SELECTIONS[] = {
 | 
				
			||||||
#endif
 | 
					    "UART0",           "UART1",
 | 
				
			||||||
 | 
					#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
 | 
				
			||||||
 | 
					    "UART2",
 | 
				
			||||||
 | 
					#endif  // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
 | 
				
			||||||
 | 
					#if defined(USE_ESP_IDF)
 | 
				
			||||||
 | 
					#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
				
			||||||
 | 
					    "USB_CDC",
 | 
				
			||||||
 | 
					#endif  // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
 | 
				
			||||||
 | 
					#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
				
			||||||
 | 
					    "USB_SERIAL_JTAG",
 | 
				
			||||||
 | 
					#endif  // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
 | 
				
			||||||
 | 
					#endif  // USE_ESP_IDF
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					#endif  // USE_ESP32
 | 
				
			||||||
#ifdef USE_ESP8266
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
 | 
					const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
 | 
				
			||||||
#endif
 | 
					#endif  // USE_ESP8266
 | 
				
			||||||
void Logger::dump_config() {
 | 
					void Logger::dump_config() {
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "Logger:");
 | 
					  ESP_LOGCONFIG(TAG, "Logger:");
 | 
				
			||||||
  ESP_LOGCONFIG(TAG, "  Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]);
 | 
					  ESP_LOGCONFIG(TAG, "  Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,9 +24,19 @@ namespace logger {
 | 
				
			|||||||
enum UARTSelection {
 | 
					enum UARTSelection {
 | 
				
			||||||
  UART_SELECTION_UART0 = 0,
 | 
					  UART_SELECTION_UART0 = 0,
 | 
				
			||||||
  UART_SELECTION_UART1,
 | 
					  UART_SELECTION_UART1,
 | 
				
			||||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2)
 | 
					#if defined(USE_ESP32)
 | 
				
			||||||
 | 
					#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
 | 
				
			||||||
  UART_SELECTION_UART2,
 | 
					  UART_SELECTION_UART2,
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					#ifdef USE_ESP_IDF
 | 
				
			||||||
 | 
					#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
				
			||||||
 | 
					  UART_SELECTION_USB_CDC,
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
 | 
				
			||||||
 | 
					  UART_SELECTION_USB_SERIAL_JTAG,
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
#ifdef USE_ESP8266
 | 
					#ifdef USE_ESP8266
 | 
				
			||||||
  UART_SELECTION_UART0_SWAP,
 | 
					  UART_SELECTION_UART0_SWAP,
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -40,7 +50,7 @@ class Logger : public Component {
 | 
				
			|||||||
  void set_baud_rate(uint32_t baud_rate);
 | 
					  void set_baud_rate(uint32_t baud_rate);
 | 
				
			||||||
  uint32_t get_baud_rate() const { return baud_rate_; }
 | 
					  uint32_t get_baud_rate() const { return baud_rate_; }
 | 
				
			||||||
#ifdef USE_ARDUINO
 | 
					#ifdef USE_ARDUINO
 | 
				
			||||||
  HardwareSerial *get_hw_serial() const { return hw_serial_; }
 | 
					  Stream *get_hw_serial() const { return hw_serial_; }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_ESP_IDF
 | 
					#ifdef USE_ESP_IDF
 | 
				
			||||||
  uart_port_t get_uart_num() const { return uart_num_; }
 | 
					  uart_port_t get_uart_num() const { return uart_num_; }
 | 
				
			||||||
@@ -119,7 +129,7 @@ class Logger : public Component {
 | 
				
			|||||||
  int tx_buffer_size_{0};
 | 
					  int tx_buffer_size_{0};
 | 
				
			||||||
  UARTSelection uart_{UART_SELECTION_UART0};
 | 
					  UARTSelection uart_{UART_SELECTION_UART0};
 | 
				
			||||||
#ifdef USE_ARDUINO
 | 
					#ifdef USE_ARDUINO
 | 
				
			||||||
  HardwareSerial *hw_serial_{nullptr};
 | 
					  Stream *hw_serial_{nullptr};
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
#ifdef USE_ESP_IDF
 | 
					#ifdef USE_ESP_IDF
 | 
				
			||||||
  uart_port_t uart_num_;
 | 
					  uart_port_t uart_num_;
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user