mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 16:41:50 +00:00 
			
		
		
		
	Compare commits
	
		
			175 Commits
		
	
	
		
			2023.4.0b3
			...
			jesserockz
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					fcf0761ba3 | ||
| 
						 | 
					4b9732629e | ||
| 
						 | 
					fb094fca0f | ||
| 
						 | 
					de10b356cf | ||
| 
						 | 
					bd6d6caa8a | ||
| 
						 | 
					1c4af08ed3 | ||
| 
						 | 
					c97d361b6c | ||
| 
						 | 
					379b1d84dd | ||
| 
						 | 
					c13e20643b | ||
| 
						 | 
					76b6fcf554 | ||
| 
						 | 
					57e909e790 | ||
| 
						 | 
					d6f7876e68 | ||
| 
						 | 
					56e0923c22 | ||
| 
						 | 
					f4b98f5e32 | ||
| 
						 | 
					2d56b70a36 | ||
| 
						 | 
					980cfaf295 | ||
| 
						 | 
					c2a43c733a | ||
| 
						 | 
					568e65a6ab | ||
| 
						 | 
					5cef11acf0 | ||
| 
						 | 
					b1ad48c824 | ||
| 
						 | 
					35b1b5fae6 | ||
| 
						 | 
					ce20b64712 | ||
| 
						 | 
					1720b2a37e | ||
| 
						 | 
					c974fbbea8 | ||
| 
						 | 
					00c5233bf4 | ||
| 
						 | 
					7efb138933 | ||
| 
						 | 
					f73435f820 | ||
| 
						 | 
					bfc55beedc | ||
| 
						 | 
					9fa057eae8 | ||
| 
						 | 
					59d6b3afa0 | ||
| 
						 | 
					b89c04b928 | ||
| 
						 | 
					12090657bb | ||
| 
						 | 
					4e21cf0bdd | ||
| 
						 | 
					ba4ef72d56 | ||
| 
						 | 
					f3e6a4314f | ||
| 
						 | 
					e14ce3d950 | ||
| 
						 | 
					70aa38f5bd | ||
| 
						 | 
					55ec082628 | ||
| 
						 | 
					6f27126c8d | ||
| 
						 | 
					ee21a91313 | ||
| 
						 | 
					c5efaa1c00 | ||
| 
						 | 
					6476357596 | ||
| 
						 | 
					77f71acbc8 | ||
| 
						 | 
					4a08a5413d | ||
| 
						 | 
					e3d89cc6b6 | ||
| 
						 | 
					64afb07e91 | ||
| 
						 | 
					f639f7c280 | ||
| 
						 | 
					986dd2ddd2 | ||
| 
						 | 
					7abdb5d046 | ||
| 
						 | 
					0f1e186189 | ||
| 
						 | 
					96d208e0d8 | ||
| 
						 | 
					38ed38864e | ||
| 
						 | 
					a12ba7bd38 | ||
| 
						 | 
					4a177e3931 | ||
| 
						 | 
					bef5b38d49 | ||
| 
						 | 
					0a95f116fc | ||
| 
						 | 
					327cd662b4 | ||
| 
						 | 
					bb05ba3d00 | ||
| 
						 | 
					c0ad5d1d16 | ||
| 
						 | 
					4c39631428 | ||
| 
						 | 
					afc2b3b74f | ||
| 
						 | 
					19fc1417ae | ||
| 
						 | 
					e2fefa51f5 | ||
| 
						 | 
					f668d5617f | ||
| 
						 | 
					47c4ff15d6 | ||
| 
						 | 
					ccf1bdc0b4 | ||
| 
						 | 
					e993fcf80c | ||
| 
						 | 
					1bdc30a09e | ||
| 
						 | 
					0f7e34e7ec | ||
| 
						 | 
					2be703b329 | ||
| 
						 | 
					4cea74ef3b | ||
| 
						 | 
					3a587ea0d4 | ||
| 
						 | 
					8a60919e1f | ||
| 
						 | 
					382dcddf12 | ||
| 
						 | 
					6b67acbeb5 | ||
| 
						 | 
					7b0fca6824 | ||
| 
						 | 
					47555d314a | ||
| 
						 | 
					0643b71908 | ||
| 
						 | 
					afc848bf22 | ||
| 
						 | 
					cc1eb648f9 | ||
| 
						 | 
					04a139fe3d | ||
| 
						 | 
					e4b2de5c68 | ||
| 
						 | 
					f862b479e7 | ||
| 
						 | 
					358c59bd8d | ||
| 
						 | 
					74fe135c9c | ||
| 
						 | 
					8d3896172d | ||
| 
						 | 
					9d9725144d | ||
| 
						 | 
					dd8dc1ef1d | ||
| 
						 | 
					bc427de16a | ||
| 
						 | 
					db5988bbe1 | ||
| 
						 | 
					a3875af4b4 | ||
| 
						 | 
					e6737479f7 | ||
| 
						 | 
					7f75832bf1 | ||
| 
						 | 
					33339e3bd8 | ||
| 
						 | 
					c037e95861 | ||
| 
						 | 
					2e1b35959f | ||
| 
						 | 
					7f46d9e0f9 | ||
| 
						 | 
					069b5f81a0 | ||
| 
						 | 
					3a36d0b13f | ||
| 
						 | 
					f0760e99b7 | ||
| 
						 | 
					18fecf8c09 | ||
| 
						 | 
					414cf1b333 | ||
| 
						 | 
					d10f891f51 | ||
| 
						 | 
					b5927322e6 | ||
| 
						 | 
					1cf4107e1c | ||
| 
						 | 
					c12408326c | ||
| 
						 | 
					4434e59e5a | ||
| 
						 | 
					45180d98f6 | ||
| 
						 | 
					44494ad18e | ||
| 
						 | 
					1447536906 | ||
| 
						 | 
					27ec517084 | ||
| 
						 | 
					ce1f034bac | ||
| 
						 | 
					f1f96f16e9 | ||
| 
						 | 
					4af4649e23 | ||
| 
						 | 
					8bcddef39d | ||
| 
						 | 
					4ac96ccea2 | ||
| 
						 | 
					3c5de77ae9 | ||
| 
						 | 
					a2925b1d37 | ||
| 
						 | 
					73748e9e20 | ||
| 
						 | 
					75c9823899 | ||
| 
						 | 
					c8c0bd3351 | ||
| 
						 | 
					e1cdeb7c8f | ||
| 
						 | 
					7f97f42552 | ||
| 
						 | 
					aa7f3569ec | ||
| 
						 | 
					2d0a08442e | ||
| 
						 | 
					d2380756b2 | ||
| 
						 | 
					925e3cb6c9 | ||
| 
						 | 
					6757acba56 | ||
| 
						 | 
					5cc91cdd95 | ||
| 
						 | 
					2b41886819 | ||
| 
						 | 
					72c6efd6a0 | ||
| 
						 | 
					a1f1804112 | ||
| 
						 | 
					a8b1ceb4e9 | ||
| 
						 | 
					4fb0f7f8c6 | ||
| 
						 | 
					958cadeca8 | ||
| 
						 | 
					00f2655f1a | ||
| 
						 | 
					074f5029eb | ||
| 
						 | 
					1691976587 | ||
| 
						 | 
					60e6b4d21e | ||
| 
						 | 
					5750591df2 | ||
| 
						 | 
					a75da54455 | ||
| 
						 | 
					de7f6c3f5f | ||
| 
						 | 
					4245480656 | ||
| 
						 | 
					1824c8131e | ||
| 
						 | 
					4e9606d2e0 | ||
| 
						 | 
					78500fa933 | ||
| 
						 | 
					9c69b98a49 | ||
| 
						 | 
					e6d8ef98d3 | ||
| 
						 | 
					3f1af1690b | ||
| 
						 | 
					84374b6b1e | ||
| 
						 | 
					391316c9b5 | ||
| 
						 | 
					705c62ebd7 | ||
| 
						 | 
					7209dd8bae | ||
| 
						 | 
					ab736c89bb | ||
| 
						 | 
					6911639617 | ||
| 
						 | 
					b9720d0715 | ||
| 
						 | 
					47b3267ed4 | ||
| 
						 | 
					e16ba2adb5 | ||
| 
						 | 
					0a19b1e32c | ||
| 
						 | 
					bae9a950c0 | ||
| 
						 | 
					72b2943332 | ||
| 
						 | 
					4ec0ef7548 | ||
| 
						 | 
					25bc6761f6 | ||
| 
						 | 
					81b6562c25 | ||
| 
						 | 
					ae74189fc2 | ||
| 
						 | 
					9e516efe10 | ||
| 
						 | 
					366e29439e | ||
| 
						 | 
					1c9c700d7f | ||
| 
						 | 
					b2e6b9d31f | ||
| 
						 | 
					7623f63846 | ||
| 
						 | 
					2bfaf9dce3 | ||
| 
						 | 
					5c2c1560bb | ||
| 
						 | 
					f7096ab78e | ||
| 
						 | 
					98f8feb625 | ||
| 
						 | 
					9944ca414e | 
							
								
								
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -11,6 +11,7 @@ on:
 | 
			
		||||
      - ".github/workflows/**"
 | 
			
		||||
      - "requirements*.txt"
 | 
			
		||||
      - "platformio.ini"
 | 
			
		||||
      - "script/platformio_install_deps.py"
 | 
			
		||||
 | 
			
		||||
  pull_request:
 | 
			
		||||
    paths:
 | 
			
		||||
@@ -18,6 +19,7 @@ on:
 | 
			
		||||
      - ".github/workflows/**"
 | 
			
		||||
      - "requirements*.txt"
 | 
			
		||||
      - "platformio.ini"
 | 
			
		||||
      - "script/platformio_install_deps.py"
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -23,6 +23,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      max-parallel: 5
 | 
			
		||||
      matrix:
 | 
			
		||||
        include:
 | 
			
		||||
          - id: ci-custom
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -117,7 +117,7 @@ jobs:
 | 
			
		||||
            --suffix "${{ matrix.image.suffix }}"
 | 
			
		||||
 | 
			
		||||
      - name: Build and push
 | 
			
		||||
        uses: docker/build-push-action@v3
 | 
			
		||||
        uses: docker/build-push-action@v4
 | 
			
		||||
        with:
 | 
			
		||||
          context: .
 | 
			
		||||
          file: ./docker/Dockerfile
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							@@ -48,7 +48,7 @@ jobs:
 | 
			
		||||
          echo "$delimiter" >> $GITHUB_OUTPUT
 | 
			
		||||
 | 
			
		||||
      - name: Commit changes
 | 
			
		||||
        uses: peter-evans/create-pull-request@v4
 | 
			
		||||
        uses: peter-evans/create-pull-request@v5
 | 
			
		||||
        with:
 | 
			
		||||
          commit-message: "Synchronise Device Classes from Home Assistant"
 | 
			
		||||
          committer: esphomebot <esphome@nabucasa.com>
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ repos:
 | 
			
		||||
          - --branch=release
 | 
			
		||||
          - --branch=beta
 | 
			
		||||
  - repo: https://github.com/asottile/pyupgrade
 | 
			
		||||
    rev: v3.3.1
 | 
			
		||||
    rev: v3.3.2
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: pyupgrade
 | 
			
		||||
        args: [--py39-plus]
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ esphome/components/airthings_wave_mini/* @ncareau
 | 
			
		||||
esphome/components/airthings_wave_plus/* @jeromelaban
 | 
			
		||||
esphome/components/am43/* @buxtronix
 | 
			
		||||
esphome/components/am43/cover/* @buxtronix
 | 
			
		||||
esphome/components/am43/sensor/* @buxtronix
 | 
			
		||||
esphome/components/analog_threshold/* @ianchi
 | 
			
		||||
esphome/components/animation/* @syndlex
 | 
			
		||||
esphome/components/anova/* @buxtronix
 | 
			
		||||
@@ -109,6 +110,7 @@ esphome/components/honeywellabp/* @RubyBailey
 | 
			
		||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
 | 
			
		||||
esphome/components/hte501/* @Stock-M
 | 
			
		||||
esphome/components/hydreon_rgxx/* @functionpointer
 | 
			
		||||
esphome/components/hyt271/* @Philippe12
 | 
			
		||||
esphome/components/i2c/* @esphome/core
 | 
			
		||||
esphome/components/i2s_audio/* @jesserockz
 | 
			
		||||
esphome/components/i2s_audio/media_player/* @jesserockz
 | 
			
		||||
@@ -138,6 +140,7 @@ esphome/components/ltr390/* @sjtrny
 | 
			
		||||
esphome/components/matrix_keypad/* @ssieb
 | 
			
		||||
esphome/components/max31865/* @DAVe3283
 | 
			
		||||
esphome/components/max44009/* @berfenger
 | 
			
		||||
esphome/components/max6956/* @looping40
 | 
			
		||||
esphome/components/max7219digit/* @rspaargaren
 | 
			
		||||
esphome/components/max9611/* @mckaymatthew
 | 
			
		||||
esphome/components/mcp23008/* @jesserockz
 | 
			
		||||
@@ -162,6 +165,7 @@ esphome/components/midea/* @dudanov
 | 
			
		||||
esphome/components/midea_ir/* @dudanov
 | 
			
		||||
esphome/components/mitsubishi/* @RubyBailey
 | 
			
		||||
esphome/components/mlx90393/* @functionpointer
 | 
			
		||||
esphome/components/mlx90614/* @jesserockz
 | 
			
		||||
esphome/components/mmc5603/* @benhoff
 | 
			
		||||
esphome/components/modbus_controller/* @martgras
 | 
			
		||||
esphome/components/modbus_controller/binary_sensor/* @martgras
 | 
			
		||||
@@ -186,6 +190,7 @@ esphome/components/nfc/* @jesserockz
 | 
			
		||||
esphome/components/number/* @esphome/core
 | 
			
		||||
esphome/components/ota/* @esphome/core
 | 
			
		||||
esphome/components/output/* @esphome/core
 | 
			
		||||
esphome/components/pca6416a/* @Mat931
 | 
			
		||||
esphome/components/pca9554/* @hwstar
 | 
			
		||||
esphome/components/pcf85063/* @brogon
 | 
			
		||||
esphome/components/pid/* @OttoWinter
 | 
			
		||||
 
 | 
			
		||||
@@ -24,8 +24,9 @@ RUN \
 | 
			
		||||
        python3-setuptools=52.0.0-4 \
 | 
			
		||||
        python3-pil=8.1.2+dfsg-0.3+deb11u1 \
 | 
			
		||||
        python3-cryptography=3.3.2-1 \
 | 
			
		||||
        python3-venv=3.9.2-3 \
 | 
			
		||||
        iputils-ping=3:20210202-1 \
 | 
			
		||||
        git=1:2.30.2-1 \
 | 
			
		||||
        git=1:2.30.2-1+deb11u2 \
 | 
			
		||||
        curl=7.74.0-1.3+deb11u7 \
 | 
			
		||||
        openssh-client=1:8.4p1-5+deb11u1 \
 | 
			
		||||
    && rm -rf \
 | 
			
		||||
@@ -59,10 +60,10 @@ RUN \
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# First install requirements to leverage caching when requirements don't change
 | 
			
		||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
 | 
			
		||||
COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini /
 | 
			
		||||
RUN \
 | 
			
		||||
    pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
 | 
			
		||||
    && /platformio_install_deps.py /platformio.ini
 | 
			
		||||
    && /platformio_install_deps.py /platformio.ini --libraries
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ======================= docker-type image =======================
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +0,0 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
# This script is used in the docker containers to preinstall
 | 
			
		||||
# all platformio libraries in the global storage
 | 
			
		||||
 | 
			
		||||
import configparser
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
config = configparser.ConfigParser(inline_comment_prefixes=(';', ))
 | 
			
		||||
config.read(sys.argv[1])
 | 
			
		||||
 | 
			
		||||
libs = []
 | 
			
		||||
# Extract from every lib_deps key in all sections
 | 
			
		||||
for section in config.sections():
 | 
			
		||||
    conf = config[section]
 | 
			
		||||
    if "lib_deps" not in conf:
 | 
			
		||||
        continue
 | 
			
		||||
    for lib_dep in conf["lib_deps"].splitlines():
 | 
			
		||||
        if not lib_dep:
 | 
			
		||||
            # Empty line or comment
 | 
			
		||||
            continue
 | 
			
		||||
        if lib_dep.startswith("${"):
 | 
			
		||||
            # Extending from another section
 | 
			
		||||
            continue
 | 
			
		||||
        if "@" not in lib_dep:
 | 
			
		||||
            # No version pinned, this is an internal lib
 | 
			
		||||
            continue
 | 
			
		||||
        libs.append(lib_dep)
 | 
			
		||||
 | 
			
		||||
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
CODEOWNERS = ["@buxtronix"]
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ from esphome.const import CONF_ID, CONF_PIN
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@buxtronix"]
 | 
			
		||||
DEPENDENCIES = ["ble_client"]
 | 
			
		||||
AUTO_LOAD = ["am43", "sensor"]
 | 
			
		||||
AUTO_LOAD = ["am43"]
 | 
			
		||||
 | 
			
		||||
CONF_INVERT_POSITION = "invert_position"
 | 
			
		||||
 | 
			
		||||
@@ -27,10 +27,10 @@ CONFIG_SCHEMA = (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    cg.add(var.set_pin(config[CONF_PIN]))
 | 
			
		||||
    cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield cover.register_cover(var, config)
 | 
			
		||||
    yield ble_client.register_ble_node(var, config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await cover.register_cover(var, config)
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,7 @@ void Am43Component::loop() {
 | 
			
		||||
 | 
			
		||||
CoverTraits Am43Component::get_traits() {
 | 
			
		||||
  auto traits = CoverTraits();
 | 
			
		||||
  traits.set_supports_stop(true);
 | 
			
		||||
  traits.set_supports_position(true);
 | 
			
		||||
  traits.set_supports_tilt(false);
 | 
			
		||||
  traits.set_is_assumed_state(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ from esphome.const import (
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["am43"]
 | 
			
		||||
CODEOWNERS = ["@buxtronix"]
 | 
			
		||||
 | 
			
		||||
am43_ns = cg.esphome_ns.namespace("am43")
 | 
			
		||||
@@ -38,15 +39,15 @@ CONFIG_SCHEMA = (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    yield ble_client.register_ble_node(var, config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
 | 
			
		||||
    if CONF_BATTERY_LEVEL in config:
 | 
			
		||||
        sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
 | 
			
		||||
        cg.add(var.set_battery(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_ILLUMINANCE in config:
 | 
			
		||||
        sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE])
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_ILLUMINANCE])
 | 
			
		||||
        cg.add(var.set_illuminance(sens))
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
#include "am43.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "am43_sensor.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
@@ -288,6 +288,7 @@ message ListEntitiesCoverResponse {
 | 
			
		||||
  bool disabled_by_default = 9;
 | 
			
		||||
  string icon = 10;
 | 
			
		||||
  EntityCategory entity_category = 11;
 | 
			
		||||
  bool supports_stop = 12;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum LegacyCoverState {
 | 
			
		||||
@@ -861,8 +862,7 @@ message ClimateStateResponse {
 | 
			
		||||
  float target_temperature = 4;
 | 
			
		||||
  float target_temperature_low = 5;
 | 
			
		||||
  float target_temperature_high = 6;
 | 
			
		||||
  // For older peers, equal to preset == CLIMATE_PRESET_AWAY
 | 
			
		||||
  bool legacy_away = 7;
 | 
			
		||||
  bool unused_legacy_away = 7;
 | 
			
		||||
  ClimateAction action = 8;
 | 
			
		||||
  ClimateFanMode fan_mode = 9;
 | 
			
		||||
  ClimateSwingMode swing_mode = 10;
 | 
			
		||||
@@ -885,9 +885,8 @@ message ClimateCommandRequest {
 | 
			
		||||
  float target_temperature_low = 7;
 | 
			
		||||
  bool has_target_temperature_high = 8;
 | 
			
		||||
  float target_temperature_high = 9;
 | 
			
		||||
  // legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
 | 
			
		||||
  bool has_legacy_away = 10;
 | 
			
		||||
  bool legacy_away = 11;
 | 
			
		||||
  bool unused_has_legacy_away = 10;
 | 
			
		||||
  bool unused_legacy_away = 11;
 | 
			
		||||
  bool has_fan_mode = 12;
 | 
			
		||||
  ClimateFanMode fan_mode = 13;
 | 
			
		||||
  bool has_swing_mode = 14;
 | 
			
		||||
 
 | 
			
		||||
@@ -530,7 +530,6 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
 | 
			
		||||
    resp.custom_fan_mode = climate->custom_fan_mode.value();
 | 
			
		||||
  if (traits.get_supports_presets() && climate->preset.has_value()) {
 | 
			
		||||
    resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
 | 
			
		||||
    resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY;
 | 
			
		||||
  }
 | 
			
		||||
  if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value())
 | 
			
		||||
    resp.custom_preset = climate->custom_preset.value();
 | 
			
		||||
@@ -591,8 +590,6 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
 | 
			
		||||
    call.set_target_temperature_low(msg.target_temperature_low);
 | 
			
		||||
  if (msg.has_target_temperature_high)
 | 
			
		||||
    call.set_target_temperature_high(msg.target_temperature_high);
 | 
			
		||||
  if (msg.has_legacy_away)
 | 
			
		||||
    call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME);
 | 
			
		||||
  if (msg.has_fan_mode)
 | 
			
		||||
    call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
 | 
			
		||||
  if (msg.has_custom_fan_mode)
 | 
			
		||||
@@ -944,7 +941,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
 | 
			
		||||
 | 
			
		||||
  HelloResponse resp;
 | 
			
		||||
  resp.api_version_major = 1;
 | 
			
		||||
  resp.api_version_minor = 7;
 | 
			
		||||
  resp.api_version_minor = 8;
 | 
			
		||||
  resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
 | 
			
		||||
  resp.name = App.get_name();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -941,6 +941,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 12: {
 | 
			
		||||
      this->supports_stop = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -993,6 +997,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_bool(9, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_string(10, this->icon);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
 | 
			
		||||
  buffer.encode_bool(12, this->supports_stop);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -1042,6 +1047,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  supports_stop: ");
 | 
			
		||||
  out.append(YESNO(this->supports_stop));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -3649,7 +3658,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->legacy_away = value.as_bool();
 | 
			
		||||
      this->unused_legacy_away = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 8: {
 | 
			
		||||
@@ -3719,7 +3728,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_float(4, this->target_temperature);
 | 
			
		||||
  buffer.encode_float(5, this->target_temperature_low);
 | 
			
		||||
  buffer.encode_float(6, this->target_temperature_high);
 | 
			
		||||
  buffer.encode_bool(7, this->legacy_away);
 | 
			
		||||
  buffer.encode_bool(7, this->unused_legacy_away);
 | 
			
		||||
  buffer.encode_enum<enums::ClimateAction>(8, this->action);
 | 
			
		||||
  buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode);
 | 
			
		||||
  buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode);
 | 
			
		||||
@@ -3760,8 +3769,8 @@ void ClimateStateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  legacy_away: ");
 | 
			
		||||
  out.append(YESNO(this->legacy_away));
 | 
			
		||||
  out.append("  unused_legacy_away: ");
 | 
			
		||||
  out.append(YESNO(this->unused_legacy_away));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  action: ");
 | 
			
		||||
@@ -3813,11 +3822,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 10: {
 | 
			
		||||
      this->has_legacy_away = value.as_bool();
 | 
			
		||||
      this->unused_has_legacy_away = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 11: {
 | 
			
		||||
      this->legacy_away = value.as_bool();
 | 
			
		||||
      this->unused_legacy_away = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 12: {
 | 
			
		||||
@@ -3902,8 +3911,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_float(7, this->target_temperature_low);
 | 
			
		||||
  buffer.encode_bool(8, this->has_target_temperature_high);
 | 
			
		||||
  buffer.encode_float(9, this->target_temperature_high);
 | 
			
		||||
  buffer.encode_bool(10, this->has_legacy_away);
 | 
			
		||||
  buffer.encode_bool(11, this->legacy_away);
 | 
			
		||||
  buffer.encode_bool(10, this->unused_has_legacy_away);
 | 
			
		||||
  buffer.encode_bool(11, this->unused_legacy_away);
 | 
			
		||||
  buffer.encode_bool(12, this->has_fan_mode);
 | 
			
		||||
  buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode);
 | 
			
		||||
  buffer.encode_bool(14, this->has_swing_mode);
 | 
			
		||||
@@ -3959,12 +3968,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_legacy_away: ");
 | 
			
		||||
  out.append(YESNO(this->has_legacy_away));
 | 
			
		||||
  out.append("  unused_has_legacy_away: ");
 | 
			
		||||
  out.append(YESNO(this->unused_has_legacy_away));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  legacy_away: ");
 | 
			
		||||
  out.append(YESNO(this->legacy_away));
 | 
			
		||||
  out.append("  unused_legacy_away: ");
 | 
			
		||||
  out.append(YESNO(this->unused_legacy_away));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  has_fan_mode: ");
 | 
			
		||||
 
 | 
			
		||||
@@ -375,6 +375,7 @@ class ListEntitiesCoverResponse : public ProtoMessage {
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  bool supports_stop{false};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -958,7 +959,7 @@ class ClimateStateResponse : public ProtoMessage {
 | 
			
		||||
  float target_temperature{0.0f};
 | 
			
		||||
  float target_temperature_low{0.0f};
 | 
			
		||||
  float target_temperature_high{0.0f};
 | 
			
		||||
  bool legacy_away{false};
 | 
			
		||||
  bool unused_legacy_away{false};
 | 
			
		||||
  enums::ClimateAction action{};
 | 
			
		||||
  enums::ClimateFanMode fan_mode{};
 | 
			
		||||
  enums::ClimateSwingMode swing_mode{};
 | 
			
		||||
@@ -986,8 +987,8 @@ class ClimateCommandRequest : public ProtoMessage {
 | 
			
		||||
  float target_temperature_low{0.0f};
 | 
			
		||||
  bool has_target_temperature_high{false};
 | 
			
		||||
  float target_temperature_high{0.0f};
 | 
			
		||||
  bool has_legacy_away{false};
 | 
			
		||||
  bool legacy_away{false};
 | 
			
		||||
  bool unused_has_legacy_away{false};
 | 
			
		||||
  bool unused_legacy_away{false};
 | 
			
		||||
  bool has_fan_mode{false};
 | 
			
		||||
  enums::ClimateFanMode fan_mode{};
 | 
			
		||||
  bool has_swing_mode{false};
 | 
			
		||||
 
 | 
			
		||||
@@ -429,15 +429,16 @@ void APIServer::on_shutdown() {
 | 
			
		||||
 | 
			
		||||
#ifdef USE_VOICE_ASSISTANT
 | 
			
		||||
bool APIServer::start_voice_assistant() {
 | 
			
		||||
  bool result = false;
 | 
			
		||||
  for (auto &c : this->clients_) {
 | 
			
		||||
    result |= c->request_voice_assistant(true);
 | 
			
		||||
    if (c->request_voice_assistant(true))
 | 
			
		||||
      return true;
 | 
			
		||||
  }
 | 
			
		||||
  return result;
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
void APIServer::stop_voice_assistant() {
 | 
			
		||||
  for (auto &c : this->clients_) {
 | 
			
		||||
    c->request_voice_assistant(false);
 | 
			
		||||
    if (c->request_voice_assistant(false))
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -18,5 +18,5 @@ async def to_code(config):
 | 
			
		||||
        # https://github.com/esphome/AsyncTCP/blob/master/library.json
 | 
			
		||||
        cg.add_library("esphome/AsyncTCP-esphome", "1.2.2")
 | 
			
		||||
    elif CORE.is_esp8266:
 | 
			
		||||
        # https://github.com/OttoWinter/ESPAsyncTCP
 | 
			
		||||
        cg.add_library("ottowinter/ESPAsyncTCP-esphome", "1.2.3")
 | 
			
		||||
        # https://github.com/esphome/ESPAsyncTCP
 | 
			
		||||
        cg.add_library("esphome/ESPAsyncTCP-esphome", "1.2.3")
 | 
			
		||||
 
 | 
			
		||||
@@ -43,12 +43,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
BinarySensor::BinarySensor() : state(false) {}
 | 
			
		||||
void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
 | 
			
		||||
std::string BinarySensor::get_device_class() {
 | 
			
		||||
  if (this->device_class_.has_value())
 | 
			
		||||
    return *this->device_class_;
 | 
			
		||||
  return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BinarySensor::add_filter(Filter *filter) {
 | 
			
		||||
  filter->parent_ = this;
 | 
			
		||||
  if (this->filter_list_ == nullptr) {
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ namespace binary_sensor {
 | 
			
		||||
 * The sub classes should notify the front-end of new states via the publish_state() method which
 | 
			
		||||
 * handles inverted inputs for you.
 | 
			
		||||
 */
 | 
			
		||||
