mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 09:01:49 +00:00 
			
		
		
		
	Compare commits
	
		
			167 Commits
		
	
	
		
			jesserockz
			...
			jesserockz
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					69fe18f1ef | ||
| 
						 | 
					39af967433 | ||
| 
						 | 
					83b5e01a28 | ||
| 
						 | 
					1eacbd50fa | ||
| 
						 | 
					9bf7c97775 | ||
| 
						 | 
					24bf3674f3 | ||
| 
						 | 
					c47dc09d34 | ||
| 
						 | 
					db3096c6e1 | ||
| 
						 | 
					eef578f4b8 | ||
| 
						 | 
					d4e232f267 | ||
| 
						 | 
					cc45945fcf | ||
| 
						 | 
					0c24d951ff | ||
| 
						 | 
					92e44b8238 | ||
| 
						 | 
					34387adbcd | ||
| 
						 | 
					c6885c1bf4 | ||
| 
						 | 
					8b8efb57af | ||
| 
						 | 
					499e120aa4 | ||
| 
						 | 
					6f198a4736 | ||
| 
						 | 
					f500f448b1 | ||
| 
						 | 
					6ad9baa870 | ||
| 
						 | 
					f843925301 | ||
| 
						 | 
					4a3b628946 | ||
| 
						 | 
					4ffdc38cf5 | ||
| 
						 | 
					f83f1bff19 | ||
| 
						 | 
					9370ff3dfa | ||
| 
						 | 
					2053b02c61 | ||
| 
						 | 
					f34e797a0d | ||
| 
						 | 
					30a2fc1273 | ||
| 
						 | 
					48da5ef1c4 | ||
| 
						 | 
					dbbbba3cf8 | ||
| 
						 | 
					16e523ca68 | ||
| 
						 | 
					3b2bbd306f | ||
| 
						 | 
					54caed36f7 | ||
| 
						 | 
					dfcccda69e | ||
| 
						 | 
					de352c1609 | ||
| 
						 | 
					c30068fc97 | ||
| 
						 | 
					f28f712827 | ||
| 
						 | 
					b63ade298f | ||
| 
						 | 
					f117d4f50a | ||
| 
						 | 
					6e4267b797 | ||
| 
						 | 
					2dd032475b | ||
| 
						 | 
					db0ed055dd | ||
| 
						 | 
					106c1bfac2 | ||
| 
						 | 
					eb2a0f45db | ||
| 
						 | 
					2b4fdd6c39 | ||
| 
						 | 
					3669320398 | ||
| 
						 | 
					d706f40ce1 | ||
| 
						 | 
					027284c29c | ||
| 
						 | 
					c55e01ff3f | ||
| 
						 | 
					a59ce7bfa2 | ||
| 
						 | 
					a6196267c9 | ||
| 
						 | 
					eb664b99ba | ||
| 
						 | 
					8414bb9a7a | ||
| 
						 | 
					ccef7c322f | ||
| 
						 | 
					120327866f | ||
| 
						 | 
					bc5c2d4eb4 | ||
| 
						 | 
					d5ff8f6117 | ||
| 
						 | 
					ad0d6f6337 | ||
| 
						 | 
					873de13b3d | ||
| 
						 | 
					56de8e5cc4 | ||
| 
						 | 
					73c82862cf | ||
| 
						 | 
					75573a3ed1 | ||
| 
						 | 
					1166d93805 | ||
| 
						 | 
					ac112a32c9 | ||
| 
						 | 
					cee45c1221 | ||
| 
						 | 
					fb56b5388e | ||
| 
						 | 
					ed42cefeee | ||
| 
						 | 
					9052947a71 | ||
| 
						 | 
					53e0af18fb | ||
| 
						 | 
					c5f59fad62 | ||
| 
						 | 
					b089a4ea80 | ||
| 
						 | 
					555bba7698 | ||
| 
						 | 
					294901fbe9 | ||
| 
						 | 
					ec576bf9f9 | ||
| 
						 | 
					9273e3775b | ||
| 
						 | 
					ce5cedb466 | ||
| 
						 | 
					b184b01600 | ||
| 
						 | 
					81b4078871 | ||
| 
						 | 
					d067c8f80b | ||
| 
						 | 
					8975b4b3f6 | ||
| 
						 | 
					20da03f8c6 | ||
| 
						 | 
					ef26677b67 | ||
| 
						 | 
					91925b1826 | ||
| 
						 | 
					1f33ad037d | ||
| 
						 | 
					fef60e335e | ||
| 
						 | 
					195c78846f | ||
| 
						 | 
					0f9c956c04 | ||
| 
						 | 
					7258a82875 | ||
| 
						 | 
					c1f696c32a | ||
| 
						 | 
					f2b63d9c67 | ||
| 
						 | 
					7896a7783b | ||
| 
						 | 
					621771e1ee | ||
| 
						 | 
					2b032e8606 | ||
| 
						 | 
					5e1b724697 | ||
| 
						 | 
					e6db61c2f0 | ||
| 
						 | 
					c2e198311c | ||
| 
						 | 
					d874626662 | ||
| 
						 | 
					eead72333e | ||
| 
						 | 
					719c212009 | ||
| 
						 | 
					65030e1c37 | ||
| 
						 | 
					3f88b63920 | ||
| 
						 | 
					70f1c71a9f | ||
| 
						 | 
					816df5ad47 | ||
| 
						 | 
					f7b1602adf | ||
| 
						 | 
					7e88eea532 | ||
| 
						 | 
					d1cdfd3b72 | ||
| 
						 | 
					d6a03d48f5 | ||
| 
						 | 
					d453b42b1a | ||
| 
						 | 
					7c19b961e2 | ||
| 
						 | 
					147b113b62 | ||
| 
						 | 
					d924702825 | ||
| 
						 | 
					e8784ba383 | ||
| 
						 | 
					e3a454d1a6 | ||
| 
						 | 
					392dc8b0db | ||
| 
						 | 
					2f62426f09 | ||
| 
						 | 
					58fda40389 | ||
| 
						 | 
					6a73699a38 | ||
| 
						 | 
					3bd6456fbe | ||
| 
						 | 
					608be4e050 | ||
| 
						 | 
					10f590324b | ||
| 
						 | 
					cb2d9e4bec | ||
| 
						 | 
					9e3ee28744 | ||
| 
						 | 
					472dcebf2c | ||
| 
						 | 
					39f0f748bf | ||
| 
						 | 
					9efe59a984 | ||
| 
						 | 
					fcb02af782 | ||
| 
						 | 
					9f30f53c6b | ||
| 
						 | 
					2f18ae00c5 | ||
| 
						 | 
					27a339fa12 | ||
| 
						 | 
					3aeef1afd4 | ||
| 
						 | 
					a45ee8f4ac | ||
| 
						 | 
					31b62d7dca | ||
| 
						 | 
					22f81475db | ||
| 
						 | 
					cc7cf73d59 | ||
| 
						 | 
					9682e60a25 | ||
| 
						 | 
					2c2e68123a | ||
| 
						 | 
					fcec7d45cb | ||
| 
						 | 
					7c8f502e7e | ||
| 
						 | 
					dc17c47634 | ||
| 
						 | 
					3d927c2f44 | ||
| 
						 | 
					0ae61410d2 | ||
| 
						 | 
					2455589f61 | ||
| 
						 | 
					c6afae0da5 | ||
| 
						 | 
					3155f02be6 | ||
| 
						 | 
					4fa0e860ad | ||
| 
						 | 
					8c122aa372 | ||
| 
						 | 
					5a0bf9fee9 | ||
| 
						 | 
					dc794918ed | ||
| 
						 | 
					02b15dbc4a | ||
| 
						 | 
					ed316b1ce3 | ||
| 
						 | 
					d7858f16c1 | ||
| 
						 | 
					291deb12ad | ||
| 
						 | 
					3e110681c9 | ||
| 
						 | 
					65fbfa2097 | ||
| 
						 | 
					16ebf9da4c | ||
| 
						 | 
					2c76381fcd | ||
| 
						 | 
					90683223dd | ||
| 
						 | 
					de79171815 | ||
| 
						 | 
					fd8b9fb028 | ||
| 
						 | 
					bdf1813b3a | ||
| 
						 | 
					45b6c93f5f | ||
| 
						 | 
					6124531479 | ||
| 
						 | 
					b8549d323c | ||
| 
						 | 
					01adece673 | ||
| 
						 | 
					0220934e4c | ||
| 
						 | 
					ca09693efa | ||
| 
						 | 
					e96d7483b3 | 
							
								
								
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -177,7 +177,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
      - name: Run yamllint
 | 
			
		||||
        if: matrix.id == 'yamllint'
 | 
			
		||||
        uses: frenck/action-yamllint@v1.3.0
 | 
			
		||||
        uses: frenck/action-yamllint@v1.3.1
 | 
			
		||||
 | 
			
		||||
      - name: Suggested changes
 | 
			
		||||
        run: script/ci-suggest-changes
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,7 +18,7 @@ jobs:
 | 
			
		||||
  lock:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: dessant/lock-threads@v3
 | 
			
		||||
      - uses: dessant/lock-threads@v4
 | 
			
		||||
        with:
 | 
			
		||||
          pr-inactive-days: "1"
 | 
			
		||||
          pr-lock-reason: ""
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -31,7 +31,7 @@ jobs:
 | 
			
		||||
            today="$(date --utc '+%Y%m%d')"
 | 
			
		||||
            TAG="${TAG}${today}"
 | 
			
		||||
          fi
 | 
			
		||||
          echo "::set-output name=tag::${TAG}"
 | 
			
		||||
          echo "tag=${TAG}" >> $GITHUB_OUTPUT
 | 
			
		||||
        # yamllint enable rule:line-length
 | 
			
		||||
 | 
			
		||||
  deploy-pypi:
 | 
			
		||||
 
 | 
			
		||||
@@ -3,15 +3,15 @@
 | 
			
		||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
			
		||||
repos:
 | 
			
		||||
  - repo: https://github.com/ambv/black
 | 
			
		||||
    rev: 22.6.0
 | 
			
		||||
    rev: 22.10.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: black
 | 
			
		||||
        args:
 | 
			
		||||
          - --safe
 | 
			
		||||
          - --quiet
 | 
			
		||||
        files: ^((esphome|script|tests)/.+)?[^/]+\.py$
 | 
			
		||||
  - repo: https://gitlab.com/pycqa/flake8
 | 
			
		||||
    rev: 4.0.1
 | 
			
		||||
  - repo: https://github.com/PyCQA/flake8
 | 
			
		||||
    rev: 6.0.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: flake8
 | 
			
		||||
        additional_dependencies:
 | 
			
		||||
