mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Compare commits
	
		
			118 Commits
		
	
	
		
			2022.9.4
			...
			jesserockz
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					3792823c24 | ||
| 
						 | 
					1554c5700e | ||
| 
						 | 
					5cf257b251 | ||
| 
						 | 
					04883e14f6 | ||
| 
						 | 
					0a649c184f | ||
| 
						 | 
					0e66c899ce | ||
| 
						 | 
					e7d236f939 | ||
| 
						 | 
					fae4d03473 | ||
| 
						 | 
					97bd3e7320 | ||
| 
						 | 
					dfca2f88d3 | ||
| 
						 | 
					8cad93de37 | ||
| 
						 | 
					e5b8dd7f2d | ||
| 
						 | 
					a1c8b8092b | ||
| 
						 | 
					109ca2406d | ||
| 
						 | 
					3a689112fd | ||
| 
						 | 
					40e0cd0f03 | ||
| 
						 | 
					bf4d3df906 | ||
| 
						 | 
					0e30c49e3f | ||
| 
						 | 
					e61a01f7bb | ||
| 
						 | 
					f8640cf2cd | ||
| 
						 | 
					4bcfeb6e33 | ||
| 
						 | 
					a5d4ca0f6d | ||
| 
						 | 
					85faecb2fd | ||
| 
						 | 
					991fc54994 | ||
| 
						 | 
					2de891dc32 | ||
| 
						 | 
					9865cb7f55 | ||
| 
						 | 
					f97252b93a | ||
| 
						 | 
					f2c4f018de | ||
| 
						 | 
					237c7dd169 | ||
| 
						 | 
					b781b8d77d | ||
| 
						 | 
					60b7d1c8a1 | ||
| 
						 | 
					8161222b33 | ||
| 
						 | 
					1000c4466f | ||
| 
						 | 
					60717b074e | ||
| 
						 | 
					c3fba97b4c | ||
| 
						 | 
					d93f35701f | ||
| 
						 | 
					702b60ce66 | ||
| 
						 | 
					f8ce597918 | ||
| 
						 | 
					22e0a944c8 | ||
| 
						 | 
					3a134ef009 | ||
| 
						 | 
					96e8cb66b6 | ||
| 
						 | 
					615288c151 | ||
| 
						 | 
					6153bcc6ad | ||
| 
						 | 
					e87edcc77a | ||
| 
						 | 
					a21c3e8e2d | ||
| 
						 | 
					d7576f67e8 | ||
| 
						 | 
					138de643a2 | ||
| 
						 | 
					f30e54d177 | ||
| 
						 | 
					41b5cb06d3 | ||
| 
						 | 
					4ac72d7d08 | ||
| 
						 | 
					06ac4980ba | ||
| 
						 | 
					ccbfa20bb9 | ||
| 
						 | 
					58cd754e07 | ||
| 
						 | 
					d1263e583b | ||
| 
						 | 
					67c911c37f | ||
| 
						 | 
					288e3c3e3e | ||
| 
						 | 
					a84378c6c2 | ||
| 
						 | 
					b6073408f4 | ||
| 
						 | 
					b2d91ac5de | ||
| 
						 | 
					8bf34e09f4 | ||
| 
						 | 
					be914f2c15 | ||
| 
						 | 
					8bb670521d | ||
| 
						 | 
					225b3c1494 | ||
| 
						 | 
					4bf94e0757 | ||
| 
						 | 
					3b21d1d81e | ||
| 
						 | 
					5ec1588110 | ||
| 
						 | 
					71387be72e | ||
| 
						 | 
					98171c9f49 | ||
| 
						 | 
					a6c999dea0 | ||
| 
						 | 
					bf15b1d302 | ||
| 
						 | 
					f422fabab4 | ||
| 
						 | 
					01b130ec59 | ||
| 
						 | 
					48a1797e72 | ||
| 
						 | 
					b34d24735a | ||
| 
						 | 
					fe38b36c26 | ||
| 
						 | 
					03fca8d91e | ||
| 
						 | 
					9f9980e338 | ||
| 
						 | 
					19900b004b | ||
| 
						 | 
					a8ff0a8913 | ||
| 
						 | 
					45861456f1 | ||
| 
						 | 
					44b335e7e3 | ||
| 
						 | 
					de23bbace2 | ||
| 
						 | 
					edff9ae322 | ||
| 
						 | 
					3c2766448d | ||
| 
						 | 
					786c8b6cfe | ||
| 
						 | 
					fd57b21aff | ||
| 
						 | 
					6087183a0c | ||
| 
						 | 
					01b7c4200e | ||
| 
						 | 
					7171286c3c | ||
| 
						 | 
					d220d41182 | ||
| 
						 | 
					c3a8972550 | ||
| 
						 | 
					263b603188 | ||
| 
						 | 
					584b722e7e | ||
| 
						 | 
					05edfd0e82 | ||
| 
						 | 
					16249c02a5 | ||
| 
						 | 
					e8ff36d1f3 | ||
| 
						 | 
					ed443c6153 | ||
| 
						 | 
					f4a84765cd | ||
| 
						 | 
					106de3530d | ||
| 
						 | 
					8095db6715 | ||
| 
						 | 
					ce2e161b08 | ||
| 
						 | 
					6ef93452f5 | ||
| 
						 | 
					68ea59f3ae | ||
| 
						 | 
					9a69769a7e | ||
| 
						 | 
					3572c62315 | ||
| 
						 | 
					1444cddda9 | ||
| 
						 | 
					b2db524366 | ||
| 
						 | 
					ab8674a5c7 | ||
| 
						 | 
					d1c85fc3fa | ||
| 
						 | 
					55ad45e3ee | ||
| 
						 | 
					f6e5a8cb2a | ||
| 
						 | 
					7a91ca9809 | ||
| 
						 | 
					917bbc669c | ||
| 
						 | 
					0ac4c055de | ||
| 
						 | 
					78b55d86e9 | ||
| 
						 | 
					aaf50fc2e6 | ||
| 
						 | 
					6a8f4e92df | ||
| 
						 | 
					88943103a2 | 
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "ESPHome Dev",
 | 
			
		||||
  "image": "esphome/esphome-lint:dev",
 | 
			
		||||
  "image": "ghcr.io/esphome/esphome-lint:dev",
 | 
			
		||||
  "postCreateCommand": [
 | 
			
		||||
    "script/devcontainer-post-create"
 | 
			
		||||
  ],
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
# What does this implement/fix?
 | 
			
		||||
 | 
			
		||||
Quick description and explanation of changes
 | 
			
		||||
<!-- Quick description and explanation of changes -->
 | 
			
		||||
 | 
			
		||||
## Types of changes
 | 
			
		||||
 | 
			
		||||
@@ -18,6 +18,7 @@ Quick description and explanation of changes
 | 
			
		||||
- [ ] ESP32
 | 
			
		||||
- [ ] ESP32 IDF
 | 
			
		||||
- [ ] ESP8266
 | 
			
		||||
- [ ] RP2040
 | 
			
		||||
 | 
			
		||||
## Example entry for `config.yaml`:
 | 
			
		||||
<!--
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -28,6 +28,7 @@ jobs:
 | 
			
		||||
    name: Build docker containers
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        arch: [amd64, armv7, aarch64]
 | 
			
		||||
        build_type: ["ha-addon", "docker", "lint"]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -48,6 +48,10 @@ jobs:
 | 
			
		||||
            file: tests/test5.yaml
 | 
			
		||||
            name: Test tests/test5.yaml
 | 
			
		||||
            pio_cache_key: test5
 | 
			
		||||
          - id: test
 | 
			
		||||
            file: tests/test6.yaml
 | 
			
		||||
            name: Test tests/test6.yaml
 | 
			
		||||
            pio_cache_key: test6
 | 
			
		||||
          - id: pytest
 | 
			
		||||
            name: Run pytest
 | 
			
		||||
          - id: clang-format
 | 
			
		||||
@@ -85,7 +89,7 @@ jobs:
 | 
			
		||||
        uses: actions/setup-python@v4
 | 
			
		||||
        id: python
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: "3.8"
 | 
			
		||||
          python-version: "3.9"
 | 
			
		||||
 | 
			
		||||
      - name: Cache virtualenv
 | 
			
		||||
        uses: actions/cache@v3
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,7 +18,7 @@ jobs:
 | 
			
		||||
  stale:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/stale@v5
 | 
			
		||||
      - uses: actions/stale@v6
 | 
			
		||||
        with:
 | 
			
		||||
          days-before-pr-stale: 90
 | 
			
		||||
          days-before-pr-close: 7
 | 
			
		||||
@@ -38,7 +38,7 @@ jobs:
 | 
			
		||||
  close-issues:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/stale@v5
 | 
			
		||||
      - uses: actions/stale@v6
 | 
			
		||||
        with:
 | 
			
		||||
          days-before-pr-stale: -1
 | 
			
		||||
          days-before-pr-close: -1
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
ports:
 | 
			
		||||
  - port: 6052
 | 
			
		||||
    onOpen: open-preview
 | 
			
		||||
tasks:
 | 
			
		||||
  # yamllint disable-line rule:line-length
 | 
			
		||||
  - before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup
 | 
			
		||||
    command: python -m esphome dashboard config
 | 
			
		||||
