mirror of
https://github.com/esphome/esphome.git
synced 2025-08-10 20:29:24 +00:00
Compare commits
157 Commits
2022.9.1
...
2022.11.0b
Author | SHA1 | Date | |
---|---|---|---|
![]() |
58fda40389 | ||
![]() |
6a73699a38 | ||
![]() |
3bd6456fbe | ||
![]() |
608be4e050 | ||
![]() |
10f590324b | ||
![]() |
39f0f748bf | ||
![]() |
9efe59a984 | ||
![]() |
fcb02af782 | ||
![]() |
3aeef1afd4 | ||
![]() |
a45ee8f4ac | ||
![]() |
31b62d7dca | ||
![]() |
22f81475db | ||
![]() |
cc7cf73d59 | ||
![]() |
9682e60a25 | ||
![]() |
c6afae0da5 | ||
![]() |
4fa0e860ad | ||
![]() |
8c122aa372 | ||
![]() |
5a0bf9fee9 | ||
![]() |
dc794918ed | ||
![]() |
02b15dbc4a | ||
![]() |
ed316b1ce3 | ||
![]() |
d7858f16c1 | ||
![]() |
291deb12ad | ||
![]() |
3e110681c9 | ||
![]() |
65fbfa2097 | ||
![]() |
16ebf9da4c | ||
![]() |
2c76381fcd | ||
![]() |
90683223dd | ||
![]() |
de79171815 | ||
![]() |
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 | ||
![]() |
e8de6a3a67 | ||
![]() |
3c320c4c83 | ||
![]() |
3b83f967e4 | ||
![]() |
5e96b8ef7c | ||
![]() |
5df0e82c37 | ||
![]() |
fd57b21aff | ||
![]() |
6087183a0c | ||
![]() |
01b7c4200e | ||
![]() |
7171286c3c | ||
![]() |
2d58239b74 | ||
![]() |
6b52f62531 | ||
![]() |
1001d9c04e | ||
![]() |
d220d41182 | ||
![]() |
c3a8972550 | ||
![]() |
263b603188 | ||
![]() |
584b722e7e | ||
![]() |
05edfd0e82 | ||
![]() |
16249c02a5 | ||
![]() |
e8ff36d1f3 | ||
![]() |
ed443c6153 | ||
![]() |
f4a84765cd | ||
![]() |
106de3530d | ||
![]() |
119c3f6f46 | ||
![]() |
d2c1c7507c | ||
![]() |
efdb3d1f40 | ||
![]() |
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
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
today="$(date --utc '+%Y%m%d')"
|
||||
TAG="${TAG}${today}"
|
||||
fi
|
||||
echo "::set-output name=tag::${TAG}"
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
# yamllint enable rule:line-length
|
||||
|
||||
deploy-pypi:
|
||||
|
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
|
@@ -3,15 +3,15 @@
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 22.6.0
|
||||
rev: 22.10.0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
- --safe
|
||||
- --quiet
|
||||
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 4.0.1
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 5.0.4
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
@@ -27,7 +27,7 @@ repos:
|
||||
- --branch=release
|
||||
- --branch=beta
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.37.3
|
||||
rev: v3.2.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
args: [--py39-plus]
|
||||
|
12
CODEOWNERS
12
CODEOWNERS
@@ -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,12 +58,14 @@ 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
|
||||
esphome/components/delonghi/* @grob6000
|
||||
esphome/components/dfplayer/* @glmnet
|
||||
esphome/components/dht/* @OttoWinter
|
||||
esphome/components/display_menu_base/* @numo68
|
||||
esphome/components/dps310/* @kbx81
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
@@ -70,13 +73,16 @@ esphome/components/ektf2232/* @jesserockz
|
||||
esphome/components/ens210/* @itn3rd77
|
||||
esphome/components/esp32/* @esphome/core
|
||||
esphome/components/esp32_ble/* @jesserockz
|
||||
esphome/components/esp32_ble_client/* @jesserockz
|
||||
esphome/components/esp32_ble_server/* @jesserockz
|
||||
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
|
||||
@@ -105,6 +111,7 @@ esphome/components/integration/* @OttoWinter
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/json/* @OttoWinter
|
||||
esphome/components/kalman_combinator/* @Cat-Ion
|
||||
esphome/components/lcd_menu/* @numo68
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
||||
@@ -179,6 +186,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
|
||||
@@ -253,8 +262,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.5 \
|
||||
# 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
|
@@ -38,7 +38,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
|
||||
}
|
||||
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->conn_id)
|
||||
if (param->read.conn_id != this->parent()->get_conn_id())
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
@@ -88,8 +88,8 @@ void AirthingsWaveMini::update() {
|
||||
}
|
||||
|
||||
void AirthingsWaveMini::request_read_values_() {
|
||||
auto status =
|
||||
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt
|
||||
}
|
||||
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->conn_id)
|
||||
if (param->read.conn_id != this->parent()->get_conn_id())
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
@@ -109,8 +109,8 @@ void AirthingsWavePlus::update() {
|
||||
}
|
||||
|
||||
void AirthingsWavePlus::request_read_values_() {
|
||||
auto status =
|
||||
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle_, ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
|
||||
}
|
||||
|
@@ -76,9 +76,9 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i
|
||||
if (this->current_sensor_ > 0) {
|
||||
if (this->illuminance_ != nullptr) {
|
||||
auto *packet = this->encoder_->get_light_level_request();
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
|
||||
this->char_handle_, packet->length, packet->data,
|
||||
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
|
||||
status);
|
||||
@@ -102,8 +102,8 @@ void Am43::update() {
|
||||
if (this->battery_ != nullptr) {
|
||||
auto *packet = this->encoder_->get_battery_level_request();
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
|
||||
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
}
|
||||
|
@@ -27,8 +27,8 @@ void Am43Component::loop() {
|
||||
if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) {
|
||||
auto *packet = this->encoder_->get_send_pin_request(this->pin_);
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
|
||||
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str());
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status);
|
||||
@@ -54,8 +54,8 @@ void Am43Component::control(const CoverCall &call) {
|
||||
if (call.get_stop()) {
|
||||
auto *packet = this->encoder_->get_stop_request();
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
|
||||
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status);
|
||||
}
|
||||
@@ -66,8 +66,8 @@ void Am43Component::control(const CoverCall &call) {
|
||||
pos = 1 - pos;
|
||||
auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100));
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length,
|
||||
packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
|
||||
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
ESP_LOGW(TAG, "[%s] Error writing set_position command to device, error = %d", this->get_name().c_str(), status);
|
||||
}
|
||||
@@ -92,7 +92,8 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
}
|
||||
this->char_handle_ = chr->handle;
|
||||
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle);
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
|
||||
chr->handle);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
@@ -122,9 +123,9 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
if (this->decoder_->pin_ok_) {
|
||||
ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str());
|
||||
auto *packet = this->encoder_->get_position_request();
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
|
||||
this->char_handle_, packet->length, packet->data,
|
||||
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
ESP_LOGW(TAG, "[%s] Error writing set_position to device, error = %d", this->get_name().c_str(), status);
|
||||
} else {
|
||||
|
@@ -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(
|
||||
{
|
||||
|
@@ -34,15 +34,17 @@ void Anova::control(const ClimateCall &call) {
|
||||
ESP_LOGW(TAG, "Unsupported mode: %d", mode);
|
||||
return;
|
||||
}
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
}
|
||||
if (call.get_target_temperature().has_value()) {
|
||||
auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature());
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
}
|
||||
@@ -65,7 +67,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
||||
}
|
||||
this->char_handle_ = chr->handle;
|
||||
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda, chr->handle);
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
|
||||
chr->handle);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
@@ -112,8 +115,8 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
||||
}
|
||||
if (pkt != nullptr) {
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length,
|
||||
pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(),
|
||||
status);
|
||||
@@ -137,8 +140,9 @@ void Anova::update() {
|
||||
auto *pkt = this->codec_->get_read_device_status_request();
|
||||
if (this->current_request_ == 0)
|
||||
this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c');
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status)
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status);
|
||||
this->current_request_++;
|
||||
|
@@ -27,7 +27,6 @@ service APIConnection {
|
||||
rpc subscribe_logs (SubscribeLogsRequest) returns (void) {}
|
||||
rpc subscribe_homeassistant_services (SubscribeHomeassistantServicesRequest) returns (void) {}
|
||||
rpc subscribe_home_assistant_states (SubscribeHomeAssistantStatesRequest) returns (void) {}
|
||||
rpc subscribe_bluetooth_le_advertisements (SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
|
||||
rpc get_time (GetTimeRequest) returns (GetTimeResponse) {
|
||||
option (needs_authentication) = false;
|
||||
}
|
||||
@@ -44,6 +43,16 @@ service APIConnection {
|
||||
rpc button_command (ButtonCommandRequest) returns (void) {}
|
||||
rpc lock_command (LockCommandRequest) returns (void) {}
|
||||
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
|
||||
|
||||
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
|
||||
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
|
||||
rpc bluetooth_gatt_get_services(BluetoothGATTGetServicesRequest) returns (void) {}
|
||||
rpc bluetooth_gatt_read(BluetoothGATTReadRequest) returns (void) {}
|
||||
rpc bluetooth_gatt_write(BluetoothGATTWriteRequest) returns (void) {}
|
||||
rpc bluetooth_gatt_read_descriptor(BluetoothGATTReadDescriptorRequest) returns (void) {}
|
||||
rpc bluetooth_gatt_write_descriptor(BluetoothGATTWriteDescriptorRequest) returns (void) {}
|
||||
rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
|
||||
rpc subscribe_bluetooth_connections_free(SubscribeBluetoothConnectionsFreeRequest) returns (BluetoothConnectionsFreeResponse) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +87,8 @@ message HelloRequest {
|
||||
// Not strictly necessary to send but nice for debugging
|
||||
// purposes.
|
||||
string client_info = 1;
|
||||
uint32 api_version_major = 2;
|
||||
uint32 api_version_minor = 3;
|
||||
}
|
||||
|
||||
// Confirmation of successful connection request.
|
||||
@@ -192,7 +203,9 @@ message DeviceInfoResponse {
|
||||
|
||||
uint32 webserver_port = 10;
|
||||
|
||||
bool has_bluetooth_proxy = 11;
|
||||
uint32 bluetooth_proxy_version = 11;
|
||||
|
||||
string manufacturer = 12;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
@@ -1111,7 +1124,8 @@ message SubscribeBluetoothLEAdvertisementsRequest {
|
||||
|
||||
message BluetoothServiceData {
|
||||
string uuid = 1;
|
||||
repeated uint32 data = 2 [packed=false];
|
||||
repeated uint32 legacy_data = 2 [deprecated = true];
|
||||
bytes data = 3; // Changed in proto version 1.7
|
||||
}
|
||||
message BluetoothLEAdvertisementResponse {
|
||||
option (id) = 67;
|
||||
@@ -1127,3 +1141,190 @@ message BluetoothLEAdvertisementResponse {
|
||||
repeated BluetoothServiceData service_data = 5;
|
||||
repeated BluetoothServiceData manufacturer_data = 6;
|
||||
}
|
||||
|
||||
enum BluetoothDeviceRequestType {
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0;
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1;
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2;
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3;
|
||||
}
|
||||
|
||||
message BluetoothDeviceRequest {
|
||||
option (id) = 68;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
BluetoothDeviceRequestType request_type = 2;
|
||||
}
|
||||
|
||||
message BluetoothDeviceConnectionResponse {
|
||||
option (id) = 69;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
bool connected = 2;
|
||||
uint32 mtu = 3;
|
||||
int32 error = 4;
|
||||
}
|
||||
|
||||
message BluetoothGATTGetServicesRequest {
|
||||
option (id) = 70;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
}
|
||||
|
||||
message BluetoothGATTDescriptor {
|
||||
repeated uint64 uuid = 1;
|
||||
uint32 handle = 2;
|
||||
}
|
||||
|
||||
message BluetoothGATTCharacteristic {
|
||||
repeated uint64 uuid = 1;
|
||||
uint32 handle = 2;
|
||||
uint32 properties = 3;
|
||||
repeated BluetoothGATTDescriptor descriptors = 4;
|
||||
}
|
||||
|
||||
message BluetoothGATTService {
|
||||
repeated uint64 uuid = 1;
|
||||
uint32 handle = 2;
|
||||
repeated BluetoothGATTCharacteristic characteristics = 3;
|
||||
}
|
||||
|
||||
message BluetoothGATTGetServicesResponse {
|
||||
option (id) = 71;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
repeated BluetoothGATTService services = 2;
|
||||
}
|
||||
|
||||
message BluetoothGATTGetServicesDoneResponse {
|
||||
option (id) = 72;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
}
|
||||
|
||||
message BluetoothGATTReadRequest {
|
||||
option (id) = 73;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
}
|
||||
|
||||
message BluetoothGATTReadResponse {
|
||||
option (id) = 74;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
|
||||
bytes data = 3;
|
||||
|
||||
}
|
||||
|
||||
message BluetoothGATTWriteRequest {
|
||||
option (id) = 75;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
bool response = 3;
|
||||
|
||||
bytes data = 4;
|
||||
}
|
||||
|
||||
message BluetoothGATTReadDescriptorRequest {
|
||||
option (id) = 76;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
}
|
||||
|
||||
message BluetoothGATTWriteDescriptorRequest {
|
||||
option (id) = 77;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
|
||||
bytes data = 3;
|
||||
}
|
||||
|
||||
message BluetoothGATTNotifyRequest {
|
||||
option (id) = 78;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
bool enable = 3;
|
||||
}
|
||||
|
||||
message BluetoothGATTNotifyDataResponse {
|
||||
option (id) = 79;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
|
||||
bytes data = 3;
|
||||
}
|
||||
|
||||
message SubscribeBluetoothConnectionsFreeRequest {
|
||||
option (id) = 80;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
}
|
||||
|
||||
message BluetoothConnectionsFreeResponse {
|
||||
option (id) = 81;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||
|
||||
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;
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
#include "api_connection.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/version.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <cerrno>
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
||||
@@ -12,6 +12,9 @@
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
@@ -823,6 +826,56 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) {
|
||||
if (!this->bluetooth_le_advertisement_subscription_)
|
||||
return false;
|
||||
if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) {
|
||||
BluetoothLEAdvertisementResponse resp = msg;
|
||||
for (auto &service : resp.service_data) {
|
||||
service.legacy_data.assign(service.data.begin(), service.data.end());
|
||||
service.data.clear();
|
||||
}
|
||||
for (auto &manufacturer_data : resp.manufacturer_data) {
|
||||
manufacturer_data.legacy_data.assign(manufacturer_data.data.begin(), manufacturer_data.data.end());
|
||||
manufacturer_data.data.clear();
|
||||
}
|
||||
return this->send_bluetooth_le_advertisement_response(resp);
|
||||
}
|
||||
return this->send_bluetooth_le_advertisement_response(msg);
|
||||
}
|
||||
void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_device_request(msg);
|
||||
}
|
||||
void APIConnection::bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read(msg);
|
||||
}
|
||||
void APIConnection::bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write(msg);
|
||||
}
|
||||
void APIConnection::bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_read_descriptor(msg);
|
||||
}
|
||||
void APIConnection::bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_write_descriptor(msg);
|
||||
}
|
||||
void APIConnection::bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_send_services(msg);
|
||||
}
|
||||
|
||||
void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) {
|
||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_notify(msg);
|
||||
}
|
||||
|
||||
BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_free(
|
||||
const SubscribeBluetoothConnectionsFreeRequest &msg) {
|
||||
BluetoothConnectionsFreeResponse resp;
|
||||
resp.free = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_free();
|
||||
resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit();
|
||||
return resp;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
|
||||
if (this->log_subscription_ < level)
|
||||
return false;
|
||||
@@ -840,11 +893,14 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
|
||||
HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")";
|
||||
this->helper_->set_log_info(client_info_);
|
||||
ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str());
|
||||
this->client_api_version_major_ = msg.api_version_major;
|
||||
this->client_api_version_minor_ = msg.api_version_minor;
|
||||
ESP_LOGV(TAG, "Hello from client: '%s' | API Version %d.%d", this->client_info_.c_str(),
|
||||
this->client_api_version_major_, this->client_api_version_minor_);
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 6;
|
||||
resp.api_version_minor = 7;
|
||||
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||
resp.name = App.get_name();
|
||||
|
||||
@@ -876,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;
|
||||
@@ -888,7 +949,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
resp.webserver_port = USE_WEBSERVER_PORT;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
resp.has_bluetooth_proxy = true;
|
||||
resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 2 : 1;
|
||||
#endif
|
||||
return resp;
|
||||
}
|
||||
|
@@ -1,15 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "api_frame_helper.h"
|
||||
#include "api_pb2.h"
|
||||
#include "api_pb2_service.h"
|
||||
#include "api_server.h"
|
||||
#include "api_frame_helper.h"
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
|
||||
#endif
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
@@ -99,11 +95,18 @@ class APIConnection : public APIServerConnection {
|
||||
this->send_homeassistant_service_response(call);
|
||||
}
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call) {
|
||||
if (!this->bluetooth_le_advertisement_subscription_)
|
||||
return false;
|
||||
return this->send_bluetooth_le_advertisement_response(call);
|
||||
}
|
||||
bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg);
|
||||
|
||||
void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
|
||||
void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) override;
|
||||
void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) override;
|
||||
void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) override;
|
||||
void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override;
|
||||
void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override;
|
||||
void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
|
||||
BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
|
||||
const SubscribeBluetoothConnectionsFreeRequest &msg) override;
|
||||
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void send_time_request() {
|
||||
@@ -181,6 +184,8 @@ class APIConnection : public APIServerConnection {
|
||||
std::unique_ptr<APIFrameHelper> helper_;
|
||||
|
||||
std::string client_info_;
|
||||
uint32_t client_api_version_major_{0};
|
||||
uint32_t client_api_version_minor_{0};
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
esp32_camera::CameraImageReader image_reader_;
|
||||
#endif
|
||||
@@ -190,7 +195,7 @@ class APIConnection : public APIServerConnection {
|
||||
uint32_t last_traffic_;
|
||||
bool sent_ping_{false};
|
||||
bool service_call_subscription_{false};
|
||||
bool bluetooth_le_advertisement_subscription_{true};
|
||||
bool bluetooth_le_advertisement_subscription_{false};
|
||||
bool next_close_ = false;
|
||||
APIServer *parent_;
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
|
@@ -340,6 +340,35 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerCommand>(enums::Me
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<>
|
||||
const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::BluetoothDeviceRequestType value) {
|
||||
switch (value) {
|
||||
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT:
|
||||
return "BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT";
|
||||
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT:
|
||||
return "BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT";
|
||||
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
|
||||
return "BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR";
|
||||
case enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
|
||||
return "BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->api_version_major = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->api_version_minor = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
@@ -350,7 +379,11 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); }
|
||||
void HelloRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->client_info);
|
||||
buffer.encode_uint32(2, this->api_version_major);
|
||||
buffer.encode_uint32(3, this->api_version_minor);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void HelloRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
@@ -358,6 +391,16 @@ void HelloRequest::dump_to(std::string &out) const {
|
||||
out.append(" client_info: ");
|
||||
out.append("'").append(this->client_info).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" api_version_major: ");
|
||||
sprintf(buffer, "%u", this->api_version_major);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" api_version_minor: ");
|
||||
sprintf(buffer, "%u", this->api_version_minor);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -496,7 +539,7 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->has_bluetooth_proxy = value.as_bool();
|
||||
this->bluetooth_proxy_version = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
@@ -533,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;
|
||||
}
|
||||
@@ -548,7 +595,8 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(8, this->project_name);
|
||||
buffer.encode_string(9, this->project_version);
|
||||
buffer.encode_uint32(10, this->webserver_port);
|
||||
buffer.encode_bool(11, this->has_bluetooth_proxy);
|
||||
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 {
|
||||
@@ -595,8 +643,13 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_bluetooth_proxy: ");
|
||||
out.append(YESNO(this->has_bluetooth_proxy));
|
||||
out.append(" bluetooth_proxy_version: ");
|
||||
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("}");
|
||||
}
|
||||
@@ -4872,7 +4925,7 @@ void SubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const
|
||||
bool BluetoothServiceData::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->data.push_back(value.as_uint32());
|
||||
this->legacy_data.push_back(value.as_uint32());
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
@@ -4885,15 +4938,20 @@ bool BluetoothServiceData::decode_length(uint32_t field_id, ProtoLengthDelimited
|
||||
this->uuid = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->data = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothServiceData::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->uuid);
|
||||
for (auto &it : this->data) {
|
||||
for (auto &it : this->legacy_data) {
|
||||
buffer.encode_uint32(2, it, true);
|
||||
}
|
||||
buffer.encode_string(3, this->data);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothServiceData::dump_to(std::string &out) const {
|
||||
@@ -4903,12 +4961,16 @@ void BluetoothServiceData::dump_to(std::string &out) const {
|
||||
out.append("'").append(this->uuid).append("'");
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->data) {
|
||||
out.append(" data: ");
|
||||
for (const auto &it : this->legacy_data) {
|
||||
out.append(" legacy_data: ");
|
||||
sprintf(buffer, "%u", it);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" data: ");
|
||||
out.append("'").append(this->data).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -5000,6 +5062,811 @@ void BluetoothLEAdvertisementResponse::dump_to(std::string &out) const {
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->request_type = value.as_enum<enums::BluetoothDeviceRequestType>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothDeviceRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_enum<enums::BluetoothDeviceRequestType>(2, this->request_type);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothDeviceRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothDeviceRequest {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" request_type: ");
|
||||
out.append(proto_enum_to_string<enums::BluetoothDeviceRequestType>(this->request_type));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothDeviceConnectionResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->connected = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->mtu = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->error = value.as_int32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothDeviceConnectionResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_bool(2, this->connected);
|
||||
buffer.encode_uint32(3, this->mtu);
|
||||
buffer.encode_int32(4, this->error);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothDeviceConnectionResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothDeviceConnectionResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" connected: ");
|
||||
out.append(YESNO(this->connected));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" mtu: ");
|
||||
sprintf(buffer, "%u", this->mtu);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" error: ");
|
||||
sprintf(buffer, "%d", this->error);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTGetServicesRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTGetServicesRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint64(1, this->address); }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTGetServicesRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTGetServicesRequest {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTDescriptor::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->uuid.push_back(value.as_uint64());
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTDescriptor::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->uuid) {
|
||||
buffer.encode_uint64(1, it, true);
|
||||
}
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTDescriptor::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTDescriptor {\n");
|
||||
for (const auto &it : this->uuid) {
|
||||
out.append(" uuid: ");
|
||||
sprintf(buffer, "%llu", it);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTCharacteristic::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->uuid.push_back(value.as_uint64());
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->properties = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool BluetoothGATTCharacteristic::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 4: {
|
||||
this->descriptors.push_back(value.as_message<BluetoothGATTDescriptor>());
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->uuid) {
|
||||
buffer.encode_uint64(1, it, true);
|
||||
}
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_uint32(3, this->properties);
|
||||
for (auto &it : this->descriptors) {
|
||||
buffer.encode_message<BluetoothGATTDescriptor>(4, it, true);
|
||||
}
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTCharacteristic::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTCharacteristic {\n");
|
||||
for (const auto &it : this->uuid) {
|
||||
out.append(" uuid: ");
|
||||
sprintf(buffer, "%llu", it);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" properties: ");
|
||||
sprintf(buffer, "%u", this->properties);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->descriptors) {
|
||||
out.append(" descriptors: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTService::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->uuid.push_back(value.as_uint64());
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->handle = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool BluetoothGATTService::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 3: {
|
||||
this->characteristics.push_back(value.as_message<BluetoothGATTCharacteristic>());
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->uuid) {
|
||||
buffer.encode_uint64(1, it, true);
|
||||
}
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
for (auto &it : this->characteristics) {
|
||||
buffer.encode_message<BluetoothGATTCharacteristic>(3, it, true);
|
||||
}
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTService::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTService {\n");
|
||||
for (const auto &it : this->uuid) {
|
||||
out.append(" uuid: ");
|
||||
sprintf(buffer, "%llu", it);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" handle: ");
|
||||
sprintf(buffer, "%u", this->handle);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->characteristics) {
|
||||
out.append(" characteristics: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTGetServicesResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool BluetoothGATTGetServicesResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->services.push_back(value.as_message<BluetoothGATTService>());
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
for (auto &it : this->services) {
|
||||
buffer.encode_message<BluetoothGATTService>(2, it, true);
|
||||
}
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTGetServicesResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTGetServicesResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->services) {
|
||||
out.append(" services: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTGetServicesDoneResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->address = value.as_uint64();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTGetServicesDoneResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTGetServicesDoneResponse {\n");
|
||||
out.append(" address: ");
|
||||
sprintf(buffer, "%llu", this->address);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTReadRequest::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 BluetoothGATTReadRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTReadRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTReadRequest {\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 BluetoothGATTReadResponse::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;
|
||||
}
|
||||
}
|
||||
bool BluetoothGATTReadResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 3: {
|
||||
this->data = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTReadResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_string(3, this->data);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTReadResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTReadResponse {\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(" data: ");
|
||||
out.append("'").append(this->data).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTWriteRequest::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->response = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 4: {
|
||||
this->data = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTWriteRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_bool(3, this->response);
|
||||
buffer.encode_string(4, this->data);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTWriteRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTWriteRequest {\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(" response: ");
|
||||
out.append(YESNO(this->response));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" data: ");
|
||||
out.append("'").append(this->data).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTReadDescriptorRequest::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 BluetoothGATTReadDescriptorRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTReadDescriptorRequest {\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 BluetoothGATTWriteDescriptorRequest::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;
|
||||
}
|
||||
}
|
||||
bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 3: {
|
||||
this->data = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTWriteDescriptorRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_string(3, this->data);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTWriteDescriptorRequest {\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(" data: ");
|
||||
out.append("'").append(this->data).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTNotifyRequest::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->enable = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTNotifyRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_bool(3, this->enable);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTNotifyRequest {\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(" enable: ");
|
||||
out.append(YESNO(this->enable));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothGATTNotifyDataResponse::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;
|
||||
}
|
||||
}
|
||||
bool BluetoothGATTNotifyDataResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 3: {
|
||||
this->data = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_string(3, this->data);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothGATTNotifyDataResponse {\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(" data: ");
|
||||
out.append("'").append(this->data).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
void SubscribeBluetoothConnectionsFreeRequest::encode(ProtoWriteBuffer buffer) const {}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const {
|
||||
out.append("SubscribeBluetoothConnectionsFreeRequest {}");
|
||||
}
|
||||
#endif
|
||||
bool BluetoothConnectionsFreeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->free = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 2: {
|
||||
this->limit = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(1, this->free);
|
||||
buffer.encode_uint32(2, this->limit);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("BluetoothConnectionsFreeResponse {\n");
|
||||
out.append(" free: ");
|
||||
sprintf(buffer, "%u", this->free);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" limit: ");
|
||||
sprintf(buffer, "%u", this->limit);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
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
|
||||
|
@@ -155,12 +155,20 @@ enum MediaPlayerCommand : uint32_t {
|
||||
MEDIA_PLAYER_COMMAND_MUTE = 3,
|
||||
MEDIA_PLAYER_COMMAND_UNMUTE = 4,
|
||||
};
|
||||
enum BluetoothDeviceRequestType : uint32_t {
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0,
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1,
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR = 2,
|
||||
BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR = 3,
|
||||
};
|
||||
|
||||
} // namespace enums
|
||||
|
||||
class HelloRequest : public ProtoMessage {
|
||||
public:
|
||||
std::string client_info{};
|
||||
uint32_t api_version_major{0};
|
||||
uint32_t api_version_minor{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -168,6 +176,7 @@ class HelloRequest : public ProtoMessage {
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class HelloResponse : public ProtoMessage {
|
||||
public:
|
||||
@@ -263,7 +272,8 @@ class DeviceInfoResponse : public ProtoMessage {
|
||||
std::string project_name{};
|
||||
std::string project_version{};
|
||||
uint32_t webserver_port{0};
|
||||
bool has_bluetooth_proxy{false};
|
||||
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;
|
||||
@@ -1227,7 +1237,8 @@ class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage {
|
||||
class BluetoothServiceData : public ProtoMessage {
|
||||
public:
|
||||
std::string uuid{};
|
||||
std::vector<uint32_t> data{};
|
||||
std::vector<uint32_t> legacy_data{};
|
||||
std::string data{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -1254,6 +1265,260 @@ class BluetoothLEAdvertisementResponse : public ProtoMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class BluetoothDeviceRequest : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
enums::BluetoothDeviceRequestType request_type{};
|
||||
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 BluetoothDeviceConnectionResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
bool connected{false};
|
||||
uint32_t mtu{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 BluetoothGATTGetServicesRequest : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{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 BluetoothGATTDescriptor : public ProtoMessage {
|
||||
public:
|
||||
std::vector<uint64_t> uuid{};
|
||||
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 BluetoothGATTCharacteristic : public ProtoMessage {
|
||||
public:
|
||||
std::vector<uint64_t> uuid{};
|
||||
uint32_t handle{0};
|
||||
uint32_t properties{0};
|
||||
std::vector<BluetoothGATTDescriptor> descriptors{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class BluetoothGATTService : public ProtoMessage {
|
||||
public:
|
||||
std::vector<uint64_t> uuid{};
|
||||
uint32_t handle{0};
|
||||
std::vector<BluetoothGATTCharacteristic> characteristics{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class BluetoothGATTGetServicesResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
std::vector<BluetoothGATTService> services{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class BluetoothGATTGetServicesDoneResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{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 BluetoothGATTReadRequest : 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 BluetoothGATTReadResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
std::string data{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class BluetoothGATTWriteRequest : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
bool response{false};
|
||||
std::string data{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class BluetoothGATTReadDescriptorRequest : 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 BluetoothGATTWriteDescriptorRequest : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
std::string data{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class BluetoothGATTNotifyRequest : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
bool enable{false};
|
||||
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 BluetoothGATTNotifyDataResponse : public ProtoMessage {
|
||||
public:
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
std::string data{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class SubscribeBluetoothConnectionsFreeRequest : public ProtoMessage {
|
||||
public:
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
class BluetoothConnectionsFreeResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t free{0};
|
||||
uint32_t limit{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 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
|
||||
|
@@ -336,6 +336,95 @@ bool APIServerConnectionBase::send_bluetooth_le_advertisement_response(const Blu
|
||||
return this->send_message_<BluetoothLEAdvertisementResponse>(msg, 67);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_device_connection_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothDeviceConnectionResponse>(msg, 69);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_gatt_get_services_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothGATTGetServicesResponse>(msg, 71);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_gatt_get_services_done_response(
|
||||
const BluetoothGATTGetServicesDoneResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_gatt_get_services_done_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothGATTGetServicesDoneResponse>(msg, 72);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_gatt_read_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothGATTReadResponse>(msg, 74);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_gatt_notify_data_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<BluetoothGATTNotifyDataResponse>(msg, 79);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool APIServerConnectionBase::send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_bluetooth_connections_free_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
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: {
|
||||
@@ -612,6 +701,94 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
this->on_subscribe_bluetooth_le_advertisements_request(msg);
|
||||
break;
|
||||
}
|
||||
case 68: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothDeviceRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_device_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_device_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 70: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothGATTGetServicesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_get_services_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_gatt_get_services_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 73: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothGATTReadRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_read_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_gatt_read_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 75: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothGATTWriteRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_write_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_gatt_write_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 76: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothGATTReadDescriptorRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_read_descriptor_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_gatt_read_descriptor_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 77: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothGATTWriteDescriptorRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_write_descriptor_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_gatt_write_descriptor_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 78: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
BluetoothGATTNotifyRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_notify_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_bluetooth_gatt_notify_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 80: {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
SubscribeBluetoothConnectionsFreeRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_subscribe_bluetooth_connections_free_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -708,18 +885,6 @@ void APIServerConnection::on_subscribe_home_assistant_states_request(const Subsc
|
||||
}
|
||||
this->subscribe_home_assistant_states(msg);
|
||||
}
|
||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
|
||||
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_bluetooth_le_advertisements(msg);
|
||||
}
|
||||
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
@@ -884,6 +1049,126 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma
|
||||
this->media_player_command(msg);
|
||||
}
|
||||
#endif
|
||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
|
||||
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->subscribe_bluetooth_le_advertisements(msg);
|
||||
}
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_device_request(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_gatt_get_services(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_gatt_read(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_gatt_write(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_gatt_read_descriptor(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_gatt_write_descriptor(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->bluetooth_gatt_notify(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
|
||||
const SubscribeBluetoothConnectionsFreeRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
BluetoothConnectionsFreeResponse ret = this->subscribe_bluetooth_connections_free(msg);
|
||||
if (!this->send_bluetooth_connections_free_response(ret)) {
|
||||
this->on_fatal_error();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
@@ -158,6 +158,57 @@ class APIServerConnectionBase : public ProtoService {
|
||||
const SubscribeBluetoothLEAdvertisementsRequest &value){};
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_le_advertisement_response(const BluetoothLEAdvertisementResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_device_connection_response(const BluetoothDeviceConnectionResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_get_services_response(const BluetoothGATTGetServicesResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_get_services_done_response(const BluetoothGATTGetServicesDoneResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
bool send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &value){};
|
||||
#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;
|
||||
@@ -175,7 +226,6 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0;
|
||||
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
|
||||
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
|
||||
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
|
||||
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
|
||||
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
|
||||
#ifdef USE_COVER
|
||||
@@ -210,6 +260,32 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
|
||||
#endif
|
||||
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_device_request(const BluetoothDeviceRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_gatt_write(const BluetoothGATTWriteRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_gatt_read_descriptor(const BluetoothGATTReadDescriptorRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
|
||||
const SubscribeBluetoothConnectionsFreeRequest &msg) = 0;
|
||||
#endif
|
||||
protected:
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
@@ -222,7 +298,6 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override;
|
||||
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
|
||||
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
void on_get_time_request(const GetTimeRequest &msg) override;
|
||||
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
||||
#ifdef USE_COVER
|
||||
@@ -257,6 +332,31 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
|
||||
#endif
|
||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_subscribe_bluetooth_connections_free_request(const SubscribeBluetoothConnectionsFreeRequest &msg) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
@@ -297,6 +297,72 @@ void APIServer::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementRe
|
||||
client->send_bluetooth_le_advertisement(call);
|
||||
}
|
||||
}
|
||||
void APIServer::send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) {
|
||||
BluetoothDeviceConnectionResponse call;
|
||||
call.address = address;
|
||||
call.connected = connected;
|
||||
call.mtu = mtu;
|
||||
call.error = error;
|
||||
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_device_connection_response(call);
|
||||
}
|
||||
}
|
||||
|
||||
void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) {
|
||||
BluetoothConnectionsFreeResponse call;
|
||||
call.free = free;
|
||||
call.limit = limit;
|
||||
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_bluetooth_connections_free_response(call);
|
||||
}
|
||||
}
|
||||
|
||||
void APIServer::send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call) {
|
||||
for (auto &client : this->clients_) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
void APIServer::send_bluetooth_gatt_services_done(uint64_t address) {
|
||||
BluetoothGATTGetServicesDoneResponse call;
|
||||
call.address = address;
|
||||
|
||||
for (auto &client : this->clients_) {
|
||||
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,6 +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 = 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
|
||||
|
@@ -8,6 +8,7 @@ CODEOWNERS = ["@OttoWinter"]
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema({}),
|
||||
cv.only_with_arduino,
|
||||
cv.only_on(["esp32", "esp8266"]),
|
||||
)
|
||||
|
||||
|
||||
|
@@ -128,9 +128,9 @@ uint8_t BedJetHub::write_bedjet_packet_(BedjetPacket *pkt) {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_cmd_,
|
||||
pkt->data_length + 1, (uint8_t *) &pkt->command, ESP_GATT_WRITE_TYPE_NO_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status = esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
|
||||
this->char_handle_cmd_, pkt->data_length + 1, (uint8_t *) &pkt->command,
|
||||
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -138,13 +138,13 @@ uint8_t BedJetHub::write_bedjet_packet_(BedjetPacket *pkt) {
|
||||
uint8_t BedJetHub::set_notify_(const bool enable) {
|
||||
uint8_t status;
|
||||
if (enable) {
|
||||
status = esp_ble_gattc_register_for_notify(this->parent_->gattc_if, this->parent_->remote_bda,
|
||||
status = esp_ble_gattc_register_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
|
||||
this->char_handle_status_);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_register_for_notify failed, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
} else {
|
||||
status = esp_ble_gattc_unregister_for_notify(this->parent_->gattc_if, this->parent_->remote_bda,
|
||||
status = esp_ble_gattc_unregister_for_notify(this->parent_->get_gattc_if(), this->parent_->get_remote_bda(),
|
||||
this->char_handle_status_);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_unregister_for_notify failed, status=%d", this->get_name().c_str(), status);
|
||||
@@ -204,8 +204,8 @@ bool BedJetHub::discover_characteristics_() {
|
||||
result = false;
|
||||
} else {
|
||||
this->char_handle_name_ = chr->handle;
|
||||
auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_name_,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
|
||||
this->char_handle_name_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGI(TAG, "[%s] Unable to read name characteristic: %d", this->get_name().c_str(), status);
|
||||
}
|
||||
@@ -230,22 +230,6 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
this->dispatch_state_(false);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
// FIXME: bug in BLEClient
|
||||
this->parent_->conn_id = param->open.conn_id;
|
||||
this->open_conn_id_ = param->open.conn_id;
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_CONNECT_EVT: {
|
||||
if (this->parent_->conn_id != param->connect.conn_id && this->open_conn_id_ != 0xff) {
|
||||
// FIXME: bug in BLEClient
|
||||
ESP_LOGW(TAG, "[%s] CONNECT_EVT unexpected conn_id; open=%d, parent=%d, param=%d", this->get_name().c_str(),
|
||||
this->open_conn_id_, this->parent_->conn_id, param->connect.conn_id);
|
||||
this->parent_->conn_id = this->open_conn_id_;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
auto result = this->discover_characteristics_();
|
||||
|
||||
@@ -301,7 +285,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent_->conn_id)
|
||||
if (param->read.conn_id != this->parent_->get_conn_id())
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
@@ -358,9 +342,9 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
if (this->processing_)
|
||||
break;
|
||||
|
||||
if (param->notify.conn_id != this->parent_->conn_id) {
|
||||
if (param->notify.conn_id != this->parent_->get_conn_id()) {
|
||||
ESP_LOGW(TAG, "[%s] Received notify event for unexpected parent conn: expect %x, got %x",
|
||||
this->get_name().c_str(), this->parent_->conn_id, param->notify.conn_id);
|
||||
this->get_name().c_str(), this->parent_->get_conn_id(), param->notify.conn_id);
|
||||
// FIXME: bug in BLEClient holding wrong conn_id.
|
||||
}
|
||||
|
||||
@@ -394,7 +378,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
if (needs_extra) {
|
||||
// This means the packet was partial, so read the status characteristic to get the second part.
|
||||
// Ideally this will complete quickly. We won't process additional notification events until it does.
|
||||
auto status = esp_ble_gattc_read_char(this->parent_->gattc_if, this->parent_->conn_id,
|
||||
auto status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
|
||||
this->char_handle_status_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGI(TAG, "[%s] Unable to read extended status packet", this->get_name().c_str());
|
||||
@@ -438,15 +422,15 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) {
|
||||
|
||||
// NOTE: BLEClient uses `uint8_t*` of length 1, but BLE spec requires 16 bits.
|
||||
uint16_t notify_en = enable ? 1 : 0;
|
||||
auto status =
|
||||
esp_ble_gattc_write_char_descr(this->parent_->gattc_if, this->parent_->conn_id, handle, sizeof(notify_en),
|
||||
(uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status = esp_ble_gattc_write_char_descr(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), 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);
|
||||
return status;
|
||||
}
|
||||
ESP_LOGD(TAG, "[%s] wrote notify=%s to status config 0x%04x, for conn %d", this->get_name().c_str(),
|
||||
enable ? "true" : "false", handle, this->parent_->conn_id);
|
||||
enable ? "true" : "false", handle, this->parent_->get_conn_id());
|
||||
return ESP_GATT_OK;
|
||||
}
|
||||
|
||||
@@ -500,7 +484,7 @@ void BedJetHub::update() { this->dispatch_status_(); }
|
||||
void BedJetHub::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BedJet Hub '%s'", this->get_name().c_str());
|
||||
ESP_LOGCONFIG(TAG, " ble_client.app_id: %d", this->parent()->app_id);
|
||||
ESP_LOGCONFIG(TAG, " ble_client.conn_id: %d", this->parent()->conn_id);
|
||||
ESP_LOGCONFIG(TAG, " ble_client.conn_id: %d", this->parent()->get_conn_id());
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
ESP_LOGCONFIG(TAG, " Child components (%d):", this->children_.size());
|
||||
for (auto *child : this->children_) {
|
||||
|
@@ -167,8 +167,6 @@ class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingCompo
|
||||
uint16_t char_handle_status_;
|
||||
uint16_t config_descr_status_;
|
||||
|
||||
uint8_t open_conn_id_ = -1;
|
||||
|
||||
uint8_t write_notify_config_descriptor_(bool enable);
|
||||
};
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import esp32_ble_tracker
|
||||
from esphome.components import esp32_ble_tracker, esp32_ble_client
|
||||
from esphome.const import (
|
||||
CONF_CHARACTERISTIC_UUID,
|
||||
CONF_ID,
|
||||
@@ -14,13 +14,12 @@ from esphome.const import (
|
||||
)
|
||||
from esphome import automation
|
||||
|
||||
AUTO_LOAD = ["esp32_ble_client"]
|
||||
CODEOWNERS = ["@buxtronix"]
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
|
||||
ble_client_ns = cg.esphome_ns.namespace("ble_client")
|
||||
BLEClient = ble_client_ns.class_(
|
||||
"BLEClient", cg.Component, esp32_ble_tracker.ESPBTClient
|
||||
)
|
||||
BLEClient = ble_client_ns.class_("BLEClient", esp32_ble_client.BLEClientBase)
|
||||
BLEClientNode = ble_client_ns.class_("BLEClientNode")
|
||||
BLEClientNodeConstRef = BLEClientNode.operator("ref").operator("const")
|
||||
# Triggers
|
||||
@@ -101,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
|
||||
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#include "automation.h"
|
||||
|
||||
#include <esp_bt_defs.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <esp_bt_defs.h>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -31,8 +31,8 @@ void BLEWriterClientNode::write(const std::vector<uint8_t> &value) {
|
||||
}
|
||||
ESP_LOGVV(TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str());
|
||||
esp_err_t err =
|
||||
esp_ble_gattc_write_char(this->parent()->gattc_if, this->parent()->conn_id, this->ble_char_handle_, value.size(),
|
||||
const_cast<uint8_t *>(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE);
|
||||
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->ble_char_handle_,
|
||||
value.size(), const_cast<uint8_t *>(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
|
||||
}
|
||||
|
@@ -28,7 +28,8 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode {
|
||||
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 {
|
||||
if (event == ESP_GATTC_DISCONNECT_EVT && memcmp(param->disconnect.remote_bda, this->parent_->remote_bda, 6) == 0)
|
||||
if (event == ESP_GATTC_DISCONNECT_EVT &&
|
||||
memcmp(param->disconnect.remote_bda, this->parent_->get_remote_bda(), 6) == 0)
|
||||
this->trigger();
|
||||
if (event == ESP_GATTC_SEARCH_CMPL_EVT)
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
@@ -45,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;
|
||||
|
||||
|
@@ -1,8 +1,9 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "ble_client.h"
|
||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "ble_client.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -11,22 +12,13 @@ namespace ble_client {
|
||||
|
||||
static const char *const TAG = "ble_client";
|
||||
|
||||
float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
|
||||
|
||||
void BLEClient::setup() {
|
||||
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);
|
||||
this->mark_failed();
|
||||
}
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
BLEClientBase::setup();
|
||||
this->enabled = true;
|
||||
}
|
||||
|
||||
void BLEClient::loop() {
|
||||
if (this->state() == espbt::ClientState::DISCOVERED) {
|
||||
this->connect();
|
||||
}
|
||||
BLEClientBase::loop();
|
||||
for (auto *node : this->nodes_)
|
||||
node->loop();
|
||||
}
|
||||
@@ -39,34 +31,7 @@ void BLEClient::dump_config() {
|
||||
bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {
|
||||
if (!this->enabled)
|
||||
return false;
|
||||
if (device.address_uint64() != this->address)
|
||||
return false;
|
||||
if (this->state() != espbt::ClientState::IDLE)
|
||||
return false;
|
||||
|
||||
ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str());
|
||||
this->set_states_(espbt::ClientState::DISCOVERED);
|
||||
|
||||
auto addr = device.address_uint64();
|
||||
this->remote_bda[0] = (addr >> 40) & 0xFF;
|
||||
this->remote_bda[1] = (addr >> 32) & 0xFF;
|
||||
this->remote_bda[2] = (addr >> 24) & 0xFF;
|
||||
this->remote_bda[3] = (addr >> 16) & 0xFF;
|
||||
this->remote_bda[4] = (addr >> 8) & 0xFF;
|
||||
this->remote_bda[5] = (addr >> 0) & 0xFF;
|
||||
this->remote_addr_type = device.get_address_type();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string BLEClient::address_str() const {
|
||||
char buf[20];
|
||||
sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", (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 ret;
|
||||
ret = buf;
|
||||
return ret;
|
||||
return BLEClientBase::parse_device(device);
|
||||
}
|
||||
|
||||
void BLEClient::set_enabled(bool enabled) {
|
||||
@@ -74,7 +39,7 @@ void BLEClient::set_enabled(bool enabled) {
|
||||
return;
|
||||
if (!enabled && this->state() != espbt::ClientState::IDLE) {
|
||||
ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str());
|
||||
auto ret = esp_ble_gattc_close(this->gattc_if, this->conn_id);
|
||||
auto ret = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s status=%d", this->address_str().c_str(), ret);
|
||||
}
|
||||
@@ -82,125 +47,13 @@ void BLEClient::set_enabled(bool enabled) {
|
||||
this->enabled = enabled;
|
||||
}
|
||||
|
||||
void BLEClient::connect() {
|
||||
ESP_LOGI(TAG, "Attempting BLE connection to %s", 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);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
} else {
|
||||
this->set_states_(espbt::ClientState::CONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
|
||||
return;
|
||||
if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if)
|
||||
return;
|
||||
|
||||
bool all_established = this->all_nodes_established_();
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
|
||||
this->conn_id = param->open.conn_id;
|
||||
if (param->open.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status);
|
||||
this->set_states_(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);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", 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);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, 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);
|
||||
for (auto &svc : this->services_)
|
||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->services_.clear();
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_RES_EVT: {
|
||||
BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
|
||||
ble_service->start_handle = param->search_res.start_handle;
|
||||
ble_service->end_handle = param->search_res.end_handle;
|
||||
ble_service->client = this;
|
||||
this->services_.push_back(ble_service);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", 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);
|
||||
svc->parse_characteristics();
|
||||
}
|
||||
this->set_states_(espbt::ClientState::CONNECTED);
|
||||
this->set_state(espbt::ClientState::ESTABLISHED);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
auto *descr = this->get_config_descriptor(param->reg_for_notify.handle);
|
||||
if (descr == 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());
|
||||
break;
|
||||
}
|
||||
uint16_t notify_en = 1;
|
||||
auto status =
|
||||
esp_ble_gattc_write_char_descr(this->gattc_if, this->conn_id, descr->handle, sizeof(notify_en),
|
||||
(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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!BLEClientBase::gattc_event_handler(event, esp_gattc_if, param))
|
||||
return false;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
for (auto *node : this->nodes_)
|
||||
node->gattc_event_handler(event, esp_gattc_if, param);
|
||||
|
||||
@@ -210,239 +63,30 @@ 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) {
|
||||
switch (event) {
|
||||
// This event is sent by the server when it requests security
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||
ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event);
|
||||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||||
break;
|
||||
// This event is sent once authentication has completed
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||||
esp_bd_addr_t bd_addr;
|
||||
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||
ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str());
|
||||
if (!param->ble_security.auth_cmpl.success) {
|
||||
ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type,
|
||||
param->ble_security.auth_cmpl.auth_mode);
|
||||
}
|
||||
break;
|
||||
// There are other events we'll want to implement at some point to support things like pass key
|
||||
// https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md
|
||||
default:
|
||||
break;
|
||||
BLEClientBase::gap_event_handler(event, param);
|
||||
|
||||
for (auto *node : this->nodes_)
|
||||
node->gap_event_handler(event, param);
|
||||
}
|
||||
|
||||
void BLEClient::set_state(espbt::ClientState state) {
|
||||
BLEClientBase::set_state(state);
|
||||
for (auto &node : nodes_)
|
||||
node->node_state = state;
|
||||
}
|
||||
|
||||
bool BLEClient::all_nodes_established_() {
|
||||
if (this->state() != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
for (auto &node : nodes_) {
|
||||
if (node->node_state != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse GATT values into a float for a sensor.
|
||||
// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
|
||||
float BLEClient::parse_char_value(uint8_t *value, uint16_t length) {
|
||||
// A length of one means a single octet value.
|
||||
if (length == 0)
|
||||
return 0;
|
||||
if (length == 1)
|
||||
return (float) ((uint8_t) value[0]);
|
||||
|
||||
switch (value[0]) {
|
||||
case 0x1: // boolean.
|
||||
case 0x2: // 2bit.
|
||||
case 0x3: // nibble.
|
||||
case 0x4: // uint8.
|
||||
return (float) ((uint8_t) value[1]);
|
||||
case 0x5: // uint12.
|
||||
case 0x6: // uint16.
|
||||
if (length > 2) {
|
||||
return (float) ((uint16_t)(value[1] << 8) + (uint16_t) value[2]);
|
||||
}
|
||||
case 0x7: // uint24.
|
||||
if (length > 3) {
|
||||
return (float) ((uint32_t)(value[1] << 16) + (uint32_t)(value[2] << 8) + (uint32_t)(value[3]));
|
||||
}
|
||||
case 0x8: // uint32.
|
||||
if (length > 4) {
|
||||
return (float) ((uint32_t)(value[1] << 24) + (uint32_t)(value[2] << 16) + (uint32_t)(value[3] << 8) +
|
||||
(uint32_t)(value[4]));
|
||||
}
|
||||
case 0xC: // int8.
|
||||
return (float) ((int8_t) value[1]);
|
||||
case 0xD: // int12.
|
||||
case 0xE: // int16.
|
||||
if (length > 2) {
|
||||
return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]);
|
||||
}
|
||||
case 0xF: // int24.
|
||||
if (length > 3) {
|
||||
return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3]));
|
||||
}
|
||||
case 0x10: // int32.
|
||||
if (length > 4) {
|
||||
return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) +
|
||||
(int32_t)(value[4]));
|
||||
}
|
||||
}
|
||||
ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length);
|
||||
return NAN;
|
||||
}
|
||||
|
||||
BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) {
|
||||
for (auto *svc : this->services_) {
|
||||
if (svc->uuid == uuid)
|
||||
return svc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLEService *BLEClient::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); }
|
||||
|
||||
BLECharacteristic *BLEClient::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) {
|
||||
auto *svc = this->get_service(service);
|
||||
if (svc == nullptr)
|
||||
return nullptr;
|
||||
return svc->get_characteristic(chr);
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEClient::get_characteristic(uint16_t service, uint16_t chr) {
|
||||
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr));
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClient::get_config_descriptor(uint16_t handle) {
|
||||
for (auto &svc : this->services_) {
|
||||
for (auto &chr : svc->characteristics) {
|
||||
if (chr->handle == handle) {
|
||||
for (auto &desc : chr->descriptors) {
|
||||
if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902))
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
|
||||
for (auto &chr : this->characteristics) {
|
||||
if (chr->uuid == uuid)
|
||||
return chr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) {
|
||||
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid));
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClient::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) {
|
||||
auto *svc = this->get_service(service);
|
||||
if (svc == nullptr)
|
||||
return nullptr;
|
||||
auto *ch = svc->get_characteristic(chr);
|
||||
if (ch == nullptr)
|
||||
return nullptr;
|
||||
return ch->get_descriptor(descr);
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClient::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr),
|
||||
espbt::ESPBTUUID::from_uint16(descr));
|
||||
}
|
||||
|
||||
BLEService::~BLEService() {
|
||||
for (auto &chr : this->characteristics)
|
||||
delete chr; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
void BLEService::parse_characteristics() {
|
||||
uint16_t offset = 0;
|
||||
esp_gattc_char_elem_t result;
|
||||
|
||||
while (true) {
|
||||
uint16_t count = 1;
|
||||
esp_gatt_status_t status = esp_ble_gattc_get_all_char(
|
||||
this->client->gattc_if, this->client->conn_id, this->start_handle, this->end_handle, &result, &count, offset);
|
||||
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status);
|
||||
break;
|
||||
}
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
|
||||
characteristic->properties = result.properties;
|
||||
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);
|
||||
characteristic->parse_descriptors();
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
BLECharacteristic::~BLECharacteristic() {
|
||||
for (auto &desc : this->descriptors)
|
||||
delete desc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
void BLECharacteristic::parse_descriptors() {
|
||||
uint16_t offset = 0;
|
||||
esp_gattc_descr_elem_t result;
|
||||
|
||||
while (true) {
|
||||
uint16_t count = 1;
|
||||
esp_gatt_status_t status = esp_ble_gattc_get_all_descr(
|
||||
this->service->client->gattc_if, this->service->client->conn_id, this->handle, &result, &count, offset);
|
||||
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status);
|
||||
break;
|
||||
}
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
|
||||
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);
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
|
||||
for (auto &desc : this->descriptors) {
|
||||
if (desc->uuid == uuid)
|
||||
return desc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
|
||||
}
|
||||
|
||||
void 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->gattc_if, client->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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
|
@@ -1,8 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -18,15 +19,16 @@ namespace ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
using namespace esp32_ble_client;
|
||||
|
||||
class BLEClient;
|
||||
class BLEService;
|
||||
class BLECharacteristic;
|
||||
|
||||
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
|
||||
@@ -42,57 +44,17 @@ class BLEClientNode {
|
||||
uint64_t address_;
|
||||
};
|
||||
|
||||
class BLEDescriptor {
|
||||
public:
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t handle;
|
||||
|
||||
BLECharacteristic *characteristic;
|
||||
};
|
||||
|
||||
class BLECharacteristic {
|
||||
public:
|
||||
~BLECharacteristic();
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t handle;
|
||||
esp_gatt_char_prop_t properties;
|
||||
std::vector<BLEDescriptor *> descriptors;
|
||||
void parse_descriptors();
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
|
||||
BLEDescriptor *get_descriptor(uint16_t uuid);
|
||||
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);
|
||||
BLEService *service;
|
||||
};
|
||||
|
||||
class BLEService {
|
||||
public:
|
||||
~BLEService();
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t start_handle;
|
||||
uint16_t end_handle;
|
||||
std::vector<BLECharacteristic *> characteristics;
|
||||
BLEClient *client;
|
||||
void parse_characteristics();
|
||||
BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid);
|
||||
BLECharacteristic *get_characteristic(uint16_t uuid);
|
||||
};
|
||||
|
||||
class BLEClient : public espbt::ESPBTClient, public Component {
|
||||
class BLEClient : public BLEClientBase {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const 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 on_scan_end() override {}
|
||||
void connect() override;
|
||||
|
||||
void set_address(uint64_t address) { this->address = address; }
|
||||
|
||||
void set_enabled(bool enabled);
|
||||
|
||||
@@ -102,43 +64,14 @@ class BLEClient : public espbt::ESPBTClient, public Component {
|
||||
this->nodes_.push_back(node);
|
||||
}
|
||||
|
||||
BLEService *get_service(espbt::ESPBTUUID uuid);
|
||||
BLEService *get_service(uint16_t uuid);
|
||||
BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr);
|
||||
BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr);
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr);
|
||||
BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr);
|
||||
// Get the configuration descriptor for the given characteristic handle.
|
||||
BLEDescriptor *get_config_descriptor(uint16_t handle);
|
||||
|
||||
float parse_char_value(uint8_t *value, uint16_t length);
|
||||
|
||||
int gattc_if;
|
||||
esp_bd_addr_t remote_bda;
|
||||
esp_ble_addr_type_t remote_addr_type;
|
||||
uint16_t conn_id;
|
||||
uint64_t address;
|
||||
bool enabled;
|
||||
std::string address_str() const;
|
||||
|
||||
void set_state(espbt::ClientState state) override;
|
||||
|
||||
protected:
|
||||
void set_states_(espbt::ClientState st) {
|
||||
this->set_state(st);
|
||||
for (auto &node : nodes_)
|
||||
node->node_state = st;
|
||||
}
|
||||
bool all_nodes_established_() {
|
||||
if (this->state() != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
for (auto &node : nodes_) {
|
||||
if (node->node_state != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool all_nodes_established_();
|
||||
|
||||
std::vector<BLEClientNode *> nodes_;
|
||||
std::vector<BLEService *> services_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
|
@@ -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)
|
||||
|
@@ -19,7 +19,8 @@ class BLESensorNotifyTrigger : public Trigger<float>, public BLESensor {
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle)
|
||||
if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() ||
|
||||
param->notify.handle != this->sensor_->handle)
|
||||
break;
|
||||
this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len));
|
||||
}
|
||||
|
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
|
@@ -63,8 +63,8 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
this->handle = descr->handle;
|
||||
}
|
||||
if (this->notify_) {
|
||||
auto status =
|
||||
esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle);
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(),
|
||||
this->parent()->get_remote_bda(), chr->handle);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
|
||||
}
|
||||
@@ -74,7 +74,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->conn_id)
|
||||
if (param->read.conn_id != this->parent()->get_conn_id())
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
@@ -87,7 +87,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle)
|
||||
if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle)
|
||||
break;
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
|
||||
param->notify.handle, param->notify.value[0]);
|
||||
@@ -122,8 +122,8 @@ void BLESensor::update() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto status =
|
||||
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
|
@@ -19,7 +19,8 @@ class BLETextSensorNotifyTrigger : public Trigger<std::string>, public BLETextSe
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle)
|
||||
if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() ||
|
||||
param->notify.handle != this->sensor_->handle)
|
||||
break;
|
||||
this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len));
|
||||
}
|
||||
|
@@ -66,8 +66,8 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
this->handle = descr->handle;
|
||||
}
|
||||
if (this->notify_) {
|
||||
auto status =
|
||||
esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle);
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(),
|
||||
this->parent()->get_remote_bda(), chr->handle);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
|
||||
}
|
||||
@@ -77,7 +77,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->conn_id)
|
||||
if (param->read.conn_id != this->parent()->get_conn_id())
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
@@ -90,7 +90,7 @@ void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle)
|
||||
if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle)
|
||||
break;
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
|
||||
param->notify.handle, param->notify.value[0]);
|
||||
@@ -121,8 +121,8 @@ void BLETextSensor::update() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto status =
|
||||
esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE);
|
||||
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(EMPTY);
|
||||
|
@@ -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,
|
||||
|
@@ -1,27 +1,75 @@
|
||||
from esphome.components import esp32_ble_tracker
|
||||
from esphome.components import esp32_ble_tracker, esp32_ble_client
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.const import CONF_ACTIVE, CONF_ID
|
||||
|
||||
DEPENDENCIES = ["esp32", "esp32_ble_tracker"]
|
||||
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
|
||||
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", cg.Component)
|
||||
BluetoothProxy = bluetooth_proxy_ns.class_(
|
||||
"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.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):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_active(config[CONF_ACTIVE]))
|
||||
await esp32_ble_tracker.register_ble_device(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
|
@@ -1,29 +1,29 @@
|
||||
#include "bluetooth_proxy.h"
|
||||
|
||||
#ifdef USE_API
|
||||
#include "esphome/components/api/api_pb2.h"
|
||||
#include "esphome/components/api/api_server.h"
|
||||
#endif // USE_API
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/api/api_server.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bluetooth_proxy {
|
||||
|
||||
static const char *const TAG = "bluetooth_proxy";
|
||||
|
||||
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);
|
||||
|
||||
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())
|
||||
@@ -35,22 +35,209 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
|
||||
for (auto &data : device.get_service_datas()) {
|
||||
api::BluetoothServiceData service_data;
|
||||
service_data.uuid = data.uuid.to_string();
|
||||
for (auto d : data.data)
|
||||
service_data.data.push_back(d);
|
||||
resp.service_data.push_back(service_data);
|
||||
service_data.data.assign(data.data.begin(), data.data.end());
|
||||
resp.service_data.push_back(std::move(service_data));
|
||||
}
|
||||
for (auto &data : device.get_manufacturer_datas()) {
|
||||
api::BluetoothServiceData manufacturer_data;
|
||||
manufacturer_data.uuid = data.uuid.to_string();
|
||||
for (auto d : data.data)
|
||||
manufacturer_data.data.push_back(d);
|
||||
resp.manufacturer_data.push_back(manufacturer_data);
|
||||
manufacturer_data.data.assign(data.data.begin(), data.data.end());
|
||||
resp.manufacturer_data.push_back(std::move(manufacturer_data));
|
||||
}
|
||||
api::global_api_server->send_bluetooth_le_advertisement(resp);
|
||||
#endif
|
||||
}
|
||||
|
||||
void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); }
|
||||
void BluetoothProxy::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
|
||||
ESP_LOGCONFIG(TAG, " Active: %s", YESNO(this->active_));
|
||||
}
|
||||
|
||||
void BluetoothProxy::loop() {
|
||||
if (!api::global_api_server->is_connected()) {
|
||||
for (auto *connection : this->connections_) {
|
||||
if (connection->get_address() != 0) {
|
||||
connection->disconnect();
|
||||
}
|
||||
}
|
||||
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_++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: {
|
||||
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;
|
||||
}
|
||||
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: {
|
||||
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;
|
||||
}
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR:
|
||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
|
||||
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 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) {
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) {
|
||||
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 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) {
|
||||
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 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) {
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace bluetooth_proxy
|
||||
} // namespace esphome
|
||||
|
@@ -4,22 +4,69 @@
|
||||
|
||||
#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 "bluetooth_connection.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bluetooth_proxy {
|
||||
|
||||
class BluetoothProxy : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
|
||||
|
||||
using namespace esp32_ble_client;
|
||||
|
||||
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 register_connection(BluetoothConnection *connection) {
|
||||
this->connections_.push_back(connection);
|
||||
connection->proxy_ = this;
|
||||
}
|
||||
|
||||
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
|
||||
void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg);
|
||||
void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg);
|
||||
void bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg);
|
||||
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);
|
||||
|
||||
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_; }
|
||||
|
||||
protected:
|
||||
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
|
||||
|
||||
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)
|
||||
|
||||
} // namespace bluetooth_proxy
|
||||
} // namespace esphome
|
||||
|
||||
|
@@ -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,30 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
|
||||
web_server_base.WebServerBase
|
||||
),
|
||||
cv.Optional(CONF_KEEP_USER_CREDENTIALS, default=False): cv.boolean,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_arduino,
|
||||
cv.only_on(["esp32", "esp8266"]),
|
||||
)
|
||||
|
||||
|
||||
def validate_wifi(config):
|
||||
wifi_conf = fv.full_config.get()[CONF_WIFI]
|
||||
if config.get(CONF_KEEP_USER_CREDENTIALS, False) and (
|
||||
CONF_SSID in wifi_conf
|
||||
or CONF_PASSWORD in wifi_conf
|
||||
or CONF_NETWORKS in wifi_conf
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"WiFi credentials cannot be used together with {CONF_KEEP_USER_CREDENTIALS}"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = validate_wifi
|
||||
|
||||
|
||||
@coroutine_with_priority(64.0)
|
||||
async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
||||
@@ -38,3 +58,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")
|
||||
|
@@ -13,6 +13,7 @@ using namespace esphome::cover;
|
||||
CoverTraits CurrentBasedCover::get_traits() {
|
||||
auto traits = CoverTraits();
|
||||
traits.set_supports_position(true);
|
||||
traits.set_supports_toggle(true);
|
||||
traits.set_is_assumed_state(false);
|
||||
return traits;
|
||||
}
|
||||
@@ -20,6 +21,20 @@ void CurrentBasedCover::control(const CoverCall &call) {
|
||||
if (call.get_stop()) {
|
||||
this->direction_idle_();
|
||||
}
|
||||
if (call.get_toggle().has_value()) {
|
||||
if (this->current_operation != COVER_OPERATION_IDLE) {
|
||||
this->start_direction_(COVER_OPERATION_IDLE);
|
||||
this->publish_state();
|
||||
} else {
|
||||
if (this->position == COVER_CLOSED || this->last_operation_ == COVER_OPERATION_CLOSING) {
|
||||
this->target_position_ = COVER_OPEN;
|
||||
this->start_direction_(COVER_OPERATION_OPENING);
|
||||
} else {
|
||||
this->target_position_ = COVER_CLOSED;
|
||||
this->start_direction_(COVER_OPERATION_CLOSING);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (call.get_position().has_value()) {
|
||||
auto pos = *call.get_position();
|
||||
if (pos == this->position) {
|
||||
@@ -202,9 +217,11 @@ void CurrentBasedCover::start_direction_(CoverOperation dir) {
|
||||
trig = this->stop_trigger_;
|
||||
break;
|
||||
case COVER_OPERATION_OPENING:
|
||||
this->last_operation_ = dir;
|
||||
trig = this->open_trigger_;
|
||||
break;
|
||||
case COVER_OPERATION_CLOSING:
|
||||
this->last_operation_ = dir;
|
||||
trig = this->close_trigger_;
|
||||
break;
|
||||
default:
|
||||
|
@@ -89,6 +89,8 @@ class CurrentBasedCover : public cover::Cover, public Component {
|
||||
uint32_t start_dir_time_{0};
|
||||
uint32_t last_publish_time_{0};
|
||||
float target_position_{0};
|
||||
|
||||
cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING};
|
||||
};
|
||||
|
||||
} // namespace current_based
|
||||
|
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
|
@@ -3,6 +3,7 @@ from pathlib import Path
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components.packages import validate_source_shorthand
|
||||
from esphome.const import CONF_WIFI
|
||||
from esphome.wizard import wizard_file
|
||||
from esphome.yaml_util import dump
|
||||
|
||||
@@ -43,7 +44,9 @@ async def to_code(config):
|
||||
cg.add(dashboard_import_ns.set_package_import_url(config[CONF_PACKAGE_IMPORT_URL]))
|
||||
|
||||
|
||||
def import_config(path: str, name: str, project_name: str, import_url: str) -> None:
|
||||
def import_config(
|
||||
path: str, name: str, project_name: str, import_url: str, network: str = CONF_WIFI
|
||||
) -> None:
|
||||
p = Path(path)
|
||||
|
||||
if p.exists():
|
||||
@@ -69,7 +72,9 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
|
||||
"name_add_mac_suffix": False,
|
||||
},
|
||||
}
|
||||
p.write_text(
|
||||
dump(config) + WIFI_CONFIG,
|
||||
encoding="utf8",
|
||||
)
|
||||
output = dump(config)
|
||||
|
||||
if network == CONF_WIFI:
|
||||
output += WIFI_CONFIG
|
||||
|
||||
p.write_text(output, encoding="utf8")
|
||||
|
430
esphome/components/display_menu_base/__init__.py
Normal file
430
esphome/components/display_menu_base/__init__.py
Normal file
@@ -0,0 +1,430 @@
|
||||
import re
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation, core
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_TYPE,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_ON_VALUE,
|
||||
CONF_COMMAND,
|
||||
CONF_NUMBER,
|
||||
CONF_FORMAT,
|
||||
CONF_MODE,
|
||||
CONF_ACTIVE,
|
||||
)
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.components.select import Select
|
||||
from esphome.components.number import Number
|
||||
from esphome.components.switch import Switch
|
||||
|
||||
CODEOWNERS = ["@numo68"]
|
||||
|
||||
display_menu_base_ns = cg.esphome_ns.namespace("display_menu_base")
|
||||
|
||||
CONF_DISPLAY_ID = "display_id"
|
||||
|
||||
CONF_ROTARY = "rotary"
|
||||
CONF_JOYSTICK = "joystick"
|
||||
CONF_LABEL = "label"
|
||||
CONF_MENU = "menu"
|
||||
CONF_BACK = "back"
|
||||
CONF_TEXT = "text"
|
||||
CONF_SELECT = "select"
|
||||
CONF_SWITCH = "switch"
|
||||
CONF_CUSTOM = "custom"
|
||||
CONF_ITEMS = "items"
|
||||
CONF_ON_TEXT = "on_text"
|
||||
CONF_OFF_TEXT = "off_text"
|
||||
CONF_VALUE_LAMBDA = "value_lambda"
|
||||
CONF_IMMEDIATE_EDIT = "immediate_edit"
|
||||
CONF_ROOT_ITEM_ID = "root_item_id"
|
||||
CONF_ON_ENTER = "on_enter"
|
||||
CONF_ON_LEAVE = "on_leave"
|
||||
CONF_ON_NEXT = "on_next"
|
||||
CONF_ON_PREV = "on_prev"
|
||||
|
||||
DisplayMenuComponent = display_menu_base_ns.class_("DisplayMenuComponent", cg.Component)
|
||||
|
||||
MenuItem = display_menu_base_ns.class_("MenuItem")
|
||||
MenuItemConstPtr = MenuItem.operator("ptr").operator("const")
|
||||
MenuItemMenu = display_menu_base_ns.class_("MenuItemMenu")
|
||||
MenuItemSelect = display_menu_base_ns.class_("MenuItemSelect")
|
||||
MenuItemNumber = display_menu_base_ns.class_("MenuItemNumber")
|
||||
MenuItemSwitch = display_menu_base_ns.class_("MenuItemSwitch")
|
||||
MenuItemCommand = display_menu_base_ns.class_("MenuItemCommand")
|
||||
MenuItemCustom = display_menu_base_ns.class_("MenuItemCustom")
|
||||
|
||||
UpAction = display_menu_base_ns.class_("UpAction", automation.Action)
|
||||
DownAction = display_menu_base_ns.class_("DownAction", automation.Action)
|
||||
LeftAction = display_menu_base_ns.class_("LeftAction", automation.Action)
|
||||
RightAction = display_menu_base_ns.class_("RightAction", automation.Action)
|
||||
EnterAction = display_menu_base_ns.class_("EnterAction", automation.Action)
|
||||
ShowAction = display_menu_base_ns.class_("ShowAction", automation.Action)
|
||||
HideAction = display_menu_base_ns.class_("HideAction", automation.Action)
|
||||
ShowMainAction = display_menu_base_ns.class_("ShowMainAction", automation.Action)
|
||||
|
||||
IsActiveCondition = display_menu_base_ns.class_(
|
||||
"IsActiveCondition", automation.Condition
|
||||
)
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
MenuItemType = display_menu_base_ns.enum("MenuItemType")
|
||||
|
||||
MENU_ITEM_TYPES = {
|
||||
CONF_LABEL: MenuItemType.MENU_ITEM_LABEL,
|
||||
CONF_MENU: MenuItemType.MENU_ITEM_MENU,
|
||||
CONF_BACK: MenuItemType.MENU_ITEM_BACK,
|
||||
CONF_SELECT: MenuItemType.MENU_ITEM_SELECT,
|
||||
CONF_NUMBER: MenuItemType.MENU_ITEM_NUMBER,
|
||||
CONF_SWITCH: MenuItemType.MENU_ITEM_SWITCH,
|
||||
CONF_COMMAND: MenuItemType.MENU_ITEM_COMMAND,
|
||||
CONF_CUSTOM: MenuItemType.MENU_ITEM_CUSTOM,
|
||||
}
|
||||
|
||||
MENU_ITEMS_WITH_SPECIALIZED_CLASSES = (
|
||||
CONF_MENU,
|
||||
CONF_SELECT,
|
||||
CONF_NUMBER,
|
||||
CONF_SWITCH,
|
||||
CONF_COMMAND,
|
||||
CONF_CUSTOM,
|
||||
)
|
||||
|
||||
MenuMode = display_menu_base_ns.enum("MenuMode")
|
||||
|
||||
MENU_MODES = {
|
||||
CONF_ROTARY: MenuMode.MENU_MODE_ROTARY,
|
||||
CONF_JOYSTICK: MenuMode.MENU_MODE_JOYSTICK,
|
||||
}
|
||||
|
||||
DisplayMenuOnEnterTrigger = display_menu_base_ns.class_(
|
||||
"DisplayMenuOnEnterTrigger", automation.Trigger
|
||||
)
|
||||
|
||||
DisplayMenuOnLeaveTrigger = display_menu_base_ns.class_(
|
||||
"DisplayMenuOnLeaveTrigger", automation.Trigger
|
||||
)
|
||||
|
||||
DisplayMenuOnValueTrigger = display_menu_base_ns.class_(
|
||||
"DisplayMenuOnValueTrigger", automation.Trigger
|
||||
)
|
||||
|
||||
DisplayMenuOnNextTrigger = display_menu_base_ns.class_(
|
||||
"DisplayMenuOnNextTrigger", automation.Trigger
|
||||
)
|
||||
|
||||
DisplayMenuOnPrevTrigger = display_menu_base_ns.class_(
|
||||
"DisplayMenuOnPrevTrigger", automation.Trigger
|
||||
)
|
||||
|
||||
|
||||
def validate_format(format):
|
||||
if re.search(r"^%([+-])*(\d+)*(\.\d+)*[fg]$", format) is None:
|
||||
raise cv.Invalid(
|
||||
f"{CONF_FORMAT}: has to specify a printf-like format string specifying exactly one f or g type conversion, '{format}' provided"
|
||||
)
|
||||
|
||||
return format
|
||||
|
||||
|
||||
# Use a simple indirection to circumvent the recursion limitation
|
||||
def menu_item_schema(value):
|
||||
return MENU_ITEM_SCHEMA(value)
|
||||
|
||||
|
||||
MENU_ITEM_COMMON_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_TEXT): cv.templatable(cv.string),
|
||||
}
|
||||
)
|
||||
|
||||
MENU_ITEM_ENTER_LEAVE_SCHEMA = MENU_ITEM_COMMON_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_ON_ENTER): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnEnterTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_LEAVE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnLeaveTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
MENU_ITEM_VALUE_SCHEMA = MENU_ITEM_COMMON_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnValueTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA = MENU_ITEM_ENTER_LEAVE_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnValueTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
MENU_ITEM_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
CONF_LABEL: MENU_ITEM_COMMON_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItem),
|
||||
}
|
||||
),
|
||||
CONF_BACK: MENU_ITEM_COMMON_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItem),
|
||||
}
|
||||
),
|
||||
CONF_MENU: MENU_ITEM_ENTER_LEAVE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItemMenu),
|
||||
cv.Required(CONF_ITEMS): cv.All(
|
||||
cv.ensure_list(menu_item_schema), cv.Length(min=1)
|
||||
),
|
||||
}
|
||||
),
|
||||
CONF_SELECT: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItemSelect),
|
||||
cv.Required(CONF_SELECT): cv.use_id(Select),
|
||||
cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
|
||||
}
|
||||
),
|
||||
CONF_NUMBER: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItemNumber),
|
||||
cv.Required(CONF_NUMBER): cv.use_id(Number),
|
||||
cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_FORMAT, default="%.1f"): cv.All(
|
||||
cv.string_strict,
|
||||
validate_format,
|
||||
),
|
||||
cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
|
||||
}
|
||||
),
|
||||
CONF_SWITCH: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItemSwitch),
|
||||
cv.Required(CONF_SWITCH): cv.use_id(Switch),
|
||||
cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_TEXT, default="On"): cv.string_strict,
|
||||
cv.Optional(CONF_OFF_TEXT, default="Off"): cv.string_strict,
|
||||
cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
|
||||
}
|
||||
),
|
||||
CONF_COMMAND: MENU_ITEM_VALUE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItemCommand),
|
||||
}
|
||||
),
|
||||
CONF_CUSTOM: MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(MenuItemCustom),
|
||||
cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_ON_NEXT): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnNextTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PREV): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnPrevTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
},
|
||||
default_type="label",
|
||||
lower=True,
|
||||
)
|
||||
|
||||
DISPLAY_MENU_BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
|
||||
cv.GenerateID(CONF_ROOT_ITEM_ID): cv.declare_id(MenuItemMenu),
|
||||
cv.Optional(CONF_MODE, default=CONF_ROTARY): cv.enum(MENU_MODES),
|
||||
cv.Optional(CONF_ON_ENTER): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnEnterTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_LEAVE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayMenuOnLeaveTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Required(CONF_ITEMS): cv.All(
|
||||
cv.ensure_list(MENU_ITEM_SCHEMA), cv.Length(min=1)
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
MENU_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(DisplayMenuComponent),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action("display_menu.up", UpAction, MENU_ACTION_SCHEMA)
|
||||
async def menu_up_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action("display_menu.down", DownAction, MENU_ACTION_SCHEMA)
|
||||
async def menu_down_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action("display_menu.left", LeftAction, MENU_ACTION_SCHEMA)
|
||||
async def menu_left_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action("display_menu.right", RightAction, MENU_ACTION_SCHEMA)
|
||||
async def menu_right_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action("display_menu.enter", EnterAction, MENU_ACTION_SCHEMA)
|
||||
async def menu_enter_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action("display_menu.show", ShowAction, MENU_ACTION_SCHEMA)
|
||||
async def menu_show_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action("display_menu.hide", HideAction, MENU_ACTION_SCHEMA)
|
||||
async def menu_hide_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"display_menu.show_main", ShowMainAction, MENU_ACTION_SCHEMA
|
||||
)
|
||||
async def menu_show_main_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"display_menu.is_active",
|
||||
IsActiveCondition,
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(DisplayMenuComponent),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def display_menu_is_active_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||
|
||||
|
||||
async def menu_item_to_code(menu, config, parent):
|
||||
if config[CONF_TYPE] in MENU_ITEMS_WITH_SPECIALIZED_CLASSES:
|
||||
item = cg.new_Pvariable(config[CONF_ID])
|
||||
else:
|
||||
item = cg.new_Pvariable(config[CONF_ID], MENU_ITEM_TYPES[config[CONF_TYPE]])
|
||||
cg.add(parent.add_item(item))
|
||||
if CONF_TEXT in config:
|
||||
if isinstance(config[CONF_TEXT], core.Lambda):
|
||||
template_ = await cg.templatable(
|
||||
config[CONF_TEXT], [(MenuItemConstPtr, "it")], cg.std_string
|
||||
)
|
||||
cg.add(item.set_text(template_))
|
||||
else:
|
||||
cg.add(item.set_text(config[CONF_TEXT]))
|
||||
if CONF_VALUE_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
config[CONF_VALUE_LAMBDA],
|
||||
[(MenuItemConstPtr, "it")],
|
||||
return_type=cg.std_string,
|
||||
)
|
||||
cg.add(item.set_value_lambda(template_))
|
||||
if CONF_ITEMS in config:
|
||||
for c in config[CONF_ITEMS]:
|
||||
await menu_item_to_code(menu, c, item)
|
||||
if CONF_IMMEDIATE_EDIT in config:
|
||||
cg.add(item.set_immediate_edit(config[CONF_IMMEDIATE_EDIT]))
|
||||
if config[CONF_TYPE] == CONF_SELECT:
|
||||
var = await cg.get_variable(config[CONF_SELECT])
|
||||
cg.add(item.set_select_variable(var))
|
||||
if config[CONF_TYPE] == CONF_NUMBER:
|
||||
var = await cg.get_variable(config[CONF_NUMBER])
|
||||
cg.add(item.set_number_variable(var))
|
||||
cg.add(item.set_format(config[CONF_FORMAT]))
|
||||
if config[CONF_TYPE] == CONF_SWITCH:
|
||||
var = await cg.get_variable(config[CONF_SWITCH])
|
||||
cg.add(item.set_switch_variable(var))
|
||||
cg.add(item.set_on_text(config[CONF_ON_TEXT]))
|
||||
cg.add(item.set_off_text(config[CONF_OFF_TEXT]))
|
||||
for conf in config.get(CONF_ON_ENTER, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
|
||||
await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
|
||||
for conf in config.get(CONF_ON_LEAVE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
|
||||
await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
|
||||
for conf in config.get(CONF_ON_VALUE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
|
||||
await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
|
||||
for conf in config.get(CONF_ON_NEXT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
|
||||
await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
|
||||
for conf in config.get(CONF_ON_PREV, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item)
|
||||
await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
|
||||
|
||||
|
||||
async def display_menu_to_code(menu, config):
|
||||
cg.add(menu.set_active(config[CONF_ACTIVE]))
|
||||
root_item = cg.new_Pvariable(config[CONF_ROOT_ITEM_ID])
|
||||
cg.add(menu.set_root_item(root_item))
|
||||
cg.add(menu.set_mode(config[CONF_MODE]))
|
||||
for c in config[CONF_ITEMS]:
|
||||
await menu_item_to_code(menu, c, root_item)
|
||||
for conf in config.get(CONF_ON_ENTER, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], root_item)
|
||||
await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
|
||||
for conf in config.get(CONF_ON_LEAVE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], root_item)
|
||||
await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf)
|
133
esphome/components/display_menu_base/automation.h
Normal file
133
esphome/components/display_menu_base/automation.h
Normal file
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "display_menu_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace display_menu_base {
|
||||
|
||||
template<typename... Ts> class UpAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit UpAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->up(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class DownAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit DownAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->down(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class LeftAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit LeftAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->left(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class RightAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit RightAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->right(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EnterAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit EnterAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->enter(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ShowAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ShowAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->show(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class HideAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit HideAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->hide(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ShowMainAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit ShowMainAction(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
|
||||
void play(Ts... x) override { this->menu_->show_main(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
template<typename... Ts> class IsActiveCondition : public Condition<Ts...> {
|
||||
public:
|
||||
explicit IsActiveCondition(DisplayMenuComponent *menu) : menu_(menu) {}
|
||||
bool check(Ts... x) override { return this->menu_->is_active(); }
|
||||
|
||||
protected:
|
||||
DisplayMenuComponent *menu_;
|
||||
};
|
||||
|
||||
class DisplayMenuOnEnterTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnEnterTrigger(MenuItem *parent) {
|
||||
parent->add_on_enter_callback([this, parent]() { this->trigger(parent); });
|
||||
}
|
||||
};
|
||||
|
||||
class DisplayMenuOnLeaveTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnLeaveTrigger(MenuItem *parent) {
|
||||
parent->add_on_leave_callback([this, parent]() { this->trigger(parent); });
|
||||
}
|
||||
};
|
||||
|
||||
class DisplayMenuOnValueTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnValueTrigger(MenuItem *parent) {
|
||||
parent->add_on_value_callback([this, parent]() { this->trigger(parent); });
|
||||
}
|
||||
};
|
||||
|
||||
class DisplayMenuOnNextTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnNextTrigger(MenuItemCustom *parent) {
|
||||
parent->add_on_next_callback([this, parent]() { this->trigger(parent); });
|
||||
}
|
||||
};
|
||||
|
||||
class DisplayMenuOnPrevTrigger : public Trigger<const MenuItem *> {
|
||||
public:
|
||||
explicit DisplayMenuOnPrevTrigger(MenuItemCustom *parent) {
|
||||
parent->add_on_prev_callback([this, parent]() { this->trigger(parent); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace display_menu_base
|
||||
} // namespace esphome
|
315
esphome/components/display_menu_base/display_menu_base.cpp
Normal file
315
esphome/components/display_menu_base/display_menu_base.cpp
Normal file
@@ -0,0 +1,315 @@
|
||||
#include "display_menu_base.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome {
|
||||
namespace display_menu_base {
|
||||
|
||||
void DisplayMenuComponent::up() {
|
||||
if (this->check_healthy_and_active_()) {
|
||||
bool changed = false;
|
||||
|
||||
if (this->editing_) {
|
||||
switch (this->mode_) {
|
||||
case MENU_MODE_ROTARY:
|
||||
changed = this->get_selected_item_()->select_prev();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
changed = this->cursor_up_();
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this->draw_and_update();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::down() {
|
||||
if (this->check_healthy_and_active_()) {
|
||||
bool changed = false;
|
||||
|
||||
if (this->editing_) {
|
||||
switch (this->mode_) {
|
||||
case MENU_MODE_ROTARY:
|
||||
changed = this->get_selected_item_()->select_next();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
changed = this->cursor_down_();
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this->draw_and_update();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::left() {
|
||||
if (this->check_healthy_and_active_()) {
|
||||
bool changed = false;
|
||||
|
||||
switch (this->get_selected_item_()->get_type()) {
|
||||
case MENU_ITEM_SELECT:
|
||||
case MENU_ITEM_SWITCH:
|
||||
case MENU_ITEM_NUMBER:
|
||||
case MENU_ITEM_CUSTOM:
|
||||
switch (this->mode_) {
|
||||
case MENU_MODE_ROTARY:
|
||||
if (this->editing_) {
|
||||
this->finish_editing_();
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
case MENU_MODE_JOYSTICK:
|
||||
if (this->editing_ || this->get_selected_item_()->get_immediate_edit())
|
||||
changed = this->get_selected_item_()->select_prev();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MENU_ITEM_BACK:
|
||||
changed = this->leave_menu_();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this->draw_and_update();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::right() {
|
||||
if (this->check_healthy_and_active_()) {
|
||||
bool changed = false;
|
||||
|
||||
switch (this->get_selected_item_()->get_type()) {
|
||||
case MENU_ITEM_SELECT:
|
||||
case MENU_ITEM_SWITCH:
|
||||
case MENU_ITEM_NUMBER:
|
||||
case MENU_ITEM_CUSTOM:
|
||||
switch (this->mode_) {
|
||||
case MENU_MODE_JOYSTICK:
|
||||
if (this->editing_ || this->get_selected_item_()->get_immediate_edit())
|
||||
changed = this->get_selected_item_()->select_next();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MENU_ITEM_MENU:
|
||||
changed = this->enter_menu_();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this->draw_and_update();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::enter() {
|
||||
if (this->check_healthy_and_active_()) {
|
||||
bool changed = false;
|
||||
MenuItem *item = this->get_selected_item_();
|
||||
|
||||
if (this->editing_) {
|
||||
this->finish_editing_();
|
||||
changed = true;
|
||||
} else {
|
||||
switch (item->get_type()) {
|
||||
case MENU_ITEM_MENU:
|
||||
changed = this->enter_menu_();
|
||||
break;
|
||||
case MENU_ITEM_BACK:
|
||||
changed = this->leave_menu_();
|
||||
break;
|
||||
case MENU_ITEM_SELECT:
|
||||
case MENU_ITEM_SWITCH:
|
||||
case MENU_ITEM_CUSTOM:
|
||||
if (item->get_immediate_edit()) {
|
||||
changed = item->select_next();
|
||||
} else {
|
||||
this->editing_ = true;
|
||||
item->on_enter();
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
case MENU_ITEM_NUMBER:
|
||||
// A number cannot be immediate in the rotary mode
|
||||
if (!item->get_immediate_edit() || this->mode_ == MENU_MODE_ROTARY) {
|
||||
this->editing_ = true;
|
||||
item->on_enter();
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
case MENU_ITEM_COMMAND:
|
||||
changed = item->select_next();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this->draw_and_update();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::draw() {
|
||||
if (this->check_healthy_and_active_())
|
||||
this->draw_menu();
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::show_main() {
|
||||
bool disp_changed = false;
|
||||
|
||||
if (this->is_failed())
|
||||
return;
|
||||
|
||||
this->process_initial_();
|
||||
|
||||
if (this->active_ && this->editing_)
|
||||
this->finish_editing_();
|
||||
|
||||
if (this->displayed_item_ != this->root_item_) {
|
||||
this->displayed_item_->on_leave();
|
||||
disp_changed = true;
|
||||
}
|
||||
|
||||
this->reset_();
|
||||
this->active_ = true;
|
||||
|
||||
if (disp_changed) {
|
||||
this->displayed_item_->on_enter();
|
||||
}
|
||||
|
||||
this->draw_and_update();
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::show() {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
|
||||
this->process_initial_();
|
||||
|
||||
if (!this->active_) {
|
||||
this->active_ = true;
|
||||
this->draw_and_update();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::hide() {
|
||||
if (this->check_healthy_and_active_()) {
|
||||
if (this->editing_)
|
||||
this->finish_editing_();
|
||||
this->active_ = false;
|
||||
this->update();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::reset_() {
|
||||
this->displayed_item_ = this->root_item_;
|
||||
this->cursor_index_ = this->top_index_ = 0;
|
||||
this->selection_stack_.clear();
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::process_initial_() {
|
||||
if (!this->root_on_enter_called_) {
|
||||
this->root_item_->on_enter();
|
||||
this->root_on_enter_called_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool DisplayMenuComponent::check_healthy_and_active_() {
|
||||
if (this->is_failed())
|
||||
return false;
|
||||
|
||||
this->process_initial_();
|
||||
|
||||
return this->active_;
|
||||
}
|
||||
|
||||
bool DisplayMenuComponent::cursor_up_() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->cursor_index_ > 0) {
|
||||
changed = true;
|
||||
|
||||
--this->cursor_index_;
|
||||
|
||||
if (this->cursor_index_ < this->top_index_)
|
||||
this->top_index_ = this->cursor_index_;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool DisplayMenuComponent::cursor_down_() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->cursor_index_ + 1 < this->displayed_item_->items_size()) {
|
||||
changed = true;
|
||||
|
||||
++this->cursor_index_;
|
||||
|
||||
if (this->cursor_index_ >= this->top_index_ + this->rows_)
|
||||
this->top_index_ = this->cursor_index_ - this->rows_ + 1;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool DisplayMenuComponent::enter_menu_() {
|
||||
this->displayed_item_->on_leave();
|
||||
this->displayed_item_ = static_cast<MenuItemMenu *>(this->get_selected_item_());
|
||||
this->selection_stack_.push_front({this->top_index_, this->cursor_index_});
|
||||
this->cursor_index_ = this->top_index_ = 0;
|
||||
this->displayed_item_->on_enter();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DisplayMenuComponent::leave_menu_() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->displayed_item_->get_parent() != nullptr) {
|
||||
this->displayed_item_->on_leave();
|
||||
this->displayed_item_ = this->displayed_item_->get_parent();
|
||||
this->top_index_ = this->selection_stack_.front().first;
|
||||
this->cursor_index_ = this->selection_stack_.front().second;
|
||||
this->selection_stack_.pop_front();
|
||||
this->displayed_item_->on_enter();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::finish_editing_() {
|
||||
switch (this->get_selected_item_()->get_type()) {
|
||||
case MENU_ITEM_SELECT:
|
||||
case MENU_ITEM_NUMBER:
|
||||
case MENU_ITEM_SWITCH:
|
||||
case MENU_ITEM_CUSTOM:
|
||||
this->get_selected_item_()->on_leave();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this->editing_ = false;
|
||||
}
|
||||
|
||||
void DisplayMenuComponent::draw_menu() {
|
||||
for (size_t i = 0; i < this->rows_ && this->top_index_ + i < this->displayed_item_->items_size(); ++i) {
|
||||
this->draw_item(this->displayed_item_->get_item(this->top_index_ + i), i,
|
||||
this->top_index_ + i == this->cursor_index_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace display_menu_base
|
||||
} // namespace esphome
|
77
esphome/components/display_menu_base/display_menu_base.h
Normal file
77
esphome/components/display_menu_base/display_menu_base.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include "menu_item.h"
|
||||
|
||||
#include <forward_list>
|
||||
|
||||
namespace esphome {
|
||||
namespace display_menu_base {
|
||||
|
||||
enum MenuMode {
|
||||
MENU_MODE_ROTARY,
|
||||
MENU_MODE_JOYSTICK,
|
||||
};
|
||||
|
||||
class MenuItem;
|
||||
|
||||
/** Class to display a hierarchical menu.
|
||||
*
|
||||
*/
|
||||
class DisplayMenuComponent : public Component {
|
||||
public:
|
||||
void set_root_item(MenuItemMenu *item) { this->displayed_item_ = this->root_item_ = item; }
|
||||
void set_active(bool active) { this->active_ = active; }
|
||||
void set_mode(MenuMode mode) { this->mode_ = mode; }
|
||||
void set_rows(uint8_t rows) { this->rows_ = rows; }
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::PROCESSOR; }
|
||||
|
||||
void up();
|
||||
void down();
|
||||
void left();
|
||||
void right();
|
||||
void enter();
|
||||
|
||||
void show_main();
|
||||
void show();
|
||||
void hide();
|
||||
|
||||
void draw();
|
||||
|
||||
bool is_active() const { return this->active_; }
|
||||
|
||||
protected:
|
||||
void reset_();
|
||||
void process_initial_();
|
||||
bool check_healthy_and_active_();
|
||||
MenuItem *get_selected_item_() { return this->displayed_item_->get_item(this->cursor_index_); }
|
||||
bool cursor_up_();
|
||||
bool cursor_down_();
|
||||
bool enter_menu_();
|
||||
bool leave_menu_();
|
||||
void finish_editing_();
|
||||
virtual void draw_menu();
|
||||
virtual void draw_item(const MenuItem *item, uint8_t row, bool selected) = 0;
|
||||
virtual void update() {}
|
||||
virtual void draw_and_update() {
|
||||
draw_menu();
|
||||
update();
|
||||
}
|
||||
|
||||
uint8_t rows_;
|
||||
bool active_;
|
||||
MenuMode mode_;
|
||||
MenuItemMenu *root_item_{nullptr};
|
||||
|
||||
MenuItemMenu *displayed_item_{nullptr};
|
||||
uint8_t top_index_{0};
|
||||
uint8_t cursor_index_{0};
|
||||
std::forward_list<std::pair<uint8_t, uint8_t>> selection_stack_{};
|
||||
bool editing_{false};
|
||||
bool root_on_enter_called_{false};
|
||||
};
|
||||
|
||||
} // namespace display_menu_base
|
||||
} // namespace esphome
|
179
esphome/components/display_menu_base/menu_item.cpp
Normal file
179
esphome/components/display_menu_base/menu_item.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
#include "menu_item.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace esphome {
|
||||
namespace display_menu_base {
|
||||
|
||||
void MenuItem::on_enter() { this->on_enter_callbacks_.call(); }
|
||||
|
||||
void MenuItem::on_leave() { this->on_leave_callbacks_.call(); }
|
||||
|
||||
void MenuItem::on_value_() { this->on_value_callbacks_.call(); }
|
||||
|
||||
#ifdef USE_SELECT
|
||||
std::string MenuItemSelect::get_value_text() const {
|
||||
std::string result;
|
||||
|
||||
if (this->value_getter_.has_value()) {
|
||||
result = this->value_getter_.value()(this);
|
||||
} else {
|
||||
if (this->select_var_ != nullptr) {
|
||||
result = this->select_var_->state;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MenuItemSelect::select_next() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->select_var_ != nullptr) {
|
||||
this->select_var_->make_call().select_next(true).perform();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool MenuItemSelect::select_prev() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->select_var_ != nullptr) {
|
||||
this->select_var_->make_call().select_previous(true).perform();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
#endif // USE_SELECT
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
std::string MenuItemNumber::get_value_text() const {
|
||||
std::string result;
|
||||
|
||||
if (this->value_getter_.has_value()) {
|
||||
result = this->value_getter_.value()(this);
|
||||
} else {
|
||||
char data[32];
|
||||
snprintf(data, sizeof(data), this->format_.c_str(), get_number_value_());
|
||||
result = data;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MenuItemNumber::select_next() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->number_var_ != nullptr) {
|
||||
float last = this->number_var_->state;
|
||||
this->number_var_->make_call().number_increment(false).perform();
|
||||
|
||||
if (this->number_var_->state != last) {
|
||||
this->on_value_();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool MenuItemNumber::select_prev() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->number_var_ != nullptr) {
|
||||
float last = this->number_var_->state;
|
||||
this->number_var_->make_call().number_decrement(false).perform();
|
||||
|
||||
if (this->number_var_->state != last) {
|
||||
this->on_value_();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
float MenuItemNumber::get_number_value_() const {
|
||||
float result = 0.0;
|
||||
|
||||
if (this->number_var_ != nullptr) {
|
||||
if (!this->number_var_->has_state() || this->number_var_->state < this->number_var_->traits.get_min_value()) {
|
||||
result = this->number_var_->traits.get_min_value();
|
||||
} else if (this->number_var_->state > this->number_var_->traits.get_max_value()) {
|
||||
result = this->number_var_->traits.get_max_value();
|
||||
} else {
|
||||
result = this->number_var_->state;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif // USE_NUMBER
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
std::string MenuItemSwitch::get_value_text() const {
|
||||
std::string result;
|
||||
|
||||
if (this->value_getter_.has_value()) {
|
||||
result = this->value_getter_.value()(this);
|
||||
} else {
|
||||
result = this->get_switch_state_() ? this->switch_on_text_ : this->switch_off_text_;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MenuItemSwitch::select_next() { return this->toggle_switch_(); }
|
||||
|
||||
bool MenuItemSwitch::select_prev() { return this->toggle_switch_(); }
|
||||
|
||||
bool MenuItemSwitch::get_switch_state_() const { return (this->switch_var_ != nullptr && this->switch_var_->state); }
|
||||
|
||||
bool MenuItemSwitch::toggle_switch_() {
|
||||
bool changed = false;
|
||||
|
||||
if (this->switch_var_ != nullptr) {
|
||||
this->switch_var_->toggle();
|
||||
this->on_value_();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
#endif // USE_SWITCH
|
||||
|
||||
std::string MenuItemCustom::get_value_text() const {
|
||||
return (this->value_getter_.has_value()) ? this->value_getter_.value()(this) : "";
|
||||
}
|
||||
|
||||
bool MenuItemCommand::select_next() {
|
||||
this->on_value_();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MenuItemCommand::select_prev() {
|
||||
this->on_value_();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MenuItemCustom::select_next() {
|
||||
this->on_next_();
|
||||
this->on_value_();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MenuItemCustom::select_prev() {
|
||||
this->on_prev_();
|
||||
this->on_value_();
|
||||
return true;
|
||||
}
|
||||
|
||||
void MenuItemCustom::on_next_() { this->on_next_callbacks_.call(); }
|
||||
|
||||
void MenuItemCustom::on_prev_() { this->on_prev_callbacks_.call(); }
|
||||
|
||||
} // namespace display_menu_base
|
||||
} // namespace esphome
|
187
esphome/components/display_menu_base/menu_item.h
Normal file
187
esphome/components/display_menu_base/menu_item.h
Normal file
@@ -0,0 +1,187 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
#include "esphome/components/number/number.h"
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
#include "esphome/components/select/select.h"
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace display_menu_base {
|
||||
|
||||
enum MenuItemType {
|
||||
MENU_ITEM_LABEL,
|
||||
MENU_ITEM_MENU,
|
||||
MENU_ITEM_BACK,
|
||||
MENU_ITEM_SELECT,
|
||||
MENU_ITEM_NUMBER,
|
||||
MENU_ITEM_SWITCH,
|
||||
MENU_ITEM_COMMAND,
|
||||
MENU_ITEM_CUSTOM,
|
||||
};
|
||||
|
||||
class MenuItem;
|
||||
class MenuItemMenu;
|
||||
using value_getter_t = std::function<std::string(const MenuItem *)>;
|
||||
|
||||
class MenuItem {
|
||||
public:
|
||||
explicit MenuItem(MenuItemType t) : item_type_(t) {}
|
||||
void set_parent(MenuItemMenu *parent) { this->parent_ = parent; }
|
||||
MenuItemMenu *get_parent() { return this->parent_; }
|
||||
MenuItemType get_type() const { return this->item_type_; }
|
||||
template<typename V> void set_text(V val) { this->text_ = val; }
|
||||
void add_on_enter_callback(std::function<void()> &&cb) { this->on_enter_callbacks_.add(std::move(cb)); }
|
||||
void add_on_leave_callback(std::function<void()> &&cb) { this->on_leave_callbacks_.add(std::move(cb)); }
|
||||
void add_on_value_callback(std::function<void()> &&cb) { this->on_value_callbacks_.add(std::move(cb)); }
|
||||
|
||||
std::string get_text() const { return const_cast<MenuItem *>(this)->text_.value(this); }
|
||||
virtual bool get_immediate_edit() const { return false; }
|
||||
virtual bool has_value() const { return false; }
|
||||
virtual std::string get_value_text() const { return ""; }
|
||||
|
||||
virtual bool select_next() { return false; }
|
||||
virtual bool select_prev() { return false; }
|
||||
|
||||
void on_enter();
|
||||
void on_leave();
|
||||
|
||||
protected:
|
||||
void on_value_();
|
||||
|
||||
MenuItemType item_type_;
|
||||
MenuItemMenu *parent_{nullptr};
|
||||
TemplatableValue<std::string, const MenuItem *> text_;
|
||||
|
||||
CallbackManager<void()> on_enter_callbacks_{};
|
||||
CallbackManager<void()> on_leave_callbacks_{};
|
||||
CallbackManager<void()> on_value_callbacks_{};
|
||||
};
|
||||
|
||||
class MenuItemMenu : public MenuItem {
|
||||
public:
|
||||
explicit MenuItemMenu() : MenuItem(MENU_ITEM_MENU) {}
|
||||
void add_item(MenuItem *item) {
|
||||
item->set_parent(this);
|
||||
this->items_.push_back(item);
|
||||
}
|
||||
size_t items_size() const { return this->items_.size(); }
|
||||
MenuItem *get_item(size_t i) { return this->items_[i]; }
|
||||
|
||||
protected:
|
||||
std::vector<MenuItem *> items_;
|
||||
};
|
||||
|
||||
class MenuItemEditable : public MenuItem {
|
||||
public:
|
||||
explicit MenuItemEditable(MenuItemType t) : MenuItem(t) {}
|
||||
void set_immediate_edit(bool val) { this->immediate_edit_ = val; }
|
||||
bool get_immediate_edit() const override { return this->immediate_edit_; }
|
||||
void set_value_lambda(value_getter_t &&getter) { this->value_getter_ = getter; }
|
||||
|
||||
protected:
|
||||
bool immediate_edit_{false};
|
||||
optional<value_getter_t> value_getter_{};
|
||||
};
|
||||
|
||||
#ifdef USE_SELECT
|
||||
class MenuItemSelect : public MenuItemEditable {
|
||||
public:
|
||||
explicit MenuItemSelect() : MenuItemEditable(MENU_ITEM_SELECT) {}
|
||||
void set_select_variable(select::Select *var) { this->select_var_ = var; }
|
||||
|
||||
bool has_value() const override { return true; }
|
||||
std::string get_value_text() const override;
|
||||
|
||||
bool select_next() override;
|
||||
bool select_prev() override;
|
||||
|
||||
protected:
|
||||
select::Select *select_var_{nullptr};
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
class MenuItemNumber : public MenuItemEditable {
|
||||
public:
|
||||
explicit MenuItemNumber() : MenuItemEditable(MENU_ITEM_NUMBER) {}
|
||||
void set_number_variable(number::Number *var) { this->number_var_ = var; }
|
||||
void set_format(const std::string &fmt) { this->format_ = fmt; }
|
||||
|
||||
bool has_value() const override { return true; }
|
||||
std::string get_value_text() const override;
|
||||
|
||||
bool select_next() override;
|
||||
bool select_prev() override;
|
||||
|
||||
protected:
|
||||
float get_number_value_() const;
|
||||
|
||||
number::Number *number_var_{nullptr};
|
||||
std::string format_;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
class MenuItemSwitch : public MenuItemEditable {
|
||||
public:
|
||||
explicit MenuItemSwitch() : MenuItemEditable(MENU_ITEM_SWITCH) {}
|
||||
void set_switch_variable(switch_::Switch *var) { this->switch_var_ = var; }
|
||||
void set_on_text(const std::string &t) { this->switch_on_text_ = t; }
|
||||
void set_off_text(const std::string &t) { this->switch_off_text_ = t; }
|
||||
|
||||
bool has_value() const override { return true; }
|
||||
std::string get_value_text() const override;
|
||||
|
||||
bool select_next() override;
|
||||
bool select_prev() override;
|
||||
|
||||
protected:
|
||||
bool get_switch_state_() const;
|
||||
bool toggle_switch_();
|
||||
|
||||
switch_::Switch *switch_var_{nullptr};
|
||||
std::string switch_on_text_;
|
||||
std::string switch_off_text_;
|
||||
};
|
||||
#endif
|
||||
|
||||
class MenuItemCommand : public MenuItem {
|
||||
public:
|
||||
explicit MenuItemCommand() : MenuItem(MENU_ITEM_COMMAND) {}
|
||||
|
||||
bool select_next() override;
|
||||
bool select_prev() override;
|
||||
};
|
||||
|
||||
class MenuItemCustom : public MenuItemEditable {
|
||||
public:
|
||||
explicit MenuItemCustom() : MenuItemEditable(MENU_ITEM_CUSTOM) {}
|
||||
void add_on_next_callback(std::function<void()> &&cb) { this->on_next_callbacks_.add(std::move(cb)); }
|
||||
void add_on_prev_callback(std::function<void()> &&cb) { this->on_prev_callbacks_.add(std::move(cb)); }
|
||||
|
||||
bool has_value() const override { return this->value_getter_.has_value(); }
|
||||
std::string get_value_text() const override;
|
||||
|
||||
bool select_next() override;
|
||||
bool select_prev() override;
|
||||
|
||||
protected:
|
||||
void on_next_();
|
||||
void on_prev_();
|
||||
|
||||
CallbackManager<void()> on_next_callbacks_{};
|
||||
CallbackManager<void()> on_prev_callbacks_{};
|
||||
};
|
||||
|
||||
} // namespace display_menu_base
|
||||
} // namespace esphome
|
@@ -105,6 +105,12 @@ _esp32_validations = {
|
||||
|
||||
def validate_gpio_pin(value):
|
||||
value = _translate_pin(value)
|
||||
board = CORE.data[KEY_ESP32][KEY_BOARD]
|
||||
board_pins = boards.ESP32_BOARD_PINS.get(board, {})
|
||||
|
||||
if value in board_pins.values():
|
||||
return value
|
||||
|
||||
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||
if variant not in _esp32_validations:
|
||||
raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
|
||||
|
@@ -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)
|
||||
|
12
esphome/components/esp32_ble_client/__init__.py
Normal file
12
esphome/components/esp32_ble_client/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome.components import esp32_ble_tracker
|
||||
|
||||
AUTO_LOAD = ["esp32_ble_tracker"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
esp32_ble_client_ns = cg.esphome_ns.namespace("esp32_ble_client")
|
||||
BLEClientBase = esp32_ble_client_ns.class_(
|
||||
"BLEClientBase", esp32_ble_tracker.ESPBTClient, cg.Component
|
||||
)
|
87
esphome/components/esp32_ble_client/ble_characteristic.cpp
Normal file
87
esphome/components/esp32_ble_client/ble_characteristic.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "ble_characteristic.h"
|
||||
#include "ble_client_base.h"
|
||||
#include "ble_service.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
static const char *const TAG = "esp32_ble_client";
|
||||
|
||||
BLECharacteristic::~BLECharacteristic() {
|
||||
for (auto &desc : this->descriptors)
|
||||
delete desc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
void BLECharacteristic::parse_descriptors() {
|
||||
uint16_t offset = 0;
|
||||
esp_gattc_descr_elem_t result;
|
||||
|
||||
while (true) {
|
||||
uint16_t count = 1;
|
||||
esp_gatt_status_t status =
|
||||
esp_ble_gattc_get_all_descr(this->service->client->get_gattc_if(), this->service->client->get_conn_id(),
|
||||
this->handle, &result, &count, offset);
|
||||
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (status != ESP_GATT_OK) {
|
||||
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) {
|
||||
break;
|
||||
}
|
||||
|
||||
BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
|
||||
desc->handle = result.handle;
|
||||
desc->characteristic = this;
|
||||
this->descriptors.push_back(desc);
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
|
||||
for (auto &desc : this->descriptors) {
|
||||
if (desc->uuid == uuid)
|
||||
return desc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
|
||||
}
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor_by_handle(uint16_t handle) {
|
||||
for (auto &desc : this->descriptors) {
|
||||
if (desc->handle == handle)
|
||||
return desc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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, "[%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;
|
||||
}
|
||||
|
||||
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
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
35
esphome/components/esp32_ble_client/ble_characteristic.h
Normal file
35
esphome/components/esp32_ble_client/ble_characteristic.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#include "ble_descriptor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEService;
|
||||
|
||||
class BLECharacteristic {
|
||||
public:
|
||||
~BLECharacteristic();
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t handle;
|
||||
esp_gatt_char_prop_t properties;
|
||||
std::vector<BLEDescriptor *> descriptors;
|
||||
void parse_descriptors();
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
|
||||
BLEDescriptor *get_descriptor(uint16_t uuid);
|
||||
BLEDescriptor *get_descriptor_by_handle(uint16_t handle);
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
339
esphome/components/esp32_ble_client/ble_client_base.cpp
Normal file
339
esphome/components/esp32_ble_client/ble_client_base.cpp
Normal file
@@ -0,0 +1,339 @@
|
||||
#include "ble_client_base.h"
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
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);
|
||||
this->mark_failed();
|
||||
}
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
}
|
||||
|
||||
void BLEClientBase::loop() {
|
||||
if (this->state_ == espbt::ClientState::DISCOVERED) {
|
||||
this->connect();
|
||||
}
|
||||
}
|
||||
|
||||
float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
|
||||
|
||||
bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
|
||||
if (device.address_uint64() != this->address_)
|
||||
return false;
|
||||
if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING)
|
||||
return false;
|
||||
|
||||
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();
|
||||
this->remote_bda_[0] = (addr >> 40) & 0xFF;
|
||||
this->remote_bda_[1] = (addr >> 32) & 0xFF;
|
||||
this->remote_bda_[2] = (addr >> 24) & 0xFF;
|
||||
this->remote_bda_[3] = (addr >> 16) & 0xFF;
|
||||
this->remote_bda_[4] = (addr >> 8) & 0xFF;
|
||||
this->remote_bda_[5] = (addr >> 0) & 0xFF;
|
||||
this->remote_addr_type_ = device.get_address_type();
|
||||
return true;
|
||||
}
|
||||
|
||||
void BLEClientBase::connect() {
|
||||
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, "[%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::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 false;
|
||||
if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_)
|
||||
return false;
|
||||
|
||||
ESP_LOGV(TAG, "[%d] [%s] gattc_event_handler: event=%d gattc_if=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), event, esp_gattc_if);
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT: {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
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, "[%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, "[%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, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(),
|
||||
param->open.status);
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_,
|
||||
this->address_str_.c_str(), ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_CFG_MTU_EVT: {
|
||||
if (param->cfg_mtu.status != ESP_GATT_OK) {
|
||||
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, "[%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 false;
|
||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->disconnect.reason);
|
||||
for (auto &svc : this->services_)
|
||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->services_.clear();
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_RES_EVT: {
|
||||
BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
|
||||
ble_service->start_handle = param->search_res.start_handle;
|
||||
ble_service->end_handle = param->search_res.end_handle;
|
||||
ble_service->client = this;
|
||||
this->services_.push_back(ble_service);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_SEARCH_CMPL_EVT", this->connection_index_, this->address_str_.c_str());
|
||||
for (auto &svc : this->services_) {
|
||||
ESP_LOGI(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
|
||||
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);
|
||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
auto *descr = this->get_config_descriptor(param->reg_for_notify.handle);
|
||||
if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
|
||||
descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Handle 0x%x (uuid %s) is not a client config char uuid", this->connection_index_,
|
||||
this->address_str_.c_str(), param->reg_for_notify.handle, descr->uuid.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
uint16_t notify_en = 1;
|
||||
auto status =
|
||||
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descr->handle, sizeof(notify_en),
|
||||
(uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, status=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
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, "[%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, "[%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, "[%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, "[%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;
|
||||
// There are other events we'll want to implement at some point to support things like pass key
|
||||
// https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse GATT values into a float for a sensor.
|
||||
// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
|
||||
float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
|
||||
// A length of one means a single octet value.
|
||||
if (length == 0)
|
||||
return 0;
|
||||
if (length == 1)
|
||||
return (float) ((uint8_t) value[0]);
|
||||
|
||||
switch (value[0]) {
|
||||
case 0x1: // boolean.
|
||||
case 0x2: // 2bit.
|
||||
case 0x3: // nibble.
|
||||
case 0x4: // uint8.
|
||||
return (float) ((uint8_t) value[1]);
|
||||
case 0x5: // uint12.
|
||||
case 0x6: // uint16.
|
||||
if (length > 2) {
|
||||
return (float) encode_uint16(value[1], value[2]);
|
||||
}
|
||||
case 0x7: // uint24.
|
||||
if (length > 3) {
|
||||
return (float) encode_uint24(value[1], value[2], value[3]);
|
||||
}
|
||||
case 0x8: // uint32.
|
||||
if (length > 4) {
|
||||
return (float) encode_uint32(value[1], value[2], value[3], value[4]);
|
||||
}
|
||||
case 0xC: // int8.
|
||||
return (float) ((int8_t) value[1]);
|
||||
case 0xD: // int12.
|
||||
case 0xE: // int16.
|
||||
if (length > 2) {
|
||||
return (float) ((int16_t)(value[1] << 8) + (int16_t) value[2]);
|
||||
}
|
||||
case 0xF: // int24.
|
||||
if (length > 3) {
|
||||
return (float) ((int32_t)(value[1] << 16) + (int32_t)(value[2] << 8) + (int32_t)(value[3]));
|
||||
}
|
||||
case 0x10: // int32.
|
||||
if (length > 4) {
|
||||
return (float) ((int32_t)(value[1] << 24) + (int32_t)(value[2] << 16) + (int32_t)(value[3] << 8) +
|
||||
(int32_t)(value[4]));
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
BLEService *BLEClientBase::get_service(espbt::ESPBTUUID uuid) {
|
||||
for (auto *svc : this->services_) {
|
||||
if (svc->uuid == uuid)
|
||||
return svc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLEService *BLEClientBase::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); }
|
||||
|
||||
BLECharacteristic *BLEClientBase::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) {
|
||||
auto *svc = this->get_service(service);
|
||||
if (svc == nullptr)
|
||||
return nullptr;
|
||||
return svc->get_characteristic(chr);
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEClientBase::get_characteristic(uint16_t service, uint16_t chr) {
|
||||
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr));
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEClientBase::get_characteristic(uint16_t handle) {
|
||||
for (auto *svc : this->services_) {
|
||||
for (auto *chr : svc->characteristics) {
|
||||
if (chr->handle == handle)
|
||||
return chr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClientBase::get_config_descriptor(uint16_t handle) {
|
||||
auto *chr = this->get_characteristic(handle);
|
||||
if (chr != nullptr) {
|
||||
for (auto &desc : chr->descriptors) {
|
||||
if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902))
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClientBase::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) {
|
||||
auto *svc = this->get_service(service);
|
||||
if (svc == nullptr)
|
||||
return nullptr;
|
||||
auto *ch = svc->get_characteristic(chr);
|
||||
if (ch == nullptr)
|
||||
return nullptr;
|
||||
return ch->get_descriptor(descr);
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClientBase::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr),
|
||||
espbt::ESPBTUUID::from_uint16(descr));
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClientBase::get_descriptor(uint16_t handle) {
|
||||
for (auto *svc : this->services_) {
|
||||
for (auto *chr : svc->characteristics) {
|
||||
for (auto *desc : chr->descriptors) {
|
||||
if (desc->handle == handle)
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
90
esphome/components/esp32_ble_client/ble_client_base.h
Normal file
90
esphome/components/esp32_ble_client/ble_client_base.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include "ble_service.h"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include <esp_bt_defs.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gatt_common_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEClientBase : public espbt::ESPBTClient, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
bool parse_device(const espbt::ESPBTDevice &device) override;
|
||||
void on_scan_end() override {}
|
||||
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();
|
||||
|
||||
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);
|
||||
BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr);
|
||||
BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr);
|
||||
BLECharacteristic *get_characteristic(uint16_t handle);
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr);
|
||||
BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr);
|
||||
BLEDescriptor *get_descriptor(uint16_t handle);
|
||||
// Get the configuration descriptor for the given characteristic handle.
|
||||
BLEDescriptor *get_config_descriptor(uint16_t handle);
|
||||
|
||||
float parse_char_value(uint8_t *value, uint16_t length);
|
||||
|
||||
int get_gattc_if() const { return this->gattc_if_; }
|
||||
uint8_t *get_remote_bda() { return this->remote_bda_; }
|
||||
esp_ble_addr_type_t get_remote_addr_type() const { return this->remote_addr_type_; }
|
||||
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_{0xFFFF};
|
||||
uint64_t address_{0};
|
||||
std::string address_str_{};
|
||||
uint8_t connection_index_;
|
||||
uint16_t mtu_{23};
|
||||
|
||||
std::vector<BLEService *> services_;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
25
esphome/components/esp32_ble_client/ble_descriptor.h
Normal file
25
esphome/components/esp32_ble_client/ble_descriptor.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLECharacteristic;
|
||||
|
||||
class BLEDescriptor {
|
||||
public:
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t handle;
|
||||
|
||||
BLECharacteristic *characteristic;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
68
esphome/components/esp32_ble_client/ble_service.cpp
Normal file
68
esphome/components/esp32_ble_client/ble_service.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#include "ble_service.h"
|
||||
#include "ble_client_base.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
static const char *const TAG = "esp32_ble_client";
|
||||
|
||||
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
|
||||
for (auto &chr : this->characteristics) {
|
||||
if (chr->uuid == uuid)
|
||||
return chr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) {
|
||||
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid));
|
||||
}
|
||||
|
||||
BLEService::~BLEService() {
|
||||
for (auto &chr : this->characteristics)
|
||||
delete chr; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
void BLEService::parse_characteristics() {
|
||||
uint16_t offset = 0;
|
||||
esp_gattc_char_elem_t result;
|
||||
|
||||
while (true) {
|
||||
uint16_t count = 1;
|
||||
esp_gatt_status_t status =
|
||||
esp_ble_gattc_get_all_char(this->client->get_gattc_if(), this->client->get_conn_id(), this->start_handle,
|
||||
this->end_handle, &result, &count, offset);
|
||||
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (status != ESP_GATT_OK) {
|
||||
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) {
|
||||
break;
|
||||
}
|
||||
|
||||
BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
|
||||
characteristic->properties = result.properties;
|
||||
characteristic->handle = result.char_handle;
|
||||
characteristic->service = this;
|
||||
this->characteristics.push_back(characteristic);
|
||||
ESP_LOGI(TAG, "[%d] [%s] characteristic %s, handle 0x%x, properties 0x%x", this->client->get_connection_index(),
|
||||
this->client->address_str().c_str(), characteristic->uuid.to_string().c_str(), characteristic->handle,
|
||||
characteristic->properties);
|
||||
characteristic->parse_descriptors();
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
32
esphome/components/esp32_ble_client/ble_service.h
Normal file
32
esphome/components/esp32_ble_client/ble_service.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#include "ble_characteristic.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEClientBase;
|
||||
|
||||
class BLEService {
|
||||
public:
|
||||
~BLEService();
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t start_handle;
|
||||
uint16_t end_handle;
|
||||
std::vector<BLECharacteristic *> characteristics;
|
||||
BLEClientBase *client;
|
||||
void parse_characteristics();
|
||||
BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid);
|
||||
BLECharacteristic *get_characteristic(uint16_t uuid);
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
@@ -3,6 +3,7 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.const import (
|
||||
CONF_ACTIVE,
|
||||
CONF_ID,
|
||||
CONF_INTERVAL,
|
||||
CONF_DURATION,
|
||||
@@ -22,7 +23,6 @@ DEPENDENCIES = ["esp32"]
|
||||
CONF_ESP32_BLE_ID = "esp32_ble_id"
|
||||
CONF_SCAN_PARAMETERS = "scan_parameters"
|
||||
CONF_WINDOW = "window"
|
||||
CONF_ACTIVE = "active"
|
||||
CONF_CONTINUOUS = "continuous"
|
||||
CONF_ON_SCAN_END = "on_scan_end"
|
||||
esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker")
|
||||
|
@@ -518,28 +518,39 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
|
||||
}
|
||||
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
|
||||
std::string ESPBTUUID::to_string() const {
|
||||
char sbuf[64];
|
||||
switch (this->uuid_.len) {
|
||||
case ESP_UUID_LEN_16:
|
||||
sprintf(sbuf, "0x%02X%02X", this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
|
||||
break;
|
||||
return str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
|
||||
case ESP_UUID_LEN_32:
|
||||
sprintf(sbuf, "0x%02X%02X%02X%02X", this->uuid_.uuid.uuid32 >> 24, (this->uuid_.uuid.uuid32 >> 16 & 0xff),
|
||||
(this->uuid_.uuid.uuid32 >> 8 & 0xff), this->uuid_.uuid.uuid32 & 0xff);
|
||||
break;
|
||||
return str_snprintf("0x%02X%02X%02X%02X", 10, this->uuid_.uuid.uuid32 >> 24,
|
||||
(this->uuid_.uuid.uuid32 >> 16 & 0xff), (this->uuid_.uuid.uuid32 >> 8 & 0xff),
|
||||
this->uuid_.uuid.uuid32 & 0xff);
|
||||
default:
|
||||
case ESP_UUID_LEN_128:
|
||||
char *bpos = sbuf;
|
||||
std::string buf;
|
||||
for (int8_t i = 15; i >= 0; i--) {
|
||||
sprintf(bpos, "%02X", this->uuid_.uuid.uuid128[i]);
|
||||
bpos += 2;
|
||||
buf += str_snprintf("%02X", 2, this->uuid_.uuid.uuid128[i]);
|
||||
if (i == 6 || i == 8 || i == 10 || i == 12)
|
||||
sprintf(bpos++, "-");
|
||||
buf += "-";
|
||||
}
|
||||
sbuf[47] = '\0';
|
||||
break;
|
||||
return buf;
|
||||
}
|
||||
return sbuf;
|
||||
return "";
|
||||
}
|
||||
|
||||
uint64_t ESPBTUUID::get_128bit_high() const {
|
||||
esp_bt_uuid_t uuid = this->as_128bit().get_uuid();
|
||||
return ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
|
||||
((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
|
||||
((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
|
||||
((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]);
|
||||
}
|
||||
uint64_t ESPBTUUID::get_128bit_low() const {
|
||||
esp_bt_uuid_t uuid = this->as_128bit().get_uuid();
|
||||
return ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
|
||||
((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
|
||||
((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
|
||||
((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]);
|
||||
}
|
||||
|
||||
ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); }
|
||||
@@ -637,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: {
|
||||
|
@@ -41,6 +41,9 @@ class ESPBTUUID {
|
||||
|
||||
std::string to_string() const;
|
||||
|
||||
uint64_t get_128bit_high() const;
|
||||
uint64_t get_128bit_low() const;
|
||||
|
||||
protected:
|
||||
esp_bt_uuid_t uuid_;
|
||||
};
|
||||
@@ -142,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.
|
||||
@@ -154,11 +159,11 @@ 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;
|
||||
void set_state(ClientState st) { this->state_ = st; }
|
||||
virtual void set_state(ClientState st) { this->state_ = st; }
|
||||
ClientState state() const { return state_; }
|
||||
int app_id;
|
||||
|
||||
|
@@ -72,13 +72,13 @@ class BLEEvent {
|
||||
// Need to also make a copy of relevant event data.
|
||||
switch (e) {
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
memcpy(this->event_.gattc.data, p->notify.value, p->notify.value_len);
|
||||
this->event_.gattc.gattc_param.notify.value = this->event_.gattc.data;
|
||||
this->data.assign(p->notify.value, p->notify.value + p->notify.value_len);
|
||||
this->event_.gattc.gattc_param.notify.value = this->data.data();
|
||||
break;
|
||||
case ESP_GATTC_READ_CHAR_EVT:
|
||||
case ESP_GATTC_READ_DESCR_EVT:
|
||||
memcpy(this->event_.gattc.data, p->read.value, p->read.value_len);
|
||||
this->event_.gattc.gattc_param.read.value = this->event_.gattc.data;
|
||||
this->data.assign(p->read.value, p->read.value + p->read.value_len);
|
||||
this->event_.gattc.gattc_param.read.value = this->data.data();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -96,9 +96,9 @@ class BLEEvent {
|
||||
esp_gattc_cb_event_t gattc_event;
|
||||
esp_gatt_if_t gattc_if;
|
||||
esp_ble_gattc_cb_param_t gattc_param;
|
||||
uint8_t data[64];
|
||||
} gattc;
|
||||
} event_;
|
||||
std::vector<uint8_t> data{};
|
||||
uint8_t type_; // 0=gap 1=gattc
|
||||
};
|
||||
|
||||
|
@@ -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,
|
||||
|
53
esphome/components/ezo/automation.h
Normal file
53
esphome/components/ezo/automation.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
#include <utility>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "ezo.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ezo {
|
||||
|
||||
class LedTrigger : public Trigger<bool> {
|
||||
public:
|
||||
explicit LedTrigger(EZOSensor *ezo) {
|
||||
ezo->add_led_state_callback([this](bool value) { this->trigger(value); });
|
||||
}
|
||||
};
|
||||
|
||||
class CustomTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
explicit CustomTrigger(EZOSensor *ezo) {
|
||||
ezo->add_custom_callback([this](std::string value) { this->trigger(std::move(value)); });
|
||||
}
|
||||
};
|
||||
|
||||
class TTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
explicit TTrigger(EZOSensor *ezo) {
|
||||
ezo->add_t_callback([this](std::string value) { this->trigger(std::move(value)); });
|
||||
}
|
||||
};
|
||||
|
||||
class CalibrationTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
explicit CalibrationTrigger(EZOSensor *ezo) {
|
||||
ezo->add_calibration_callback([this](std::string value) { this->trigger(std::move(value)); });
|
||||
}
|
||||
};
|
||||
|
||||
class SlopeTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
explicit SlopeTrigger(EZOSensor *ezo) {
|
||||
ezo->add_slope_callback([this](std::string value) { this->trigger(std::move(value)); });
|
||||
}
|
||||
};
|
||||
|
||||
class DeviceInformationTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
explicit DeviceInformationTrigger(EZOSensor *ezo) {
|
||||
ezo->add_device_infomation_callback([this](std::string value) { this->trigger(std::move(value)); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ezo
|
||||
} // namespace esphome
|
@@ -5,11 +5,11 @@
|
||||
namespace esphome {
|
||||
namespace ezo {
|
||||
|
||||
static const char *const TAG = "ezo.sensor";
|
||||
static const char *const EZO_COMMAND_TYPE_STRINGS[] = {"EZO_READ", "EZO_LED", "EZO_DEVICE_INFORMATION",
|
||||
"EZO_SLOPE", "EZO_CALIBRATION", "EZO_SLEEP",
|
||||
"EZO_I2C", "EZO_T", "EZO_CUSTOM"};
|
||||
|
||||
static const uint16_t EZO_STATE_WAIT = 1;
|
||||
static const uint16_t EZO_STATE_SEND_TEMP = 2;
|
||||
static const uint16_t EZO_STATE_WAIT_TEMP = 4;
|
||||
static const char *const EZO_CALIBRATION_TYPE_STRINGS[] = {"LOW", "MID", "HIGH"};
|
||||
|
||||
void EZOSensor::dump_config() {
|
||||
LOG_SENSOR("", "EZO", this);
|
||||
@@ -20,37 +20,75 @@ void EZOSensor::dump_config() {
|
||||
}
|
||||
|
||||
void EZOSensor::update() {
|
||||
if (this->state_ & EZO_STATE_WAIT) {
|
||||
ESP_LOGE(TAG, "update overrun, still waiting for previous response");
|
||||
// Check if a read is in there already and if not insert on in the second position
|
||||
|
||||
if (!this->commands_.empty() && this->commands_.front()->command_type != EzoCommandType::EZO_READ &&
|
||||
this->commands_.size() > 1) {
|
||||
bool found = false;
|
||||
|
||||
for (auto &i : this->commands_) {
|
||||
if (i->command_type == EzoCommandType::EZO_READ) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
std::unique_ptr<EzoCommand> ezo_command(new EzoCommand);
|
||||
ezo_command->command = "R";
|
||||
ezo_command->command_type = EzoCommandType::EZO_READ;
|
||||
ezo_command->delay_ms = 900;
|
||||
|
||||
auto it = this->commands_.begin();
|
||||
++it;
|
||||
this->commands_.insert(it, std::move(ezo_command));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
uint8_t c = 'R';
|
||||
this->write(&c, 1);
|
||||
this->state_ |= EZO_STATE_WAIT;
|
||||
this->start_time_ = millis();
|
||||
this->wait_time_ = 900;
|
||||
|
||||
this->get_state();
|
||||
}
|
||||
|
||||
void EZOSensor::loop() {
|
||||
uint8_t buf[21];
|
||||
if (!(this->state_ & EZO_STATE_WAIT)) {
|
||||
if (this->state_ & EZO_STATE_SEND_TEMP) {
|
||||
int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_);
|
||||
this->write(buf, len);
|
||||
this->state_ = EZO_STATE_WAIT | EZO_STATE_WAIT_TEMP;
|
||||
this->start_time_ = millis();
|
||||
this->wait_time_ = 300;
|
||||
if (this->commands_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
EzoCommand *to_run = this->commands_.front().get();
|
||||
|
||||
if (!to_run->command_sent) {
|
||||
const uint8_t *data = reinterpret_cast<const uint8_t *>(to_run->command.c_str());
|
||||
ESP_LOGVV(TAG, "Sending command \"%s\"", data);
|
||||
|
||||
this->write(data, to_run->command.length());
|
||||
|
||||
if (to_run->command_type == EzoCommandType::EZO_SLEEP ||
|
||||
to_run->command_type == EzoCommandType::EZO_I2C) { // Commands with no return data
|
||||
this->commands_.pop_front();
|
||||
if (to_run->command_type == EzoCommandType::EZO_I2C)
|
||||
this->address_ = this->new_address_;
|
||||
return;
|
||||
}
|
||||
|
||||
this->start_time_ = millis();
|
||||
to_run->command_sent = true;
|
||||
return;
|
||||
}
|
||||
if (millis() - this->start_time_ < this->wait_time_)
|
||||
|
||||
if (millis() - this->start_time_ < to_run->delay_ms)
|
||||
return;
|
||||
|
||||
uint8_t buf[32];
|
||||
|
||||
buf[0] = 0;
|
||||
if (!this->read_bytes_raw(buf, 20)) {
|
||||
|
||||
if (!this->read_bytes_raw(buf, 32)) {
|
||||
ESP_LOGE(TAG, "read error");
|
||||
this->state_ = 0;
|
||||
this->commands_.pop_front();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (buf[0]) {
|
||||
case 1:
|
||||
break;
|
||||
@@ -66,28 +104,142 @@ void EZOSensor::loop() {
|
||||
ESP_LOGE(TAG, "device returned an unknown response: %d", buf[0]);
|
||||
break;
|
||||
}
|
||||
if (this->state_ & EZO_STATE_WAIT_TEMP) {
|
||||
this->state_ = 0;
|
||||
return;
|
||||
}
|
||||
this->state_ &= ~EZO_STATE_WAIT;
|
||||
if (buf[0] != 1)
|
||||
return;
|
||||
|
||||
// some sensors return multiple comma-separated values, terminate string after first one
|
||||
for (size_t i = 1; i < sizeof(buf) - 1; i++) {
|
||||
if (buf[i] == ',')
|
||||
buf[i] = '\0';
|
||||
ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", buf, EZO_COMMAND_TYPE_STRINGS[to_run->command_type]);
|
||||
|
||||
if ((buf[0] == 1) || (to_run->command_type == EzoCommandType::EZO_CALIBRATION)) { // EZO_CALIBRATION returns 0-3
|
||||
// some sensors return multiple comma-separated values, terminate string after first one
|
||||
for (size_t i = 1; i < sizeof(buf) - 1; i++) {
|
||||
if (buf[i] == ',') {
|
||||
buf[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::string payload = reinterpret_cast<char *>(&buf[1]);
|
||||
if (!payload.empty()) {
|
||||
switch (to_run->command_type) {
|
||||
case EzoCommandType::EZO_READ: {
|
||||
auto val = parse_number<float>(payload);
|
||||
if (!val.has_value()) {
|
||||
ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
|
||||
} else {
|
||||
this->publish_state(*val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EzoCommandType::EZO_LED: {
|
||||
this->led_callback_.call(payload.back() == '1');
|
||||
break;
|
||||
}
|
||||
case EzoCommandType::EZO_DEVICE_INFORMATION: {
|
||||
int start_location = 0;
|
||||
if ((start_location = payload.find(',')) != std::string::npos) {
|
||||
this->device_infomation_callback_.call(payload.substr(start_location + 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EzoCommandType::EZO_SLOPE: {
|
||||
int start_location = 0;
|
||||
if ((start_location = payload.find(',')) != std::string::npos) {
|
||||
this->slope_callback_.call(payload.substr(start_location + 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EzoCommandType::EZO_CALIBRATION: {
|
||||
int start_location = 0;
|
||||
if ((start_location = payload.find(',')) != std::string::npos) {
|
||||
this->calibration_callback_.call(payload.substr(start_location + 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EzoCommandType::EZO_T: {
|
||||
this->t_callback_.call(payload);
|
||||
break;
|
||||
}
|
||||
case EzoCommandType::EZO_CUSTOM: {
|
||||
this->custom_callback_.call(payload);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float val = parse_number<float>((char *) &buf[1]).value_or(0);
|
||||
this->publish_state(val);
|
||||
this->commands_.pop_front();
|
||||
}
|
||||
|
||||
void EZOSensor::set_tempcomp_value(float temp) {
|
||||
this->tempcomp_ = temp;
|
||||
this->state_ |= EZO_STATE_SEND_TEMP;
|
||||
void EZOSensor::add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms) {
|
||||
std::unique_ptr<EzoCommand> ezo_command(new EzoCommand);
|
||||
ezo_command->command = command;
|
||||
ezo_command->command_type = command_type;
|
||||
ezo_command->delay_ms = delay_ms;
|
||||
this->commands_.push_back(std::move(ezo_command));
|
||||
};
|
||||
|
||||
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
|
||||
std::string payload = str_sprintf("Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
|
||||
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
|
||||
}
|
||||
|
||||
void EZOSensor::set_address(uint8_t address) {
|
||||
if (address > 0 && address < 128) {
|
||||
std::string payload = str_sprintf("I2C,%u", address);
|
||||
this->new_address_ = address;
|
||||
this->add_command_(payload, EzoCommandType::EZO_I2C);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid I2C address");
|
||||
}
|
||||
}
|
||||
|
||||
void EZOSensor::get_device_information() { this->add_command_("i", EzoCommandType::EZO_DEVICE_INFORMATION); }
|
||||
|
||||
void EZOSensor::set_sleep() { this->add_command_("Sleep", EzoCommandType::EZO_SLEEP); }
|
||||
|
||||
void EZOSensor::get_state() { this->add_command_("R", EzoCommandType::EZO_READ, 900); }
|
||||
|
||||
void EZOSensor::get_slope() { this->add_command_("Slope,?", EzoCommandType::EZO_SLOPE); }
|
||||
|
||||
void EZOSensor::get_t() { this->add_command_("T,?", EzoCommandType::EZO_T); }
|
||||
|
||||
void EZOSensor::set_t(float value) {
|
||||
std::string payload = str_sprintf("T,%0.2f", value);
|
||||
this->add_command_(payload, EzoCommandType::EZO_T);
|
||||
}
|
||||
|
||||
void EZOSensor::set_tempcomp_value(float temp) { this->set_t(temp); }
|
||||
|
||||
void EZOSensor::get_calibration() { this->add_command_("Cal,?", EzoCommandType::EZO_CALIBRATION); }
|
||||
|
||||
void EZOSensor::set_calibration_point_low(float value) {
|
||||
this->set_calibration_point_(EzoCalibrationType::EZO_CAL_LOW, value);
|
||||
}
|
||||
|
||||
void EZOSensor::set_calibration_point_mid(float value) {
|
||||
this->set_calibration_point_(EzoCalibrationType::EZO_CAL_MID, value);
|
||||
}
|
||||
|
||||
void EZOSensor::set_calibration_point_high(float value) {
|
||||
this->set_calibration_point_(EzoCalibrationType::EZO_CAL_HIGH, value);
|
||||
}
|
||||
|
||||
void EZOSensor::set_calibration_generic(float value) {
|
||||
std::string payload = str_sprintf("Cal,%0.2f", value);
|
||||
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
|
||||
}
|
||||
|
||||
void EZOSensor::clear_calibration() { this->add_command_("Cal,clear", EzoCommandType::EZO_CALIBRATION); }
|
||||
|
||||
void EZOSensor::get_led_state() { this->add_command_("L,?", EzoCommandType::EZO_LED); }
|
||||
|
||||
void EZOSensor::set_led_state(bool on) {
|
||||
std::string to_send = "L,";
|
||||
to_send += on ? "1" : "0";
|
||||
this->add_command_(to_send, EzoCommandType::EZO_LED);
|
||||
}
|
||||
|
||||
void EZOSensor::send_custom(const std::string &to_send) { this->add_command_(to_send, EzoCommandType::EZO_CUSTOM); }
|
||||
|
||||
} // namespace ezo
|
||||
} // namespace esphome
|
||||
|
@@ -3,10 +3,35 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include <deque>
|
||||
|
||||
namespace esphome {
|
||||
namespace ezo {
|
||||
|
||||
static const char *const TAG = "ezo.sensor";
|
||||
|
||||
enum EzoCommandType : uint8_t {
|
||||
EZO_READ = 0,
|
||||
EZO_LED = 1,
|
||||
EZO_DEVICE_INFORMATION = 2,
|
||||
EZO_SLOPE = 3,
|
||||
EZO_CALIBRATION,
|
||||
EZO_SLEEP = 4,
|
||||
EZO_I2C = 5,
|
||||
EZO_T = 6,
|
||||
EZO_CUSTOM = 7
|
||||
};
|
||||
|
||||
enum EzoCalibrationType : uint8_t { EZO_CAL_LOW = 0, EZO_CAL_MID = 1, EZO_CAL_HIGH = 2 };
|
||||
|
||||
class EzoCommand {
|
||||
public:
|
||||
std::string command;
|
||||
uint16_t delay_ms = 0;
|
||||
bool command_sent = false;
|
||||
EzoCommandType command_type;
|
||||
};
|
||||
|
||||
/// This class implements support for the EZO circuits in i2c mode
|
||||
class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
@@ -15,13 +40,71 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2
|
||||
void update() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; };
|
||||
|
||||
void set_tempcomp_value(float temp);
|
||||
// I2C
|
||||
void set_address(uint8_t address);
|
||||
|
||||
// Device Information
|
||||
void get_device_information();
|
||||
void add_device_infomation_callback(std::function<void(std::string)> &&callback) {
|
||||
this->device_infomation_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
// Sleep
|
||||
void set_sleep();
|
||||
|
||||
// R
|
||||
void get_state();
|
||||
|
||||
// Slope
|
||||
void get_slope();
|
||||
void add_slope_callback(std::function<void(std::string)> &&callback) {
|
||||
this->slope_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
// T
|
||||
void get_t();
|
||||
void set_t(float value);
|
||||
void set_tempcomp_value(float temp); // For backwards compatibility
|
||||
void add_t_callback(std::function<void(std::string)> &&callback) { this->t_callback_.add(std::move(callback)); }
|
||||
|
||||
// Calibration
|
||||
void get_calibration();
|
||||
void set_calibration_point_low(float value);
|
||||
void set_calibration_point_mid(float value);
|
||||
void set_calibration_point_high(float value);
|
||||
void set_calibration_generic(float value);
|
||||
void clear_calibration();
|
||||
void add_calibration_callback(std::function<void(std::string)> &&callback) {
|
||||
this->calibration_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
// LED
|
||||
void get_led_state();
|
||||
void set_led_state(bool on);
|
||||
void add_led_state_callback(std::function<void(bool)> &&callback) { this->led_callback_.add(std::move(callback)); }
|
||||
|
||||
// Custom
|
||||
void send_custom(const std::string &to_send);
|
||||
void add_custom_callback(std::function<void(std::string)> &&callback) {
|
||||
this->custom_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
protected:
|
||||
std::deque<std::unique_ptr<EzoCommand>> commands_;
|
||||
int new_address_;
|
||||
|
||||
void add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms = 300);
|
||||
|
||||
void set_calibration_point_(EzoCalibrationType type, float value);
|
||||
|
||||
CallbackManager<void(std::string)> device_infomation_callback_{};
|
||||
CallbackManager<void(std::string)> calibration_callback_{};
|
||||
CallbackManager<void(std::string)> slope_callback_{};
|
||||
CallbackManager<void(std::string)> t_callback_{};
|
||||
CallbackManager<void(std::string)> custom_callback_{};
|
||||
CallbackManager<void(bool)> led_callback_{};
|
||||
|
||||
uint32_t start_time_ = 0;
|
||||
uint32_t wait_time_ = 0;
|
||||
uint16_t state_ = 0;
|
||||
float tempcomp_;
|
||||
};
|
||||
|
||||
} // namespace ezo
|
||||
|
@@ -1,22 +1,81 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.const import CONF_ID, CONF_TRIGGER_ID
|
||||
|
||||
CODEOWNERS = ["@ssieb"]
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
CONF_ON_LED = "on_led"
|
||||
CONF_ON_DEVICE_INFORMATION = "on_device_information"
|
||||
CONF_ON_SLOPE = "on_slope"
|
||||
CONF_ON_CALIBRATION = "on_calibration"
|
||||
CONF_ON_T = "on_t"
|
||||
CONF_ON_CUSTOM = "on_custom"
|
||||
|
||||
ezo_ns = cg.esphome_ns.namespace("ezo")
|
||||
|
||||
EZOSensor = ezo_ns.class_(
|
||||
"EZOSensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CustomTrigger = ezo_ns.class_(
|
||||
"CustomTrigger", automation.Trigger.template(cg.std_string)
|
||||
)
|
||||
|
||||
|
||||
TTrigger = ezo_ns.class_("TTrigger", automation.Trigger.template(cg.std_string))
|
||||
|
||||
SlopeTrigger = ezo_ns.class_("SlopeTrigger", automation.Trigger.template(cg.std_string))
|
||||
|
||||
CalibrationTrigger = ezo_ns.class_(
|
||||
"CalibrationTrigger", automation.Trigger.template(cg.std_string)
|
||||
)
|
||||
|
||||
DeviceInformationTrigger = ezo_ns.class_(
|
||||
"DeviceInformationTrigger", automation.Trigger.template(cg.std_string)
|
||||
)
|
||||
|
||||
LedTrigger = ezo_ns.class_("LedTrigger", automation.Trigger.template(cg.bool_))
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EZOSensor),
|
||||
cv.Optional(CONF_ON_CUSTOM): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CustomTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_CALIBRATION): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CalibrationTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_SLOPE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SlopeTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_T): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_DEVICE_INFORMATION): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DeviceInformationTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_LED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LedTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
@@ -29,3 +88,27 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
for conf in config.get(CONF_ON_CUSTOM, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_LED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(bool, "x")], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_DEVICE_INFORMATION, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_SLOPE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_CALIBRATION, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_T, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
|
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
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user