@@ -27,7 +27,7 @@ repos:
 | 
			
		||||
          - --branch=release
 | 
			
		||||
          - --branch=beta
 | 
			
		||||
  - repo: https://github.com/asottile/pyupgrade
 | 
			
		||||
    rev: v3.0.0
 | 
			
		||||
    rev: v3.3.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: pyupgrade
 | 
			
		||||
        args: [--py39-plus]
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,7 @@ esphome/components/debug/* @OttoWinter
 | 
			
		||||
esphome/components/delonghi/* @grob6000
 | 
			
		||||
esphome/components/dfplayer/* @glmnet
 | 
			
		||||
esphome/components/dht/* @OttoWinter
 | 
			
		||||
esphome/components/display_menu_base/* @numo68
 | 
			
		||||
esphome/components/dps310/* @kbx81
 | 
			
		||||
esphome/components/ds1307/* @badbadc0ffee
 | 
			
		||||
esphome/components/dsmr/* @glmnet @zuidwijk
 | 
			
		||||
@@ -110,6 +111,7 @@ esphome/components/integration/* @OttoWinter
 | 
			
		||||
esphome/components/interval/* @esphome/core
 | 
			
		||||
esphome/components/json/* @OttoWinter
 | 
			
		||||
esphome/components/kalman_combinator/* @Cat-Ion
 | 
			
		||||
esphome/components/lcd_menu/* @numo68
 | 
			
		||||
esphome/components/ledc/* @OttoWinter
 | 
			
		||||
esphome/components/light/* @esphome/core
 | 
			
		||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
 | 
			
		||||
@@ -206,6 +208,7 @@ esphome/components/sim800l/* @glmnet
 | 
			
		||||
esphome/components/sm2135/* @BoukeHaarsma23
 | 
			
		||||
esphome/components/sml/* @alengwenus
 | 
			
		||||
esphome/components/smt100/* @piechade
 | 
			
		||||
esphome/components/sn74hc165/* @jesserockz
 | 
			
		||||
esphome/components/socket/* @esphome/core
 | 
			
		||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
 | 
			
		||||
esphome/components/spi/* @esphome/core
 | 
			
		||||
 
 | 
			
		||||
@@ -6,17 +6,14 @@
 | 
			
		||||
ARG BASEIMGTYPE=docker
 | 
			
		||||
 | 
			
		||||
# https://github.com/hassio-addons/addon-debian-base/releases
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.3.0 AS base-hassio-amd64
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.3.0 AS base-hassio-arm64
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.3.0 AS base-hassio-armv7
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base:6.1.3 AS base-hassio
 | 
			
		||||
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
 | 
			
		||||
FROM debian:bullseye-20220328-slim AS base-docker-amd64
 | 
			
		||||
FROM debian:bullseye-20220328-slim AS base-docker-arm64
 | 
			
		||||
FROM debian:bullseye-20220328-slim AS base-docker-armv7
 | 
			
		||||
FROM debian:bullseye-20221024-slim AS base-docker
 | 
			
		||||
 | 
			
		||||
# Use TARGETARCH/TARGETVARIANT defined by docker
 | 
			
		||||
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
 | 
			
		||||
FROM base-${BASEIMGTYPE}-${TARGETARCH}${TARGETVARIANT} AS base
 | 
			
		||||
FROM base-${BASEIMGTYPE} AS base
 | 
			
		||||
 | 
			
		||||
ARG TARGETARCH
 | 
			
		||||
ARG TARGETVARIANT
 | 
			
		||||
 | 
			
		||||
RUN \
 | 
			
		||||
    apt-get update \
 | 
			
		||||
@@ -29,8 +26,8 @@ RUN \
 | 
			
		||||
        python3-cryptography=3.3.2-1 \
 | 
			
		||||
        iputils-ping=3:20210202-1 \
 | 
			
		||||
        git=1:2.30.2-1 \
 | 
			
		||||
        curl=7.74.0-1.3+deb11u1 \
 | 
			
		||||
        openssh-client=1:8.4p1-5 \
 | 
			
		||||
        curl=7.74.0-1.3+deb11u3 \
 | 
			
		||||
        openssh-client=1:8.4p1-5+deb11u1 \
 | 
			
		||||
    && rm -rf \
 | 
			
		||||
        /tmp/* \
 | 
			
		||||
        /var/{cache,log}/* \
 | 
			
		||||
@@ -42,11 +39,19 @@ ENV \
 | 
			
		||||
  # Store globally installed pio libs in /piolibs
 | 
			
		||||
  PLATFORMIO_GLOBALLIB_DIR=/piolibs
 | 
			
		||||
 | 
			
		||||
# Support legacy binaries on Debian multiarch system. There is no "correct" way
 | 
			
		||||
# to do this, other than using properly built toolchains...
 | 
			
		||||
# See: https://unix.stackexchange.com/questions/553743/correct-way-to-add-lib-ld-linux-so-3-in-debian
 | 
			
		||||
RUN \
 | 
			
		||||
    if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
 | 
			
		||||
        ln -s /lib/arm-linux-gnueabihf/ld-linux.so.3 /lib/ld-linux.so.3; \
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
RUN \
 | 
			
		||||
    # Ubuntu python3-pip is missing wheel
 | 
			
		||||
    pip3 install --no-cache-dir \
 | 
			
		||||
        wheel==0.37.1 \
 | 
			
		||||
        platformio==6.1.4 \
 | 
			
		||||
        platformio==6.1.5 \
 | 
			
		||||
    # Change some platformio settings
 | 
			
		||||
    && platformio settings set enable_telemetry No \
 | 
			
		||||
    && platformio settings set check_platformio_interval 1000000 \
 | 
			
		||||
@@ -94,7 +99,7 @@ RUN \
 | 
			
		||||
    apt-get update \
 | 
			
		||||
    # Use pinned versions so that we get updates with build caching
 | 
			
		||||
    && apt-get install -y --no-install-recommends \
 | 
			
		||||
        nginx-light=1.18.0-6.1+deb11u2 \
 | 
			
		||||
        nginx-light=1.18.0-6.1+deb11u3 \
 | 
			
		||||
    && rm -rf \
 | 
			
		||||
        /tmp/* \
 | 
			
		||||
        /var/{cache,log}/* \
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								docker/ha-addon-rootfs/etc/cont-init.d/30-dirs.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								docker/ha-addon-rootfs/etc/cont-init.d/30-dirs.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -3,7 +3,13 @@
 | 
			
		||||
# Community Hass.io Add-ons: ESPHome
 | 
			
		||||
# Take down the S6 supervision tree when ESPHome fails
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
if -n { s6-test $# -ne 0 }
 | 
			
		||||
if -n { s6-test ${1} -eq 256 }
 | 
			
		||||
 | 
			
		||||
s6-svscanctl -t /var/run/s6/services
 | 
			
		||||
declare APP_EXIT_CODE=${1}
 | 
			
		||||
 | 
			
		||||
if [[ "${APP_EXIT_CODE}" -ne 0 ]] && [[ "${APP_EXIT_CODE}" -ne 256 ]]; then
 | 
			
		||||
  bashio::log.warning "Halt add-on with exit code ${APP_EXIT_CODE}"
 | 
			
		||||
  echo "${APP_EXIT_CODE}" > /run/s6-linux-init-container-results/exitcode
 | 
			
		||||
  exec /run/s6/basedir/bin/halt
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
bashio::log.info "Service restart after closing"
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,14 @@ if bashio::config.has_value 'relative_url'; then
 | 
			
		||||
    export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if bashio::config.has_value 'default_compile_process_limit'; then
 | 
			
		||||
    export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=$(bashio::config 'default_compile_process_limit')
 | 
			
		||||
else
 | 
			
		||||
    if grep -q 'Raspberry Pi 3' /proc/cpuinfo; then
 | 
			
		||||
        export ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT=1;
 | 
			
		||||
    fi
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
pio_cache_base=/data/cache/platformio
 | 
			
		||||
# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
 | 
			
		||||
# setting `core_dir` would therefore prevent pio from accessing
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,13 @@
 | 
			
		||||
# Community Hass.io Add-ons: ESPHome
 | 
			
		||||
# Take down the S6 supervision tree when NGINX fails
 | 
			
		||||
# ==============================================================================
 | 
			
		||||
if -n { s6-test $# -ne 0 }
 | 
			
		||||
if -n { s6-test ${1} -eq 256 }
 | 
			
		||||
 | 
			
		||||
s6-svscanctl -t /var/run/s6/services
 | 
			
		||||
declare APP_EXIT_CODE=${1}
 | 
			
		||||
 | 
			
		||||
if [[ "${APP_EXIT_CODE}" -ne 0 ]] && [[ "${APP_EXIT_CODE}" -ne 256 ]]; then
 | 
			
		||||
  bashio::log.warning "Halt add-on with exit code ${APP_EXIT_CODE}"
 | 
			
		||||
  echo "${APP_EXIT_CODE}" > /run/s6-linux-init-container-results/exitcode
 | 
			
		||||
  exec /run/s6/basedir/bin/halt
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
bashio::log.info "Service restart after closing"
 | 
			
		||||
 
 | 
			
		||||
@@ -254,8 +254,7 @@ def upload_using_esptool(config, port):
 | 
			
		||||
        if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
 | 
			
		||||
            import esptool
 | 
			
		||||
 | 
			
		||||
            # pylint: disable=protected-access
 | 
			
		||||
            return run_external_command(esptool._main, *cmd)
 | 
			
		||||
            return run_external_command(esptool.main, *cmd)  # pylint: disable=no-member
 | 
			
		||||
 | 
			
		||||
        return run_external_process(*cmd)
 | 
			
		||||
 | 
			
		||||
@@ -298,6 +297,8 @@ def upload_program(config, args, host):
 | 
			
		||||
    ota_conf = config[CONF_OTA]
 | 
			
		||||
    remote_port = ota_conf[CONF_PORT]
 | 
			
		||||
    password = ota_conf.get(CONF_PASSWORD, "")
 | 
			
		||||
    if getattr(args, "file", None) is not None:
 | 
			
		||||
        return espota2.run_ota(host, remote_port, password, args.file)
 | 
			
		||||
    return espota2.run_ota(host, remote_port, password, CORE.firmware_bin)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -687,6 +688,10 @@ def parse_args(argv):
 | 
			
		||||
        "--device",
 | 
			
		||||
        help="Manually specify the serial port/address to use, for example /dev/ttyUSB0.",
 | 
			
		||||
    )
 | 
			
		||||
    parser_upload.add_argument(
 | 
			
		||||
        "--file",
 | 
			
		||||
        help="Manually specify the binary file to upload.",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser_logs = subparsers.add_parser(
 | 
			
		||||
        "logs",
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,10 @@ ADC_MODE(ADC_VCC)
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
#include <hardware/adc.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace adc {
 | 
			
		||||
 | 
			
		||||
@@ -32,9 +36,13 @@ static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1;    // 4095 (12 b
 | 
			
		||||
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;  // 2048 (12 bit) or 4096 (13 bit)
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void ADCSensor::setup() {
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
extern "C"
 | 
			
		||||
#endif
 | 
			
		||||
    void
 | 
			
		||||
    ADCSensor::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
 | 
			
		||||
#ifndef USE_ADC_SENSOR_VCC
 | 
			
		||||
#if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040)
 | 
			
		||||
  pin_->setup();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@@ -63,6 +71,16 @@ void ADCSensor::setup() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  static bool initialized = false;
 | 
			
		||||
  if (!initialized) {
 | 
			
		||||
    adc_init();
 | 
			
		||||
    initialized = true;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADCSensor::dump_config() {
 | 
			
		||||
@@ -98,6 +116,13 @@ void ADCSensor::dump_config() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  if (this->is_temperature_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Pin: Temperature");
 | 
			
		||||
  } else {
 | 
			
		||||
    LOG_PIN("  Pin: ", pin_);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -175,6 +200,29 @@ float ADCSensor::sample() {
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
float ADCSensor::sample() {
 | 
			
		||||
  if (this->is_temperature_) {
 | 
			
		||||
    adc_set_temp_sensor_enabled(true);
 | 
			
		||||
    delay(1);
 | 
			
		||||
    adc_select_input(4);
 | 
			
		||||
  } else {
 | 
			
		||||
    uint8_t pin = this->pin_->get_pin();
 | 
			
		||||
    adc_gpio_init(pin);
 | 
			
		||||
    adc_select_input(pin - 26);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int raw = adc_read();
 | 
			
		||||
  if (this->is_temperature_) {
 | 
			
		||||
    adc_set_temp_sensor_enabled(false);
 | 
			
		||||
  }
 | 
			
		||||
  if (output_raw_) {
 | 
			
		||||
    return raw;
 | 
			
		||||
  }
 | 
			
		||||
  return raw * 3.3f / 4096.0f;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -38,10 +38,18 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
			
		||||
  std::string unique_id() override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  void set_is_temperature() { is_temperature_ = true; }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  InternalGPIOPin *pin_;
 | 
			
		||||
  bool output_raw_{false};
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  bool is_temperature_{false};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  adc_atten_t attenuation_{ADC_ATTEN_DB_0};
 | 
			
		||||
  adc1_channel_t channel_{};
 | 
			
		||||
 
 | 
			
		||||
@@ -94,6 +94,9 @@ def validate_adc_pin(value):
 | 
			
		||||
    if str(value).upper() == "VCC":
 | 
			
		||||
        return cv.only_on_esp8266("VCC")
 | 
			
		||||
 | 
			
		||||
    if str(value).upper() == "TEMPERATURE":
 | 
			
		||||
        return cv.only_on_rp2040("TEMPERATURE")
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        value = pins.internal_gpio_input_pin_number(value)
 | 
			
		||||
        variant = get_esp32_variant()
 | 
			
		||||
@@ -117,6 +120,12 @@ def validate_adc_pin(value):
 | 
			
		||||
            {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
 | 
			
		||||
        )(value)
 | 
			
		||||
 | 
			
		||||
    if CORE.is_rp2040:
 | 
			
		||||
        value = pins.internal_gpio_input_pin_number(value)
 | 
			
		||||
        if value not in (26, 27, 28, 29):
 | 
			
		||||
            raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.")
 | 
			
		||||
        return pins.internal_gpio_input_pin_schema(value)
 | 
			
		||||
 | 
			
		||||
    raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -160,6 +169,8 @@ async def to_code(config):
 | 
			
		||||
 | 
			
		||||
    if config[CONF_PIN] == "VCC":
 | 
			
		||||
        cg.add_define("USE_ADC_SENSOR_VCC")
 | 
			
		||||
    elif config[CONF_PIN] == "TEMPERATURE":
 | 
			
		||||
        cg.add(var.set_is_temperature())
 | 
			
		||||
    else:
 | 
			
		||||
        pin = await cg.gpio_pin_expression(config[CONF_PIN])
 | 
			
		||||
        cg.add(var.set_pin(pin))
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@
 | 
			
		||||
#include "esphome/components/display/display_buffer.h"
 | 
			
		||||
#include "esphome/components/light/addressable_light.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace addressable_light {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ade7953 {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ads1115 {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -122,8 +122,9 @@ void AHT10Component::update() {
 | 
			
		||||
    this->temperature_sensor_->publish_state(temperature);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->humidity_sensor_ != nullptr) {
 | 
			
		||||
    if (std::isnan(humidity))
 | 
			
		||||
    if (std::isnan(humidity)) {
 | 
			
		||||
      ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
 | 
			
		||||
    }
 | 
			
		||||
    this->humidity_sensor_->publish_state(humidity);
 | 
			
		||||
  }
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
 
 | 
			
		||||
@@ -104,9 +104,10 @@ void Am43::update() {
 | 
			
		||||
      auto status =
 | 
			
		||||
          esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                   packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
      if (status)
 | 
			
		||||
      if (status) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this->current_sensor_++;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -56,9 +56,10 @@ void Am43Component::control(const CoverCall &call) {
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status)
 | 
			
		||||
    if (status) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (call.get_position().has_value()) {
 | 
			
		||||
    auto pos = *call.get_position();
 | 
			
		||||
 | 
			
		||||
@@ -68,10 +69,11 @@ void Am43Component::control(const CoverCall &call) {
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status)
 | 
			
		||||
    if (status) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                        esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
@@ -126,18 +128,21 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
          auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
 | 
			
		||||
                                                 this->char_handle_, packet->length, packet->data,
 | 
			
		||||
                                                 ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
          if (status)
 | 
			
		||||
          if (status) {
 | 
			
		||||
            ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          ESP_LOGW(TAG, "[%s] AM43 pin rejected!", this->get_name().c_str());
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this->decoder_->has_set_position_response() && !this->decoder_->set_position_ok_)
 | 
			
		||||
      if (this->decoder_->has_set_position_response() && !this->decoder_->set_position_ok_) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] Got nack after sending set_position. Bad pin?", this->get_name().c_str());
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this->decoder_->has_set_state_response() && !this->decoder_->set_state_ok_)
 | 
			
		||||
      if (this->decoder_->has_set_state_response() && !this->decoder_->set_state_ok_) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%s] Got nack after sending set_state. Bad pin?", this->get_name().c_str());
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
 
 | 
			
		||||
@@ -37,18 +37,20 @@ void Anova::control(const ClimateCall &call) {
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status)
 | 
			
		||||
    if (status) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (call.get_target_temperature().has_value()) {
 | 
			
		||||
    auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature());
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status)
 | 
			
		||||
    if (status) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  switch (event) {
 | 
			
		||||
@@ -143,8 +145,9 @@ void Anova::update() {
 | 
			
		||||
    auto status =
 | 
			
		||||
        esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
 | 
			
		||||
                                 pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
    if (status)
 | 
			
		||||
    if (status) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
 | 
			
		||||
    }
 | 
			
		||||
    this->current_request_++;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -915,6 +915,7 @@ message ListEntitiesNumberResponse {
 | 
			
		||||
  EntityCategory entity_category = 10;
 | 
			
		||||
  string unit_of_measurement = 11;
 | 
			
		||||
  NumberMode mode = 12;
 | 
			
		||||
  string device_class = 13;
 | 
			
		||||
}
 | 
			
		||||
message NumberStateResponse {
 | 
			
		||||
  option (id) = 50;
 | 
			
		||||
@@ -1140,6 +1141,8 @@ message BluetoothLEAdvertisementResponse {
 | 
			
		||||
  repeated string service_uuids = 4;
 | 
			
		||||
  repeated BluetoothServiceData service_data = 5;
 | 
			
		||||
  repeated BluetoothServiceData manufacturer_data = 6;
 | 
			
		||||
 | 
			
		||||
  uint32 address_type = 7;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum BluetoothDeviceRequestType {
 | 
			
		||||
@@ -1147,6 +1150,8 @@ enum BluetoothDeviceRequestType {
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4;
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothDeviceRequest {
 | 
			
		||||
@@ -1156,6 +1161,8 @@ message BluetoothDeviceRequest {
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  BluetoothDeviceRequestType request_type = 2;
 | 
			
		||||
  bool has_address_type = 3;
 | 
			
		||||
  uint32 address_type = 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothDeviceConnectionResponse {
 | 
			
		||||
 
 | 
			
		||||
@@ -616,6 +616,7 @@ bool APIConnection::send_number_info(number::Number *number) {
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category());
 | 
			
		||||
  msg.unit_of_measurement = number->traits.get_unit_of_measurement();
 | 
			
		||||
  msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
 | 
			
		||||
  msg.device_class = number->traits.get_device_class();
 | 
			
		||||
 | 
			
		||||
  msg.min_value = number->traits.get_min_value();
 | 
			
		||||
  msg.max_value = number->traits.get_max_value();
 | 
			
		||||
@@ -949,7 +950,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
 | 
			
		||||
  resp.webserver_port = USE_WEBSERVER_PORT;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 2 : 1;
 | 
			
		||||
  resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 3 : 1;
 | 
			
		||||
#endif
 | 
			
		||||
  return resp;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::EntityCategory value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::ENTITY_CATEGORY_NONE:
 | 
			
		||||
@@ -18,6 +19,8 @@ template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::Entity
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::LegacyCoverState>(enums::LegacyCoverState value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::LEGACY_COVER_STATE_OPEN:
 | 
			
		||||
@@ -28,6 +31,8 @@ template<> const char *proto_enum_to_string<enums::LegacyCoverState>(enums::Lega
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::CoverOperation>(enums::CoverOperation value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::COVER_OPERATION_IDLE:
 | 
			
		||||
@@ -40,6 +45,8 @@ template<> const char *proto_enum_to_string<enums::CoverOperation>(enums::CoverO
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::LegacyCoverCommand>(enums::LegacyCoverCommand value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::LEGACY_COVER_COMMAND_OPEN:
 | 
			
		||||
@@ -52,6 +59,8 @@ template<> const char *proto_enum_to_string<enums::LegacyCoverCommand>(enums::Le
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::FanSpeed>(enums::FanSpeed value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::FAN_SPEED_LOW:
 | 
			
		||||
@@ -64,6 +73,8 @@ template<> const char *proto_enum_to_string<enums::FanSpeed>(enums::FanSpeed val
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirection value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::FAN_DIRECTION_FORWARD:
 | 
			
		||||
@@ -74,6 +85,8 @@ template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirec
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::ColorMode>(enums::ColorMode value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::COLOR_MODE_UNKNOWN:
 | 
			
		||||
@@ -100,6 +113,8 @@ template<> const char *proto_enum_to_string<enums::ColorMode>(enums::ColorMode v
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::SensorStateClass>(enums::SensorStateClass value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::STATE_CLASS_NONE:
 | 
			
		||||
@@ -114,6 +129,8 @@ template<> const char *proto_enum_to_string<enums::SensorStateClass>(enums::Sens
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::SensorLastResetType>(enums::SensorLastResetType value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::LAST_RESET_NONE:
 | 
			
		||||
@@ -126,6 +143,8 @@ template<> const char *proto_enum_to_string<enums::SensorLastResetType>(enums::S
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::LOG_LEVEL_NONE:
 | 
			
		||||
@@ -148,6 +167,8 @@ template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel val
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::ServiceArgType>(enums::ServiceArgType value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::SERVICE_ARG_TYPE_BOOL:
 | 
			
		||||
@@ -170,6 +191,8 @@ template<> const char *proto_enum_to_string<enums::ServiceArgType>(enums::Servic
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMode value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::CLIMATE_MODE_OFF:
 | 
			
		||||
@@ -190,6 +213,8 @@ template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMo
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::ClimateFanMode>(enums::ClimateFanMode value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::CLIMATE_FAN_ON:
 | 
			
		||||
@@ -214,6 +239,8 @@ template<> const char *proto_enum_to_string<enums::ClimateFanMode>(enums::Climat
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::ClimateSwingMode>(enums::ClimateSwingMode value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::CLIMATE_SWING_OFF:
 | 
			
		||||
@@ -228,6 +255,8 @@ template<> const char *proto_enum_to_string<enums::ClimateSwingMode>(enums::Clim
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::ClimateAction>(enums::ClimateAction value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::CLIMATE_ACTION_OFF:
 | 
			
		||||
@@ -246,6 +275,8 @@ template<> const char *proto_enum_to_string<enums::ClimateAction>(enums::Climate
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::ClimatePreset value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::CLIMATE_PRESET_NONE:
 | 
			
		||||
@@ -268,6 +299,8 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::NUMBER_MODE_AUTO:
 | 
			
		||||
@@ -280,6 +313,8 @@ template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::LockState>(enums::LockState value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::LOCK_STATE_NONE:
 | 
			
		||||
@@ -298,6 +333,8 @@ template<> const char *proto_enum_to_string<enums::LockState>(enums::LockState v
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::LockCommand>(enums::LockCommand value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::LOCK_UNLOCK:
 | 
			
		||||
@@ -310,6 +347,8 @@ template<> const char *proto_enum_to_string<enums::LockCommand>(enums::LockComma
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::MediaPlayerState>(enums::MediaPlayerState value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::MEDIA_PLAYER_STATE_NONE:
 | 
			
		||||
@@ -324,6 +363,8 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerState>(enums::Medi
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::MediaPlayerCommand>(enums::MediaPlayerCommand value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::MEDIA_PLAYER_COMMAND_PLAY:
 | 
			
		||||
@@ -340,6 +381,8 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerCommand>(enums::Me
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
template<>
 | 
			
		||||
const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::BluetoothDeviceRequestType value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
@@ -351,10 +394,15 @@ const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::Bluet
 | 
			
		||||
      return "BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR";
 | 
			
		||||
    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
 | 
			
		||||
      return "BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR";
 | 
			
		||||
    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE:
 | 
			
		||||
      return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE";
 | 
			
		||||
    case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE:
 | 
			
		||||
      return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE";
 | 
			
		||||
    default:
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 2: {
 | 
			
		||||
@@ -3942,6 +3990,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel
 | 
			
		||||
      this->unit_of_measurement = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 13: {
 | 
			
		||||
      this->device_class = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -3981,6 +4033,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
 | 
			
		||||
  buffer.encode_string(11, this->unit_of_measurement);
 | 
			
		||||
  buffer.encode_enum<enums::NumberMode>(12, this->mode);
 | 
			
		||||
  buffer.encode_string(13, this->device_class);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -4037,6 +4090,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  mode: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::NumberMode>(this->mode));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  device_class: ");
 | 
			
		||||
  out.append("'").append(this->device_class).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -4984,6 +5041,10 @@ bool BluetoothLEAdvertisementResponse::decode_varint(uint32_t field_id, ProtoVar
 | 
			
		||||
      this->rssi = value.as_sint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->address_type = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -5023,6 +5084,7 @@ void BluetoothLEAdvertisementResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  for (auto &it : this->manufacturer_data) {
 | 
			
		||||
    buffer.encode_message<BluetoothServiceData>(6, it, true);
 | 
			
		||||
  }
 | 
			
		||||
  buffer.encode_uint32(7, this->address_type);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -5059,6 +5121,11 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const {
 | 
			
		||||
    it.dump_to(out);
 | 
			
		||||
    out.append("\n");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  out.append("  address_type: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->address_type);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -5072,6 +5139,14 @@ bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
 | 
			
		||||
      this->request_type = value.as_enum<enums::BluetoothDeviceRequestType>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->has_address_type = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 4: {
 | 
			
		||||
      this->address_type = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -5079,6 +5154,8 @@ bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
 | 
			
		||||
void BluetoothDeviceRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_enum<enums::BluetoothDeviceRequestType>(2, this->request_type);
 | 
			
		||||
  buffer.encode_bool(3, this->has_address_type);
 | 
			
		||||
  buffer.encode_uint32(4, this->address_type);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothDeviceRequest::dump_to(std::string &out) const {
 | 
			
		||||
@@ -5092,6 +5169,15 @@ void BluetoothDeviceRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  request_type: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::BluetoothDeviceRequestType>(this->request_type));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_address_type: ");
 | 
			
		||||
  out.append(YESNO(this->has_address_type));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  address_type: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->address_type);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -160,6 +160,8 @@ enum BluetoothDeviceRequestType : uint32_t {
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1,
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2,
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3,
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE = 4,
 | 
			
		||||
  BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace enums
 | 
			
		||||
@@ -1004,6 +1006,7 @@ class ListEntitiesNumberResponse : public ProtoMessage {
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  std::string unit_of_measurement{};
 | 
			
		||||
  enums::NumberMode mode{};
 | 
			
		||||
  std::string device_class{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -1256,6 +1259,7 @@ class BluetoothLEAdvertisementResponse : public ProtoMessage {
 | 
			
		||||
  std::vector<std::string> service_uuids{};
 | 
			
		||||
  std::vector<BluetoothServiceData> service_data{};
 | 
			
		||||
  std::vector<BluetoothServiceData> manufacturer_data{};
 | 
			
		||||
  uint32_t address_type{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -1269,6 +1273,8 @@ class BluetoothDeviceRequest : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  enums::BluetoothDeviceRequestType request_type{};
 | 
			
		||||
  bool has_address_type{false};
 | 
			
		||||
  uint32_t address_type{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,8 @@
 | 
			
		||||
#include "user_services.h"
 | 
			
		||||
#include "api_noise_context.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@
 | 
			
		||||
#include "api_pb2.h"
 | 
			
		||||
#include "api_server.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
 | 
			
		||||
#define HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ CODEOWNERS = ["@OttoWinter"]
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema({}),
 | 
			
		||||
    cv.only_with_arduino,
 | 
			
		||||
    cv.only_on(["esp32", "esp8266"]),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,8 @@
 | 
			
		||||
#include "bedjet_child.h"
 | 
			
		||||
#include "bedjet_codec.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TIME
 | 
			
		||||
#include "esphome/components/time/real_time_clock.h"
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/components/binary_sensor/filter.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
 | 
			
		||||
namespace binary_sensor {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
 | 
			
		||||
namespace binary_sensor {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace binary_sensor_map {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ CONF_BLE_CLIENT_ID = "ble_client_id"
 | 
			
		||||
 | 
			
		||||
BLE_CLIENT_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_BLE_CLIENT_ID): cv.use_id(BLEClient),
 | 
			
		||||
        cv.GenerateID(CONF_BLE_CLIENT_ID): cv.use_id(BLEClient),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -78,7 +78,7 @@ async def register_ble_node(var, config):
 | 
			
		||||
 | 
			
		||||
BLE_WRITE_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(BLEClient),
 | 
			
		||||
        cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
 | 
			
		||||
        cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
        cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
        cv.Required(CONF_VALUE): cv.templatable(cv.ensure_list(cv.hex_uint8_t)),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/components/ble_client/ble_client.h"
 | 
			
		||||
 
 | 
			
		||||
@@ -7,12 +7,13 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <esp_gap_ble_api.h>
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
#include <esp_bt_defs.h>
 | 
			
		||||
#include <esp_gap_ble_api.h>
 | 
			
		||||
#include <esp_gatt_common_api.h>
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ble_client {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,11 +2,13 @@ from esphome.components import esp32_ble_tracker, esp32_ble_client
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.const import CONF_ACTIVE, CONF_ID
 | 
			
		||||
from esphome.components.esp32 import add_idf_sdkconfig_option
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
 | 
			
		||||
DEPENDENCIES = ["api", "esp32"]
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
 | 
			
		||||
CONF_CACHE_SERVICES = "cache_services"
 | 
			
		||||
CONF_CONNECTIONS = "connections"
 | 
			
		||||
MAX_CONNECTIONS = 3
 | 
			
		||||
 | 
			
		||||
@@ -47,6 +49,9 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BluetoothProxy),
 | 
			
		||||
            cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
 | 
			
		||||
            cv.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All(
 | 
			
		||||
                cv.only_with_esp_idf, cv.boolean
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CONNECTIONS): cv.All(
 | 
			
		||||
                cv.ensure_list(CONNECTION_SCHEMA),
 | 
			
		||||
                cv.Length(min=1, max=MAX_CONNECTIONS),
 | 
			
		||||
@@ -72,4 +77,7 @@ async def to_code(config):
 | 
			
		||||
        cg.add(var.register_connection(connection_var))
 | 
			
		||||
        await esp32_ble_tracker.register_client(connection_var, connection_conf)
 | 
			
		||||
 | 
			
		||||
    if config.get(CONF_CACHE_SERVICES):
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_BT_GATTC_CACHE_NVS_FLASH", True)
 | 
			
		||||
 | 
			
		||||
    cg.add_define("USE_BLUETOOTH_PROXY")
 | 
			
		||||
 
 | 
			
		||||
@@ -34,12 +34,39 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
        this->set_address(0);
 | 
			
		||||
        api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
			
		||||
                                                                this->proxy_->get_bluetooth_connections_limit());
 | 
			
		||||
      } else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
 | 
			
		||||
        api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
			
		||||
                                                                this->proxy_->get_bluetooth_connections_limit());
 | 
			
		||||
      }
 | 
			
		||||
      this->seen_mtu_or_services_ = false;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_CFG_MTU_EVT: {
 | 
			
		||||
      if (param->cfg_mtu.conn_id != this->conn_id_)
 | 
			
		||||
        break;
 | 
			
		||||
      if (!this->seen_mtu_or_services_) {
 | 
			
		||||
        // We don't know if we will get the MTU or the services first, so
 | 
			
		||||
        // only send the device connection true if we have already received
 | 
			
		||||
        // the services.
 | 
			
		||||
        this->seen_mtu_or_services_ = true;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->proxy_->get_bluetooth_connections_limit());
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
      if (param->search_cmpl.conn_id != this->conn_id_)
 | 
			
		||||
        break;
 | 
			
		||||
      if (!this->seen_mtu_or_services_) {
 | 
			
		||||
        // We don't know if we will get the MTU or the services first, so
 | 
			
		||||
        // only send the device connection true if we have already received
 | 
			
		||||
        // the mtu.
 | 
			
		||||
        this->seen_mtu_or_services_ = true;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->proxy_->get_bluetooth_connections_limit());
 | 
			
		||||
@@ -82,8 +109,6 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      if (this->get_characteristic(param->unreg_for_notify.handle) == nullptr)  // No conn_id in this event
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->unreg_for_notify.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d",
 | 
			
		||||
                 this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle,
 | 
			
		||||
@@ -99,8 +124,6 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      if (this->get_characteristic(param->reg_for_notify.handle) == nullptr)  // No conn_id in this event
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->reg_for_notify.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status);
 | 
			
		||||
@@ -141,18 +164,11 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_NOT_CONNECTED;
 | 
			
		||||
  }
 | 
			
		||||
  auto *characteristic = this->get_characteristic(handle);
 | 
			
		||||
  if (characteristic == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not found.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_INVALID_HANDLE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           characteristic->uuid.to_string().c_str());
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           handle);
 | 
			
		||||
 | 
			
		||||
  esp_err_t err =
 | 
			
		||||
      esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, characteristic->handle, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  esp_err_t err = esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char error, err=%d", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), err);
 | 
			
		||||
@@ -167,18 +183,12 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_NOT_CONNECTED;
 | 
			
		||||
  }
 | 
			
		||||
  auto *characteristic = this->get_characteristic(handle);
 | 
			
		||||
  if (characteristic == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not found.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_INVALID_HANDLE;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           handle);
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           characteristic->uuid.to_string().c_str());
 | 
			
		||||
 | 
			
		||||
  auto err = characteristic->write_value((uint8_t *) data.data(), data.size(),
 | 
			
		||||
                                         response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP);
 | 
			
		||||
  esp_err_t err =
 | 
			
		||||
      esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
 | 
			
		||||
                               response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), err);
 | 
			
		||||
@@ -193,18 +203,10 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_NOT_CONNECTED;
 | 
			
		||||
  }
 | 
			
		||||
  auto *descriptor = this->get_descriptor(handle);
 | 
			
		||||
  if (descriptor == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not found.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_INVALID_HANDLE;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           handle);
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           descriptor->uuid.to_string().c_str());
 | 
			
		||||
 | 
			
		||||
  esp_err_t err =
 | 
			
		||||
      esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  esp_err_t err = esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, handle, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char_descr error, err=%d", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), err);
 | 
			
		||||
@@ -213,25 +215,18 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
 | 
			
		||||
  return ESP_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data) {
 | 
			
		||||
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) {
 | 
			
		||||
  if (!this->connected()) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_NOT_CONNECTED;
 | 
			
		||||
  }
 | 
			
		||||
  auto *descriptor = this->get_descriptor(handle);
 | 
			
		||||
  if (descriptor == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not found.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_INVALID_HANDLE;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           handle);
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           descriptor->uuid.to_string().c_str());
 | 
			
		||||
 | 
			
		||||
  auto err =
 | 
			
		||||
      esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, data.size(),
 | 
			
		||||
                                     (uint8_t *) data.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  esp_err_t err = esp_ble_gattc_write_char_descr(
 | 
			
		||||
      this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
 | 
			
		||||
      response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), err);
 | 
			
		||||
@@ -246,26 +241,20 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_NOT_CONNECTED;
 | 
			
		||||
  }
 | 
			
		||||
  auto *characteristic = this->get_characteristic(handle);
 | 
			
		||||
  if (characteristic == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not found.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_INVALID_HANDLE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (enable) {
 | 
			
		||||
    ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications %s", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), characteristic->uuid.to_string().c_str());
 | 
			
		||||
    esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
 | 
			
		||||
    ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications handle %d", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), handle);
 | 
			
		||||
    esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, handle);
 | 
			
		||||
    if (err != ESP_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->connection_index_,
 | 
			
		||||
               this->address_str_.c_str(), err);
 | 
			
		||||
      return err;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications %s", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), characteristic->uuid.to_string().c_str());
 | 
			
		||||
    esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
 | 
			
		||||
    ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications handle %d", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), handle);
 | 
			
		||||
    esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, handle);
 | 
			
		||||
    if (err != ESP_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_unregister_for_notify failed, err=%d", this->connection_index_,
 | 
			
		||||
               this->address_str_.c_str(), err);
 | 
			
		||||
 
 | 
			
		||||
@@ -17,14 +17,15 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
 | 
			
		||||
  esp_err_t read_characteristic(uint16_t handle);
 | 
			
		||||
  esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);
 | 
			
		||||
  esp_err_t read_descriptor(uint16_t handle);
 | 
			
		||||
  esp_err_t write_descriptor(uint16_t handle, const std::string &data);
 | 
			
		||||
  esp_err_t write_descriptor(uint16_t handle, const std::string &data, bool response);
 | 
			
		||||
 | 
			
		||||
  esp_err_t notify_characteristic(uint16_t handle, bool enable);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  friend class BluetoothProxy;
 | 
			
		||||
  bool seen_mtu_or_services_{false};
 | 
			
		||||
 | 
			
		||||
  int16_t send_service_{-1};
 | 
			
		||||
  int16_t send_service_{-2};
 | 
			
		||||
  BluetoothProxy *proxy_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,19 @@ namespace esphome {
 | 
			
		||||
namespace bluetooth_proxy {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "bluetooth_proxy";
 | 
			
		||||
static const int DONE_SENDING_SERVICES = -2;
 | 
			
		||||
 | 
			
		||||
std::vector<uint64_t> get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) {
 | 
			
		||||
  esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid();
 | 
			
		||||
  return std::vector<uint64_t>{((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
 | 
			
		||||
                                   ((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
 | 
			
		||||
                                   ((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
 | 
			
		||||
                                   ((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]),
 | 
			
		||||
                               ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
 | 
			
		||||
                                   ((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
 | 
			
		||||
                                   ((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
 | 
			
		||||
                                   ((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0])};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
 | 
			
		||||
 | 
			
		||||
@@ -26,6 +39,7 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
 | 
			
		||||
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
			
		||||
  api::BluetoothLEAdvertisementResponse resp;
 | 
			
		||||
  resp.address = device.address_uint64();
 | 
			
		||||
  resp.address_type = device.get_address_type();
 | 
			
		||||
  if (!device.get_name().empty())
 | 
			
		||||
    resp.name = device.get_name();
 | 
			
		||||
  resp.rssi = device.get_rssi();
 | 
			
		||||
@@ -52,6 +66,20 @@ void BluetoothProxy::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Active: %s", YESNO(this->active_));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int BluetoothProxy::get_bluetooth_connections_free() {
 | 
			
		||||
  int free = 0;
 | 
			
		||||
  for (auto *connection : this->connections_) {
 | 
			
		||||
    if (connection->address_ == 0) {
 | 
			
		||||
      free++;
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] Free connection", connection->get_connection_index());
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] Used connection by [%s]", connection->get_connection_index(),
 | 
			
		||||
               connection->address_str().c_str());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return free;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::loop() {
 | 
			
		||||
  if (!api::global_api_server->is_connected()) {
 | 
			
		||||
    for (auto *connection : this->connections_) {
 | 
			
		||||
@@ -62,32 +90,87 @@ void BluetoothProxy::loop() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  for (auto *connection : this->connections_) {
 | 
			
		||||
    if (connection->send_service_ == connection->services_.size()) {
 | 
			
		||||
      connection->send_service_ = -1;
 | 
			
		||||
    if (connection->send_service_ == connection->service_count_) {
 | 
			
		||||
      connection->send_service_ = DONE_SENDING_SERVICES;
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_services_done(connection->get_address());
 | 
			
		||||
      if (connection->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
 | 
			
		||||
          connection->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
 | 
			
		||||
        connection->release_services();
 | 
			
		||||
      }
 | 
			
		||||
    } else if (connection->send_service_ >= 0) {
 | 
			
		||||
      auto &service = connection->services_[connection->send_service_];
 | 
			
		||||
      esp_gattc_service_elem_t service_result;
 | 
			
		||||
      uint16_t service_count = 1;
 | 
			
		||||
      esp_gatt_status_t service_status =
 | 
			
		||||
          esp_ble_gattc_get_service(connection->get_gattc_if(), connection->get_conn_id(), nullptr, &service_result,
 | 
			
		||||
                                    &service_count, connection->send_service_);
 | 
			
		||||
      connection->send_service_++;
 | 
			
		||||
      if (service_status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service error at offset=%d, status=%d",
 | 
			
		||||
                 connection->get_connection_index(), connection->address_str().c_str(), connection->send_service_ - 1,
 | 
			
		||||
                 service_status);
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      if (service_count == 0) {
 | 
			
		||||
        ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service missing, service_count=%d",
 | 
			
		||||
                 connection->get_connection_index(), connection->address_str().c_str(), service_count);
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTGetServicesResponse resp;
 | 
			
		||||
      resp.address = connection->get_address();
 | 
			
		||||
      api::BluetoothGATTService service_resp;
 | 
			
		||||
      service_resp.uuid = {service->uuid.get_128bit_high(), service->uuid.get_128bit_low()};
 | 
			
		||||
      service_resp.handle = service->start_handle;
 | 
			
		||||
      for (auto &characteristic : service->characteristics) {
 | 
			
		||||
      service_resp.uuid = get_128bit_uuid_vec(service_result.uuid);
 | 
			
		||||
      service_resp.handle = service_result.start_handle;
 | 
			
		||||
      uint16_t char_offset = 0;
 | 
			
		||||
      esp_gattc_char_elem_t char_result;
 | 
			
		||||
      while (true) {  // characteristics
 | 
			
		||||
        uint16_t char_count = 1;
 | 
			
		||||
        esp_gatt_status_t char_status = esp_ble_gattc_get_all_char(
 | 
			
		||||
            connection->get_gattc_if(), connection->get_conn_id(), service_result.start_handle,
 | 
			
		||||
            service_result.end_handle, &char_result, &char_count, char_offset);
 | 
			
		||||
        if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) {
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        if (char_status != ESP_GATT_OK) {
 | 
			
		||||
          ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", connection->get_connection_index(),
 | 
			
		||||
                   connection->address_str().c_str(), char_status);
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        if (char_count == 0) {
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        api::BluetoothGATTCharacteristic characteristic_resp;
 | 
			
		||||
        characteristic_resp.uuid = {characteristic->uuid.get_128bit_high(), characteristic->uuid.get_128bit_low()};
 | 
			
		||||
        characteristic_resp.handle = characteristic->handle;
 | 
			
		||||
        characteristic_resp.properties = characteristic->properties;
 | 
			
		||||
        for (auto &descriptor : characteristic->descriptors) {
 | 
			
		||||
        characteristic_resp.uuid = get_128bit_uuid_vec(char_result.uuid);
 | 
			
		||||
        characteristic_resp.handle = char_result.char_handle;
 | 
			
		||||
        characteristic_resp.properties = char_result.properties;
 | 
			
		||||
        char_offset++;
 | 
			
		||||
        uint16_t desc_offset = 0;
 | 
			
		||||
        esp_gattc_descr_elem_t desc_result;
 | 
			
		||||
        while (true) {  // descriptors
 | 
			
		||||
          uint16_t desc_count = 1;
 | 
			
		||||
          esp_gatt_status_t desc_status =
 | 
			
		||||
              esp_ble_gattc_get_all_descr(connection->get_gattc_if(), connection->get_conn_id(),
 | 
			
		||||
                                          char_result.char_handle, &desc_result, &desc_count, desc_offset);
 | 
			
		||||
          if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) {
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          if (desc_status != ESP_GATT_OK) {
 | 
			
		||||
            ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", connection->get_connection_index(),
 | 
			
		||||
                     connection->address_str().c_str(), desc_status);
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          if (desc_count == 0) {
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          api::BluetoothGATTDescriptor descriptor_resp;
 | 
			
		||||
          descriptor_resp.uuid = {descriptor->uuid.get_128bit_high(), descriptor->uuid.get_128bit_low()};
 | 
			
		||||
          descriptor_resp.handle = descriptor->handle;
 | 
			
		||||
          descriptor_resp.uuid = get_128bit_uuid_vec(desc_result.uuid);
 | 
			
		||||
          descriptor_resp.handle = desc_result.handle;
 | 
			
		||||
          characteristic_resp.descriptors.push_back(std::move(descriptor_resp));
 | 
			
		||||
          desc_offset++;
 | 
			
		||||
        }
 | 
			
		||||
        service_resp.characteristics.push_back(std::move(characteristic_resp));
 | 
			
		||||
      }
 | 
			
		||||
      resp.services.push_back(std::move(service_resp));
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_services(resp);
 | 
			
		||||
      connection->send_service_++;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -103,7 +186,13 @@ BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool rese
 | 
			
		||||
 | 
			
		||||
  for (auto *connection : this->connections_) {
 | 
			
		||||
    if (connection->get_address() == 0) {
 | 
			
		||||
      connection->send_service_ = DONE_SENDING_SERVICES;
 | 
			
		||||
      connection->set_address(address);
 | 
			
		||||
      // All connections must start at INIT
 | 
			
		||||
      // We only set the state if we allocate the connection
 | 
			
		||||
      // to avoid a race where multiple connection attempts
 | 
			
		||||
      // are made.
 | 
			
		||||
      connection->set_state(espbt::ClientState::INIT);
 | 
			
		||||
      return connection;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -113,6 +202,8 @@ BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool rese
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
 | 
			
		||||
  switch (msg.request_type) {
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE:
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE:
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
 | 
			
		||||
      auto *connection = this->get_connection_(msg.address, true);
 | 
			
		||||
      if (connection == nullptr) {
 | 
			
		||||
@@ -120,9 +211,63 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(msg.address, false);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] [%s] Searching to connect", connection->get_connection_index(),
 | 
			
		||||
      if (connection->state() == espbt::ClientState::CONNECTED ||
 | 
			
		||||
          connection->state() == espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Connection already established", connection->get_connection_index(),
 | 
			
		||||
                 connection->address_str().c_str());
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(msg.address, true);
 | 
			
		||||
        api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
			
		||||
                                                                this->get_bluetooth_connections_limit());
 | 
			
		||||
        return;
 | 
			
		||||
      } else if (connection->state() == espbt::ClientState::SEARCHING) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already searching for device",
 | 
			
		||||
                 connection->get_connection_index(), connection->address_str().c_str());
 | 
			
		||||
        return;
 | 
			
		||||
      } else if (connection->state() == espbt::ClientState::DISCOVERED) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, device already discovered",
 | 
			
		||||
                 connection->get_connection_index(), connection->address_str().c_str());
 | 
			
		||||
        return;
 | 
			
		||||
      } else if (connection->state() == espbt::ClientState::READY_TO_CONNECT) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, waiting in line to connect",
 | 
			
		||||
                 connection->get_connection_index(), connection->address_str().c_str());
 | 
			
		||||
        return;
 | 
			
		||||
      } else if (connection->state() == espbt::ClientState::CONNECTING) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, already connecting", connection->get_connection_index(),
 | 
			
		||||
                 connection->address_str().c_str());
 | 
			
		||||
        return;
 | 
			
		||||
      } else if (connection->state() == espbt::ClientState::DISCONNECTING) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Connection request ignored, device is disconnecting",
 | 
			
		||||
                 connection->get_connection_index(), connection->address_str().c_str());
 | 
			
		||||
        return;
 | 
			
		||||
      } else if (connection->state() != espbt::ClientState::INIT) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Connection already in progress", connection->get_connection_index(),
 | 
			
		||||
                 connection->address_str().c_str());
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (msg.request_type == api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITH_CACHE) {
 | 
			
		||||
        connection->set_connection_type(espbt::ConnectionType::V3_WITH_CACHE);
 | 
			
		||||
        ESP_LOGI(TAG, "[%d] [%s] Connecting v3 with cache", connection->get_connection_index(),
 | 
			
		||||
                 connection->address_str().c_str());
 | 
			
		||||
      } else if (msg.request_type == api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE) {
 | 
			
		||||
        connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
 | 
			
		||||
        ESP_LOGI(TAG, "[%d] [%s] Connecting v3 without cache", connection->get_connection_index(),
 | 
			
		||||
                 connection->address_str().c_str());
 | 
			
		||||
      } else {
 | 
			
		||||
        connection->set_connection_type(espbt::ConnectionType::V1);
 | 
			
		||||
        ESP_LOGI(TAG, "[%d] [%s] Connecting v1", connection->get_connection_index(), connection->address_str().c_str());
 | 
			
		||||
      }
 | 
			
		||||
      if (msg.has_address_type) {
 | 
			
		||||
        connection->remote_bda_[0] = (msg.address >> 40) & 0xFF;
 | 
			
		||||
        connection->remote_bda_[1] = (msg.address >> 32) & 0xFF;
 | 
			
		||||
        connection->remote_bda_[2] = (msg.address >> 24) & 0xFF;
 | 
			
		||||
        connection->remote_bda_[3] = (msg.address >> 16) & 0xFF;
 | 
			
		||||
        connection->remote_bda_[4] = (msg.address >> 8) & 0xFF;
 | 
			
		||||
        connection->remote_bda_[5] = (msg.address >> 0) & 0xFF;
 | 
			
		||||
        connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
 | 
			
		||||
        connection->set_state(espbt::ClientState::DISCOVERED);
 | 
			
		||||
      } else {
 | 
			
		||||
        connection->set_state(espbt::ClientState::SEARCHING);
 | 
			
		||||
      }
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->get_bluetooth_connections_limit());
 | 
			
		||||
      break;
 | 
			
		||||
@@ -201,7 +346,7 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto err = connection->write_descriptor(msg.handle, msg.data);
 | 
			
		||||
  auto err = connection->write_descriptor(msg.handle, msg.data, true);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
@@ -214,12 +359,13 @@ void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetSer
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (connection->services_.empty()) {
 | 
			
		||||
  if (!connection->service_count_) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str());
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_services_done(msg.address);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (connection->send_service_ == -1)  // Don't start sending services again if we're already sending them
 | 
			
		||||
  if (connection->send_service_ ==
 | 
			
		||||
      DONE_SENDING_SERVICES)  // Only start sending services if we're not already sending them
 | 
			
		||||
    connection->send_service_ = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/api/api_pb2.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
 | 
			
		||||
@@ -40,15 +41,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
			
		||||
  void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg);
 | 
			
		||||
  void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg);
 | 
			
		||||
 | 
			
		||||
  int get_bluetooth_connections_free() {
 | 
			
		||||
    int free = 0;
 | 
			
		||||
    for (auto *connection : this->connections_) {
 | 
			
		||||
      if (connection->address_ == 0) {
 | 
			
		||||
        free++;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return free;
 | 
			
		||||
  }
 | 
			
		||||
  int get_bluetooth_connections_free();
 | 
			
		||||
  int get_bluetooth_connections_limit() { return this->connections_.size(); }
 | 
			
		||||
 | 
			
		||||
  void set_active(bool active) { this->active_ = active; }
 | 
			
		||||
@@ -59,7 +52,6 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
 | 
			
		||||
 | 
			
		||||
  BluetoothConnection *get_connection_(uint64_t address, bool reserve);
 | 
			
		||||
 | 
			
		||||
  int16_t send_service_{-1};
 | 
			
		||||
  bool active_;
 | 
			
		||||
 | 
			
		||||
  std::vector<BluetoothConnection *> connections_{};
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/optional.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace canbus {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@
 | 
			
		||||
#include "esphome/components/output/binary_output.h"
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace cap1188 {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.final_validate as fv
 | 
			
		||||
from esphome.components import web_server_base
 | 
			
		||||
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
 | 
			
		||||
from esphome.const import CONF_ID, CONF_NETWORKS, CONF_PASSWORD, CONF_SSID, CONF_WIFI
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
from esphome.core import coroutine_with_priority, CORE
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["web_server_base"]
 | 
			
		||||
@@ -13,7 +12,6 @@ CODEOWNERS = ["@OttoWinter"]
 | 
			
		||||
captive_portal_ns = cg.esphome_ns.namespace("captive_portal")
 | 
			
		||||
CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component)
 | 
			
		||||
 | 
			
		||||
CONF_KEEP_USER_CREDENTIALS = "keep_user_credentials"
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
@@ -21,29 +19,13 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
 | 
			
		||||
                web_server_base.WebServerBase
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_KEEP_USER_CREDENTIALS, default=False): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.only_with_arduino,
 | 
			
		||||
    cv.only_on(["esp32", "esp8266"]),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_wifi(config):
 | 
			
		||||
    wifi_conf = fv.full_config.get()[CONF_WIFI]
 | 
			
		||||
    if config.get(CONF_KEEP_USER_CREDENTIALS, False) and (
 | 
			
		||||
        CONF_SSID in wifi_conf
 | 
			
		||||
        or CONF_PASSWORD in wifi_conf
 | 
			
		||||
        or CONF_NETWORKS in wifi_conf
 | 
			
		||||
    ):
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"WiFi credentials cannot be used together with {CONF_KEEP_USER_CREDENTIALS}"
 | 
			
		||||
        )
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = validate_wifi
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(64.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
 | 
			
		||||
@@ -57,6 +39,3 @@ async def to_code(config):
 | 
			
		||||
        cg.add_library("WiFi", None)
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        cg.add_library("DNSServer", None)
 | 
			
		||||
 | 
			
		||||
    if config.get(CONF_KEEP_USER_CREDENTIALS, False):
 | 
			
		||||
        cg.add_define("USE_CAPTIVE_PORTAL_KEEP_USER_CREDENTIALS")
 | 
			
		||||
 
 | 
			
		||||
@@ -27,10 +27,10 @@ DEFAULT_DELAY = "2ms"
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CD74HC4067Component),
 | 
			
		||||
        cv.Required(CONF_PIN_S0): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
        cv.Required(CONF_PIN_S1): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
        cv.Required(CONF_PIN_S2): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
        cv.Required(CONF_PIN_S3): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
        cv.Required(CONF_PIN_S0): pins.gpio_output_pin_schema,
 | 
			
		||||
        cv.Required(CONF_PIN_S1): pins.gpio_output_pin_schema,
 | 
			
		||||
        cv.Required(CONF_PIN_S2): pins.gpio_output_pin_schema,
 | 
			
		||||
        cv.Required(CONF_PIN_S3): pins.gpio_output_pin_schema,
 | 
			
		||||
        cv.Optional(
 | 
			
		||||
            CONF_DELAY, default=DEFAULT_DELAY
 | 
			
		||||
        ): cv.positive_time_period_milliseconds,
 | 
			
		||||
 
 | 
			
		||||
@@ -19,22 +19,22 @@ class CD74HC4067Component : public Component {
 | 
			
		||||
  void activate_pin(uint8_t pin);
 | 
			
		||||
 | 
			
		||||
  /// set the pin connected to multiplexer control pin 0
 | 
			
		||||
  void set_pin_s0(InternalGPIOPin *pin) { this->pin_s0_ = pin; }
 | 
			
		||||
  void set_pin_s0(GPIOPin *pin) { this->pin_s0_ = pin; }
 | 
			
		||||
  /// set the pin connected to multiplexer control pin 1
 | 
			
		||||
  void set_pin_s1(InternalGPIOPin *pin) { this->pin_s1_ = pin; }
 | 
			
		||||
  void set_pin_s1(GPIOPin *pin) { this->pin_s1_ = pin; }
 | 
			
		||||
  /// set the pin connected to multiplexer control pin 2
 | 
			
		||||
  void set_pin_s2(InternalGPIOPin *pin) { this->pin_s2_ = pin; }
 | 
			
		||||
  void set_pin_s2(GPIOPin *pin) { this->pin_s2_ = pin; }
 | 
			
		||||
  /// set the pin connected to multiplexer control pin 3
 | 
			
		||||
  void set_pin_s3(InternalGPIOPin *pin) { this->pin_s3_ = pin; }
 | 
			
		||||
  void set_pin_s3(GPIOPin *pin) { this->pin_s3_ = pin; }
 | 
			
		||||
 | 
			
		||||
  /// set the delay needed after an input switch
 | 
			
		||||
  void set_switch_delay(uint32_t switch_delay) { this->switch_delay_ = switch_delay; }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  InternalGPIOPin *pin_s0_;
 | 
			
		||||
  InternalGPIOPin *pin_s1_;
 | 
			
		||||
  InternalGPIOPin *pin_s2_;
 | 
			
		||||
  InternalGPIOPin *pin_s3_;
 | 
			
		||||
  GPIOPin *pin_s0_;
 | 
			
		||||
  GPIOPin *pin_s1_;
 | 
			
		||||
  GPIOPin *pin_s2_;
 | 
			
		||||
  GPIOPin *pin_s3_;
 | 
			
		||||
  /// the currently active pin
 | 
			
		||||
  uint8_t active_pin_;
 | 
			
		||||
  uint32_t switch_delay_;
 | 
			
		||||
 
 | 
			
		||||
@@ -113,7 +113,9 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
 | 
			
		||||
                cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
 | 
			
		||||
                cv.Optional(CONF_TEMPERATURE_STEP): cv.temperature,
 | 
			
		||||
                cv.Optional(CONF_TEMPERATURE_STEP): cv.float_with_unit(
 | 
			
		||||
                    "visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?"
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
 | 
			
		||||
 
 | 
			
		||||
@@ -540,12 +540,15 @@ void Climate::dump_traits_(const char *tag) {
 | 
			
		||||
  ESP_LOGCONFIG(tag, "      - Min: %.1f", traits.get_visual_min_temperature());
 | 
			
		||||
  ESP_LOGCONFIG(tag, "      - Max: %.1f", traits.get_visual_max_temperature());
 | 
			
		||||
  ESP_LOGCONFIG(tag, "      - Step: %.1f", traits.get_visual_temperature_step());
 | 
			
		||||
  if (traits.get_supports_current_temperature())
 | 
			
		||||
  if (traits.get_supports_current_temperature()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "  [x] Supports current temperature");
 | 
			
		||||
  if (traits.get_supports_two_point_target_temperature())
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_two_point_target_temperature()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "  [x] Supports two-point target temperature");
 | 
			
		||||
  if (traits.get_supports_action())
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_action()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "  [x] Supports action");
 | 
			
		||||
  }
 | 
			
		||||
  if (!traits.get_supported_modes().empty()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "  [x] Supported modes:");
 | 
			
		||||
    for (ClimateMode m : traits.get_supported_modes())
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ using namespace esphome::cover;
 | 
			
		||||
CoverTraits CurrentBasedCover::get_traits() {
 | 
			
		||||
  auto traits = CoverTraits();
 | 
			
		||||
  traits.set_supports_position(true);
 | 
			
		||||
  traits.set_supports_toggle(true);
 | 
			
		||||
  traits.set_is_assumed_state(false);
 | 
			
		||||
  return traits;
 | 
			
		||||
}
 | 
			
		||||
@@ -20,6 +21,20 @@ void CurrentBasedCover::control(const CoverCall &call) {
 | 
			
		||||
  if (call.get_stop()) {
 | 
			
		||||
    this->direction_idle_();
 | 
			
		||||
  }
 | 
			
		||||
  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()) {
 | 
			
		||||
    auto pos = *call.get_position();
 | 
			
		||||
    if (pos == this->position) {
 | 
			
		||||
@@ -164,7 +179,7 @@ bool CurrentBasedCover::is_closing_blocked_() const {
 | 
			
		||||
  if (this->close_obstacle_current_threshold_ == FLT_MAX) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return this->open_sensor_->get_state() > this->open_obstacle_current_threshold_;
 | 
			
		||||
  return this->close_sensor_->get_state() > this->close_obstacle_current_threshold_;
 | 
			
		||||
}
 | 
			
		||||
bool CurrentBasedCover::is_initial_delay_finished_() const {
 | 
			
		||||
  return millis() - this->start_dir_time_ > this->start_sensing_delay_;
 | 
			
		||||
@@ -202,9 +217,11 @@ void CurrentBasedCover::start_direction_(CoverOperation dir) {
 | 
			
		||||
      trig = this->stop_trigger_;
 | 
			
		||||
      break;
 | 
			
		||||
    case COVER_OPERATION_OPENING:
 | 
			
		||||
      this->last_operation_ = dir;
 | 
			
		||||
      trig = this->open_trigger_;
 | 
			
		||||
      break;
 | 
			
		||||
    case COVER_OPERATION_CLOSING:
 | 
			
		||||
      this->last_operation_ = dir;
 | 
			
		||||
      trig = this->close_trigger_;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
 
 | 
			
		||||
@@ -89,6 +89,8 @@ class CurrentBasedCover : public cover::Cover, public Component {
 | 
			
		||||
  uint32_t start_dir_time_{0};
 | 
			
		||||
  uint32_t last_publish_time_{0};
 | 
			
		||||
  float target_position_{0};
 | 
			
		||||
 | 
			
		||||
  cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace current_based
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace custom {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/climate/climate.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace custom {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,8 @@
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/cover/cover.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace custom {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/light/light_output.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace custom {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@
 | 
			
		||||
#include "esphome/components/output/binary_output.h"
 | 
			
		||||
#include "esphome/components/output/float_output.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace custom {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace custom {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/switch/switch.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace custom {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/text_sensor/text_sensor.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace custom {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace custom_component {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -29,8 +29,9 @@ void DAC7678Output::setup() {
 | 
			
		||||
    ESP_LOGE(TAG, "Reset failed");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  } else
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGV(TAG, "Reset succeeded");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  delayMicroseconds(1000);
 | 
			
		||||
 | 
			
		||||
@@ -40,17 +41,19 @@ void DAC7678Output::setup() {
 | 
			
		||||
      ESP_LOGE(TAG, "Set internal reference failed");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return;
 | 
			
		||||
    } else
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGV(TAG, "Internal reference enabled");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DAC7678Output::dump_config() {
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Setting up DAC7678 failed!");
 | 
			
		||||
  } else
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "DAC7678 initialised");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DAC7678Output::register_channel(DAC7678Channel *channel) {
 | 
			
		||||
  auto c = channel->channel_;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esp_one_wire.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dallas {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -85,9 +85,7 @@ bool HOT IRAM_ATTR ESPOneWire::read_bit() {
 | 
			
		||||
  // whereas on esp-idf it already happens during the pin_mode(OUTPUT)
 | 
			
		||||
  // manually correct for this with these constants.
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
 | 
			
		||||
  uint32_t timing_constant = 14;
 | 
			
		||||
#elif defined(USE_ESP32_FRAMEWORK_ESP_IDF)
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  uint32_t timing_constant = 12;
 | 
			
		||||
#else
 | 
			
		||||
  uint32_t timing_constant = 14;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
#include "esphome/components/uart/uart.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace daly_bms {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
@@ -6,6 +7,7 @@ from esphome.components.packages import validate_source_shorthand
 | 
			
		||||
from esphome.const import CONF_WIFI
 | 
			
		||||
from esphome.wizard import wizard_file
 | 
			
		||||
from esphome.yaml_util import dump
 | 
			
		||||
from esphome import git
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import")
 | 
			
		||||
@@ -25,9 +27,12 @@ def validate_import_url(value):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONF_PACKAGE_IMPORT_URL = "package_import_url"
 | 
			
		||||
CONF_IMPORT_FULL_CONFIG = "import_full_config"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_PACKAGE_IMPORT_URL): validate_import_url,
 | 
			
		||||
        cv.Optional(CONF_IMPORT_FULL_CONFIG, default=False): cv.boolean,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -41,6 +46,9 @@ wifi:
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    cg.add_define("USE_DASHBOARD_IMPORT")
 | 
			
		||||
    url = config[CONF_PACKAGE_IMPORT_URL]
 | 
			
		||||
    if config[CONF_IMPORT_FULL_CONFIG]:
 | 
			
		||||
        url += "?full_config"
 | 
			
		||||
    cg.add(dashboard_import_ns.set_package_import_url(config[CONF_PACKAGE_IMPORT_URL]))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -63,6 +71,19 @@ def import_config(
 | 
			
		||||
            ),
 | 
			
		||||
            encoding="utf8",
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        git_file = git.GitFile.from_shorthand(import_url)
 | 
			
		||||
 | 
			
		||||
        if git_file.query and "full_config" in git_file.query:
 | 
			
		||||
            url = git_file.raw_url
 | 
			
		||||
            try:
 | 
			
		||||
                req = requests.get(url, timeout=30)
 | 
			
		||||
                req.raise_for_status()
 | 
			
		||||
            except requests.exceptions.RequestException as e:
 | 
			
		||||
                raise ValueError(f"Error while fetching {url}: {e}") from e
 | 
			
		||||
 | 
			
		||||
            p.write_text(req.text, encoding="utf8")
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            config = {
 | 
			
		||||
                "substitutions": {"name": name},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,15 @@
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.final_validate as fv
 | 
			
		||||
from esphome.components import logger
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_DEVICE,
 | 
			
		||||
    CONF_FREE,
 | 
			
		||||
    CONF_FRAGMENTATION,
 | 
			
		||||
    CONF_BLOCK,
 | 
			
		||||
    CONF_DEVICE,
 | 
			
		||||
    CONF_FRAGMENTATION,
 | 
			
		||||
    CONF_FREE,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_LEVEL,
 | 
			
		||||
    CONF_LOGGER,
 | 
			
		||||
    CONF_LOOP_TIME,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -39,6 +43,18 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
).extend(cv.polling_component_schema("60s"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _final_validate(_):
 | 
			
		||||
    logger_conf = fv.full_config.get()[CONF_LOGGER]
 | 
			
		||||
    severity = logger.LOG_LEVEL_SEVERITY.index(logger_conf[CONF_LEVEL])
 | 
			
		||||
    if severity < logger.LOG_LEVEL_SEVERITY.index("DEBUG"):
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "The debug component requires the logger to be at least at DEBUG level"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = _final_validate
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -38,14 +38,9 @@ static uint32_t get_free_heap() {
 | 
			
		||||
 | 
			
		||||
void DebugComponent::dump_config() {
 | 
			
		||||
  std::string device_info;
 | 
			
		||||
  std::string reset_reason;
 | 
			
		||||
  device_info.reserve(256);
 | 
			
		||||
 | 
			
		||||
#ifndef ESPHOME_LOG_HAS_DEBUG
 | 
			
		||||
  ESP_LOGE(TAG, "Debug Component requires debug log level!");
 | 
			
		||||
  this->status_set_error();
 | 
			
		||||
  return;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Debug component:");
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  LOG_TEXT_SENSOR("  ", "Device info", this->device_info_);
 | 
			
		||||
@@ -146,7 +141,6 @@ void DebugComponent::dump_config() {
 | 
			
		||||
  device_info += "|EFuse MAC: ";
 | 
			
		||||
  device_info += mac;
 | 
			
		||||
 | 
			
		||||
  const char *reset_reason;
 | 
			
		||||
  switch (rtc_get_reset_reason(0)) {
 | 
			
		||||
    case POWERON_RESET:
 | 
			
		||||
      reset_reason = "Power On Reset";
 | 
			
		||||
@@ -196,7 +190,7 @@ void DebugComponent::dump_config() {
 | 
			
		||||
    default:
 | 
			
		||||
      reset_reason = "Unknown Reset Reason";
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "Reset Reason: %s", reset_reason);
 | 
			
		||||
  ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
 | 
			
		||||
  device_info += "|Reset: ";
 | 
			
		||||
  device_info += reset_reason;
 | 
			
		||||
 | 
			
		||||
@@ -270,6 +264,8 @@ void DebugComponent::dump_config() {
 | 
			
		||||
  device_info += ESP.getResetReason().c_str();
 | 
			
		||||
  device_info += "|";
 | 
			
		||||
  device_info += ESP.getResetInfo().c_str();
 | 
			
		||||
 | 
			
		||||
  reset_reason = ESP.getResetReason().c_str();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
@@ -278,6 +274,9 @@ void DebugComponent::dump_config() {
 | 
			
		||||
      device_info.resize(255);
 | 
			
		||||
    this->device_info_->publish_state(device_info);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->reset_reason_ != nullptr) {
 | 
			
		||||
    this->reset_reason_->publish_state(reset_reason);
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_TEXT_SENSOR
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ class DebugComponent : public PollingComponent {
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  void set_device_info_sensor(text_sensor::TextSensor *device_info) { device_info_ = device_info; }
 | 
			
		||||
  void set_reset_reason_sensor(text_sensor::TextSensor *reset_reason) { reset_reason_ = reset_reason; }
 | 
			
		||||
#endif  // USE_TEXT_SENSOR
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; }
 | 
			
		||||
@@ -50,6 +51,7 @@ class DebugComponent : public PollingComponent {
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  text_sensor::TextSensor *device_info_{nullptr};
 | 
			
		||||
  text_sensor::TextSensor *reset_reason_{nullptr};
 | 
			
		||||
#endif  // USE_TEXT_SENSOR
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,29 @@
 | 
			
		||||
from esphome.components import text_sensor
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.const import CONF_DEVICE, ENTITY_CATEGORY_DIAGNOSTIC
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DEVICE,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    ICON_CHIP,
 | 
			
		||||
    ICON_RESTART,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from . import CONF_DEBUG_ID, DebugComponent
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["debug"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONF_RESET_REASON = "reset_reason"
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent),
 | 
			
		||||
        cv.Optional(CONF_DEVICE): text_sensor.text_sensor_schema(
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC
 | 
			
		||||
            icon=ICON_CHIP,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_RESET_REASON): text_sensor.text_sensor_schema(
 | 
			
		||||
            icon=ICON_RESTART,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
@@ -24,3 +35,6 @@ async def to_code(config):
 | 
			
		||||
    if CONF_DEVICE in config:
 | 
			
		||||
        sens = await text_sensor.new_text_sensor(config[CONF_DEVICE])
 | 
			
		||||
        cg.add(debug_component.set_device_info_sensor(sens))
 | 
			
		||||
    if CONF_RESET_REASON in config:
 | 
			
		||||
        sens = await text_sensor.new_text_sensor(config[CONF_RESET_REASON])
 | 
			
		||||
        cg.add(debug_component.set_reset_reason_sensor(sens))
 | 
			
		||||
 
 | 
			
		||||
@@ -114,9 +114,9 @@ void DeepSleepComponent::begin_sleep(bool manual) {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "Beginning Deep Sleep");
 | 
			
		||||
  if (this->sleep_duration_.has_value())
 | 
			
		||||
  if (this->sleep_duration_.has_value()) {
 | 
			
		||||
    ESP_LOGI(TAG, "Sleeping for %" PRId64 "us", *this->sleep_duration_);
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
  App.run_safe_shutdown_hooks();
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP32)
 | 
			
		||||
@@ -147,7 +147,7 @@ void DeepSleepComponent::begin_sleep(bool manual) {
 | 
			
		||||
    if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
 | 
			
		||||
      level = !level;
 | 
			
		||||
    }
 | 
			
		||||
    esp_deep_sleep_enable_gpio_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()),
 | 
			
		||||
    esp_deep_sleep_enable_gpio_wakeup(1 << this->wakeup_pin_->get_pin(),
 | 
			
		||||
                                      static_cast<esp_deepsleep_gpio_wake_up_mode_t>(level));
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -77,14 +77,16 @@ void DFPlayer::loop() {
 | 
			
		||||
          case 0x3A:
 | 
			
		||||
            if (argument == 1) {
 | 
			
		||||
              ESP_LOGI(TAG, "USB loaded");
 | 
			
		||||
            } else if (argument == 2)
 | 
			
		||||
            } else if (argument == 2) {
 | 
			
		||||
              ESP_LOGI(TAG, "TF Card loaded");
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
          case 0x3B:
 | 
			
		||||
            if (argument == 1) {
 | 
			
		||||
              ESP_LOGI(TAG, "USB unloaded");
 | 
			
		||||
            } else if (argument == 2)
 | 
			
		||||
            } else if (argument == 2) {
 | 
			
		||||
              ESP_LOGI(TAG, "TF Card unloaded");
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
          case 0x3F:
 | 
			
		||||
            if (argument == 1) {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,9 @@
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "display_color_utils.h"
 | 
			
		||||
 | 
			
		||||
#include <cstdarg>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TIME
 | 
			
		||||
#include "esphome/components/time/real_time_clock.h"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										430
									
								
								esphome/components/display_menu_base/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										430
									
								
								esphome/components/display_menu_base/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,430 @@
 | 
			
		||||
import re
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation, core
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_ON_VALUE,
 | 
			
		||||
    CONF_COMMAND,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
    CONF_FORMAT,
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_ACTIVE,
 | 
			
		||||
)
 | 
			
		||||
from esphome.automation import maybe_simple_id
 | 
			
		||||
from esphome.components.select import Select
 | 
			
		||||
from esphome.components.number import Number
 | 
			
		||||
from esphome.components.switch import Switch
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@numo68"]
 | 
			
		||||
 | 
			
		||||
display_menu_base_ns = cg.esphome_ns.namespace("display_menu_base")
 | 
			
		||||
 | 
			
		||||
CONF_DISPLAY_ID = "display_id"
 | 
			
		||||
 | 
			
		||||
CONF_ROTARY = "rotary"
 | 
			
		||||
CONF_JOYSTICK = "joystick"
 | 
			
		||||
CONF_LABEL = "label"
 | 
			
		||||
CONF_MENU = "menu"
 | 
			
		||||
CONF_BACK = "back"
 | 
			
		||||
CONF_TEXT = "text"
 | 
			
		||||
CONF_SELECT = "select"
 | 
			
		||||
CONF_SWITCH = "switch"
 | 
			
		||||
CONF_CUSTOM = "custom"
 | 
			
		||||
CONF_ITEMS = "items"
 | 
			
		||||
CONF_ON_TEXT = "on_text"
 | 
			
		||||
CONF_OFF_TEXT = "off_text"
 | 
			
		||||
CONF_VALUE_LAMBDA = "value_lambda"
 | 
			
		||||
CONF_IMMEDIATE_EDIT = "immediate_edit"
 | 
			
		||||
CONF_ROOT_ITEM_ID = "root_item_id"
 | 
			
		||||
CONF_ON_ENTER = "on_enter"
 | 
			
		||||
CONF_ON_LEAVE = "on_leave"
 | 
			
		||||
CONF_ON_NEXT = "on_next"
 | 
			
		||||
CONF_ON_PREV = "on_prev"
 | 
			
		||||
 | 
			
		||||
DisplayMenuComponent = display_menu_base_ns.class_("DisplayMenuComponent", cg.Component)
 | 
			
		||||
 | 
			
		||||
MenuItem = display_menu_base_ns.class_("MenuItem")
 | 
			
		||||
MenuItemConstPtr = MenuItem.operator("ptr").operator("const")
 | 
			
		||||
MenuItemMenu = display_menu_base_ns.class_("MenuItemMenu")
 | 
			
		||||
MenuItemSelect = display_menu_base_ns.class_("MenuItemSelect")
 | 
			
		||||
MenuItemNumber = display_menu_base_ns.class_("MenuItemNumber")
 | 
			
		||||
MenuItemSwitch = display_menu_base_ns.class_("MenuItemSwitch")
 | 
			
		||||
MenuItemCommand = display_menu_base_ns.class_("MenuItemCommand")
 | 
			
		||||
MenuItemCustom = display_menu_base_ns.class_("MenuItemCustom")
 | 
			
		||||
 | 
			
		||||
UpAction = display_menu_base_ns.class_("UpAction", automation.Action)
 | 
			
		||||
DownAction = display_menu_base_ns.class_("DownAction", automation.Action)
 | 
			
		||||
LeftAction = display_menu_base_ns.class_("LeftAction", automation.Action)
 | 
			
		||||
RightAction = display_menu_base_ns.class_("RightAction", automation.Action)
 | 
			
		||||
EnterAction = display_menu_base_ns.class_("EnterAction", automation.Action)
 | 
			
		||||
ShowAction = display_menu_base_ns.class_("ShowAction", automation.Action)
 | 
			
		||||
HideAction = display_menu_base_ns.class_("HideAction", automation.Action)
 | 
			
		||||
ShowMainAction = display_menu_base_ns.class_("ShowMainAction", automation.Action)
 | 
			
		||||
 | 
			
		||||
IsActiveCondition = display_menu_base_ns.class_(
 | 
			
		||||
    "IsActiveCondition", automation.Condition
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
MenuItemType = display_menu_base_ns.enum("MenuItemType")
 | 
			
		||||
 | 
			
		||||
MENU_ITEM_TYPES = {
 | 
			
		||||
    CONF_LABEL: MenuItemType.MENU_ITEM_LABEL,
 | 
			
		||||
    CONF_MENU: MenuItemType.MENU_ITEM_MENU,
 | 
			
		||||
    CONF_BACK: MenuItemType.MENU_ITEM_BACK,
 | 
			
		||||
    CONF_SELECT: MenuItemType.MENU_ITEM_SELECT,
 | 
			
		||||
    CONF_NUMBER: MenuItemType.MENU_ITEM_NUMBER,
 | 
			
		||||
    CONF_SWITCH: MenuItemType.MENU_ITEM_SWITCH,
 | 
			
		||||
    CONF_COMMAND: MenuItemType.MENU_ITEM_COMMAND,
 | 
			
		||||
    CONF_CUSTOM: MenuItemType.MENU_ITEM_CUSTOM,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MENU_ITEMS_WITH_SPECIALIZED_CLASSES = (
 | 
			
		||||
    CONF_MENU,
 | 
			
		||||
    CONF_SELECT,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
    CONF_SWITCH,
 | 
			
		||||
    CONF_COMMAND,
 | 
			
		||||
    CONF_CUSTOM,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MenuMode = display_menu_base_ns.enum("MenuMode")
 | 
			
		||||
 | 
			
		||||
MENU_MODES = {
 | 
			
		||||
    CONF_ROTARY: MenuMode.MENU_MODE_ROTARY,
 | 
			
		||||
    CONF_JOYSTICK: MenuMode.MENU_MODE_JOYSTICK,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DisplayMenuOnEnterTrigger = display_menu_base_ns.class_(
 | 
			
		||||
    "DisplayMenuOnEnterTrigger", automation.Trigger
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DisplayMenuOnLeaveTrigger = display_menu_base_ns.class_(
 | 
			
		||||
    "DisplayMenuOnLeaveTrigger", automation.Trigger
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DisplayMenuOnValueTrigger = display_menu_base_ns.class_(
 | 
			
		||||
    "DisplayMenuOnValueTrigger", automation.Trigger
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DisplayMenuOnNextTrigger = display_menu_base_ns.class_(
 | 
			
		||||
    "DisplayMenuOnNextTrigger", automation.Trigger
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DisplayMenuOnPrevTrigger = display_menu_base_ns.class_(
 | 
			
		||||
    "DisplayMenuOnPrevTrigger", automation.Trigger
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_format(format):
 | 
			
		||||
    if re.search(r"^%([+-])*(\d+)*(\.\d+)*[fg]$", format) is None:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"{CONF_FORMAT}: has to specify a printf-like format string specifying exactly one f or g type conversion, '{format}' provided"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return format
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Use a simple indirection to circumvent the recursion limitation
 | 
			
		||||
def menu_item_schema(value):
 | 
			
		||||
    return MENU_ITEM_SCHEMA(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MENU_ITEM_COMMON_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_TEXT): cv.templatable(cv.string),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MENU_ITEM_ENTER_LEAVE_SCHEMA = MENU_ITEM_COMMON_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_ON_ENTER): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                    DisplayMenuOnEnterTrigger
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_LEAVE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                    DisplayMenuOnLeaveTrigger
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MENU_ITEM_VALUE_SCHEMA = MENU_ITEM_COMMON_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_ON_VALUE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                    DisplayMenuOnValueTrigger
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA = MENU_ITEM_ENTER_LEAVE_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_ON_VALUE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                    DisplayMenuOnValueTrigger
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
MENU_ITEM_SCHEMA = cv.typed_schema(
 | 
			
		||||
    {
 | 
			
		||||
        CONF_LABEL: MENU_ITEM_COMMON_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItem),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_BACK: MENU_ITEM_COMMON_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItem),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_MENU: MENU_ITEM_ENTER_LEAVE_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItemMenu),
 | 
			
		||||
                cv.Required(CONF_ITEMS): cv.All(
 | 
			
		||||
                    cv.ensure_list(menu_item_schema), cv.Length(min=1)
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_SELECT: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItemSelect),
 | 
			
		||||
                cv.Required(CONF_SELECT): cv.use_id(Select),
 | 
			
		||||
                cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_NUMBER: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItemNumber),
 | 
			
		||||
                cv.Required(CONF_NUMBER): cv.use_id(Number),
 | 
			
		||||
                cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_FORMAT, default="%.1f"): cv.All(
 | 
			
		||||
                    cv.string_strict,
 | 
			
		||||
                    validate_format,
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_SWITCH: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItemSwitch),
 | 
			
		||||
                cv.Required(CONF_SWITCH): cv.use_id(Switch),
 | 
			
		||||
                cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_ON_TEXT, default="On"): cv.string_strict,
 | 
			
		||||
                cv.Optional(CONF_OFF_TEXT, default="Off"): cv.string_strict,
 | 
			
		||||
                cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_COMMAND: MENU_ITEM_VALUE_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItemCommand),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_CUSTOM: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_ID): cv.declare_id(MenuItemCustom),
 | 
			
		||||
                cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
 | 
			
		||||
                cv.Optional(CONF_ON_NEXT): automation.validate_automation(
 | 
			
		||||
                    {
 | 
			
		||||
                        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                            DisplayMenuOnNextTrigger
 | 
			
		||||
                        ),
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
                cv.Optional(CONF_ON_PREV): automation.validate_automation(
 | 
			
		||||
                    {
 | 
			
		||||
                        cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                            DisplayMenuOnPrevTrigger
 | 
			
		||||
                        ),
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    },
 | 
			
		||||
    default_type="label",
 | 
			
		||||
    lower=True,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DISPLAY_MENU_BASE_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
 | 
			
		||||
        cv.GenerateID(CONF_ROOT_ITEM_ID): cv.declare_id(MenuItemMenu),
 | 
			
		||||
        cv.Optional(CONF_MODE, default=CONF_ROTARY): cv.enum(MENU_MODES),
 | 
			
		||||
        cv.Optional(CONF_ON_ENTER): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                    DisplayMenuOnEnterTrigger
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_LEAVE): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                    DisplayMenuOnLeaveTrigger
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Required(CONF_ITEMS): cv.All(
 | 
			
		||||
            cv.ensure_list(MENU_ITEM_SCHEMA), cv.Length(min=1)
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
MENU_ACTION_SCHEMA = maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_ID): cv.use_id(DisplayMenuComponent),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("display_menu.up", UpAction, MENU_ACTION_SCHEMA)
 | 
			
		||||
async def menu_up_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("display_menu.down", DownAction, MENU_ACTION_SCHEMA)
 | 
			
		||||
async def menu_down_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("display_menu.left", LeftAction, MENU_ACTION_SCHEMA)
 | 
			
		||||
async def menu_left_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("display_menu.right", RightAction, MENU_ACTION_SCHEMA)
 | 
			
		||||
async def menu_right_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("display_menu.enter", EnterAction, MENU_ACTION_SCHEMA)
 | 
			
		||||
async def menu_enter_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("display_menu.show", ShowAction, MENU_ACTION_SCHEMA)
 | 
			
		||||
async def menu_show_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("display_menu.hide", HideAction, MENU_ACTION_SCHEMA)
 | 
			
		||||
async def menu_hide_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "display_menu.show_main", ShowMainAction, MENU_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
async def menu_show_main_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_condition(
 | 
			
		||||
    "display_menu.is_active",
 | 
			
		||||
    IsActiveCondition,
 | 
			
		||||
    automation.maybe_simple_id(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(CONF_ID): cv.use_id(DisplayMenuComponent),
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
async def display_menu_is_active_to_code(config, condition_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(condition_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def menu_item_to_code(menu, config, parent):
 | 
			
		||||
    if config[CONF_TYPE] in MENU_ITEMS_WITH_SPECIALIZED_CLASSES:
 | 
			
		||||
        item = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    else:
 | 
			
		||||
        item = cg.new_Pvariable(config[CONF_ID], MENU_ITEM_TYPES[config[CONF_TYPE]])
 | 
			
		||||
    cg.add(parent.add_item(item))
 | 
			
		||||
    if CONF_TEXT in config:
 | 
			
		||||
        if isinstance(config[CONF_TEXT], core.Lambda):
 | 
			
		||||
            template_ = await cg.templatable(
 | 
			
		||||
                config[CONF_TEXT], [(MenuItemConstPtr, "it")], cg.std_string
 | 
			
		||||
            )
 | 
			
		||||
            cg.add(item.set_text(template_))
 | 
			
		||||
        else:
 | 
			
		||||
            cg.add(item.set_text(config[CONF_TEXT]))
 | 
			
		||||
    if CONF_VALUE_LAMBDA in config:
 | 
			
		||||
        template_ = await cg.process_lambda(
 | 
			
		||||
            config[CONF_VALUE_LAMBDA],
 | 
			
		||||
            [(MenuItemConstPtr, "it")],
 | 
			
		||||
            return_type=cg.std_string,
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(item.set_value_lambda(template_))
 | 
			
		||||
    if CONF_ITEMS in config:
 | 
			
		||||
        for c in config[CONF_ITEMS]:
 | 
			
		||||
            await menu_item_to_code(menu, c, item)
 | 
			
		||||
    if CONF_IMMEDIATE_EDIT in config:
 | 
			
		||||
        cg.add(item.set_immediate_edit(config[CONF_IMMEDIATE_EDIT]))
 | 
			
		||||
    if config[CONF_TYPE] == CONF_SELECT:
 | 
			
		||||
        var = await cg.get_variable(config[CONF_SELECT])
 | 
			
		||||
        cg.add(item.set_select_variable(var))
 | 
			
		||||
    if config[CONF_TYPE] == CONF_NUMBER:
 | 
			
		||||
        var = await cg.get_variable(config[CONF_NUMBER])
 | 
			
		||||
        cg.add(item.set_number_variable(var))
 | 
			
		||||
        cg.add(item.set_format(config[CONF_FORMAT]))
 | 
			
		||||
    if config[CONF_TYPE] == CONF_SWITCH:
 | 
			
		||||
        var = await cg.get_variable(config[CONF_SWITCH])
 | 
			
		||||
        cg.add(item.set_switch_variable(var))
 | 
			
		||||
        cg.add(item.set_on_text(config[CONF_ON_TEXT]))
 | 
			
		||||
        cg.add(item.set_off_text(config[CONF_OFF_TEXT]))
 | 
			
		||||
    for conf in config.get(CONF_ON_ENTER, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
 | 
			
		||||
        await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_LEAVE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
 | 
			
		||||
        await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_VALUE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
 | 
			
		||||
        await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_NEXT, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
 | 
			
		||||
        await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_PREV, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
 | 
			
		||||
        await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def display_menu_to_code(menu, config):
 | 
			
		||||
    cg.add(menu.set_active(config[CONF_ACTIVE]))
 | 
			
		||||
    root_item = cg.new_Pvariable(config[CONF_ROOT_ITEM_ID])
 | 
			
		||||
    cg.add(menu.set_root_item(root_item))
 | 
			
		||||
    cg.add(menu.set_mode(config[CONF_MODE]))
 | 
			
		||||
    for c in config[CONF_ITEMS]:
 | 
			
		||||
        await menu_item_to_code(menu, c, root_item)
 | 
			
		||||
    for conf in config.get(CONF_ON_ENTER, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], root_item)
 | 
			
		||||
        await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_LEAVE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], root_item)
 | 
			
		||||
        await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
 | 
			
		||||
							
								
								
									
										133
									
								
								esphome/components/display_menu_base/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								esphome/components/display_menu_base/automation.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "display_menu_base.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display_menu_base {
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class UpAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit UpAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->up(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class DownAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DownAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->down(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class LeftAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit LeftAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->left(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class RightAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit RightAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->right(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class EnterAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit EnterAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->enter(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class ShowAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit ShowAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->show(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class HideAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit HideAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->hide(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class ShowMainAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit ShowMainAction(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->menu_->show_main(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
template<typename... Ts> class IsActiveCondition : public Condition<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit IsActiveCondition(DisplayMenuComponent *menu) : menu_(menu) {}
 | 
			
		||||
  bool check(Ts... x) override { return this->menu_->is_active(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  DisplayMenuComponent *menu_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayMenuOnEnterTrigger : public Trigger<const MenuItem *> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DisplayMenuOnEnterTrigger(MenuItem *parent) {
 | 
			
		||||
    parent->add_on_enter_callback([this, parent]() { this->trigger(parent); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayMenuOnLeaveTrigger : public Trigger<const MenuItem *> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DisplayMenuOnLeaveTrigger(MenuItem *parent) {
 | 
			
		||||
    parent->add_on_leave_callback([this, parent]() { this->trigger(parent); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayMenuOnValueTrigger : public Trigger<const MenuItem *> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DisplayMenuOnValueTrigger(MenuItem *parent) {
 | 
			
		||||
    parent->add_on_value_callback([this, parent]() { this->trigger(parent); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayMenuOnNextTrigger : public Trigger<const MenuItem *> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DisplayMenuOnNextTrigger(MenuItemCustom *parent) {
 | 
			
		||||
    parent->add_on_next_callback([this, parent]() { this->trigger(parent); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayMenuOnPrevTrigger : public Trigger<const MenuItem *> {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DisplayMenuOnPrevTrigger(MenuItemCustom *parent) {
 | 
			
		||||
    parent->add_on_prev_callback([this, parent]() { this->trigger(parent); });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace display_menu_base
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										315
									
								
								esphome/components/display_menu_base/display_menu_base.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								esphome/components/display_menu_base/display_menu_base.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,315 @@
 | 
			
		||||
#include "display_menu_base.h"
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display_menu_base {
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::up() {
 | 
			
		||||
  if (this->check_healthy_and_active_()) {
 | 
			
		||||
    bool changed = false;
 | 
			
		||||
 | 
			
		||||
    if (this->editing_) {
 | 
			
		||||
      switch (this->mode_) {
 | 
			
		||||
        case MENU_MODE_ROTARY:
 | 
			
		||||
          changed = this->get_selected_item_()->select_prev();
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      changed = this->cursor_up_();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changed)
 | 
			
		||||
      this->draw_and_update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::down() {
 | 
			
		||||
  if (this->check_healthy_and_active_()) {
 | 
			
		||||
    bool changed = false;
 | 
			
		||||
 | 
			
		||||
    if (this->editing_) {
 | 
			
		||||
      switch (this->mode_) {
 | 
			
		||||
        case MENU_MODE_ROTARY:
 | 
			
		||||
          changed = this->get_selected_item_()->select_next();
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      changed = this->cursor_down_();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changed)
 | 
			
		||||
      this->draw_and_update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::left() {
 | 
			
		||||
  if (this->check_healthy_and_active_()) {
 | 
			
		||||
    bool changed = false;
 | 
			
		||||
 | 
			
		||||
    switch (this->get_selected_item_()->get_type()) {
 | 
			
		||||
      case MENU_ITEM_SELECT:
 | 
			
		||||
      case MENU_ITEM_SWITCH:
 | 
			
		||||
      case MENU_ITEM_NUMBER:
 | 
			
		||||
      case MENU_ITEM_CUSTOM:
 | 
			
		||||
        switch (this->mode_) {
 | 
			
		||||
          case MENU_MODE_ROTARY:
 | 
			
		||||
            if (this->editing_) {
 | 
			
		||||
              this->finish_editing_();
 | 
			
		||||
              changed = true;
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
          case MENU_MODE_JOYSTICK:
 | 
			
		||||
            if (this->editing_ || this->get_selected_item_()->get_immediate_edit())
 | 
			
		||||
              changed = this->get_selected_item_()->select_prev();
 | 
			
		||||
            break;
 | 
			
		||||
          default:
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      case MENU_ITEM_BACK:
 | 
			
		||||
        changed = this->leave_menu_();
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changed)
 | 
			
		||||
      this->draw_and_update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::right() {
 | 
			
		||||
  if (this->check_healthy_and_active_()) {
 | 
			
		||||
    bool changed = false;
 | 
			
		||||
 | 
			
		||||
    switch (this->get_selected_item_()->get_type()) {
 | 
			
		||||
      case MENU_ITEM_SELECT:
 | 
			
		||||
      case MENU_ITEM_SWITCH:
 | 
			
		||||
      case MENU_ITEM_NUMBER:
 | 
			
		||||
      case MENU_ITEM_CUSTOM:
 | 
			
		||||
        switch (this->mode_) {
 | 
			
		||||
          case MENU_MODE_JOYSTICK:
 | 
			
		||||
            if (this->editing_ || this->get_selected_item_()->get_immediate_edit())
 | 
			
		||||
              changed = this->get_selected_item_()->select_next();
 | 
			
		||||
          default:
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      case MENU_ITEM_MENU:
 | 
			
		||||
        changed = this->enter_menu_();
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changed)
 | 
			
		||||
      this->draw_and_update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::enter() {
 | 
			
		||||
  if (this->check_healthy_and_active_()) {
 | 
			
		||||
    bool changed = false;
 | 
			
		||||
    MenuItem *item = this->get_selected_item_();
 | 
			
		||||
 | 
			
		||||
    if (this->editing_) {
 | 
			
		||||
      this->finish_editing_();
 | 
			
		||||
      changed = true;
 | 
			
		||||
    } else {
 | 
			
		||||
      switch (item->get_type()) {
 | 
			
		||||
        case MENU_ITEM_MENU:
 | 
			
		||||
          changed = this->enter_menu_();
 | 
			
		||||
          break;
 | 
			
		||||
        case MENU_ITEM_BACK:
 | 
			
		||||
          changed = this->leave_menu_();
 | 
			
		||||
          break;
 | 
			
		||||
        case MENU_ITEM_SELECT:
 | 
			
		||||
        case MENU_ITEM_SWITCH:
 | 
			
		||||
        case MENU_ITEM_CUSTOM:
 | 
			
		||||
          if (item->get_immediate_edit()) {
 | 
			
		||||
            changed = item->select_next();
 | 
			
		||||
          } else {
 | 
			
		||||
            this->editing_ = true;
 | 
			
		||||
            item->on_enter();
 | 
			
		||||
            changed = true;
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        case MENU_ITEM_NUMBER:
 | 
			
		||||
          // A number cannot be immediate in the rotary mode
 | 
			
		||||
          if (!item->get_immediate_edit() || this->mode_ == MENU_MODE_ROTARY) {
 | 
			
		||||
            this->editing_ = true;
 | 
			
		||||
            item->on_enter();
 | 
			
		||||
            changed = true;
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
        case MENU_ITEM_COMMAND:
 | 
			
		||||
          changed = item->select_next();
 | 
			
		||||
          break;
 | 
			
		||||
        default:
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changed)
 | 
			
		||||
      this->draw_and_update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::draw() {
 | 
			
		||||
  if (this->check_healthy_and_active_())
 | 
			
		||||
    this->draw_menu();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::show_main() {
 | 
			
		||||
  bool disp_changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->is_failed())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  this->process_initial_();
 | 
			
		||||
 | 
			
		||||
  if (this->active_ && this->editing_)
 | 
			
		||||
    this->finish_editing_();
 | 
			
		||||
 | 
			
		||||
  if (this->displayed_item_ != this->root_item_) {
 | 
			
		||||
    this->displayed_item_->on_leave();
 | 
			
		||||
    disp_changed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->reset_();
 | 
			
		||||
  this->active_ = true;
 | 
			
		||||
 | 
			
		||||
  if (disp_changed) {
 | 
			
		||||
    this->displayed_item_->on_enter();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->draw_and_update();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::show() {
 | 
			
		||||
  if (this->is_failed())
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  this->process_initial_();
 | 
			
		||||
 | 
			
		||||
  if (!this->active_) {
 | 
			
		||||
    this->active_ = true;
 | 
			
		||||
    this->draw_and_update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::hide() {
 | 
			
		||||
  if (this->check_healthy_and_active_()) {
 | 
			
		||||
    if (this->editing_)
 | 
			
		||||
      this->finish_editing_();
 | 
			
		||||
    this->active_ = false;
 | 
			
		||||
    this->update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::reset_() {
 | 
			
		||||
  this->displayed_item_ = this->root_item_;
 | 
			
		||||
  this->cursor_index_ = this->top_index_ = 0;
 | 
			
		||||
  this->selection_stack_.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::process_initial_() {
 | 
			
		||||
  if (!this->root_on_enter_called_) {
 | 
			
		||||
    this->root_item_->on_enter();
 | 
			
		||||
    this->root_on_enter_called_ = true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DisplayMenuComponent::check_healthy_and_active_() {
 | 
			
		||||
  if (this->is_failed())
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  this->process_initial_();
 | 
			
		||||
 | 
			
		||||
  return this->active_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DisplayMenuComponent::cursor_up_() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->cursor_index_ > 0) {
 | 
			
		||||
    changed = true;
 | 
			
		||||
 | 
			
		||||
    --this->cursor_index_;
 | 
			
		||||
 | 
			
		||||
    if (this->cursor_index_ < this->top_index_)
 | 
			
		||||
      this->top_index_ = this->cursor_index_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DisplayMenuComponent::cursor_down_() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->cursor_index_ + 1 < this->displayed_item_->items_size()) {
 | 
			
		||||
    changed = true;
 | 
			
		||||
 | 
			
		||||
    ++this->cursor_index_;
 | 
			
		||||
 | 
			
		||||
    if (this->cursor_index_ >= this->top_index_ + this->rows_)
 | 
			
		||||
      this->top_index_ = this->cursor_index_ - this->rows_ + 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DisplayMenuComponent::enter_menu_() {
 | 
			
		||||
  this->displayed_item_->on_leave();
 | 
			
		||||
  this->displayed_item_ = static_cast<MenuItemMenu *>(this->get_selected_item_());
 | 
			
		||||
  this->selection_stack_.push_front({this->top_index_, this->cursor_index_});
 | 
			
		||||
  this->cursor_index_ = this->top_index_ = 0;
 | 
			
		||||
  this->displayed_item_->on_enter();
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DisplayMenuComponent::leave_menu_() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->displayed_item_->get_parent() != nullptr) {
 | 
			
		||||
    this->displayed_item_->on_leave();
 | 
			
		||||
    this->displayed_item_ = this->displayed_item_->get_parent();
 | 
			
		||||
    this->top_index_ = this->selection_stack_.front().first;
 | 
			
		||||
    this->cursor_index_ = this->selection_stack_.front().second;
 | 
			
		||||
    this->selection_stack_.pop_front();
 | 
			
		||||
    this->displayed_item_->on_enter();
 | 
			
		||||
    changed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::finish_editing_() {
 | 
			
		||||
  switch (this->get_selected_item_()->get_type()) {
 | 
			
		||||
    case MENU_ITEM_SELECT:
 | 
			
		||||
    case MENU_ITEM_NUMBER:
 | 
			
		||||
    case MENU_ITEM_SWITCH:
 | 
			
		||||
    case MENU_ITEM_CUSTOM:
 | 
			
		||||
      this->get_selected_item_()->on_leave();
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->editing_ = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisplayMenuComponent::draw_menu() {
 | 
			
		||||
  for (size_t i = 0; i < this->rows_ && this->top_index_ + i < this->displayed_item_->items_size(); ++i) {
 | 
			
		||||
    this->draw_item(this->displayed_item_->get_item(this->top_index_ + i), i,
 | 
			
		||||
                    this->top_index_ + i == this->cursor_index_);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace display_menu_base
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										77
									
								
								esphome/components/display_menu_base/display_menu_base.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								esphome/components/display_menu_base/display_menu_base.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
#include "menu_item.h"
 | 
			
		||||
 | 
			
		||||
#include <forward_list>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display_menu_base {
 | 
			
		||||
 | 
			
		||||
enum MenuMode {
 | 
			
		||||
  MENU_MODE_ROTARY,
 | 
			
		||||
  MENU_MODE_JOYSTICK,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MenuItem;
 | 
			
		||||
 | 
			
		||||
/** Class to display a hierarchical menu.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class DisplayMenuComponent : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_root_item(MenuItemMenu *item) { this->displayed_item_ = this->root_item_ = item; }
 | 
			
		||||
  void set_active(bool active) { this->active_ = active; }
 | 
			
		||||
  void set_mode(MenuMode mode) { this->mode_ = mode; }
 | 
			
		||||
  void set_rows(uint8_t rows) { this->rows_ = rows; }
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::PROCESSOR; }
 | 
			
		||||
 | 
			
		||||
  void up();
 | 
			
		||||
  void down();
 | 
			
		||||
  void left();
 | 
			
		||||
  void right();
 | 
			
		||||
  void enter();
 | 
			
		||||
 | 
			
		||||
  void show_main();
 | 
			
		||||
  void show();
 | 
			
		||||
  void hide();
 | 
			
		||||
 | 
			
		||||
  void draw();
 | 
			
		||||
 | 
			
		||||
  bool is_active() const { return this->active_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void reset_();
 | 
			
		||||
  void process_initial_();
 | 
			
		||||
  bool check_healthy_and_active_();
 | 
			
		||||
  MenuItem *get_selected_item_() { return this->displayed_item_->get_item(this->cursor_index_); }
 | 
			
		||||
  bool cursor_up_();
 | 
			
		||||
  bool cursor_down_();
 | 
			
		||||
  bool enter_menu_();
 | 
			
		||||
  bool leave_menu_();
 | 
			
		||||
  void finish_editing_();
 | 
			
		||||
  virtual void draw_menu();
 | 
			
		||||
  virtual void draw_item(const MenuItem *item, uint8_t row, bool selected) = 0;
 | 
			
		||||
  virtual void update() {}
 | 
			
		||||
  virtual void draw_and_update() {
 | 
			
		||||
    draw_menu();
 | 
			
		||||
    update();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t rows_;
 | 
			
		||||
  bool active_;
 | 
			
		||||
  MenuMode mode_;
 | 
			
		||||
  MenuItemMenu *root_item_{nullptr};
 | 
			
		||||
 | 
			
		||||
  MenuItemMenu *displayed_item_{nullptr};
 | 
			
		||||
  uint8_t top_index_{0};
 | 
			
		||||
  uint8_t cursor_index_{0};
 | 
			
		||||
  std::forward_list<std::pair<uint8_t, uint8_t>> selection_stack_{};
 | 
			
		||||
  bool editing_{false};
 | 
			
		||||
  bool root_on_enter_called_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace display_menu_base
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										179
									
								
								esphome/components/display_menu_base/menu_item.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								esphome/components/display_menu_base/menu_item.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,179 @@
 | 
			
		||||
#include "menu_item.h"
 | 
			
		||||
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display_menu_base {
 | 
			
		||||
 | 
			
		||||
void MenuItem::on_enter() { this->on_enter_callbacks_.call(); }
 | 
			
		||||
 | 
			
		||||
void MenuItem::on_leave() { this->on_leave_callbacks_.call(); }
 | 
			
		||||
 | 
			
		||||
void MenuItem::on_value_() { this->on_value_callbacks_.call(); }
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
std::string MenuItemSelect::get_value_text() const {
 | 
			
		||||
  std::string result;
 | 
			
		||||
 | 
			
		||||
  if (this->value_getter_.has_value()) {
 | 
			
		||||
    result = this->value_getter_.value()(this);
 | 
			
		||||
  } else {
 | 
			
		||||
    if (this->select_var_ != nullptr) {
 | 
			
		||||
      result = this->select_var_->state;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemSelect::select_next() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->select_var_ != nullptr) {
 | 
			
		||||
    this->select_var_->make_call().select_next(true).perform();
 | 
			
		||||
    changed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemSelect::select_prev() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->select_var_ != nullptr) {
 | 
			
		||||
    this->select_var_->make_call().select_previous(true).perform();
 | 
			
		||||
    changed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_SELECT
 | 
			
		||||
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
std::string MenuItemNumber::get_value_text() const {
 | 
			
		||||
  std::string result;
 | 
			
		||||
 | 
			
		||||
  if (this->value_getter_.has_value()) {
 | 
			
		||||
    result = this->value_getter_.value()(this);
 | 
			
		||||
  } else {
 | 
			
		||||
    char data[32];
 | 
			
		||||
    snprintf(data, sizeof(data), this->format_.c_str(), get_number_value_());
 | 
			
		||||
    result = data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemNumber::select_next() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->number_var_ != nullptr) {
 | 
			
		||||
    float last = this->number_var_->state;
 | 
			
		||||
    this->number_var_->make_call().number_increment(false).perform();
 | 
			
		||||
 | 
			
		||||
    if (this->number_var_->state != last) {
 | 
			
		||||
      this->on_value_();
 | 
			
		||||
      changed = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemNumber::select_prev() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->number_var_ != nullptr) {
 | 
			
		||||
    float last = this->number_var_->state;
 | 
			
		||||
    this->number_var_->make_call().number_decrement(false).perform();
 | 
			
		||||
 | 
			
		||||
    if (this->number_var_->state != last) {
 | 
			
		||||
      this->on_value_();
 | 
			
		||||
      changed = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float MenuItemNumber::get_number_value_() const {
 | 
			
		||||
  float result = 0.0;
 | 
			
		||||
 | 
			
		||||
  if (this->number_var_ != nullptr) {
 | 
			
		||||
    if (!this->number_var_->has_state() || this->number_var_->state < this->number_var_->traits.get_min_value()) {
 | 
			
		||||
      result = this->number_var_->traits.get_min_value();
 | 
			
		||||
    } else if (this->number_var_->state > this->number_var_->traits.get_max_value()) {
 | 
			
		||||
      result = this->number_var_->traits.get_max_value();
 | 
			
		||||
    } else {
 | 
			
		||||
      result = this->number_var_->state;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_NUMBER
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
std::string MenuItemSwitch::get_value_text() const {
 | 
			
		||||
  std::string result;
 | 
			
		||||
 | 
			
		||||
  if (this->value_getter_.has_value()) {
 | 
			
		||||
    result = this->value_getter_.value()(this);
 | 
			
		||||
  } else {
 | 
			
		||||
    result = this->get_switch_state_() ? this->switch_on_text_ : this->switch_off_text_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemSwitch::select_next() { return this->toggle_switch_(); }
 | 
			
		||||
 | 
			
		||||
bool MenuItemSwitch::select_prev() { return this->toggle_switch_(); }
 | 
			
		||||
 | 
			
		||||
bool MenuItemSwitch::get_switch_state_() const { return (this->switch_var_ != nullptr && this->switch_var_->state); }
 | 
			
		||||
 | 
			
		||||
bool MenuItemSwitch::toggle_switch_() {
 | 
			
		||||
  bool changed = false;
 | 
			
		||||
 | 
			
		||||
  if (this->switch_var_ != nullptr) {
 | 
			
		||||
    this->switch_var_->toggle();
 | 
			
		||||
    this->on_value_();
 | 
			
		||||
    changed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changed;
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_SWITCH
 | 
			
		||||
 | 
			
		||||
std::string MenuItemCustom::get_value_text() const {
 | 
			
		||||
  return (this->value_getter_.has_value()) ? this->value_getter_.value()(this) : "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemCommand::select_next() {
 | 
			
		||||
  this->on_value_();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemCommand::select_prev() {
 | 
			
		||||
  this->on_value_();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemCustom::select_next() {
 | 
			
		||||
  this->on_next_();
 | 
			
		||||
  this->on_value_();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MenuItemCustom::select_prev() {
 | 
			
		||||
  this->on_prev_();
 | 
			
		||||
  this->on_value_();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MenuItemCustom::on_next_() { this->on_next_callbacks_.call(); }
 | 
			
		||||
 | 
			
		||||
void MenuItemCustom::on_prev_() { this->on_prev_callbacks_.call(); }
 | 
			
		||||
 | 
			
		||||
}  // namespace display_menu_base
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										187
									
								
								esphome/components/display_menu_base/menu_item.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								esphome/components/display_menu_base/menu_item.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,187 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
#include "esphome/components/number/number.h"
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
#include "esphome/components/select/select.h"
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
#include "esphome/components/switch/switch.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace display_menu_base {
 | 
			
		||||
 | 
			
		||||
enum MenuItemType {
 | 
			
		||||
  MENU_ITEM_LABEL,
 | 
			
		||||
  MENU_ITEM_MENU,
 | 
			
		||||
  MENU_ITEM_BACK,
 | 
			
		||||
  MENU_ITEM_SELECT,
 | 
			
		||||
  MENU_ITEM_NUMBER,
 | 
			
		||||
  MENU_ITEM_SWITCH,
 | 
			
		||||
  MENU_ITEM_COMMAND,
 | 
			
		||||
  MENU_ITEM_CUSTOM,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MenuItem;
 | 
			
		||||
class MenuItemMenu;
 | 
			
		||||
using value_getter_t = std::function<std::string(const MenuItem *)>;
 | 
			
		||||
 | 
			
		||||
class MenuItem {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItem(MenuItemType t) : item_type_(t) {}
 | 
			
		||||
  void set_parent(MenuItemMenu *parent) { this->parent_ = parent; }
 | 
			
		||||
  MenuItemMenu *get_parent() { return this->parent_; }
 | 
			
		||||
  MenuItemType get_type() const { return this->item_type_; }
 | 
			
		||||
  template<typename V> void set_text(V val) { this->text_ = val; }
 | 
			
		||||
  void add_on_enter_callback(std::function<void()> &&cb) { this->on_enter_callbacks_.add(std::move(cb)); }
 | 
			
		||||
  void add_on_leave_callback(std::function<void()> &&cb) { this->on_leave_callbacks_.add(std::move(cb)); }
 | 
			
		||||
  void add_on_value_callback(std::function<void()> &&cb) { this->on_value_callbacks_.add(std::move(cb)); }
 | 
			
		||||
 | 
			
		||||
  std::string get_text() const { return const_cast<MenuItem *>(this)->text_.value(this); }
 | 
			
		||||
  virtual bool get_immediate_edit() const { return false; }
 | 
			
		||||
  virtual bool has_value() const { return false; }
 | 
			
		||||
  virtual std::string get_value_text() const { return ""; }
 | 
			
		||||
 | 
			
		||||
  virtual bool select_next() { return false; }
 | 
			
		||||
  virtual bool select_prev() { return false; }
 | 
			
		||||
 | 
			
		||||
  void on_enter();
 | 
			
		||||
  void on_leave();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void on_value_();
 | 
			
		||||
 | 
			
		||||
  MenuItemType item_type_;
 | 
			
		||||
  MenuItemMenu *parent_{nullptr};
 | 
			
		||||
  TemplatableValue<std::string, const MenuItem *> text_;
 | 
			
		||||
 | 
			
		||||
  CallbackManager<void()> on_enter_callbacks_{};
 | 
			
		||||
  CallbackManager<void()> on_leave_callbacks_{};
 | 
			
		||||
  CallbackManager<void()> on_value_callbacks_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MenuItemMenu : public MenuItem {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItemMenu() : MenuItem(MENU_ITEM_MENU) {}
 | 
			
		||||
  void add_item(MenuItem *item) {
 | 
			
		||||
    item->set_parent(this);
 | 
			
		||||
    this->items_.push_back(item);
 | 
			
		||||
  }
 | 
			
		||||
  size_t items_size() const { return this->items_.size(); }
 | 
			
		||||
  MenuItem *get_item(size_t i) { return this->items_[i]; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::vector<MenuItem *> items_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MenuItemEditable : public MenuItem {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItemEditable(MenuItemType t) : MenuItem(t) {}
 | 
			
		||||
  void set_immediate_edit(bool val) { this->immediate_edit_ = val; }
 | 
			
		||||
  bool get_immediate_edit() const override { return this->immediate_edit_; }
 | 
			
		||||
  void set_value_lambda(value_getter_t &&getter) { this->value_getter_ = getter; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool immediate_edit_{false};
 | 
			
		||||
  optional<value_getter_t> value_getter_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SELECT
 | 
			
		||||
class MenuItemSelect : public MenuItemEditable {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItemSelect() : MenuItemEditable(MENU_ITEM_SELECT) {}
 | 
			
		||||
  void set_select_variable(select::Select *var) { this->select_var_ = var; }
 | 
			
		||||
 | 
			
		||||
  bool has_value() const override { return true; }
 | 
			
		||||
  std::string get_value_text() const override;
 | 
			
		||||
 | 
			
		||||
  bool select_next() override;
 | 
			
		||||
  bool select_prev() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  select::Select *select_var_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
class MenuItemNumber : public MenuItemEditable {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItemNumber() : MenuItemEditable(MENU_ITEM_NUMBER) {}
 | 
			
		||||
  void set_number_variable(number::Number *var) { this->number_var_ = var; }
 | 
			
		||||
  void set_format(const std::string &fmt) { this->format_ = fmt; }
 | 
			
		||||
 | 
			
		||||
  bool has_value() const override { return true; }
 | 
			
		||||
  std::string get_value_text() const override;
 | 
			
		||||
 | 
			
		||||
  bool select_next() override;
 | 
			
		||||
  bool select_prev() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  float get_number_value_() const;
 | 
			
		||||
 | 
			
		||||
  number::Number *number_var_{nullptr};
 | 
			
		||||
  std::string format_;
 | 
			
		||||
};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SWITCH
 | 
			
		||||
class MenuItemSwitch : public MenuItemEditable {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItemSwitch() : MenuItemEditable(MENU_ITEM_SWITCH) {}
 | 
			
		||||
  void set_switch_variable(switch_::Switch *var) { this->switch_var_ = var; }
 | 
			
		||||
  void set_on_text(const std::string &t) { this->switch_on_text_ = t; }
 | 
			
		||||
  void set_off_text(const std::string &t) { this->switch_off_text_ = t; }
 | 
			
		||||
 | 
			
		||||
  bool has_value() const override { return true; }
 | 
			
		||||
  std::string get_value_text() const override;
 | 
			
		||||
 | 
			
		||||
  bool select_next() override;
 | 
			
		||||
  bool select_prev() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool get_switch_state_() const;
 | 
			
		||||
  bool toggle_switch_();
 | 
			
		||||
 | 
			
		||||
  switch_::Switch *switch_var_{nullptr};
 | 
			
		||||
  std::string switch_on_text_;
 | 
			
		||||
  std::string switch_off_text_;
 | 
			
		||||
};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
class MenuItemCommand : public MenuItem {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItemCommand() : MenuItem(MENU_ITEM_COMMAND) {}
 | 
			
		||||
 | 
			
		||||
  bool select_next() override;
 | 
			
		||||
  bool select_prev() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MenuItemCustom : public MenuItemEditable {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit MenuItemCustom() : MenuItemEditable(MENU_ITEM_CUSTOM) {}
 | 
			
		||||
  void add_on_next_callback(std::function<void()> &&cb) { this->on_next_callbacks_.add(std::move(cb)); }
 | 
			
		||||
  void add_on_prev_callback(std::function<void()> &&cb) { this->on_prev_callbacks_.add(std::move(cb)); }
 | 
			
		||||
 | 
			
		||||
  bool has_value() const override { return this->value_getter_.has_value(); }
 | 
			
		||||
  std::string get_value_text() const override;
 | 
			
		||||
 | 
			
		||||
  bool select_next() override;
 | 
			
		||||
  bool select_prev() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void on_next_();
 | 
			
		||||
  void on_prev_();
 | 
			
		||||
 | 
			
		||||
  CallbackManager<void()> on_next_callbacks_{};
 | 
			
		||||
  CallbackManager<void()> on_prev_callbacks_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace display_menu_base
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -13,6 +13,8 @@
 | 
			
		||||
#include <dsmr/parser.h>
 | 
			
		||||
#include <dsmr/fields.h>
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace dsmr {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,8 @@
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ektf2232 {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import i2c, touchscreen
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
@@ -17,10 +17,8 @@ EKTF2232Touchscreen = ektf2232_ns.class_(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_EKTF2232_ID = "ektf2232_id"
 | 
			
		||||
CONF_INTERRUPT_PIN = "interrupt_pin"
 | 
			
		||||
CONF_RTS_PIN = "rts_pin"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ from .const import (  # noqa
 | 
			
		||||
    VARIANT_FRIENDLY,
 | 
			
		||||
    VARIANTS,
 | 
			
		||||
)
 | 
			
		||||
from .boards import BOARD_TO_VARIANT
 | 
			
		||||
from .boards import BOARDS
 | 
			
		||||
 | 
			
		||||
# force import gpio to register pin schema
 | 
			
		||||
from .gpio import esp32_pin_to_code  # noqa
 | 
			
		||||
@@ -129,27 +129,27 @@ def _format_framework_espidf_version(ver: cv.Version) -> str:
 | 
			
		||||
# The default/recommended arduino framework version
 | 
			
		||||
#  - https://github.com/espressif/arduino-esp32/releases
 | 
			
		||||
#  - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif32
 | 
			
		||||
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(1, 0, 6)
 | 
			
		||||
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 0, 5)
 | 
			
		||||
# The platformio/espressif32 version to use for arduino frameworks
 | 
			
		||||
#  - https://github.com/platformio/platform-espressif32/releases
 | 
			
		||||
#  - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
 | 
			
		||||
ARDUINO_PLATFORM_VERSION = cv.Version(3, 5, 0)
 | 
			
		||||
ARDUINO_PLATFORM_VERSION = cv.Version(5, 2, 0)
 | 
			
		||||
 | 
			
		||||
# The default/recommended esp-idf framework version
 | 
			
		||||
#  - https://github.com/espressif/esp-idf/releases
 | 
			
		||||
#  - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
 | 
			
		||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 3, 2)
 | 
			
		||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 2)
 | 
			
		||||
# The platformio/espressif32 version to use for esp-idf frameworks
 | 
			
		||||
#  - https://github.com/platformio/platform-espressif32/releases
 | 
			
		||||
#  - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
 | 
			
		||||
ESP_IDF_PLATFORM_VERSION = cv.Version(3, 5, 0)
 | 
			
		||||
ESP_IDF_PLATFORM_VERSION = cv.Version(5, 2, 0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _arduino_check_versions(value):
 | 
			
		||||
    value = value.copy()
 | 
			
		||||
    lookups = {
 | 
			
		||||
        "dev": (cv.Version(2, 0, 0), "https://github.com/espressif/arduino-esp32.git"),
 | 
			
		||||
        "latest": (cv.Version(1, 0, 6), None),
 | 
			
		||||
        "dev": (cv.Version(2, 0, 5), "https://github.com/espressif/arduino-esp32.git"),
 | 
			
		||||
        "latest": (cv.Version(2, 0, 5), None),
 | 
			
		||||
        "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -184,7 +184,7 @@ def _esp_idf_check_versions(value):
 | 
			
		||||
    value = value.copy()
 | 
			
		||||
    lookups = {
 | 
			
		||||
        "dev": (cv.Version(5, 0, 0), "https://github.com/espressif/esp-idf.git"),
 | 
			
		||||
        "latest": (cv.Version(4, 3, 2), None),
 | 
			
		||||
        "latest": (cv.Version(4, 4, 2), None),
 | 
			
		||||
        "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -230,14 +230,14 @@ def _parse_platform_version(value):
 | 
			
		||||
def _detect_variant(value):
 | 
			
		||||
    if CONF_VARIANT not in value:
 | 
			
		||||
        board = value[CONF_BOARD]
 | 
			
		||||
        if board not in BOARD_TO_VARIANT:
 | 
			
		||||
        if board not in BOARDS:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                "This board is unknown, please set the variant manually",
 | 
			
		||||
                path=[CONF_BOARD],
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        value = value.copy()
 | 
			
		||||
        value[CONF_VARIANT] = BOARD_TO_VARIANT[board]
 | 
			
		||||
        value[CONF_VARIANT] = BOARDS[board][KEY_VARIANT]
 | 
			
		||||
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
@@ -327,6 +327,11 @@ async def to_code(config):
 | 
			
		||||
            "platform_packages",
 | 
			
		||||
            [f"platformio/framework-espidf @ {conf[CONF_SOURCE]}"],
 | 
			
		||||
        )
 | 
			
		||||
        # platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years
 | 
			
		||||
        # This is espressif's own published version which is more up to date.
 | 
			
		||||
        cg.add_platformio_option(
 | 
			
		||||
            "platform_packages", ["espressif/toolchain-esp32ulp @ 2.35.0-20220830"]
 | 
			
		||||
        )
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
 | 
			
		||||
        add_idf_sdkconfig_option(
 | 
			
		||||
@@ -393,11 +398,11 @@ spiffs,   data, spiffs,  0x391000, 0x00F000
 | 
			
		||||
 | 
			
		||||
IDF_PARTITIONS_CSV = """\
 | 
			
		||||
# Name,   Type, SubType, Offset,   Size, Flags
 | 
			
		||||
nvs,      data, nvs,     ,        0x4000,
 | 
			
		||||
otadata,  data, ota,     ,        0x2000,
 | 
			
		||||
phy_init, data, phy,     ,        0x1000,
 | 
			
		||||
app0,     app,  ota_0,   ,      0x1C0000,
 | 
			
		||||
app1,     app,  ota_1,   ,      0x1C0000,
 | 
			
		||||
nvs,      data, nvs,     ,       0x6d000,
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
from .const import VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32C3
 | 
			
		||||
from .const import VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32C3, VARIANT_ESP32S3
 | 
			
		||||
 | 
			
		||||
ESP32_BASE_PINS = {
 | 
			
		||||
    "TX": 1,
 | 
			
		||||
@@ -42,6 +42,64 @@ ESP32_BASE_PINS = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ESP32_BOARD_PINS = {
 | 
			
		||||
    "adafruit_qtpy_esp32c3": {
 | 
			
		||||
        "A0": 4,
 | 
			
		||||
        "A1": 3,
 | 
			
		||||
        "A2": 1,
 | 
			
		||||
        "A3": 0,
 | 
			
		||||
        "SDA": 5,
 | 
			
		||||
        "SCL": 6,
 | 
			
		||||
        "MOSI": 7,
 | 
			
		||||
        "MISO": 8,
 | 
			
		||||
        "SCK": 10,
 | 
			
		||||
        "RX": 20,
 | 
			
		||||
        "TX": 21,
 | 
			
		||||
        "NEOPIXEL": 2,
 | 
			
		||||
        "PIN_NEOPIXEL": 2,
 | 
			
		||||
        "BUTTON": 9,
 | 
			
		||||
        "SWITCH": 9,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_qtpy_esp32s2": {
 | 
			
		||||
        "A0": 18,
 | 
			
		||||
        "A1": 17,
 | 
			
		||||
        "A2": 9,
 | 
			
		||||
        "A3": 8,
 | 
			
		||||
        "SDA": 7,
 | 
			
		||||
        "SCL": 6,
 | 
			
		||||
        "MOSI": 35,
 | 
			
		||||
        "MISO": 37,
 | 
			
		||||
        "SCK": 36,
 | 
			
		||||
        "RX": 16,
 | 
			
		||||
        "TX": 5,
 | 
			
		||||
        "SDA1": 41,
 | 
			
		||||
        "SCL1": 40,
 | 
			
		||||
        "NEOPIXEL": 39,
 | 
			
		||||
        "PIN_NEOPIXEL": 39,
 | 
			
		||||
        "NEOPIXEL_POWER": 38,
 | 
			
		||||
        "D0": 0,
 | 
			
		||||
        "BUTTON": 0,
 | 
			
		||||
        "SWITCH": 0,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_qtpy_esp32": {
 | 
			
		||||
        "A0": 26,
 | 
			
		||||
        "A1": 25,
 | 
			
		||||
        "A2": 27,
 | 
			
		||||
        "A3": 15,
 | 
			
		||||
        "SDA": 4,
 | 
			
		||||
        "SCL": 33,
 | 
			
		||||
        "MOSI": 13,
 | 
			
		||||
        "MISO": 12,
 | 
			
		||||
        "SCK": 14,
 | 
			
		||||
        "RX": 7,
 | 
			
		||||
        "TX": 32,
 | 
			
		||||
        "SDA1": 22,
 | 
			
		||||
        "SCL1": 19,
 | 
			
		||||
        "NEOPIXEL": 5,
 | 
			
		||||
        "PIN_NEOPIXEL": 5,
 | 
			
		||||
        "NEOPIXEL_POWER": 8,
 | 
			
		||||
        "BUTTON": 0,
 | 
			
		||||
        "SWITCH": 0,
 | 
			
		||||
    },
 | 
			
		||||
    "alksesp32": {
 | 
			
		||||
        "A0": 32,
 | 
			
		||||
        "A1": 33,
 | 
			
		||||
@@ -550,6 +608,25 @@ ESP32_BOARD_PINS = {
 | 
			
		||||
    },
 | 
			
		||||
    "lolin_d32": {"LED": 5, "_VBAT": 35},
 | 
			
		||||
    "lolin_d32_pro": {"LED": 5, "_VBAT": 35},
 | 
			
		||||
    "lolin_s2_mini": {
 | 
			
		||||
        "TX": 43,
 | 
			
		||||
        "RX": 44,
 | 
			
		||||
        "SPICS1": 29,
 | 
			
		||||
        "SPIHD": 31,
 | 
			
		||||
        "SPIWP": 32,
 | 
			
		||||
        "SPICS0": 33,
 | 
			
		||||
        "SPICLK": 34,
 | 
			
		||||
        "SPIQ": 35,
 | 
			
		||||
        "SPID": 36,
 | 
			
		||||
        "MISO": 9,
 | 
			
		||||
        "MOSI": 11,
 | 
			
		||||
        "SCK": 7,
 | 
			
		||||
        "SCL": 35,
 | 
			
		||||
        "SDA": 33,
 | 
			
		||||
        "DAC1": 17,
 | 
			
		||||
        "DAC2": 18,
 | 
			
		||||
        "LED": 15,
 | 
			
		||||
    },
 | 
			
		||||
    "lopy": {
 | 
			
		||||
        "A1": 37,
 | 
			
		||||
        "A2": 38,
 | 
			
		||||
@@ -984,127 +1061,743 @@ ESP32_BOARD_PINS = {
 | 
			
		||||
        "D13": 2,
 | 
			
		||||
    },
 | 
			
		||||
    "xinabox_cw02": {"LED": 27},
 | 
			
		||||
    "upesy_wroom": {"LED": 2},
 | 
			
		||||
    "upesy_wrover": {"LED": 2},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
BOARD_TO_VARIANT generated with:
 | 
			
		||||
BOARDS generated with:
 | 
			
		||||
 | 
			
		||||
git clone https://github.com/platformio/platform-espressif32
 | 
			
		||||
for x in platform-espressif32/boards/*.json; do
 | 
			
		||||
  mcu=$(jq -r .build.mcu <"$x");
 | 
			
		||||
  name=$(jq -r .name <"$x");
 | 
			
		||||
  fname=$(basename "$x")
 | 
			
		||||
  board="${fname%.*}"
 | 
			
		||||
  variant=$(echo "$mcu" | tr '[:lower:]' '[:upper:]')
 | 
			
		||||
  echo "    \"$board\": VARIANT_${variant},"
 | 
			
		||||
  echo "    \"$board\": {\"name\": \"$name\", \"variant\": VARIANT_${variant},},"
 | 
			
		||||
done | sort
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
BOARD_TO_VARIANT = {
 | 
			
		||||
    "alksesp32": VARIANT_ESP32,
 | 
			
		||||
    "az-delivery-devkit-v4": VARIANT_ESP32,
 | 
			
		||||
    "bpi-bit": VARIANT_ESP32,
 | 
			
		||||
    "briki_abc_esp32": VARIANT_ESP32,
 | 
			
		||||
    "briki_mbc-wb_esp32": VARIANT_ESP32,
 | 
			
		||||
    "d-duino-32": VARIANT_ESP32,
 | 
			
		||||
    "esp320": VARIANT_ESP32,
 | 
			
		||||
    "esp32-c3-devkitm-1": VARIANT_ESP32C3,
 | 
			
		||||
    "esp32cam": VARIANT_ESP32,
 | 
			
		||||
    "esp32-devkitlipo": VARIANT_ESP32,
 | 
			
		||||
    "esp32dev": VARIANT_ESP32,
 | 
			
		||||
    "esp32doit-devkit-v1": VARIANT_ESP32,
 | 
			
		||||
    "esp32doit-espduino": VARIANT_ESP32,
 | 
			
		||||
    "esp32-evb": VARIANT_ESP32,
 | 
			
		||||
    "esp32-gateway": VARIANT_ESP32,
 | 
			
		||||
    "esp32-poe-iso": VARIANT_ESP32,
 | 
			
		||||
    "esp32-poe": VARIANT_ESP32,
 | 
			
		||||
    "esp32-pro": VARIANT_ESP32,
 | 
			
		||||
    "esp32-s2-kaluga-1": VARIANT_ESP32S2,
 | 
			
		||||
    "esp32-s2-saola-1": VARIANT_ESP32S2,
 | 
			
		||||
    "esp32thing_plus": VARIANT_ESP32,
 | 
			
		||||
    "esp32thing": VARIANT_ESP32,
 | 
			
		||||
    "esp32vn-iot-uno": VARIANT_ESP32,
 | 
			
		||||
    "espea32": VARIANT_ESP32,
 | 
			
		||||
    "espectro32": VARIANT_ESP32,
 | 
			
		||||
    "espino32": VARIANT_ESP32,
 | 
			
		||||
    "esp-wrover-kit": VARIANT_ESP32,
 | 
			
		||||
    "etboard": VARIANT_ESP32,
 | 
			
		||||
    "featheresp32-s2": VARIANT_ESP32S2,
 | 
			
		||||
    "featheresp32": VARIANT_ESP32,
 | 
			
		||||
    "firebeetle32": VARIANT_ESP32,
 | 
			
		||||
    "fm-devkit": VARIANT_ESP32,
 | 
			
		||||
    "frogboard": VARIANT_ESP32,
 | 
			
		||||
    "healthypi4": VARIANT_ESP32,
 | 
			
		||||
    "heltec_wifi_kit_32_v2": VARIANT_ESP32,
 | 
			
		||||
    "heltec_wifi_kit_32": VARIANT_ESP32,
 | 
			
		||||
    "heltec_wifi_lora_32_V2": VARIANT_ESP32,
 | 
			
		||||
    "heltec_wifi_lora_32": VARIANT_ESP32,
 | 
			
		||||
    "heltec_wireless_stick_lite": VARIANT_ESP32,
 | 
			
		||||
    "heltec_wireless_stick": VARIANT_ESP32,
 | 
			
		||||
    "honeylemon": VARIANT_ESP32,
 | 
			
		||||
    "hornbill32dev": VARIANT_ESP32,
 | 
			
		||||
    "hornbill32minima": VARIANT_ESP32,
 | 
			
		||||
    "imbrios-logsens-v1p1": VARIANT_ESP32,
 | 
			
		||||
    "inex_openkb": VARIANT_ESP32,
 | 
			
		||||
    "intorobot": VARIANT_ESP32,
 | 
			
		||||
    "iotaap_magnolia": VARIANT_ESP32,
 | 
			
		||||
    "iotbusio": VARIANT_ESP32,
 | 
			
		||||
    "iotbusproteus": VARIANT_ESP32,
 | 
			
		||||
    "kits-edu": VARIANT_ESP32,
 | 
			
		||||
    "labplus_mpython": VARIANT_ESP32,
 | 
			
		||||
    "lolin32_lite": VARIANT_ESP32,
 | 
			
		||||
    "lolin32": VARIANT_ESP32,
 | 
			
		||||
    "lolin_c3_mini": VARIANT_ESP32C3,
 | 
			
		||||
    "lolin_d32_pro": VARIANT_ESP32,
 | 
			
		||||
    "lolin_d32": VARIANT_ESP32,
 | 
			
		||||
    "lopy4": VARIANT_ESP32,
 | 
			
		||||
    "lopy": VARIANT_ESP32,
 | 
			
		||||
    "m5stack-atom": VARIANT_ESP32,
 | 
			
		||||
    "m5stack-core2": VARIANT_ESP32,
 | 
			
		||||
    "m5stack-core-esp32": VARIANT_ESP32,
 | 
			
		||||
    "m5stack-coreink": VARIANT_ESP32,
 | 
			
		||||
    "m5stack-fire": VARIANT_ESP32,
 | 
			
		||||
    "m5stack-grey": VARIANT_ESP32,
 | 
			
		||||
    "m5stack-timer-cam": VARIANT_ESP32,
 | 
			
		||||
    "m5stick-c": VARIANT_ESP32,
 | 
			
		||||
    "magicbit": VARIANT_ESP32,
 | 
			
		||||
    "mgbot-iotik32a": VARIANT_ESP32,
 | 
			
		||||
    "mgbot-iotik32b": VARIANT_ESP32,
 | 
			
		||||
    "mhetesp32devkit": VARIANT_ESP32,
 | 
			
		||||
    "mhetesp32minikit": VARIANT_ESP32,
 | 
			
		||||
    "microduino-core-esp32": VARIANT_ESP32,
 | 
			
		||||
    "nano32": VARIANT_ESP32,
 | 
			
		||||
    "nina_w10": VARIANT_ESP32,
 | 
			
		||||
    "node32s": VARIANT_ESP32,
 | 
			
		||||
    "nodemcu-32s": VARIANT_ESP32,
 | 
			
		||||
    "nscreen-32": VARIANT_ESP32,
 | 
			
		||||
    "odroid_esp32": VARIANT_ESP32,
 | 
			
		||||
    "onehorse32dev": VARIANT_ESP32,
 | 
			
		||||
    "oroca_edubot": VARIANT_ESP32,
 | 
			
		||||
    "pico32": VARIANT_ESP32,
 | 
			
		||||
    "piranha_esp32": VARIANT_ESP32,
 | 
			
		||||
    "pocket_32": VARIANT_ESP32,
 | 
			
		||||
    "pycom_gpy": VARIANT_ESP32,
 | 
			
		||||
    "qchip": VARIANT_ESP32,
 | 
			
		||||
    "quantum": VARIANT_ESP32,
 | 
			
		||||
    "sensesiot_weizen": VARIANT_ESP32,
 | 
			
		||||
    "sg-o_airMon": VARIANT_ESP32,
 | 
			
		||||
    "s_odi_ultra": VARIANT_ESP32,
 | 
			
		||||
    "sparkfun_lora_gateway_1-channel": VARIANT_ESP32,
 | 
			
		||||
    "tinypico": VARIANT_ESP32,
 | 
			
		||||
    "ttgo-lora32-v1": VARIANT_ESP32,
 | 
			
		||||
    "ttgo-lora32-v21": VARIANT_ESP32,
 | 
			
		||||
    "ttgo-lora32-v2": VARIANT_ESP32,
 | 
			
		||||
    "ttgo-t1": VARIANT_ESP32,
 | 
			
		||||
    "ttgo-t7-v13-mini32": VARIANT_ESP32,
 | 
			
		||||
    "ttgo-t7-v14-mini32": VARIANT_ESP32,
 | 
			
		||||
    "ttgo-t-beam": VARIANT_ESP32,
 | 
			
		||||
    "ttgo-t-watch": VARIANT_ESP32,
 | 
			
		||||
    "turta_iot_node": VARIANT_ESP32,
 | 
			
		||||
    "vintlabs-devkit-v1": VARIANT_ESP32,
 | 
			
		||||
    "wemosbat": VARIANT_ESP32,
 | 
			
		||||
    "wemos_d1_mini32": VARIANT_ESP32,
 | 
			
		||||
    "wesp32": VARIANT_ESP32,
 | 
			
		||||
    "widora-air": VARIANT_ESP32,
 | 
			
		||||
    "wifiduino32": VARIANT_ESP32,
 | 
			
		||||
    "xinabox_cw02": VARIANT_ESP32,
 | 
			
		||||
BOARDS = {
 | 
			
		||||
    "adafruit_feather_esp32s2_tft": {
 | 
			
		||||
        "name": "Adafruit Feather ESP32-S2 TFT",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_feather_esp32s3": {
 | 
			
		||||
        "name": "Adafruit Feather ESP32-S3 2MB PSRAM",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_feather_esp32s3_nopsram": {
 | 
			
		||||
        "name": "Adafruit Feather ESP32-S3 No PSRAM",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_feather_esp32s3_tft": {
 | 
			
		||||
        "name": "Adafruit Feather ESP32-S3 TFT",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_feather_esp32_v2": {
 | 
			
		||||
        "name": "Adafruit Feather ESP32 V2",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_funhouse_esp32s2": {
 | 
			
		||||
        "name": "Adafruit FunHouse",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_itsybitsy_esp32": {
 | 
			
		||||
        "name": "Adafruit ItsyBitsy ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_magtag29_esp32s2": {
 | 
			
		||||
        "name": "Adafruit MagTag 2.9",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_metro_esp32s2": {
 | 
			
		||||
        "name": "Adafruit Metro ESP32-S2",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_qtpy_esp32c3": {
 | 
			
		||||
        "name": "Adafruit QT Py ESP32-C3",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_qtpy_esp32": {
 | 
			
		||||
        "name": "Adafruit QT Py ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_qtpy_esp32s2": {
 | 
			
		||||
        "name": "Adafruit QT Py ESP32-S2",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "adafruit_qtpy_esp32s3_nopsram": {
 | 
			
		||||
        "name": "Adafruit QT Py ESP32-S3 No PSRAM",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "airm2m_core_esp32c3": {
 | 
			
		||||
        "name": "AirM2M CORE ESP32C3",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "alksesp32": {
 | 
			
		||||
        "name": "ALKS ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "atmegazero_esp32s2": {
 | 
			
		||||
        "name": "EspinalLab ATMegaZero ESP32-S2",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "az-delivery-devkit-v4": {
 | 
			
		||||
        "name": "AZ-Delivery ESP-32 Dev Kit C V4",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "bee_motion_mini": {
 | 
			
		||||
        "name": "Smart Bee Motion Mini",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "bee_motion": {
 | 
			
		||||
        "name": "Smart Bee Motion",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "bee_motion_s3": {
 | 
			
		||||
        "name": "Smart Bee Motion S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "bee_s3": {
 | 
			
		||||
        "name": "Smart Bee S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "bpi-bit": {
 | 
			
		||||
        "name": "BPI-Bit",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "briki_abc_esp32": {
 | 
			
		||||
        "name": "Briki ABC (MBC-WB) - ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "briki_mbc-wb_esp32": {
 | 
			
		||||
        "name": "Briki MBC-WB - ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "cnrs_aw2eth": {
 | 
			
		||||
        "name": "CNRS AW2ETH",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "connaxio_espoir": {
 | 
			
		||||
        "name": "Connaxio's Espoir",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "d-duino-32": {
 | 
			
		||||
        "name": "D-duino-32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "deneyapkart1A": {
 | 
			
		||||
        "name": "Deneyap Kart 1A",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "deneyapkartg": {
 | 
			
		||||
        "name": "Deneyap Kart G",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "deneyapkart": {
 | 
			
		||||
        "name": "Deneyap Kart",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "deneyapmini": {
 | 
			
		||||
        "name": "Deneyap Mini",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "denky32": {
 | 
			
		||||
        "name": "Denky32 (WROOM32)",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "denky_d4": {
 | 
			
		||||
        "name": "Denky D4 (PICO-V3-02)",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "dfrobot_beetle_esp32c3": {
 | 
			
		||||
        "name": "DFRobot Beetle ESP32-C3",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "dfrobot_firebeetle2_esp32s3": {
 | 
			
		||||
        "name": "DFRobot Firebeetle 2 ESP32-S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "dpu_esp32": {
 | 
			
		||||
        "name": "TAMC DPU ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp320": {
 | 
			
		||||
        "name": "Electronic SweetPeas ESP320",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-c3-devkitm-1": {
 | 
			
		||||
        "name": "Espressif ESP32-C3-DevKitM-1",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32cam": {
 | 
			
		||||
        "name": "AI Thinker ESP32-CAM",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-devkitlipo": {
 | 
			
		||||
        "name": "OLIMEX ESP32-DevKit-LiPo",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32dev": {
 | 
			
		||||
        "name": "Espressif ESP32 Dev Module",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32doit-devkit-v1": {
 | 
			
		||||
        "name": "DOIT ESP32 DEVKIT V1",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32doit-espduino": {
 | 
			
		||||
        "name": "DOIT ESPduino32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-evb": {
 | 
			
		||||
        "name": "OLIMEX ESP32-EVB",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-gateway": {
 | 
			
		||||
        "name": "OLIMEX ESP32-GATEWAY",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-poe-iso": {
 | 
			
		||||
        "name": "OLIMEX ESP32-PoE-ISO",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-poe": {
 | 
			
		||||
        "name": "OLIMEX ESP32-PoE",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-pro": {
 | 
			
		||||
        "name": "OLIMEX ESP32-PRO",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-s2-franzininho": {
 | 
			
		||||
        "name": "Franzininho WiFi Board",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-s2-kaluga-1": {
 | 
			
		||||
        "name": "Espressif ESP32-S2-Kaluga-1 Kit",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-s2-saola-1": {
 | 
			
		||||
        "name": "Espressif ESP32-S2-Saola-1",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32s3box": {
 | 
			
		||||
        "name": "Espressif ESP32-S3-Box",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32s3camlcd": {
 | 
			
		||||
        "name": "ESP32S3 CAM LCD",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32-s3-devkitc-1": {
 | 
			
		||||
        "name": "Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32thing": {
 | 
			
		||||
        "name": "SparkFun ESP32 Thing",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32thing_plus": {
 | 
			
		||||
        "name": "SparkFun ESP32 Thing Plus",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp32vn-iot-uno": {
 | 
			
		||||
        "name": "ESP32vn IoT Uno",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "espea32": {
 | 
			
		||||
        "name": "April Brother ESPea32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "espectro32": {
 | 
			
		||||
        "name": "ESPectro32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "espino32": {
 | 
			
		||||
        "name": "ESPino32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "esp-wrover-kit": {
 | 
			
		||||
        "name": "Espressif ESP-WROVER-KIT",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "etboard": {
 | 
			
		||||
        "name": "ETBoard",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "featheresp32": {
 | 
			
		||||
        "name": "Adafruit ESP32 Feather",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "featheresp32-s2": {
 | 
			
		||||
        "name": "Adafruit ESP32-S2 Feather Development Board",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "firebeetle32": {
 | 
			
		||||
        "name": "FireBeetle-ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "fm-devkit": {
 | 
			
		||||
        "name": "ESP32 FM DevKit",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "franzininho_wifi_esp32s2": {
 | 
			
		||||
        "name": "Franzininho WiFi",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "franzininho_wifi_msc_esp32s2": {
 | 
			
		||||
        "name": "Franzininho WiFi MSC",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "frogboard": {
 | 
			
		||||
        "name": "Frog Board ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "healthypi4": {
 | 
			
		||||
        "name": "ProtoCentral HealthyPi 4",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "heltec_wifi_kit_32": {
 | 
			
		||||
        "name": "Heltec WiFi Kit 32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "heltec_wifi_kit_32_v2": {
 | 
			
		||||
        "name": "Heltec WiFi Kit 32 (V2)",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "heltec_wifi_lora_32": {
 | 
			
		||||
        "name": "Heltec WiFi LoRa 32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "heltec_wifi_lora_32_V2": {
 | 
			
		||||
        "name": "Heltec WiFi LoRa 32 (V2)",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "heltec_wireless_stick_lite": {
 | 
			
		||||
        "name": "Heltec Wireless Stick Lite",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "heltec_wireless_stick": {
 | 
			
		||||
        "name": "Heltec Wireless Stick",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "honeylemon": {
 | 
			
		||||
        "name": "HONEYLemon",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "hornbill32dev": {
 | 
			
		||||
        "name": "Hornbill ESP32 Dev",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "hornbill32minima": {
 | 
			
		||||
        "name": "Hornbill ESP32 Minima",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "imbrios-logsens-v1p1": {
 | 
			
		||||
        "name": "Imbrios LogSens V1P1",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "inex_openkb": {
 | 
			
		||||
        "name": "INEX OpenKB",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "intorobot": {
 | 
			
		||||
        "name": "IntoRobot Fig",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "iotaap_magnolia": {
 | 
			
		||||
        "name": "IoTaaP Magnolia",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "iotbusio": {
 | 
			
		||||
        "name": "oddWires IoT-Bus Io",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "iotbusproteus": {
 | 
			
		||||
        "name": "oddWires IoT-Bus Proteus",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "kb32-ft": {
 | 
			
		||||
        "name": "MakerAsia KB32-FT",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "kits-edu": {
 | 
			
		||||
        "name": "KITS ESP32 EDU",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "labplus_mpython": {
 | 
			
		||||
        "name": "Labplus mPython",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "lionbit": {
 | 
			
		||||
        "name": "Lion:Bit Dev Board",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "lolin32_lite": {
 | 
			
		||||
        "name": "WEMOS LOLIN32 Lite",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "lolin32": {
 | 
			
		||||
        "name": "WEMOS LOLIN32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "lolin_c3_mini": {
 | 
			
		||||
        "name": "WEMOS LOLIN C3 Mini",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "lolin_d32": {
 | 
			
		||||
        "name": "WEMOS LOLIN D32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "lolin_d32_pro": {
 | 
			
		||||
        "name": "WEMOS LOLIN D32 PRO",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "lolin_s2_mini": {
 | 
			
		||||
        "name": "WEMOS LOLIN S2 Mini",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "lolin_s2_pico": {
 | 
			
		||||
        "name": "WEMOS LOLIN S2 PICO",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "lolin_s3": {
 | 
			
		||||
        "name": "WEMOS LOLIN S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "lopy4": {
 | 
			
		||||
        "name": "Pycom LoPy4",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "lopy": {
 | 
			
		||||
        "name": "Pycom LoPy",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-atom": {
 | 
			
		||||
        "name": "M5Stack-ATOM",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-core2": {
 | 
			
		||||
        "name": "M5Stack Core2",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-core-esp32": {
 | 
			
		||||
        "name": "M5Stack Core ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-coreink": {
 | 
			
		||||
        "name": "M5Stack-Core Ink",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-fire": {
 | 
			
		||||
        "name": "M5Stack FIRE",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-grey": {
 | 
			
		||||
        "name": "M5Stack GREY ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-station": {
 | 
			
		||||
        "name": "M5Stack Station",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stack-timer-cam": {
 | 
			
		||||
        "name": "M5Stack Timer CAM",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "m5stick-c": {
 | 
			
		||||
        "name": "M5Stick-C",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "magicbit": {
 | 
			
		||||
        "name": "MagicBit",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "mgbot-iotik32a": {
 | 
			
		||||
        "name": "MGBOT IOTIK 32A",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "mgbot-iotik32b": {
 | 
			
		||||
        "name": "MGBOT IOTIK 32B",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "mhetesp32devkit": {
 | 
			
		||||
        "name": "MH ET LIVE ESP32DevKIT",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "mhetesp32minikit": {
 | 
			
		||||
        "name": "MH ET LIVE ESP32MiniKit",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "microduino-core-esp32": {
 | 
			
		||||
        "name": "Microduino Core ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "micros2": {
 | 
			
		||||
        "name": "microS2",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "minimain_esp32s2": {
 | 
			
		||||
        "name": "Deparment of Alchemy MiniMain ESP32-S2",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "nano32": {
 | 
			
		||||
        "name": "MakerAsia Nano32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "nina_w10": {
 | 
			
		||||
        "name": "u-blox NINA-W10 series",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "node32s": {
 | 
			
		||||
        "name": "Node32s",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "nodemcu-32s2": {
 | 
			
		||||
        "name": "Ai-Thinker NodeMCU-32S2 (ESP-12K)",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "nodemcu-32s": {
 | 
			
		||||
        "name": "NodeMCU-32S",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "nscreen-32": {
 | 
			
		||||
        "name": "YeaCreate NSCREEN-32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "odroid_esp32": {
 | 
			
		||||
        "name": "ODROID-GO",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "onehorse32dev": {
 | 
			
		||||
        "name": "Onehorse ESP32 Dev Module",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "oroca_edubot": {
 | 
			
		||||
        "name": "OROCA EduBot",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "pico32": {
 | 
			
		||||
        "name": "ESP32 Pico Kit",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "piranha_esp32": {
 | 
			
		||||
        "name": "Fishino Piranha ESP-32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "pocket_32": {
 | 
			
		||||
        "name": "Dongsen Tech Pocket 32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "pycom_gpy": {
 | 
			
		||||
        "name": "Pycom GPy",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "qchip": {
 | 
			
		||||
        "name": "Qchip",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "quantum": {
 | 
			
		||||
        "name": "Noduino Quantum",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "seeed_xiao_esp32c3": {
 | 
			
		||||
        "name": "Seeed Studio XIAO ESP32C3",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "sensesiot_weizen": {
 | 
			
		||||
        "name": "LOGISENSES Senses Weizen",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "sg-o_airMon": {
 | 
			
		||||
        "name": "SG-O AirMon",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "s_odi_ultra": {
 | 
			
		||||
        "name": "S.ODI Ultra v1",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "sparkfun_esp32_iot_redboard": {
 | 
			
		||||
        "name": "SparkFun ESP32 IoT RedBoard",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "sparkfun_esp32micromod": {
 | 
			
		||||
        "name": "SparkFun ESP32 MicroMod",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "sparkfun_esp32s2_thing_plus_c": {
 | 
			
		||||
        "name": "SparkFun ESP32 Thing Plus C",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "sparkfun_esp32s2_thing_plus": {
 | 
			
		||||
        "name": "SparkFun ESP32-S2 Thing Plus",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "sparkfun_lora_gateway_1-channel": {
 | 
			
		||||
        "name": "SparkFun LoRa Gateway 1-Channel",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "tamc_termod_s3": {
 | 
			
		||||
        "name": "TAMC Termod S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "tinypico": {
 | 
			
		||||
        "name": "Unexpected Maker TinyPICO",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "trueverit-iot-driver-mk2": {
 | 
			
		||||
        "name": "Trueverit ESP32 Universal IoT Driver MK II",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "trueverit-iot-driver-mk3": {
 | 
			
		||||
        "name": "Trueverit ESP32 Universal IoT Driver MK III",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "trueverit-iot-driver": {
 | 
			
		||||
        "name": "Trueverit ESP32 Universal IoT Driver",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "ttgo-lora32-v1": {
 | 
			
		||||
        "name": "TTGO LoRa32-OLED V1",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "ttgo-lora32-v21": {
 | 
			
		||||
        "name": "TTGO LoRa32-OLED v2.1.6",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "ttgo-lora32-v2": {
 | 
			
		||||
        "name": "TTGO LoRa32-OLED V2",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "ttgo-t1": {
 | 
			
		||||
        "name": "TTGO T1",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "ttgo-t7-v13-mini32": {
 | 
			
		||||
        "name": "TTGO T7 V1.3 Mini32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "ttgo-t7-v14-mini32": {
 | 
			
		||||
        "name": "TTGO T7 V1.4 Mini32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "ttgo-t-beam": {
 | 
			
		||||
        "name": "TTGO T-Beam",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "ttgo-t-oi-plus": {
 | 
			
		||||
        "name": "TTGO T-OI PLUS RISC-V ESP32-C3",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "ttgo-t-watch": {
 | 
			
		||||
        "name": "TTGO T-Watch",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "turta_iot_node": {
 | 
			
		||||
        "name": "Turta IoT Node",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "um_feathers2": {
 | 
			
		||||
        "name": "Unexpected Maker FeatherS2",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "um_feathers2_neo": {
 | 
			
		||||
        "name": "Unexpected Maker FeatherS2 Neo",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "um_feathers3": {
 | 
			
		||||
        "name": "Unexpected Maker FeatherS3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "um_pros3": {
 | 
			
		||||
        "name": "Unexpected Maker PROS3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "um_rmp": {
 | 
			
		||||
        "name": "Unexpected Maker RMP",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "um_tinys2": {
 | 
			
		||||
        "name": "Unexpected Maker TinyS2",
 | 
			
		||||
        "variant": VARIANT_ESP32S2,
 | 
			
		||||
    },
 | 
			
		||||
    "um_tinys3": {
 | 
			
		||||
        "name": "Unexpected Maker TinyS3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "unphone7": {
 | 
			
		||||
        "name": "unPhone 7",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "unphone8": {
 | 
			
		||||
        "name": "unPhone 8",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "unphone9": {
 | 
			
		||||
        "name": "unPhone 9",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "upesy_wroom": {
 | 
			
		||||
        "name": "uPesy ESP32 Wroom DevKit",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "upesy_wrover": {
 | 
			
		||||
        "name": "uPesy ESP32 Wrover DevKit",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "vintlabs-devkit-v1": {
 | 
			
		||||
        "name": "VintLabs ESP32 Devkit",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "watchy": {
 | 
			
		||||
        "name": "SQFMI Watchy v2.0",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "wemosbat": {
 | 
			
		||||
        "name": "WeMos WiFi and Bluetooth Battery",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "wemos_d1_mini32": {
 | 
			
		||||
        "name": "WEMOS D1 MINI ESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "wemos_d1_uno32": {
 | 
			
		||||
        "name": "WEMOS D1 R32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "wesp32": {
 | 
			
		||||
        "name": "Silicognition wESP32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "widora-air": {
 | 
			
		||||
        "name": "Widora AIR",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "wifiduino32c3": {
 | 
			
		||||
        "name": "Blinker WiFiduinoV2 (ESP32-C3)",
 | 
			
		||||
        "variant": VARIANT_ESP32C3,
 | 
			
		||||
    },
 | 
			
		||||
    "wifiduino32": {
 | 
			
		||||
        "name": "Blinker WiFiduino32",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "wifiduino32s3": {
 | 
			
		||||
        "name": "Blinker WiFiduino32S3",
 | 
			
		||||
        "variant": VARIANT_ESP32S3,
 | 
			
		||||
    },
 | 
			
		||||
    "wipy3": {
 | 
			
		||||
        "name": "Pycom WiPy3",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "wt32-eth01": {
 | 
			
		||||
        "name": "Wireless-Tag WT32-ETH01 Ethernet Module",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
    "xinabox_cw02": {
 | 
			
		||||
        "name": "XinaBox CW02",
 | 
			
		||||
        "variant": VARIANT_ESP32,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
#ifdef USE_ESP32_FRAMEWORK_ESP_IDF
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "gpio_idf.h"
 | 
			
		||||
#include "gpio.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -8,13 +8,11 @@ namespace esp32 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp32";
 | 
			
		||||
 | 
			
		||||
bool IDFInternalGPIOPin::isr_service_installed = false;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
bool ESP32InternalGPIOPin::isr_service_installed = false;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
static gpio_mode_t IRAM_ATTR flags_to_mode(gpio::Flags flags) {
 | 
			
		||||
  flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN));
 | 
			
		||||
  if (flags == gpio::FLAG_NONE) {
 | 
			
		||||
    return GPIO_MODE_DISABLE;
 | 
			
		||||
  } else if (flags == gpio::FLAG_INPUT) {
 | 
			
		||||
  if (flags == gpio::FLAG_INPUT) {
 | 
			
		||||
    return GPIO_MODE_INPUT;
 | 
			
		||||
  } else if (flags == gpio::FLAG_OUTPUT) {
 | 
			
		||||
    return GPIO_MODE_OUTPUT;
 | 
			
		||||
@@ -25,7 +23,7 @@ static gpio_mode_t IRAM_ATTR flags_to_mode(gpio::Flags flags) {
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT)) {
 | 
			
		||||
    return GPIO_MODE_INPUT_OUTPUT;
 | 
			
		||||
  } else {
 | 
			
		||||
    // unsupported
 | 
			
		||||
    // unsupported or gpio::FLAG_NONE
 | 
			
		||||
    return GPIO_MODE_DISABLE;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -35,14 +33,14 @@ struct ISRPinArg {
 | 
			
		||||
  bool inverted;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const {
 | 
			
		||||
ISRInternalGPIOPin ESP32InternalGPIOPin::to_isr() const {
 | 
			
		||||
  auto *arg = new ISRPinArg{};  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  arg->pin = pin_;
 | 
			
		||||
  arg->inverted = inverted_;
 | 
			
		||||
  return ISRInternalGPIOPin((void *) arg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
 | 
			
		||||
void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
 | 
			
		||||
  gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
 | 
			
		||||
  switch (type) {
 | 
			
		||||
    case gpio::INTERRUPT_RISING_EDGE:
 | 
			
		||||
@@ -74,13 +72,13 @@ void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio:
 | 
			
		||||
  gpio_isr_handler_add(pin_, func, arg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string IDFInternalGPIOPin::dump_summary() const {
 | 
			
		||||
std::string ESP32InternalGPIOPin::dump_summary() const {
 | 
			
		||||
  char buffer[32];
 | 
			
		||||
  snprintf(buffer, sizeof(buffer), "GPIO%u", static_cast<uint32_t>(pin_));
 | 
			
		||||
  return buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IDFInternalGPIOPin::setup() {
 | 
			
		||||
void ESP32InternalGPIOPin::setup() {
 | 
			
		||||
  gpio_config_t conf{};
 | 
			
		||||
  conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(pin_);
 | 
			
		||||
  conf.mode = flags_to_mode(flags_);
 | 
			
		||||
@@ -88,10 +86,12 @@ void IDFInternalGPIOPin::setup() {
 | 
			
		||||
  conf.pull_down_en = flags_ & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
 | 
			
		||||
  conf.intr_type = GPIO_INTR_DISABLE;
 | 
			
		||||
  gpio_config(&conf);
 | 
			
		||||
  if (flags_ & gpio::FLAG_OUTPUT) {
 | 
			
		||||
    gpio_set_drive_capability(pin_, drive_strength_);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  // can't call gpio_config here because that logs in esp-idf which may cause issues
 | 
			
		||||
  gpio_set_direction(pin_, flags_to_mode(flags));
 | 
			
		||||
  gpio_pull_mode_t pull_mode = GPIO_FLOATING;
 | 
			
		||||
@@ -105,9 +105,9 @@ void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  gpio_set_pull_mode(pin_, pull_mode);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
 | 
			
		||||
void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
 | 
			
		||||
void IDFInternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); }
 | 
			
		||||
bool ESP32InternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
 | 
			
		||||
void ESP32InternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
 | 
			
		||||
void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); }
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32
 | 
			
		||||
 | 
			
		||||
@@ -140,4 +140,4 @@ void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32_FRAMEWORK_ESP_IDF
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
@@ -1,13 +1,13 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_FRAMEWORK_ESP_IDF
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include <driver/gpio.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32 {
 | 
			
		||||
 | 
			
		||||
class IDFInternalGPIOPin : public InternalGPIOPin {
 | 
			
		||||
class ESP32InternalGPIOPin : public InternalGPIOPin {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_pin(gpio_num_t pin) { pin_ = pin; }
 | 
			
		||||
  void set_inverted(bool inverted) { inverted_ = inverted; }
 | 
			
		||||
@@ -37,4 +37,4 @@ class IDFInternalGPIOPin : public InternalGPIOPin {
 | 
			
		||||
}  // namespace esp32
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32_FRAMEWORK_ESP_IDF
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
@@ -38,8 +38,7 @@ from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_support
 | 
			
		||||
from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin)
 | 
			
		||||
ArduinoInternalGPIOPin = esp32_ns.class_("ArduinoInternalGPIOPin", cg.InternalGPIOPin)
 | 
			
		||||
ESP32InternalGPIOPin = esp32_ns.class_("ESP32InternalGPIOPin", cg.InternalGPIOPin)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _lookup_pin(value):
 | 
			
		||||
@@ -105,6 +104,16 @@ _esp32_validations = {
 | 
			
		||||
 | 
			
		||||
def validate_gpio_pin(value):
 | 
			
		||||
    value = _translate_pin(value)
 | 
			
		||||
    board = CORE.data[KEY_ESP32][KEY_BOARD]
 | 
			
		||||
    board_pins = boards.ESP32_BOARD_PINS.get(board, {})
 | 
			
		||||
 | 
			
		||||
    # Resolved aliased board pins (shorthand when two boards have the same pin configuration)
 | 
			
		||||
    while isinstance(board_pins, str):
 | 
			
		||||
        board_pins = boards.ESP32_BOARD_PINS[board_pins]
 | 
			
		||||
 | 
			
		||||
    if value in board_pins.values():
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
    variant = CORE.data[KEY_ESP32][KEY_VARIANT]
 | 
			
		||||
    if variant not in _esp32_validations:
 | 
			
		||||
        raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
 | 
			
		||||
@@ -163,18 +172,10 @@ DRIVE_STRENGTHS = {
 | 
			
		||||
gpio_num_t = cg.global_ns.enum("gpio_num_t")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _choose_pin_declaration(value):
 | 
			
		||||
    if CORE.using_esp_idf:
 | 
			
		||||
        return cv.declare_id(IDFInternalGPIOPin)(value)
 | 
			
		||||
    if CORE.using_arduino:
 | 
			
		||||
        return cv.declare_id(ArduinoInternalGPIOPin)(value)
 | 
			
		||||
    raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONF_DRIVE_STRENGTH = "drive_strength"
 | 
			
		||||
ESP32_PIN_SCHEMA = cv.All(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): _choose_pin_declaration,
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(ESP32InternalGPIOPin),
 | 
			
		||||
        cv.Required(CONF_NUMBER): validate_gpio_pin,
 | 
			
		||||
        cv.Optional(CONF_MODE, default={}): cv.Schema(
 | 
			
		||||
            {
 | 
			
		||||
@@ -186,8 +187,7 @@ ESP32_PIN_SCHEMA = cv.All(
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_INVERTED, default=False): cv.boolean,
 | 
			
		||||
        cv.SplitDefault(CONF_DRIVE_STRENGTH, esp32_idf="20mA"): cv.All(
 | 
			
		||||
            cv.only_with_esp_idf,
 | 
			
		||||
        cv.Optional(CONF_DRIVE_STRENGTH, default="20mA"): cv.All(
 | 
			
		||||
            cv.float_with_unit("current", "mA", optional_unit=True),
 | 
			
		||||
            cv.enum(DRIVE_STRENGTHS),
 | 
			
		||||
        ),
 | 
			
		||||
@@ -200,10 +200,7 @@ ESP32_PIN_SCHEMA = cv.All(
 | 
			
		||||
async def esp32_pin_to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    num = config[CONF_NUMBER]
 | 
			
		||||
    if CORE.using_esp_idf:
 | 
			
		||||
    cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}")))
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_pin(num))
 | 
			
		||||
    cg.add(var.set_inverted(config[CONF_INVERTED]))
 | 
			
		||||
    if CONF_DRIVE_STRENGTH in config:
 | 
			
		||||
        cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH]))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,114 +0,0 @@
 | 
			
		||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
 | 
			
		||||
 | 
			
		||||
#include "gpio_arduino.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include <esp32-hal-gpio.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp32";
 | 
			
		||||
 | 
			
		||||
static int IRAM_ATTR flags_to_mode(gpio::Flags flags) {
 | 
			
		||||
  if (flags == gpio::FLAG_INPUT) {
 | 
			
		||||
    return INPUT;
 | 
			
		||||
  } else if (flags == gpio::FLAG_OUTPUT) {
 | 
			
		||||
    return OUTPUT;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
 | 
			
		||||
    return INPUT_PULLUP;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
 | 
			
		||||
    return INPUT_PULLDOWN;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
 | 
			
		||||
    return OUTPUT_OPEN_DRAIN;
 | 
			
		||||
  } else {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ISRPinArg {
 | 
			
		||||
  uint8_t pin;
 | 
			
		||||
  bool inverted;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ISRInternalGPIOPin ArduinoInternalGPIOPin::to_isr() const {
 | 
			
		||||
  auto *arg = new ISRPinArg{};  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  arg->pin = pin_;
 | 
			
		||||
  arg->inverted = inverted_;
 | 
			
		||||
  return ISRInternalGPIOPin((void *) arg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
 | 
			
		||||
  uint8_t arduino_mode = DISABLED;
 | 
			
		||||
  switch (type) {
 | 
			
		||||
    case gpio::INTERRUPT_RISING_EDGE:
 | 
			
		||||
      arduino_mode = inverted_ ? FALLING : RISING;
 | 
			
		||||
      break;
 | 
			
		||||
    case gpio::INTERRUPT_FALLING_EDGE:
 | 
			
		||||
      arduino_mode = inverted_ ? RISING : FALLING;
 | 
			
		||||
      break;
 | 
			
		||||
    case gpio::INTERRUPT_ANY_EDGE:
 | 
			
		||||
      arduino_mode = CHANGE;
 | 
			
		||||
      break;
 | 
			
		||||
    case gpio::INTERRUPT_LOW_LEVEL:
 | 
			
		||||
      arduino_mode = inverted_ ? ONHIGH : ONLOW;
 | 
			
		||||
      break;
 | 
			
		||||
    case gpio::INTERRUPT_HIGH_LEVEL:
 | 
			
		||||
      arduino_mode = inverted_ ? ONLOW : ONHIGH;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  attachInterruptArg(pin_, func, arg, arduino_mode);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  pinMode(pin_, flags_to_mode(flags));  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string ArduinoInternalGPIOPin::dump_summary() const {
 | 
			
		||||
  char buffer[32];
 | 
			
		||||
  snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
 | 
			
		||||
  return buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ArduinoInternalGPIOPin::digital_read() {
 | 
			
		||||
  return bool(digitalRead(pin_)) != inverted_;  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
void ArduinoInternalGPIOPin::digital_write(bool value) {
 | 
			
		||||
  digitalWrite(pin_, value != inverted_ ? 1 : 0);  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
void ArduinoInternalGPIOPin::detach_interrupt() const {
 | 
			
		||||
  detachInterrupt(pin_);  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32
 | 
			
		||||
 | 
			
		||||
using namespace esp32;
 | 
			
		||||
 | 
			
		||||
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  return bool(digitalRead(arg->pin)) != arg->inverted;  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  digitalWrite(arg->pin, value != arg->inverted ? 1 : 0);  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
 | 
			
		||||
  GPIO.status_w1tc.val = 1UL << arg->pin;
 | 
			
		||||
#else
 | 
			
		||||
  if (arg->pin < 32) {
 | 
			
		||||
    GPIO.status_w1tc = 1UL << arg->pin;
 | 
			
		||||
  } else {
 | 
			
		||||
    GPIO.status1_w1tc.intr_st = 1UL << (arg->pin - 32);
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  pinMode(arg->pin, flags_to_mode(flags));  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32_FRAMEWORK_ARDUINO
 | 
			
		||||
@@ -1,36 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32 {
 | 
			
		||||
 | 
			
		||||
class ArduinoInternalGPIOPin : public InternalGPIOPin {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_pin(uint8_t pin) { pin_ = pin; }
 | 
			
		||||
  void set_inverted(bool inverted) { inverted_ = inverted; }
 | 
			
		||||
  void set_flags(gpio::Flags flags) { flags_ = flags; }
 | 
			
		||||
 | 
			
		||||
  void setup() override { pin_mode(flags_); }
 | 
			
		||||
  void pin_mode(gpio::Flags flags) override;
 | 
			
		||||
  bool digital_read() override;
 | 
			
		||||
  void digital_write(bool value) override;
 | 
			
		||||
  std::string dump_summary() const override;
 | 
			
		||||
  void detach_interrupt() const override;
 | 
			
		||||
  ISRInternalGPIOPin to_isr() const override;
 | 
			
		||||
  uint8_t get_pin() const override { return pin_; }
 | 
			
		||||
  bool is_inverted() const override { return inverted_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
 | 
			
		||||
 | 
			
		||||
  uint8_t pin_;
 | 
			
		||||
  bool inverted_;
 | 
			
		||||
  gpio::Flags flags_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32_FRAMEWORK_ARDUINO
 | 
			
		||||
@@ -2,12 +2,14 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
from esphome.components.esp32 import add_idf_sdkconfig_option
 | 
			
		||||
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant, const
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32"]
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
 | 
			
		||||
 | 
			
		||||
NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2]
 | 
			
		||||
 | 
			
		||||
esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
 | 
			
		||||
ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component)
 | 
			
		||||
 | 
			
		||||
@@ -19,6 +21,15 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_variant(_):
 | 
			
		||||
    variant = get_esp32_variant()
 | 
			
		||||
    if variant in NO_BLUTOOTH_VARIANTS:
 | 
			
		||||
        raise cv.Invalid(f"{variant} does not support Bluetooth")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = validate_variant
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, CONF_TX_POWER
 | 
			
		||||
from esphome.core import CORE, TimePeriod
 | 
			
		||||
from esphome.components.esp32 import add_idf_sdkconfig_option
 | 
			
		||||
from esphome.components import esp32_ble
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32"]
 | 
			
		||||
CONFLICTS_WITH = ["esp32_ble_tracker"]
 | 
			
		||||
@@ -54,6 +55,8 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    validate_config,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    uuid = config[CONF_UUID].hex
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,15 @@ BLECharacteristic::~BLECharacteristic() {
 | 
			
		||||
    delete desc;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLECharacteristic::release_descriptors() {
 | 
			
		||||
  this->parsed = false;
 | 
			
		||||
  for (auto &desc : this->descriptors)
 | 
			
		||||
    delete desc;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  this->descriptors.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLECharacteristic::parse_descriptors() {
 | 
			
		||||
  this->parsed = true;
 | 
			
		||||
  uint16_t offset = 0;
 | 
			
		||||
  esp_gattc_descr_elem_t result;
 | 
			
		||||
 | 
			
		||||
@@ -49,6 +57,8 @@ void BLECharacteristic::parse_descriptors() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
 | 
			
		||||
  if (!this->parsed)
 | 
			
		||||
    this->parse_descriptors();
 | 
			
		||||
  for (auto &desc : this->descriptors) {
 | 
			
		||||
    if (desc->uuid == uuid)
 | 
			
		||||
      return desc;
 | 
			
		||||
@@ -59,6 +69,8 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
 | 
			
		||||
  return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
 | 
			
		||||
}
 | 
			
		||||
BLEDescriptor *BLECharacteristic::get_descriptor_by_handle(uint16_t handle) {
 | 
			
		||||
  if (!this->parsed)
 | 
			
		||||
    this->parse_descriptors();
 | 
			
		||||
  for (auto &desc : this->descriptors) {
 | 
			
		||||
    if (desc->handle == handle)
 | 
			
		||||
      return desc;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@
 | 
			
		||||
 | 
			
		||||
#include "ble_descriptor.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_ble_client {
 | 
			
		||||
 | 
			
		||||
@@ -16,11 +18,13 @@ class BLEService;
 | 
			
		||||
class BLECharacteristic {
 | 
			
		||||
 public:
 | 
			
		||||
  ~BLECharacteristic();
 | 
			
		||||
  bool parsed = false;
 | 
			
		||||
  espbt::ESPBTUUID uuid;
 | 
			
		||||
  uint16_t handle;
 | 
			
		||||
  esp_gatt_char_prop_t properties;
 | 
			
		||||
  std::vector<BLEDescriptor *> descriptors;
 | 
			
		||||
  void parse_descriptors();
 | 
			
		||||
  void release_descriptors();
 | 
			
		||||
  BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
 | 
			
		||||
  BLEDescriptor *get_descriptor(uint16_t uuid);
 | 
			
		||||
  BLEDescriptor *get_descriptor_by_handle(uint16_t handle);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,13 @@ namespace esphome {
 | 
			
		||||
namespace esp32_ble_client {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp32_ble_client";
 | 
			
		||||
static const esp_bt_uuid_t NOTIFY_DESC_UUID = {
 | 
			
		||||
    .len = ESP_UUID_LEN_16,
 | 
			
		||||
    .uuid =
 | 
			
		||||
        {
 | 
			
		||||
            .uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,
 | 
			
		||||
        },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::setup() {
 | 
			
		||||
  static uint8_t connection_index = 0;
 | 
			
		||||
@@ -23,7 +30,9 @@ void BLEClientBase::setup() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::loop() {
 | 
			
		||||
  if (this->state_ == espbt::ClientState::DISCOVERED) {
 | 
			
		||||
  // READY_TO_CONNECT means we have discovered the device
 | 
			
		||||
  // and the scanner has been stopped by the tracker.
 | 
			
		||||
  if (this->state_ == espbt::ClientState::READY_TO_CONNECT) {
 | 
			
		||||
    this->connect();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -51,7 +60,8 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::connect() {
 | 
			
		||||
  ESP_LOGI(TAG, "[%d] [%s] Attempting BLE connection", this->connection_index_, this->address_str_.c_str());
 | 
			
		||||
  ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           this->remote_addr_type_);
 | 
			
		||||
  auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
 | 
			
		||||
  if (ret) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
@@ -63,6 +73,8 @@ void BLEClientBase::connect() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::disconnect() {
 | 
			
		||||
  if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING)
 | 
			
		||||
    return;
 | 
			
		||||
  ESP_LOGI(TAG, "[%d] [%s] Disconnecting.", this->connection_index_, this->address_str_.c_str());
 | 
			
		||||
  auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
@@ -70,12 +82,24 @@ void BLEClientBase::disconnect() {
 | 
			
		||||
             err);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->state_ == espbt::ClientState::SEARCHING) {
 | 
			
		||||
  if (this->state_ == espbt::ClientState::SEARCHING || this->state_ == espbt::ClientState::READY_TO_CONNECT ||
 | 
			
		||||
      this->state_ == espbt::ClientState::DISCOVERED) {
 | 
			
		||||
    this->set_address(0);
 | 
			
		||||
    this->set_state(espbt::ClientState::IDLE);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->set_state(espbt::ClientState::DISCONNECTING);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::release_services() {
 | 
			
		||||
  for (auto &svc : this->services_)
 | 
			
		||||
    delete svc;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  this->services_.clear();
 | 
			
		||||
#ifndef CONFIG_BT_GATTC_CACHE_NVS_FLASH
 | 
			
		||||
  esp_ble_gattc_cache_clean(this->remote_bda_);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
 | 
			
		||||
                                        esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
 | 
			
		||||
@@ -101,17 +125,24 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT", this->connection_index_, this->address_str_.c_str());
 | 
			
		||||
      this->conn_id_ = param->open.conn_id;
 | 
			
		||||
      this->service_count_ = 0;
 | 
			
		||||
      if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
                 param->open.status);
 | 
			
		||||
        this->set_state(espbt::ClientState::IDLE);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
 | 
			
		||||
        this->set_state(espbt::ClientState::CONNECTED);
 | 
			
		||||
        this->state_ = espbt::ClientState::ESTABLISHED;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id);
 | 
			
		||||
      if (ret) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), ret);
 | 
			
		||||
      }
 | 
			
		||||
      esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_CFG_MTU_EVT: {
 | 
			
		||||
@@ -124,7 +155,6 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
               param->cfg_mtu.status, param->cfg_mtu.mtu);
 | 
			
		||||
      this->mtu_ = param->cfg_mtu.mtu;
 | 
			
		||||
      esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
@@ -132,13 +162,17 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
        return false;
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_,
 | 
			
		||||
               this->address_str_.c_str(), param->disconnect.reason);
 | 
			
		||||
      for (auto &svc : this->services_)
 | 
			
		||||
        delete svc;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
      this->services_.clear();
 | 
			
		||||
      this->release_services();
 | 
			
		||||
      this->set_state(espbt::ClientState::IDLE);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_RES_EVT: {
 | 
			
		||||
      this->service_count_++;
 | 
			
		||||
      if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
 | 
			
		||||
        // V3 clients don't need services initialized since
 | 
			
		||||
        // they only request by handle after receiving the services.
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      BLEService *ble_service = new BLEService();  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
      ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
 | 
			
		||||
      ble_service->start_handle = param->search_res.start_handle;
 | 
			
		||||
@@ -150,27 +184,49 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_SEARCH_CMPL_EVT", this->connection_index_, this->address_str_.c_str());
 | 
			
		||||
      for (auto &svc : this->services_) {
 | 
			
		||||
        ESP_LOGI(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
        ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
                 svc->uuid.to_string().c_str());
 | 
			
		||||
        ESP_LOGI(TAG, "[%d] [%s]  start_handle: 0x%x  end_handle: 0x%x", this->connection_index_,
 | 
			
		||||
        ESP_LOGV(TAG, "[%d] [%s]  start_handle: 0x%x  end_handle: 0x%x", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), svc->start_handle, svc->end_handle);
 | 
			
		||||
        svc->parse_characteristics();
 | 
			
		||||
      }
 | 
			
		||||
      this->set_state(espbt::ClientState::CONNECTED);
 | 
			
		||||
      this->state_ = espbt::ClientState::ESTABLISHED;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      auto *descr = this->get_config_descriptor(param->reg_for_notify.handle);
 | 
			
		||||
      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, "[%d] [%s] Handle 0x%x (uuid %s) is not a client config char uuid", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->reg_for_notify.handle, descr->uuid.to_string().c_str());
 | 
			
		||||
      if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
 | 
			
		||||
          this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
 | 
			
		||||
        // Client is responsible for flipping the descriptor value
 | 
			
		||||
        // when using the cache
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      uint16_t notify_en = 1;
 | 
			
		||||
      auto status =
 | 
			
		||||
          esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descr->handle, sizeof(notify_en),
 | 
			
		||||
      esp_gattc_descr_elem_t desc_result;
 | 
			
		||||
      uint16_t count = 1;
 | 
			
		||||
      esp_gatt_status_t descr_status =
 | 
			
		||||
          esp_ble_gattc_get_descr_by_char_handle(this->gattc_if_, this->connection_index_, param->reg_for_notify.handle,
 | 
			
		||||
                                                 NOTIFY_DESC_UUID, &desc_result, &count);
 | 
			
		||||
      if (descr_status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_descr_by_char_handle error, status=%d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), descr_status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      esp_gattc_char_elem_t char_result;
 | 
			
		||||
      esp_gatt_status_t char_status =
 | 
			
		||||
          esp_ble_gattc_get_all_char(this->gattc_if_, this->connection_index_, param->reg_for_notify.handle,
 | 
			
		||||
                                     param->reg_for_notify.handle, &char_result, &count, 0);
 | 
			
		||||
      if (char_status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), char_status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      /*
 | 
			
		||||
        1 = notify
 | 
			
		||||
        2 = indicate
 | 
			
		||||
      */
 | 
			
		||||
      uint16_t notify_en = char_result.properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY ? 1 : 2;
 | 
			
		||||
      esp_err_t status =
 | 
			
		||||
          esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, desc_result.handle, sizeof(notify_en),
 | 
			
		||||
                                         (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
      if (status) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, status=%d", this->connection_index_,
 | 
			
		||||
@@ -234,14 +290,17 @@ float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
 | 
			
		||||
      if (length > 2) {
 | 
			
		||||
        return (float) encode_uint16(value[1], value[2]);
 | 
			
		||||
      }
 | 
			
		||||
      // fall through
 | 
			
		||||
    case 0x7:  // uint24.
 | 
			
		||||
      if (length > 3) {
 | 
			
		||||
        return (float) encode_uint24(value[1], value[2], value[3]);
 | 
			
		||||
      }
 | 
			
		||||
      // fall through
 | 
			
		||||
    case 0x8:  // uint32.
 | 
			
		||||
      if (length > 4) {
 | 
			
		||||
        return (float) encode_uint32(value[1], value[2], value[3], value[4]);
 | 
			
		||||
      }
 | 
			
		||||
      // fall through
 | 
			
		||||
    case 0xC:  // int8.
 | 
			
		||||
      return (float) ((int8_t) value[1]);
 | 
			
		||||
    case 0xD:  // int12.
 | 
			
		||||
@@ -249,10 +308,12 @@ float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
 | 
			
		||||
      if (length > 2) {
 | 
			
		||||
        return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]);
 | 
			
		||||
      }
 | 
			
		||||
      // fall through
 | 
			
		||||
    case 0xF:  // int24.
 | 
			
		||||
      if (length > 3) {
 | 
			
		||||
        return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3]));
 | 
			
		||||
      }
 | 
			
		||||
      // fall through
 | 
			
		||||
    case 0x10:  // int32.
 | 
			
		||||
      if (length > 4) {
 | 
			
		||||
        return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) +
 | 
			
		||||