@@ -27,7 +27,7 @@ repos:
 | 
			
		||||
          - --branch=release
 | 
			
		||||
          - --branch=beta
 | 
			
		||||
  - repo: https://github.com/asottile/pyupgrade
 | 
			
		||||
    rev: v2.37.3
 | 
			
		||||
    rev: v3.0.0
 | 
			
		||||
    hooks:
 | 
			
		||||
      - id: pyupgrade
 | 
			
		||||
        args: [--py38-plus]
 | 
			
		||||
        args: [--py39-plus]
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ esphome/core/* @esphome/core
 | 
			
		||||
# Integrations
 | 
			
		||||
esphome/components/ac_dimmer/* @glmnet
 | 
			
		||||
esphome/components/adc/* @esphome/core
 | 
			
		||||
esphome/components/adc128s102/* @DeerMaximum
 | 
			
		||||
esphome/components/addressable_light/* @justfalter
 | 
			
		||||
esphome/components/airthings_ble/* @jeromelaban
 | 
			
		||||
esphome/components/airthings_wave_mini/* @ncareau
 | 
			
		||||
@@ -57,6 +58,7 @@ esphome/components/cse7761/* @berfenger
 | 
			
		||||
esphome/components/ct_clamp/* @jesserockz
 | 
			
		||||
esphome/components/current_based/* @djwmarcx
 | 
			
		||||
esphome/components/dac7678/* @NickB1
 | 
			
		||||
esphome/components/daikin_brc/* @hagak
 | 
			
		||||
esphome/components/daly_bms/* @s1lvi0
 | 
			
		||||
esphome/components/dashboard_import/* @esphome/core
 | 
			
		||||
esphome/components/debug/* @OttoWinter
 | 
			
		||||
@@ -76,8 +78,10 @@ esphome/components/esp32_camera_web_server/* @ayufan
 | 
			
		||||
esphome/components/esp32_can/* @Sympatron
 | 
			
		||||
esphome/components/esp32_improv/* @jesserockz
 | 
			
		||||
esphome/components/esp8266/* @esphome/core
 | 
			
		||||
esphome/components/ethernet_info/* @gtjadsonsantos
 | 
			
		||||
esphome/components/exposure_notifications/* @OttoWinter
 | 
			
		||||
esphome/components/ezo/* @ssieb
 | 
			
		||||
esphome/components/ezo_pmp/* @carlos-sarmiento
 | 
			
		||||
esphome/components/factory_reset/* @anatoly-savchenkov
 | 
			
		||||
esphome/components/fastled_base/* @OttoWinter
 | 
			
		||||
esphome/components/feedback/* @ianchi
 | 
			
		||||
@@ -180,6 +184,8 @@ esphome/components/rc522_spi/* @glmnet
 | 
			
		||||
esphome/components/restart/* @esphome/core
 | 
			
		||||
esphome/components/rf_bridge/* @jesserockz
 | 
			
		||||
esphome/components/rgbct/* @jesserockz
 | 
			
		||||
esphome/components/rp2040/* @jesserockz
 | 
			
		||||
esphome/components/rp2040_pwm/* @jesserockz
 | 
			
		||||
esphome/components/rtttl/* @glmnet
 | 
			
		||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
 | 
			
		||||
esphome/components/scd4x/* @martgras @sjtrny
 | 
			
		||||
@@ -254,8 +260,9 @@ esphome/components/wake_on_lan/* @willwill2will54
 | 
			
		||||
esphome/components/web_server_base/* @OttoWinter
 | 
			
		||||
esphome/components/whirlpool/* @glmnet
 | 
			
		||||
esphome/components/whynter/* @aeonsablaze
 | 
			
		||||
esphome/components/wl_134/* @hobbypunk90
 | 
			
		||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
 | 
			
		||||
esphome/components/xiaomi_mhoc303/* @drug123
 | 
			
		||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
 | 
			
		||||
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
 | 
			
		||||
esphome/components/xpt2046/* @numo68
 | 
			
		||||
esphome/components/xpt2046/* @nielsnl68 @numo68
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ RUN \
 | 
			
		||||
    # Ubuntu python3-pip is missing wheel
 | 
			
		||||
    pip3 install --no-cache-dir \
 | 
			
		||||
        wheel==0.37.1 \
 | 
			
		||||
        platformio==6.0.2 \
 | 
			
		||||
        platformio==6.1.4 \
 | 
			
		||||
    # Change some platformio settings
 | 
			
		||||
    && platformio settings set enable_telemetry No \
 | 
			
		||||
    && platformio settings set check_platformio_interval 1000000 \
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import logging
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
from esphome import const, writer, yaml_util
 | 
			
		||||
@@ -22,6 +23,9 @@ from esphome.const import (
 | 
			
		||||
    CONF_ESPHOME,
 | 
			
		||||
    CONF_PLATFORMIO_OPTIONS,
 | 
			
		||||
    CONF_SUBSTITUTIONS,
 | 
			
		||||
    PLATFORM_ESP32,
 | 
			
		||||
    PLATFORM_ESP8266,
 | 
			
		||||
    PLATFORM_RP2040,
 | 
			
		||||
    SECRETS_FILES,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, EsphomeError, coroutine
 | 
			
		||||
@@ -101,11 +105,11 @@ def run_miniterm(config, port):
 | 
			
		||||
 | 
			
		||||
    if CONF_LOGGER not in config:
 | 
			
		||||
        _LOGGER.info("Logger is not enabled. Not starting UART logs.")
 | 
			
		||||
        return
 | 
			
		||||
        return 1
 | 
			
		||||
    baud_rate = config["logger"][CONF_BAUD_RATE]
 | 
			
		||||
    if baud_rate == 0:
 | 
			
		||||
        _LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
 | 
			
		||||
        return
 | 
			
		||||
        return 1
 | 
			
		||||
    _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
 | 
			
		||||
 | 
			
		||||
    backtrace_state = False
 | 
			
		||||
@@ -119,25 +123,34 @@ def run_miniterm(config, port):
 | 
			
		||||
        ser.dtr = False
 | 
			
		||||
        ser.rts = False
 | 
			
		||||
 | 
			
		||||
    with ser:
 | 
			
		||||
        while True:
 | 
			
		||||
            try:
 | 
			
		||||
                raw = ser.readline()
 | 
			
		||||
            except serial.SerialException:
 | 
			
		||||
                _LOGGER.error("Serial port closed!")
 | 
			
		||||
                return
 | 
			
		||||
            line = (
 | 
			
		||||
                raw.replace(b"\r", b"")
 | 
			
		||||
                .replace(b"\n", b"")
 | 
			
		||||
                .decode("utf8", "backslashreplace")
 | 
			
		||||
            )
 | 
			
		||||
            time = datetime.now().time().strftime("[%H:%M:%S]")
 | 
			
		||||
            message = time + line
 | 
			
		||||
            safe_print(message)
 | 
			
		||||
    tries = 0
 | 
			
		||||
    while tries < 5:
 | 
			
		||||
        try:
 | 
			
		||||
            with ser:
 | 
			
		||||
                while True:
 | 
			
		||||
                    try:
 | 
			
		||||
                        raw = ser.readline()
 | 
			
		||||
                    except serial.SerialException:
 | 
			
		||||
                        _LOGGER.error("Serial port closed!")
 | 
			
		||||
                        return 0
 | 
			
		||||
                    line = (
 | 
			
		||||
                        raw.replace(b"\r", b"")
 | 
			
		||||
                        .replace(b"\n", b"")
 | 
			
		||||
                        .decode("utf8", "backslashreplace")
 | 
			
		||||
                    )
 | 
			
		||||
                    time_str = datetime.now().time().strftime("[%H:%M:%S]")
 | 
			
		||||
                    message = time_str + line
 | 
			
		||||
                    safe_print(message)
 | 
			
		||||
 | 
			
		||||
            backtrace_state = platformio_api.process_stacktrace(
 | 
			
		||||
                config, line, backtrace_state=backtrace_state
 | 
			
		||||
            )
 | 
			
		||||
                    backtrace_state = platformio_api.process_stacktrace(
 | 
			
		||||
                        config, line, backtrace_state=backtrace_state
 | 
			
		||||
                    )
 | 
			
		||||
        except serial.SerialException:
 | 
			
		||||
            tries += 1
 | 
			
		||||
            time.sleep(1)
 | 
			
		||||
    if tries >= 5:
 | 
			
		||||
        _LOGGER.error("Could not connect to serial port %s", port)
 | 
			
		||||
        return 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def wrap_to_code(name, comp):
 | 
			
		||||
@@ -258,9 +271,21 @@ def upload_using_esptool(config, port):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def upload_program(config, args, host):
 | 
			
		||||
    # if upload is to a serial port use platformio, otherwise assume ota
 | 
			
		||||
    if get_port_type(host) == "SERIAL":
 | 
			
		||||
        return upload_using_esptool(config, host)
 | 
			
		||||
        if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
 | 
			
		||||
            return upload_using_esptool(config, host)
 | 
			
		||||
 | 
			
		||||
        if CORE.target_platform in (PLATFORM_RP2040):
 | 
			
		||||
            from esphome import platformio_api
 | 
			
		||||
 | 
			
		||||
            upload_args = ["-t", "upload"]
 | 
			
		||||
            if args.device is not None:
 | 
			
		||||
                upload_args += ["--upload-port", args.device]
 | 
			
		||||
            return platformio_api.run_platformio_cli_run(
 | 
			
		||||
                config, CORE.verbose, *upload_args
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        return 1  # Unknown target platform
 | 
			
		||||
 | 
			
		||||
    from esphome import espota2
 | 
			
		||||
 | 
			
		||||
@@ -280,8 +305,7 @@ def show_logs(config, args, port):
 | 
			
		||||
    if "logger" not in config:
 | 
			
		||||
        raise EsphomeError("Logger is not configured!")
 | 
			
		||||
    if get_port_type(port) == "SERIAL":
 | 
			
		||||
        run_miniterm(config, port)
 | 
			
		||||
        return 0
 | 
			
		||||
        return run_miniterm(config, port)
 | 
			
		||||
    if get_port_type(port) == "NETWORK" and "api" in config:
 | 
			
		||||
        from esphome.components.api.client import run_logs
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ from esphome.cpp_generator import (  # noqa
 | 
			
		||||
    static_const_array,
 | 
			
		||||
    statement,
 | 
			
		||||
    variable,
 | 
			
		||||
    with_local_variable,
 | 
			
		||||
    new_variable,
 | 
			
		||||
    Pvariable,
 | 
			
		||||
    new_Pvariable,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								esphome/components/adc128s102/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphome/components/adc128s102/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import spi
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["spi"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
CODEOWNERS = ["@DeerMaximum"]
 | 
			
		||||
 | 
			
		||||
adc128s102_ns = cg.esphome_ns.namespace("adc128s102")
 | 
			
		||||
ADC128S102 = adc128s102_ns.class_("ADC128S102", cg.Component, spi.SPIDevice)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(ADC128S102),
 | 
			
		||||
    }
 | 
			
		||||
).extend(spi.spi_device_schema(cs_pin_required=True))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await spi.register_spi_device(var, config)
 | 
			
		||||
							
								
								
									
										35
									
								
								esphome/components/adc128s102/adc128s102.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								esphome/components/adc128s102/adc128s102.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
#include "adc128s102.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace adc128s102 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "adc128s102";
 | 
			
		||||
 | 
			
		||||
float ADC128S102::get_setup_priority() const { return setup_priority::HARDWARE; }
 | 
			
		||||
 | 
			
		||||
void ADC128S102::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up adc128s102");
 | 
			
		||||
  this->spi_setup();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADC128S102::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ADC128S102:");
 | 
			
		||||
  LOG_PIN("  CS Pin:", this->cs_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t ADC128S102::read_data(uint8_t channel) {
 | 
			
		||||
  uint8_t control = channel << 3;
 | 
			
		||||
 | 
			
		||||
  this->enable();
 | 
			
		||||
  uint8_t adc_primary_byte = this->transfer_byte(control);
 | 
			
		||||
  uint8_t adc_secondary_byte = this->transfer_byte(0x00);
 | 
			
		||||
  this->disable();
 | 
			
		||||
 | 
			
		||||
  uint16_t digital_value = adc_primary_byte << 8 | adc_secondary_byte;
 | 
			
		||||
 | 
			
		||||
  return digital_value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace adc128s102
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										23
									
								
								esphome/components/adc128s102/adc128s102.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								esphome/components/adc128s102/adc128s102.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/components/spi/spi.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace adc128s102 {
 | 
			
		||||
 | 
			
		||||
class ADC128S102 : public Component,
 | 
			
		||||
                   public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
 | 
			
		||||
                                         spi::DATA_RATE_10MHZ> {
 | 
			
		||||
 public:
 | 
			
		||||
  ADC128S102() = default;
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  uint16_t read_data(uint8_t channel);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace adc128s102
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										35
									
								
								esphome/components/adc128s102/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								esphome/components/adc128s102/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, voltage_sampler
 | 
			
		||||
from esphome.const import CONF_ID, CONF_CHANNEL
 | 
			
		||||
 | 
			
		||||
from .. import adc128s102_ns, ADC128S102
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["voltage_sampler"]
 | 
			
		||||
DEPENDENCIES = ["adc128s102"]
 | 
			
		||||
 | 
			
		||||
ADC128S102Sensor = adc128s102_ns.class_(
 | 
			
		||||
    "ADC128S102Sensor",
 | 
			
		||||
    sensor.Sensor,
 | 
			
		||||
    cg.PollingComponent,
 | 
			
		||||
    voltage_sampler.VoltageSampler,
 | 
			
		||||
)
 | 
			
		||||
CONF_ADC128S102_ID = "adc128s102_id"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(ADC128S102Sensor),
 | 
			
		||||
        cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102),
 | 
			
		||||
        cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.polling_component_schema("60s"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(
 | 
			
		||||
        config[CONF_ID],
 | 
			
		||||
        config[CONF_CHANNEL],
 | 
			
		||||
    )
 | 
			
		||||
    await cg.register_parented(var, config[CONF_ADC128S102_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await sensor.register_sensor(var, config)
 | 
			
		||||
							
								
								
									
										24
									
								
								esphome/components/adc128s102/sensor/adc128s102_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								esphome/components/adc128s102/sensor/adc128s102_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
#include "adc128s102_sensor.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace adc128s102 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "adc128s102.sensor";
 | 
			
		||||
 | 
			
		||||
ADC128S102Sensor::ADC128S102Sensor(uint8_t channel) : channel_(channel) {}
 | 
			
		||||
 | 
			
		||||
float ADC128S102Sensor::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
void ADC128S102Sensor::dump_config() {
 | 
			
		||||
  LOG_SENSOR("", "ADC128S102 Sensor", this);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Pin: %u", this->channel_);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ADC128S102Sensor::sample() { return this->parent_->read_data(this->channel_); }
 | 
			
		||||
void ADC128S102Sensor::update() { this->publish_state(this->sample()); }
 | 
			
		||||
 | 
			
		||||
}  // namespace adc128s102
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										29
									
								
								esphome/components/adc128s102/sensor/adc128s102_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								esphome/components/adc128s102/sensor/adc128s102_sensor.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
#include "../adc128s102.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace adc128s102 {
 | 
			
		||||
 | 
			
		||||
class ADC128S102Sensor : public PollingComponent,
 | 
			
		||||
                         public Parented<ADC128S102>,
 | 
			
		||||
                         public sensor::Sensor,
 | 
			
		||||
                         public voltage_sampler::VoltageSampler {
 | 
			
		||||
 public:
 | 
			
		||||
  ADC128S102Sensor(uint8_t channel);
 | 
			
		||||
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  float sample() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t channel_;
 | 
			
		||||
};
 | 
			
		||||
}  // namespace adc128s102
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__)
 | 
			
		||||
DEPENDENCIES = ["display"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
Animation_ = display.display_ns.class_("Animation")
 | 
			
		||||
Animation_ = display.display_ns.class_("Animation", espImage.Image_)
 | 
			
		||||
 | 
			
		||||
ANIMATION_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -204,6 +204,8 @@ message DeviceInfoResponse {
 | 
			
		||||
  uint32 webserver_port = 10;
 | 
			
		||||
 | 
			
		||||
  uint32 bluetooth_proxy_version = 11;
 | 
			
		||||
 | 
			
		||||
  string manufacturer = 12;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message ListEntitiesRequest {
 | 
			
		||||
@@ -1298,3 +1300,31 @@ message BluetoothConnectionsFreeResponse {
 | 
			
		||||
  uint32 free = 1;
 | 
			
		||||
  uint32 limit = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTErrorResponse {
 | 
			
		||||
  option (id) = 82;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
  int32 error = 3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTWriteResponse {
 | 
			
		||||
  option (id) = 83;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message BluetoothGATTNotifyResponse {
 | 
			
		||||
  option (id) = 84;
 | 
			
		||||
  option (source) = SOURCE_SERVER;
 | 
			
		||||
  option (ifdef) = "USE_BLUETOOTH_PROXY";
 | 
			
		||||
 | 
			
		||||
  uint64 address = 1;
 | 
			
		||||
  uint32 handle = 2;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -932,6 +932,11 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
 | 
			
		||||
  resp.mac_address = get_mac_address_pretty();
 | 
			
		||||
  resp.esphome_version = ESPHOME_VERSION;
 | 
			
		||||
  resp.compilation_time = App.get_compilation_time();
 | 
			
		||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
 | 
			
		||||
  resp.manufacturer = "Espressif";
 | 
			
		||||
#elif defined(USE_RP2040)
 | 
			
		||||
  resp.manufacturer = "Raspberry Pi";
 | 
			
		||||
#endif
 | 
			
		||||
  resp.model = ESPHOME_BOARD;
 | 
			
		||||
#ifdef USE_DEEP_SLEEP
 | 
			
		||||
  resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
 | 
			
		||||
 
 | 
			
		||||
@@ -576,6 +576,10 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
 | 
			
		||||
      this->project_version = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 12: {
 | 
			
		||||
      this->manufacturer = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -592,6 +596,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(9, this->project_version);
 | 
			
		||||
  buffer.encode_uint32(10, this->webserver_port);
 | 
			
		||||
  buffer.encode_uint32(11, this->bluetooth_proxy_version);
 | 
			
		||||
  buffer.encode_string(12, this->manufacturer);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void DeviceInfoResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -642,6 +647,10 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
 | 
			
		||||
  sprintf(buffer, "%u", this->bluetooth_proxy_version);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  manufacturer: ");
 | 
			
		||||
  out.append("'").append(this->manufacturer).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -5746,6 +5755,118 @@ void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTErrorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 3: {
 | 
			
		||||
      this->error = value.as_int32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTErrorResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
  buffer.encode_int32(3, this->error);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTErrorResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTErrorResponse {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  error: ");
 | 
			
		||||
  sprintf(buffer, "%d", this->error);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTWriteResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTWriteResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTWriteResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTWriteResponse {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool BluetoothGATTNotifyResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
  switch (field_id) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
      this->address = value.as_uint64();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
      this->handle = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void BluetoothGATTNotifyResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_uint64(1, this->address);
 | 
			
		||||
  buffer.encode_uint32(2, this->handle);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void BluetoothGATTNotifyResponse::dump_to(std::string &out) const {
 | 
			
		||||
  __attribute__((unused)) char buffer[64];
 | 
			
		||||
  out.append("BluetoothGATTNotifyResponse {\n");
 | 
			
		||||
  out.append("  address: ");
 | 
			
		||||
  sprintf(buffer, "%llu", this->address);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  handle: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->handle);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -273,6 +273,7 @@ class DeviceInfoResponse : public ProtoMessage {
 | 
			
		||||
  std::string project_version{};
 | 
			
		||||
  uint32_t webserver_port{0};
 | 
			
		||||
  uint32_t bluetooth_proxy_version{0};
 | 
			
		||||
  std::string manufacturer{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -1481,6 +1482,43 @@ class BluetoothConnectionsFreeResponse : public ProtoMessage {
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTErrorResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  int32_t error{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTWriteResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
class BluetoothGATTNotifyResponse : public ProtoMessage {
 | 
			
		||||
 public:
 | 
			
		||||
  uint64_t address{0};
 | 
			
		||||
  uint32_t handle{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace api
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -401,6 +401,30 @@ bool APIServerConnectionBase::send_bluetooth_connections_free_response(const Blu
 | 
			
		||||
  return this->send_message_<BluetoothConnectionsFreeResponse>(msg, 81);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_gatt_error_response(const BluetoothGATTErrorResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_gatt_error_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothGATTErrorResponse>(msg, 82);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_gatt_write_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothGATTWriteResponse>(msg, 83);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
bool APIServerConnectionBase::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg) {
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  ESP_LOGVV(TAG, "send_bluetooth_gatt_notify_response: %s", msg.dump().c_str());
 | 
			
		||||
#endif
 | 
			
		||||
  return this->send_message_<BluetoothGATTNotifyResponse>(msg, 84);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
 | 
			
		||||
  switch (msg_type) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
 
 | 
			
		||||
@@ -200,6 +200,15 @@ class APIServerConnectionBase : public ProtoService {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_gatt_error_response(const BluetoothGATTErrorResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg);
 | 
			
		||||
#endif
 | 
			
		||||
 protected:
 | 
			
		||||
  bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
 | 
			
		||||
 
 | 
			
		||||
@@ -324,11 +324,21 @@ void APIServer::send_bluetooth_gatt_read_response(const BluetoothGATTReadRespons
 | 
			
		||||
    client->send_bluetooth_gatt_read_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_write_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_notify_data_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_notify_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call) {
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_get_services_response(call);
 | 
			
		||||
@@ -342,6 +352,17 @@ void APIServer::send_bluetooth_gatt_services_done(uint64_t address) {
 | 
			
		||||
    client->send_bluetooth_gatt_get_services_done_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void APIServer::send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
 | 
			
		||||
  BluetoothGATTErrorResponse call;
 | 
			
		||||
  call.address = address;
 | 
			
		||||
  call.handle = handle;
 | 
			
		||||
  call.error = error;
 | 
			
		||||
 | 
			
		||||
  for (auto &client : this->clients_) {
 | 
			
		||||
    client->send_bluetooth_gatt_error_response(call);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
APIServer::APIServer() { global_api_server = this; }
 | 
			
		||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
 | 
			
		||||
 
 | 
			
		||||
@@ -75,12 +75,15 @@ class APIServer : public Component, public Controller {
 | 
			
		||||
  void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
 | 
			
		||||
#ifdef USE_BLUETOOTH_PROXY
 | 
			
		||||
  void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call);
 | 
			
		||||
  void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK);
 | 
			
		||||
  void send_bluetooth_connections_free(uint8_t free, uint8_t limit);
 | 
			
		||||
  void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call);
 | 
			
		||||
  void send_bluetooth_gatt_services_done(uint64_t address);
 | 
			
		||||
  void send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error);
 | 
			
		||||
#endif
 | 
			
		||||
  void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
 | 
			
		||||
#ifdef USE_HOMEASSISTANT_TIME
 | 
			
		||||
 
 | 
			
		||||
@@ -100,12 +100,40 @@ async def ble_write_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    else:
 | 
			
		||||
        cg.add(var.set_value_simple(value))
 | 
			
		||||
 | 
			
		||||
    serv_uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
 | 
			
		||||
    cg.add(var.set_service_uuid128(serv_uuid128))
 | 
			
		||||
    char_uuid128 = esp32_ble_tracker.as_reversed_hex_array(
 | 
			
		||||
        config[CONF_CHARACTERISTIC_UUID]
 | 
			
		||||
    )
 | 
			
		||||
    cg.add(var.set_char_uuid128(char_uuid128))
 | 
			
		||||
    if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
 | 
			
		||||
        )
 | 
			
		||||
    elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
 | 
			
		||||
        )
 | 
			
		||||
    elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
 | 
			
		||||
        uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
 | 
			
		||||
        cg.add(var.set_service_uuid128(uuid128))
 | 
			
		||||
 | 
			
		||||
    if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_char_uuid16(
 | 
			
		||||
                esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
 | 
			
		||||
        esp32_ble_tracker.bt_uuid32_format
 | 
			
		||||
    ):
 | 
			
		||||
        cg.add(
 | 
			
		||||
            var.set_char_uuid32(
 | 
			
		||||
                esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
 | 
			
		||||
        esp32_ble_tracker.bt_uuid128_format
 | 
			
		||||
    ):
 | 
			
		||||
        uuid128 = esp32_ble_tracker.as_reversed_hex_array(
 | 
			
		||||
            config[CONF_CHARACTERISTIC_UUID]
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_char_uuid128(uuid128))
 | 
			
		||||
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -46,10 +46,14 @@ class BLEWriterClientNode : public BLEClientNode {
 | 
			
		||||
  // Attempts to write the contents of value to char_uuid_.
 | 
			
		||||
  void write(const std::vector<uint8_t> &value);
 | 
			
		||||
 | 
			
		||||
  void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
 | 
			
		||||
 | 
			
		||||
  void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
 | 
			
		||||
  void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
 | 
			
		||||
  void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
 | 
			
		||||
 | 
			
		||||
  void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
 | 
			
		||||
  void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
 | 
			
		||||
  void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
 | 
			
		||||
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -47,11 +47,12 @@ void BLEClient::set_enabled(bool enabled) {
 | 
			
		||||
  this->enabled = enabled;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
 | 
			
		||||
bool BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
 | 
			
		||||
                                    esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  bool all_established = this->all_nodes_established_();
 | 
			
		||||
 | 
			
		||||
  BLEClientBase::gattc_event_handler(event, esp_gattc_if, param);
 | 
			
		||||
  if (!BLEClientBase::gattc_event_handler(event, esp_gattc_if, param))
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  for (auto *node : this->nodes_)
 | 
			
		||||
    node->gattc_event_handler(event, esp_gattc_if, param);
 | 
			
		||||
@@ -62,6 +63,14 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
 | 
			
		||||
      delete svc;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
    this->services_.clear();
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
 | 
			
		||||
  BLEClientBase::gap_event_handler(event, param);
 | 
			
		||||
 | 
			
		||||
  for (auto *node : this->nodes_)
 | 
			
		||||
    node->gap_event_handler(event, param);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClient::set_state(espbt::ClientState state) {
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,8 @@ 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;
 | 
			
		||||
  virtual void loop(){};
 | 
			
		||||
  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; }
 | 
			
		||||
  espbt::ESPBTClient *client;
 | 
			
		||||
  // This should be transitioned to Established once the node no longer needs
 | 
			
		||||
@@ -49,8 +50,10 @@ class BLEClient : public BLEClientBase {
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
  bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
 | 
			
		||||
  void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
 | 
			
		||||
  bool parse_device(const espbt::ESPBTDevice &device) override;
 | 
			
		||||
 | 
			
		||||
  void set_enabled(bool enabled);
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,11 @@ from esphome.const import (
 | 
			
		||||
    CONF_CHARACTERISTIC_UUID,
 | 
			
		||||
    CONF_LAMBDA,
 | 
			
		||||
    CONF_TRIGGER_ID,
 | 
			
		||||
    CONF_TYPE,
 | 
			
		||||
    CONF_SERVICE_UUID,
 | 
			
		||||
    DEVICE_CLASS_SIGNAL_STRENGTH,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_DECIBEL_MILLIWATT,
 | 
			
		||||
)
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from .. import ble_client_ns
 | 
			
		||||
@@ -16,6 +20,8 @@ CONF_DESCRIPTOR_UUID = "descriptor_uuid"
 | 
			
		||||
 | 
			
		||||
CONF_NOTIFY = "notify"
 | 
			
		||||
CONF_ON_NOTIFY = "on_notify"
 | 
			
		||||
TYPE_CHARACTERISTIC = "characteristic"
 | 
			
		||||
TYPE_RSSI = "rssi"
 | 
			
		||||
 | 
			
		||||
adv_data_t = cg.std_vector.template(cg.uint8)
 | 
			
		||||
adv_data_t_const_ref = adv_data_t.operator("ref").operator("const")
 | 
			
		||||
@@ -27,33 +33,67 @@ BLESensorNotifyTrigger = ble_client_ns.class_(
 | 
			
		||||
    "BLESensorNotifyTrigger", automation.Trigger.template(cg.float_)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    sensor.sensor_schema(
 | 
			
		||||
        BLESensor,
 | 
			
		||||
        accuracy_decimals=0,
 | 
			
		||||
    )
 | 
			
		||||
    .extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
            cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
            cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
            cv.Optional(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
            cv.Optional(CONF_NOTIFY, default=False): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_ON_NOTIFY): automation.validate_automation(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                        BLESensorNotifyTrigger
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(ble_client.BLE_CLIENT_SCHEMA)
 | 
			
		||||
BLEClientRssiSensor = ble_client_ns.class_(
 | 
			
		||||
    "BLEClientRSSISensor", sensor.Sensor, cg.PollingComponent, ble_client.BLEClientNode
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
def checkType(value):
 | 
			
		||||
    if CONF_TYPE not in value and CONF_SERVICE_UUID in value:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "Looks like you're trying to create a ble characteristic sensor. Please add `type: characteristic` to your sensor config."
 | 
			
		||||
        )
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    checkType,
 | 
			
		||||
    cv.typed_schema(
 | 
			
		||||
        {
 | 
			
		||||
            TYPE_CHARACTERISTIC: sensor.sensor_schema(
 | 
			
		||||
                BLESensor,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
            )
 | 
			
		||||
            .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
            .extend(ble_client.BLE_CLIENT_SCHEMA)
 | 
			
		||||
            .extend(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
                    cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
                    cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
 | 
			
		||||
                    cv.Optional(CONF_LAMBDA): cv.returning_lambda,
 | 
			
		||||
                    cv.Optional(CONF_NOTIFY, default=False): cv.boolean,
 | 
			
		||||
                    cv.Optional(CONF_ON_NOTIFY): automation.validate_automation(
 | 
			
		||||
                        {
 | 
			
		||||
                            cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
 | 
			
		||||
                                BLESensorNotifyTrigger
 | 
			
		||||
                            ),
 | 
			
		||||
                        }
 | 
			
		||||
                    ),
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            TYPE_RSSI: sensor.sensor_schema(
 | 
			
		||||
                BLEClientRssiSensor,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
 | 
			
		||||
                device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            )
 | 
			
		||||
            .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
            .extend(ble_client.BLE_CLIENT_SCHEMA),
 | 
			
		||||
        },
 | 
			
		||||
        lower=True,
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def rssi_sensor_to_code(config):
 | 
			
		||||
    var = await sensor.new_sensor(config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def characteristic_sensor_to_code(config):
 | 
			
		||||
    var = await sensor.new_sensor(config)
 | 
			
		||||
    if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
        cg.add(
 | 
			
		||||
@@ -125,3 +165,10 @@ async def to_code(config):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
        await ble_client.register_ble_node(trigger, config)
 | 
			
		||||
        await automation.build_automation(trigger, [(float, "x")], conf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    if config[CONF_TYPE] == TYPE_RSSI:
 | 
			
		||||
        await rssi_sensor_to_code(config)
 | 
			
		||||
    elif config[CONF_TYPE] == TYPE_CHARACTERISTIC:
 | 
			
		||||
        await characteristic_sensor_to_code(config)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										78
									
								
								esphome/components/ble_client/sensor/ble_rssi_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								esphome/components/ble_client/sensor/ble_rssi_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
#include "ble_rssi_sensor.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ble_client {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ble_rssi_sensor";
 | 
			
		||||
 | 
			
		||||
void BLEClientRSSISensor::loop() {}
 | 
			
		||||
 | 
			
		||||
void BLEClientRSSISensor::dump_config() {
 | 
			
		||||
  LOG_SENSOR("", "BLE Client RSSI Sensor", this);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  MAC address        : %s", this->parent()->address_str().c_str());
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientRSSISensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                              esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      if (param->open.status == ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGI(TAG, "[%s] Connected successfully!", this->get_name().c_str());
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
      ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str());
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
      this->publish_state(NAN);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT:
 | 
			
		||||
      this->node_state = espbt::ClientState::ESTABLISHED;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientRSSISensor::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    // server response on RSSI request:
 | 
			
		||||
    case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
 | 
			
		||||
      if (param->read_rssi_cmpl.status == ESP_BT_STATUS_SUCCESS) {
 | 
			
		||||
        int8_t rssi = param->read_rssi_cmpl.rssi;
 | 
			
		||||
        ESP_LOGI(TAG, "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT RSSI: %d", rssi);
 | 
			
		||||
        this->publish_state(rssi);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientRSSISensor::update() {
 | 
			
		||||
  if (this->node_state != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "requesting rssi from %s", this->parent()->address_str().c_str());
 | 
			
		||||
  auto status = esp_ble_gap_read_rssi(this->parent()->get_remote_bda());
 | 
			
		||||
  if (status != ESP_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "esp_ble_gap_read_rssi error, address=%s, status=%d", this->parent()->address_str().c_str(), status);
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    this->publish_state(NAN);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										31
									
								
								esphome/components/ble_client/sensor/ble_rssi_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								esphome/components/ble_client/sensor/ble_rssi_sensor.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/ble_client/ble_client.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ble_client {
 | 
			
		||||
 | 
			
		||||
namespace espbt = esphome::esp32_ble_tracker;
 | 
			
		||||
 | 
			
		||||
class BLEClientRSSISensor : public sensor::Sensor, public PollingComponent, public BLEClientNode {
 | 
			
		||||
 public:
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
  void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
 | 
			
		||||
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ble_client
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
#endif
 | 
			
		||||
@@ -58,7 +58,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
 | 
			
		||||
      case MATCH_BY_SERVICE_UUID:
 | 
			
		||||
        for (auto uuid : device.get_service_uuids()) {
 | 
			
		||||
          if (this->uuid_ == uuid) {
 | 
			
		||||
            this->publish_state(device.get_rssi());
 | 
			
		||||
            this->publish_state(true);
 | 
			
		||||
            this->found_ = true;
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
@@ -83,7 +83,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this->publish_state(device.get_rssi());
 | 
			
		||||
        this->publish_state(true);
 | 
			
		||||
        this->found_ = true;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_MAC_ADDRESS,
 | 
			
		||||
    DEVICE_CLASS_SIGNAL_STRENGTH,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_DECIBEL,
 | 
			
		||||
    UNIT_DECIBEL_MILLIWATT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32_ble_tracker"]
 | 
			
		||||
@@ -31,7 +31,7 @@ def _validate(config):
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    sensor.sensor_schema(
 | 
			
		||||
        BLERSSISensor,
 | 
			
		||||
        unit_of_measurement=UNIT_DECIBEL,
 | 
			
		||||
        unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
 | 
			
		||||
        accuracy_decimals=0,
 | 
			
		||||
        device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
 | 
			
		||||
        state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,22 +4,59 @@ import esphome.codegen as cg
 | 
			
		||||
from esphome.const import CONF_ACTIVE, CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
 | 
			
		||||
DEPENDENCIES = ["esp32"]
 | 
			
		||||
DEPENDENCIES = ["api", "esp32"]
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
 | 
			
		||||
CONF_CONNECTIONS = "connections"
 | 
			
		||||
MAX_CONNECTIONS = 3
 | 
			
		||||
 | 
			
		||||
bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy")
 | 
			
		||||
 | 
			
		||||
BluetoothProxy = bluetooth_proxy_ns.class_(
 | 
			
		||||
    "BluetoothProxy", esp32_ble_client.BLEClientBase
 | 
			
		||||
    "BluetoothProxy", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
 | 
			
		||||
)
 | 
			
		||||
BluetoothConnection = bluetooth_proxy_ns.class_(
 | 
			
		||||
    "BluetoothConnection", esp32_ble_client.BLEClientBase
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
CONNECTION_SCHEMA = esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(BluetoothProxy),
 | 
			
		||||
        cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(BluetoothConnection),
 | 
			
		||||
    }
 | 
			
		||||
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_connections(config):
 | 
			
		||||
    if CONF_CONNECTIONS in config:
 | 
			
		||||
        if not config[CONF_ACTIVE]:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                "Connections can only be used if the proxy is set to active"
 | 
			
		||||
            )
 | 
			
		||||
    else:
 | 
			
		||||
        if config[CONF_ACTIVE]:
 | 
			
		||||
            conf = config.copy()
 | 
			
		||||
            conf[CONF_CONNECTIONS] = [
 | 
			
		||||
                CONNECTION_SCHEMA({}) for _ in range(MAX_CONNECTIONS)
 | 
			
		||||
            ]
 | 
			
		||||
            return conf
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(BluetoothProxy),
 | 
			
		||||
            cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_CONNECTIONS): cv.All(
 | 
			
		||||
                cv.ensure_list(CONNECTION_SCHEMA),
 | 
			
		||||
                cv.Length(min=1, max=MAX_CONNECTIONS),
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    validate_connections,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
@@ -27,7 +64,12 @@ async def to_code(config):
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_active(config[CONF_ACTIVE]))
 | 
			
		||||
    await esp32_ble_tracker.register_ble_device(var, config)
 | 
			
		||||
 | 
			
		||||
    await esp32_ble_tracker.register_client(var, config)
 | 
			
		||||
    for connection_conf in config.get(CONF_CONNECTIONS, []):
 | 
			
		||||
        connection_var = cg.new_Pvariable(connection_conf[CONF_ID])
 | 
			
		||||
        await cg.register_component(connection_var, connection_conf)
 | 
			
		||||
        cg.add(var.register_connection(connection_var))
 | 
			
		||||
        await esp32_ble_tracker.register_client(connection_var, connection_conf)
 | 
			
		||||
 | 
			
		||||
    cg.add_define("USE_BLUETOOTH_PROXY")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										281
									
								
								esphome/components/bluetooth_proxy/bluetooth_connection.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										281
									
								
								esphome/components/bluetooth_proxy/bluetooth_connection.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,281 @@
 | 
			
		||||
#include "bluetooth_connection.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/api/api_server.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "bluetooth_proxy.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bluetooth_proxy {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "bluetooth_proxy.connection";
 | 
			
		||||
 | 
			
		||||
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                              esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_connection(this->address_, false, 0, param->disconnect.reason);
 | 
			
		||||
      this->set_address(0);
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->proxy_->get_bluetooth_connections_limit());
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      if (param->open.conn_id != this->conn_id_)
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(this->address_, false, 0, param->open.status);
 | 
			
		||||
        this->set_address(0);
 | 
			
		||||
        api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
			
		||||
                                                                this->proxy_->get_bluetooth_connections_limit());
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
      if (param->search_cmpl.conn_id != this->conn_id_)
 | 
			
		||||
        break;
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->proxy_->get_bluetooth_connections_limit());
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_READ_DESCR_EVT:
 | 
			
		||||
    case ESP_GATTC_READ_CHAR_EVT: {
 | 
			
		||||
      if (param->read.conn_id != this->conn_id_)
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->read.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->read.handle, param->read.status);
 | 
			
		||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->read.handle, param->read.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTReadResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->read.handle;
 | 
			
		||||
      resp.data.reserve(param->read.value_len);
 | 
			
		||||
      for (uint16_t i = 0; i < param->read.value_len; i++) {
 | 
			
		||||
        resp.data.push_back(param->read.value[i]);
 | 
			
		||||
      }
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_read_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_WRITE_CHAR_EVT:
 | 
			
		||||
    case ESP_GATTC_WRITE_DESCR_EVT: {
 | 
			
		||||
      if (param->write.conn_id != this->conn_id_)
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->write.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->write.handle, param->write.status);
 | 
			
		||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->write.handle, param->write.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTWriteResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->write.handle;
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_write_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      if (this->get_characteristic(param->unreg_for_notify.handle) == nullptr)  // No conn_id in this event
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->unreg_for_notify.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d",
 | 
			
		||||
                 this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle,
 | 
			
		||||
                 param->unreg_for_notify.status);
 | 
			
		||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->unreg_for_notify.handle,
 | 
			
		||||
                                                          param->unreg_for_notify.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTNotifyResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->unreg_for_notify.handle;
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_notify_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      if (this->get_characteristic(param->reg_for_notify.handle) == nullptr)  // No conn_id in this event
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->reg_for_notify.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status);
 | 
			
		||||
        api::global_api_server->send_bluetooth_gatt_error(this->address_, param->reg_for_notify.handle,
 | 
			
		||||
                                                          param->reg_for_notify.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      api::BluetoothGATTNotifyResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->reg_for_notify.handle;
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_notify_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_NOTIFY_EVT: {
 | 
			
		||||
      if (param->notify.conn_id != this->conn_id_)
 | 
			
		||||
        break;
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
               param->notify.handle);
 | 
			
		||||
      api::BluetoothGATTNotifyDataResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->notify.handle;
 | 
			
		||||
      resp.data.reserve(param->notify.value_len);
 | 
			
		||||
      for (uint16_t i = 0; i < param->notify.value_len; i++) {
 | 
			
		||||
        resp.data.push_back(param->notify.value[i]);
 | 
			
		||||
      }
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_notify_data_response(resp);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
 | 
			
		||||
  if (!this->connected()) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_NOT_CONNECTED;
 | 
			
		||||
  }
 | 
			
		||||
  auto *characteristic = this->get_characteristic(handle);
 | 
			
		||||
  if (characteristic == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not found.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_INVALID_HANDLE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           characteristic->uuid.to_string().c_str());
 | 
			
		||||
 | 
			
		||||
  esp_err_t err =
 | 
			
		||||
      esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, characteristic->handle, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char error, err=%d", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), err);
 | 
			
		||||
    return err;
 | 
			
		||||
  }
 | 
			
		||||
  return ESP_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) {
 | 
			
		||||
  if (!this->connected()) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not connected.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_NOT_CONNECTED;
 | 
			
		||||
  }
 | 
			
		||||
  auto *characteristic = this->get_characteristic(handle);
 | 
			
		||||
  if (characteristic == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not found.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_INVALID_HANDLE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           characteristic->uuid.to_string().c_str());
 | 
			
		||||
 | 
			
		||||
  auto err = characteristic->write_value((uint8_t *) data.data(), data.size(),
 | 
			
		||||
                                         response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), err);
 | 
			
		||||
    return err;
 | 
			
		||||
  }
 | 
			
		||||
  return ESP_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
 | 
			
		||||
  if (!this->connected()) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not connected.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_NOT_CONNECTED;
 | 
			
		||||
  }
 | 
			
		||||
  auto *descriptor = this->get_descriptor(handle);
 | 
			
		||||
  if (descriptor == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not found.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_INVALID_HANDLE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           descriptor->uuid.to_string().c_str());
 | 
			
		||||
 | 
			
		||||
  esp_err_t err =
 | 
			
		||||
      esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char_descr error, err=%d", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), err);
 | 
			
		||||
    return err;
 | 
			
		||||
  }
 | 
			
		||||
  return ESP_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data) {
 | 
			
		||||
  if (!this->connected()) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_NOT_CONNECTED;
 | 
			
		||||
  }
 | 
			
		||||
  auto *descriptor = this->get_descriptor(handle);
 | 
			
		||||
  if (descriptor == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not found.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_INVALID_HANDLE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           descriptor->uuid.to_string().c_str());
 | 
			
		||||
 | 
			
		||||
  auto err =
 | 
			
		||||
      esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, data.size(),
 | 
			
		||||
                                     (uint8_t *) data.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), err);
 | 
			
		||||
    return err;
 | 
			
		||||
  }
 | 
			
		||||
  return ESP_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enable) {
 | 
			
		||||
  if (!this->connected()) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not connected.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_NOT_CONNECTED;
 | 
			
		||||
  }
 | 
			
		||||
  auto *characteristic = this->get_characteristic(handle);
 | 
			
		||||
  if (characteristic == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not found.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    return ESP_GATT_INVALID_HANDLE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (enable) {
 | 
			
		||||
    ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications %s", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), characteristic->uuid.to_string().c_str());
 | 
			
		||||
    esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
 | 
			
		||||
    if (err != ESP_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->connection_index_,
 | 
			
		||||
               this->address_str_.c_str(), err);
 | 
			
		||||
      return err;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications %s", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), characteristic->uuid.to_string().c_str());
 | 
			
		||||
    esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
 | 
			
		||||
    if (err != ESP_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_unregister_for_notify failed, err=%d", this->connection_index_,
 | 
			
		||||
               this->address_str_.c_str(), err);
 | 
			
		||||
      return err;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return ESP_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace bluetooth_proxy
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
							
								
								
									
										34
									
								
								esphome/components/bluetooth_proxy/bluetooth_connection.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								esphome/components/bluetooth_proxy/bluetooth_connection.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bluetooth_proxy {
 | 
			
		||||
 | 
			
		||||
class BluetoothProxy;
 | 
			
		||||
 | 
			
		||||
class BluetoothConnection : public esp32_ble_client::BLEClientBase {
 | 
			
		||||
 public:
 | 
			
		||||
  bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
 | 
			
		||||
  esp_err_t read_characteristic(uint16_t handle);
 | 
			
		||||
  esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);
 | 
			
		||||
  esp_err_t read_descriptor(uint16_t handle);
 | 
			
		||||
  esp_err_t write_descriptor(uint16_t handle, const std::string &data);
 | 
			
		||||
 | 
			
		||||
  esp_err_t notify_characteristic(uint16_t handle, bool enable);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  friend class BluetoothProxy;
 | 
			
		||||
 | 
			
		||||
  int16_t send_service_{-1};
 | 
			
		||||
  BluetoothProxy *proxy_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace bluetooth_proxy
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
@@ -4,43 +4,26 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API
 | 
			
		||||
#include "esphome/components/api/api_server.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bluetooth_proxy {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "bluetooth_proxy";
 | 
			
		||||
 | 
			
		||||
BluetoothProxy::BluetoothProxy() {
 | 
			
		||||
  global_bluetooth_proxy = this;
 | 
			
		||||
  this->address_ = 0;
 | 
			
		||||
}
 | 
			
		||||
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
 | 
			
		||||
 | 
			
		||||
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
			
		||||
  if (!api::global_api_server->is_connected())
 | 
			
		||||
    return false;
 | 
			
		||||
  ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(),
 | 
			
		||||
           device.get_rssi());
 | 
			
		||||
  this->send_api_packet_(device);
 | 
			
		||||
 | 
			
		||||
  this->address_type_map_[device.address_uint64()] = device.get_address_type();
 | 
			
		||||
 | 
			
		||||
  if (this->address_ == 0)
 | 
			
		||||
    return true;
 | 
			
		||||
 | 
			
		||||
  if (this->state_ == espbt::ClientState::DISCOVERED) {
 | 
			
		||||
    ESP_LOGV(TAG, "Connecting to address %s", this->address_str().c_str());
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  BLEClientBase::parse_device(device);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
 | 
			
		||||
#ifndef USE_API
 | 
			
		||||
  return;
 | 
			
		||||
#else
 | 
			
		||||
  api::BluetoothLEAdvertisementResponse resp;
 | 
			
		||||
  resp.address = device.address_uint64();
 | 
			
		||||
  if (!device.get_name().empty())
 | 
			
		||||
@@ -62,152 +45,103 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
 | 
			
		||||
    resp.manufacturer_data.push_back(std::move(manufacturer_data));
 | 
			
		||||
  }
 | 
			
		||||
  api::global_api_server->send_bluetooth_le_advertisement(resp);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                         esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  BLEClientBase::gattc_event_handler(event, gattc_if, param);
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
#ifdef USE_API
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_,
 | 
			
		||||
                                                               param->disconnect.reason);
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->get_bluetooth_connections_limit());
 | 
			
		||||
#endif
 | 
			
		||||
      this->address_ = 0;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
 | 
			
		||||
#ifdef USE_API
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_, param->open.status);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
#ifdef USE_API
 | 
			
		||||
      api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->get_bluetooth_connections_limit());
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_READ_DESCR_EVT:
 | 
			
		||||
    case ESP_GATTC_READ_CHAR_EVT: {
 | 
			
		||||
      if (param->read.conn_id != this->conn_id_)
 | 
			
		||||
        break;
 | 
			
		||||
      if (param->read.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "Error reading char/descriptor at handle %d, status=%d", param->read.handle, param->read.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
#ifdef USE_API
 | 
			
		||||
      api::BluetoothGATTReadResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->read.handle;
 | 
			
		||||
      resp.data.reserve(param->read.value_len);
 | 
			
		||||
      for (uint16_t i = 0; i < param->read.value_len; i++) {
 | 
			
		||||
        resp.data.push_back(param->read.value[i]);
 | 
			
		||||
      }
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_read_response(resp);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_NOTIFY_EVT: {
 | 
			
		||||
      if (param->notify.conn_id != this->conn_id_)
 | 
			
		||||
        break;
 | 
			
		||||
      ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT: handle=0x%x", param->notify.handle);
 | 
			
		||||
#ifdef USE_API
 | 
			
		||||
      api::BluetoothGATTNotifyDataResponse resp;
 | 
			
		||||
      resp.address = this->address_;
 | 
			
		||||
      resp.handle = param->notify.handle;
 | 
			
		||||
      resp.data.reserve(param->notify.value_len);
 | 
			
		||||
      for (uint16_t i = 0; i < param->notify.value_len; i++) {
 | 
			
		||||
        resp.data.push_back(param->notify.value[i]);
 | 
			
		||||
      }
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_notify_data_response(resp);
 | 
			
		||||
#endif
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
void BluetoothProxy::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Active: %s", YESNO(this->active_));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); }
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::loop() {
 | 
			
		||||
  BLEClientBase::loop();
 | 
			
		||||
#ifdef USE_API
 | 
			
		||||
  if (this->state_ != espbt::ClientState::IDLE && !api::global_api_server->is_connected()) {
 | 
			
		||||
    ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str());
 | 
			
		||||
    auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
 | 
			
		||||
    if (err != ERR_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->send_service_ == this->services_.size()) {
 | 
			
		||||
    this->send_service_ = -1;
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_services_done(this->address_);
 | 
			
		||||
  } else if (this->send_service_ >= 0) {
 | 
			
		||||
    auto &service = this->services_[this->send_service_];
 | 
			
		||||
    api::BluetoothGATTGetServicesResponse resp;
 | 
			
		||||
    resp.address = this->address_;
 | 
			
		||||
    api::BluetoothGATTService service_resp;
 | 
			
		||||
    service_resp.uuid = {service->uuid.get_128bit_high(), service->uuid.get_128bit_low()};
 | 
			
		||||
    service_resp.handle = service->start_handle;
 | 
			
		||||
    for (auto &characteristic : service->characteristics) {
 | 
			
		||||
      api::BluetoothGATTCharacteristic characteristic_resp;
 | 
			
		||||
      characteristic_resp.uuid = {characteristic->uuid.get_128bit_high(), characteristic->uuid.get_128bit_low()};
 | 
			
		||||
      characteristic_resp.handle = characteristic->handle;
 | 
			
		||||
      characteristic_resp.properties = characteristic->properties;
 | 
			
		||||
      for (auto &descriptor : characteristic->descriptors) {
 | 
			
		||||
        api::BluetoothGATTDescriptor descriptor_resp;
 | 
			
		||||
        descriptor_resp.uuid = {descriptor->uuid.get_128bit_high(), descriptor->uuid.get_128bit_low()};
 | 
			
		||||
        descriptor_resp.handle = descriptor->handle;
 | 
			
		||||
        characteristic_resp.descriptors.push_back(std::move(descriptor_resp));
 | 
			
		||||
  if (!api::global_api_server->is_connected()) {
 | 
			
		||||
    for (auto *connection : this->connections_) {
 | 
			
		||||
      if (connection->get_address() != 0) {
 | 
			
		||||
        connection->disconnect();
 | 
			
		||||
      }
 | 
			
		||||
      service_resp.characteristics.push_back(std::move(characteristic_resp));
 | 
			
		||||
    }
 | 
			
		||||
    resp.services.push_back(std::move(service_resp));
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_services(resp);
 | 
			
		||||
    this->send_service_++;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  for (auto *connection : this->connections_) {
 | 
			
		||||
    if (connection->send_service_ == connection->services_.size()) {
 | 
			
		||||
      connection->send_service_ = -1;
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_services_done(connection->get_address());
 | 
			
		||||
    } else if (connection->send_service_ >= 0) {
 | 
			
		||||
      auto &service = connection->services_[connection->send_service_];
 | 
			
		||||
      api::BluetoothGATTGetServicesResponse resp;
 | 
			
		||||
      resp.address = connection->get_address();
 | 
			
		||||
      api::BluetoothGATTService service_resp;
 | 
			
		||||
      service_resp.uuid = {service->uuid.get_128bit_high(), service->uuid.get_128bit_low()};
 | 
			
		||||
      service_resp.handle = service->start_handle;
 | 
			
		||||
      for (auto &characteristic : service->characteristics) {
 | 
			
		||||
        api::BluetoothGATTCharacteristic characteristic_resp;
 | 
			
		||||
        characteristic_resp.uuid = {characteristic->uuid.get_128bit_high(), characteristic->uuid.get_128bit_low()};
 | 
			
		||||
        characteristic_resp.handle = characteristic->handle;
 | 
			
		||||
        characteristic_resp.properties = characteristic->properties;
 | 
			
		||||
        for (auto &descriptor : characteristic->descriptors) {
 | 
			
		||||
          api::BluetoothGATTDescriptor descriptor_resp;
 | 
			
		||||
          descriptor_resp.uuid = {descriptor->uuid.get_128bit_high(), descriptor->uuid.get_128bit_low()};
 | 
			
		||||
          descriptor_resp.handle = descriptor->handle;
 | 
			
		||||
          characteristic_resp.descriptors.push_back(std::move(descriptor_resp));
 | 
			
		||||
        }
 | 
			
		||||
        service_resp.characteristics.push_back(std::move(characteristic_resp));
 | 
			
		||||
      }
 | 
			
		||||
      resp.services.push_back(std::move(service_resp));
 | 
			
		||||
      api::global_api_server->send_bluetooth_gatt_services(resp);
 | 
			
		||||
      connection->send_service_++;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API
 | 
			
		||||
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
 | 
			
		||||
  for (auto *connection : this->connections_) {
 | 
			
		||||
    if (connection->get_address() == address)
 | 
			
		||||
      return connection;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!reserve)
 | 
			
		||||
    return nullptr;
 | 
			
		||||
 | 
			
		||||
  for (auto *connection : this->connections_) {
 | 
			
		||||
    if (connection->get_address() == 0) {
 | 
			
		||||
      connection->set_address(address);
 | 
			
		||||
      return connection;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
 | 
			
		||||
  switch (msg.request_type) {
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
 | 
			
		||||
      this->address_ = msg.address;
 | 
			
		||||
      if (this->address_type_map_.find(this->address_) != this->address_type_map_.end()) {
 | 
			
		||||
        // Utilise the address type cache
 | 
			
		||||
        this->remote_addr_type_ = this->address_type_map_[this->address_];
 | 
			
		||||
      } else {
 | 
			
		||||
        this->remote_addr_type_ = BLE_ADDR_TYPE_PUBLIC;
 | 
			
		||||
      auto *connection = this->get_connection_(msg.address, true);
 | 
			
		||||
      if (connection == nullptr) {
 | 
			
		||||
        ESP_LOGW(TAG, "No free connections available");
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(msg.address, false);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      this->remote_bda_[0] = (this->address_ >> 40) & 0xFF;
 | 
			
		||||
      this->remote_bda_[1] = (this->address_ >> 32) & 0xFF;
 | 
			
		||||
      this->remote_bda_[2] = (this->address_ >> 24) & 0xFF;
 | 
			
		||||
      this->remote_bda_[3] = (this->address_ >> 16) & 0xFF;
 | 
			
		||||
      this->remote_bda_[4] = (this->address_ >> 8) & 0xFF;
 | 
			
		||||
      this->remote_bda_[5] = (this->address_ >> 0) & 0xFF;
 | 
			
		||||
      this->set_state(espbt::ClientState::DISCOVERED);
 | 
			
		||||
      esp_ble_gap_stop_scanning();
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] [%s] Searching to connect", connection->get_connection_index(),
 | 
			
		||||
               connection->address_str().c_str());
 | 
			
		||||
      connection->set_state(espbt::ClientState::SEARCHING);
 | 
			
		||||
      api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
			
		||||
                                                              this->get_bluetooth_connections_limit());
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: {
 | 
			
		||||
      if (this->state() != espbt::ClientState::IDLE) {
 | 
			
		||||
        ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str());
 | 
			
		||||
        auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
 | 
			
		||||
        if (err != ERR_OK) {
 | 
			
		||||
          ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err);
 | 
			
		||||
        }
 | 
			
		||||
      auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
      if (connection == nullptr) {
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(msg.address, false);
 | 
			
		||||
        api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
			
		||||
                                                                this->get_bluetooth_connections_limit());
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (connection->state() != espbt::ClientState::IDLE) {
 | 
			
		||||
        connection->disconnect();
 | 
			
		||||
      } else {
 | 
			
		||||
        connection->set_address(0);
 | 
			
		||||
        api::global_api_server->send_bluetooth_device_connection(msg.address, false);
 | 
			
		||||
        api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
 | 
			
		||||
                                                                this->get_bluetooth_connections_limit());
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -218,141 +152,91 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
 | 
			
		||||
  if (this->state_ != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->address_ != msg.address) {
 | 
			
		||||
    ESP_LOGW(TAG, "Address mismatch for read GATT characteristic request");
 | 
			
		||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
  if (connection == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto *characteristic = this->get_characteristic(msg.handle);
 | 
			
		||||
  if (characteristic == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot read GATT characteristic, not found.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Reading GATT characteristic %s", characteristic->uuid.to_string().c_str());
 | 
			
		||||
 | 
			
		||||
  esp_err_t err =
 | 
			
		||||
      esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, characteristic->handle, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err);
 | 
			
		||||
  auto err = connection->read_characteristic(msg.handle);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) {
 | 
			
		||||
  if (this->state_ != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->address_ != msg.address) {
 | 
			
		||||
    ESP_LOGW(TAG, "Address mismatch for write GATT characteristic request");
 | 
			
		||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
  if (connection == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto *characteristic = this->get_characteristic(msg.handle);
 | 
			
		||||
  if (characteristic == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot write GATT characteristic, not found.");
 | 
			
		||||
    return;
 | 
			
		||||
  auto err = connection->write_characteristic(msg.handle, msg.data, msg.response);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Writing GATT characteristic %s", characteristic->uuid.to_string().c_str());
 | 
			
		||||
  characteristic->write_value((uint8_t *) msg.data.data(), msg.data.size(),
 | 
			
		||||
                              msg.response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) {
 | 
			
		||||
  if (this->state_ != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not connected.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->address_ != msg.address) {
 | 
			
		||||
    ESP_LOGW(TAG, "Address mismatch for read GATT characteristic descriptor request");
 | 
			
		||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
  if (connection == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot read GATT descriptor, not connected");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto *descriptor = this->get_descriptor(msg.handle);
 | 
			
		||||
  if (descriptor == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not found.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Reading GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(),
 | 
			
		||||
           descriptor->uuid.to_string().c_str());
 | 
			
		||||
 | 
			
		||||
  esp_err_t err =
 | 
			
		||||
      esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err);
 | 
			
		||||
  auto err = connection->read_descriptor(msg.handle);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) {
 | 
			
		||||
  if (this->state_ != espbt::ClientState::ESTABLISHED) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not connected.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->address_ != msg.address) {
 | 
			
		||||
    ESP_LOGW(TAG, "Address mismatch for write GATT characteristic descriptor request");
 | 
			
		||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
  if (connection == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot write GATT descriptor, not connected");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto *descriptor = this->get_descriptor(msg.handle);
 | 
			
		||||
  if (descriptor == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not found.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Writing GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(),
 | 
			
		||||
           descriptor->uuid.to_string().c_str());
 | 
			
		||||
 | 
			
		||||
  esp_err_t err =
 | 
			
		||||
      esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, msg.data.size(),
 | 
			
		||||
                                     (uint8_t *) msg.data.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, err=%d", err);
 | 
			
		||||
  auto err = connection->write_descriptor(msg.handle, msg.data);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) {
 | 
			
		||||
  if (this->address_ != msg.address) {
 | 
			
		||||
    ESP_LOGW(TAG, "Address mismatch for service list request");
 | 
			
		||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
  if (connection == nullptr || !connection->connected()) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot get GATT services, not connected");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->send_service_ = 0;
 | 
			
		||||
  if (connection->services_.empty()) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str());
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_services_done(msg.address);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (connection->send_service_ == -1)  // Don't start sending services again if we're already sending them
 | 
			
		||||
    connection->send_service_ = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) {
 | 
			
		||||
  if (this->address_ != msg.address) {
 | 
			
		||||
    ESP_LOGW(TAG, "Address mismatch for notify");
 | 
			
		||||
  auto *connection = this->get_connection_(msg.address, false);
 | 
			
		||||
  if (connection == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot notify GATT characteristic, not connected");
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto *characteristic = this->get_characteristic(msg.handle);
 | 
			
		||||
 | 
			
		||||
  if (characteristic == nullptr) {
 | 
			
		||||
    ESP_LOGW(TAG, "Cannot notify GATT characteristic, not found.");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
  if (msg.enable) {
 | 
			
		||||
    err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
 | 
			
		||||
    if (err != ESP_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, err=%d", err);
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
 | 
			
		||||
    if (err != ESP_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "esp_ble_gattc_unregister_for_notify failed, err=%d", err);
 | 
			
		||||
    }
 | 
			
		||||
  auto err = connection->notify_characteristic(msg.handle, msg.enable);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
BluetoothProxy *global_bluetooth_proxy = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
}  // namespace bluetooth_proxy
 | 
			
		||||
 
 | 
			
		||||
@@ -4,34 +4,34 @@
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/api/api_pb2.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
 | 
			
		||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API
 | 
			
		||||
#include "esphome/components/api/api_pb2.h"
 | 
			
		||||
#endif  // USE_API
 | 
			
		||||
#include "bluetooth_connection.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bluetooth_proxy {
 | 
			
		||||
 | 
			
		||||
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
 | 
			
		||||
 | 
			
		||||
using namespace esp32_ble_client;
 | 
			
		||||
 | 
			
		||||
class BluetoothProxy : public BLEClientBase {
 | 
			
		||||
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  BluetoothProxy();
 | 
			
		||||
  bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
  void register_connection(BluetoothConnection *connection) {
 | 
			
		||||
    this->connections_.push_back(connection);
 | 
			
		||||
    connection->proxy_ = this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API
 | 
			
		||||
  void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
 | 
			
		||||
  void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg);
 | 
			
		||||
  void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg);
 | 
			
		||||
@@ -39,10 +39,17 @@ class BluetoothProxy : public BLEClientBase {
 | 
			
		||||
  void bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg);
 | 
			
		||||
  void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg);
 | 
			
		||||
  void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  int get_bluetooth_connections_free() { return this->state_ == espbt::ClientState::IDLE ? 1 : 0; }
 | 
			
		||||
  int get_bluetooth_connections_limit() { return 1; }
 | 
			
		||||
  int get_bluetooth_connections_free() {
 | 
			
		||||
    int free = 0;
 | 
			
		||||
    for (auto *connection : this->connections_) {
 | 
			
		||||
      if (connection->address_ == 0) {
 | 
			
		||||
        free++;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return free;
 | 
			
		||||
  }
 | 
			
		||||
  int get_bluetooth_connections_limit() { return this->connections_.size(); }
 | 
			
		||||
 | 
			
		||||
  void set_active(bool active) { this->active_ = active; }
 | 
			
		||||
  bool has_active() { return this->active_; }
 | 
			
		||||
@@ -50,9 +57,12 @@ class BluetoothProxy : public BLEClientBase {
 | 
			
		||||
 protected:
 | 
			
		||||
  void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
 | 
			
		||||
 | 
			
		||||
  std::map<uint64_t, esp_ble_addr_type_t> address_type_map_;
 | 
			
		||||
  BluetoothConnection *get_connection_(uint64_t address, bool reserve);
 | 
			
		||||
 | 
			
		||||
  int16_t send_service_{-1};
 | 
			
		||||
  bool active_;
 | 
			
		||||
 | 
			
		||||
  std::vector<BluetoothConnection *> connections_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern BluetoothProxy *global_bluetooth_proxy;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.final_validate as fv
 | 
			
		||||
from esphome.components import web_server_base
 | 
			
		||||
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
from esphome.const import CONF_ID, CONF_NETWORKS, CONF_PASSWORD, CONF_SSID, CONF_WIFI
 | 
			
		||||
from esphome.core import coroutine_with_priority, CORE
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["web_server_base"]
 | 
			
		||||
@@ -12,6 +13,7 @@ CODEOWNERS = ["@OttoWinter"]
 | 
			
		||||
captive_portal_ns = cg.esphome_ns.namespace("captive_portal")
 | 
			
		||||
CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component)
 | 
			
		||||
 | 
			
		||||
CONF_KEEP_USER_CREDENTIALS = "keep_user_credentials"
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
@@ -19,12 +21,29 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
 | 
			
		||||
                web_server_base.WebServerBase
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_KEEP_USER_CREDENTIALS, default=False): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    cv.only_with_arduino,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_wifi(config):
 | 
			
		||||
    wifi_conf = fv.full_config.get()[CONF_WIFI]
 | 
			
		||||
    if config.get(CONF_KEEP_USER_CREDENTIALS, False) and (
 | 
			
		||||
        CONF_SSID in wifi_conf
 | 
			
		||||
        or CONF_PASSWORD in wifi_conf
 | 
			
		||||
        or CONF_NETWORKS in wifi_conf
 | 
			
		||||
    ):
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"WiFi credentials cannot be used together with {CONF_KEEP_USER_CREDENTIALS}"
 | 
			
		||||
        )
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = validate_wifi
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(64.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
 | 
			
		||||
@@ -38,3 +57,6 @@ async def to_code(config):
 | 
			
		||||
        cg.add_library("WiFi", None)
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        cg.add_library("DNSServer", None)
 | 
			
		||||
 | 
			
		||||
    if config.get(CONF_KEEP_USER_CREDENTIALS, False):
 | 
			
		||||
        cg.add_define("USE_CAPTIVE_PORTAL_KEEP_USER_CREDENTIALS")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								esphome/components/daikin_brc/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/daikin_brc/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
CODEOWNERS = ["@hagak"]
 | 
			
		||||
							
								
								
									
										24
									
								
								esphome/components/daikin_brc/climate.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								esphome/components/daikin_brc/climate.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import climate_ir
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["climate_ir"]
 | 
			
		||||
 | 
			
		||||
daikin_brc_ns = cg.esphome_ns.namespace("daikin_brc")
 | 
			
		||||
DaikinBrcClimate = daikin_brc_ns.class_("DaikinBrcClimate", climate_ir.ClimateIR)
 | 
			
		||||
 | 
			
		||||
CONF_USE_FAHRENHEIT = "use_fahrenheit"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(DaikinBrcClimate),
 | 
			
		||||
        cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await climate_ir.register_climate_ir(var, config)
 | 
			
		||||
    cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT]))
 | 
			
		||||
							
								
								
									
										273
									
								
								esphome/components/daikin_brc/daikin_brc.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										273
									
								
								esphome/components/daikin_brc/daikin_brc.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,273 @@
 | 
			
		||||
#include "daikin_brc.h"
 | 
			
		||||
#include "esphome/components/remote_base/remote_base.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace daikin_brc {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "daikin_brc.climate";
 | 
			
		||||
 | 
			
		||||
void DaikinBrcClimate::control(const climate::ClimateCall &call) {
 | 
			
		||||
  this->mode_button_ = 0x00;
 | 
			
		||||
  if (call.get_mode().has_value()) {
 | 
			
		||||
    // Need to determine if this is call due to Mode button pressed so that we can set the Mode button byte
 | 
			
		||||
    this->mode_button_ = DAIKIN_BRC_IR_MODE_BUTTON;
 | 
			
		||||
  }
 | 
			
		||||
  ClimateIR::control(call);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DaikinBrcClimate::transmit_state() {
 | 
			
		||||
  uint8_t remote_state[DAIKIN_BRC_TRANSMIT_FRAME_SIZE] = {0x11, 0xDA, 0x17, 0x18, 0x04, 0x00, 0x1E, 0x11,
 | 
			
		||||
                                                          0xDA, 0x17, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
                                                          0x00, 0x00, 0x00, 0x00, 0x20, 0x00};
 | 
			
		||||
 | 
			
		||||
  remote_state[12] = this->alt_mode_();
 | 
			
		||||
  remote_state[13] = this->mode_button_;
 | 
			
		||||
  remote_state[14] = this->operation_mode_();
 | 
			
		||||
  remote_state[17] = this->temperature_();
 | 
			
		||||
  remote_state[18] = this->fan_speed_swing_();
 | 
			
		||||
 | 
			
		||||
  // Calculate checksum
 | 
			
		||||
  for (int i = DAIKIN_BRC_PREAMBLE_SIZE; i < DAIKIN_BRC_TRANSMIT_FRAME_SIZE - 1; i++) {
 | 
			
		||||
    remote_state[DAIKIN_BRC_TRANSMIT_FRAME_SIZE - 1] += remote_state[i];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto transmit = this->transmitter_->transmit();
 | 
			
		||||
  auto *data = transmit.get_data();
 | 
			
		||||
  data->set_carrier_frequency(DAIKIN_BRC_IR_FREQUENCY);
 | 
			
		||||
 | 
			
		||||
  data->mark(DAIKIN_BRC_HEADER_MARK);
 | 
			
		||||
  data->space(DAIKIN_BRC_HEADER_SPACE);
 | 
			
		||||
  for (int i = 0; i < DAIKIN_BRC_PREAMBLE_SIZE; i++) {
 | 
			
		||||
    for (uint8_t mask = 1; mask > 0; mask <<= 1) {  // iterate through bit mask
 | 
			
		||||
      data->mark(DAIKIN_BRC_BIT_MARK);
 | 
			
		||||
      bool bit = remote_state[i] & mask;
 | 
			
		||||
      data->space(bit ? DAIKIN_BRC_ONE_SPACE : DAIKIN_BRC_ZERO_SPACE);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  data->mark(DAIKIN_BRC_BIT_MARK);
 | 
			
		||||
  data->space(DAIKIN_BRC_MESSAGE_SPACE);
 | 
			
		||||
  data->mark(DAIKIN_BRC_HEADER_MARK);
 | 
			
		||||
  data->space(DAIKIN_BRC_HEADER_SPACE);
 | 
			
		||||
 | 
			
		||||
  for (int i = DAIKIN_BRC_PREAMBLE_SIZE; i < DAIKIN_BRC_TRANSMIT_FRAME_SIZE; i++) {
 | 
			
		||||
    for (uint8_t mask = 1; mask > 0; mask <<= 1) {  // iterate through bit mask
 | 
			
		||||
      data->mark(DAIKIN_BRC_BIT_MARK);
 | 
			
		||||
      bool bit = remote_state[i] & mask;
 | 
			
		||||
      data->space(bit ? DAIKIN_BRC_ONE_SPACE : DAIKIN_BRC_ZERO_SPACE);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  data->mark(DAIKIN_BRC_BIT_MARK);
 | 
			
		||||
  data->space(0);
 | 
			
		||||
 | 
			
		||||
  transmit.perform();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t DaikinBrcClimate::alt_mode_() {
 | 
			
		||||
  uint8_t alt_mode = 0x00;
 | 
			
		||||
  switch (this->mode) {
 | 
			
		||||
    case climate::CLIMATE_MODE_DRY:
 | 
			
		||||
      alt_mode = 0x23;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_FAN_ONLY:
 | 
			
		||||
      alt_mode = 0x63;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_HEAT_COOL:
 | 
			
		||||
    case climate::CLIMATE_MODE_COOL:
 | 
			
		||||
    case climate::CLIMATE_MODE_HEAT:
 | 
			
		||||
    default:
 | 
			
		||||
      alt_mode = 0x73;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  return alt_mode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t DaikinBrcClimate::operation_mode_() {
 | 
			
		||||
  uint8_t operating_mode = DAIKIN_BRC_MODE_ON;
 | 
			
		||||
  switch (this->mode) {
 | 
			
		||||
    case climate::CLIMATE_MODE_COOL:
 | 
			
		||||
      operating_mode |= DAIKIN_BRC_MODE_COOL;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_DRY:
 | 
			
		||||
      operating_mode |= DAIKIN_BRC_MODE_DRY;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_HEAT:
 | 
			
		||||
      operating_mode |= DAIKIN_BRC_MODE_HEAT;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_HEAT_COOL:
 | 
			
		||||
      operating_mode |= DAIKIN_BRC_MODE_AUTO;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_FAN_ONLY:
 | 
			
		||||
      operating_mode |= DAIKIN_BRC_MODE_FAN;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_MODE_OFF:
 | 
			
		||||
    default:
 | 
			
		||||
      operating_mode = DAIKIN_BRC_MODE_OFF;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return operating_mode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t DaikinBrcClimate::fan_speed_swing_() {
 | 
			
		||||
  uint16_t fan_speed;
 | 
			
		||||
  switch (this->fan_mode.value()) {
 | 
			
		||||
    case climate::CLIMATE_FAN_LOW:
 | 
			
		||||
      fan_speed = DAIKIN_BRC_FAN_1;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_MEDIUM:
 | 
			
		||||
      fan_speed = DAIKIN_BRC_FAN_2;
 | 
			
		||||
      break;
 | 
			
		||||
    case climate::CLIMATE_FAN_HIGH:
 | 
			
		||||
      fan_speed = DAIKIN_BRC_FAN_3;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      fan_speed = DAIKIN_BRC_FAN_1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // If swing is enabled switch first 4 bits to 1111
 | 
			
		||||
  switch (this->swing_mode) {
 | 
			
		||||
    case climate::CLIMATE_SWING_BOTH:
 | 
			
		||||
      fan_speed |= DAIKIN_BRC_IR_SWING_ON;
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      fan_speed |= DAIKIN_BRC_IR_SWING_OFF;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  return fan_speed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t DaikinBrcClimate::temperature_() {
 | 
			
		||||
  // Force special temperatures depending on the mode
 | 
			
		||||
  switch (this->mode) {
 | 
			
		||||
    case climate::CLIMATE_MODE_FAN_ONLY:
 | 
			
		||||
    case climate::CLIMATE_MODE_DRY:
 | 
			
		||||
      if (this->fahrenheit_) {
 | 
			
		||||
        return DAIKIN_BRC_IR_DRY_FAN_TEMP_F;
 | 
			
		||||
      }
 | 
			
		||||
      return DAIKIN_BRC_IR_DRY_FAN_TEMP_C;
 | 
			
		||||
    case climate::CLIMATE_MODE_HEAT_COOL:
 | 
			
		||||
    default:
 | 
			
		||||
      uint8_t temperature;
 | 
			
		||||
      // Temperature in remote is in F
 | 
			
		||||
      if (this->fahrenheit_) {
 | 
			
		||||
        temperature = (uint8_t) roundf(
 | 
			
		||||
            clamp<float>(((this->target_temperature * 1.8) + 32), DAIKIN_BRC_TEMP_MIN_F, DAIKIN_BRC_TEMP_MAX_F));
 | 
			
		||||
      } else {
 | 
			
		||||
        temperature = ((uint8_t) roundf(this->target_temperature) - 9) << 1;
 | 
			
		||||
      }
 | 
			
		||||
      return temperature;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DaikinBrcClimate::parse_state_frame_(const uint8_t frame[]) {
 | 
			
		||||
  uint8_t checksum = 0;
 | 
			
		||||
  for (int i = 0; i < (DAIKIN_BRC_STATE_FRAME_SIZE - 1); i++) {
 | 
			
		||||
    checksum += frame[i];
 | 
			
		||||
  }
 | 
			
		||||
  if (frame[DAIKIN_BRC_STATE_FRAME_SIZE - 1] != checksum) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "Bad CheckSum %x", checksum);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t mode = frame[7];
 | 
			
		||||
  if (mode & DAIKIN_BRC_MODE_ON) {
 | 
			
		||||
    switch (mode & 0xF0) {
 | 
			
		||||
      case DAIKIN_BRC_MODE_COOL:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_COOL;
 | 
			
		||||
        break;
 | 
			
		||||
      case DAIKIN_BRC_MODE_DRY:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_DRY;
 | 
			
		||||
        break;
 | 
			
		||||
      case DAIKIN_BRC_MODE_HEAT:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_HEAT;
 | 
			
		||||
        break;
 | 
			
		||||
      case DAIKIN_BRC_MODE_AUTO:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_HEAT_COOL;
 | 
			
		||||
        break;
 | 
			
		||||
      case DAIKIN_BRC_MODE_FAN:
 | 
			
		||||
        this->mode = climate::CLIMATE_MODE_FAN_ONLY;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    this->mode = climate::CLIMATE_MODE_OFF;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t temperature = frame[10];
 | 
			
		||||
  float temperature_c;
 | 
			
		||||
  if (this->fahrenheit_) {
 | 
			
		||||
    temperature_c = clamp<float>(((temperature - 32) / 1.8), DAIKIN_BRC_TEMP_MIN_C, DAIKIN_BRC_TEMP_MAX_C);
 | 
			
		||||
  } else {
 | 
			
		||||
    temperature_c = (temperature >> 1) + 9;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->target_temperature = temperature_c;
 | 
			
		||||
 | 
			
		||||
  uint8_t fan_mode = frame[11];
 | 
			
		||||
  uint8_t swing_mode = frame[11];
 | 
			
		||||
  switch (swing_mode & 0xF) {
 | 
			
		||||
    case DAIKIN_BRC_IR_SWING_ON:
 | 
			
		||||
      this->swing_mode = climate::CLIMATE_SWING_BOTH;
 | 
			
		||||
      break;
 | 
			
		||||
    case DAIKIN_BRC_IR_SWING_OFF:
 | 
			
		||||
      this->swing_mode = climate::CLIMATE_SWING_OFF;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (fan_mode & 0xF0) {
 | 
			
		||||
    case DAIKIN_BRC_FAN_1:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_LOW;
 | 
			
		||||
      break;
 | 
			
		||||
    case DAIKIN_BRC_FAN_2:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
 | 
			
		||||
      break;
 | 
			
		||||
    case DAIKIN_BRC_FAN_3:
 | 
			
		||||
      this->fan_mode = climate::CLIMATE_FAN_HIGH;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  this->publish_state();
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DaikinBrcClimate::on_receive(remote_base::RemoteReceiveData data) {
 | 
			
		||||
  uint8_t state_frame[DAIKIN_BRC_STATE_FRAME_SIZE] = {};
 | 
			
		||||
  if (!data.expect_item(DAIKIN_BRC_HEADER_MARK, DAIKIN_BRC_HEADER_SPACE)) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  for (uint8_t pos = 0; pos < DAIKIN_BRC_STATE_FRAME_SIZE; pos++) {
 | 
			
		||||
    uint8_t byte = 0;
 | 
			
		||||
    for (int8_t bit = 0; bit < 8; bit++) {
 | 
			
		||||
      if (data.expect_item(DAIKIN_BRC_BIT_MARK, DAIKIN_BRC_ONE_SPACE)) {
 | 
			
		||||
        byte |= 1 << bit;
 | 
			
		||||
      } else if (!data.expect_item(DAIKIN_BRC_BIT_MARK, DAIKIN_BRC_ZERO_SPACE)) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    state_frame[pos] = byte;
 | 
			
		||||
    if (pos == 0) {
 | 
			
		||||
      // frame header
 | 
			
		||||
      if (byte != 0x11)
 | 
			
		||||
        return false;
 | 
			
		||||
    } else if (pos == 1) {
 | 
			
		||||
      // frame header
 | 
			
		||||
      if (byte != 0xDA)
 | 
			
		||||
        return false;
 | 
			
		||||
    } else if (pos == 2) {
 | 
			
		||||
      // frame header
 | 
			
		||||
      if (byte != 0x17)
 | 
			
		||||
        return false;
 | 
			
		||||
    } else if (pos == 3) {
 | 
			
		||||
      // frame header
 | 
			
		||||
      if (byte != 0x18)
 | 
			
		||||
        return false;
 | 
			
		||||
    } else if (pos == 4) {
 | 
			
		||||
      // frame type
 | 
			
		||||
      if (byte != 0x00)
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return this->parse_state_frame_(state_frame);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace daikin_brc
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										82
									
								
								esphome/components/daikin_brc/daikin_brc.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								esphome/components/daikin_brc/daikin_brc.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/climate_ir/climate_ir.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace daikin_brc {
 | 
			
		||||
 | 
			
		||||
// Values for Daikin BRC4CXXX IR Controllers
 | 
			
		||||
// Temperature
 | 
			
		||||
const uint8_t DAIKIN_BRC_TEMP_MIN_F = 60;                                // fahrenheit
 | 
			
		||||
const uint8_t DAIKIN_BRC_TEMP_MAX_F = 90;                                // fahrenheit
 | 
			
		||||
const float DAIKIN_BRC_TEMP_MIN_C = (DAIKIN_BRC_TEMP_MIN_F - 32) / 1.8;  // fahrenheit
 | 
			
		||||
const float DAIKIN_BRC_TEMP_MAX_C = (DAIKIN_BRC_TEMP_MAX_F - 32) / 1.8;  // fahrenheit
 | 
			
		||||
 | 
			
		||||
// Modes
 | 
			
		||||
const uint8_t DAIKIN_BRC_MODE_AUTO = 0x30;
 | 
			
		||||
const uint8_t DAIKIN_BRC_MODE_COOL = 0x20;
 | 
			
		||||
const uint8_t DAIKIN_BRC_MODE_HEAT = 0x10;
 | 
			
		||||
const uint8_t DAIKIN_BRC_MODE_DRY = 0x70;
 | 
			
		||||
const uint8_t DAIKIN_BRC_MODE_FAN = 0x00;
 | 
			
		||||
const uint8_t DAIKIN_BRC_MODE_OFF = 0x00;
 | 
			
		||||
const uint8_t DAIKIN_BRC_MODE_ON = 0x01;
 | 
			
		||||
 | 
			
		||||
// Fan Speed
 | 
			
		||||
const uint8_t DAIKIN_BRC_FAN_1 = 0x10;
 | 
			
		||||
const uint8_t DAIKIN_BRC_FAN_2 = 0x30;
 | 
			
		||||
const uint8_t DAIKIN_BRC_FAN_3 = 0x50;
 | 
			
		||||
const uint8_t DAIKIN_BRC_FAN_AUTO = 0xA0;
 | 
			
		||||
 | 
			
		||||
// IR Transmission
 | 
			
		||||
const uint32_t DAIKIN_BRC_IR_FREQUENCY = 38000;
 | 
			
		||||
const uint32_t DAIKIN_BRC_HEADER_MARK = 5070;
 | 
			
		||||
const uint32_t DAIKIN_BRC_HEADER_SPACE = 2140;
 | 
			
		||||
const uint32_t DAIKIN_BRC_BIT_MARK = 370;
 | 
			
		||||
const uint32_t DAIKIN_BRC_ONE_SPACE = 1780;
 | 
			
		||||
const uint32_t DAIKIN_BRC_ZERO_SPACE = 710;
 | 
			
		||||
const uint32_t DAIKIN_BRC_MESSAGE_SPACE = 29410;
 | 
			
		||||
 | 
			
		||||
const uint8_t DAIKIN_BRC_IR_DRY_FAN_TEMP_F = 72;            // Dry/Fan mode is always 17 Celsius.
 | 
			
		||||
const uint8_t DAIKIN_BRC_IR_DRY_FAN_TEMP_C = (17 - 9) * 2;  // Dry/Fan mode is always 17 Celsius.
 | 
			
		||||
const uint8_t DAIKIN_BRC_IR_SWING_ON = 0x5;
 | 
			
		||||
const uint8_t DAIKIN_BRC_IR_SWING_OFF = 0x6;
 | 
			
		||||
const uint8_t DAIKIN_BRC_IR_MODE_BUTTON = 0x4;  // This is set after a mode action
 | 
			
		||||
 | 
			
		||||
// State Frame size
 | 
			
		||||
const uint8_t DAIKIN_BRC_STATE_FRAME_SIZE = 15;
 | 
			
		||||
// Preamble size
 | 
			
		||||
const uint8_t DAIKIN_BRC_PREAMBLE_SIZE = 7;
 | 
			
		||||
// Transmit Frame size - includes a preamble
 | 
			
		||||
const uint8_t DAIKIN_BRC_TRANSMIT_FRAME_SIZE = DAIKIN_BRC_PREAMBLE_SIZE + DAIKIN_BRC_STATE_FRAME_SIZE;
 | 
			
		||||
 | 
			
		||||
class DaikinBrcClimate : public climate_ir::ClimateIR {
 | 
			
		||||
 public:
 | 
			
		||||
  DaikinBrcClimate()
 | 
			
		||||
      : climate_ir::ClimateIR(DAIKIN_BRC_TEMP_MIN_C, DAIKIN_BRC_TEMP_MAX_C, 0.5f, true, true,
 | 
			
		||||
                              {climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
 | 
			
		||||
                              {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH}) {}
 | 
			
		||||
 | 
			
		||||
  /// Set use of Fahrenheit units
 | 
			
		||||
  void set_fahrenheit(bool value) {
 | 
			
		||||
    this->fahrenheit_ = value;
 | 
			
		||||
    this->temperature_step_ = value ? 0.5f : 1.0f;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t mode_button_ = 0x00;
 | 
			
		||||
  // Capture if the MODE was changed
 | 
			
		||||
  void control(const climate::ClimateCall &call) override;
 | 
			
		||||
  // Transmit via IR the state of this climate controller.
 | 
			
		||||
  void transmit_state() override;
 | 
			
		||||
  uint8_t alt_mode_();
 | 
			
		||||
  uint8_t operation_mode_();
 | 
			
		||||
  uint8_t fan_speed_swing_();
 | 
			
		||||
  uint8_t temperature_();
 | 
			
		||||
  // Handle received IR Buffer
 | 
			
		||||
  bool on_receive(remote_base::RemoteReceiveData data) override;
 | 
			
		||||
  bool parse_state_frame_(const uint8_t frame[]);
 | 
			
		||||
  bool fahrenheit_{false};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace daikin_brc
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, CONF_TX_POWER
 | 
			
		||||
from esphome.core import CORE, TimePeriod
 | 
			
		||||
from esphome.components.esp32 import add_idf_sdkconfig_option
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32"]
 | 
			
		||||
@@ -12,16 +12,47 @@ ESP32BLEBeacon = esp32_ble_beacon_ns.class_("ESP32BLEBeacon", cg.Component)
 | 
			
		||||
 | 
			
		||||
CONF_MAJOR = "major"
 | 
			
		||||
CONF_MINOR = "minor"
 | 
			
		||||
CONF_MIN_INTERVAL = "min_interval"
 | 
			
		||||
CONF_MAX_INTERVAL = "max_interval"
 | 
			
		||||
CONF_MEASURED_POWER = "measured_power"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(ESP32BLEBeacon),
 | 
			
		||||
        cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True),
 | 
			
		||||
        cv.Required(CONF_UUID): cv.uuid,
 | 
			
		||||
        cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t,
 | 
			
		||||
        cv.Optional(CONF_MINOR, default=61958): cv.uint16_t,
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
def validate_config(config):
 | 
			
		||||
    if config[CONF_MIN_INTERVAL] > config.get(CONF_MAX_INTERVAL):
 | 
			
		||||
        raise cv.Invalid("min_interval must be <= max_interval")
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(ESP32BLEBeacon),
 | 
			
		||||
            cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True),
 | 
			
		||||
            cv.Required(CONF_UUID): cv.uuid,
 | 
			
		||||
            cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t,
 | 
			
		||||
            cv.Optional(CONF_MINOR, default=61958): cv.uint16_t,
 | 
			
		||||
            cv.Optional(CONF_MIN_INTERVAL, default="100ms"): cv.All(
 | 
			
		||||
                cv.positive_time_period_milliseconds,
 | 
			
		||||
                cv.Range(
 | 
			
		||||
                    min=TimePeriod(milliseconds=20), max=TimePeriod(milliseconds=10240)
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_MAX_INTERVAL, default="100ms"): cv.All(
 | 
			
		||||
                cv.positive_time_period_milliseconds,
 | 
			
		||||
                cv.Range(
 | 
			
		||||
                    min=TimePeriod(milliseconds=20), max=TimePeriod(milliseconds=10240)
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_MEASURED_POWER, default=-59): cv.int_range(
 | 
			
		||||
                min=-128, max=0
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_TX_POWER, default="3dBm"): cv.All(
 | 
			
		||||
                cv.decibel, cv.one_of(-12, -9, -6, -3, 0, 3, 6, 9, int=True)
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.COMPONENT_SCHEMA),
 | 
			
		||||
    validate_config,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
@@ -31,6 +62,10 @@ async def to_code(config):
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    cg.add(var.set_major(config[CONF_MAJOR]))
 | 
			
		||||
    cg.add(var.set_minor(config[CONF_MINOR]))
 | 
			
		||||
    cg.add(var.set_min_interval(config[CONF_MIN_INTERVAL]))
 | 
			
		||||
    cg.add(var.set_max_interval(config[CONF_MAX_INTERVAL]))
 | 
			
		||||
    cg.add(var.set_measured_power(config[CONF_MEASURED_POWER]))
 | 
			
		||||
    cg.add(var.set_tx_power(config[CONF_TX_POWER]))
 | 
			
		||||
 | 
			
		||||
    if CORE.using_esp_idf:
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
 | 
			
		||||
 
 | 
			
		||||
@@ -36,11 +36,24 @@ static esp_ble_adv_params_t ble_adv_params = {
 | 
			
		||||
#define ENDIAN_CHANGE_U16(x) ((((x) &0xFF00) >> 8) + (((x) &0xFF) << 8))
 | 
			
		||||
 | 
			
		||||
static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = {
 | 
			
		||||
    .flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = 0x004C, .beacon_type = 0x1502};
 | 
			
		||||
    .flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = {0x4C, 0x00}, .beacon_type = {0x02, 0x15}};
 | 
			
		||||
 | 
			
		||||
void ESP32BLEBeacon::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ESP32 BLE Beacon:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Major: %u, Minor: %u", this->major_, this->minor_);
 | 
			
		||||
  char uuid[37];
 | 
			
		||||
  char *bpos = uuid;
 | 
			
		||||
  for (int8_t ii = 0; ii < 16; ++ii) {
 | 
			
		||||
    bpos += sprintf(bpos, "%02X", this->uuid_[ii]);
 | 
			
		||||
    if (ii == 3 || ii == 5 || ii == 7 || ii == 9) {
 | 
			
		||||
      bpos += sprintf(bpos, "-");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  uuid[36] = '\0';
 | 
			
		||||
  ESP_LOGCONFIG(TAG,
 | 
			
		||||
                "  UUID: %s, Major: %u, Minor: %u, Min Interval: %ums, Max Interval: %ums, Measured Power: %d"
 | 
			
		||||
                ", TX Power: %ddBm",
 | 
			
		||||
                uuid, this->major_, this->minor_, this->min_interval_, this->max_interval_, this->measured_power_,
 | 
			
		||||
                this->tx_power_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ESP32BLEBeacon::setup() {
 | 
			
		||||
@@ -67,6 +80,9 @@ void ESP32BLEBeacon::ble_core_task(void *params) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ESP32BLEBeacon::ble_setup() {
 | 
			
		||||
  ble_adv_params.adv_int_min = static_cast<uint16_t>(global_esp32_ble_beacon->min_interval_ / 0.625f);
 | 
			
		||||
  ble_adv_params.adv_int_max = static_cast<uint16_t>(global_esp32_ble_beacon->max_interval_ / 0.625f);
 | 
			
		||||
 | 
			
		||||
  // Initialize non-volatile storage for the bluetooth controller
 | 
			
		||||
  esp_err_t err = nvs_flash_init();
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
@@ -118,6 +134,12 @@ void ESP32BLEBeacon::ble_setup() {
 | 
			
		||||
    ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV,
 | 
			
		||||
                             static_cast<esp_power_level_t>((global_esp32_ble_beacon->tx_power_ + 12) / 3));
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err));
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  err = esp_ble_gap_register_callback(ESP32BLEBeacon::gap_event_handler);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
 | 
			
		||||
@@ -130,7 +152,7 @@ void ESP32BLEBeacon::ble_setup() {
 | 
			
		||||
         sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid));
 | 
			
		||||
  ibeacon_adv_data.ibeacon_vendor.minor = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->minor_);
 | 
			
		||||
  ibeacon_adv_data.ibeacon_vendor.major = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->major_);
 | 
			
		||||
  ibeacon_adv_data.ibeacon_vendor.measured_power = 0xC5;
 | 
			
		||||
  ibeacon_adv_data.ibeacon_vendor.measured_power = static_cast<uint8_t>(global_esp32_ble_beacon->measured_power_);
 | 
			
		||||
 | 
			
		||||
  esp_ble_gap_config_adv_data_raw((uint8_t *) &ibeacon_adv_data, sizeof(ibeacon_adv_data));
 | 
			
		||||
}
 | 
			
		||||
@@ -153,7 +175,7 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: {
 | 
			
		||||
      err = param->adv_start_cmpl.status;
 | 
			
		||||
      err = param->adv_stop_cmpl.status;
 | 
			
		||||
      if (err != ESP_BT_STATUS_SUCCESS) {
 | 
			
		||||
        ESP_LOGE(TAG, "BLE adv stop failed: %s", esp_err_to_name(err));
 | 
			
		||||
      } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <esp_gap_ble_api.h>
 | 
			
		||||
#include <esp_bt.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_ble_beacon {
 | 
			
		||||
@@ -14,8 +15,8 @@ typedef struct {
 | 
			
		||||
  uint8_t flags[3];
 | 
			
		||||
  uint8_t length;
 | 
			
		||||
  uint8_t type;
 | 
			
		||||
  uint16_t company_id;
 | 
			
		||||
  uint16_t beacon_type;
 | 
			
		||||
  uint8_t company_id[2];
 | 
			
		||||
  uint8_t beacon_type[2];
 | 
			
		||||
} __attribute__((packed)) esp_ble_ibeacon_head_t;
 | 
			
		||||
 | 
			
		||||
// NOLINTNEXTLINE(modernize-use-using)
 | 
			
		||||
@@ -42,6 +43,10 @@ class ESP32BLEBeacon : public Component {
 | 
			
		||||
 | 
			
		||||
  void set_major(uint16_t major) { this->major_ = major; }
 | 
			
		||||
  void set_minor(uint16_t minor) { this->minor_ = minor; }
 | 
			
		||||
  void set_min_interval(uint16_t val) { this->min_interval_ = val; }
 | 
			
		||||
  void set_max_interval(uint16_t val) { this->max_interval_ = val; }
 | 
			
		||||
  void set_measured_power(int8_t val) { this->measured_power_ = val; }
 | 
			
		||||
  void set_tx_power(int8_t val) { this->tx_power_ = val; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
 | 
			
		||||
@@ -51,6 +56,10 @@ class ESP32BLEBeacon : public Component {
 | 
			
		||||
  std::array<uint8_t, 16> uuid_;
 | 
			
		||||
  uint16_t major_{};
 | 
			
		||||
  uint16_t minor_{};
 | 
			
		||||
  uint16_t min_interval_{};
 | 
			
		||||
  uint16_t max_interval_{};
 | 
			
		||||
  int8_t measured_power_{};
 | 
			
		||||
  int8_t tx_power_{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_ble_client {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp32_ble_client.characteristic";
 | 
			
		||||
static const char *const TAG = "esp32_ble_client";
 | 
			
		||||
 | 
			
		||||
BLECharacteristic::~BLECharacteristic() {
 | 
			
		||||
  for (auto &desc : this->descriptors)
 | 
			
		||||
@@ -29,7 +29,8 @@ void BLECharacteristic::parse_descriptors() {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    if (status != ESP_GATT_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status);
 | 
			
		||||
      ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d",
 | 
			
		||||
               this->service->client->get_connection_index(), this->service->client->address_str().c_str(), status);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    if (count == 0) {
 | 
			
		||||
@@ -41,7 +42,8 @@ void BLECharacteristic::parse_descriptors() {
 | 
			
		||||
    desc->handle = result.handle;
 | 
			
		||||
    desc->characteristic = this;
 | 
			
		||||
    this->descriptors.push_back(desc);
 | 
			
		||||
    ESP_LOGV(TAG, "   descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle);
 | 
			
		||||
    ESP_LOGV(TAG, "[%d] [%s]    descriptor %s, handle 0x%x", this->service->client->get_connection_index(),
 | 
			
		||||
             this->service->client->address_str().c_str(), desc->uuid.to_string().c_str(), desc->handle);
 | 
			
		||||
    offset++;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -64,17 +66,19 @@ BLEDescriptor *BLECharacteristic::get_descriptor_by_handle(uint16_t handle) {
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
 | 
			
		||||
esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
 | 
			
		||||
  auto *client = this->service->client;
 | 
			
		||||
  auto status = esp_ble_gattc_write_char(client->get_gattc_if(), client->get_conn_id(), this->handle, new_val_size,
 | 
			
		||||
                                         new_val, write_type, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
  if (status) {
 | 
			
		||||
    ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Error sending write value to BLE gattc server, status=%d",
 | 
			
		||||
             this->service->client->get_connection_index(), this->service->client->address_str().c_str(), status);
 | 
			
		||||
  }
 | 
			
		||||
  return status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
 | 
			
		||||
  write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
 | 
			
		||||
esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
 | 
			
		||||
  return write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32_ble_client
 | 
			
		||||
 
 | 
			
		||||
@@ -24,8 +24,8 @@ class BLECharacteristic {
 | 
			
		||||
  BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
 | 
			
		||||
  BLEDescriptor *get_descriptor(uint16_t uuid);
 | 
			
		||||
  BLEDescriptor *get_descriptor_by_handle(uint16_t handle);
 | 
			
		||||
  void write_value(uint8_t *new_val, int16_t new_val_size);
 | 
			
		||||
  void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
 | 
			
		||||
  esp_err_t write_value(uint8_t *new_val, int16_t new_val_size);
 | 
			
		||||
  esp_err_t write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
 | 
			
		||||
  BLEService *service;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,9 @@ namespace esp32_ble_client {
 | 
			
		||||
static const char *const TAG = "esp32_ble_client";
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::setup() {
 | 
			
		||||
  static uint8_t connection_index = 0;
 | 
			
		||||
  this->connection_index_ = connection_index++;
 | 
			
		||||
 | 
			
		||||
  auto ret = esp_ble_gattc_app_register(this->app_id);
 | 
			
		||||
  if (ret) {
 | 
			
		||||
    ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
 | 
			
		||||
@@ -30,10 +33,10 @@ float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_B
 | 
			
		||||
bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
 | 
			
		||||
  if (device.address_uint64() != this->address_)
 | 
			
		||||
    return false;
 | 
			
		||||
  if (this->state_ != espbt::ClientState::IDLE)
 | 
			
		||||
  if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str());
 | 
			
		||||
  ESP_LOGD(TAG, "[%d] [%s] Found device", this->connection_index_, this->address_str_.c_str());
 | 
			
		||||
  this->set_state(espbt::ClientState::DISCOVERED);
 | 
			
		||||
 | 
			
		||||
  auto addr = device.address_uint64();
 | 
			
		||||
@@ -47,80 +50,88 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string BLEClientBase::address_str() const {
 | 
			
		||||
  return str_snprintf("%02x:%02x:%02x:%02x:%02x:%02x", 17, (uint8_t)(this->address_ >> 40) & 0xff,
 | 
			
		||||
                      (uint8_t)(this->address_ >> 32) & 0xff, (uint8_t)(this->address_ >> 24) & 0xff,
 | 
			
		||||
                      (uint8_t)(this->address_ >> 16) & 0xff, (uint8_t)(this->address_ >> 8) & 0xff,
 | 
			
		||||
                      (uint8_t)(this->address_ >> 0) & 0xff);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::connect() {
 | 
			
		||||
  ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str());
 | 
			
		||||
  ESP_LOGI(TAG, "[%d] [%s] Attempting BLE connection", this->connection_index_, this->address_str_.c_str());
 | 
			
		||||
  auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
 | 
			
		||||
  if (ret) {
 | 
			
		||||
    ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret);
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
             ret);
 | 
			
		||||
    this->set_state(espbt::ClientState::IDLE);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->set_state(espbt::ClientState::CONNECTING);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
 | 
			
		||||
void BLEClientBase::disconnect() {
 | 
			
		||||
  ESP_LOGI(TAG, "[%d] [%s] Disconnecting.", this->connection_index_, this->address_str_.c_str());
 | 
			
		||||
  auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_close error, err=%d", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
             err);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->state_ == espbt::ClientState::SEARCHING) {
 | 
			
		||||
    this->set_address(0);
 | 
			
		||||
    this->set_state(espbt::ClientState::IDLE);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
 | 
			
		||||
                                        esp_ble_gattc_cb_param_t *param) {
 | 
			
		||||
  if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
 | 
			
		||||
    return;
 | 
			
		||||
    return false;
 | 
			
		||||
  if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_)
 | 
			
		||||
    return;
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "[%d] [%s] gattc_event_handler: event=%d gattc_if=%d conn_id=%d", this->connection_index_,
 | 
			
		||||
           this->address_str_.c_str(), event, esp_gattc_if, this->conn_id_);
 | 
			
		||||
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    case ESP_GATTC_REG_EVT: {
 | 
			
		||||
      if (param->reg.status == ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGV(TAG, "gattc registered app id %d", this->app_id);
 | 
			
		||||
        ESP_LOGV(TAG, "[%d] [%s] gattc registered app id %d", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
                 this->app_id);
 | 
			
		||||
        this->gattc_if_ = esp_gattc_if;
 | 
			
		||||
      } else {
 | 
			
		||||
        ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status);
 | 
			
		||||
        ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->reg.app_id, param->reg.status);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_OPEN_EVT: {
 | 
			
		||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT", this->connection_index_, this->address_str_.c_str());
 | 
			
		||||
      this->conn_id_ = param->open.conn_id;
 | 
			
		||||
      if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
 | 
			
		||||
        ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status);
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
                 param->open.status);
 | 
			
		||||
        this->set_state(espbt::ClientState::IDLE);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_CONNECT_EVT: {
 | 
			
		||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str());
 | 
			
		||||
      if (this->conn_id_ != param->connect.conn_id) {
 | 
			
		||||
        ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d",
 | 
			
		||||
                 this->address_str().c_str(), param->connect.conn_id, this->conn_id_);
 | 
			
		||||
      }
 | 
			
		||||
      auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->connect.conn_id);
 | 
			
		||||
      auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id);
 | 
			
		||||
      if (ret) {
 | 
			
		||||
        ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret);
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), ret);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_CFG_MTU_EVT: {
 | 
			
		||||
      if (param->cfg_mtu.status != ESP_GATT_OK) {
 | 
			
		||||
        ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu,
 | 
			
		||||
                 param->cfg_mtu.status);
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->cfg_mtu.mtu, param->cfg_mtu.status);
 | 
			
		||||
        this->set_state(espbt::ClientState::IDLE);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu);
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
               param->cfg_mtu.status, param->cfg_mtu.mtu);
 | 
			
		||||
      this->mtu_ = param->cfg_mtu.mtu;
 | 
			
		||||
      esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_DISCONNECT_EVT: {
 | 
			
		||||
      if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason);
 | 
			
		||||
      if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0)
 | 
			
		||||
        return false;
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_,
 | 
			
		||||
               this->address_str_.c_str(), param->disconnect.reason);
 | 
			
		||||
      for (auto &svc : this->services_)
 | 
			
		||||
        delete svc;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
      this->services_.clear();
 | 
			
		||||
@@ -137,10 +148,12 @@ void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_SEARCH_CMPL_EVT: {
 | 
			
		||||
      ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str());
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_SEARCH_CMPL_EVT", this->connection_index_, this->address_str_.c_str());
 | 
			
		||||
      for (auto &svc : this->services_) {
 | 
			
		||||
        ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str());
 | 
			
		||||
        ESP_LOGI(TAG, "  start_handle: 0x%x  end_handle: 0x%x", svc->start_handle, svc->end_handle);
 | 
			
		||||
        ESP_LOGI(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
                 svc->uuid.to_string().c_str());
 | 
			
		||||
        ESP_LOGI(TAG, "[%d] [%s]  start_handle: 0x%x  end_handle: 0x%x", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), svc->start_handle, svc->end_handle);
 | 
			
		||||
        svc->parse_characteristics();
 | 
			
		||||
      }
 | 
			
		||||
      this->set_state(espbt::ClientState::CONNECTED);
 | 
			
		||||
@@ -149,14 +162,10 @@ void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
    }
 | 
			
		||||
    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      auto *descr = this->get_config_descriptor(param->reg_for_notify.handle);
 | 
			
		||||
      if (descr == nullptr) {
 | 
			
		||||
        ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
 | 
			
		||||
          descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
 | 
			
		||||
        ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle,
 | 
			
		||||
                 descr->uuid.to_string().c_str());
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Handle 0x%x (uuid %s) is not a client config char uuid", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->reg_for_notify.handle, descr->uuid.to_string().c_str());
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      uint16_t notify_en = 1;
 | 
			
		||||
@@ -164,7 +173,8 @@ void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
          esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descr->handle, sizeof(notify_en),
 | 
			
		||||
                                         (uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
 | 
			
		||||
      if (status) {
 | 
			
		||||
        ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, status=%d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), status);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -172,24 +182,28 @@ void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
 | 
			
		||||
  switch (event) {
 | 
			
		||||
    // This event is sent by the server when it requests security
 | 
			
		||||
    case ESP_GAP_BLE_SEC_REQ_EVT:
 | 
			
		||||
      ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event);
 | 
			
		||||
      ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event);
 | 
			
		||||
      esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
 | 
			
		||||
      break;
 | 
			
		||||
    // This event is sent once authentication has completed
 | 
			
		||||
    case ESP_GAP_BLE_AUTH_CMPL_EVT:
 | 
			
		||||
      esp_bd_addr_t bd_addr;
 | 
			
		||||
      memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
 | 
			
		||||
      ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str());
 | 
			
		||||
      ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
               format_hex(bd_addr, 6).c_str());
 | 
			
		||||
      if (!param->ble_security.auth_cmpl.success) {
 | 
			
		||||
        ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason);
 | 
			
		||||
        ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
                 param->ble_security.auth_cmpl.fail_reason);
 | 
			
		||||
      } else {
 | 
			
		||||
        ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type,
 | 
			
		||||
        ESP_LOGV(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type,
 | 
			
		||||
                 param->ble_security.auth_cmpl.auth_mode);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
@@ -245,7 +259,8 @@ float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
 | 
			
		||||
                        (int32_t)(value[4]));
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length);
 | 
			
		||||
  ESP_LOGW(TAG, "[%d] [%s] Cannot parse characteristic value of type 0x%x length %d", this->connection_index_,
 | 
			
		||||
           this->address_str_.c_str(), value[0], length);
 | 
			
		||||
  return NAN;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,13 +28,27 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
 | 
			
		||||
 | 
			
		||||
  bool parse_device(const espbt::ESPBTDevice &device) override;
 | 
			
		||||
  void on_scan_end() override {}
 | 
			
		||||
  void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
  bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                           esp_ble_gattc_cb_param_t *param) override;
 | 
			
		||||
  void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
 | 
			
		||||
  void connect() override;
 | 
			
		||||
  void disconnect();
 | 
			
		||||
 | 
			
		||||
  void set_address(uint64_t address) { this->address_ = address; }
 | 
			
		||||
  std::string address_str() const;
 | 
			
		||||
  bool connected() { return this->state_ == espbt::ClientState::ESTABLISHED; }
 | 
			
		||||
 | 
			
		||||
  void set_address(uint64_t address) {
 | 
			
		||||
    this->address_ = address;
 | 
			
		||||
    if (address == 0) {
 | 
			
		||||
      memset(this->remote_bda_, 0, sizeof(this->remote_bda_));
 | 
			
		||||
      this->address_str_ = "";
 | 
			
		||||
    } else {
 | 
			
		||||
      this->address_str_ = str_snprintf("%02x:%02x:%02x:%02x:%02x:%02x", 17, (uint8_t)(this->address_ >> 40) & 0xff,
 | 
			
		||||
                                        (uint8_t)(this->address_ >> 32) & 0xff, (uint8_t)(this->address_ >> 24) & 0xff,
 | 
			
		||||
                                        (uint8_t)(this->address_ >> 16) & 0xff, (uint8_t)(this->address_ >> 8) & 0xff,
 | 
			
		||||
                                        (uint8_t)(this->address_ >> 0) & 0xff);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  std::string address_str() const { return this->address_str_; }
 | 
			
		||||
 | 
			
		||||
  BLEService *get_service(espbt::ESPBTUUID uuid);
 | 
			
		||||
  BLEService *get_service(uint16_t uuid);
 | 
			
		||||
@@ -55,12 +69,16 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
 | 
			
		||||
  uint16_t get_conn_id() const { return this->conn_id_; }
 | 
			
		||||
  uint64_t get_address() const { return this->address_; }
 | 
			
		||||
 | 
			
		||||
  uint8_t get_connection_index() const { return this->connection_index_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  int gattc_if_;
 | 
			
		||||
  esp_bd_addr_t remote_bda_;
 | 
			
		||||
  esp_ble_addr_type_t remote_addr_type_;
 | 
			
		||||
  uint16_t conn_id_;
 | 
			
		||||
  uint64_t address_;
 | 
			
		||||
  uint16_t conn_id_{0xFFFF};
 | 
			
		||||
  uint64_t address_{0};
 | 
			
		||||
  std::string address_str_{};
 | 
			
		||||
  uint8_t connection_index_;
 | 
			
		||||
  uint16_t mtu_{23};
 | 
			
		||||
 | 
			
		||||
  std::vector<BLEService *> services_;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_ble_client {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp32_ble_client.service";
 | 
			
		||||
static const char *const TAG = "esp32_ble_client";
 | 
			
		||||
 | 
			
		||||
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
 | 
			
		||||
  for (auto &chr : this->characteristics) {
 | 
			
		||||
@@ -40,7 +40,8 @@ void BLEService::parse_characteristics() {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    if (status != ESP_GATT_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status);
 | 
			
		||||
      ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->client->get_connection_index(),
 | 
			
		||||
               this->client->address_str().c_str(), status);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    if (count == 0) {
 | 
			
		||||
@@ -53,8 +54,9 @@ void BLEService::parse_characteristics() {
 | 
			
		||||
    characteristic->handle = result.char_handle;
 | 
			
		||||
    characteristic->service = this;
 | 
			
		||||
    this->characteristics.push_back(characteristic);
 | 
			
		||||
    ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(),
 | 
			
		||||
             characteristic->handle, characteristic->properties);
 | 
			
		||||
    ESP_LOGI(TAG, "[%d] [%s]  characteristic %s, handle 0x%x, properties 0x%x", this->client->get_connection_index(),
 | 
			
		||||
             this->client->address_str().c_str(), characteristic->uuid.to_string().c_str(), characteristic->handle,
 | 
			
		||||
             characteristic->properties);
 | 
			
		||||
    characteristic->parse_descriptors();
 | 
			
		||||
    offset++;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -648,11 +648,17 @@ void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_p
 | 
			
		||||
    // (called CSS here)
 | 
			
		||||
 | 
			
		||||
    switch (record_type) {
 | 
			
		||||
      case ESP_BLE_AD_TYPE_NAME_SHORT:
 | 
			
		||||
      case ESP_BLE_AD_TYPE_NAME_CMPL: {
 | 
			
		||||
        // CSS 1.2 LOCAL NAME
 | 
			
		||||
        // "The Local Name data type shall be the same as, or a shortened version of, the local name assigned to the
 | 
			
		||||
        // device." CSS 1: Optional in this context; shall not appear more than once in a block.
 | 
			
		||||
        this->name_ = std::string(reinterpret_cast<const char *>(record), record_length);
 | 
			
		||||
        // SHORTENED LOCAL NAME
 | 
			
		||||
        // "The Shortened Local Name data type defines a shortened version of the Local Name data type. The Shortened
 | 
			
		||||
        // Local Name data type shall not be used to advertise a name that is longer than the Local Name data type."
 | 
			
		||||
        if (record_length > this->name_.length()) {
 | 
			
		||||
          this->name_ = std::string(reinterpret_cast<const char *>(record), record_length);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case ESP_BLE_AD_TYPE_TX_PWR: {
 | 
			
		||||
 
 | 
			
		||||
@@ -145,6 +145,8 @@ class ESPBTDeviceListener {
 | 
			
		||||
enum class ClientState {
 | 
			
		||||
  // Connection is idle, no device detected.
 | 
			
		||||
  IDLE,
 | 
			
		||||
  // Searching for device.
 | 
			
		||||
  SEARCHING,
 | 
			
		||||
  // Device advertisement found.
 | 
			
		||||
  DISCOVERED,
 | 
			
		||||
  // Connection in progress.
 | 
			
		||||
@@ -157,7 +159,7 @@ enum class ClientState {
 | 
			
		||||
 | 
			
		||||
class ESPBTClient : public ESPBTDeviceListener {
 | 
			
		||||
 public:
 | 
			
		||||
  virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
  virtual bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
 | 
			
		||||
                                   esp_ble_gattc_cb_param_t *param) = 0;
 | 
			
		||||
  virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
 | 
			
		||||
  virtual void connect() = 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,7 @@ async def to_code(config):
 | 
			
		||||
    cg.add(ble_server.register_service_component(var))
 | 
			
		||||
 | 
			
		||||
    cg.add_define("USE_IMPROV")
 | 
			
		||||
    cg.add_library("esphome/Improv", "1.2.1")
 | 
			
		||||
    cg.add_library("esphome/Improv", "1.2.3")
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
 | 
			
		||||
    cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import logging
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
from typing import List
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
@@ -200,7 +199,7 @@ async def esp8266_pin_to_code(config):
 | 
			
		||||
@coroutine_with_priority(-999.0)
 | 
			
		||||
async def add_pin_initial_states_array():
 | 
			
		||||
    # Add includes at the very end, so that they override everything
 | 
			
		||||
    initial_states: List[PinInitialState] = CORE.data[KEY_ESP8266][
 | 
			
		||||
    initial_states: list[PinInitialState] = CORE.data[KEY_ESP8266][
 | 
			
		||||
        KEY_PIN_INITIAL_STATES
 | 
			
		||||
    ]
 | 
			
		||||
    initial_modes_s = ", ".join(str(x.mode) for x in initial_states)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								esphome/components/ethernet_info/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/ethernet_info/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
CODEOWNERS = ["@gtjadsonsantos"]
 | 
			
		||||
@@ -0,0 +1,16 @@
 | 
			
		||||
#include "ethernet_info_text_sensor.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ethernet_info {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ethernet_info";
 | 
			
		||||
 | 
			
		||||
void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IPAddress", this); }
 | 
			
		||||
 | 
			
		||||
}  // namespace ethernet_info
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32_FRAMEWORK_ARDUINO
 | 
			
		||||
							
								
								
									
										35
									
								
								esphome/components/ethernet_info/ethernet_info_text_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								esphome/components/ethernet_info/ethernet_info_text_sensor.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/text_sensor/text_sensor.h"
 | 
			
		||||
#include "esphome/components/ethernet/ethernet_component.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ethernet_info {
 | 
			
		||||
 | 
			
		||||
class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor {
 | 
			
		||||
 public:
 | 
			
		||||
  void update() override {
 | 
			
		||||
    tcpip_adapter_ip_info_t tcpip;
 | 
			
		||||
    tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &tcpip);
 | 
			
		||||
    auto ip = tcpip.ip.addr;
 | 
			
		||||
    if (ip != this->last_ip_) {
 | 
			
		||||
      this->last_ip_ = ip;
 | 
			
		||||
      this->publish_state(network::IPAddress(ip).str());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::ETHERNET; }
 | 
			
		||||
  std::string unique_id() override { return get_mac_address() + "-ethernetinfo"; }
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  network::IPAddress last_ip_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ethernet_info
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32_FRAMEWORK_ARDUINO
 | 
			
		||||
							
								
								
									
										34
									
								
								esphome/components/ethernet_info/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								esphome/components/ethernet_info/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import text_sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_IP_ADDRESS,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["ethernet"]
 | 
			
		||||
 | 
			
		||||
ethernet_info_ns = cg.esphome_ns.namespace("ethernet_info")
 | 
			
		||||
 | 
			
		||||
IPAddressEsthernetInfo = ethernet_info_ns.class_(
 | 
			
		||||
    "IPAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema(
 | 
			
		||||
            IPAddressEsthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
 | 
			
		||||
        ).extend(cv.polling_component_schema("1s"))
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup_conf(config, key):
 | 
			
		||||
    if key in config:
 | 
			
		||||
        conf = config[key]
 | 
			
		||||
        var = await text_sensor.new_text_sensor(conf)
 | 
			
		||||
        await cg.register_component(var, conf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    await setup_conf(config, CONF_IP_ADDRESS)
 | 
			
		||||
@@ -98,7 +98,7 @@ async def to_code(config):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _process_git_config(config: dict, refresh) -> str:
 | 
			
		||||
    repo_dir = git.clone_or_update(
 | 
			
		||||
    repo_dir, _ = git.clone_or_update(
 | 
			
		||||
        url=config[CONF_URL],
 | 
			
		||||
        ref=config.get(CONF_REF),
 | 
			
		||||
        refresh=refresh,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										291
									
								
								esphome/components/ezo_pmp/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								esphome/components/ezo_pmp/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,291 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
from esphome.const import CONF_ADDRESS, CONF_COMMAND, CONF_ID, CONF_DURATION
 | 
			
		||||
from esphome import automation
 | 
			
		||||
from esphome.automation import maybe_simple_id
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@carlos-sarmiento"]
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
CONF_VOLUME = "volume"
 | 
			
		||||
CONF_VOLUME_PER_MINUTE = "volume_per_minute"
 | 
			
		||||
 | 
			
		||||
ezo_pmp_ns = cg.esphome_ns.namespace("ezo_pmp")
 | 
			
		||||
EzoPMP = ezo_pmp_ns.class_("EzoPMP", cg.PollingComponent, i2c.I2CDevice)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(EzoPMP),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(i2c.i2c_device_schema(103))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
EZO_PMP_NO_ARGS_ACTION_SCHEMA = maybe_simple_id(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(EzoPMP),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Actions that do not require more arguments
 | 
			
		||||
 | 
			
		||||
EzoPMPFindAction = ezo_pmp_ns.class_("EzoPMPFindAction", automation.Action)
 | 
			
		||||
EzoPMPClearTotalVolumeDispensedAction = ezo_pmp_ns.class_(
 | 
			
		||||
    "EzoPMPClearTotalVolumeDispensedAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
EzoPMPClearCalibrationAction = ezo_pmp_ns.class_(
 | 
			
		||||
    "EzoPMPClearCalibrationAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
EzoPMPPauseDosingAction = ezo_pmp_ns.class_(
 | 
			
		||||
    "EzoPMPPauseDosingAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
EzoPMPStopDosingAction = ezo_pmp_ns.class_("EzoPMPStopDosingAction", automation.Action)
 | 
			
		||||
EzoPMPDoseContinuouslyAction = ezo_pmp_ns.class_(
 | 
			
		||||
    "EzoPMPDoseContinuouslyAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Actions that require more arguments
 | 
			
		||||
EzoPMPDoseVolumeAction = ezo_pmp_ns.class_("EzoPMPDoseVolumeAction", automation.Action)
 | 
			
		||||
EzoPMPDoseVolumeOverTimeAction = ezo_pmp_ns.class_(
 | 
			
		||||
    "EzoPMPDoseVolumeOverTimeAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
EzoPMPDoseWithConstantFlowRateAction = ezo_pmp_ns.class_(
 | 
			
		||||
    "EzoPMPDoseWithConstantFlowRateAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
EzoPMPSetCalibrationVolumeAction = ezo_pmp_ns.class_(
 | 
			
		||||
    "EzoPMPSetCalibrationVolumeAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
EzoPMPChangeI2CAddressAction = ezo_pmp_ns.class_(
 | 
			
		||||
    "EzoPMPChangeI2CAddressAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
EzoPMPArbitraryCommandAction = ezo_pmp_ns.class_(
 | 
			
		||||
    "EzoPMPArbitraryCommandAction", automation.Action
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ezo_pmp.find", EzoPMPFindAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
async def ezo_pmp_find_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ezo_pmp.dose_continuously",
 | 
			
		||||
    EzoPMPDoseContinuouslyAction,
 | 
			
		||||
    EZO_PMP_NO_ARGS_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
async def ezo_pmp_dose_continuously_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ezo_pmp.clear_total_volume_dosed",
 | 
			
		||||
    EzoPMPClearTotalVolumeDispensedAction,
 | 
			
		||||
    EZO_PMP_NO_ARGS_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
async def ezo_pmp_clear_total_volume_dosed_to_code(
 | 
			
		||||
    config, action_id, template_arg, args
 | 
			
		||||
):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ezo_pmp.clear_calibration",
 | 
			
		||||
    EzoPMPClearCalibrationAction,
 | 
			
		||||
    EZO_PMP_NO_ARGS_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
async def ezo_pmp_clear_calibration_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ezo_pmp.pause_dosing", EzoPMPPauseDosingAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
async def ezo_pmp_pause_dosing_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ezo_pmp.stop_dosing", EzoPMPStopDosingAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
async def ezo_pmp_stop_dosing_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Actions that require Multiple Args
 | 
			
		||||
 | 
			
		||||
EZO_PMP_DOSE_VOLUME_ACTION_SCHEMA = cv.All(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(EzoPMP),
 | 
			
		||||
        cv.Required(CONF_VOLUME): cv.templatable(
 | 
			
		||||
            cv.float_range()
 | 
			
		||||
        ),  # Any way to represent as proper volume (vs. raw int)
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ezo_pmp.dose_volume", EzoPMPDoseVolumeAction, EZO_PMP_DOSE_VOLUME_ACTION_SCHEMA
 | 
			
		||||
)
 | 
			
		||||
async def ezo_pmp_dose_volume_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_VOLUME], args, cg.double)
 | 
			
		||||
    cg.add(var.set_volume(template_))
 | 
			
		||||
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
EZO_PMP_DOSE_VOLUME_OVER_TIME_ACTION_SCHEMA = cv.All(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(EzoPMP),
 | 
			
		||||
        cv.Required(CONF_VOLUME): cv.templatable(
 | 
			
		||||
            cv.float_range()
 | 
			
		||||
        ),  # Any way to represent as proper volume (vs. raw int)
 | 
			
		||||
        cv.Required(CONF_DURATION): cv.templatable(
 | 
			
		||||
            cv.int_range(1)
 | 
			
		||||
        ),  # Any way to represent it as minutes (vs. raw int)
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ezo_pmp.dose_volume_over_time",
 | 
			
		||||
    EzoPMPDoseVolumeOverTimeAction,
 | 
			
		||||
    EZO_PMP_DOSE_VOLUME_OVER_TIME_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
async def ezo_pmp_dose_volume_over_time_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_VOLUME], args, cg.double)
 | 
			
		||||
    cg.add(var.set_volume(template_))
 | 
			
		||||
 | 
			
		||||
    template_ = await cg.templatable(config[CONF_DURATION], args, int)
 | 
			
		||||
    cg.add(var.set_duration(template_))
 | 
			
		||||
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
EZO_PMP_DOSE_WITH_CONSTANT_FLOW_RATE_ACTION_SCHEMA = cv.All(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(EzoPMP),
 | 
			
		||||
        cv.Required(CONF_VOLUME_PER_MINUTE): cv.templatable(
 | 
			
		||||
            cv.float_range()
 | 
			
		||||
        ),  # Any way to represent as proper volume (vs. raw int)
 | 
			
		||||
        cv.Required(CONF_DURATION): cv.templatable(
 | 
			
		||||
            cv.int_range(1)
 | 
			
		||||
        ),  # Any way to represent it as minutes (vs. raw int)
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ezo_pmp.dose_with_constant_flow_rate",
 | 
			
		||||
    EzoPMPDoseWithConstantFlowRateAction,
 | 
			
		||||
    EZO_PMP_DOSE_WITH_CONSTANT_FLOW_RATE_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
async def ezo_pmp_dose_with_constant_flow_rate_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_VOLUME_PER_MINUTE], args, cg.double)
 | 
			
		||||
    cg.add(var.set_volume(template_))
 | 
			
		||||
 | 
			
		||||
    template_ = await cg.templatable(config[CONF_DURATION], args, int)
 | 
			
		||||
    cg.add(var.set_duration(template_))
 | 
			
		||||
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
EZO_PMP_SET_CALIBRATION_VOLUME_ACTION_SCHEMA = cv.All(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(EzoPMP),
 | 
			
		||||
        cv.Required(CONF_VOLUME): cv.templatable(
 | 
			
		||||
            cv.float_range()
 | 
			
		||||
        ),  # Any way to represent as proper volume (vs. raw int)
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ezo_pmp.set_calibration_volume",
 | 
			
		||||
    EzoPMPSetCalibrationVolumeAction,
 | 
			
		||||
    EZO_PMP_SET_CALIBRATION_VOLUME_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
async def ezo_pmp_set_calibration_volume_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_VOLUME], args, cg.double)
 | 
			
		||||
    cg.add(var.set_volume(template_))
 | 
			
		||||
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
EZO_PMP_CHANGE_I2C_ADDRESS_ACTION_SCHEMA = cv.All(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(EzoPMP),
 | 
			
		||||
        cv.Required(CONF_ADDRESS): cv.templatable(cv.int_range(min=1, max=127)),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ezo_pmp.change_i2c_address",
 | 
			
		||||
    EzoPMPChangeI2CAddressAction,
 | 
			
		||||
    EZO_PMP_CHANGE_I2C_ADDRESS_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
async def ezo_pmp_change_i2c_address_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_ADDRESS], args, cg.double)
 | 
			
		||||
    cg.add(var.set_address(template_))
 | 
			
		||||
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
EZO_PMP_ARBITRARY_COMMAND_ACTION_SCHEMA = cv.All(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Required(CONF_ID): cv.use_id(EzoPMP),
 | 
			
		||||
        cv.Required(CONF_COMMAND): cv.templatable(cv.string_strict),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action(
 | 
			
		||||
    "ezo_pmp.arbitrary_command",
 | 
			
		||||
    EzoPMPArbitraryCommandAction,
 | 
			
		||||
    EZO_PMP_ARBITRARY_COMMAND_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
async def ezo_pmp_arbitrary_command_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_COMMAND], args, cg.std_string)
 | 
			
		||||
    cg.add(var.set_command(template_))
 | 
			
		||||
 | 
			
		||||
    return var
 | 
			
		||||
							
								
								
									
										42
									
								
								esphome/components/ezo_pmp/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								esphome/components/ezo_pmp/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import binary_sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    ENTITY_CATEGORY_NONE,
 | 
			
		||||
    DEVICE_CLASS_RUNNING,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from . import EzoPMP
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["ezo_pmp"]
 | 
			
		||||
 | 
			
		||||
CONF_PUMP_STATE = "pump_state"
 | 
			
		||||
CONF_IS_PAUSED = "is_paused"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(EzoPMP),
 | 
			
		||||
        cv.Optional(CONF_PUMP_STATE): binary_sensor.binary_sensor_schema(
 | 
			
		||||
            device_class=DEVICE_CLASS_RUNNING,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_NONE,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_IS_PAUSED): binary_sensor.binary_sensor_schema(
 | 
			
		||||
            device_class=DEVICE_CLASS_EMPTY,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_NONE,
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
 | 
			
		||||
    if CONF_PUMP_STATE in config:
 | 
			
		||||
        sens = await binary_sensor.new_binary_sensor(config[CONF_PUMP_STATE])
 | 
			
		||||
        cg.add(parent.set_is_dosing(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_IS_PAUSED in config:
 | 
			
		||||
        sens = await binary_sensor.new_binary_sensor(config[CONF_IS_PAUSED])
 | 
			
		||||
        cg.add(parent.set_is_paused(sens))
 | 
			
		||||
							
								
								
									
										542
									
								
								esphome/components/ezo_pmp/ezo_pmp.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										542
									
								
								esphome/components/ezo_pmp/ezo_pmp.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,542 @@
 | 
			
		||||
#include "ezo_pmp.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ezo_pmp {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "ezo-pmp";
 | 
			
		||||
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_NONE = 0;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_TYPE_READ = 1;
 | 
			
		||||
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_FIND = 2;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_DOSE_CONTINUOUSLY = 4;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_DOSE_VOLUME = 8;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME = 16;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE = 32;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME = 64;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED = 128;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_CLEAR_CALIBRATION = 256;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_PAUSE_DOSING = 512;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_STOP_DOSING = 1024;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS = 2048;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS = 4096;
 | 
			
		||||
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_READ_DOSING = 3;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_READ_SINGLE_REPORT = 5;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_READ_MAX_FLOW_RATE = 9;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_READ_PAUSE_STATUS = 17;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED = 33;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED = 65;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_READ_CALIBRATION_STATUS = 129;
 | 
			
		||||
static const uint16_t EZO_PMP_COMMAND_READ_PUMP_VOLTAGE = 257;
 | 
			
		||||
 | 
			
		||||
static const std::string DOSING_MODE_NONE = "None";
 | 
			
		||||
static const std::string DOSING_MODE_VOLUME = "Volume";
 | 
			
		||||
static const std::string DOSING_MODE_VOLUME_OVER_TIME = "Volume/Time";
 | 
			
		||||
static const std::string DOSING_MODE_CONSTANT_FLOW_RATE = "Constant Flow Rate";
 | 
			
		||||
static const std::string DOSING_MODE_CONTINUOUS = "Continuous";
 | 
			
		||||
 | 
			
		||||
void EzoPMP::dump_config() {
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
  if (this->is_failed())
 | 
			
		||||
    ESP_LOGE(TAG, "Communication with EZO-PMP circuit failed!");
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::update() {
 | 
			
		||||
  if (this->is_waiting_) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->is_first_read_) {
 | 
			
		||||
    this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, (bool) this->calibration_status_);
 | 
			
		||||
    this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, (bool) this->max_flow_rate_);
 | 
			
		||||
    this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
 | 
			
		||||
    this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, (bool) this->total_volume_dosed_);
 | 
			
		||||
    this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0,
 | 
			
		||||
                         (bool) this->absolute_total_volume_dosed_);
 | 
			
		||||
    this->queue_command_(EZO_PMP_COMMAND_READ_PAUSE_STATUS, 0, 0, true);
 | 
			
		||||
    this->is_first_read_ = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!this->is_waiting_ && this->peek_next_command_() == EZO_PMP_COMMAND_NONE) {
 | 
			
		||||
    this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
 | 
			
		||||
 | 
			
		||||
    if (this->is_dosing_flag_) {
 | 
			
		||||
      this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
 | 
			
		||||
      this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, (bool) this->total_volume_dosed_);
 | 
			
		||||
      this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0,
 | 
			
		||||
                           (bool) this->absolute_total_volume_dosed_);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this->queue_command_(EZO_PMP_COMMAND_READ_PUMP_VOLTAGE, 0, 0, (bool) this->pump_voltage_);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGV(TAG, "Not Scheduling new Command during update()");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::loop() {
 | 
			
		||||
  // If we are not waiting for anything and there is no command to be sent, return
 | 
			
		||||
  if (!this->is_waiting_ && this->peek_next_command_() == EZO_PMP_COMMAND_NONE) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // If we are not waiting for anything and there IS a command to be sent, do it.
 | 
			
		||||
  if (!this->is_waiting_ && this->peek_next_command_() != EZO_PMP_COMMAND_NONE) {
 | 
			
		||||
    this->send_next_command_();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // If we are waiting for something but it isn't ready yet, then return
 | 
			
		||||
  if (this->is_waiting_ && millis() - this->start_time_ < this->wait_time_) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // We are waiting for something and it should be ready.
 | 
			
		||||
  this->read_command_result_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::clear_current_command_() {
 | 
			
		||||
  this->current_command_ = EZO_PMP_COMMAND_NONE;
 | 
			
		||||
  this->is_waiting_ = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::read_command_result_() {
 | 
			
		||||
  uint8_t response_buffer[21] = {'\0'};
 | 
			
		||||
 | 
			
		||||
  response_buffer[0] = 0;
 | 
			
		||||
  if (!this->read_bytes_raw(response_buffer, 20)) {
 | 
			
		||||
    ESP_LOGE(TAG, "read error");
 | 
			
		||||
    this->clear_current_command_();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  switch (response_buffer[0]) {
 | 
			
		||||
    case 254:
 | 
			
		||||
      return;  // keep waiting
 | 
			
		||||
    case 1:
 | 
			
		||||
      break;
 | 
			
		||||
    case 2:
 | 
			
		||||
      ESP_LOGE(TAG, "device returned a syntax error");
 | 
			
		||||
      this->clear_current_command_();
 | 
			
		||||
      return;
 | 
			
		||||
    case 255:
 | 
			
		||||
      ESP_LOGE(TAG, "device returned no data");
 | 
			
		||||
      this->clear_current_command_();
 | 
			
		||||
      return;
 | 
			
		||||
    default:
 | 
			
		||||
      ESP_LOGE(TAG, "device returned an unknown response: %d", response_buffer[0]);
 | 
			
		||||
      this->clear_current_command_();
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  char first_parameter_buffer[10] = {'\0'};
 | 
			
		||||
  char second_parameter_buffer[10] = {'\0'};
 | 
			
		||||
  char third_parameter_buffer[10] = {'\0'};
 | 
			
		||||
 | 
			
		||||
  first_parameter_buffer[0] = '\0';
 | 
			
		||||
  second_parameter_buffer[0] = '\0';
 | 
			
		||||
  third_parameter_buffer[0] = '\0';
 | 
			
		||||
 | 
			
		||||
  int current_parameter = 1;
 | 
			
		||||
 | 
			
		||||
  size_t position_in_parameter_buffer = 0;
 | 
			
		||||
  // some sensors return multiple comma-separated values, terminate string after first one
 | 
			
		||||
  for (size_t i = 1; i < sizeof(response_buffer) - 1; i++) {
 | 
			
		||||
    char current_char = response_buffer[i];
 | 
			
		||||
 | 
			
		||||
    if (current_char == '\0') {
 | 
			
		||||
      ESP_LOGV(TAG, "Read Response from device: %s", (char *) response_buffer);
 | 
			
		||||
      ESP_LOGV(TAG, "First Component: %s", (char *) first_parameter_buffer);
 | 
			
		||||
      ESP_LOGV(TAG, "Second Component: %s", (char *) second_parameter_buffer);
 | 
			
		||||
      ESP_LOGV(TAG, "Third Component: %s", (char *) third_parameter_buffer);
 | 
			
		||||
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (current_char == ',') {
 | 
			
		||||
      current_parameter++;
 | 
			
		||||
      position_in_parameter_buffer = 0;
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch (current_parameter) {
 | 
			
		||||
      case 1:
 | 
			
		||||
        first_parameter_buffer[position_in_parameter_buffer] = current_char;
 | 
			
		||||
        first_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
 | 
			
		||||
        break;
 | 
			
		||||
      case 2:
 | 
			
		||||
        second_parameter_buffer[position_in_parameter_buffer] = current_char;
 | 
			
		||||
        second_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
 | 
			
		||||
        break;
 | 
			
		||||
      case 3:
 | 
			
		||||
        third_parameter_buffer[position_in_parameter_buffer] = current_char;
 | 
			
		||||
        third_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    position_in_parameter_buffer++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto parsed_first_parameter = parse_number<float>(first_parameter_buffer);
 | 
			
		||||
  auto parsed_second_parameter = parse_number<float>(second_parameter_buffer);
 | 
			
		||||
  auto parsed_third_parameter = parse_number<float>(third_parameter_buffer);
 | 
			
		||||
 | 
			
		||||
  switch (this->current_command_) {
 | 
			
		||||
    // Read Commands
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_DOSING:  // Page 54
 | 
			
		||||
      if (parsed_third_parameter.has_value())
 | 
			
		||||
        this->is_dosing_flag_ = parsed_third_parameter.value_or(0) == 1;
 | 
			
		||||
 | 
			
		||||
      if (this->is_dosing_)
 | 
			
		||||
        this->is_dosing_->publish_state(this->is_dosing_flag_);
 | 
			
		||||
 | 
			
		||||
      if (parsed_second_parameter.has_value() && this->last_volume_requested_) {
 | 
			
		||||
        this->last_volume_requested_->publish_state(parsed_second_parameter.value_or(0));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!this->is_dosing_flag_ && !this->is_paused_flag_) {
 | 
			
		||||
        // If pump is not paused and not dispensing
 | 
			
		||||
        if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_NONE)
 | 
			
		||||
          this->dosing_mode_->publish_state(DOSING_MODE_NONE);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_SINGLE_REPORT:  // Single Report (page 53)
 | 
			
		||||
      if (parsed_first_parameter.has_value() && (bool) this->current_volume_dosed_) {
 | 
			
		||||
        this->current_volume_dosed_->publish_state(parsed_first_parameter.value_or(0));
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE:  // Constant Flow Rate (page 57)
 | 
			
		||||
      if (parsed_second_parameter.has_value() && this->max_flow_rate_)
 | 
			
		||||
        this->max_flow_rate_->publish_state(parsed_second_parameter.value_or(0));
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_PAUSE_STATUS:  // Pause (page 61)
 | 
			
		||||
      if (parsed_second_parameter.has_value())
 | 
			
		||||
        this->is_paused_flag_ = parsed_second_parameter.value_or(0) == 1;
 | 
			
		||||
 | 
			
		||||
      if (this->is_paused_)
 | 
			
		||||
        this->is_paused_->publish_state(this->is_paused_flag_);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED:  // Total Volume Dispensed (page 64)
 | 
			
		||||
      if (parsed_second_parameter.has_value() && this->total_volume_dosed_)
 | 
			
		||||
        this->total_volume_dosed_->publish_state(parsed_second_parameter.value_or(0));
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED:  // Total Volume Dispensed (page 64)
 | 
			
		||||
      if (parsed_second_parameter.has_value() && this->absolute_total_volume_dosed_)
 | 
			
		||||
        this->absolute_total_volume_dosed_->publish_state(parsed_second_parameter.value_or(0));
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS:  // Calibration (page 65)
 | 
			
		||||
      if (parsed_second_parameter.has_value() && this->calibration_status_) {
 | 
			
		||||
        if (parsed_second_parameter.value_or(0) == 1) {
 | 
			
		||||
          this->calibration_status_->publish_state("Fixed Volume");
 | 
			
		||||
        } else if (parsed_second_parameter.value_or(0) == 2) {
 | 
			
		||||
          this->calibration_status_->publish_state("Volume/Time");
 | 
			
		||||
        } else if (parsed_second_parameter.value_or(0) == 3) {
 | 
			
		||||
          this->calibration_status_->publish_state("Fixed Volume & Volume/Time");
 | 
			
		||||
        } else {
 | 
			
		||||
          this->calibration_status_->publish_state("Uncalibrated");
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE:  // Pump Voltage (page 67)
 | 
			
		||||
      if (parsed_second_parameter.has_value() && this->pump_voltage_)
 | 
			
		||||
        this->pump_voltage_->publish_state(parsed_second_parameter.value_or(0));
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
      // Non-Read Commands
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_DOSE_VOLUME:  // Volume Dispensing (page 55)
 | 
			
		||||
      if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_VOLUME)
 | 
			
		||||
        this->dosing_mode_->publish_state(DOSING_MODE_VOLUME);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME:  // Dose over time (page 56)
 | 
			
		||||
      if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_VOLUME_OVER_TIME)
 | 
			
		||||
        this->dosing_mode_->publish_state(DOSING_MODE_VOLUME_OVER_TIME);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE:  // Constant Flow Rate (page 57)
 | 
			
		||||
      if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_CONSTANT_FLOW_RATE)
 | 
			
		||||
        this->dosing_mode_->publish_state(DOSING_MODE_CONSTANT_FLOW_RATE);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY:  // Continuous Dispensing (page 54)
 | 
			
		||||
      if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_CONTINUOUS)
 | 
			
		||||
        this->dosing_mode_->publish_state(DOSING_MODE_CONTINUOUS);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_STOP_DOSING:  // Stop (page 62)
 | 
			
		||||
      this->is_paused_flag_ = false;
 | 
			
		||||
      if (this->is_paused_)
 | 
			
		||||
        this->is_paused_->publish_state(this->is_paused_flag_);
 | 
			
		||||
      if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_NONE)
 | 
			
		||||
        this->dosing_mode_->publish_state(DOSING_MODE_NONE);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS:
 | 
			
		||||
      ESP_LOGI(TAG, "Arbitrary Command Response: %s", (char *) response_buffer);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_CLEAR_CALIBRATION:         // Clear Calibration (page 65)
 | 
			
		||||
    case EZO_PMP_COMMAND_PAUSE_DOSING:              // Pause (page 61)
 | 
			
		||||
    case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME:    // Set Calibration Volume (page 65)
 | 
			
		||||
    case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED:  // Clear Total Volume Dosed (page 64)
 | 
			
		||||
    case EZO_PMP_COMMAND_FIND:                      // Find (page 52)
 | 
			
		||||
      // Nothing to do here
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_TYPE_READ:
 | 
			
		||||
    case EZO_PMP_COMMAND_NONE:
 | 
			
		||||
    default:
 | 
			
		||||
      ESP_LOGE(TAG, "Unsupported command received: %d", this->current_command_);
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->clear_current_command_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::send_next_command_() {
 | 
			
		||||
  int wait_time_for_command = 400;  // milliseconds
 | 
			
		||||
  uint8_t command_buffer[21];
 | 
			
		||||
  int command_buffer_length = 0;
 | 
			
		||||
 | 
			
		||||
  this->pop_next_command_();  // this->next_command will be updated.
 | 
			
		||||
 | 
			
		||||
  switch (this->next_command_) {
 | 
			
		||||
    // Read Commands
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_DOSING:  // Page 54
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "D,?");
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_SINGLE_REPORT:  // Single Report (page 53)
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "R");
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE:
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "DC,?");
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_PAUSE_STATUS:
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "P,?");
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED:
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "TV,?");
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED:
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "ATV,?");
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS:
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "Cal,?");
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE:
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "PV,?");
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
      // Non-Read Commands
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_FIND:  // Find (page 52)
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "Find");
 | 
			
		||||
      wait_time_for_command = 60000;  // This command will block all updates for a minute
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY:  // Continuous Dispensing (page 54)
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "D,*");
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED:  // Clear Total Volume Dosed (page 64)
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "Clear");
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_CLEAR_CALIBRATION:  // Clear Calibration (page 65)
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "Cal,clear");
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_PAUSE_DOSING:  // Pause (page 61)
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "P");
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_STOP_DOSING:  // Stop (page 62)
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "X");
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
      // Non-Read commands with parameters
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_DOSE_VOLUME:  // Volume Dispensing (page 55)
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "D,%0.1f", this->next_command_volume_);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME:  // Dose over time (page 56)
 | 
			
		||||
      command_buffer_length =
 | 
			
		||||
          sprintf((char *) command_buffer, "D,%0.1f,%i", this->next_command_volume_, this->next_command_duration_);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE:  // Constant Flow Rate (page 57)
 | 
			
		||||
      command_buffer_length =
 | 
			
		||||
          sprintf((char *) command_buffer, "DC,%0.1f,%i", this->next_command_volume_, this->next_command_duration_);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME:  // Set Calibration Volume (page 65)
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "Cal,%0.2f", this->next_command_volume_);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS:  // Change I2C Address (page 73)
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, "I2C,%i", this->next_command_duration_);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS:  // Run an arbitrary command
 | 
			
		||||
      command_buffer_length = sprintf((char *) command_buffer, this->arbitrary_command_, this->next_command_duration_);
 | 
			
		||||
      ESP_LOGI(TAG, "Sending arbitrary command: %s", (char *) command_buffer);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case EZO_PMP_COMMAND_TYPE_READ:
 | 
			
		||||
    case EZO_PMP_COMMAND_NONE:
 | 
			
		||||
    default:
 | 
			
		||||
      ESP_LOGE(TAG, "Unsupported command received: %d", this->next_command_);
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Send command
 | 
			
		||||
  ESP_LOGV(TAG, "Sending command to device: %s", (char *) command_buffer);
 | 
			
		||||
  this->write(command_buffer, command_buffer_length);
 | 
			
		||||
 | 
			
		||||
  this->current_command_ = this->next_command_;
 | 
			
		||||
  this->next_command_ = EZO_PMP_COMMAND_NONE;
 | 
			
		||||
  this->is_waiting_ = true;
 | 
			
		||||
  this->start_time_ = millis();
 | 
			
		||||
  this->wait_time_ = wait_time_for_command;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::pop_next_command_() {
 | 
			
		||||
  if (this->next_command_queue_length_ <= 0) {
 | 
			
		||||
    ESP_LOGE(TAG, "Tried to dequeue command from empty queue");
 | 
			
		||||
    this->next_command_ = EZO_PMP_COMMAND_NONE;
 | 
			
		||||
    this->next_command_volume_ = 0;
 | 
			
		||||
    this->next_command_duration_ = 0;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Read from Head
 | 
			
		||||
  this->next_command_ = this->next_command_queue_[this->next_command_queue_head_];
 | 
			
		||||
  this->next_command_volume_ = this->next_command_volume_queue_[this->next_command_queue_head_];
 | 
			
		||||
  this->next_command_duration_ = this->next_command_duration_queue_[this->next_command_queue_head_];
 | 
			
		||||
 | 
			
		||||
  // Move positions
 | 
			
		||||
  next_command_queue_head_++;
 | 
			
		||||
  if (next_command_queue_head_ >= 10) {
 | 
			
		||||
    next_command_queue_head_ = 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  next_command_queue_length_--;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t EzoPMP::peek_next_command_() {
 | 
			
		||||
  if (this->next_command_queue_length_ <= 0) {
 | 
			
		||||
    return EZO_PMP_COMMAND_NONE;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return this->next_command_queue_[this->next_command_queue_head_];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::queue_command_(uint16_t command, double volume, int duration, bool should_schedule) {
 | 
			
		||||
  if (!should_schedule) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->next_command_queue_length_ >= 10) {
 | 
			
		||||
    ESP_LOGE(TAG, "Tried to queue command '%d' but queue is full", command);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->next_command_queue_[this->next_command_queue_last_] = command;
 | 
			
		||||
  this->next_command_volume_queue_[this->next_command_queue_last_] = volume;
 | 
			
		||||
  this->next_command_duration_queue_[this->next_command_queue_last_] = duration;
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Queue command '%d' in position '%d'", command, next_command_queue_last_);
 | 
			
		||||
 | 
			
		||||
  // Move positions
 | 
			
		||||
  next_command_queue_last_++;
 | 
			
		||||
  if (next_command_queue_last_ >= 10) {
 | 
			
		||||
    next_command_queue_last_ = 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  next_command_queue_length_++;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Actions
 | 
			
		||||
 | 
			
		||||
void EzoPMP::find() { this->queue_command_(EZO_PMP_COMMAND_FIND, 0, 0, true); }
 | 
			
		||||
 | 
			
		||||
void EzoPMP::dose_continuously() {
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_DOSE_CONTINUOUSLY, 0, 0, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::dose_volume(double volume) {
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_DOSE_VOLUME, volume, 0, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::dose_volume_over_time(double volume, int duration) {
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME, volume, duration, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::dose_with_constant_flow_rate(double volume, int duration) {
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE, volume, duration, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::set_calibration_volume(double volume) {
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME, volume, 0, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::clear_total_volume_dosed() {
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED, 0, 0, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::clear_calibration() {
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_CLEAR_CALIBRATION, 0, 0, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::pause_dosing() {
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_PAUSE_DOSING, 0, 0, true);
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_READ_PAUSE_STATUS, 0, 0, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::stop_dosing() { this->queue_command_(EZO_PMP_COMMAND_STOP_DOSING, 0, 0, true); }
 | 
			
		||||
 | 
			
		||||
void EzoPMP::change_i2c_address(int address) {
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS, 0, address, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EzoPMP::exec_arbitrary_command(const std::basic_string<char> &command) {
 | 
			
		||||
  this->arbitrary_command_ = command.c_str();
 | 
			
		||||
  this->queue_command_(EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS, 0, 0, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace ezo_pmp
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										252
									
								
								esphome/components/ezo_pmp/ezo_pmp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								esphome/components/ezo_pmp/ezo_pmp.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,252 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
#include "esphome/components/text_sensor/text_sensor.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ezo_pmp {
 | 
			
		||||
 | 
			
		||||
class EzoPMP : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; };
 | 
			
		||||
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  void set_current_volume_dosed(sensor::Sensor *current_volume_dosed) { current_volume_dosed_ = current_volume_dosed; }
 | 
			
		||||
  void set_total_volume_dosed(sensor::Sensor *total_volume_dosed) { total_volume_dosed_ = total_volume_dosed; }
 | 
			
		||||
  void set_absolute_total_volume_dosed(sensor::Sensor *absolute_total_volume_dosed) {
 | 
			
		||||
    absolute_total_volume_dosed_ = absolute_total_volume_dosed;
 | 
			
		||||
  }
 | 
			
		||||
  void set_pump_voltage(sensor::Sensor *pump_voltage) { pump_voltage_ = pump_voltage; }
 | 
			
		||||
  void set_last_volume_requested(sensor::Sensor *last_volume_requested) {
 | 
			
		||||
    last_volume_requested_ = last_volume_requested;
 | 
			
		||||
  }
 | 
			
		||||
  void set_max_flow_rate(sensor::Sensor *max_flow_rate) { max_flow_rate_ = max_flow_rate; }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
  void set_is_dosing(binary_sensor::BinarySensor *is_dosing) { is_dosing_ = is_dosing; }
 | 
			
		||||
  void set_is_paused(binary_sensor::BinarySensor *is_paused) { is_paused_ = is_paused; }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  void set_dosing_mode(text_sensor::TextSensor *dosing_mode) { dosing_mode_ = dosing_mode; }
 | 
			
		||||
  void set_calibration_status(text_sensor::TextSensor *calibration_status) { calibration_status_ = calibration_status; }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  // Actions for EZO-PMP
 | 
			
		||||
  void find();
 | 
			
		||||
  void dose_continuously();
 | 
			
		||||
  void dose_volume(double volume);
 | 
			
		||||
  void dose_volume_over_time(double volume, int duration);
 | 
			
		||||
  void dose_with_constant_flow_rate(double volume, int duration);
 | 
			
		||||
  void set_calibration_volume(double volume);
 | 
			
		||||
  void clear_total_volume_dosed();
 | 
			
		||||
  void clear_calibration();
 | 
			
		||||
  void pause_dosing();
 | 
			
		||||
  void stop_dosing();
 | 
			
		||||
  void change_i2c_address(int address);
 | 
			
		||||
  void exec_arbitrary_command(const std::basic_string<char> &command);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint32_t start_time_ = 0;
 | 
			
		||||
  uint32_t wait_time_ = 0;
 | 
			
		||||
  bool is_waiting_ = false;
 | 
			
		||||
  bool is_first_read_ = true;
 | 
			
		||||
 | 
			
		||||
  uint16_t next_command_ = 0;
 | 
			
		||||
  double next_command_volume_ = 0;  // might be negative
 | 
			
		||||
  int next_command_duration_ = 0;
 | 
			
		||||
 | 
			
		||||
  uint16_t next_command_queue_[10];
 | 
			
		||||
  double next_command_volume_queue_[10];
 | 
			
		||||
  int next_command_duration_queue_[10];
 | 
			
		||||
  int next_command_queue_head_ = 0;
 | 
			
		||||
  int next_command_queue_last_ = 0;
 | 
			
		||||
  int next_command_queue_length_ = 0;
 | 
			
		||||
 | 
			
		||||
  uint16_t current_command_ = 0;
 | 
			
		||||
  bool is_paused_flag_ = false;
 | 
			
		||||
  bool is_dosing_flag_ = false;
 | 
			
		||||
 | 
			
		||||
  const char *arbitrary_command_{nullptr};
 | 
			
		||||
 | 
			
		||||
  void send_next_command_();
 | 
			
		||||
  void read_command_result_();
 | 
			
		||||
  void clear_current_command_();
 | 
			
		||||
  void queue_command_(uint16_t command, double volume, int duration, bool should_schedule);
 | 
			
		||||
  void pop_next_command_();
 | 
			
		||||
  uint16_t peek_next_command_();
 | 
			
		||||
 | 
			
		||||
#ifdef USE_SENSOR
 | 
			
		||||
  sensor::Sensor *current_volume_dosed_{nullptr};
 | 
			
		||||
  sensor::Sensor *total_volume_dosed_{nullptr};
 | 
			
		||||
  sensor::Sensor *absolute_total_volume_dosed_{nullptr};
 | 
			
		||||
  sensor::Sensor *pump_voltage_{nullptr};
 | 
			
		||||
  sensor::Sensor *max_flow_rate_{nullptr};
 | 
			
		||||
  sensor::Sensor *last_volume_requested_{nullptr};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_BINARY_SENSOR
 | 
			
		||||
  binary_sensor::BinarySensor *is_dosing_{nullptr};
 | 
			
		||||
  binary_sensor::BinarySensor *is_paused_{nullptr};
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_TEXT_SENSOR
 | 
			
		||||
  text_sensor::TextSensor *dosing_mode_{nullptr};
 | 
			
		||||
  text_sensor::TextSensor *calibration_status_{nullptr};
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Action Templates
 | 
			
		||||
template<typename... Ts> class EzoPMPFindAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  EzoPMPFindAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->ezopmp_->find(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  EzoPMP *ezopmp_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class EzoPMPDoseContinuouslyAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  EzoPMPDoseContinuouslyAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->ezopmp_->dose_continuously(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  EzoPMP *ezopmp_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class EzoPMPDoseVolumeAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  EzoPMPDoseVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->ezopmp_->dose_volume(this->volume_.value(x...)); }
 | 
			
		||||
  TEMPLATABLE_VALUE(double, volume)
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  EzoPMP *ezopmp_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class EzoPMPDoseVolumeOverTimeAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  EzoPMPDoseVolumeOverTimeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    this->ezopmp_->dose_volume_over_time(this->volume_.value(x...), this->duration_.value(x...));
 | 
			
		||||
  }
 | 
			
		||||
  TEMPLATABLE_VALUE(double, volume)
 | 
			
		||||
  TEMPLATABLE_VALUE(int, duration)
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  EzoPMP *ezopmp_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class EzoPMPDoseWithConstantFlowRateAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  EzoPMPDoseWithConstantFlowRateAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override {
 | 
			
		||||
    this->ezopmp_->dose_with_constant_flow_rate(this->volume_.value(x...), this->duration_.value(x...));
 | 
			
		||||
  }
 | 
			
		||||
  TEMPLATABLE_VALUE(double, volume)
 | 
			
		||||
  TEMPLATABLE_VALUE(int, duration)
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  EzoPMP *ezopmp_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class EzoPMPSetCalibrationVolumeAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  EzoPMPSetCalibrationVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->ezopmp_->set_calibration_volume(this->volume_.value(x...)); }
 | 
			
		||||
  TEMPLATABLE_VALUE(double, volume)
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  EzoPMP *ezopmp_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class EzoPMPClearTotalVolumeDispensedAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  EzoPMPClearTotalVolumeDispensedAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->ezopmp_->clear_total_volume_dosed(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  EzoPMP *ezopmp_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class EzoPMPClearCalibrationAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  EzoPMPClearCalibrationAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->ezopmp_->clear_calibration(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  EzoPMP *ezopmp_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class EzoPMPPauseDosingAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  EzoPMPPauseDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->ezopmp_->pause_dosing(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  EzoPMP *ezopmp_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class EzoPMPStopDosingAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  EzoPMPStopDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->ezopmp_->stop_dosing(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  EzoPMP *ezopmp_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class EzoPMPChangeI2CAddressAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  EzoPMPChangeI2CAddressAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->ezopmp_->change_i2c_address(this->address_.value(x...)); }
 | 
			
		||||
  TEMPLATABLE_VALUE(int, address)
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  EzoPMP *ezopmp_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class EzoPMPArbitraryCommandAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
  EzoPMPArbitraryCommandAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
 | 
			
		||||
 | 
			
		||||
  void play(Ts... x) override { this->ezopmp_->exec_arbitrary_command(this->command_.value(x...)); }
 | 
			
		||||
  TEMPLATABLE_VALUE(std::string, command)
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  EzoPMP *ezopmp_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ezo_pmp
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										104
									
								
								esphome/components/ezo_pmp/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								esphome/components/ezo_pmp/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    ENTITY_CATEGORY_NONE,
 | 
			
		||||
    DEVICE_CLASS_EMPTY,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    STATE_CLASS_NONE,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from . import EzoPMP
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["ezo_pmp"]
 | 
			
		||||
 | 
			
		||||
CONF_CURRENT_VOLUME_DOSED = "current_volume_dosed"
 | 
			
		||||
CONF_TOTAL_VOLUME_DOSED = "total_volume_dosed"
 | 
			
		||||
CONF_ABSOLUTE_TOTAL_VOLUME_DOSED = "absolute_total_volume_dosed"
 | 
			
		||||
CONF_PUMP_VOLTAGE = "pump_voltage"
 | 
			
		||||
CONF_LAST_VOLUME_REQUESTED = "last_volume_requested"
 | 
			
		||||
CONF_MAX_FLOW_RATE = "max_flow_rate"
 | 
			
		||||
 | 
			
		||||
UNIT_MILILITER = "ml"
 | 
			
		||||
UNIT_MILILITERS_PER_MINUTE = "ml/min"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(EzoPMP),
 | 
			
		||||
        cv.Optional(CONF_CURRENT_VOLUME_DOSED): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_MILILITER,
 | 
			
		||||
            accuracy_decimals=2,
 | 
			
		||||
            device_class=DEVICE_CLASS_EMPTY,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_NONE,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_LAST_VOLUME_REQUESTED): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_MILILITER,
 | 
			
		||||
            accuracy_decimals=2,
 | 
			
		||||
            device_class=DEVICE_CLASS_EMPTY,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_NONE,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_MAX_FLOW_RATE): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_MILILITERS_PER_MINUTE,
 | 
			
		||||
            accuracy_decimals=2,
 | 
			
		||||
            device_class=DEVICE_CLASS_EMPTY,
 | 
			
		||||
            state_class=STATE_CLASS_NONE,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_TOTAL_VOLUME_DOSED): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_MILILITER,
 | 
			
		||||
            accuracy_decimals=2,
 | 
			
		||||
            device_class=DEVICE_CLASS_EMPTY,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_ABSOLUTE_TOTAL_VOLUME_DOSED): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_MILILITER,
 | 
			
		||||
            accuracy_decimals=2,
 | 
			
		||||
            device_class=DEVICE_CLASS_EMPTY,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_PUMP_VOLTAGE): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_VOLT,
 | 
			
		||||
            accuracy_decimals=2,
 | 
			
		||||
            device_class=DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
 | 
			
		||||
    if CONF_CURRENT_VOLUME_DOSED in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_CURRENT_VOLUME_DOSED])
 | 
			
		||||
        cg.add(parent.set_current_volume_dosed(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_LAST_VOLUME_REQUESTED in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_LAST_VOLUME_REQUESTED])
 | 
			
		||||
        cg.add(parent.set_last_volume_requested(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_TOTAL_VOLUME_DOSED in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_TOTAL_VOLUME_DOSED])
 | 
			
		||||
        cg.add(parent.set_total_volume_dosed(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_ABSOLUTE_TOTAL_VOLUME_DOSED in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_ABSOLUTE_TOTAL_VOLUME_DOSED])
 | 
			
		||||
        cg.add(parent.set_absolute_total_volume_dosed(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_PUMP_VOLTAGE in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_PUMP_VOLTAGE])
 | 
			
		||||
        cg.add(parent.set_pump_voltage(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_MAX_FLOW_RATE in config:
 | 
			
		||||
        sens = await sensor.new_sensor(config[CONF_MAX_FLOW_RATE])
 | 
			
		||||
        cg.add(parent.set_max_flow_rate(sens))
 | 
			
		||||
							
								
								
									
										39
									
								
								esphome/components/ezo_pmp/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								esphome/components/ezo_pmp/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import text_sensor
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    ENTITY_CATEGORY_NONE,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from . import EzoPMP
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["ezo_pmp"]
 | 
			
		||||
 | 
			
		||||
CONF_DOSING_MODE = "dosing_mode"
 | 
			
		||||
CONF_CALIBRATION_STATUS = "calibration_status"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.use_id(EzoPMP),
 | 
			
		||||
        cv.Optional(CONF_DOSING_MODE): text_sensor.text_sensor_schema(
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_NONE,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_CALIBRATION_STATUS): text_sensor.text_sensor_schema(
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    parent = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
 | 
			
		||||
    if CONF_DOSING_MODE in config:
 | 
			
		||||
        sens = await text_sensor.new_text_sensor(config[CONF_DOSING_MODE])
 | 
			
		||||
        cg.add(parent.set_dosing_mode(sens))
 | 
			
		||||
 | 
			
		||||
    if CONF_CALIBRATION_STATUS in config:
 | 
			
		||||
        sens = await text_sensor.new_text_sensor(config[CONF_CALIBRATION_STATUS])
 | 
			
		||||
        cg.add(parent.set_calibration_status(sens))
 | 
			
		||||
@@ -8,6 +8,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_PROTOCOL,
 | 
			
		||||
    CONF_VISUAL,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@rob-deutsch"]
 | 
			
		||||
 | 
			
		||||
@@ -58,6 +59,7 @@ PROTOCOLS = {
 | 
			
		||||
    "sharp": Protocol.PROTOCOL_SHARP,
 | 
			
		||||
    "toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI,
 | 
			
		||||
    "toshiba": Protocol.PROTOCOL_TOSHIBA,
 | 
			
		||||
    "zhlt01": Protocol.PROTOCOL_ZHLT01,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CONF_HORIZONTAL_DEFAULT = "horizontal_default"
 | 
			
		||||
@@ -114,3 +116,6 @@ def to_code(config):
 | 
			
		||||
    cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
 | 
			
		||||
 | 
			
		||||
    cg.add_library("tonia/HeatpumpIR", "1.0.20")
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp8266 or CORE.is_esp32:
 | 
			
		||||
        cg.add_library("crankyoldgit/IRremoteESP8266", "2.7.12")
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
 | 
			
		||||
    {PROTOCOL_SHARP, []() { return new SharpHeatpumpIR(); }},                                // NOLINT
 | 
			
		||||
    {PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }},         // NOLINT
 | 
			
		||||
    {PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }},                            // NOLINT
 | 
			
		||||
    {PROTOCOL_ZHLT01, []() { return new ZHLT01HeatpumpIR(); }},                              // NOLINT
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void HeatpumpIRClimate::setup() {
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,7 @@ enum Protocol {
 | 
			
		||||
  PROTOCOL_SHARP,
 | 
			
		||||
  PROTOCOL_TOSHIBA_DAISEIKAI,
 | 
			
		||||
  PROTOCOL_TOSHIBA,
 | 
			
		||||
  PROTOCOL_ZHLT01,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Simple enum to represent horizontal directios
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ static const char *const TAG = "i2c.arduino";
 | 
			
		||||
void ArduinoI2CBus::setup() {
 | 
			
		||||
  recover_();
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#if defined(USE_ESP32)
 | 
			
		||||
  static uint8_t next_bus_num = 0;
 | 
			
		||||
  if (next_bus_num == 0) {
 | 
			
		||||
    wire_ = &Wire;
 | 
			
		||||
@@ -22,11 +22,25 @@ void ArduinoI2CBus::setup() {
 | 
			
		||||
    wire_ = new TwoWire(next_bus_num);  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  }
 | 
			
		||||
  next_bus_num++;
 | 
			
		||||
#else
 | 
			
		||||
#elif defined(USE_ESP8266)
 | 
			
		||||
  wire_ = &Wire;  // NOLINT(cppcoreguidelines-prefer-member-initializer)
 | 
			
		||||
#elif defined(USE_RP2040)
 | 
			
		||||
  static bool first = true;
 | 
			
		||||
  if (first) {
 | 
			
		||||
    wire_ = &Wire;
 | 
			
		||||
    first = false;
 | 
			
		||||
  } else {
 | 
			
		||||
    wire_ = &Wire1;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  wire_->setSDA(this->sda_pin_);
 | 
			
		||||
  wire_->setSCL(this->scl_pin_);
 | 
			
		||||
  wire_->begin();
 | 
			
		||||
#else
 | 
			
		||||
  wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_));
 | 
			
		||||
#endif
 | 
			
		||||
  wire_->setClock(frequency_);
 | 
			
		||||
  initialized_ = true;
 | 
			
		||||
  if (this->scan_) {
 | 
			
		||||
 
 | 
			
		||||
@@ -37,4 +37,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    cg.add_library("esphome/Improv", "1.2.1")
 | 
			
		||||
    cg.add_library("esphome/Improv", "1.2.3")
 | 
			
		||||
 
 | 
			
		||||
@@ -77,6 +77,8 @@ UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1]
 | 
			
		||||
 | 
			
		||||
ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG]
 | 
			
		||||
 | 
			
		||||
UART_SELECTION_RP2040 = [UART0, UART1]
 | 
			
		||||
 | 
			
		||||
HARDWARE_UART_TO_UART_SELECTION = {
 | 
			
		||||
    UART0: logger_ns.UART_SELECTION_UART0,
 | 
			
		||||
    UART0_SWAP: logger_ns.UART_SELECTION_UART0_SWAP,
 | 
			
		||||
@@ -106,6 +108,8 @@ def uart_selection(value):
 | 
			
		||||
            return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
 | 
			
		||||
    if CORE.is_rp2040:
 | 
			
		||||
        return cv.one_of(*UART_SELECTION_RP2040, upper=True)(value)
 | 
			
		||||
    raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -158,12 +162,13 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
@coroutine_with_priority(90.0)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    baud_rate = config[CONF_BAUD_RATE]
 | 
			
		||||
    rhs = Logger.new(
 | 
			
		||||
        baud_rate,
 | 
			
		||||
        config[CONF_TX_BUFFER_SIZE],
 | 
			
		||||
        HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]],
 | 
			
		||||
    )
 | 
			
		||||
    log = cg.Pvariable(config[CONF_ID], rhs)
 | 
			
		||||
    log = cg.new_Pvariable(config[CONF_ID], baud_rate, config[CONF_TX_BUFFER_SIZE])
 | 
			
		||||
    if CONF_HARDWARE_UART in config:
 | 
			
		||||
        cg.add(
 | 
			
		||||
            log.set_uart_selection(
 | 
			
		||||
                HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]]
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    cg.add(log.pre_setup())
 | 
			
		||||
 | 
			
		||||
    for tag, level in config[CONF_LOGS].items():
 | 
			
		||||
 
 | 
			
		||||
@@ -148,8 +148,7 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
 | 
			
		||||
  this->log_callback_.call(level, tag, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart)
 | 
			
		||||
    : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size), uart_(uart) {
 | 
			
		||||
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) {
 | 
			
		||||
  // add 1 to buffer size for null terminator
 | 
			
		||||
  this->tx_buffer_ = new char[this->tx_buffer_size_ + 1];  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
@@ -270,6 +269,9 @@ const char *const UART_SELECTIONS[] = {
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1"};
 | 
			
		||||
#endif  // USE_ESP8266
 | 
			
		||||
void Logger::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Logger:");
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,15 @@
 | 
			
		||||
#include <cstdarg>
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
 | 
			
		||||
#include <HardwareSerial.h>
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ESP8266 || USE_ESP32
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
#include <HardwareSerial.h>
 | 
			
		||||
#include <SerialUSB.h>
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
#include <driver/uart.h>
 | 
			
		||||
#endif
 | 
			
		||||
@@ -44,7 +51,7 @@ enum UARTSelection {
 | 
			
		||||
 | 
			
		||||
class Logger : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart);
 | 
			
		||||
  explicit Logger(uint32_t baud_rate, size_t tx_buffer_size);
 | 
			
		||||
 | 
			
		||||
  /// Manually set the baud rate for serial, set to 0 to disable.
 | 
			
		||||
  void set_baud_rate(uint32_t baud_rate);
 | 
			
		||||
@@ -56,6 +63,7 @@ class Logger : public Component {
 | 
			
		||||
  uart_port_t get_uart_num() const { return uart_num_; }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
 | 
			
		||||
  /// Get the UART used by the logger.
 | 
			
		||||
  UARTSelection get_uart() const;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,13 @@ void MCP23S17::setup() {
 | 
			
		||||
  this->transfer_byte(0b00011000);  // Enable HAEN pins for addressing
 | 
			
		||||
  this->disable();
 | 
			
		||||
 | 
			
		||||
  this->enable();
 | 
			
		||||
  cmd = 0b01001000;
 | 
			
		||||
  this->transfer_byte(cmd);
 | 
			
		||||
  this->transfer_byte(mcp23x17_base::MCP23X17_IOCONA);
 | 
			
		||||
  this->transfer_byte(0b00011000);  // Enable HAEN pins for addressing
 | 
			
		||||
  this->disable();
 | 
			
		||||
 | 
			
		||||
  // Read current output register state
 | 
			
		||||
  this->read_reg(mcp23x17_base::MCP23X17_OLATA, &this->olat_a_);
 | 
			
		||||
  this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace md5 {
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#if defined(USE_ARDUINO) && !defined(USE_RP2040)
 | 
			
		||||
void MD5Digest::init() {
 | 
			
		||||
  memset(this->digest_, 0, 16);
 | 
			
		||||
  MD5Init(&this->ctx_);
 | 
			
		||||
@@ -15,7 +15,7 @@ void MD5Digest::init() {
 | 
			
		||||
void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); }
 | 
			
		||||
 | 
			
		||||
void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); }
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
#endif  // USE_ARDUINO && !USE_RP2040
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
void MD5Digest::init() {
 | 
			
		||||
@@ -28,6 +28,17 @@ void MD5Digest::add(const uint8_t *data, size_t len) { esp_rom_md5_update(&this-
 | 
			
		||||
void MD5Digest::calculate() { esp_rom_md5_final(this->digest_, &this->ctx_); }
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
void MD5Digest::init() {
 | 
			
		||||
  memset(this->digest_, 0, 16);
 | 
			
		||||
  br_md5_init(&this->ctx_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MD5Digest::add(const uint8_t *data, size_t len) { br_md5_update(&this->ctx_, data, len); }
 | 
			
		||||
 | 
			
		||||
void MD5Digest::calculate() { br_md5_out(&this->ctx_, this->digest_); }
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
 | 
			
		||||
void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); }
 | 
			
		||||
 | 
			
		||||
void MD5Digest::get_hex(char *output) {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,11 @@
 | 
			
		||||
#define MD5_CTX_TYPE md5_context_t
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
#include <MD5Builder.h>
 | 
			
		||||
#define MD5_CTX_TYPE br_md5_context
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace md5 {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,8 @@ async def to_code(config):
 | 
			
		||||
            cg.add_library("ESPmDNS", None)
 | 
			
		||||
        elif CORE.is_esp8266:
 | 
			
		||||
            cg.add_library("ESP8266mDNS", None)
 | 
			
		||||
        elif CORE.is_rp2040:
 | 
			
		||||
            cg.add_library("LEAmDNS", None)
 | 
			
		||||
 | 
			
		||||
    if config[CONF_DISABLED]:
 | 
			
		||||
        return
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,9 @@ void MDNSComponent::compile_records_() {
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
    platform = "ESP32";
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
    platform = "RP2040";
 | 
			
		||||
#endif
 | 
			
		||||
    if (platform != nullptr) {
 | 
			
		||||
      service.txt_records.push_back({"platform", platform});
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ class MDNSComponent : public Component {
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
#if defined(USE_ESP8266) && defined(USE_ARDUINO)
 | 
			
		||||
#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_ARDUINO)
 | 
			
		||||
  void loop() override;
 | 
			
		||||
#endif
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								esphome/components/mdns/mdns_rp2040.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								esphome/components/mdns/mdns_rp2040.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/network/ip_address.h"
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "mdns_component.h"
 | 
			
		||||
 | 
			
		||||
#include <ESP8266mDNS.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace mdns {
 | 
			
		||||
 | 
			
		||||
void MDNSComponent::setup() {
 | 
			
		||||
  this->compile_records_();
 | 
			
		||||
 | 
			
		||||
  network::IPAddress addr = network::get_ip_address();
 | 
			
		||||
  MDNS.begin(this->hostname_.c_str(), (uint32_t) addr);
 | 
			
		||||
 | 
			
		||||
  for (const auto &service : this->services_) {
 | 
			
		||||
    // Strip the leading underscore from the proto and service_type. While it is
 | 
			
		||||
    // part of the wire protocol to have an underscore, and for example ESP-IDF
 | 
			
		||||
    // expects the underscore to be there, the ESP8266 implementation always adds
 | 
			
		||||
    // the underscore itself.
 | 
			
		||||
    auto *proto = service.proto.c_str();
 | 
			
		||||
    while (*proto == '_') {
 | 
			
		||||
      proto++;
 | 
			
		||||
    }
 | 
			
		||||
    auto *service_type = service.service_type.c_str();
 | 
			
		||||
    while (*service_type == '_') {
 | 
			
		||||
      service_type++;
 | 
			
		||||
    }
 | 
			
		||||
    MDNS.addService(service_type, proto, service.port);
 | 
			
		||||
    for (const auto &record : service.txt_records) {
 | 
			
		||||
      MDNS.addServiceTxt(service_type, proto, record.key.c_str(), record.value.c_str());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MDNSComponent::loop() { MDNS.update(); }
 | 
			
		||||
 | 
			
		||||
}  // namespace mdns
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -571,24 +571,16 @@ int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sens
 | 
			
		||||
          static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
 | 
			
		||||
    } break;
 | 
			
		||||
    case SensorValueType::U_QWORD:
 | 
			
		||||
      // Ignore bitmask for U_QWORD
 | 
			
		||||
      value = get_data<uint64_t>(data, offset);
 | 
			
		||||
      break;
 | 
			
		||||
    case SensorValueType::S_QWORD:
 | 
			
		||||
      // Ignore bitmask for S_QWORD
 | 
			
		||||
      value = get_data<int64_t>(data, offset);
 | 
			
		||||
      // Ignore bitmask for QWORD
 | 
			
		||||
      value = get_data<uint64_t>(data, offset);
 | 
			
		||||
      break;
 | 
			
		||||
    case SensorValueType::U_QWORD_R:
 | 
			
		||||
      // Ignore bitmask for U_QWORD
 | 
			
		||||
      value = get_data<uint64_t>(data, offset);
 | 
			
		||||
      value = static_cast<uint64_t>(value & 0xFFFF) << 48 | (value & 0xFFFF000000000000) >> 48 |
 | 
			
		||||
              static_cast<uint64_t>(value & 0xFFFF0000) << 32 | (value & 0x0000FFFF00000000) >> 32 |
 | 
			
		||||
              static_cast<uint64_t>(value & 0xFFFF00000000) << 16 | (value & 0x00000000FFFF0000) >> 16;
 | 
			
		||||
      break;
 | 
			
		||||
    case SensorValueType::S_QWORD_R:
 | 
			
		||||
      // Ignore bitmask for S_QWORD
 | 
			
		||||
      value = get_data<int64_t>(data, offset);
 | 
			
		||||
      break;
 | 
			
		||||
    case SensorValueType::S_QWORD_R: {
 | 
			
		||||
      // Ignore bitmask for QWORD
 | 
			
		||||
      uint64_t tmp = get_data<uint64_t>(data, offset);
 | 
			
		||||
      value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000);
 | 
			
		||||
    } break;
 | 
			
		||||
    case SensorValueType::RAW:
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
from typing import Any, List
 | 
			
		||||
from typing import Any
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
@@ -349,7 +349,7 @@ def _spi_extra_validate(config):
 | 
			
		||||
class MethodDescriptor:
 | 
			
		||||
    method_schema: Any
 | 
			
		||||
    to_code: Any
 | 
			
		||||
    supported_chips: List[str]
 | 
			
		||||
    supported_chips: list[str]
 | 
			
		||||
    extra_validate: Any = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(OTAComponent),
 | 
			
		||||
        cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
 | 
			
		||||
        cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232): cv.port,
 | 
			
		||||
        cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232, rp2040=2040): cv.port,
 | 
			
		||||
        cv.Optional(CONF_PASSWORD): cv.string,
 | 
			
		||||
        cv.Optional(
 | 
			
		||||
            CONF_REBOOT_TIMEOUT, default="5min"
 | 
			
		||||
@@ -94,6 +94,9 @@ async def to_code(config):
 | 
			
		||||
    if CORE.is_esp32 and CORE.using_arduino:
 | 
			
		||||
        cg.add_library("Update", None)
 | 
			
		||||
 | 
			
		||||
    if CORE.is_rp2040 and CORE.using_arduino:
 | 
			
		||||
        cg.add_library("Updater", None)
 | 
			
		||||
 | 
			
		||||
    use_state_callback = False
 | 
			
		||||
    for conf in config.get(CONF_ON_STATE_CHANGE, []):
 | 
			
		||||
        trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										59
									
								
								esphome/components/ota/ota_backend_arduino_rp2040.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								esphome/components/ota/ota_backend_arduino_rp2040.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/rp2040/preferences.h"
 | 
			
		||||
#include "ota_backend.h"
 | 
			
		||||
#include "ota_backend_arduino_rp2040.h"
 | 
			
		||||
#include "ota_component.h"
 | 
			
		||||
 | 
			
		||||
#include <Updater.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ota {
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) {
 | 
			
		||||
  bool ret = Update.begin(image_size, U_FLASH);
 | 
			
		||||
  if (ret) {
 | 
			
		||||
    rp2040::preferences_prevent_write(true);
 | 
			
		||||
    return OTA_RESPONSE_OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t error = Update.getError();
 | 
			
		||||
  if (error == UPDATE_ERROR_BOOTSTRAP)
 | 
			
		||||
    return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING;
 | 
			
		||||
  if (error == UPDATE_ERROR_NEW_FLASH_CONFIG)
 | 
			
		||||
    return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG;
 | 
			
		||||
  if (error == UPDATE_ERROR_FLASH_CONFIG)
 | 
			
		||||
    return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG;
 | 
			
		||||
  if (error == UPDATE_ERROR_SPACE)
 | 
			
		||||
    return OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE;
 | 
			
		||||
  return OTA_RESPONSE_ERROR_UNKNOWN;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ArduinoRP2040OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); }
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) {
 | 
			
		||||
  size_t written = Update.write(data, len);
 | 
			
		||||
  if (written != len) {
 | 
			
		||||
    return OTA_RESPONSE_ERROR_WRITING_FLASH;
 | 
			
		||||
  }
 | 
			
		||||
  return OTA_RESPONSE_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
OTAResponseTypes ArduinoRP2040OTABackend::end() {
 | 
			
		||||
  if (!Update.end())
 | 
			
		||||
    return OTA_RESPONSE_ERROR_UPDATE_END;
 | 
			
		||||
  return OTA_RESPONSE_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ArduinoRP2040OTABackend::abort() {
 | 
			
		||||
  Update.end();
 | 
			
		||||
  rp2040::preferences_prevent_write(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace ota
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
							
								
								
									
										27
									
								
								esphome/components/ota/ota_backend_arduino_rp2040.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/ota/ota_backend_arduino_rp2040.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/macros.h"
 | 
			
		||||
#include "ota_backend.h"
 | 
			
		||||
#include "ota_component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ota {
 | 
			
		||||
 | 
			
		||||
class ArduinoRP2040OTABackend : public OTABackend {
 | 
			
		||||
 public:
 | 
			
		||||
  OTAResponseTypes begin(size_t image_size) override;
 | 
			
		||||
  void set_update_md5(const char *md5) override;
 | 
			
		||||
  OTAResponseTypes write(uint8_t *data, size_t len) override;
 | 
			
		||||
  OTAResponseTypes end() override;
 | 
			
		||||
  void abort() override;
 | 
			
		||||
  bool supports_compression() override { return false; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ota
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
#include "ota_backend.h"
 | 
			
		||||
#include "ota_backend_arduino_esp32.h"
 | 
			
		||||
#include "ota_backend_arduino_esp8266.h"
 | 
			
		||||
#include "ota_backend_arduino_rp2040.h"
 | 
			
		||||
#include "ota_backend_esp_idf.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
@@ -35,6 +36,9 @@ std::unique_ptr<OTABackend> make_ota_backend() {
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
  return make_unique<IDFOTABackend>();
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
  return make_unique<ArduinoRP2040OTABackend>();
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
OTAComponent::OTAComponent() { global_ota_component = this; }
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ enum OTAResponseTypes {
 | 
			
		||||
  OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137,
 | 
			
		||||
  OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138,
 | 
			
		||||
  OTA_RESPONSE_ERROR_MD5_MISMATCH = 139,
 | 
			
		||||
  OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 140,
 | 
			
		||||
  OTA_RESPONSE_ERROR_UNKNOWN = 255,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,14 +5,17 @@ from esphome.config_helpers import merge_config
 | 
			
		||||
 | 
			
		||||
from esphome import git, yaml_util
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ESPHOME,
 | 
			
		||||
    CONF_FILE,
 | 
			
		||||
    CONF_FILES,
 | 
			
		||||
    CONF_MIN_VERSION,
 | 
			
		||||
    CONF_PACKAGES,
 | 
			
		||||
    CONF_REF,
 | 
			
		||||
    CONF_REFRESH,
 | 
			
		||||
    CONF_URL,
 | 
			
		||||
    CONF_USERNAME,
 | 
			
		||||
    CONF_PASSWORD,
 | 
			
		||||
    __version__ as ESPHOME_VERSION,
 | 
			
		||||
)
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
@@ -104,7 +107,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _process_base_package(config: dict) -> dict:
 | 
			
		||||
    repo_dir = git.clone_or_update(
 | 
			
		||||
    repo_dir, revert = git.clone_or_update(
 | 
			
		||||
        url=config[CONF_URL],
 | 
			
		||||
        ref=config.get(CONF_REF),
 | 
			
		||||
        refresh=config[CONF_REFRESH],
 | 
			
		||||
@@ -112,21 +115,51 @@ def _process_base_package(config: dict) -> dict:
 | 
			
		||||
        username=config.get(CONF_USERNAME),
 | 
			
		||||
        password=config.get(CONF_PASSWORD),
 | 
			
		||||
    )
 | 
			
		||||
    files: str = config[CONF_FILES]
 | 
			
		||||
    files: list[str] = config[CONF_FILES]
 | 
			
		||||
 | 
			
		||||
    def get_packages(files) -> dict:
 | 
			
		||||
        packages = {}
 | 
			
		||||
        for file in files:
 | 
			
		||||
            yaml_file: Path = repo_dir / file
 | 
			
		||||
 | 
			
		||||
            if not yaml_file.is_file():
 | 
			
		||||
                raise cv.Invalid(
 | 
			
		||||
                    f"{file} does not exist in repository", path=[CONF_FILES]
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                new_yaml = yaml_util.load_yaml(yaml_file)
 | 
			
		||||
                if (
 | 
			
		||||
                    CONF_ESPHOME in new_yaml
 | 
			
		||||
                    and CONF_MIN_VERSION in new_yaml[CONF_ESPHOME]
 | 
			
		||||
                ):
 | 
			
		||||
                    min_version = new_yaml[CONF_ESPHOME][CONF_MIN_VERSION]
 | 
			
		||||
                    if cv.Version.parse(min_version) > cv.Version.parse(
 | 
			
		||||
                        ESPHOME_VERSION
 | 
			
		||||
                    ):
 | 
			
		||||
                        raise cv.Invalid(
 | 
			
		||||
                            f"Current ESPHome Version is too old to use this package: {ESPHOME_VERSION} < {min_version}"
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
                packages[file] = new_yaml
 | 
			
		||||
            except EsphomeError as e:
 | 
			
		||||
                raise cv.Invalid(
 | 
			
		||||
                    f"{file} is not a valid YAML file. Please check the file contents."
 | 
			
		||||
                ) from e
 | 
			
		||||
        return packages
 | 
			
		||||
 | 
			
		||||
    packages = {}
 | 
			
		||||
    for file in files:
 | 
			
		||||
        yaml_file: Path = repo_dir / file
 | 
			
		||||
 | 
			
		||||
        if not yaml_file.is_file():
 | 
			
		||||
            raise cv.Invalid(f"{file} does not exist in repository", path=[CONF_FILES])
 | 
			
		||||
    try:
 | 
			
		||||
        packages = get_packages(files)
 | 
			
		||||
    except cv.Invalid:
 | 
			
		||||
        if revert is not None:
 | 
			
		||||
            revert()
 | 
			
		||||
            packages = get_packages(files)
 | 
			
		||||
    finally:
 | 
			
		||||
        if packages is None:
 | 
			
		||||
            raise cv.Invalid("Failed to load packages")
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            packages[file] = yaml_util.load_yaml(yaml_file)
 | 
			
		||||
        except EsphomeError as e:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"{file} is not a valid YAML file. Please check the file contents."
 | 
			
		||||
            ) from e
 | 
			
		||||
    return {"packages": packages}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,62 +11,43 @@ void PulseMeterSensor::setup() {
 | 
			
		||||
  this->isr_pin_ = pin_->to_isr();
 | 
			
		||||
  this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE);
 | 
			
		||||
 | 
			
		||||
  this->pulse_width_us_ = 0;
 | 
			
		||||
  this->last_detected_edge_us_ = 0;
 | 
			
		||||
  this->last_valid_low_edge_us_ = 0;
 | 
			
		||||
  this->last_valid_high_edge_us_ = 0;
 | 
			
		||||
  this->last_valid_low_edge_us_ = 0;
 | 
			
		||||
  this->sensor_is_high_ = this->isr_pin_.digital_read();
 | 
			
		||||
  this->has_valid_high_edge_ = false;
 | 
			
		||||
  this->has_valid_low_edge_ = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PulseMeterSensor::loop() {
 | 
			
		||||
  // Get a local copy of the volatile sensor values, to make sure they are not
 | 
			
		||||
  // modified by the ISR. This could cause overflow in the following arithmetic
 | 
			
		||||
  const uint32_t last_valid_high_edge_us = this->last_valid_high_edge_us_;
 | 
			
		||||
  const bool has_valid_high_edge = this->has_valid_high_edge_;
 | 
			
		||||
  const uint32_t now = micros();
 | 
			
		||||
 | 
			
		||||
  // Check to see if we should filter this edge out
 | 
			
		||||
  if (this->filter_mode_ == FILTER_EDGE) {
 | 
			
		||||
    if ((this->last_detected_edge_us_ - this->last_valid_high_edge_us_) >= this->filter_us_) {
 | 
			
		||||
      // Don't measure the first valid pulse (we need at least two pulses to measure the width)
 | 
			
		||||
      if (this->last_valid_high_edge_us_ != 0) {
 | 
			
		||||
        this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_);
 | 
			
		||||
      }
 | 
			
		||||
      this->total_pulses_++;
 | 
			
		||||
      this->last_valid_high_edge_us_ = this->last_detected_edge_us_;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    // Make sure the signal has been stable long enough
 | 
			
		||||
    if ((now - this->last_detected_edge_us_) >= this->filter_us_) {
 | 
			
		||||
      // Only consider HIGH pulses and "new" edges if sensor state is LOW
 | 
			
		||||
      if (!this->sensor_is_high_ && this->isr_pin_.digital_read() &&
 | 
			
		||||
          (this->last_detected_edge_us_ != this->last_valid_high_edge_us_)) {
 | 
			
		||||
        // Don't measure the first valid pulse (we need at least two pulses to measure the width)
 | 
			
		||||
        if (this->last_valid_high_edge_us_ != 0) {
 | 
			
		||||
          this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_);
 | 
			
		||||
        }
 | 
			
		||||
        this->sensor_is_high_ = true;
 | 
			
		||||
        this->total_pulses_++;
 | 
			
		||||
        this->last_valid_high_edge_us_ = this->last_detected_edge_us_;
 | 
			
		||||
      }
 | 
			
		||||
      // Only consider LOW pulses and "new" edges if sensor state is HIGH
 | 
			
		||||
      else if (this->sensor_is_high_ && !this->isr_pin_.digital_read() &&
 | 
			
		||||
               (this->last_detected_edge_us_ != this->last_valid_low_edge_us_)) {
 | 
			
		||||
        this->sensor_is_high_ = false;
 | 
			
		||||
        this->last_valid_low_edge_us_ = this->last_detected_edge_us_;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // If we've exceeded our timeout interval without receiving any pulses, assume 0 pulses/min until
 | 
			
		||||
  // we get at least two valid pulses.
 | 
			
		||||
  const uint32_t time_since_valid_edge_us = now - this->last_valid_high_edge_us_;
 | 
			
		||||
  if ((this->last_valid_high_edge_us_ != 0) && (time_since_valid_edge_us > this->timeout_us_) &&
 | 
			
		||||
      (this->pulse_width_us_ != 0)) {
 | 
			
		||||
  // If we've exceeded our timeout interval without receiving any pulses, assume
 | 
			
		||||
  // 0 pulses/min until we get at least two valid pulses.
 | 
			
		||||
  const uint32_t time_since_valid_edge_us = now - last_valid_high_edge_us;
 | 
			
		||||
  if ((has_valid_high_edge) && (time_since_valid_edge_us > this->timeout_us_)) {
 | 
			
		||||
    ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000);
 | 
			
		||||
 | 
			
		||||
    this->pulse_width_us_ = 0;
 | 
			
		||||
    this->last_detected_edge_us_ = 0;
 | 
			
		||||
    this->last_valid_high_edge_us_ = 0;
 | 
			
		||||
    this->last_valid_low_edge_us_ = 0;
 | 
			
		||||
    this->has_detected_edge_ = false;
 | 
			
		||||
    this->has_valid_high_edge_ = false;
 | 
			
		||||
    this->has_valid_low_edge_ = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // We quantize our pulse widths to 1 ms to avoid unnecessary jitter
 | 
			
		||||
  const uint32_t pulse_width_ms = this->pulse_width_us_ / 1000;
 | 
			
		||||
  if (this->pulse_width_dedupe_.next(pulse_width_ms)) {
 | 
			
		||||
    if (pulse_width_ms == 0) {
 | 
			
		||||
      // Treat 0 pulse width as 0 pulses/min (normally because we've not detected any pulses for a while)
 | 
			
		||||
      // Treat 0 pulse width as 0 pulses/min (normally because we've not
 | 
			
		||||
      // detected any pulses for a while)
 | 
			
		||||
      this->publish_state(0);
 | 
			
		||||
    } else {
 | 
			
		||||
      // Calculate pulses/min from the pulse width in ms
 | 
			
		||||
@@ -96,9 +77,11 @@ void PulseMeterSensor::dump_config() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) {
 | 
			
		||||
  // This is an interrupt handler - we can't call any virtual method from this method
 | 
			
		||||
  // This is an interrupt handler - we can't call any virtual method from this
 | 
			
		||||
  // method
 | 
			
		||||
 | 
			
		||||
  // Get the current time before we do anything else so the measurements are consistent
 | 
			
		||||
  // Get the current time before we do anything else so the measurements are
 | 
			
		||||
  // consistent
 | 
			
		||||
  const uint32_t now = micros();
 | 
			
		||||
 | 
			
		||||
  // We only look at rising edges in EDGE mode, and all edges in PULSE mode
 | 
			
		||||
@@ -106,7 +89,45 @@ void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) {
 | 
			
		||||
    if (sensor->isr_pin_.digital_read()) {
 | 
			
		||||
      sensor->last_detected_edge_us_ = now;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Check to see if we should filter this edge out
 | 
			
		||||
  if (sensor->filter_mode_ == FILTER_EDGE) {
 | 
			
		||||
    if ((sensor->last_detected_edge_us_ - sensor->last_valid_high_edge_us_) >= sensor->filter_us_) {
 | 
			
		||||
      // Don't measure the first valid pulse (we need at least two pulses to
 | 
			
		||||
      // measure the width)
 | 
			
		||||
      if (sensor->has_valid_high_edge_) {
 | 
			
		||||
        sensor->pulse_width_us_ = (sensor->last_detected_edge_us_ - sensor->last_valid_high_edge_us_);
 | 
			
		||||
      }
 | 
			
		||||
      sensor->total_pulses_++;
 | 
			
		||||
      sensor->last_valid_high_edge_us_ = sensor->last_detected_edge_us_;
 | 
			
		||||
      sensor->has_valid_high_edge_ = true;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    // Filter Mode is PULSE
 | 
			
		||||
    bool pin_val = sensor->isr_pin_.digital_read();
 | 
			
		||||
    // Ignore false edges that may be caused by bouncing and exit the ISR ASAP
 | 
			
		||||
    if (pin_val == sensor->sensor_is_high_) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    // Make sure the signal has been stable long enough
 | 
			
		||||
    if (sensor->has_detected_edge_ && (now - sensor->last_detected_edge_us_ >= sensor->filter_us_)) {
 | 
			
		||||
      if (pin_val) {
 | 
			
		||||
        sensor->has_valid_high_edge_ = true;
 | 
			
		||||
        sensor->last_valid_high_edge_us_ = sensor->last_detected_edge_us_;
 | 
			
		||||
        sensor->sensor_is_high_ = true;
 | 
			
		||||
      } else {
 | 
			
		||||
        // Count pulses when a sufficiently long high pulse is concluded.
 | 
			
		||||
        sensor->total_pulses_++;
 | 
			
		||||
        if (sensor->has_valid_low_edge_) {
 | 
			
		||||
          sensor->pulse_width_us_ = sensor->last_detected_edge_us_ - sensor->last_valid_low_edge_us_;
 | 
			
		||||
        }
 | 
			
		||||
        sensor->has_valid_low_edge_ = true;
 | 
			
		||||
        sensor->last_valid_low_edge_us_ = sensor->last_detected_edge_us_;
 | 
			
		||||
        sensor->sensor_is_high_ = false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    sensor->has_detected_edge_ = true;
 | 
			
		||||
    sensor->last_detected_edge_us_ = now;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -42,11 +42,14 @@ class PulseMeterSensor : public sensor::Sensor, public Component {
 | 
			
		||||
  Deduplicator<uint32_t> total_dedupe_;
 | 
			
		||||
 | 
			
		||||
  volatile uint32_t last_detected_edge_us_ = 0;
 | 
			
		||||
  volatile uint32_t last_valid_low_edge_us_ = 0;
 | 
			
		||||
  volatile uint32_t last_valid_high_edge_us_ = 0;
 | 
			
		||||
  volatile uint32_t last_valid_low_edge_us_ = 0;
 | 
			
		||||
  volatile uint32_t pulse_width_us_ = 0;
 | 
			
		||||
  volatile uint32_t total_pulses_ = 0;
 | 
			
		||||
  volatile bool sensor_is_high_ = false;
 | 
			
		||||
  volatile bool has_detected_edge_ = false;
 | 
			
		||||
  volatile bool has_valid_high_edge_ = false;
 | 
			
		||||
  volatile bool has_valid_low_edge_ = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace pulse_meter
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["uart"]
 | 
			
		||||
AUTO_LOAD = ["binary_sensor"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
rdm6300_ns = cg.esphome_ns.namespace("rdm6300")
 | 
			
		||||
RDM6300Component = rdm6300_ns.class_("RDM6300Component", cg.Component, uart.UARTDevice)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										161
									
								
								esphome/components/rp2040/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								esphome/components/rp2040/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,161 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_BOARD,
 | 
			
		||||
    CONF_FRAMEWORK,
 | 
			
		||||
    CONF_SOURCE,
 | 
			
		||||
    CONF_VERSION,
 | 
			
		||||
    KEY_CORE,
 | 
			
		||||
    KEY_FRAMEWORK_VERSION,
 | 
			
		||||
    KEY_TARGET_FRAMEWORK,
 | 
			
		||||
    KEY_TARGET_PLATFORM,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, coroutine_with_priority
 | 
			
		||||
 | 
			
		||||
from .const import KEY_BOARD, KEY_RP2040, rp2040_ns
 | 
			
		||||
 | 
			
		||||
# force import gpio to register pin schema
 | 
			
		||||
from .gpio import rp2040_pin_to_code  # noqa
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
CODEOWNERS = ["@jesserockz"]
 | 
			
		||||
AUTO_LOAD = []
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def set_core_data(config):
 | 
			
		||||
    CORE.data[KEY_RP2040] = {}
 | 
			
		||||
    CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "rp2040"
 | 
			
		||||
    CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
 | 
			
		||||
    CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
 | 
			
		||||
        config[CONF_FRAMEWORK][CONF_VERSION]
 | 
			
		||||
    )
 | 
			
		||||
    CORE.data[KEY_RP2040][KEY_BOARD] = config[CONF_BOARD]
 | 
			
		||||
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _format_framework_arduino_version(ver: cv.Version) -> str:
 | 
			
		||||
    # The most recent releases have not been uploaded to platformio so grabbing them directly from
 | 
			
		||||
    # the GitHub release is one path forward for now.
 | 
			
		||||
    return f"https://github.com/earlephilhower/arduino-pico/releases/download/{ver}/rp2040-{ver}.zip"
 | 
			
		||||
 | 
			
		||||
    # format the given arduino (https://github.com/earlephilhower/arduino-pico/releases) version to
 | 
			
		||||
    # a PIO earlephilhower/framework-arduinopico value
 | 
			
		||||
    # List of package versions: https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico
 | 
			
		||||
    # return f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# NOTE: Keep this in mind when updating the recommended version:
 | 
			
		||||
#  * The new version needs to be thoroughly validated before changing the
 | 
			
		||||
#    recommended version as otherwise a bunch of devices could be bricked
 | 
			
		||||
#  * For all constants below, update platformio.ini (in this repo)
 | 
			
		||||
#    and platformio.ini/platformio-lint.ini in the esphome-docker-base repository
 | 
			
		||||
 | 
			
		||||
# The default/recommended arduino framework version
 | 
			
		||||
#  - https://github.com/earlephilhower/arduino-pico/releases
 | 
			
		||||
#  - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico
 | 
			
		||||
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 6, 2)
 | 
			
		||||
 | 
			
		||||
# The platformio/raspberrypi version to use for arduino frameworks
 | 
			
		||||
#  - https://github.com/platformio/platform-raspberrypi/releases
 | 
			
		||||
#  - https://api.registry.platformio.org/v3/packages/platformio/platform/raspberrypi
 | 
			
		||||
ARDUINO_PLATFORM_VERSION = cv.Version(1, 7, 0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _arduino_check_versions(value):
 | 
			
		||||
    value = value.copy()
 | 
			
		||||
    lookups = {
 | 
			
		||||
        "dev": (cv.Version(2, 6, 2), "https://github.com/earlephilhower/arduino-pico"),
 | 
			
		||||
        "latest": (cv.Version(2, 6, 2), None),
 | 
			
		||||
        "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if value[CONF_VERSION] in lookups:
 | 
			
		||||
        if CONF_SOURCE in value:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                "Framework version needs to be explicitly specified when custom source is used."
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        version, source = lookups[value[CONF_VERSION]]
 | 
			
		||||
    else:
 | 
			
		||||
        version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
 | 
			
		||||
        source = value.get(CONF_SOURCE, None)
 | 
			
		||||
 | 
			
		||||
    value[CONF_VERSION] = str(version)
 | 
			
		||||
    value[CONF_SOURCE] = source or _format_framework_arduino_version(version)
 | 
			
		||||
 | 
			
		||||
    value[CONF_PLATFORM_VERSION] = value.get(
 | 
			
		||||
        CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION))
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
            "The selected Arduino framework version is not the recommended one."
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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}"
 | 
			
		||||
    except cv.Invalid:
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONF_PLATFORM_VERSION = "platform_version"
 | 
			
		||||
 | 
			
		||||
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_SOURCE): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    _arduino_check_versions,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_BOARD): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_FRAMEWORK, default={}): ARDUINO_FRAMEWORK_SCHEMA,
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    set_core_data,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@coroutine_with_priority(1000)
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    cg.add(rp2040_ns.setup_preferences())
 | 
			
		||||
 | 
			
		||||
    cg.add_platformio_option("board", config[CONF_BOARD])
 | 
			
		||||
    cg.add_build_flag("-DUSE_RP2040")
 | 
			
		||||
    cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
 | 
			
		||||
    cg.add_define("ESPHOME_VARIANT", "RP2040")
 | 
			
		||||
 | 
			
		||||
    conf = config[CONF_FRAMEWORK]
 | 
			
		||||
    cg.add_platformio_option("framework", "arduino")
 | 
			
		||||
    cg.add_build_flag("-DUSE_ARDUINO")
 | 
			
		||||
    cg.add_build_flag("-DUSE_RP2040_FRAMEWORK_ARDUINO")
 | 
			
		||||
    # cg.add_build_flag("-DPICO_BOARD=pico_w")
 | 
			
		||||
    cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
 | 
			
		||||
    cg.add_platformio_option(
 | 
			
		||||
        "platform_packages",
 | 
			
		||||
        [f"earlephilhower/framework-arduinopico @ {conf[CONF_SOURCE]}"],
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    cg.add_platformio_option("board_build.core", "earlephilhower")
 | 
			
		||||
    cg.add_platformio_option("board_build.filesystem_size", "1m")
 | 
			
		||||
 | 
			
		||||
    ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
 | 
			
		||||
    cg.add_define(
 | 
			
		||||
        "USE_ARDUINO_VERSION_CODE",
 | 
			
		||||
        cg.RawExpression(f"VERSION_CODE({ver.major}, {ver.minor}, {ver.patch})"),
 | 
			
		||||
    )
 | 
			
		||||
							
								
								
									
										19
									
								
								esphome/components/rp2040/boards.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								esphome/components/rp2040/boards.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
RP2040_BASE_PINS = {}
 | 
			
		||||
 | 
			
		||||
RP2040_BOARD_PINS = {
 | 
			
		||||
    "pico": {
 | 
			
		||||
        "SDA": 4,
 | 
			
		||||
        "SCL": 5,
 | 
			
		||||
        "LED": 25,
 | 
			
		||||
        "SDA1": 26,
 | 
			
		||||
        "SCL1": 27,
 | 
			
		||||
    },
 | 
			
		||||
    "rpipico": "pico",
 | 
			
		||||
    "rpipicow": {
 | 
			
		||||
        "SDA": 4,
 | 
			
		||||
        "SCL": 5,
 | 
			
		||||
        "LED": 32,
 | 
			
		||||
        "SDA1": 26,
 | 
			
		||||
        "SCL1": 27,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								esphome/components/rp2040/const.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								esphome/components/rp2040/const.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
KEY_BOARD = "board"
 | 
			
		||||
KEY_RP2040 = "rp2040"
 | 
			
		||||
 | 
			
		||||
rp2040_ns = cg.esphome_ns.namespace("rp2040")
 | 
			
		||||
							
								
								
									
										33
									
								
								esphome/components/rp2040/core.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								esphome/components/rp2040/core.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
 | 
			
		||||
#include "core.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#include "hardware/watchdog.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR HOT yield() { ::yield(); }
 | 
			
		||||
uint32_t IRAM_ATTR HOT millis() { return ::millis(); }
 | 
			
		||||
void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
 | 
			
		||||
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
 | 
			
		||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
 | 
			
		||||
void arch_restart() {
 | 
			
		||||
  watchdog_reboot(0, 0, 10);
 | 
			
		||||
  while (1) {
 | 
			
		||||
    continue;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
void arch_init() { watchdog_enable(0x7fffff, false); }
 | 
			
		||||
void IRAM_ATTR HOT arch_feed_wdt() { watchdog_update(); }
 | 
			
		||||
 | 
			
		||||
uint8_t progmem_read_byte(const uint8_t *addr) {
 | 
			
		||||
  return pgm_read_byte(addr);  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return ulMainGetRunTimeCounterValue(); }
 | 
			
		||||
uint32_t arch_get_cpu_freq_hz() { return RP2040::f_cpu(); }
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
							
								
								
									
										14
									
								
								esphome/components/rp2040/core.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								esphome/components/rp2040/core.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include <pico.h>
 | 
			
		||||
 | 
			
		||||
extern "C" unsigned long ulMainGetRunTimeCounterValue();
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace rp2040 {}  // namespace rp2040
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
							
								
								
									
										103
									
								
								esphome/components/rp2040/gpio.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								esphome/components/rp2040/gpio.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
 | 
			
		||||
#include "gpio.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace rp2040 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "rp2040";
 | 
			
		||||
 | 
			
		||||
static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
 | 
			
		||||
  if (flags == gpio::FLAG_INPUT) {  // NOLINT(bugprone-branch-clone)
 | 
			
		||||
    return INPUT;
 | 
			
		||||
  } else if (flags == gpio::FLAG_OUTPUT) {
 | 
			
		||||
    return OUTPUT;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
 | 
			
		||||
    return INPUT_PULLUP;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
 | 
			
		||||
    return INPUT_PULLDOWN;
 | 
			
		||||
    // } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
 | 
			
		||||
    //   return OpenDrain;
 | 
			
		||||
  } else {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ISRPinArg {
 | 
			
		||||
  uint8_t pin;
 | 
			
		||||
  bool inverted;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ISRInternalGPIOPin RP2040GPIOPin::to_isr() const {
 | 
			
		||||
  auto *arg = new ISRPinArg{};  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  arg->pin = pin_;
 | 
			
		||||
  arg->inverted = inverted_;
 | 
			
		||||
  return ISRInternalGPIOPin((void *) arg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RP2040GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
 | 
			
		||||
  PinStatus arduino_mode = LOW;
 | 
			
		||||
  switch (type) {
 | 
			
		||||
    case gpio::INTERRUPT_RISING_EDGE:
 | 
			
		||||
      arduino_mode = inverted_ ? FALLING : RISING;
 | 
			
		||||
      break;
 | 
			
		||||
    case gpio::INTERRUPT_FALLING_EDGE:
 | 
			
		||||
      arduino_mode = inverted_ ? RISING : FALLING;
 | 
			
		||||
      break;
 | 
			
		||||
    case gpio::INTERRUPT_ANY_EDGE:
 | 
			
		||||
      arduino_mode = CHANGE;
 | 
			
		||||
      break;
 | 
			
		||||
    case gpio::INTERRUPT_LOW_LEVEL:
 | 
			
		||||
      arduino_mode = inverted_ ? HIGH : LOW;
 | 
			
		||||
      break;
 | 
			
		||||
    case gpio::INTERRUPT_HIGH_LEVEL:
 | 
			
		||||
      arduino_mode = inverted_ ? LOW : HIGH;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  attachInterrupt(pin_, func, arduino_mode, arg);
 | 
			
		||||
}
 | 
			
		||||
void RP2040GPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  pinMode(pin_, flags_to_mode(flags, pin_));  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string RP2040GPIOPin::dump_summary() const {
 | 
			
		||||
  char buffer[32];
 | 
			
		||||
  snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
 | 
			
		||||
  return buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool RP2040GPIOPin::digital_read() {
 | 
			
		||||
  return bool(digitalRead(pin_)) != inverted_;  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
void RP2040GPIOPin::digital_write(bool value) {
 | 
			
		||||
  digitalWrite(pin_, value != inverted_ ? 1 : 0);  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
void RP2040GPIOPin::detach_interrupt() const { detachInterrupt(pin_); }
 | 
			
		||||
 | 
			
		||||
}  // namespace rp2040
 | 
			
		||||
 | 
			
		||||
using namespace rp2040;
 | 
			
		||||
 | 
			
		||||
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  return bool(digitalRead(arg->pin)) != arg->inverted;  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  digitalWrite(arg->pin, value != arg->inverted ? 1 : 0);  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
 | 
			
		||||
  // TODO: implement
 | 
			
		||||
  // auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  // GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin);
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  pinMode(arg->pin, flags_to_mode(flags, arg->pin));  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_RP2040
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user