class BinarySensor : public EntityBase {
 | 
			
		||||
class BinarySensor : public EntityBase, public EntityBase_DeviceClass {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit BinarySensor();
 | 
			
		||||
 | 
			
		||||
@@ -60,12 +60,6 @@ class BinarySensor : public EntityBase {
 | 
			
		||||
  /// The current reported state of the binary sensor.
 | 
			
		||||
  bool state;
 | 
			
		||||
 | 
			
		||||
  /// Manually set the Home Assistant device class (see binary_sensor::device_class)
 | 
			
		||||
  void set_device_class(const std::string &device_class);
 | 
			
		||||
 | 
			
		||||
  /// Get the device class for this binary sensor, using the manual override if specified.
 | 
			
		||||
  std::string get_device_class();
 | 
			
		||||
 | 
			
		||||
  void add_filter(Filter *filter);
 | 
			
		||||
  void add_filters(const std::vector<Filter *> &filters);
 | 
			
		||||
 | 
			
		||||
@@ -82,7 +76,6 @@ class BinarySensor : public EntityBase {
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  CallbackManager<void(bool)> state_callback_{};
 | 
			
		||||
  optional<std::string> device_class_{};  ///< Stores the override of the device class
 | 
			
		||||
  Filter *filter_list_{nullptr};
 | 
			
		||||
  bool has_state_{false};
 | 
			
		||||
  bool publish_initial_state_{false};
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,9 @@ void BinarySensorMap::loop() {
 | 
			
		||||
    case BINARY_SENSOR_MAP_TYPE_SUM:
 | 
			
		||||
      this->process_sum_();
 | 
			
		||||
      break;
 | 
			
		||||
    case BINARY_SENSOR_MAP_TYPE_BAYESIAN:
 | 
			
		||||
      this->process_bayesian_();
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -23,46 +26,51 @@ void BinarySensorMap::process_group_() {
 | 
			
		||||
  float total_current_value = 0.0;
 | 
			
		||||
  uint8_t num_active_sensors = 0;
 | 
			
		||||
  uint64_t mask = 0x00;
 | 
			
		||||
  // check all binary_sensors for its state. when active add its value to total_current_value.
 | 
			
		||||
  // create a bitmask for the binary_sensor status on all channels
 | 
			
		||||
 | 
			
		||||
  // - check all binary_sensors for its state
 | 
			
		||||
  //  - if active, add its value to total_current_value.
 | 
			
		||||
  // - creates a bitmask for the binary_sensor states on all channels
 | 
			
		||||
  for (size_t i = 0; i < this->channels_.size(); i++) {
 | 
			
		||||
    auto bs = this->channels_[i];
 | 
			
		||||
    if (bs.binary_sensor->state) {
 | 
			
		||||
      num_active_sensors++;
 | 
			
		||||
      total_current_value += bs.sensor_value;
 | 
			
		||||
      total_current_value += bs.parameters.sensor_value;
 | 
			
		||||
      mask |= 1ULL << i;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // check if the sensor map was touched
 | 
			
		||||
 | 
			
		||||
  // potentially update state only if a binary_sensor is active
 | 
			
		||||
  if (mask != 0ULL) {
 | 
			
		||||
    // did the bit_mask change or is it a new sensor touch
 | 
			
		||||
    // publish the average if the bitmask has changed
 | 
			
		||||
    if (this->last_mask_ != mask) {
 | 
			
		||||
      float publish_value = total_current_value / num_active_sensors;
 | 
			
		||||
      this->publish_state(publish_value);
 | 
			
		||||
    }
 | 
			
		||||
  } else if (this->last_mask_ != 0ULL) {
 | 
			
		||||
    // is this a new sensor release
 | 
			
		||||
    // no buttons are pressed and the states have changed since last run, so publish NAN
 | 
			
		||||
    ESP_LOGV(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str());
 | 
			
		||||
    this->publish_state(NAN);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->last_mask_ = mask;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BinarySensorMap::process_sum_() {
 | 
			
		||||
  float total_current_value = 0.0;
 | 
			
		||||
  uint64_t mask = 0x00;
 | 
			
		||||
 | 
			
		||||
  // - check all binary_sensor states
 | 
			
		||||
  // - if active, add its value to total_current_value
 | 
			
		||||
  // - creates a bitmask for the binary_sensor status on all channels
 | 
			
		||||
  // - creates a bitmask for the binary_sensor states on all channels
 | 
			
		||||
  for (size_t i = 0; i < this->channels_.size(); i++) {
 | 
			
		||||
    auto bs = this->channels_[i];
 | 
			
		||||
    if (bs.binary_sensor->state) {
 | 
			
		||||
      total_current_value += bs.sensor_value;
 | 
			
		||||
      total_current_value += bs.parameters.sensor_value;
 | 
			
		||||
      mask |= 1ULL << i;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // update state only if the binary sensor states have changed or if no state has ever been sent on boot
 | 
			
		||||
  // update state only if any binary_sensor states have changed or if no state has ever been sent on boot
 | 
			
		||||
  if ((this->last_mask_ != mask) || (!this->has_state())) {
 | 
			
		||||
    this->publish_state(total_current_value);
 | 
			
		||||
  }
 | 
			
		||||
@@ -70,15 +78,65 @@ void BinarySensorMap::process_sum_() {
 | 
			
		||||
  this->last_mask_ = mask;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BinarySensorMap::process_bayesian_() {
 | 
			
		||||
  float posterior_probability = this->bayesian_prior_;
 | 
			
		||||
  uint64_t mask = 0x00;
 | 
			
		||||
 | 
			
		||||
  // - compute the posterior probability by taking the product of the predicate probablities for each observation
 | 
			
		||||
  // - create a bitmask for the binary_sensor states on all channels/observations
 | 
			
		||||
  for (size_t i = 0; i < this->channels_.size(); i++) {
 | 
			
		||||
    auto bs = this->channels_[i];
 | 
			
		||||
 | 
			
		||||
    posterior_probability *=
 | 
			
		||||
        this->bayesian_predicate_(bs.binary_sensor->state, posterior_probability,
 | 
			
		||||
                                  bs.parameters.probabilities.given_true, bs.parameters.probabilities.given_false);
 | 
			
		||||
 | 
			
		||||
    mask |= ((uint64_t) (bs.binary_sensor->state)) << i;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // update state only if any binary_sensor states have changed or if no state has ever been sent on boot
 | 
			
		||||
  if ((this->last_mask_ != mask) || (!this->has_state())) {
 | 
			
		||||
    this->publish_state(posterior_probability);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->last_mask_ = mask;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float BinarySensorMap::bayesian_predicate_(bool sensor_state, float prior, float prob_given_true,
 | 
			
		||||
                                           float prob_given_false) {
 | 
			
		||||
  float prob_state_source_true = prob_given_true;
 | 
			
		||||
  float prob_state_source_false = prob_given_false;
 | 
			
		||||
 | 
			
		||||
  // if sensor is off, then we use the probabilities for the observation's complement
 | 
			
		||||
  if (!sensor_state) {
 | 
			
		||||
    prob_state_source_true = 1 - prob_given_true;
 | 
			
		||||
    prob_state_source_false = 1 - prob_given_false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return prob_state_source_true / (prior * prob_state_source_true + (1.0 - prior) * prob_state_source_false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) {
 | 
			
		||||
  BinarySensorMapChannel sensor_channel{
 | 
			
		||||
      .binary_sensor = sensor,
 | 
			
		||||
      .sensor_value = value,
 | 
			
		||||
      .parameters{
 | 
			
		||||
          .sensor_value = value,
 | 
			
		||||
      },
 | 
			
		||||
  };
 | 
			
		||||
  this->channels_.push_back(sensor_channel);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BinarySensorMap::set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; }
 | 
			
		||||
 | 
			
		||||
void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float prob_given_true, float prob_given_false) {
 | 
			
		||||
  BinarySensorMapChannel sensor_channel{
 | 
			
		||||
      .binary_sensor = sensor,
 | 
			
		||||
      .parameters{
 | 
			
		||||
          .probabilities{
 | 
			
		||||
              .given_true = prob_given_true,
 | 
			
		||||
              .given_false = prob_given_false,
 | 
			
		||||
          },
 | 
			
		||||
      },
 | 
			
		||||
  };
 | 
			
		||||
  this->channels_.push_back(sensor_channel);
 | 
			
		||||
}
 | 
			
		||||
}  // namespace binary_sensor_map
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -12,51 +12,88 @@ namespace binary_sensor_map {
 | 
			
		||||
enum BinarySensorMapType {
 | 
			
		||||
  BINARY_SENSOR_MAP_TYPE_GROUP,
 | 
			
		||||
  BINARY_SENSOR_MAP_TYPE_SUM,
 | 
			
		||||
  BINARY_SENSOR_MAP_TYPE_BAYESIAN,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct BinarySensorMapChannel {
 | 
			
		||||
  binary_sensor::BinarySensor *binary_sensor;
 | 
			
		||||
  float sensor_value;
 | 
			
		||||
  union {
 | 
			
		||||
    float sensor_value;
 | 
			
		||||
    struct {
 | 
			
		||||
      float given_true;
 | 
			
		||||
      float given_false;
 | 
			
		||||
    } probabilities;
 | 
			
		||||
  } parameters;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** Class to group binary_sensors to one Sensor.
 | 
			
		||||
/** Class to map one or more binary_sensors to one Sensor.
 | 
			
		||||
 *
 | 
			
		||||
 * Each binary sensor represents a float value in the group.
 | 
			
		||||
 * Each binary sensor has configured parameters that each mapping type uses to compute the single numerical result
 | 
			
		||||
 */
 | 
			
		||||
class BinarySensorMap : public sensor::Sensor, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The loop checks all binary_sensor states
 | 
			
		||||
   * When the binary_sensor reports a true value for its state, then the float value it represents is added to the
 | 
			
		||||
   * total_current_value
 | 
			
		||||
   * The loop calls the configured type processing method
 | 
			
		||||
   *
 | 
			
		||||
   * Only when the total_current_value changed and at least one sensor reports an active state we publish the sensors
 | 
			
		||||
   * average value. When the value changed and no sensors ar active we publish NAN.
 | 
			
		||||
   * */
 | 
			
		||||
   * The processing method loops through all sensors and calculates the numerical result
 | 
			
		||||
   * The result is only published if a binary sensor state has changed or, for some types, on initial boot
 | 
			
		||||
   */
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
			
		||||
  /** Add binary_sensors to the group.
 | 
			
		||||
   * Each binary_sensor represents a float value when its state is true
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add binary_sensors to the group when only one parameter is needed for the configured mapping type.
 | 
			
		||||
   *
 | 
			
		||||
   * @param *sensor The binary sensor.
 | 
			
		||||
   * @param value  The value this binary_sensor represents
 | 
			
		||||
   */
 | 
			
		||||
  void add_channel(binary_sensor::BinarySensor *sensor, float value);
 | 
			
		||||
  void set_sensor_type(BinarySensorMapType sensor_type);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Add binary_sensors to the group when two parameters are needed for the Bayesian mapping type.
 | 
			
		||||
   *
 | 
			
		||||
   * @param *sensor The binary sensor.
 | 
			
		||||
   * @param prob_given_true Probability this observation is on when the Bayesian event is true
 | 
			
		||||
   * @param prob_given_false Probability this observation is on when the Bayesian event is false
 | 
			
		||||
   */
 | 
			
		||||
  void add_channel(binary_sensor::BinarySensor *sensor, float prob_given_true, float prob_given_false);
 | 
			
		||||
 | 
			
		||||
  void set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; }
 | 
			
		||||
 | 
			
		||||
  void set_bayesian_prior(float prior) { this->bayesian_prior_ = prior; };
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::vector<BinarySensorMapChannel> channels_{};
 | 
			
		||||
  BinarySensorMapType sensor_type_{BINARY_SENSOR_MAP_TYPE_GROUP};
 | 
			
		||||
  // this gives max 64 channels per binary_sensor_map
 | 
			
		||||
 | 
			
		||||
  // this allows a max of 64 channels/observations in order to keep track of binary_sensor states
 | 
			
		||||
  uint64_t last_mask_{0x00};
 | 
			
		||||
 | 
			
		||||
  // Bayesian event prior probability before taking into account any observations
 | 
			
		||||
  float bayesian_prior_{};
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * methods to process the types of binary_sensor_maps
 | 
			
		||||
   * GROUP: process_group_() just map to a value
 | 
			
		||||
   * Methods to process the binary_sensor_maps types
 | 
			
		||||
   *
 | 
			
		||||
   * GROUP: process_group_() averages all the values
 | 
			
		||||
   * ADD: process_add_() adds all the values
 | 
			
		||||
   * BAYESIAN: process_bayesian_() computes the predicate probability
 | 
			
		||||
   * */
 | 
			
		||||
  void process_group_();
 | 
			
		||||
  void process_sum_();
 | 
			
		||||
  void process_bayesian_();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Computes the Bayesian predicate for a specific observation
 | 
			
		||||
   * If the sensor state is false, then we use the parameters' probabilities for the observatiosn complement
 | 
			
		||||
   *
 | 
			
		||||
   * @param sensor_state  State of observation
 | 
			
		||||
   * @param prior Prior probability before accounting for this observation
 | 
			
		||||
   * @param prob_given_true Probability this observation is on when the Bayesian event is true
 | 
			
		||||
   * @param prob_given_false Probability this observation is on when the Bayesian event is false
 | 
			
		||||
   * */
 | 
			
		||||
  float bayesian_predicate_(bool sensor_state, float prior, float prob_given_true, float prob_given_false);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace binary_sensor_map
 | 
			
		||||
 
 | 
			
		||||
@@ -20,16 +20,29 @@ BinarySensorMap = binary_sensor_map_ns.class_(
 | 
			
		||||
)
 | 
			
		||||
SensorMapType = binary_sensor_map_ns.enum("SensorMapType")
 | 
			
		||||
 | 
			
		||||
CONF_BAYESIAN = "bayesian"
 | 
			
		||||
CONF_PRIOR = "prior"
 | 
			
		||||
CONF_PROB_GIVEN_TRUE = "prob_given_true"
 | 
			
		||||
CONF_PROB_GIVEN_FALSE = "prob_given_false"
 | 
			
		||||
CONF_OBSERVATIONS = "observations"
 | 
			
		||||
 | 
			
		||||
SENSOR_MAP_TYPES = {
 | 
			
		||||
    CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP,
 | 
			
		||||
    CONF_SUM: SensorMapType.BINARY_SENSOR_MAP_TYPE_SUM,
 | 
			
		||||
    CONF_BAYESIAN: SensorMapType.BINARY_SENSOR_MAP_TYPE_BAYESIAN,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
entry = {
 | 
			
		||||
entry_one_parameter = {
 | 
			
		||||
    cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor),
 | 
			
		||||
    cv.Required(CONF_VALUE): cv.float_,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
entry_bayesian_parameters = {
 | 
			
		||||
    cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor),
 | 
			
		||||
    cv.Required(CONF_PROB_GIVEN_TRUE): cv.float_range(min=0, max=1),
 | 
			
		||||
    cv.Required(CONF_PROB_GIVEN_FALSE): cv.float_range(min=0, max=1),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.typed_schema(
 | 
			
		||||
    {
 | 
			
		||||
        CONF_GROUP: sensor.sensor_schema(
 | 
			
		||||
@@ -39,7 +52,7 @@ CONFIG_SCHEMA = cv.typed_schema(
 | 
			
		||||
        ).extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Required(CONF_CHANNELS): cv.All(
 | 
			
		||||
                    cv.ensure_list(entry), cv.Length(min=1, max=64)
 | 
			
		||||
                    cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64)
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
@@ -50,7 +63,18 @@ CONFIG_SCHEMA = cv.typed_schema(
 | 
			
		||||
        ).extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Required(CONF_CHANNELS): cv.All(
 | 
			
		||||
                    cv.ensure_list(entry), cv.Length(min=1, max=64)
 | 
			
		||||
                    cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64)
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        CONF_BAYESIAN: sensor.sensor_schema(
 | 
			
		||||
            BinarySensorMap,
 | 
			
		||||
            accuracy_decimals=2,
 | 
			
		||||
        ).extend(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Required(CONF_PRIOR): cv.float_range(min=0, max=1),
 | 
			
		||||
                cv.Required(CONF_OBSERVATIONS): cv.All(
 | 
			
		||||
                    cv.ensure_list(entry_bayesian_parameters), cv.Length(min=1, max=64)
 | 
			
		||||
                ),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
@@ -66,6 +90,17 @@ async def to_code(config):
 | 
			
		||||
    constant = SENSOR_MAP_TYPES[config[CONF_TYPE]]
 | 
			
		||||
    cg.add(var.set_sensor_type(constant))
 | 
			
		||||
 | 
			
		||||
    for ch in config[CONF_CHANNELS]:
 | 
			
		||||
        input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR])
 | 
			
		||||
        cg.add(var.add_channel(input_var, ch[CONF_VALUE]))
 | 
			
		||||
    if config[CONF_TYPE] == CONF_BAYESIAN:
 | 
			
		||||
        cg.add(var.set_bayesian_prior(config[CONF_PRIOR]))
 | 
			
		||||
 | 
			
		||||
        for obs in config[CONF_OBSERVATIONS]:
 | 
			
		||||
            input_var = await cg.get_variable(obs[CONF_BINARY_SENSOR])
 | 
			
		||||
            cg.add(
 | 
			
		||||
                var.add_channel(
 | 
			
		||||
                    input_var, obs[CONF_PROB_GIVEN_TRUE], obs[CONF_PROB_GIVEN_FALSE]
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
    else:
 | 
			
		||||
        for ch in config[CONF_CHANNELS]:
 | 
			
		||||
            input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR])
 | 
			
		||||
            cg.add(var.add_channel(input_var, ch[CONF_VALUE]))
 | 
			
		||||
 
 | 
			
		||||
@@ -29,8 +29,35 @@ BLEClientConnectTrigger = ble_client_ns.class_(
 | 
			
		||||
BLEClientDisconnectTrigger = ble_client_ns.class_(
 | 
			
		||||
    "BLEClientDisconnectTrigger", automation.Trigger.template(BLEClientNodeConstRef)
 | 
			
		||||
)
 | 
			
		||||
BLEClientPasskeyRequestTrigger = ble_client_ns.class_(
 | 
			
		||||
    "BLEClientPasskeyRequestTrigger", automation.Trigger.template(BLEClientNodeConstRef)
 | 
			
		||||
)
 | 
			
		||||
BLEClientPasskeyNotificationTrigger = ble_client_ns.class_(
 | 
			
		||||
    "BLEClientPasskeyNotificationTrigger",
 | 
			
		||||
    automation.Trigger.template(BLEClientNodeConstRef, cg.uint32),
 | 
			
		||||
)
 | 
			
		||||
BLEClientNumericComparisonRequestTrigger = ble_client_ns.class_(
 | 
			
		||||
    "BLEClientNumericComparisonRequestTrigger",
 | 
			
		||||
    automation.Trigger.template(BLEClientNodeConstRef, cg.uint32),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Actions
 | 
			
		||||
BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action)
 | 
			
		||||
BLEPasskeyReplyAction = ble_client_ns.class_(
 | 
			
		||||
    "BLEClientPasskeyReplyAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
BLENumericComparisonReplyAction = ble_client_ns.class_(
 | 
			
		||||
    "BLEClientNumericComparisonReplyAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
BLERemoveBondAction = ble_client_ns.class_(
 | 
			
		||||
    "BLEClientRemoveBondAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_PASSKEY = "passkey"
 | 
			
		||||
CONF_ACCEPT = "accept"
 | 
			
		||||
CONF_ON_PASSKEY_REQUEST = "on_passkey_request"
 | 
			
		||||
CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification"
 | 
			
		||||
CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request"
 | 
			
		||||
 | 
			
		||||
# Espressif platformio framework is built with MAX_BLE_CONN to 3, so
 | 
			
		||||
# enforce this in yaml checks.
 | 
			
		||||
@@ -56,6 +83,29 @@ CONFIG_SCHEMA = (
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_PASSKEY_REQUEST): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                        BLEClientPasskeyRequestTrigger
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ON_PASSKEY_NOTIFICATION): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                        BLEClientPasskeyNotificationTrigger
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_ON_NUMERIC_COMPARISON_REQUEST
 | 
			
		||||
            ): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                        BLEClientNumericComparisonRequestTrigger
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
@@ -85,13 +135,34 @@ BLE_WRITE_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
 | 
			
		||||
        cv.Required(CONF_ACCEPT): cv.templatable(cv.boolean),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
BLE_PASSKEY_REPLY_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
 | 
			
		||||
        cv.Required(CONF_PASSKEY): cv.templatable(cv.int_range(min=0, max=999999)),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BLE_REMOVE_BOND_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
async def ble_write_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, parent)
 | 
			
		||||
 | 
			
		||||
    value = config[CONF_VALUE]
 | 
			
		||||
    if cg.is_template(value):
 | 
			
		||||
@@ -137,6 +208,54 @@ async def ble_write_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ble_client.numeric_comparison_reply",
 | 
			
		||||
    BLENumericComparisonReplyAction,
 | 
			
		||||
    BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
async def numeric_comparison_reply_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, parent)
 | 
			
		||||
 | 
			
		||||
    accept = config[CONF_ACCEPT]
 | 
			
		||||
    if cg.is_template(accept):
 | 
			
		||||
        templ = await cg.templatable(accept, args, cg.bool_)
 | 
			
		||||
        cg.add(var.set_value_template(templ))
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_value_simple(accept))
 | 
			
		||||
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ble_client.passkey_reply", BLEPasskeyReplyAction, BLE_PASSKEY_REPLY_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
async def passkey_reply_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, parent)
 | 
			
		||||
 | 
			
		||||
    passkey = config[CONF_PASSKEY]
 | 
			
		||||
    if cg.is_template(passkey):
 | 
			
		||||
        templ = await cg.templatable(passkey, args, cg.uint32)
 | 
			
		||||
        cg.add(var.set_value_template(templ))
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_value_simple(passkey))
 | 
			
		||||
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ble_client.remove_bond",
 | 
			
		||||
    BLERemoveBondAction,
 | 
			
		||||
    BLE_REMOVE_BOND_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
async def remove_bond_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, parent)
 | 
			
		||||
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
@@ -148,3 +267,12 @@ async def to_code(config):
 | 
			
		||||
    for conf in config.get(CONF_ON_DISCONNECT, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_PASSKEY_REQUEST, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_PASSKEY_NOTIFICATION, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf)
 | 
			
		||||
    for conf in config.get(CONF_ON_NUMERIC_COMPARISON_REQUEST, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf)
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,44 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode {
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BLEClientPasskeyRequestTrigger : public Trigger<>, public BLEClientNode {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit BLEClientPasskeyRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); }
 | 
			
		||||
  void loop() override {}
 | 
			
		||||
  void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
 | 
			
		||||
    if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT &&
 | 
			
		||||
        memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
 | 
			
		||||
      this->trigger();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BLEClientPasskeyNotificationTrigger : public Trigger<uint32_t>, public BLEClientNode {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit BLEClientPasskeyNotificationTrigger(BLEClient *parent) { parent->register_ble_node(this); }
 | 
			
		||||
  void loop() override {}
 | 
			
		||||
  void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
 | 
			
		||||
    if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT &&
 | 
			
		||||
        memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
 | 
			
		||||
      uint32_t passkey = param->ble_security.key_notif.passkey;
 | 
			
		||||
      this->trigger(passkey);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BLEClientNumericComparisonRequestTrigger : public Trigger<uint32_t>, public BLEClientNode {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit BLEClientNumericComparisonRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); }
 | 
			
		||||
  void loop() override {}
 | 
			
		||||
  void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
 | 
			
		||||
    if (event == ESP_GAP_BLE_NC_REQ_EVT &&
 | 
			
		||||
        memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
 | 
			
		||||
      uint32_t passkey = param->ble_security.key_notif.passkey;
 | 
			
		||||
      this->trigger(passkey);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BLEWriterClientNode : public BLEClientNode {
 | 
			
		||||
 public:
 | 
			
		||||
  BLEWriterClientNode(BLEClient *ble_client) {
 | 
			
		||||
@@ -94,6 +132,86 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
 | 
			
		||||
  std::function<std::vector<uint8_t>(Ts...)> value_template_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  BLEClientPasskeyReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    uint32_t passkey;
 | 
			
		||||
    if (has_simple_value_) {
 | 
			
		||||
      passkey = this->value_simple_;
 | 
			
		||||
    } else {
 | 
			
		||||
      passkey = this->value_template_(x...);
 | 
			
		||||
    }
 | 
			
		||||
    if (passkey > 999999)
 | 
			
		||||
      return;
 | 
			
		||||
    esp_bd_addr_t remote_bda;
 | 
			
		||||
    memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
 | 
			
		||||
    esp_ble_passkey_reply(remote_bda, true, passkey);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_value_template(std::function<uint32_t(Ts...)> func) {
 | 
			
		||||
    this->value_template_ = std::move(func);
 | 
			
		||||
    has_simple_value_ = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_value_simple(const uint32_t &value) {
 | 
			
		||||
    this->value_simple_ = value;
 | 
			
		||||
    has_simple_value_ = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  BLEClient *parent_{nullptr};
 | 
			
		||||
  bool has_simple_value_ = true;
 | 
			
		||||
  uint32_t value_simple_{0};
 | 
			
		||||
  std::function<uint32_t(Ts...)> value_template_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    esp_bd_addr_t remote_bda;
 | 
			
		||||
    memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
 | 
			
		||||
    if (has_simple_value_) {
 | 
			
		||||
      esp_ble_confirm_reply(remote_bda, this->value_simple_);
 | 
			
		||||
    } else {
 | 
			
		||||
      esp_ble_confirm_reply(remote_bda, this->value_template_(x...));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_value_template(std::function<bool(Ts...)> func) {
 | 
			
		||||
    this->value_template_ = std::move(func);
 | 
			
		||||
    has_simple_value_ = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void set_value_simple(const bool &value) {
 | 
			
		||||
    this->value_simple_ = value;
 | 
			
		||||
    has_simple_value_ = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  BLEClient *parent_{nullptr};
 | 
			
		||||
  bool has_simple_value_ = true;
 | 
			
		||||
  bool value_simple_{false};
 | 
			
		||||
  std::function<bool(Ts...)> value_template_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  BLEClientRemoveBondAction(BLEClient *ble_client) { parent_ = ble_client; }
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    esp_bd_addr_t remote_bda;
 | 
			
		||||
    memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
 | 
			
		||||
    esp_ble_remove_bond_device(remote_bda);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  BLEClient *parent_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ class BLEClient;
 | 
			
		||||
class BLEClientNode {
 | 
			
		||||
 public:
 | 
			
		||||
  virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                   esp_ble_gattc_cb_param_t *param) = 0;
 | 
			
		||||
                                   esp_ble_gattc_cb_param_t *param){};
 | 
			
		||||
  virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {}
 | 
			
		||||
  virtual void loop() {}
 | 
			
		||||
  void set_address(uint64_t address) { address_ = address; }
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,5 @@ void Button::press() {
 | 
			
		||||
}
 | 
			
		||||
void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); }
 | 
			
		||||
 | 
			
		||||
void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
 | 
			
		||||
std::string Button::get_device_class() { return this->device_class_; }
 | 
			
		||||
 | 
			
		||||
}  // namespace button
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ namespace button {
 | 
			
		||||
 *
 | 
			
		||||
 * A button is just a momentary switch that does not have a state, only a trigger.
 | 
			
		||||
 */
 | 
			
		||||
class Button : public EntityBase {
 | 
			
		||||
class Button : public EntityBase, public EntityBase_DeviceClass {
 | 
			
		||||
 public:
 | 
			
		||||
  /** Press this button. This is called by the front-end.
 | 
			
		||||
   *
 | 
			
		||||
@@ -40,19 +40,12 @@ class Button : public EntityBase {
 | 
			
		||||
   */
 | 
			
		||||
  void add_on_press_callback(std::function<void()> &&callback);
 | 
			
		||||
 | 
			
		||||
  /// Set the Home Assistant device class (see button::device_class).
 | 
			
		||||
  void set_device_class(const std::string &device_class);
 | 
			
		||||
 | 
			
		||||
  /// Get the device class for this button.
 | 
			
		||||
  std::string get_device_class();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  /** You should implement this virtual method if you want to create your own button.
 | 
			
		||||
   */
 | 
			
		||||
  virtual void press_action() = 0;
 | 
			
		||||
 | 
			
		||||
  CallbackManager<void()> press_callback_{};
 | 
			
		||||
  std::string device_class_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace button
 | 
			
		||||
 
 | 
			
		||||
@@ -343,7 +343,7 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
        cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
 | 
			
		||||
        cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),
 | 
			
		||||
        cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
 | 
			
		||||
        cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
 | 
			
		||||
        cv.Optional(CONF_AWAY): cv.invalid("Use preset instead"),
 | 
			
		||||
        cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable(
 | 
			
		||||
            validate_climate_fan_mode
 | 
			
		||||
        ),
 | 
			
		||||
@@ -379,9 +379,6 @@ async def climate_control_to_code(config, action_id, template_arg, args):
 | 
			
		||||
            config[CONF_TARGET_TEMPERATURE_HIGH], args, float
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_target_temperature_high(template_))
 | 
			
		||||
    if CONF_AWAY in config:
 | 
			
		||||
        template_ = await cg.templatable(config[CONF_AWAY], args, bool)
 | 
			
		||||
        cg.add(var.set_away(template_))
 | 
			
		||||
    if CONF_FAN_MODE in config:
 | 
			
		||||
        template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode)
 | 
			
		||||
        cg.add(var.set_fan_mode(template_))
 | 
			
		||||
 
 | 
			
		||||
@@ -264,25 +264,11 @@ const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_;
 | 
			
		||||
const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; }
 | 
			
		||||
const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
 | 
			
		||||
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
 | 
			
		||||
optional<bool> ClimateCall::get_away() const {
 | 
			
		||||
  if (!this->preset_.has_value())
 | 
			
		||||
    return {};
 | 
			
		||||
  return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY;
 | 
			
		||||
}
 | 
			
		||||
const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; }
 | 
			
		||||
const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; }
 | 
			
		||||
const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; }
 | 
			
		||||
const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; }
 | 
			
		||||
const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; }
 | 
			
		||||
ClimateCall &ClimateCall::set_away(bool away) {
 | 
			
		||||
  this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
ClimateCall &ClimateCall::set_away(optional<bool> away) {
 | 
			
		||||
  if (away.has_value())
 | 
			
		||||
    this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) {
 | 
			
		||||
  this->target_temperature_high_ = target_temperature_high;
 | 
			
		||||
  return *this;
 | 
			
		||||
 
 | 
			
		||||
@@ -64,10 +64,6 @@ class ClimateCall {
 | 
			
		||||
   * For climate devices with two point target temperature control
 | 
			
		||||
   */
 | 
			
		||||
  ClimateCall &set_target_temperature_high(optional<float> target_temperature_high);
 | 
			
		||||
  ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20")
 | 
			
		||||
  ClimateCall &set_away(bool away);
 | 
			
		||||
  ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20")
 | 
			
		||||
  ClimateCall &set_away(optional<bool> away);
 | 
			
		||||
  /// Set the fan mode of the climate device.
 | 
			
		||||
  ClimateCall &set_fan_mode(ClimateFanMode fan_mode);
 | 
			
		||||
  /// Set the fan mode of the climate device.
 | 
			
		||||
@@ -97,8 +93,6 @@ class ClimateCall {
 | 
			
		||||
  const optional<float> &get_target_temperature() const;
 | 
			
		||||
  const optional<float> &get_target_temperature_low() const;
 | 
			
		||||
  const optional<float> &get_target_temperature_high() const;
 | 
			
		||||
  ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead", "v1.20")
 | 
			
		||||
  optional<bool> get_away() const;
 | 
			
		||||
  const optional<ClimateFanMode> &get_fan_mode() const;
 | 
			
		||||
  const optional<ClimateSwingMode> &get_swing_mode() const;
 | 
			
		||||
  const optional<std::string> &get_custom_fan_mode() const;
 | 
			
		||||
@@ -184,14 +178,6 @@ class Climate : public EntityBase {
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /** Whether the climate device is in away mode.
 | 
			
		||||
   *
 | 
			
		||||
   * Away allows climate devices to have two different target temperature configs:
 | 
			
		||||
   * one for normal mode and one for away mode.
 | 
			
		||||
   */
 | 
			
		||||
  ESPDEPRECATED("away is deprecated, use preset instead", "v1.20")
 | 
			
		||||
  bool away{false};
 | 
			
		||||
 | 
			
		||||
  /// The active fan mode of the climate device.
 | 
			
		||||
  optional<ClimateFanMode> fan_mode;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -117,15 +117,6 @@ class ClimateTraits {
 | 
			
		||||
  bool supports_custom_preset(const std::string &custom_preset) const {
 | 
			
		||||
    return supported_custom_presets_.count(custom_preset);
 | 
			
		||||
  }
 | 
			
		||||
  ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead", "v1.20")
 | 
			
		||||
  void set_supports_away(bool supports) {
 | 
			
		||||
    if (supports) {
 | 
			
		||||
      supported_presets_.insert(CLIMATE_PRESET_AWAY);
 | 
			
		||||
      supported_presets_.insert(CLIMATE_PRESET_HOME);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  ESPDEPRECATED("This method is deprecated, use supports_preset() instead", "v1.20")
 | 
			
		||||
  bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); }
 | 
			
		||||
 | 
			
		||||
  void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); }
 | 
			
		||||
  void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); }
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ cover::CoverTraits CopyCover::get_traits() {
 | 
			
		||||
  // copy traits manually so it doesn't break when new options are added
 | 
			
		||||
  // but the control() method hasn't implemented them yet.
 | 
			
		||||
  traits.set_is_assumed_state(base.get_is_assumed_state());
 | 
			
		||||
  traits.set_supports_stop(base.get_supports_stop());
 | 
			
		||||
  traits.set_supports_position(base.get_supports_position());
 | 
			
		||||
  traits.set_supports_tilt(base.get_supports_tilt());
 | 
			
		||||
  traits.set_supports_toggle(base.get_supports_toggle());
 | 
			
		||||
 
 | 
			
		||||
@@ -145,7 +145,7 @@ CoverCall &CoverCall::set_stop(bool stop) {
 | 
			
		||||
  return *this;
 | 
			
		||||
}
 | 
			
		||||
bool CoverCall::get_stop() const { return this->stop_; }
 | 
			
		||||
void Cover::set_device_class(const std::string &device_class) { this->device_class_override_ = device_class; }
 | 
			
		||||
 | 
			
		||||
CoverCall Cover::make_call() { return {this}; }
 | 
			
		||||
void Cover::open() {
 | 
			
		||||
  auto call = this->make_call();
 | 
			
		||||
@@ -204,11 +204,7 @@ optional<CoverRestoreState> Cover::restore_state_() {
 | 
			
		||||
    return {};
 | 
			
		||||
  return recovered;
 | 
			
		||||
}
 | 
			
		||||
std::string Cover::get_device_class() {
 | 
			
		||||
  if (this->device_class_override_.has_value())
 | 
			
		||||
    return *this->device_class_override_;
 | 
			
		||||
  return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Cover::is_fully_open() const { return this->position == COVER_OPEN; }
 | 
			
		||||
bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -108,7 +108,7 @@ const char *cover_operation_to_str(CoverOperation op);
 | 
			
		||||
 * to control all values of the cover. Also implement get_traits() to return what operations
 | 
			
		||||
 * the cover supports.
 | 
			
		||||
 */
 | 
			
		||||
class Cover : public EntityBase {
 | 
			
		||||
class Cover : public EntityBase, public EntityBase_DeviceClass {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit Cover();
 | 
			
		||||
 | 
			
		||||
@@ -156,8 +156,6 @@ class Cover : public EntityBase {
 | 
			
		||||
  void publish_state(bool save = true);
 | 
			
		||||
 | 
			
		||||
  virtual CoverTraits get_traits() = 0;
 | 
			
		||||
  void set_device_class(const std::string &device_class);
 | 
			
		||||
  std::string get_device_class();
 | 
			
		||||
 | 
			
		||||
  /// Helper method to check if the cover is fully open. Equivalent to comparing .position against 1.0
 | 
			
		||||
  bool is_fully_open() const;
 | 
			
		||||
@@ -172,7 +170,6 @@ class Cover : public EntityBase {
 | 
			
		||||
  optional<CoverRestoreState> restore_state_();
 | 
			
		||||
 | 
			
		||||
  CallbackManager<void()> state_callback_{};
 | 
			
		||||
  optional<std::string> device_class_override_{};
 | 
			
		||||
 | 
			
		||||
  ESPPreferenceObject rtc_;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -15,12 +15,15 @@ class CoverTraits {
 | 
			
		||||
  void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; }
 | 
			
		||||
  bool get_supports_toggle() const { return this->supports_toggle_; }
 | 
			
		||||
  void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; }
 | 
			
		||||
  bool get_supports_stop() const { return this->supports_stop_; }
 | 
			
		||||
  void set_supports_stop(bool supports_stop) { this->supports_stop_ = supports_stop; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool is_assumed_state_{false};
 | 
			
		||||
  bool supports_position_{false};
 | 
			
		||||
  bool supports_tilt_{false};
 | 
			
		||||
  bool supports_toggle_{false};
 | 
			
		||||
  bool supports_stop_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace cover
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ using namespace esphome::cover;
 | 
			
		||||
 | 
			
		||||
CoverTraits CurrentBasedCover::get_traits() {
 | 
			
		||||
  auto traits = CoverTraits();
 | 
			
		||||
  traits.set_supports_stop(true);
 | 
			
		||||
  traits.set_supports_position(true);
 | 
			
		||||
  traits.set_supports_toggle(true);
 | 
			
		||||
  traits.set_is_assumed_state(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -17,26 +17,29 @@ debug_ns = cg.esphome_ns.namespace("debug")
 | 
			
		||||
DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(DebugComponent),
 | 
			
		||||
        cv.Optional(CONF_DEVICE): cv.invalid(
 | 
			
		||||
            "The 'device' option has been moved to the 'debug' text_sensor component"
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_FREE): cv.invalid(
 | 
			
		||||
            "The 'free' option has been moved to the 'debug' sensor component"
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_BLOCK): cv.invalid(
 | 
			
		||||
            "The 'block' option has been moved to the 'debug' sensor component"
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_FRAGMENTATION): cv.invalid(
 | 
			
		||||
            "The 'fragmentation' option has been moved to the 'debug' sensor component"
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_LOOP_TIME): cv.invalid(
 | 
			
		||||
            "The 'loop_time' option has been moved to the 'debug' sensor component"
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.polling_component_schema("60s"))
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(DebugComponent),
 | 
			
		||||
            cv.Optional(CONF_DEVICE): cv.invalid(
 | 
			
		||||
                "The 'device' option has been moved to the 'debug' text_sensor component"
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_FREE): cv.invalid(
 | 
			
		||||
                "The 'free' option has been moved to the 'debug' sensor component"
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_BLOCK): cv.invalid(
 | 
			
		||||
                "The 'block' option has been moved to the 'debug' sensor component"
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_FRAGMENTATION): cv.invalid(
 | 
			
		||||
                "The 'fragmentation' option has been moved to the 'debug' sensor component"
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_LOOP_TIME): cv.invalid(
 | 
			
		||||
                "The 'loop_time' option has been moved to the 'debug' sensor component"
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.polling_component_schema("60s")),
 | 
			
		||||
    cv.only_on(["esp32", "esp8266"]),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
 
 | 
			
		||||
@@ -72,6 +72,7 @@ class DemoCover : public cover::Cover, public Component {
 | 
			
		||||
        traits.set_supports_tilt(true);
 | 
			
		||||
        break;
 | 
			
		||||
      case DemoCoverType::TYPE_4:
 | 
			
		||||
        traits.set_supports_stop(true);
 | 
			
		||||
        traits.set_is_assumed_state(true);
 | 
			
		||||
        traits.set_supports_tilt(true);
 | 
			
		||||
        break;
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,7 @@ DEVICE = {
 | 
			
		||||
 | 
			
		||||
NextAction = dfplayer_ns.class_("NextAction", automation.Action)
 | 
			
		||||
PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action)
 | 
			
		||||
PlayMp3Action = dfplayer_ns.class_("PlayMp3Action", automation.Action)
 | 
			
		||||
PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action)
 | 
			
		||||
PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action)
 | 
			
		||||
SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action)
 | 
			
		||||
@@ -113,6 +114,25 @@ async def dfplayer_previous_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "dfplayer.play_mp3",
 | 
			
		||||
    PlayMp3Action,
 | 
			
		||||
    cv.maybe_simple_value(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.use_id(DFPlayer),
 | 
			
		||||
            cv.Required(CONF_FILE): cv.templatable(cv.int_),
 | 
			
		||||
        },
 | 
			
		||||
        key=CONF_FILE,
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
async def dfplayer_play_mp3_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg)
 | 
			
		||||
    await cg.register_parented(var, config[CONF_ID])
 | 
			
		||||
    template_ = await cg.templatable(config[CONF_FILE], args, float)
 | 
			
		||||
    cg.add(var.set_file(template_))
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "dfplayer.play",
 | 
			
		||||
    PlayFileAction,
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,10 @@ namespace dfplayer {
 | 
			
		||||
static const char *const TAG = "dfplayer";
 | 
			
		||||
 | 
			
		||||
void DFPlayer::play_folder(uint16_t folder, uint16_t file) {
 | 
			
		||||
  if (folder < 100 && file < 256) {
 | 
			
		||||
  if (folder <= 10 && file <= 1000) {
 | 
			
		||||
    this->ack_set_is_playing_ = true;
 | 
			
		||||
    this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file);
 | 
			
		||||
  } else if (folder <= 10 && file <= 1000) {
 | 
			
		||||
  } else if (folder < 100 && file < 256) {
 | 
			
		||||
    this->ack_set_is_playing_ = true;
 | 
			
		||||
    this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file);
 | 
			
		||||
  } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,10 @@ class DFPlayer : public uart::UARTDevice, public Component {
 | 
			
		||||
    this->ack_set_is_playing_ = true;
 | 
			
		||||
    this->send_cmd_(0x02);
 | 
			
		||||
  }
 | 
			
		||||
  void play_mp3(uint16_t file) {
 | 
			
		||||
    this->ack_set_is_playing_ = true;
 | 
			
		||||
    this->send_cmd_(0x12, file);
 | 
			
		||||
  }
 | 
			
		||||
  void play_file(uint16_t file) {
 | 
			
		||||
    this->ack_set_is_playing_ = true;
 | 
			
		||||
    this->send_cmd_(0x03, file);
 | 
			
		||||
@@ -113,6 +117,16 @@ class DFPlayer : public uart::UARTDevice, public Component {
 | 
			
		||||
DFPLAYER_SIMPLE_ACTION(NextAction, next)
 | 
			
		||||
DFPLAYER_SIMPLE_ACTION(PreviousAction, previous)
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class PlayMp3Action : public Action<Ts...>, public Parented<DFPlayer> {
 | 
			
		||||
 public:
 | 
			
		||||
  TEMPLATABLE_VALUE(uint16_t, file)
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    auto file = this->file_.value(x...);
 | 
			
		||||
    this->parent_->play_mp3(file);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class PlayFileAction : public Action<Ts...>, public Parented<DFPlayer> {
 | 
			
		||||
 public:
 | 
			
		||||
  TEMPLATABLE_VALUE(uint16_t, file)
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ using namespace esphome::cover;
 | 
			
		||||
 | 
			
		||||
CoverTraits EndstopCover::get_traits() {
 | 
			
		||||
  auto traits = CoverTraits();
 | 
			
		||||
  traits.set_supports_stop(true);
 | 
			
		||||
  traits.set_supports_position(true);
 | 
			
		||||
  traits.set_supports_toggle(true);
 | 
			
		||||
  traits.set_is_assumed_state(false);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								esphome/components/es8388/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								esphome/components/es8388/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
es8388_ns = cg.esphome_ns.namespace("es8388")
 | 
			
		||||
ES8388Component = es8388_ns.class_("ES8388Component", cg.Component, i2c.I2CDevice)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema({cv.GenerateID(): cv.declare_id(ES8388Component)})
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x10))
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await i2c.register_i2c_device(var, config)
 | 
			
		||||
							
								
								
									
										75
									
								
								esphome/components/es8388/es8388_component.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								esphome/components/es8388/es8388_component.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
#include "es8388_component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
#include <soc/io_mux_reg.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace es8388 {
 | 
			
		||||
 | 
			
		||||
void ES8388Component::setup() {
 | 
			
		||||
  PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
 | 
			
		||||
  WRITE_PERI_REG(PIN_CTRL, READ_PERI_REG(PIN_CTRL) & 0xFFFFFFF0);
 | 
			
		||||
 | 
			
		||||
  // mute
 | 
			
		||||
  this->write_byte(0x19, 0x04);
 | 
			
		||||
  // powerup
 | 
			
		||||
  this->write_byte(0x01, 0x50);
 | 
			
		||||
  this->write_byte(0x02, 0x00);
 | 
			
		||||
  // worker mode
 | 
			
		||||
  this->write_byte(0x08, 0x00);
 | 
			
		||||
  // DAC powerdown
 | 
			
		||||
  this->write_byte(0x04, 0xC0);
 | 
			
		||||
  // vmidsel/500k ADC/DAC idem
 | 
			
		||||
  this->write_byte(0x00, 0x12);
 | 
			
		||||
 | 
			
		||||
  // i2s 16 bits
 | 
			
		||||
  this->write_byte(0x17, 0x18);
 | 
			
		||||
  // sample freq 256
 | 
			
		||||
  this->write_byte(0x18, 0x02);
 | 
			
		||||
  // LIN2/RIN2 for mixer
 | 
			
		||||
  this->write_byte(0x26, 0x00);
 | 
			
		||||
  // left DAC to left mixer
 | 
			
		||||
  this->write_byte(0x27, 0x90);
 | 
			
		||||
  // right DAC to right mixer
 | 
			
		||||
  this->write_byte(0x2A, 0x90);
 | 
			
		||||
  // DACLRC ADCLRC idem
 | 
			
		||||
  this->write_byte(0x2B, 0x80);
 | 
			
		||||
  this->write_byte(0x2D, 0x00);
 | 
			
		||||
  // DAC volume max
 | 
			
		||||
  this->write_byte(0x1B, 0x00);
 | 
			
		||||
  this->write_byte(0x1A, 0x00);
 | 
			
		||||
 | 
			
		||||
  // ADC poweroff
 | 
			
		||||
  this->write_byte(0x03, 0xFF);
 | 
			
		||||
  // ADC amp 24dB
 | 
			
		||||
  this->write_byte(0x09, 0x88);
 | 
			
		||||
  // LINPUT1/RINPUT1
 | 
			
		||||
  this->write_byte(0x0A, 0x00);
 | 
			
		||||
  // ADC mono left
 | 
			
		||||
  this->write_byte(0x0B, 0x02);
 | 
			
		||||
  // i2S 16b
 | 
			
		||||
  this->write_byte(0x0C, 0x0C);
 | 
			
		||||
  // MCLK 256
 | 
			
		||||
  this->write_byte(0x0D, 0x02);
 | 
			
		||||
  // ADC Volume
 | 
			
		||||
  this->write_byte(0x10, 0x00);
 | 
			
		||||
  this->write_byte(0x11, 0x00);
 | 
			
		||||
  // ALC OFF
 | 
			
		||||
  this->write_byte(0x03, 0x09);
 | 
			
		||||
  this->write_byte(0x2B, 0x80);
 | 
			
		||||
 | 
			
		||||
  this->write_byte(0x02, 0xF0);
 | 
			
		||||
  delay(1);
 | 
			
		||||
  this->write_byte(0x02, 0x00);
 | 
			
		||||
  // DAC power-up LOUT1/ROUT1 enabled
 | 
			
		||||
  this->write_byte(0x04, 0x30);
 | 
			
		||||
  this->write_byte(0x03, 0x00);
 | 
			
		||||
  // DAC volume max
 | 
			
		||||
  this->write_byte(0x2E, 0x1C);
 | 
			
		||||
  this->write_byte(0x2F, 0x1C);
 | 
			
		||||
  // unmute
 | 
			
		||||
  this->write_byte(0x19, 0x00);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace es8388
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										17
									
								
								esphome/components/es8388/es8388_component.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								esphome/components/es8388/es8388_component.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace es8388 {
 | 
			
		||||
 | 
			
		||||
class ES8388Component : public Component, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::LATE - 1; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace es8388
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -163,7 +163,7 @@ 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(5, 2, 0)
 | 
			
		||||
ARDUINO_PLATFORM_VERSION = cv.Version(5, 3, 0)
 | 
			
		||||
 | 
			
		||||
# The default/recommended esp-idf framework version
 | 
			
		||||
#  - https://github.com/espressif/esp-idf/releases
 | 
			
		||||
@@ -252,7 +252,7 @@ def _parse_platform_version(value):
 | 
			
		||||
    try:
 | 
			
		||||
        # if platform version is a valid version constraint, prefix the default package
 | 
			
		||||
        cv.platformio_version_constraint(value)
 | 
			
		||||
        return f"platformio/espressif32 @ {value}"
 | 
			
		||||
        return f"platformio/espressif32@{value}"
 | 
			
		||||
    except cv.Invalid:
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
@@ -367,12 +367,12 @@ async def to_code(config):
 | 
			
		||||
        cg.add_build_flag("-Wno-nonnull-compare")
 | 
			
		||||
        cg.add_platformio_option(
 | 
			
		||||
            "platform_packages",
 | 
			
		||||
            [f"platformio/framework-espidf @ {conf[CONF_SOURCE]}"],
 | 
			
		||||
            [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"]
 | 
			
		||||
            "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)
 | 
			
		||||
@@ -433,7 +433,7 @@ async def to_code(config):
 | 
			
		||||
        cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
 | 
			
		||||
        cg.add_platformio_option(
 | 
			
		||||
            "platform_packages",
 | 
			
		||||
            [f"platformio/framework-arduinoespressif32 @ {conf[CONF_SOURCE]}"],
 | 
			
		||||
            [f"platformio/framework-arduinoespressif32@{conf[CONF_SOURCE]}"],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        cg.add_platformio_option("board_build.partitions", "partitions.csv")
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
#include <freertos/task.h>
 | 
			
		||||
#include <esp_idf_version.h>
 | 
			
		||||
#include <esp_task_wdt.h>
 | 
			
		||||
#include <esp_timer.h>
 | 
			
		||||
#include <soc/rtc.h>
 | 
			
		||||
 | 
			
		||||
#if ESP_IDF_VERSION_MAJOR >= 4
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,9 @@ CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
CONFLICTS_WITH = ["esp32_ble_beacon"]
 | 
			
		||||
 | 
			
		||||
CONF_BLE_ID = "ble_id"
 | 
			
		||||
CONF_IO_CAPABILITY = "io_capability"
 | 
			
		||||
 | 
			
		||||
NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2]
 | 
			
		||||
NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
 | 
			
		||||
 | 
			
		||||
esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
 | 
			
		||||
ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component)
 | 
			
		||||
@@ -19,17 +20,28 @@ GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler")
 | 
			
		||||
GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler")
 | 
			
		||||
GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler")
 | 
			
		||||
 | 
			
		||||
IoCapability = esp32_ble_ns.enum("IoCapability")
 | 
			
		||||
IO_CAPABILITY = {
 | 
			
		||||
    "none": IoCapability.IO_CAP_NONE,
 | 
			
		||||
    "keyboard_only": IoCapability.IO_CAP_IN,
 | 
			
		||||
    "keyboard_display": IoCapability.IO_CAP_KBDISP,
 | 
			
		||||
    "display_only": IoCapability.IO_CAP_OUT,
 | 
			
		||||
    "display_yes_no": IoCapability.IO_CAP_IO,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(ESP32BLE),
 | 
			
		||||
        cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
 | 
			
		||||
            IO_CAPABILITY, lower=True
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_variant(_):
 | 
			
		||||
    variant = get_esp32_variant()
 | 
			
		||||
    if variant in NO_BLUTOOTH_VARIANTS:
 | 
			
		||||
    if variant in NO_BLUETOOTH_VARIANTS:
 | 
			
		||||
        raise cv.Invalid(f"{variant} does not support Bluetooth")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -39,6 +51,7 @@ FINAL_VALIDATE_SCHEMA = validate_variant
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY]))
 | 
			
		||||
 | 
			
		||||
    if CORE.using_esp_idf:
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
 | 
			
		||||
 
 | 
			
		||||
@@ -134,8 +134,7 @@ bool ESP32BLE::ble_setup_() {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;
 | 
			
		||||
  err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
 | 
			
		||||
  err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &(this->io_cap_), sizeof(uint8_t));
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "esp_ble_gap_set_security_param failed: %d", err);
 | 
			
		||||
    return false;
 | 
			
		||||
@@ -215,9 +214,31 @@ float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; }
 | 
			
		||||
void ESP32BLE::dump_config() {
 | 
			
		||||
  const uint8_t *mac_address = esp_bt_dev_get_address();
 | 
			
		||||
  if (mac_address) {
 | 
			
		||||
    const char *io_capability_s;
 | 
			
		||||
    switch (this->io_cap_) {
 | 
			
		||||
      case ESP_IO_CAP_OUT:
 | 
			
		||||
        io_capability_s = "display_only";
 | 
			
		||||
        break;
 | 
			
		||||
      case ESP_IO_CAP_IO:
 | 
			
		||||
        io_capability_s = "display_yes_no";
 | 
			
		||||
        break;
 | 
			
		||||
      case ESP_IO_CAP_IN:
 | 
			
		||||
        io_capability_s = "keyboard_only";
 | 
			
		||||
        break;
 | 
			
		||||
      case ESP_IO_CAP_NONE:
 | 
			
		||||
        io_capability_s = "none";
 | 
			
		||||
        break;
 | 
			
		||||
      case ESP_IO_CAP_KBDISP:
 | 
			
		||||
        io_capability_s = "keyboard_display";
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        io_capability_s = "invalid";
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "ESP32 BLE:");
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  MAC address: %02X:%02X:%02X:%02X:%02X:%02X", mac_address[0], mac_address[1], mac_address[2],
 | 
			
		||||
                  mac_address[3], mac_address[4], mac_address[5]);
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  IO Capability: %s", io_capability_s);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled");
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,14 @@ typedef struct {
 | 
			
		||||
  uint16_t mtu;
 | 
			
		||||
} conn_status_t;
 | 
			
		||||
 | 
			
		||||
enum IoCapability {
 | 
			
		||||
  IO_CAP_OUT = ESP_IO_CAP_OUT,
 | 
			
		||||
  IO_CAP_IO = ESP_IO_CAP_IO,
 | 
			
		||||
  IO_CAP_IN = ESP_IO_CAP_IN,
 | 
			
		||||
  IO_CAP_NONE = ESP_IO_CAP_NONE,
 | 
			
		||||
  IO_CAP_KBDISP = ESP_IO_CAP_KBDISP,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class GAPEventHandler {
 | 
			
		||||
 public:
 | 
			
		||||
  virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
 | 
			
		||||
@@ -44,6 +52,8 @@ class GATTsEventHandler {
 | 
			
		||||
 | 
			
		||||
class ESP32BLE : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; }
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
@@ -72,6 +82,7 @@ class ESP32BLE : public Component {
 | 
			
		||||
 | 
			
		||||
  Queue<BLEEvent> ble_events_;
 | 
			
		||||
  BLEAdvertising *advertising_;
 | 
			
		||||
  esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 
 | 
			
		||||
@@ -125,7 +125,7 @@ def _parse_platform_version(value):
 | 
			
		||||
    try:
 | 
			
		||||
        # if platform version is a valid version constraint, prefix the default package
 | 
			
		||||
        cv.platformio_version_constraint(value)
 | 
			
		||||
        return f"platformio/espressif8266 @ {value}"
 | 
			
		||||
        return f"platformio/espressif8266@{value}"
 | 
			
		||||
    except cv.Invalid:
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
@@ -181,7 +181,7 @@ async def to_code(config):
 | 
			
		||||
    cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
 | 
			
		||||
    cg.add_platformio_option(
 | 
			
		||||
        "platform_packages",
 | 
			
		||||
        [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"],
 | 
			
		||||
        [f"platformio/framework-arduinoespressif8266@{conf[CONF_SOURCE]}"],
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # Default for platformio is LWIP2_LOW_MEMORY with:
 | 
			
		||||
 
 | 
			
		||||
@@ -26,8 +26,10 @@ EthernetComponent::EthernetComponent() { global_eth_component = this; }
 | 
			
		||||
 | 
			
		||||
void EthernetComponent::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up Ethernet...");
 | 
			
		||||
  // Delay here to allow power to stabilise before Ethernet is initialised.
 | 
			
		||||
  delay(300);  // NOLINT
 | 
			
		||||
  if (esp_reset_reason() != ESP_RST_DEEPSLEEP) {
 | 
			
		||||
    // Delay here to allow power to stabilise before Ethernet is initialized.
 | 
			
		||||
    delay(300);  // NOLINT
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
  err = esp_netif_init();
 | 
			
		||||
@@ -52,30 +54,29 @@ void EthernetComponent::setup() {
 | 
			
		||||
 | 
			
		||||
  esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
 | 
			
		||||
 | 
			
		||||
  esp_eth_phy_t *phy;
 | 
			
		||||
  switch (this->type_) {
 | 
			
		||||
    case ETHERNET_TYPE_LAN8720: {
 | 
			
		||||
      phy = esp_eth_phy_new_lan87xx(&phy_config);
 | 
			
		||||
      this->phy_ = esp_eth_phy_new_lan87xx(&phy_config);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ETHERNET_TYPE_RTL8201: {
 | 
			
		||||
      phy = esp_eth_phy_new_rtl8201(&phy_config);
 | 
			
		||||
      this->phy_ = esp_eth_phy_new_rtl8201(&phy_config);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ETHERNET_TYPE_DP83848: {
 | 
			
		||||
      phy = esp_eth_phy_new_dp83848(&phy_config);
 | 
			
		||||
      this->phy_ = esp_eth_phy_new_dp83848(&phy_config);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ETHERNET_TYPE_IP101: {
 | 
			
		||||
      phy = esp_eth_phy_new_ip101(&phy_config);
 | 
			
		||||
      this->phy_ = esp_eth_phy_new_ip101(&phy_config);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ETHERNET_TYPE_JL1101: {
 | 
			
		||||
      phy = esp_eth_phy_new_jl1101(&phy_config);
 | 
			
		||||
      this->phy_ = esp_eth_phy_new_jl1101(&phy_config);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ETHERNET_TYPE_KSZ8081: {
 | 
			
		||||
      phy = esp_eth_phy_new_ksz8081(&phy_config);
 | 
			
		||||
      this->phy_ = esp_eth_phy_new_ksz8081(&phy_config);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    default: {
 | 
			
		||||
@@ -84,7 +85,7 @@ void EthernetComponent::setup() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
 | 
			
		||||
  esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, this->phy_);
 | 
			
		||||
  this->eth_handle_ = nullptr;
 | 
			
		||||
  err = esp_eth_driver_install(ð_config, &this->eth_handle_);
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "ETH driver install error");
 | 
			
		||||
@@ -356,6 +357,21 @@ std::string EthernetComponent::get_use_address() const {
 | 
			
		||||
 | 
			
		||||
void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; }
 | 
			
		||||
 | 
			
		||||
bool EthernetComponent::powerdown() {
 | 
			
		||||
  ESP_LOGI(TAG, "Powering down ethernet PHY");
 | 
			
		||||
  if (this->phy_ == nullptr) {
 | 
			
		||||
    ESP_LOGE(TAG, "Ethernet PHY not assigned");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  this->connected_ = false;
 | 
			
		||||
  this->started_ = false;
 | 
			
		||||
  if (this->phy_->pwrctl(this->phy_, false) != ESP_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "Error powering down ethernet PHY");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace ethernet
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,7 @@ class EthernetComponent : public Component {
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  bool can_proceed() override;
 | 
			
		||||
  void on_shutdown() override { powerdown(); }
 | 
			
		||||
  bool is_connected();
 | 
			
		||||
 | 
			
		||||
  void set_phy_addr(uint8_t phy_addr);
 | 
			
		||||
@@ -58,6 +59,7 @@ class EthernetComponent : public Component {
 | 
			
		||||
  network::IPAddress get_ip_address();
 | 
			
		||||
  std::string get_use_address() const;
 | 
			
		||||
  void set_use_address(const std::string &use_address);
 | 
			
		||||
  bool powerdown();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
 | 
			
		||||
@@ -82,6 +84,7 @@ class EthernetComponent : public Component {
 | 
			
		||||
  uint32_t connect_begin_;
 | 
			
		||||
  esp_netif_t *eth_netif_{nullptr};
 | 
			
		||||
  esp_eth_handle_t eth_handle_;
 | 
			
		||||
  esp_eth_phy_t *phy_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@ void FeedbackCover::setup() {
 | 
			
		||||
 | 
			
		||||
CoverTraits FeedbackCover::get_traits() {
 | 
			
		||||
  auto traits = CoverTraits();
 | 
			
		||||
  traits.set_supports_stop(true);
 | 
			
		||||
  traits.set_supports_position(true);
 | 
			
		||||
  traits.set_supports_toggle(true);
 | 
			
		||||
  traits.set_is_assumed_state(this->assumed_state_);
 | 
			
		||||
 
 | 
			
		||||
@@ -77,10 +77,12 @@ void FingerprintGrowComponent::finish_enrollment(uint8_t result) {
 | 
			
		||||
    this->enrollment_done_callback_.call(this->enrollment_slot_);
 | 
			
		||||
    this->get_fingerprint_count_();
 | 
			
		||||
  } else {
 | 
			
		||||
    this->enrollment_failed_callback_.call(this->enrollment_slot_);
 | 
			
		||||
    if (this->enrollment_slot_ != ENROLLMENT_SLOT_UNUSED) {
 | 
			
		||||
      this->enrollment_failed_callback_.call(this->enrollment_slot_);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  this->enrollment_image_ = 0;
 | 
			
		||||
  this->enrollment_slot_ = 0;
 | 
			
		||||
  this->enrollment_slot_ = ENROLLMENT_SLOT_UNUSED;
 | 
			
		||||
  if (this->enrolling_binary_sensor_ != nullptr) {
 | 
			
		||||
    this->enrolling_binary_sensor_->publish_state(false);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,8 @@ namespace fingerprint_grow {
 | 
			
		||||
 | 
			
		||||
static const uint16_t START_CODE = 0xEF01;
 | 
			
		||||
 | 
			
		||||
static const uint16_t ENROLLMENT_SLOT_UNUSED = 0xFFFF;
 | 
			
		||||
 | 
			
		||||
enum GrowPacketType {
 | 
			
		||||
  COMMAND = 0x01,
 | 
			
		||||
  DATA = 0x02,
 | 
			
		||||
@@ -158,7 +160,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
 | 
			
		||||
  uint32_t new_password_ = -1;
 | 
			
		||||
  GPIOPin *sensing_pin_{nullptr};
 | 
			
		||||
  uint8_t enrollment_image_ = 0;
 | 
			
		||||
  uint16_t enrollment_slot_ = 0;
 | 
			
		||||
  uint16_t enrollment_slot_ = ENROLLMENT_SLOT_UNUSED;
 | 
			
		||||
  uint8_t enrollment_buffers_ = 5;
 | 
			
		||||
  bool waiting_removal_ = false;
 | 
			
		||||
  uint32_t last_aura_led_control_ = 0;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								esphome/components/hyt271/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/hyt271/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
CODEOWNERS = ["@Philippe12"]
 | 
			
		||||
							
								
								
									
										52
									
								
								esphome/components/hyt271/hyt271.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								esphome/components/hyt271/hyt271.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
#include "hyt271.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace hyt271 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "hyt271";
 | 
			
		||||
 | 
			
		||||
static const uint8_t HYT271_ADDRESS = 0x28;
 | 
			
		||||
 | 
			
		||||
void HYT271Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "HYT271:");
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
  LOG_SENSOR("  ", "Temperature", this->temperature_);
 | 
			
		||||
  LOG_SENSOR("  ", "Humidity", this->humidity_);
 | 
			
		||||
}
 | 
			
		||||
void HYT271Component::update() {
 | 
			
		||||
  uint8_t raw_data[4];
 | 
			
		||||
 | 
			
		||||
  if (this->write(&raw_data[0], 0) != i2c::ERROR_OK) {
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    ESP_LOGE(TAG, "Communication with HYT271 failed! => Ask new values");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->set_timeout("wait_convert", 50, [this]() {
 | 
			
		||||
    uint8_t raw_data[4];
 | 
			
		||||
    if (this->read(raw_data, 4) != i2c::ERROR_OK) {
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
      ESP_LOGE(TAG, "Communication with HYT271 failed! => Read values");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    uint16_t raw_temperature = ((raw_data[2] << 8) | raw_data[3]) >> 2;
 | 
			
		||||
    uint16_t raw_humidity = ((raw_data[0] & 0x3F) << 8) | raw_data[1];
 | 
			
		||||
 | 
			
		||||
    float temperature = ((float(raw_temperature)) * (165.0f / 16383.0f)) - 40.0f;
 | 
			
		||||
    float humidity = (float(raw_humidity)) * (100.0f / 16383.0f);
 | 
			
		||||
 | 
			
		||||
    ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity);
 | 
			
		||||
 | 
			
		||||
    if (this->temperature_ != nullptr)
 | 
			
		||||
      this->temperature_->publish_state(temperature);
 | 
			
		||||
    if (this->humidity_ != nullptr)
 | 
			
		||||
      this->humidity_->publish_state(humidity);
 | 
			
		||||
    this->status_clear_warning();
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
float HYT271Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
}  // namespace hyt271
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										27
									
								
								esphome/components/hyt271/hyt271.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/hyt271/hyt271.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace hyt271 {
 | 
			
		||||
 | 
			
		||||
class HYT271Component : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
 | 
			
		||||
  void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
 | 
			
		||||
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  /// Update the sensor values (temperature+humidity).
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  sensor::Sensor *temperature_{nullptr};
 | 
			
		||||
  sensor::Sensor *humidity_{nullptr};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace hyt271
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										56
									
								
								esphome/components/hyt271/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								esphome/components/hyt271/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_HUMIDITY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
hyt271_ns = cg.esphome_ns.namespace("hyt271")
 | 
			
		||||
HYT271Component = hyt271_ns.class_(
 | 
			
		||||
    "HYT271Component", cg.PollingComponent, i2c.I2CDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(HYT271Component),
 | 
			
		||||
            cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_CELSIUS,
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Required(CONF_HUMIDITY): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_PERCENT,
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                device_class=DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x28))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await i2c.register_i2c_device(var, config)
 | 
			
		||||
 | 
			
		||||
    if CONF_TEMPERATURE in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
 | 
			
		||||
        cg.add(var.set_temperature(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_HUMIDITY in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_HUMIDITY])
 | 
			
		||||
        cg.add(var.set_humidity(sens))
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
#include "i2c_bus_arduino.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
@@ -227,10 +228,14 @@ void ArduinoI2CBus::recover_() {
 | 
			
		||||
    // When SCL is kept LOW at this point, we might be looking at a device
 | 
			
		||||
    // that applies clock stretching. Wait for the release of the SCL line,
 | 
			
		||||
    // but not forever. There is no specification for the maximum allowed
 | 
			
		||||
    // time. We'll stick to 500ms here.
 | 
			
		||||
    auto wait = 20;
 | 
			
		||||
    // time. We yield and reset the WDT, so as to avoid triggering reset.
 | 
			
		||||
    // No point in trying to recover the bus by forcing a uC reset. Bus
 | 
			
		||||
    // should recover in a few ms or less else not likely to recovery at
 | 
			
		||||
    // all.
 | 
			
		||||
    auto wait = 250;
 | 
			
		||||
    while (wait-- && digitalRead(scl_pin_) == LOW) {  // NOLINT
 | 
			
		||||
      delay(25);
 | 
			
		||||
      App.feed_wdt();
 | 
			
		||||
      delayMicroseconds(half_period_usec * 2);
 | 
			
		||||
    }
 | 
			
		||||
    if (digitalRead(scl_pin_) == LOW) {  // NOLINT
 | 
			
		||||
      ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle");
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
 | 
			
		||||
@@ -273,10 +274,14 @@ void IDFI2CBus::recover_() {
 | 
			
		||||
    // When SCL is kept LOW at this point, we might be looking at a device
 | 
			
		||||
    // that applies clock stretching. Wait for the release of the SCL line,
 | 
			
		||||
    // but not forever. There is no specification for the maximum allowed
 | 
			
		||||
    // time. We'll stick to 500ms here.
 | 
			
		||||
    auto wait = 20;
 | 
			
		||||
    // time. We yield and reset the WDT, so as to avoid triggering reset.
 | 
			
		||||
    // No point in trying to recover the bus by forcing a uC reset. Bus
 | 
			
		||||
    // should recover in a few ms or less else not likely to recovery at
 | 
			
		||||
    // all.
 | 
			
		||||
    auto wait = 250;
 | 
			
		||||
    while (wait-- && gpio_get_level(scl_pin) == 0) {
 | 
			
		||||
      delay(25);
 | 
			
		||||
      App.feed_wdt();
 | 
			
		||||
      delayMicroseconds(half_period_usec * 2);
 | 
			
		||||
    }
 | 
			
		||||
    if (gpio_get_level(scl_pin) == 0) {
 | 
			
		||||
      ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle");
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										148
									
								
								esphome/components/max6956/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								esphome/components/max6956/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,148 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins, automation
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_INVERTED,
 | 
			
		||||
    CONF_INPUT,
 | 
			
		||||
    CONF_OUTPUT,
 | 
			
		||||
    CONF_PULLUP,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@looping40"]
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
CONF_BRIGHTNESS_MODE = "brightness_mode"
 | 
			
		||||
CONF_BRIGHTNESS_GLOBAL = "brightness_global"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
max6956_ns = cg.esphome_ns.namespace("max6956")
 | 
			
		||||
 | 
			
		||||
MAX6956 = max6956_ns.class_("MAX6956", cg.Component, i2c.I2CDevice)
 | 
			
		||||
MAX6956GPIOPin = max6956_ns.class_("MAX6956GPIOPin", cg.GPIOPin)
 | 
			
		||||
 | 
			
		||||
# Actions
 | 
			
		||||
SetCurrentGlobalAction = max6956_ns.class_("SetCurrentGlobalAction", automation.Action)
 | 
			
		||||
SetCurrentModeAction = max6956_ns.class_("SetCurrentModeAction", automation.Action)
 | 
			
		||||
 | 
			
		||||
MAX6956_CURRENTMODE = max6956_ns.enum("MAX6956CURRENTMODE")
 | 
			
		||||
CURRENT_MODES = {
 | 
			
		||||
    "global": MAX6956_CURRENTMODE.GLOBAL,
 | 
			
		||||
    "segment": MAX6956_CURRENTMODE.SEGMENT,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_ID): cv.declare_id(MAX6956),
 | 
			
		||||
            cv.Optional(CONF_BRIGHTNESS_GLOBAL, default="0"): cv.int_range(
 | 
			
		||||
                min=0, max=15
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_BRIGHTNESS_MODE, default="global"): cv.enum(
 | 
			
		||||
                CURRENT_MODES, lower=True
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x40))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await i2c.register_i2c_device(var, config)
 | 
			
		||||
    cg.add(var.set_brightness_mode(config[CONF_BRIGHTNESS_MODE]))
 | 
			
		||||
    cg.add(var.set_brightness_global(config[CONF_BRIGHTNESS_GLOBAL]))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_mode(value):
 | 
			
		||||
    if not (value[CONF_INPUT] or value[CONF_OUTPUT]):
 | 
			
		||||
        raise cv.Invalid("Mode must be either input or output")
 | 
			
		||||
    if value[CONF_INPUT] and value[CONF_OUTPUT]:
 | 
			
		||||
        raise cv.Invalid("Mode must be either input or output")
 | 
			
		||||
    if value[CONF_PULLUP] and not value[CONF_INPUT]:
 | 
			
		||||
        raise cv.Invalid("Pullup only available with input")
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONF_MAX6956 = "max6956"
 | 
			
		||||
 | 
			
		||||
MAX6956_PIN_SCHEMA = cv.All(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(MAX6956GPIOPin),
 | 
			
		||||
        cv.Required(CONF_MAX6956): cv.use_id(MAX6956),
 | 
			
		||||
        cv.Required(CONF_NUMBER): cv.int_range(min=4, max=31),
 | 
			
		||||
        cv.Optional(CONF_MODE, default={}): cv.All(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(CONF_INPUT, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_PULLUP, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
 | 
			
		||||
            },
 | 
			
		||||
            validate_mode,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_INVERTED, default=False): cv.boolean,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pins.PIN_SCHEMA_REGISTRY.register(CONF_MAX6956, MAX6956_PIN_SCHEMA)
 | 
			
		||||
async def max6956_pin_to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_MAX6956])
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_parent(parent))
 | 
			
		||||
 | 
			
		||||
    num = config[CONF_NUMBER]
 | 
			
		||||
    cg.add(var.set_pin(num))
 | 
			
		||||
    cg.add(var.set_inverted(config[CONF_INVERTED]))
 | 
			
		||||
    cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "max6956.set_brightness_global",
 | 
			
		||||
    SetCurrentGlobalAction,
 | 
			
		||||
    cv.maybe_simple_value(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(CONF_ID): cv.use_id(MAX6956),
 | 
			
		||||
            cv.Required(CONF_BRIGHTNESS_GLOBAL): cv.templatable(
 | 
			
		||||
                cv.int_range(min=0, max=15)
 | 
			
		||||
            ),
 | 
			
		||||
        },
 | 
			
		||||
        key=CONF_BRIGHTNESS_GLOBAL,
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
async def max6956_set_brightness_global_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
    template_ = await cg.templatable(config[CONF_BRIGHTNESS_GLOBAL], args, float)
 | 
			
		||||
    cg.add(var.set_brightness_global(template_))
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "max6956.set_brightness_mode",
 | 
			
		||||
    SetCurrentModeAction,
 | 
			
		||||
    cv.maybe_simple_value(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_ID): cv.use_id(MAX6956),
 | 
			
		||||
            cv.Required(CONF_BRIGHTNESS_MODE): cv.templatable(
 | 
			
		||||
                cv.enum(CURRENT_MODES, lower=True)
 | 
			
		||||
            ),
 | 
			
		||||
        },
 | 
			
		||||
        key=CONF_BRIGHTNESS_MODE,
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
async def max6956_set_brightness_mode_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
    template_ = await cg.templatable(config[CONF_BRIGHTNESS_MODE], args, float)
 | 
			
		||||
    cg.add(var.set_brightness_mode(template_))
 | 
			
		||||
    return var
 | 
			
		||||
							
								
								
									
										40
									
								
								esphome/components/max6956/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								esphome/components/max6956/automation.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/components/max6956/max6956.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace max6956 {
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class SetCurrentGlobalAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  SetCurrentGlobalAction(MAX6956 *max6956) : max6956_(max6956) {}
 | 
			
		||||
 | 
			
		||||
  TEMPLATABLE_VALUE(uint8_t, brightness_global)
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    this->max6956_->set_brightness_global(this->brightness_global_.value(x...));
 | 
			
		||||
    this->max6956_->write_brightness_global();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  MAX6956 *max6956_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class SetCurrentModeAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  SetCurrentModeAction(MAX6956 *max6956) : max6956_(max6956) {}
 | 
			
		||||
 | 
			
		||||
  TEMPLATABLE_VALUE(max6956::MAX6956CURRENTMODE, brightness_mode)
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    this->max6956_->set_brightness_mode(this->brightness_mode_.value(x...));
 | 
			
		||||
    this->max6956_->write_brightness_mode();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  MAX6956 *max6956_;
 | 
			
		||||
};
 | 
			
		||||
}  // namespace max6956
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										170
									
								
								esphome/components/max6956/max6956.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								esphome/components/max6956/max6956.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,170 @@
 | 
			
		||||
#include "max6956.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace max6956 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "max6956";
 | 
			
		||||
 | 
			
		||||
/// Masks for MAX6956 Configuration register
 | 
			
		||||
const uint32_t MASK_TRANSITION_DETECTION = 0x80;
 | 
			
		||||
const uint32_t MASK_INDIVIDUAL_CURRENT = 0x40;
 | 
			
		||||
const uint32_t MASK_NORMAL_OPERATION = 0x01;
 | 
			
		||||
 | 
			
		||||
const uint32_t MASK_1PORT_VALUE = 0x03;
 | 
			
		||||
const uint32_t MASK_PORT_CONFIG = 0x03;
 | 
			
		||||
const uint8_t MASK_CONFIG_CURRENT = 0x40;
 | 
			
		||||
const uint8_t MASK_CURRENT_PIN = 0x0F;
 | 
			
		||||
 | 
			
		||||
/**************************************
 | 
			
		||||
 *    MAX6956                         *
 | 
			
		||||
 **************************************/
 | 
			
		||||
void MAX6956::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up MAX6956...");
 | 
			
		||||
  uint8_t configuration;
 | 
			
		||||
  if (!this->read_reg_(MAX6956_CONFIGURATION, &configuration)) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  write_brightness_global();
 | 
			
		||||
  write_brightness_mode();
 | 
			
		||||
 | 
			
		||||
  /** TO DO : read transition detection in yaml
 | 
			
		||||
      TO DO : read indivdual current in yaml **/
 | 
			
		||||
  this->read_reg_(MAX6956_CONFIGURATION, &configuration);
 | 
			
		||||
  ESP_LOGD(TAG, "Initial reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration);
 | 
			
		||||
  configuration = configuration | MASK_NORMAL_OPERATION;
 | 
			
		||||
  this->write_reg_(MAX6956_CONFIGURATION, configuration);
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Enabling normal operation");
 | 
			
		||||
  ESP_LOGD(TAG, "setup reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MAX6956::digital_read(uint8_t pin) {
 | 
			
		||||
  uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin;
 | 
			
		||||
  uint8_t value = 0;
 | 
			
		||||
  this->read_reg_(reg_addr, &value);
 | 
			
		||||
  return (value & MASK_1PORT_VALUE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MAX6956::digital_write(uint8_t pin, bool value) {
 | 
			
		||||
  uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin;
 | 
			
		||||
  this->write_reg_(reg_addr, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MAX6956::pin_mode(uint8_t pin, gpio::Flags flags) {
 | 
			
		||||
  uint8_t reg_addr = MAX6956_PORT_CONFIG_START + (pin - MAX6956_MIN) / 4;
 | 
			
		||||
  uint8_t config = 0;
 | 
			
		||||
  uint8_t shift = 2 * (pin % 4);
 | 
			
		||||
  MAX6956GPIOMode mode = MAX6956_INPUT;
 | 
			
		||||
 | 
			
		||||
  if (flags == gpio::FLAG_INPUT) {
 | 
			
		||||
    mode = MAX6956GPIOMode::MAX6956_INPUT;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
 | 
			
		||||
    mode = MAX6956GPIOMode::MAX6956_INPUT_PULLUP;
 | 
			
		||||
  } else if (flags == gpio::FLAG_OUTPUT) {
 | 
			
		||||
    mode = MAX6956GPIOMode::MAX6956_OUTPUT;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->read_reg_(reg_addr, &config);
 | 
			
		||||
  config &= ~(MASK_PORT_CONFIG << shift);
 | 
			
		||||
  config |= (mode << shift);
 | 
			
		||||
  this->write_reg_(reg_addr, config);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MAX6956::pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags) {
 | 
			
		||||
  uint8_t reg_addr = MAX6956_PORT_CONFIG_START + (pin - MAX6956_MIN) / 4;
 | 
			
		||||
  uint8_t config = 0;
 | 
			
		||||
  uint8_t shift = 2 * (pin % 4);
 | 
			
		||||
  MAX6956GPIOMode mode = MAX6956GPIOMode::MAX6956_LED;
 | 
			
		||||
 | 
			
		||||
  if (flags == max6956::FLAG_LED) {
 | 
			
		||||
    mode = MAX6956GPIOMode::MAX6956_LED;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->read_reg_(reg_addr, &config);
 | 
			
		||||
  config &= ~(MASK_PORT_CONFIG << shift);
 | 
			
		||||
  config |= (mode << shift);
 | 
			
		||||
  this->write_reg_(reg_addr, config);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MAX6956::set_brightness_global(uint8_t current) {
 | 
			
		||||
  if (current > 15) {
 | 
			
		||||
    ESP_LOGE(TAG, "Global brightness out off range (%u)", current);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  global_brightness_ = current;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MAX6956::write_brightness_global() { this->write_reg_(MAX6956_GLOBAL_CURRENT, global_brightness_); }
 | 
			
		||||
 | 
			
		||||
void MAX6956::set_brightness_mode(max6956::MAX6956CURRENTMODE brightness_mode) { brightness_mode_ = brightness_mode; };
 | 
			
		||||
 | 
			
		||||
void MAX6956::write_brightness_mode() {
 | 
			
		||||
  uint8_t reg_addr = MAX6956_CONFIGURATION;
 | 
			
		||||
  uint8_t config = 0;
 | 
			
		||||
 | 
			
		||||
  this->read_reg_(reg_addr, &config);
 | 
			
		||||
  config &= ~MASK_CONFIG_CURRENT;
 | 
			
		||||
  config |= brightness_mode_ << 6;
 | 
			
		||||
  this->write_reg_(reg_addr, config);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MAX6956::set_pin_brightness(uint8_t pin, float brightness) {
 | 
			
		||||
  uint8_t reg_addr = MAX6956_CURRENT_START + (pin - MAX6956_MIN) / 2;
 | 
			
		||||
  uint8_t config = 0;
 | 
			
		||||
  uint8_t shift = 4 * (pin % 2);
 | 
			
		||||
  uint8_t bright = roundf(brightness * 15);
 | 
			
		||||
 | 
			
		||||
  if (prev_bright_[pin - MAX6956_MIN] == bright)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  prev_bright_[pin - MAX6956_MIN] = bright;
 | 
			
		||||
 | 
			
		||||
  this->read_reg_(reg_addr, &config);
 | 
			
		||||
  config &= ~(MASK_CURRENT_PIN << shift);
 | 
			
		||||
  config |= (bright << shift);
 | 
			
		||||
  this->write_reg_(reg_addr, config);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MAX6956::read_reg_(uint8_t reg, uint8_t *value) {
 | 
			
		||||
  if (this->is_failed())
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  return this->read_byte(reg, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MAX6956::write_reg_(uint8_t reg, uint8_t value) {
 | 
			
		||||
  if (this->is_failed())
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  return this->write_byte(reg, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MAX6956::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "MAX6956");
 | 
			
		||||
 | 
			
		||||
  if (brightness_mode_ == MAX6956CURRENTMODE::GLOBAL) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "current mode: global");
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "global brightness: %u", global_brightness_);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "current mode: segment");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**************************************
 | 
			
		||||
 *    MAX6956GPIOPin                  *
 | 
			
		||||
 **************************************/
 | 
			
		||||
void MAX6956GPIOPin::setup() { pin_mode(flags_); }
 | 
			
		||||
void MAX6956GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
 | 
			
		||||
bool MAX6956GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
 | 
			
		||||
void MAX6956GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
 | 
			
		||||
std::string MAX6956GPIOPin::dump_summary() const {
 | 
			
		||||
  char buffer[32];
 | 
			
		||||
  snprintf(buffer, sizeof(buffer), "%u via Max6956", pin_);
 | 
			
		||||
  return buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace max6956
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										94
									
								
								esphome/components/max6956/max6956.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								esphome/components/max6956/max6956.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace max6956 {
 | 
			
		||||
 | 
			
		||||
/// Modes for MAX6956 pins
 | 
			
		||||
enum MAX6956GPIOMode : uint8_t {
 | 
			
		||||
  MAX6956_LED = 0x00,
 | 
			
		||||
  MAX6956_OUTPUT = 0x01,
 | 
			
		||||
  MAX6956_INPUT = 0x02,
 | 
			
		||||
  MAX6956_INPUT_PULLUP = 0x03
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Range for MAX6956 pins
 | 
			
		||||
enum MAX6956GPIORange : uint8_t {
 | 
			
		||||
  MAX6956_MIN = 4,
 | 
			
		||||
  MAX6956_MAX = 31,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum MAX6956GPIORegisters {
 | 
			
		||||
  MAX6956_GLOBAL_CURRENT = 0x02,
 | 
			
		||||
  MAX6956_CONFIGURATION = 0x04,
 | 
			
		||||
  MAX6956_TRANSITION_DETECT_MASK = 0x06,
 | 
			
		||||
  MAX6956_DISPLAY_TEST = 0x07,
 | 
			
		||||
  MAX6956_PORT_CONFIG_START = 0x09,   // Port Configuration P7, P6, P5, P4
 | 
			
		||||
  MAX6956_CURRENT_START = 0x12,       // Current054
 | 
			
		||||
  MAX6956_1PORT_VALUE_START = 0x20,   // Port 0 only (virtual port, no action)
 | 
			
		||||
  MAX6956_8PORTS_VALUE_START = 0x44,  // 8 ports 4–11 (data bits D0–D7)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum MAX6956GPIOFlag { FLAG_LED = 0x20 };
 | 
			
		||||
 | 
			
		||||
enum MAX6956CURRENTMODE { GLOBAL = 0x00, SEGMENT = 0x01 };
 | 
			
		||||
 | 
			
		||||
class MAX6956 : public Component, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  MAX6956() = default;
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
 | 
			
		||||
  bool digital_read(uint8_t pin);
 | 
			
		||||
  void digital_write(uint8_t pin, bool value);
 | 
			
		||||
  void pin_mode(uint8_t pin, gpio::Flags flags);
 | 
			
		||||
  void pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags);
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::HARDWARE; }
 | 
			
		||||
 | 
			
		||||
  void set_brightness_global(uint8_t current);
 | 
			
		||||
  void set_brightness_mode(max6956::MAX6956CURRENTMODE brightness_mode);
 | 
			
		||||
  void set_pin_brightness(uint8_t pin, float brightness);
 | 
			
		||||
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  void write_brightness_global();
 | 
			
		||||
  void write_brightness_mode();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  // read a given register
 | 
			
		||||
  bool read_reg_(uint8_t reg, uint8_t *value);
 | 
			
		||||
  // write a value to a given register
 | 
			
		||||
  bool write_reg_(uint8_t reg, uint8_t value);
 | 
			
		||||
  max6956::MAX6956CURRENTMODE brightness_mode_;
 | 
			
		||||
  uint8_t global_brightness_;
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  int8_t prev_bright_[28] = {0};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MAX6956GPIOPin : public GPIOPin {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void pin_mode(gpio::Flags flags) override;
 | 
			
		||||
  bool digital_read() override;
 | 
			
		||||
  void digital_write(bool value) override;
 | 
			
		||||
  std::string dump_summary() const override;
 | 
			
		||||
 | 
			
		||||
  void set_parent(MAX6956 *parent) { parent_ = parent; }
 | 
			
		||||
  void set_pin(uint8_t pin) { pin_ = pin; }
 | 
			
		||||
  void set_inverted(bool inverted) { inverted_ = inverted; }
 | 
			
		||||
  void set_flags(gpio::Flags flags) { flags_ = flags; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  MAX6956 *parent_;
 | 
			
		||||
  uint8_t pin_;
 | 
			
		||||
  bool inverted_;
 | 
			
		||||
  gpio::Flags flags_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace max6956
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										28
									
								
								esphome/components/max6956/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/max6956/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import output
 | 
			
		||||
from esphome.const import CONF_PIN, CONF_ID
 | 
			
		||||
from .. import MAX6956, max6956_ns, CONF_MAX6956
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["max6956"]
 | 
			
		||||
 | 
			
		||||
MAX6956LedChannel = max6956_ns.class_(
 | 
			
		||||
    "MAX6956LedChannel", output.FloatOutput, cg.Component
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.declare_id(MAX6956LedChannel),
 | 
			
		||||
        cv.GenerateID(CONF_MAX6956): cv.use_id(MAX6956),
 | 
			
		||||
        cv.Required(CONF_PIN): cv.int_range(min=4, max=31),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_MAX6956])
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await output.register_output(var, config)
 | 
			
		||||
    cg.add(var.set_pin(config[CONF_PIN]))
 | 
			
		||||
    cg.add(var.set_parent(parent))
 | 
			
		||||
							
								
								
									
										26
									
								
								esphome/components/max6956/output/max6956_led_output.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/max6956/output/max6956_led_output.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
#include "max6956_led_output.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace max6956 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "max6956_led_channel";
 | 
			
		||||
 | 
			
		||||
void MAX6956LedChannel::write_state(float state) { this->parent_->set_pin_brightness(this->pin_, state); }
 | 
			
		||||
 | 
			
		||||
void MAX6956LedChannel::write_state(bool state) { this->parent_->digital_write(this->pin_, state); }
 | 
			
		||||
 | 
			
		||||
void MAX6956LedChannel::setup() {
 | 
			
		||||
  this->parent_->pin_mode(this->pin_, max6956::FLAG_LED);
 | 
			
		||||
  this->turn_off();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MAX6956LedChannel::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "MAX6956 current:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  MAX6956 pin: %d", this->pin_);
 | 
			
		||||
  LOG_FLOAT_OUTPUT(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace max6956
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										28
									
								
								esphome/components/max6956/output/max6956_led_output.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/max6956/output/max6956_led_output.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/max6956/max6956.h"
 | 
			
		||||
#include "esphome/components/output/float_output.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace max6956 {
 | 
			
		||||
 | 
			
		||||
class MAX6956;
 | 
			
		||||
 | 
			
		||||
class MAX6956LedChannel : public output::FloatOutput, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_parent(MAX6956 *parent) { this->parent_ = parent; }
 | 
			
		||||
  void set_pin(uint8_t pin) { pin_ = pin; }
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::HARDWARE; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void write_state(float state) override;
 | 
			
		||||
  void write_state(bool state) override;
 | 
			
		||||
 | 
			
		||||
  MAX6956 *parent_;
 | 
			
		||||
  uint8_t pin_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace max6956
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -93,7 +93,7 @@ async def to_code(config):
 | 
			
		||||
    cg.add(var.set_scroll(config[CONF_SCROLL_ENABLE]))
 | 
			
		||||
    cg.add(var.set_scroll_mode(config[CONF_SCROLL_MODE]))
 | 
			
		||||
    cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE]))
 | 
			
		||||
    cg.add(var.set_flip_x([CONF_FLIP_X]))
 | 
			
		||||
    cg.add(var.set_flip_x(config[CONF_FLIP_X]))
 | 
			
		||||
 | 
			
		||||
    if CONF_LAMBDA in config:
 | 
			
		||||
        lambda_ = await cg.process_lambda(
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,13 @@ from esphome.const import (
 | 
			
		||||
    CONF_PROTOCOL,
 | 
			
		||||
    CONF_SERVICES,
 | 
			
		||||
    CONF_SERVICE,
 | 
			
		||||
    KEY_CORE,
 | 
			
		||||
    KEY_FRAMEWORK_VERSION,
 | 
			
		||||
)
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
from esphome.components.esp32 import add_idf_component
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
DEPENDENCIES = ["network"]
 | 
			
		||||
@@ -79,6 +82,16 @@ async def to_code(config):
 | 
			
		||||
        elif CORE.is_rp2040:
 | 
			
		||||
            cg.add_library("LEAmDNS", None)
 | 
			
		||||
 | 
			
		||||
    if CORE.using_esp_idf and CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(
 | 
			
		||||
        5, 0, 0
 | 
			
		||||
    ):
 | 
			
		||||
        add_idf_component(
 | 
			
		||||
            "mdns",
 | 
			
		||||
            "https://github.com/espressif/esp-protocols.git",
 | 
			
		||||
            "mdns-v1.0.9",
 | 
			
		||||
            "components/mdns",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if config[CONF_DISABLED]:
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								esphome/components/mlx90614/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/mlx90614/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										122
									
								
								esphome/components/mlx90614/mlx90614.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								esphome/components/mlx90614/mlx90614.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
			
		||||
#include "mlx90614.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace mlx90614 {
 | 
			
		||||
 | 
			
		||||
static const uint8_t MLX90614_RAW_IR_1 = 0x04;
 | 
			
		||||
static const uint8_t MLX90614_RAW_IR_2 = 0x05;
 | 
			
		||||
static const uint8_t MLX90614_TEMPERATURE_AMBIENT = 0x06;
 | 
			
		||||
static const uint8_t MLX90614_TEMPERATURE_OBJECT_1 = 0x07;
 | 
			
		||||
static const uint8_t MLX90614_TEMPERATURE_OBJECT_2 = 0x08;
 | 
			
		||||
 | 
			
		||||
static const uint8_t MLX90614_TOMAX = 0x20;
 | 
			
		||||
static const uint8_t MLX90614_TOMIN = 0x21;
 | 
			
		||||
static const uint8_t MLX90614_PWMCTRL = 0x22;
 | 
			
		||||
static const uint8_t MLX90614_TARANGE = 0x23;
 | 
			
		||||
static const uint8_t MLX90614_EMISSIVITY = 0x24;
 | 
			
		||||
static const uint8_t MLX90614_CONFIG = 0x25;
 | 
			
		||||
static const uint8_t MLX90614_ADDR = 0x2E;
 | 
			
		||||
static const uint8_t MLX90614_ID1 = 0x3C;
 | 
			
		||||
static const uint8_t MLX90614_ID2 = 0x3D;
 | 
			
		||||
static const uint8_t MLX90614_ID3 = 0x3E;
 | 
			
		||||
static const uint8_t MLX90614_ID4 = 0x3F;
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "mlx90614";
 | 
			
		||||
 | 
			
		||||
void MLX90614Component::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up MLX90614...");
 | 
			
		||||
  if (!this->write_emissivity_()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Communication with MLX90614 failed!");
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MLX90614Component::write_emissivity_() {
 | 
			
		||||
  if (std::isnan(this->emissivity_))
 | 
			
		||||
    return true;
 | 
			
		||||
  uint16_t value = (uint16_t) (this->emissivity_ * 65535);
 | 
			
		||||
  if (!this->write_bytes_(MLX90614_EMISSIVITY, 0)) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  delay(10);
 | 
			
		||||
  if (!this->write_bytes_(MLX90614_EMISSIVITY, value)) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  delay(10);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t MLX90614Component::crc8_pec_(const uint8_t *data, uint8_t len) {
 | 
			
		||||
  uint8_t crc = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < len; i++) {
 | 
			
		||||
    uint8_t in = data[i];
 | 
			
		||||
    for (uint8_t j = 0; j < 8; j++) {
 | 
			
		||||
      bool carry = (crc ^ in) & 0x80;
 | 
			
		||||
      crc <<= 1;
 | 
			
		||||
      if (carry)
 | 
			
		||||
        crc ^= 0x07;
 | 
			
		||||
      in <<= 1;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return crc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MLX90614Component::write_bytes_(uint8_t reg, uint16_t data) {
 | 
			
		||||
  uint8_t buf[5];
 | 
			
		||||
  buf[0] = this->address_ << 1;
 | 
			
		||||
  buf[1] = reg;
 | 
			
		||||
  buf[2] = data & 0xFF;
 | 
			
		||||
  buf[3] = data >> 8;
 | 
			
		||||
  buf[4] = this->crc8_pec_(buf, 4);
 | 
			
		||||
  return this->write_bytes(reg, buf + 2, 3);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MLX90614Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "MLX90614:");
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Communication with MLX90614 failed!");
 | 
			
		||||
  }
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
  LOG_SENSOR("  ", "Ambient", this->ambient_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Object", this->object_sensor_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float MLX90614Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
void MLX90614Component::update() {
 | 
			
		||||
  uint8_t emissivity[3];
 | 
			
		||||
  if (this->read_register(MLX90614_EMISSIVITY, emissivity, 3, false) != i2c::ERROR_OK) {
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  uint8_t raw_object[3];
 | 
			
		||||
  if (this->read_register(MLX90614_TEMPERATURE_OBJECT_1, raw_object, 3, false) != i2c::ERROR_OK) {
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t raw_ambient[3];
 | 
			
		||||
  if (this->read_register(MLX90614_TEMPERATURE_AMBIENT, raw_ambient, 3, false) != i2c::ERROR_OK) {
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  float ambient = raw_ambient[1] & 0x80 ? NAN : encode_uint16(raw_ambient[1], raw_ambient[0]) * 0.02f - 273.15f;
 | 
			
		||||
  float object = raw_object[1] & 0x80 ? NAN : encode_uint16(raw_object[1], raw_object[0]) * 0.02f - 273.15f;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Got Temperature=%.1f°C Ambient=%.1f°C", object, ambient);
 | 
			
		||||
 | 
			
		||||
  if (this->ambient_sensor_ != nullptr && !std::isnan(ambient))
 | 
			
		||||
    this->ambient_sensor_->publish_state(ambient);
 | 
			
		||||
  if (this->object_sensor_ != nullptr && !std::isnan(object))
 | 
			
		||||
    this->object_sensor_->publish_state(object);
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace mlx90614
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										34
									
								
								esphome/components/mlx90614/mlx90614.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								esphome/components/mlx90614/mlx90614.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace mlx90614 {
 | 
			
		||||
 | 
			
		||||
class MLX90614Component : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
  void set_ambient_sensor(sensor::Sensor *ambient_sensor) { ambient_sensor_ = ambient_sensor; }
 | 
			
		||||
  void set_object_sensor(sensor::Sensor *object_sensor) { object_sensor_ = object_sensor; }
 | 
			
		||||
 | 
			
		||||
  void set_emissivity(float emissivity) { emissivity_ = emissivity; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool write_emissivity_();
 | 
			
		||||
 | 
			
		||||
  uint8_t crc8_pec_(const uint8_t *data, uint8_t len);
 | 
			
		||||
  bool write_bytes_(uint8_t reg, uint16_t data);
 | 
			
		||||
 | 
			
		||||
  sensor::Sensor *ambient_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *object_sensor_{nullptr};
 | 
			
		||||
 | 
			
		||||
  float emissivity_{NAN};
 | 
			
		||||
};
 | 
			
		||||
}  // namespace mlx90614
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										63
									
								
								esphome/components/mlx90614/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								esphome/components/mlx90614/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c, sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
 | 
			
		||||
CONF_AMBIENT = "ambient"
 | 
			
		||||
CONF_EMISSIVITY = "emissivity"
 | 
			
		||||
CONF_OBJECT = "object"
 | 
			
		||||
 | 
			
		||||
mlx90614_ns = cg.esphome_ns.namespace("mlx90614")
 | 
			
		||||
MLX90614Component = mlx90614_ns.class_(
 | 
			
		||||
    "MLX90614Component", cg.PollingComponent, i2c.I2CDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(MLX90614Component),
 | 
			
		||||
            cv.Optional(CONF_AMBIENT): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_CELSIUS,
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_OBJECT): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_CELSIUS,
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ).extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Optional(CONF_EMISSIVITY, default=1.0): cv.percentage,
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x5A))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await i2c.register_i2c_device(var, config)
 | 
			
		||||
 | 
			
		||||
    if CONF_AMBIENT in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_AMBIENT])
 | 
			
		||||
        cg.add(var.set_ambient_sensor(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_OBJECT in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_OBJECT])
 | 
			
		||||
        cg.add(var.set_object_sensor(sens))
 | 
			
		||||
 | 
			
		||||
        cg.add(var.set_emissivity(config[CONF_OBJECT][CONF_EMISSIVITY]))
 | 
			
		||||
@@ -75,13 +75,8 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
 | 
			
		||||
    JsonArray presets = root.createNestedArray("preset_modes");
 | 
			
		||||
    if (traits.supports_preset(CLIMATE_PRESET_HOME))
 | 
			
		||||
      presets.add("home");
 | 
			
		||||
    if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
 | 
			
		||||
      // away_mode_command_topic
 | 
			
		||||
      root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic();
 | 
			
		||||
      // away_mode_state_topic
 | 
			
		||||
      root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic();
 | 
			
		||||
    if (traits.supports_preset(CLIMATE_PRESET_AWAY))
 | 
			
		||||
      presets.add("away");
 | 
			
		||||
    }
 | 
			
		||||
    if (traits.supports_preset(CLIMATE_PRESET_BOOST))
 | 
			
		||||
      presets.add("boost");
 | 
			
		||||
    if (traits.supports_preset(CLIMATE_PRESET_COMFORT))
 | 
			
		||||
@@ -197,29 +192,6 @@ void MQTTClimateComponent::setup() {
 | 
			
		||||
                    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
 | 
			
		||||
    this->subscribe(this->get_away_command_topic(), [this](const std::string &topic, const std::string &payload) {
 | 
			
		||||
      auto onoff = parse_on_off(payload.c_str());
 | 
			
		||||
      auto call = this->device_->make_call();
 | 
			
		||||
      switch (onoff) {
 | 
			
		||||
        case PARSE_ON:
 | 
			
		||||
          call.set_preset(CLIMATE_PRESET_AWAY);
 | 
			
		||||
          break;
 | 
			
		||||
        case PARSE_OFF:
 | 
			
		||||
          call.set_preset(CLIMATE_PRESET_HOME);
 | 
			
		||||
          break;
 | 
			
		||||
        case PARSE_TOGGLE:
 | 
			
		||||
          call.set_preset(this->device_->preset == CLIMATE_PRESET_AWAY ? CLIMATE_PRESET_HOME : CLIMATE_PRESET_AWAY);
 | 
			
		||||
          break;
 | 
			
		||||
        case PARSE_NONE:
 | 
			
		||||
        default:
 | 
			
		||||
          ESP_LOGW(TAG, "Unknown payload '%s'", payload.c_str());
 | 
			
		||||
          return;
 | 
			
		||||
      }
 | 
			
		||||
      call.perform();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
 | 
			
		||||
    this->subscribe(this->get_preset_command_topic(), [this](const std::string &topic, const std::string &payload) {
 | 
			
		||||
      auto call = this->device_->make_call();
 | 
			
		||||
@@ -301,11 +273,6 @@ bool MQTTClimateComponent::publish_state_() {
 | 
			
		||||
      success = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
 | 
			
		||||
    std::string payload = ONOFF(this->device_->preset == CLIMATE_PRESET_AWAY);
 | 
			
		||||
    if (!this->publish(this->get_away_state_topic(), payload))
 | 
			
		||||
      success = false;
 | 
			
		||||
  }
 | 
			
		||||
  if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
 | 
			
		||||
    std::string payload;
 | 
			
		||||
    if (this->device_->preset.has_value()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,13 +16,5 @@ std::string NumberTraits::get_unit_of_measurement() {
 | 
			
		||||
  return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NumberTraits::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
 | 
			
		||||
 | 
			
		||||
std::string NumberTraits::get_device_class() {
 | 
			
		||||
  if (this->device_class_.has_value())
 | 
			
		||||
    return *this->device_class_;
 | 
			
		||||
  return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace number
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/entity_base.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -11,7 +12,7 @@ enum NumberMode : uint8_t {
 | 
			
		||||
  NUMBER_MODE_SLIDER = 2,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class NumberTraits {
 | 
			
		||||
class NumberTraits : public EntityBase_DeviceClass {
 | 
			
		||||
 public:
 | 
			
		||||
  // Set/get the number value boundaries.
 | 
			
		||||
  void set_min_value(float min_value) { min_value_ = min_value; }
 | 
			
		||||
@@ -32,17 +33,12 @@ class NumberTraits {
 | 
			
		||||
  void set_mode(NumberMode mode) { this->mode_ = mode; }
 | 
			
		||||
  NumberMode get_mode() const { return this->mode_; }
 | 
			
		||||
 | 
			
		||||
  // Set/get the device class.
 | 
			
		||||
  void set_device_class(const std::string &device_class);
 | 
			
		||||
  std::string get_device_class();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  float min_value_ = NAN;
 | 
			
		||||
  float max_value_ = NAN;
 | 
			
		||||
  float step_ = NAN;
 | 
			
		||||
  optional<std::string> unit_of_measurement_;  ///< Unit of measurement override
 | 
			
		||||
  NumberMode mode_{NUMBER_MODE_AUTO};
 | 
			
		||||
  optional<std::string> device_class_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace number
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,10 @@
 | 
			
		||||
#include <esp_ota_ops.h>
 | 
			
		||||
#include "esphome/components/md5/md5.h"
 | 
			
		||||
 | 
			
		||||
#if ESP_IDF_VERSION_MAJOR >= 5
 | 
			
		||||
#include <spi_flash_mmap.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ota {
 | 
			
		||||
 | 
			
		||||
@@ -16,9 +20,28 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) {
 | 
			
		||||
  if (this->partition_ == nullptr) {
 | 
			
		||||
    return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION;
 | 
			
		||||
  }
 | 
			
		||||
  esp_task_wdt_init(15, false);  // The following function takes longer than the 5 seconds timeout of WDT
 | 
			
		||||
 | 
			
		||||
  // The following function takes longer than the 5 seconds timeout of WDT
 | 
			
		||||
#if ESP_IDF_VERSION_MAJOR >= 5
 | 
			
		||||
  esp_task_wdt_config_t wdtc;
 | 
			
		||||
  wdtc.timeout_ms = 15000;
 | 
			
		||||
  wdtc.idle_core_mask = 0;
 | 
			
		||||
  wdtc.trigger_panic = false;
 | 
			
		||||
  esp_task_wdt_reconfigure(&wdtc);
 | 
			
		||||
#else
 | 
			
		||||
  esp_task_wdt_init(15, false);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  esp_err_t err = esp_ota_begin(this->partition_, image_size, &this->update_handle_);
 | 
			
		||||
  esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false);  // Set the WDT back to the configured timeout
 | 
			
		||||
 | 
			
		||||
  // Set the WDT back to the configured timeout
 | 
			
		||||
#if ESP_IDF_VERSION_MAJOR >= 5
 | 
			
		||||
  wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S;
 | 
			
		||||
  esp_task_wdt_reconfigure(&wdtc);
 | 
			
		||||
#else
 | 
			
		||||
  esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    esp_ota_abort(this->update_handle_);
 | 
			
		||||
    this->update_handle_ = 0;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										78
									
								
								esphome/components/pca6416a/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								esphome/components/pca6416a/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INPUT,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_INVERTED,
 | 
			
		||||
    CONF_OUTPUT,
 | 
			
		||||
    CONF_PULLUP,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@Mat931"]
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
pca6416a_ns = cg.esphome_ns.namespace("pca6416a")
 | 
			
		||||
 | 
			
		||||
PCA6416AComponent = pca6416a_ns.class_("PCA6416AComponent", cg.Component, i2c.I2CDevice)
 | 
			
		||||
PCA6416AGPIOPin = pca6416a_ns.class_(
 | 
			
		||||
    "PCA6416AGPIOPin", cg.GPIOPin, cg.Parented.template(PCA6416AComponent)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_PCA6416A = "pca6416a"
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema({cv.Required(CONF_ID): cv.declare_id(PCA6416AComponent)})
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x21))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await i2c.register_i2c_device(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_mode(value):
 | 
			
		||||
    if not (value[CONF_INPUT] or value[CONF_OUTPUT]):
 | 
			
		||||
        raise cv.Invalid("Mode must be either input or output")
 | 
			
		||||
    if value[CONF_INPUT] and value[CONF_OUTPUT]:
 | 
			
		||||
        raise cv.Invalid("Mode must be either input or output")
 | 
			
		||||
    if value[CONF_PULLUP] and not value[CONF_INPUT]:
 | 
			
		||||
        raise cv.Invalid("Pullup only available with input")
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
PCA6416A_PIN_SCHEMA = cv.All(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(PCA6416AGPIOPin),
 | 
			
		||||
        cv.Required(CONF_PCA6416A): cv.use_id(PCA6416AComponent),
 | 
			
		||||
        cv.Required(CONF_NUMBER): cv.int_range(min=0, max=16),
 | 
			
		||||
        cv.Optional(CONF_MODE, default={}): cv.All(
 | 
			
		||||
            {
 | 
			
		||||
                cv.Optional(CONF_INPUT, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_PULLUP, default=False): cv.boolean,
 | 
			
		||||
                cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
 | 
			
		||||
            },
 | 
			
		||||
            validate_mode,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_INVERTED, default=False): cv.boolean,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pins.PIN_SCHEMA_REGISTRY.register("pca6416a", PCA6416A_PIN_SCHEMA)
 | 
			
		||||
async def pca6416a_pin_to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_PCA6416A])
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_parent(parent))
 | 
			
		||||
 | 
			
		||||
    num = config[CONF_NUMBER]
 | 
			
		||||
    cg.add(var.set_pin(num))
 | 
			
		||||
    cg.add(var.set_inverted(config[CONF_INVERTED]))
 | 
			
		||||
    cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
 | 
			
		||||
    return var
 | 
			
		||||
							
								
								
									
										174
									
								
								esphome/components/pca6416a/pca6416a.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								esphome/components/pca6416a/pca6416a.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,174 @@
 | 
			
		||||
#include "pca6416a.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace pca6416a {
 | 
			
		||||
 | 
			
		||||
enum PCA6416AGPIORegisters {
 | 
			
		||||
  // 0 side
 | 
			
		||||
  PCA6416A_INPUT0 = 0x00,
 | 
			
		||||
  PCA6416A_OUTPUT0 = 0x02,
 | 
			
		||||
  PCA6416A_INVERT0 = 0x04,
 | 
			
		||||
  PCA6416A_CONFIG0 = 0x06,
 | 
			
		||||
  PCAL6416A_PULL_EN0 = 0x46,
 | 
			
		||||
  PCAL6416A_PULL_DIR0 = 0x48,
 | 
			
		||||
  // 1 side
 | 
			
		||||
  PCA6416A_INPUT1 = 0x01,
 | 
			
		||||
  PCA6416A_OUTPUT1 = 0x03,
 | 
			
		||||
  PCA6416A_INVERT1 = 0x05,
 | 
			
		||||
  PCA6416A_CONFIG1 = 0x07,
 | 
			
		||||
  PCAL6416A_PULL_EN1 = 0x47,
 | 
			
		||||
  PCAL6416A_PULL_DIR1 = 0x49,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "pca6416a";
 | 
			
		||||
 | 
			
		||||
void PCA6416AComponent::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up PCA6416A...");
 | 
			
		||||
  // Test to see if device exists
 | 
			
		||||
  uint8_t value;
 | 
			
		||||
  if (!this->read_register_(PCA6416A_INPUT0, &value)) {
 | 
			
		||||
    ESP_LOGE(TAG, "PCA6416A not available under 0x%02X", this->address_);
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Test to see if the device supports pull-up resistors
 | 
			
		||||
  if (this->read_register(PCAL6416A_PULL_EN0, &value, 1, true) == esphome::i2c::ERROR_OK) {
 | 
			
		||||
    this->has_pullup_ = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // No polarity inversion
 | 
			
		||||
  this->write_register_(PCA6416A_INVERT0, 0);
 | 
			
		||||
  this->write_register_(PCA6416A_INVERT1, 0);
 | 
			
		||||
  // Set all pins to input
 | 
			
		||||
  this->write_register_(PCA6416A_CONFIG0, 0xff);
 | 
			
		||||
  this->write_register_(PCA6416A_CONFIG1, 0xff);
 | 
			
		||||
  // Read current output register state
 | 
			
		||||
  this->read_register_(PCA6416A_OUTPUT0, &this->output_0_);
 | 
			
		||||
  this->read_register_(PCA6416A_OUTPUT1, &this->output_1_);
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
 | 
			
		||||
           this->status_has_error());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PCA6416AComponent::dump_config() {
 | 
			
		||||
  if (this->has_pullup_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "PCAL6416A:");
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "PCA6416A:");
 | 
			
		||||
  }
 | 
			
		||||
  LOG_I2C_DEVICE(this)
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Communication with PCA6416A failed!");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PCA6416AComponent::digital_read(uint8_t pin) {
 | 
			
		||||
  uint8_t bit = pin % 8;
 | 
			
		||||
  uint8_t reg_addr = pin < 8 ? PCA6416A_INPUT0 : PCA6416A_INPUT1;
 | 
			
		||||
  uint8_t value = 0;
 | 
			
		||||
  this->read_register_(reg_addr, &value);
 | 
			
		||||
  return value & (1 << bit);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PCA6416AComponent::digital_write(uint8_t pin, bool value) {
 | 
			
		||||
  uint8_t reg_addr = pin < 8 ? PCA6416A_OUTPUT0 : PCA6416A_OUTPUT1;
 | 
			
		||||
  this->update_register_(pin, value, reg_addr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PCA6416AComponent::pin_mode(uint8_t pin, gpio::Flags flags) {
 | 
			
		||||
  uint8_t io_dir = pin < 8 ? PCA6416A_CONFIG0 : PCA6416A_CONFIG1;
 | 
			
		||||
  uint8_t pull_en = pin < 8 ? PCAL6416A_PULL_EN0 : PCAL6416A_PULL_EN1;
 | 
			
		||||
  uint8_t pull_dir = pin < 8 ? PCAL6416A_PULL_DIR0 : PCAL6416A_PULL_DIR1;
 | 
			
		||||
  if (flags == gpio::FLAG_INPUT) {
 | 
			
		||||
    this->update_register_(pin, true, io_dir);
 | 
			
		||||
    if (has_pullup_) {
 | 
			
		||||
      this->update_register_(pin, true, pull_dir);
 | 
			
		||||
      this->update_register_(pin, false, pull_en);
 | 
			
		||||
    }
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
 | 
			
		||||
    this->update_register_(pin, true, io_dir);
 | 
			
		||||
    if (has_pullup_) {
 | 
			
		||||
      this->update_register_(pin, true, pull_dir);
 | 
			
		||||
      this->update_register_(pin, true, pull_en);
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGW(TAG, "Your PCA6416A does not support pull-up resistors");
 | 
			
		||||
    }
 | 
			
		||||
  } else if (flags == gpio::FLAG_OUTPUT) {
 | 
			
		||||
    this->update_register_(pin, false, io_dir);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PCA6416AComponent::read_register_(uint8_t reg, uint8_t *value) {
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    ESP_LOGD(TAG, "Device marked failed");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ((this->last_error_ = this->read_register(reg, value, 1, true)) != esphome::i2c::ERROR_OK) {
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PCA6416AComponent::write_register_(uint8_t reg, uint8_t value) {
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    ESP_LOGD(TAG, "Device marked failed");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ((this->last_error_ = this->write_register(reg, &value, 1, true)) != esphome::i2c::ERROR_OK) {
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PCA6416AComponent::update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr) {
 | 
			
		||||
  uint8_t bit = pin % 8;
 | 
			
		||||
  uint8_t reg_value = 0;
 | 
			
		||||
  if (reg_addr == PCA6416A_OUTPUT0) {
 | 
			
		||||
    reg_value = this->output_0_;
 | 
			
		||||
  } else if (reg_addr == PCA6416A_OUTPUT1) {
 | 
			
		||||
    reg_value = this->output_1_;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->read_register_(reg_addr, ®_value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (pin_value) {
 | 
			
		||||
    reg_value |= 1 << bit;
 | 
			
		||||
  } else {
 | 
			
		||||
    reg_value &= ~(1 << bit);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->write_register_(reg_addr, reg_value);
 | 
			
		||||
 | 
			
		||||
  if (reg_addr == PCA6416A_OUTPUT0) {
 | 
			
		||||
    this->output_0_ = reg_value;
 | 
			
		||||
  } else if (reg_addr == PCA6416A_OUTPUT1) {
 | 
			
		||||
    this->output_1_ = reg_value;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float PCA6416AComponent::get_setup_priority() const { return setup_priority::IO; }
 | 
			
		||||
 | 
			
		||||
void PCA6416AGPIOPin::setup() { pin_mode(flags_); }
 | 
			
		||||
void PCA6416AGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
 | 
			
		||||
bool PCA6416AGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
 | 
			
		||||
void PCA6416AGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
 | 
			
		||||
std::string PCA6416AGPIOPin::dump_summary() const {
 | 
			
		||||
  char buffer[32];
 | 
			
		||||
  snprintf(buffer, sizeof(buffer), "%u via PCA6416A", pin_);
 | 
			
		||||
  return buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace pca6416a
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										63
									
								
								esphome/components/pca6416a/pca6416a.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								esphome/components/pca6416a/pca6416a.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace pca6416a {
 | 
			
		||||
 | 
			
		||||
class PCA6416AComponent : public Component, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  PCA6416AComponent() = default;
 | 
			
		||||
 | 
			
		||||
  /// Check i2c availability and setup masks
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  /// Helper function to read the value of a pin.
 | 
			
		||||
  bool digital_read(uint8_t pin);
 | 
			
		||||
  /// Helper function to write the value of a pin.
 | 
			
		||||
  void digital_write(uint8_t pin, bool value);
 | 
			
		||||
  /// Helper function to set the pin mode of a pin.
 | 
			
		||||
  void pin_mode(uint8_t pin, gpio::Flags flags);
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool read_register_(uint8_t reg, uint8_t *value);
 | 
			
		||||
  bool write_register_(uint8_t reg, uint8_t value);
 | 
			
		||||
  void update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr);
 | 
			
		||||
 | 
			
		||||
  /// The mask to write as output state - 1 means HIGH, 0 means LOW
 | 
			
		||||
  uint8_t output_0_{0x00};
 | 
			
		||||
  uint8_t output_1_{0x00};
 | 
			
		||||
  /// Storage for last I2C error seen
 | 
			
		||||
  esphome::i2c::ErrorCode last_error_;
 | 
			
		||||
  /// Only the PCAL6416A has pull-up resistors
 | 
			
		||||
  bool has_pullup_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Helper class to expose a PCA6416A pin as an internal input GPIO pin.
 | 
			
		||||
class PCA6416AGPIOPin : public GPIOPin {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void pin_mode(gpio::Flags flags) override;
 | 
			
		||||
  bool digital_read() override;
 | 
			
		||||
  void digital_write(bool value) override;
 | 
			
		||||
  std::string dump_summary() const override;
 | 
			
		||||
 | 
			
		||||
  void set_parent(PCA6416AComponent *parent) { parent_ = parent; }
 | 
			
		||||
  void set_pin(uint8_t pin) { pin_ = pin; }
 | 
			
		||||
  void set_inverted(bool inverted) { inverted_ = inverted; }
 | 
			
		||||
  void set_flags(gpio::Flags flags) { flags_ = flags; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  PCA6416AComponent *parent_;
 | 
			
		||||
  uint8_t pin_;
 | 
			
		||||
  bool inverted_;
 | 
			
		||||
  gpio::Flags flags_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace pca6416a
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -81,7 +81,32 @@ void PN532::setup() {
 | 
			
		||||
  this->turn_off_rf_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PN532::powerdown() {
 | 
			
		||||
  updates_enabled_ = false;
 | 
			
		||||
  requested_read_ = false;
 | 
			
		||||
  ESP_LOGI(TAG, "Powering down PN532");
 | 
			
		||||
  if (!this->write_command_({PN532_COMMAND_POWERDOWN, 0b10100000})) {  // enable i2c,spi wakeup
 | 
			
		||||
    ESP_LOGE(TAG, "Error writing powerdown command to PN532");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  std::vector<uint8_t> response;
 | 
			
		||||
  if (!this->read_response(PN532_COMMAND_POWERDOWN, response)) {
 | 
			
		||||
    ESP_LOGE(TAG, "Error reading PN532 powerdown response");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  if (response[0] != 0x00) {
 | 
			
		||||
    ESP_LOGE(TAG, "Error on PN532 powerdown: %02x", response[0]);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "Powerdown successful");
 | 
			
		||||
  delay(1);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PN532::update() {
 | 
			
		||||
  if (!updates_enabled_)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  for (auto *obj : this->binary_sensors_)
 | 
			
		||||
    obj->on_scan_end();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ static const uint8_t PN532_COMMAND_SAMCONFIGURATION = 0x14;
 | 
			
		||||
static const uint8_t PN532_COMMAND_RFCONFIGURATION = 0x32;
 | 
			
		||||
static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40;
 | 
			
		||||
static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A;
 | 
			
		||||
static const uint8_t PN532_COMMAND_POWERDOWN = 0x16;
 | 
			
		||||
 | 
			
		||||
class PN532BinarySensor;
 | 
			
		||||
 | 
			
		||||
@@ -30,6 +31,7 @@ class PN532 : public PollingComponent {
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void on_shutdown() override { powerdown(); }
 | 
			
		||||
 | 
			
		||||
  void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
 | 
			
		||||
  void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
 | 
			
		||||
@@ -45,6 +47,7 @@ class PN532 : public PollingComponent {
 | 
			
		||||
  void clean_mode();
 | 
			
		||||
  void format_mode();
 | 
			
		||||
  void write_mode(nfc::NdefMessage *message);
 | 
			
		||||
  bool powerdown();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void turn_off_rf_();
 | 
			
		||||
@@ -79,6 +82,7 @@ class PN532 : public PollingComponent {
 | 
			
		||||
  bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
 | 
			
		||||
  bool clean_mifare_ultralight_();
 | 
			
		||||
 | 
			
		||||
  bool updates_enabled_{true};
 | 
			
		||||
  bool requested_read_{false};
 | 
			
		||||
  std::vector<PN532BinarySensor *> binary_sensors_;
 | 
			
		||||
  std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,12 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import automation, pins
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID, CONF_RESET_PIN
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ON_TAG,
 | 
			
		||||
    CONF_ON_TAG_REMOVED,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_RESET_PIN,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@glmnet"]
 | 
			
		||||
AUTO_LOAD = ["binary_sensor"]
 | 
			
		||||
@@ -24,6 +29,11 @@ RC522_SCHEMA = cv.Schema(
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation(
 | 
			
		||||
            {
 | 
			
		||||
                cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.polling_component_schema("1s"))
 | 
			
		||||
 | 
			
		||||
@@ -37,5 +47,10 @@ async def setup_rc522(var, config):
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_TAG, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
 | 
			
		||||
        cg.add(var.register_trigger(trigger))
 | 
			
		||||
        cg.add(var.register_ontag_trigger(trigger))
 | 
			
		||||
        await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
 | 
			
		||||
 | 
			
		||||
    for conf in config.get(CONF_ON_TAG_REMOVED, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
 | 
			
		||||
        cg.add(var.register_ontagremoved_trigger(trigger))
 | 
			
		||||
        await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
 | 
			
		||||
 
 | 
			
		||||
@@ -256,7 +256,7 @@ void RC522::loop() {
 | 
			
		||||
 | 
			
		||||
      this->current_uid_ = rfid_uid;
 | 
			
		||||
 | 
			
		||||
      for (auto *trigger : this->triggers_)
 | 
			
		||||
      for (auto *trigger : this->triggers_ontag_)
 | 
			
		||||
        trigger->process(rfid_uid);
 | 
			
		||||
 | 
			
		||||
      if (report) {
 | 
			
		||||
@@ -265,6 +265,11 @@ void RC522::loop() {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case STATE_DONE: {
 | 
			
		||||
      if (!this->current_uid_.empty()) {
 | 
			
		||||
        ESP_LOGV(TAG, "Tag '%s' removed", format_uid(this->current_uid_).c_str());
 | 
			
		||||
        for (auto *trigger : this->triggers_ontagremoved_)
 | 
			
		||||
          trigger->process(this->current_uid_);
 | 
			
		||||
      }
 | 
			
		||||
      this->current_uid_ = {};
 | 
			
		||||
      state_ = STATE_INIT;
 | 
			
		||||
      break;
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,8 @@ class RC522 : public PollingComponent {
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
  void register_tag(RC522BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
 | 
			
		||||
  void register_trigger(RC522Trigger *trig) { this->triggers_.push_back(trig); }
 | 
			
		||||
  void register_ontag_trigger(RC522Trigger *trig) { this->triggers_ontag_.push_back(trig); }
 | 
			
		||||
  void register_ontagremoved_trigger(RC522Trigger *trig) { this->triggers_ontagremoved_.push_back(trig); }
 | 
			
		||||
 | 
			
		||||
  void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
 | 
			
		||||
 | 
			
		||||
@@ -242,7 +243,8 @@ class RC522 : public PollingComponent {
 | 
			
		||||
  uint8_t reset_count_{0};
 | 
			
		||||
  uint32_t reset_timeout_{0};
 | 
			
		||||
  std::vector<RC522BinarySensor *> binary_sensors_;
 | 
			
		||||
  std::vector<RC522Trigger *> triggers_;
 | 
			
		||||
  std::vector<RC522Trigger *> triggers_ontag_;
 | 
			
		||||
  std::vector<RC522Trigger *> triggers_ontagremoved_;
 | 
			
		||||
  std::vector<uint8_t> current_uid_;
 | 
			
		||||
 | 
			
		||||
  enum RC522Error {
 | 
			
		||||
 
 | 
			
		||||
@@ -791,6 +791,57 @@ async def raw_action(var, config, args):
 | 
			
		||||
    cg.add(var.set_carrier_frequency(templ))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Drayton
 | 
			
		||||
(
 | 
			
		||||
    DraytonData,
 | 
			
		||||
    DraytonBinarySensor,
 | 
			
		||||
    DraytonTrigger,
 | 
			
		||||
    DraytonAction,
 | 
			
		||||
    DraytonDumper,
 | 
			
		||||
) = declare_protocol("Drayton")
 | 
			
		||||
DRAYTON_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFF)),
 | 
			
		||||
        cv.Required(CONF_CHANNEL): cv.All(cv.hex_int, cv.Range(min=0, max=0x1F)),
 | 
			
		||||
        cv.Required(CONF_COMMAND): cv.All(cv.hex_int, cv.Range(min=0, max=0x7F)),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_binary_sensor("drayton", DraytonBinarySensor, DRAYTON_SCHEMA)
 | 
			
		||||
def drayton_binary_sensor(var, config):
 | 
			
		||||
    cg.add(
 | 
			
		||||
        var.set_data(
 | 
			
		||||
            cg.StructInitializer(
 | 
			
		||||
                DraytonData,
 | 
			
		||||
                ("address", config[CONF_ADDRESS]),
 | 
			
		||||
                ("channel", config[CONF_CHANNEL]),
 | 
			
		||||
                ("command", config[CONF_COMMAND]),
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_trigger("drayton", DraytonTrigger, DraytonData)
 | 
			
		||||
def drayton_trigger(var, config):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_dumper("drayton", DraytonDumper)
 | 
			
		||||
def drayton_dumper(var, config):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_action("drayton", DraytonAction, DRAYTON_SCHEMA)
 | 
			
		||||
async def drayton_action(var, config, args):
 | 
			
		||||
    template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16)
 | 
			
		||||
    cg.add(var.set_address(template_))
 | 
			
		||||
    template_ = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)
 | 
			
		||||
    cg.add(var.set_channel(template_))
 | 
			
		||||
    template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8)
 | 
			
		||||
    cg.add(var.set_command(template_))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# RC5
 | 
			
		||||
RC5Data, RC5BinarySensor, RC5Trigger, RC5Action, RC5Dumper = declare_protocol("RC5")
 | 
			
		||||
RC5_SCHEMA = cv.Schema(
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										213
									
								
								esphome/components/remote_base/drayton_protocol.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								esphome/components/remote_base/drayton_protocol.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,213 @@
 | 
			
		||||
#include "drayton_protocol.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace remote_base {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "remote.drayton";
 | 
			
		||||
 | 
			
		||||
static const uint32_t BIT_TIME_US = 500;
 | 
			
		||||
static const uint8_t CARRIER_KHZ = 2;
 | 
			
		||||
static const uint8_t NBITS_PREAMBLE = 12;
 | 
			
		||||
static const uint8_t NBITS_SYNC = 4;
 | 
			
		||||
static const uint8_t NBITS_ADDRESS = 16;
 | 
			
		||||
static const uint8_t NBITS_CHANNEL = 5;
 | 
			
		||||
static const uint8_t NBITS_COMMAND = 7;
 | 
			
		||||
static const uint8_t NBITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND;
 | 
			
		||||
 | 
			
		||||
static const uint8_t CMD_ON = 0x41;
 | 
			
		||||
static const uint8_t CMD_OFF = 0x02;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Drayton Protocol
 | 
			
		||||
Using an oscilloscope to capture the data transmitted by the Digistat two
 | 
			
		||||
distinct packets for 'On' and 'Off' are transmitted. Each transmitted bit
 | 
			
		||||
has a period of 500us, a bit rate of 2000 baud.
 | 
			
		||||
 | 
			
		||||
Each packet consists of an initial 1010 pattern to set up the receiver bias.
 | 
			
		||||
The number of these bits seen at the receiver varies depending on the state
 | 
			
		||||
of the bias when the packet transmission starts. The receiver algoritmn takes
 | 
			
		||||
account of this.
 | 
			
		||||
 | 
			
		||||
The packet appears to be Manchester encoded, with a '10' tranmitted pair
 | 
			
		||||
representing a '1' bit and a '01' pair representing a '0' bit. Each packet is
 | 
			
		||||
begun with a '1100' syncronisation symbol which breaks this rule. Following
 | 
			
		||||
the sync are 28 '01' or '10' pairs.
 | 
			
		||||
 | 
			
		||||
--------------------
 | 
			
		||||
 | 
			
		||||
Boiler On Command as received:
 | 
			
		||||
101010101010110001101001010101101001010101010101100101010101101001011001
 | 
			
		||||
ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-1-0-0-0-0-0-1-1-0-0-1-0
 | 
			
		||||
 | 
			
		||||
(Where pppp represents the preamble bits and SSSS represents the sync symbol)
 | 
			
		||||
 | 
			
		||||
28 bits of data received 01100001100000001000001 10010 (bin) or 6180832 (hex)
 | 
			
		||||
 | 
			
		||||
Boiler Off Command as received:
 | 
			
		||||
101010101010110001101001010101101001010101010101010101010110011001011001
 | 
			
		||||
ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-0-0-0-0-0-1-0-1-0-0-1-0
 | 
			
		||||
 | 
			
		||||
28 bits of data received 0110000110000000000001010010 (bin) or 6180052 (hex)
 | 
			
		||||
 | 
			
		||||
--------------------
 | 
			
		||||
 | 
			
		||||
I have used 'RFLink' software (RLink Firmware Version: 1.1 Revision: 48) to
 | 
			
		||||
capture and retransmit the Digistat packets. RFLink splits each packet into an
 | 
			
		||||
ID, SWITCH, and CMD field.
 | 
			
		||||
 | 
			
		||||
0;17;Drayton;ID=c300;SWITCH=12;CMD=ON;
 | 
			
		||||
20;18;Drayton;ID=c300;SWITCH=12;CMD=OFF;
 | 
			
		||||
 | 
			
		||||
--------------------
 | 
			
		||||
 | 
			
		||||
Spliting my received data into three parts of 16, 7 and 5 bits gives address,
 | 
			
		||||
channel and Command values of:
 | 
			
		||||
 | 
			
		||||
On  6180832  0110000110000000 1000001 10010
 | 
			
		||||
address: '0x6180' channel: '0x12' command: '0x41'
 | 
			
		||||
 | 
			
		||||
Off 6180052  0110000110000000 0000010 10010
 | 
			
		||||
address: '0x6180' channel: '0x12' command: '0x02'
 | 
			
		||||
 | 
			
		||||
These values are slightly different to those used by RFLink (the RFLink
 | 
			
		||||
ID/Adress value is rotated/manipulated), and I don't know who's interpretation
 | 
			
		||||
is correct. A larger data sample would help (I have only found five different
 | 
			
		||||
packet captures online) or definitive information from Drayton.
 | 
			
		||||
 | 
			
		||||
Splitting each packet in this way works well for me with esphome. Any
 | 
			
		||||
corrections or additional data samples would be gratefully received.
 | 
			
		||||
 | 
			
		||||
marshn
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
void DraytonProtocol::encode(RemoteTransmitData *dst, const DraytonData &data) {
 | 
			
		||||
  uint16_t khz = CARRIER_KHZ;
 | 
			
		||||
  dst->set_carrier_frequency(khz * 1000);
 | 
			
		||||
 | 
			
		||||
  // Preamble = 101010101010
 | 
			
		||||
  uint32_t out_data = 0x0AAA;
 | 
			
		||||
  for (uint32_t mask = 1UL << (NBITS_PREAMBLE - 1); mask != 0; mask >>= 1) {
 | 
			
		||||
    if (out_data & mask) {
 | 
			
		||||
      dst->mark(BIT_TIME_US);
 | 
			
		||||
    } else {
 | 
			
		||||
      dst->space(BIT_TIME_US);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Sync = 1100
 | 
			
		||||
  out_data = 0x000C;
 | 
			
		||||
  for (uint32_t mask = 1UL << (NBITS_SYNC - 1); mask != 0; mask >>= 1) {
 | 
			
		||||
    if (out_data & mask) {
 | 
			
		||||
      dst->mark(BIT_TIME_US);
 | 
			
		||||
    } else {
 | 
			
		||||
      dst->space(BIT_TIME_US);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Send Drayton: address=%04x channel=%03x cmd=%02x", data.address, data.channel, data.command);
 | 
			
		||||
 | 
			
		||||
  out_data = data.address;
 | 
			
		||||
  out_data <<= NBITS_COMMAND;
 | 
			
		||||
  out_data |= data.command;
 | 
			
		||||
  out_data <<= NBITS_CHANNEL;
 | 
			
		||||
  out_data |= data.channel;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Send Drayton: out_data %08x", out_data);
 | 
			
		||||
 | 
			
		||||
  for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) {
 | 
			
		||||
    if (out_data & mask) {
 | 
			
		||||
      dst->mark(BIT_TIME_US);
 | 
			
		||||
      dst->space(BIT_TIME_US);
 | 
			
		||||
    } else {
 | 
			
		||||
      dst->space(BIT_TIME_US);
 | 
			
		||||
      dst->mark(BIT_TIME_US);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
 | 
			
		||||
  DraytonData out{
 | 
			
		||||
      .address = 0,
 | 
			
		||||
      .channel = 0,
 | 
			
		||||
      .command = 0,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  if (src.size() < 45) {
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGVV(TAG, "Decode Drayton: %d, %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(),
 | 
			
		||||
            src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7),
 | 
			
		||||
            src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14),
 | 
			
		||||
            src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19));
 | 
			
		||||
 | 
			
		||||
  // If first preamble item is a space, skip it
 | 
			
		||||
  if (src.peek_space_at_least(1)) {
 | 
			
		||||
    src.advance(1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Look for sync pulse, after. If sucessful index points to space of sync symbol
 | 
			
		||||
  for (uint16_t preamble = 0; preamble <= NBITS_PREAMBLE * 2; preamble += 2) {
 | 
			
		||||
    ESP_LOGVV(TAG, "Decode Drayton: preamble %d  %d %d", preamble, src.peek(preamble), src.peek(preamble + 1));
 | 
			
		||||
    if (src.peek_mark(2 * BIT_TIME_US, preamble) &&
 | 
			
		||||
        (src.peek_space(2 * BIT_TIME_US, preamble + 1) || src.peek_space(3 * BIT_TIME_US, preamble + 1))) {
 | 
			
		||||
      src.advance(preamble + 1);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Read data. Index points to space of sync symbol
 | 
			
		||||
  // Extract first bit
 | 
			
		||||
  // Checks next bit to leave index pointing correctly
 | 
			
		||||
  uint32_t out_data = 0;
 | 
			
		||||
  uint8_t bit = NBITS_ADDRESS + NBITS_COMMAND + NBITS_CHANNEL - 1;
 | 
			
		||||
  if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
 | 
			
		||||
    out_data |= 0 << bit;
 | 
			
		||||
  } else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) &&
 | 
			
		||||
             (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
 | 
			
		||||
    out_data |= 1 << bit;
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGV(TAG, "Decode Drayton: Fail 1, - %d", src.get_index());
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Before/after each bit is read the index points to the transition at the start of the bit period or,
 | 
			
		||||
  // if there is no transition at the start of the bit period, then the transition in the middle of
 | 
			
		||||
  // the previous bit period.
 | 
			
		||||
  while (--bit >= 1) {
 | 
			
		||||
    ESP_LOGVV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
 | 
			
		||||
    if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) &&
 | 
			
		||||
        (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
 | 
			
		||||
      out_data |= 0 << bit;
 | 
			
		||||
    } else if ((src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) &&
 | 
			
		||||
               (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
 | 
			
		||||
      out_data |= 1 << bit;
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGVV(TAG, "Decode Drayton: Fail 2, %2d %08x", bit, out_data);
 | 
			
		||||
      return {};
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) {
 | 
			
		||||
    out_data |= 0;
 | 
			
		||||
  } else if (src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) {
 | 
			
		||||
    out_data |= 1;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
 | 
			
		||||
 | 
			
		||||
  out.channel = (uint8_t) (out_data & 0x1F);
 | 
			
		||||
  out_data >>= NBITS_CHANNEL;
 | 
			
		||||
  out.command = (uint8_t) (out_data & 0x7F);
 | 
			
		||||
  out_data >>= NBITS_COMMAND;
 | 
			
		||||
  out.address = (uint16_t) (out_data & 0xFFFF);
 | 
			
		||||
 | 
			
		||||
  return out;
 | 
			
		||||
}
 | 
			
		||||
void DraytonProtocol::dump(const DraytonData &data) {
 | 
			
		||||
  ESP_LOGD(TAG, "Received Drayton: address=0x%04X (0x%04x), channel=0x%03x command=0x%03X", data.address,
 | 
			
		||||
           ((data.address << 1) & 0xffff), data.channel, data.command);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace remote_base
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										44
									
								
								esphome/components/remote_base/drayton_protocol.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								esphome/components/remote_base/drayton_protocol.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "remote_base.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace remote_base {
 | 
			
		||||
 | 
			
		||||
struct DraytonData {
 | 
			
		||||
  uint16_t address;
 | 
			
		||||
  uint8_t channel;
 | 
			
		||||
  uint8_t command;
 | 
			
		||||
 | 
			
		||||
  bool operator==(const DraytonData &rhs) const {
 | 
			
		||||
    return address == rhs.address && channel == rhs.channel && command == rhs.command;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DraytonProtocol : public RemoteProtocol<DraytonData> {
 | 
			
		||||
 public:
 | 
			
		||||
  void encode(RemoteTransmitData *dst, const DraytonData &data) override;
 | 
			
		||||
  optional<DraytonData> decode(RemoteReceiveData src) override;
 | 
			
		||||
  void dump(const DraytonData &data) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
DECLARE_REMOTE_PROTOCOL(Drayton)
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class DraytonAction : public RemoteTransmitterActionBase<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  TEMPLATABLE_VALUE(uint16_t, address)
 | 
			
		||||
  TEMPLATABLE_VALUE(uint8_t, channel)
 | 
			
		||||
  TEMPLATABLE_VALUE(uint8_t, command)
 | 
			
		||||
 | 
			
		||||
  void encode(RemoteTransmitData *dst, Ts... x) override {
 | 
			
		||||
    DraytonData data{};
 | 
			
		||||
    data.address = this->address_.value(x...);
 | 
			
		||||
    data.channel = this->channel_.value(x...);
 | 
			
		||||
    data.command = this->command_.value(x...);
 | 
			
		||||
    DraytonProtocol().encode(dst, data);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace remote_base
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -102,7 +102,7 @@ def _parse_platform_version(value):
 | 
			
		||||
    try:
 | 
			
		||||
        # if platform version is a valid version constraint, prefix the default package
 | 
			
		||||
        cv.platformio_version_constraint(value)
 | 
			
		||||
        return f"platformio/raspberrypi @ {value}"
 | 
			
		||||
        return f"platformio/raspberrypi@{value}"
 | 
			
		||||
    except cv.Invalid:
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
@@ -148,7 +148,7 @@ async def to_code(config):
 | 
			
		||||
    cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
 | 
			
		||||
    cg.add_platformio_option(
 | 
			
		||||
        "platform_packages",
 | 
			
		||||
        [f"earlephilhower/framework-arduinopico @ {conf[CONF_SOURCE]}"],
 | 
			
		||||
        [f"earlephilhower/framework-arduinopico@{conf[CONF_SOURCE]}"],
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    cg.add_platformio_option("board_build.core", "earlephilhower")
 | 
			
		||||
 
 | 
			
		||||
@@ -25,10 +25,12 @@ from esphome.const import (
 | 
			
		||||
    CONF_STATE_CLASS,
 | 
			
		||||
    CONF_TO,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
    CONF_UNIT_OF_MEASUREMENT,
 | 
			
		||||
    CONF_WINDOW_SIZE,
 | 
			
		||||
    CONF_MQTT_ID,
 | 
			
		||||
    CONF_FORCE_UPDATE,
 | 
			
		||||
    CONF_VALUE,
 | 
			
		||||
    DEVICE_CLASS_APPARENT_POWER,
 | 
			
		||||
    DEVICE_CLASS_AQI,
 | 
			
		||||
    DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
 | 
			
		||||
@@ -476,21 +478,38 @@ async def lambda_filter_to_code(config, filter_id):
 | 
			
		||||
    return cg.new_Pvariable(filter_id, lambda_)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DELTA_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_VALUE): cv.positive_float,
 | 
			
		||||
        cv.Optional(CONF_TYPE, default="absolute"): cv.one_of(
 | 
			
		||||
            "absolute", "percentage", lower=True
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_delta(config):
 | 
			
		||||
    try:
 | 
			
		||||
        return (cv.positive_float(config), False)
 | 
			
		||||
        value = cv.positive_float(config)
 | 
			
		||||
        return DELTA_SCHEMA({CONF_VALUE: value, CONF_TYPE: "absolute"})
 | 
			
		||||
    except cv.Invalid:
 | 
			
		||||
        pass
 | 
			
		||||
    try:
 | 
			
		||||
        return (cv.percentage(config), True)
 | 
			
		||||
        value = cv.percentage(config)
 | 
			
		||||
        return DELTA_SCHEMA({CONF_VALUE: value, CONF_TYPE: "percentage"})
 | 
			
		||||
    except cv.Invalid:
 | 
			
		||||
        pass
 | 
			
		||||
    raise cv.Invalid("Delta filter requires a positive number or percentage value.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@FILTER_REGISTRY.register("delta", DeltaFilter, validate_delta)
 | 
			
		||||
@FILTER_REGISTRY.register("delta", DeltaFilter, cv.Any(DELTA_SCHEMA, validate_delta))
 | 
			
		||||
async def delta_filter_to_code(config, filter_id):
 | 
			
		||||
    return cg.new_Pvariable(filter_id, *config)
 | 
			
		||||
    percentage = config[CONF_TYPE] == "percentage"
 | 
			
		||||
    return cg.new_Pvariable(
 | 
			
		||||
        filter_id,
 | 
			
		||||
        config[CONF_VALUE],
 | 
			
		||||
        percentage,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@FILTER_REGISTRY.register("or", OrFilter, validate_filters)
 | 
			
		||||
 
 | 
			
		||||
@@ -38,13 +38,6 @@ int8_t Sensor::get_accuracy_decimals() {
 | 
			
		||||
}
 | 
			
		||||
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; }
 | 
			
		||||
 | 
			
		||||
std::string Sensor::get_device_class() {
 | 
			
		||||
  if (this->device_class_.has_value())
 | 
			
		||||
    return *this->device_class_;
 | 
			
		||||
  return "";
 | 
			
		||||
}
 | 
			
		||||
void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
 | 
			
		||||
 | 
			
		||||
void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; }
 | 
			
		||||
StateClass Sensor::get_state_class() {
 | 
			
		||||
  if (this->state_class_.has_value())
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ std::string state_class_to_string(StateClass state_class);
 | 
			
		||||
 *
 | 
			
		||||
 * A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy.
 | 
			
		||||
 */
 | 
			
		||||
class Sensor : public EntityBase {
 | 
			
		||||
class Sensor : public EntityBase, public EntityBase_DeviceClass {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit Sensor();
 | 
			
		||||
 | 
			
		||||
@@ -68,11 +68,6 @@ class Sensor : public EntityBase {
 | 
			
		||||
  /// Manually set the accuracy in decimals.
 | 
			
		||||
  void set_accuracy_decimals(int8_t accuracy_decimals);
 | 
			
		||||
 | 
			
		||||
  /// Get the device class, using the manual override if set.
 | 
			
		||||
  std::string get_device_class();
 | 
			
		||||
  /// Manually set the device class.
 | 
			
		||||
  void set_device_class(const std::string &device_class);
 | 
			
		||||
 | 
			
		||||
  /// Get the state class, using the manual override if set.
 | 
			
		||||
  StateClass get_state_class();
 | 
			
		||||
  /// Manually set the state class.
 | 
			
		||||
@@ -165,7 +160,6 @@ class Sensor : public EntityBase {
 | 
			
		||||
 | 
			
		||||
  optional<std::string> unit_of_measurement_;           ///< Unit of measurement override
 | 
			
		||||
  optional<int8_t> accuracy_decimals_;                  ///< Accuracy in decimals override
 | 
			
		||||
  optional<std::string> device_class_;                  ///< Device class override
 | 
			
		||||
  optional<StateClass> state_class_{STATE_CLASS_NONE};  ///< State class override
 | 
			
		||||
  bool force_update_{false};                            ///< Force update mode
 | 
			
		||||
  bool has_state_{false};
 | 
			
		||||
 
 | 
			
		||||
@@ -286,7 +286,9 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_ENABLE_SWITCH): cv.maybe_simple_value(
 | 
			
		||||
            switch.switch_schema(
 | 
			
		||||
                SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
 | 
			
		||||
                SprinklerControllerSwitch,
 | 
			
		||||
                entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
                default_restore_mode="RESTORE_DEFAULT_OFF",
 | 
			
		||||
            ),
 | 
			
		||||
            key=CONF_NAME,
 | 
			
		||||
        ),
 | 
			
		||||
@@ -333,7 +335,9 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema(
 | 
			
		||||
        cv.Optional(CONF_NAME): cv.string,
 | 
			
		||||
        cv.Optional(CONF_AUTO_ADVANCE_SWITCH): cv.maybe_simple_value(
 | 
			
		||||
            switch.switch_schema(
 | 
			
		||||
                SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
 | 
			
		||||
                SprinklerControllerSwitch,
 | 
			
		||||
                entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
                default_restore_mode="RESTORE_DEFAULT_OFF",
 | 
			
		||||
            ),
 | 
			
		||||
            key=CONF_NAME,
 | 
			
		||||
        ),
 | 
			
		||||
@@ -343,19 +347,25 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema(
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_QUEUE_ENABLE_SWITCH): cv.maybe_simple_value(
 | 
			
		||||
            switch.switch_schema(
 | 
			
		||||
                SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
 | 
			
		||||
                SprinklerControllerSwitch,
 | 
			
		||||
                entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
                default_restore_mode="RESTORE_DEFAULT_OFF",
 | 
			
		||||
            ),
 | 
			
		||||
            key=CONF_NAME,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_REVERSE_SWITCH): cv.maybe_simple_value(
 | 
			
		||||
            switch.switch_schema(
 | 
			
		||||
                SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
 | 
			
		||||
                SprinklerControllerSwitch,
 | 
			
		||||
                entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
                default_restore_mode="RESTORE_DEFAULT_OFF",
 | 
			
		||||
            ),
 | 
			
		||||
            key=CONF_NAME,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_STANDBY_SWITCH): cv.maybe_simple_value(
 | 
			
		||||
            switch.switch_schema(
 | 
			
		||||
                SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
 | 
			
		||||
                SprinklerControllerSwitch,
 | 
			
		||||
                entity_category=ENTITY_CATEGORY_CONFIG,
 | 
			
		||||
                default_restore_mode="RESTORE_DEFAULT_OFF",
 | 
			
		||||
            ),
 | 
			
		||||
            key=CONF_NAME,
 | 
			
		||||
        ),
 | 
			
		||||
 
 | 
			
		||||
@@ -1176,6 +1176,21 @@ optional<uint32_t> Sprinkler::time_remaining_current_operation() {
 | 
			
		||||
  return nullopt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Sprinkler::any_controller_is_active() {
 | 
			
		||||
  if (this->state_ != IDLE) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (auto &controller : this->other_controllers_) {
 | 
			
		||||
    if (controller != this) {  // dummy check
 | 
			
		||||
      if (controller->controller_state() != IDLE) {
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SprinklerControllerSwitch *Sprinkler::control_switch(size_t valve_number) {
 | 
			
		||||
  if (this->is_a_valid_valve(valve_number)) {
 | 
			
		||||
    return this->valve_[valve_number].controller_switch;
 | 
			
		||||
 
 | 
			
		||||
@@ -406,6 +406,12 @@ class Sprinkler : public Component {
 | 
			
		||||
  /// returns the amount of time remaining in seconds for all valves remaining, including the active valve, if any
 | 
			
		||||
  optional<uint32_t> time_remaining_current_operation();
 | 
			
		||||
 | 
			
		||||
  /// returns true if this or any sprinkler controller this controller knows about is active
 | 
			
		||||
  bool any_controller_is_active();
 | 
			
		||||
 | 
			
		||||
  /// returns the current state of the sprinkler controller
 | 
			
		||||
  SprinklerState controller_state() { return this->state_; };
 | 
			
		||||
 | 
			
		||||
  /// returns a pointer to a valve's control switch object
 | 
			
		||||
  SprinklerControllerSwitch *control_switch(size_t valve_number);
 | 
			
		||||
 | 
			
		||||
@@ -503,7 +509,6 @@ class Sprinkler : public Component {
 | 
			
		||||
  /// callback functions for timers
 | 
			
		||||
  void valve_selection_callback_();
 | 
			
		||||
  void sm_timer_callback_();
 | 
			
		||||
  void pump_stop_delay_callback_();
 | 
			
		||||
 | 
			
		||||
  /// Maximum allowed queue size
 | 
			
		||||
  const uint8_t max_queue_size_{100};
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user