@@ -287,6 +348,8 @@ BLECharacteristic *BLEClientBase::get_characteristic(uint16_t service, uint16_t
 | 
			
		||||
 | 
			
		||||
BLECharacteristic *BLEClientBase::get_characteristic(uint16_t handle) {
 | 
			
		||||
  for (auto *svc : this->services_) {
 | 
			
		||||
    if (!svc->parsed)
 | 
			
		||||
      svc->parse_characteristics();
 | 
			
		||||
    for (auto *chr : svc->characteristics) {
 | 
			
		||||
      if (chr->handle == handle)
 | 
			
		||||
        return chr;
 | 
			
		||||
@@ -298,8 +361,10 @@ BLECharacteristic *BLEClientBase::get_characteristic(uint16_t handle) {
 | 
			
		||||
BLEDescriptor *BLEClientBase::get_config_descriptor(uint16_t handle) {
 | 
			
		||||
  auto *chr = this->get_characteristic(handle);
 | 
			
		||||
  if (chr != nullptr) {
 | 
			
		||||
    if (!chr->parsed)
 | 
			
		||||
      chr->parse_descriptors();
 | 
			
		||||
    for (auto &desc : chr->descriptors) {
 | 
			
		||||
      if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902))
 | 
			
		||||
      if (desc->uuid.get_uuid().uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG)
 | 
			
		||||
        return desc;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -323,7 +388,11 @@ BLEDescriptor *BLEClientBase::get_descriptor(uint16_t service, uint16_t chr, uin
 | 
			
		||||
 | 
			
		||||
BLEDescriptor *BLEClientBase::get_descriptor(uint16_t handle) {
 | 
			
		||||
  for (auto *svc : this->services_) {
 | 
			
		||||
    if (!svc->parsed)
 | 
			
		||||
      svc->parse_characteristics();
 | 
			
		||||
    for (auto *chr : svc->characteristics) {
 | 
			
		||||
      if (!chr->parsed)
 | 
			
		||||
        chr->parse_descriptors();
 | 
			
		||||
      for (auto *desc : chr->descriptors) {
 | 
			
		||||
        if (desc->handle == handle)
 | 
			
		||||
          return desc;
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include <esp_bt_defs.h>
 | 
			
		||||
#include <esp_gap_ble_api.h>
 | 
			
		||||
@@ -33,6 +34,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
 | 
			
		||||
  void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
 | 
			
		||||
  void connect() override;
 | 
			
		||||
  void disconnect();
 | 
			
		||||
  void release_services();
 | 
			
		||||
 | 
			
		||||
  bool connected() { return this->state_ == espbt::ClientState::ESTABLISHED; }
 | 
			
		||||
 | 
			
		||||
@@ -42,7 +44,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
 | 
			
		||||
      memset(this->remote_bda_, 0, sizeof(this->remote_bda_));
 | 
			
		||||
      this->address_str_ = "";
 | 
			
		||||
    } else {
 | 
			
		||||
      this->address_str_ = str_snprintf("%02x:%02x:%02x:%02x:%02x:%02x", 17, (uint8_t)(this->address_ >> 40) & 0xff,
 | 
			
		||||
      this->address_str_ = str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, (uint8_t)(this->address_ >> 40) & 0xff,
 | 
			
		||||
                                        (uint8_t)(this->address_ >> 32) & 0xff, (uint8_t)(this->address_ >> 24) & 0xff,
 | 
			
		||||
                                        (uint8_t)(this->address_ >> 16) & 0xff, (uint8_t)(this->address_ >> 8) & 0xff,
 | 
			
		||||
                                        (uint8_t)(this->address_ >> 0) & 0xff);
 | 
			
		||||
@@ -66,11 +68,14 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
 | 
			
		||||
  int get_gattc_if() const { return this->gattc_if_; }
 | 
			
		||||
  uint8_t *get_remote_bda() { return this->remote_bda_; }
 | 
			
		||||
  esp_ble_addr_type_t get_remote_addr_type() const { return this->remote_addr_type_; }
 | 
			
		||||
  void set_remote_addr_type(esp_ble_addr_type_t address_type) { this->remote_addr_type_ = address_type; }
 | 
			
		||||
  uint16_t get_conn_id() const { return this->conn_id_; }
 | 
			
		||||
  uint64_t get_address() const { return this->address_; }
 | 
			
		||||
 | 
			
		||||
  uint8_t get_connection_index() const { return this->connection_index_; }
 | 
			
		||||
 | 
			
		||||
  virtual void set_connection_type(espbt::ConnectionType ct) { this->connection_type_ = ct; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  int gattc_if_;
 | 
			
		||||
  esp_bd_addr_t remote_bda_;
 | 
			
		||||
@@ -79,7 +84,9 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
 | 
			
		||||
  uint64_t address_{0};
 | 
			
		||||
  std::string address_str_{};
 | 
			
		||||
  uint8_t connection_index_;
 | 
			
		||||
  int16_t service_count_{0};
 | 
			
		||||
  uint16_t mtu_{23};
 | 
			
		||||
  espbt::ConnectionType connection_type_{espbt::ConnectionType::V1};
 | 
			
		||||
 | 
			
		||||
  std::vector<BLEService *> services_;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,8 @@ namespace esp32_ble_client {
 | 
			
		||||
static const char *const TAG = "esp32_ble_client";
 | 
			
		||||
 | 
			
		||||
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
 | 
			
		||||
  if (!this->parsed)
 | 
			
		||||
    this->parse_characteristics();
 | 
			
		||||
  for (auto &chr : this->characteristics) {
 | 
			
		||||
    if (chr->uuid == uuid)
 | 
			
		||||
      return chr;
 | 
			
		||||
@@ -27,7 +29,15 @@ BLEService::~BLEService() {
 | 
			
		||||
    delete chr;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEService::release_characteristics() {
 | 
			
		||||
  this->parsed = false;
 | 
			
		||||
  for (auto &chr : this->characteristics)
 | 
			
		||||
    delete chr;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  this->characteristics.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEService::parse_characteristics() {
 | 
			
		||||
  this->parsed = true;
 | 
			
		||||
  uint16_t offset = 0;
 | 
			
		||||
  esp_gattc_char_elem_t result;
 | 
			
		||||
 | 
			
		||||
@@ -54,10 +64,9 @@ void BLEService::parse_characteristics() {
 | 
			
		||||
    characteristic->handle = result.char_handle;
 | 
			
		||||
    characteristic->service = this;
 | 
			
		||||
    this->characteristics.push_back(characteristic);
 | 
			
		||||
    ESP_LOGI(TAG, "[%d] [%s]  characteristic %s, handle 0x%x, properties 0x%x", this->client->get_connection_index(),
 | 
			
		||||
    ESP_LOGV(TAG, "[%d] [%s]  characteristic %s, handle 0x%x, properties 0x%x", this->client->get_connection_index(),
 | 
			
		||||
             this->client->address_str().c_str(), characteristic->uuid.to_string().c_str(), characteristic->handle,
 | 
			
		||||
             characteristic->properties);
 | 
			
		||||
    characteristic->parse_descriptors();
 | 
			
		||||
    offset++;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@
 | 
			
		||||
 | 
			
		||||
#include "ble_characteristic.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_ble_client {
 | 
			
		||||
 | 
			
		||||
@@ -16,12 +18,14 @@ class BLEClientBase;
 | 
			
		||||
class BLEService {
 | 
			
		||||
 public:
 | 
			
		||||
  ~BLEService();
 | 
			
		||||
  bool parsed = false;
 | 
			
		||||
  espbt::ESPBTUUID uuid;
 | 
			
		||||
  uint16_t start_handle;
 | 
			
		||||
  uint16_t end_handle;
 | 
			
		||||
  std::vector<BLECharacteristic *> characteristics;
 | 
			
		||||
  BLEClientBase *client;
 | 
			
		||||
  void parse_characteristics();
 | 
			
		||||
  void release_characteristics();
 | 
			
		||||
  BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid);
 | 
			
		||||
  BLECharacteristic *get_characteristic(uint16_t uuid);
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user