mirror of
https://github.com/esphome/esphome.git
synced 2025-11-02 16:11:53 +00:00
Compare commits
95 Commits
jesserockz
...
2022.11.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7096ab78e | ||
|
|
98f8feb625 | ||
|
|
9944ca414e | ||
|
|
70f1c71a9f | ||
|
|
816df5ad47 | ||
|
|
7e88eea532 | ||
|
|
d1cdfd3b72 | ||
|
|
d6a03d48f5 | ||
|
|
d453b42b1a | ||
|
|
7c19b961e2 | ||
|
|
d924702825 | ||
|
|
e8784ba383 | ||
|
|
e3a454d1a6 | ||
|
|
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 | ||
|
|
fd8b9fb028 | ||
|
|
97bd3e7320 | ||
|
|
dfca2f88d3 | ||
|
|
8cad93de37 | ||
|
|
bdf1813b3a | ||
|
|
45b6c93f5f | ||
|
|
e5b8dd7f2d | ||
|
|
a1c8b8092b | ||
|
|
109ca2406d | ||
|
|
3a689112fd | ||
|
|
40e0cd0f03 | ||
|
|
bf4d3df906 | ||
|
|
0e30c49e3f | ||
|
|
e61a01f7bb | ||
|
|
f8640cf2cd | ||
|
|
4bcfeb6e33 | ||
|
|
a5d4ca0f6d | ||
|
|
85faecb2fd | ||
|
|
991fc54994 | ||
|
|
2de891dc32 | ||
|
|
9865cb7f55 | ||
|
|
f97252b93a | ||
|
|
6124531479 | ||
|
|
b8549d323c | ||
|
|
01adece673 | ||
|
|
0220934e4c | ||
|
|
ca09693efa | ||
|
|
e96d7483b3 | ||
|
|
f2c4f018de | ||
|
|
237c7dd169 | ||
|
|
b781b8d77d | ||
|
|
60b7d1c8a1 | ||
|
|
8161222b33 | ||
|
|
1000c4466f | ||
|
|
60717b074e | ||
|
|
c3fba97b4c | ||
|
|
d93f35701f | ||
|
|
702b60ce66 | ||
|
|
f8ce597918 | ||
|
|
22e0a944c8 | ||
|
|
3a134ef009 | ||
|
|
96e8cb66b6 | ||
|
|
615288c151 | ||
|
|
6153bcc6ad | ||
|
|
e87edcc77a | ||
|
|
a21c3e8e2d |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ESPHome Dev",
|
"name": "ESPHome Dev",
|
||||||
"image": "esphome/esphome-lint:dev",
|
"image": "ghcr.io/esphome/esphome-lint:dev",
|
||||||
"postCreateCommand": [
|
"postCreateCommand": [
|
||||||
"script/devcontainer-post-create"
|
"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?
|
# What does this implement/fix?
|
||||||
|
|
||||||
Quick description and explanation of changes
|
<!-- Quick description and explanation of changes -->
|
||||||
|
|
||||||
## Types of changes
|
## Types of changes
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ Quick description and explanation of changes
|
|||||||
- [ ] ESP32
|
- [ ] ESP32
|
||||||
- [ ] ESP32 IDF
|
- [ ] ESP32 IDF
|
||||||
- [ ] ESP8266
|
- [ ] ESP8266
|
||||||
|
- [ ] RP2040
|
||||||
|
|
||||||
## Example entry for `config.yaml`:
|
## Example entry for `config.yaml`:
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -48,6 +48,10 @@ jobs:
|
|||||||
file: tests/test5.yaml
|
file: tests/test5.yaml
|
||||||
name: Test tests/test5.yaml
|
name: Test tests/test5.yaml
|
||||||
pio_cache_key: test5
|
pio_cache_key: test5
|
||||||
|
- id: test
|
||||||
|
file: tests/test6.yaml
|
||||||
|
name: Test tests/test6.yaml
|
||||||
|
pio_cache_key: test6
|
||||||
- id: pytest
|
- id: pytest
|
||||||
name: Run pytest
|
name: Run pytest
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
|||||||
today="$(date --utc '+%Y%m%d')"
|
today="$(date --utc '+%Y%m%d')"
|
||||||
TAG="${TAG}${today}"
|
TAG="${TAG}${today}"
|
||||||
fi
|
fi
|
||||||
echo "::set-output name=tag::${TAG}"
|
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||||
# yamllint enable rule:line-length
|
# yamllint enable rule:line-length
|
||||||
|
|
||||||
deploy-pypi:
|
deploy-pypi:
|
||||||
|
|||||||
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v5
|
- uses: actions/stale@v6
|
||||||
with:
|
with:
|
||||||
days-before-pr-stale: 90
|
days-before-pr-stale: 90
|
||||||
days-before-pr-close: 7
|
days-before-pr-close: 7
|
||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
close-issues:
|
close-issues:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v5
|
- uses: actions/stale@v6
|
||||||
with:
|
with:
|
||||||
days-before-pr-stale: -1
|
days-before-pr-stale: -1
|
||||||
days-before-pr-close: -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
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/ambv/black
|
- repo: https://github.com/ambv/black
|
||||||
rev: 22.6.0
|
rev: 22.10.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args:
|
args:
|
||||||
- --safe
|
- --safe
|
||||||
- --quiet
|
- --quiet
|
||||||
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
|
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
|
||||||
- repo: https://gitlab.com/pycqa/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 4.0.1
|
rev: 5.0.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
@@ -27,7 +27,7 @@ repos:
|
|||||||
- --branch=release
|
- --branch=release
|
||||||
- --branch=beta
|
- --branch=beta
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v3.0.0
|
rev: v3.2.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py39-plus]
|
args: [--py39-plus]
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ esphome/components/debug/* @OttoWinter
|
|||||||
esphome/components/delonghi/* @grob6000
|
esphome/components/delonghi/* @grob6000
|
||||||
esphome/components/dfplayer/* @glmnet
|
esphome/components/dfplayer/* @glmnet
|
||||||
esphome/components/dht/* @OttoWinter
|
esphome/components/dht/* @OttoWinter
|
||||||
|
esphome/components/display_menu_base/* @numo68
|
||||||
esphome/components/dps310/* @kbx81
|
esphome/components/dps310/* @kbx81
|
||||||
esphome/components/ds1307/* @badbadc0ffee
|
esphome/components/ds1307/* @badbadc0ffee
|
||||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||||
@@ -110,6 +111,7 @@ esphome/components/integration/* @OttoWinter
|
|||||||
esphome/components/interval/* @esphome/core
|
esphome/components/interval/* @esphome/core
|
||||||
esphome/components/json/* @OttoWinter
|
esphome/components/json/* @OttoWinter
|
||||||
esphome/components/kalman_combinator/* @Cat-Ion
|
esphome/components/kalman_combinator/* @Cat-Ion
|
||||||
|
esphome/components/lcd_menu/* @numo68
|
||||||
esphome/components/ledc/* @OttoWinter
|
esphome/components/ledc/* @OttoWinter
|
||||||
esphome/components/light/* @esphome/core
|
esphome/components/light/* @esphome/core
|
||||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
||||||
@@ -184,6 +186,8 @@ esphome/components/rc522_spi/* @glmnet
|
|||||||
esphome/components/restart/* @esphome/core
|
esphome/components/restart/* @esphome/core
|
||||||
esphome/components/rf_bridge/* @jesserockz
|
esphome/components/rf_bridge/* @jesserockz
|
||||||
esphome/components/rgbct/* @jesserockz
|
esphome/components/rgbct/* @jesserockz
|
||||||
|
esphome/components/rp2040/* @jesserockz
|
||||||
|
esphome/components/rp2040_pwm/* @jesserockz
|
||||||
esphome/components/rtttl/* @glmnet
|
esphome/components/rtttl/* @glmnet
|
||||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
||||||
esphome/components/scd4x/* @martgras @sjtrny
|
esphome/components/scd4x/* @martgras @sjtrny
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ RUN \
|
|||||||
# Ubuntu python3-pip is missing wheel
|
# Ubuntu python3-pip is missing wheel
|
||||||
pip3 install --no-cache-dir \
|
pip3 install --no-cache-dir \
|
||||||
wheel==0.37.1 \
|
wheel==0.37.1 \
|
||||||
platformio==6.0.2 \
|
platformio==6.1.5 \
|
||||||
# Change some platformio settings
|
# Change some platformio settings
|
||||||
&& platformio settings set enable_telemetry No \
|
&& platformio settings set enable_telemetry No \
|
||||||
&& platformio settings set check_platformio_interval 1000000 \
|
&& platformio settings set check_platformio_interval 1000000 \
|
||||||
@@ -94,7 +94,7 @@ RUN \
|
|||||||
apt-get update \
|
apt-get update \
|
||||||
# Use pinned versions so that we get updates with build caching
|
# Use pinned versions so that we get updates with build caching
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
nginx-light=1.18.0-6.1+deb11u2 \
|
nginx-light=1.18.0-6.1+deb11u3 \
|
||||||
&& rm -rf \
|
&& rm -rf \
|
||||||
/tmp/* \
|
/tmp/* \
|
||||||
/var/{cache,log}/* \
|
/var/{cache,log}/* \
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from esphome import const, writer, yaml_util
|
from esphome import const, writer, yaml_util
|
||||||
@@ -22,6 +23,9 @@ from esphome.const import (
|
|||||||
CONF_ESPHOME,
|
CONF_ESPHOME,
|
||||||
CONF_PLATFORMIO_OPTIONS,
|
CONF_PLATFORMIO_OPTIONS,
|
||||||
CONF_SUBSTITUTIONS,
|
CONF_SUBSTITUTIONS,
|
||||||
|
PLATFORM_ESP32,
|
||||||
|
PLATFORM_ESP8266,
|
||||||
|
PLATFORM_RP2040,
|
||||||
SECRETS_FILES,
|
SECRETS_FILES,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, EsphomeError, coroutine
|
from esphome.core import CORE, EsphomeError, coroutine
|
||||||
@@ -101,11 +105,11 @@ def run_miniterm(config, port):
|
|||||||
|
|
||||||
if CONF_LOGGER not in config:
|
if CONF_LOGGER not in config:
|
||||||
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
|
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
|
||||||
return
|
return 1
|
||||||
baud_rate = config["logger"][CONF_BAUD_RATE]
|
baud_rate = config["logger"][CONF_BAUD_RATE]
|
||||||
if baud_rate == 0:
|
if baud_rate == 0:
|
||||||
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
|
_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)
|
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
|
||||||
|
|
||||||
backtrace_state = False
|
backtrace_state = False
|
||||||
@@ -119,25 +123,34 @@ def run_miniterm(config, port):
|
|||||||
ser.dtr = False
|
ser.dtr = False
|
||||||
ser.rts = False
|
ser.rts = False
|
||||||
|
|
||||||
with ser:
|
tries = 0
|
||||||
while True:
|
while tries < 5:
|
||||||
try:
|
try:
|
||||||
raw = ser.readline()
|
with ser:
|
||||||
except serial.SerialException:
|
while True:
|
||||||
_LOGGER.error("Serial port closed!")
|
try:
|
||||||
return
|
raw = ser.readline()
|
||||||
line = (
|
except serial.SerialException:
|
||||||
raw.replace(b"\r", b"")
|
_LOGGER.error("Serial port closed!")
|
||||||
.replace(b"\n", b"")
|
return 0
|
||||||
.decode("utf8", "backslashreplace")
|
line = (
|
||||||
)
|
raw.replace(b"\r", b"")
|
||||||
time = datetime.now().time().strftime("[%H:%M:%S]")
|
.replace(b"\n", b"")
|
||||||
message = time + line
|
.decode("utf8", "backslashreplace")
|
||||||
safe_print(message)
|
)
|
||||||
|
time_str = datetime.now().time().strftime("[%H:%M:%S]")
|
||||||
|
message = time_str + line
|
||||||
|
safe_print(message)
|
||||||
|
|
||||||
backtrace_state = platformio_api.process_stacktrace(
|
backtrace_state = platformio_api.process_stacktrace(
|
||||||
config, line, backtrace_state=backtrace_state
|
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):
|
def wrap_to_code(name, comp):
|
||||||
@@ -258,9 +271,21 @@ def upload_using_esptool(config, port):
|
|||||||
|
|
||||||
|
|
||||||
def upload_program(config, args, host):
|
def upload_program(config, args, host):
|
||||||
# if upload is to a serial port use platformio, otherwise assume ota
|
|
||||||
if get_port_type(host) == "SERIAL":
|
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
|
from esphome import espota2
|
||||||
|
|
||||||
@@ -280,8 +305,7 @@ def show_logs(config, args, port):
|
|||||||
if "logger" not in config:
|
if "logger" not in config:
|
||||||
raise EsphomeError("Logger is not configured!")
|
raise EsphomeError("Logger is not configured!")
|
||||||
if get_port_type(port) == "SERIAL":
|
if get_port_type(port) == "SERIAL":
|
||||||
run_miniterm(config, port)
|
return run_miniterm(config, port)
|
||||||
return 0
|
|
||||||
if get_port_type(port) == "NETWORK" and "api" in config:
|
if get_port_type(port) == "NETWORK" and "api" in config:
|
||||||
from esphome.components.api.client import run_logs
|
from esphome.components.api.client import run_logs
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ ADC_MODE(ADC_VCC)
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
#include <hardware/adc.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace adc {
|
namespace adc {
|
||||||
|
|
||||||
@@ -32,9 +36,13 @@ static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1; // 4095 (12 b
|
|||||||
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; // 2048 (12 bit) or 4096 (13 bit)
|
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; // 2048 (12 bit) or 4096 (13 bit)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void ADCSensor::setup() {
|
#ifdef USE_RP2040
|
||||||
|
extern "C"
|
||||||
|
#endif
|
||||||
|
void
|
||||||
|
ADCSensor::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
||||||
#ifndef USE_ADC_SENSOR_VCC
|
#if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040)
|
||||||
pin_->setup();
|
pin_->setup();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -63,6 +71,16 @@ void ADCSensor::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
static bool initialized = false;
|
||||||
|
if (!initialized) {
|
||||||
|
adc_init();
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADCSensor::dump_config() {
|
void ADCSensor::dump_config() {
|
||||||
@@ -98,6 +116,12 @@ void ADCSensor::dump_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
if (this->is_temperature_)
|
||||||
|
ESP_LOGCONFIG(TAG, " Pin: Temperature");
|
||||||
|
else
|
||||||
|
LOG_PIN(" Pin: ", pin_);
|
||||||
|
#endif
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +199,29 @@ float ADCSensor::sample() {
|
|||||||
}
|
}
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
float ADCSensor::sample() {
|
||||||
|
if (this->is_temperature_) {
|
||||||
|
adc_set_temp_sensor_enabled(true);
|
||||||
|
delay(1);
|
||||||
|
adc_select_input(4);
|
||||||
|
} else {
|
||||||
|
uint8_t pin = this->pin_->get_pin();
|
||||||
|
adc_gpio_init(pin);
|
||||||
|
adc_select_input(pin - 26);
|
||||||
|
}
|
||||||
|
|
||||||
|
int raw = adc_read();
|
||||||
|
if (this->is_temperature_) {
|
||||||
|
adc_set_temp_sensor_enabled(false);
|
||||||
|
}
|
||||||
|
if (output_raw_) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
return raw * 3.3f / 4096.0f;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
|
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -38,10 +38,18 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
|||||||
std::string unique_id() override;
|
std::string unique_id() override;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
void set_is_temperature() { is_temperature_ = true; }
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
InternalGPIOPin *pin_;
|
InternalGPIOPin *pin_;
|
||||||
bool output_raw_{false};
|
bool output_raw_{false};
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
bool is_temperature_{false};
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
|
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
|
||||||
adc1_channel_t channel_{};
|
adc1_channel_t channel_{};
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ def validate_adc_pin(value):
|
|||||||
if str(value).upper() == "VCC":
|
if str(value).upper() == "VCC":
|
||||||
return cv.only_on_esp8266("VCC")
|
return cv.only_on_esp8266("VCC")
|
||||||
|
|
||||||
|
if str(value).upper() == "TEMPERATURE":
|
||||||
|
return cv.only_on_rp2040("TEMPERATURE")
|
||||||
|
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
value = pins.internal_gpio_input_pin_number(value)
|
value = pins.internal_gpio_input_pin_number(value)
|
||||||
variant = get_esp32_variant()
|
variant = get_esp32_variant()
|
||||||
@@ -117,6 +120,12 @@ def validate_adc_pin(value):
|
|||||||
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
|
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
|
||||||
)(value)
|
)(value)
|
||||||
|
|
||||||
|
if CORE.is_rp2040:
|
||||||
|
value = pins.internal_gpio_input_pin_number(value)
|
||||||
|
if value not in (26, 27, 28, 29):
|
||||||
|
raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.")
|
||||||
|
return pins.internal_gpio_input_pin_schema(value)
|
||||||
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@@ -160,6 +169,8 @@ async def to_code(config):
|
|||||||
|
|
||||||
if config[CONF_PIN] == "VCC":
|
if config[CONF_PIN] == "VCC":
|
||||||
cg.add_define("USE_ADC_SENSOR_VCC")
|
cg.add_define("USE_ADC_SENSOR_VCC")
|
||||||
|
elif config[CONF_PIN] == "TEMPERATURE":
|
||||||
|
cg.add(var.set_is_temperature())
|
||||||
else:
|
else:
|
||||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||||
cg.add(var.set_pin(pin))
|
cg.add(var.set_pin(pin))
|
||||||
|
|||||||
@@ -204,6 +204,8 @@ message DeviceInfoResponse {
|
|||||||
uint32 webserver_port = 10;
|
uint32 webserver_port = 10;
|
||||||
|
|
||||||
uint32 bluetooth_proxy_version = 11;
|
uint32 bluetooth_proxy_version = 11;
|
||||||
|
|
||||||
|
string manufacturer = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListEntitiesRequest {
|
message ListEntitiesRequest {
|
||||||
|
|||||||
@@ -932,6 +932,11 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
|||||||
resp.mac_address = get_mac_address_pretty();
|
resp.mac_address = get_mac_address_pretty();
|
||||||
resp.esphome_version = ESPHOME_VERSION;
|
resp.esphome_version = ESPHOME_VERSION;
|
||||||
resp.compilation_time = App.get_compilation_time();
|
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;
|
resp.model = ESPHOME_BOARD;
|
||||||
#ifdef USE_DEEP_SLEEP
|
#ifdef USE_DEEP_SLEEP
|
||||||
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
|
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
|
||||||
|
|||||||
@@ -576,6 +576,10 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
|
|||||||
this->project_version = value.as_string();
|
this->project_version = value.as_string();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 12: {
|
||||||
|
this->manufacturer = value.as_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -592,6 +596,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_string(9, this->project_version);
|
buffer.encode_string(9, this->project_version);
|
||||||
buffer.encode_uint32(10, this->webserver_port);
|
buffer.encode_uint32(10, this->webserver_port);
|
||||||
buffer.encode_uint32(11, this->bluetooth_proxy_version);
|
buffer.encode_uint32(11, this->bluetooth_proxy_version);
|
||||||
|
buffer.encode_string(12, this->manufacturer);
|
||||||
}
|
}
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||||
@@ -642,6 +647,10 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
|||||||
sprintf(buffer, "%u", this->bluetooth_proxy_version);
|
sprintf(buffer, "%u", this->bluetooth_proxy_version);
|
||||||
out.append(buffer);
|
out.append(buffer);
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" manufacturer: ");
|
||||||
|
out.append("'").append(this->manufacturer).append("'");
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -273,6 +273,7 @@ class DeviceInfoResponse : public ProtoMessage {
|
|||||||
std::string project_version{};
|
std::string project_version{};
|
||||||
uint32_t webserver_port{0};
|
uint32_t webserver_port{0};
|
||||||
uint32_t bluetooth_proxy_version{0};
|
uint32_t bluetooth_proxy_version{0};
|
||||||
|
std::string manufacturer{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class APIServer : public Component, public Controller {
|
|||||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call);
|
void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call);
|
||||||
void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error = ESP_OK);
|
void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK);
|
||||||
void send_bluetooth_connections_free(uint8_t free, uint8_t limit);
|
void send_bluetooth_connections_free(uint8_t free, uint8_t limit);
|
||||||
void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call);
|
void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call);
|
||||||
void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call);
|
void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ CODEOWNERS = ["@OttoWinter"]
|
|||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema({}),
|
cv.Schema({}),
|
||||||
cv.only_with_arduino,
|
cv.only_with_arduino,
|
||||||
|
cv.only_on(["esp32", "esp8266"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -47,11 +47,12 @@ void BLEClient::set_enabled(bool enabled) {
|
|||||||
this->enabled = enabled;
|
this->enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
bool BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
||||||
esp_ble_gattc_cb_param_t *param) {
|
esp_ble_gattc_cb_param_t *param) {
|
||||||
bool all_established = this->all_nodes_established_();
|
bool all_established = this->all_nodes_established_();
|
||||||
|
|
||||||
BLEClientBase::gattc_event_handler(event, esp_gattc_if, param);
|
if (!BLEClientBase::gattc_event_handler(event, esp_gattc_if, param))
|
||||||
|
return false;
|
||||||
|
|
||||||
for (auto *node : this->nodes_)
|
for (auto *node : this->nodes_)
|
||||||
node->gattc_event_handler(event, esp_gattc_if, param);
|
node->gattc_event_handler(event, esp_gattc_if, param);
|
||||||
@@ -62,6 +63,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
|||||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
this->services_.clear();
|
this->services_.clear();
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class BLEClient : public BLEClientBase {
|
|||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
esp_ble_gattc_cb_param_t *param) override;
|
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 gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||||
|
|||||||
@@ -7,19 +7,56 @@ AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
|
|||||||
DEPENDENCIES = ["api", "esp32"]
|
DEPENDENCIES = ["api", "esp32"]
|
||||||
CODEOWNERS = ["@jesserockz"]
|
CODEOWNERS = ["@jesserockz"]
|
||||||
|
|
||||||
|
CONF_CONNECTIONS = "connections"
|
||||||
|
MAX_CONNECTIONS = 3
|
||||||
|
|
||||||
bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy")
|
bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy")
|
||||||
|
|
||||||
BluetoothProxy = bluetooth_proxy_ns.class_(
|
BluetoothProxy = bluetooth_proxy_ns.class_(
|
||||||
"BluetoothProxy", esp32_ble_client.BLEClientBase
|
"BluetoothProxy", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
|
||||||
|
)
|
||||||
|
BluetoothConnection = bluetooth_proxy_ns.class_(
|
||||||
|
"BluetoothConnection", esp32_ble_client.BLEClientBase
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
CONNECTION_SCHEMA = esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(BluetoothProxy),
|
cv.GenerateID(): cv.declare_id(BluetoothConnection),
|
||||||
cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
|
|
||||||
}
|
}
|
||||||
).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):
|
async def to_code(config):
|
||||||
@@ -27,7 +64,12 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
cg.add(var.set_active(config[CONF_ACTIVE]))
|
cg.add(var.set_active(config[CONF_ACTIVE]))
|
||||||
|
await esp32_ble_tracker.register_ble_device(var, config)
|
||||||
|
|
||||||
await esp32_ble_tracker.register_client(var, config)
|
for connection_conf in config.get(CONF_CONNECTIONS, []):
|
||||||
|
connection_var = cg.new_Pvariable(connection_conf[CONF_ID])
|
||||||
|
await cg.register_component(connection_var, connection_conf)
|
||||||
|
cg.add(var.register_connection(connection_var))
|
||||||
|
await esp32_ble_tracker.register_client(connection_var, connection_conf)
|
||||||
|
|
||||||
cg.add_define("USE_BLUETOOTH_PROXY")
|
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
|
||||||
@@ -11,13 +11,7 @@ namespace bluetooth_proxy {
|
|||||||
|
|
||||||
static const char *const TAG = "bluetooth_proxy";
|
static const char *const TAG = "bluetooth_proxy";
|
||||||
|
|
||||||
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
|
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
|
||||||
static const esp_err_t ESP_GATT_WRONG_ADDRESS = -2;
|
|
||||||
|
|
||||||
BluetoothProxy::BluetoothProxy() {
|
|
||||||
global_bluetooth_proxy = this;
|
|
||||||
this->address_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||||
if (!api::global_api_server->is_connected())
|
if (!api::global_api_server->is_connected())
|
||||||
@@ -26,10 +20,6 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
|
|||||||
device.get_rssi());
|
device.get_rssi());
|
||||||
this->send_api_packet_(device);
|
this->send_api_packet_(device);
|
||||||
|
|
||||||
if (this->address_ == 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
BLEClientBase::parse_device(device);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,167 +47,101 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
|
|||||||
api::global_api_server->send_bluetooth_le_advertisement(resp);
|
api::global_api_server->send_bluetooth_le_advertisement(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
void BluetoothProxy::dump_config() {
|
||||||
esp_ble_gattc_cb_param_t *param) {
|
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
|
||||||
BLEClientBase::gattc_event_handler(event, gattc_if, param);
|
ESP_LOGCONFIG(TAG, " Active: %s", YESNO(this->active_));
|
||||||
switch (event) {
|
}
|
||||||
case ESP_GATTC_DISCONNECT_EVT: {
|
|
||||||
api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_,
|
void BluetoothProxy::loop() {
|
||||||
param->disconnect.reason);
|
if (!api::global_api_server->is_connected()) {
|
||||||
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
|
for (auto *connection : this->connections_) {
|
||||||
this->get_bluetooth_connections_limit());
|
if (connection->get_address() != 0) {
|
||||||
this->address_ = 0;
|
connection->disconnect();
|
||||||
}
|
|
||||||
case ESP_GATTC_OPEN_EVT: {
|
|
||||||
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, this->mtu_, param->open.status);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
return;
|
||||||
api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
|
}
|
||||||
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
|
for (auto *connection : this->connections_) {
|
||||||
this->get_bluetooth_connections_limit());
|
if (connection->send_service_ == connection->services_.size()) {
|
||||||
break;
|
connection->send_service_ = -1;
|
||||||
}
|
api::global_api_server->send_bluetooth_gatt_services_done(connection->get_address());
|
||||||
case ESP_GATTC_READ_DESCR_EVT:
|
} else if (connection->send_service_ >= 0) {
|
||||||
case ESP_GATTC_READ_CHAR_EVT: {
|
auto &service = connection->services_[connection->send_service_];
|
||||||
if (param->read.conn_id != this->conn_id_)
|
api::BluetoothGATTGetServicesResponse resp;
|
||||||
break;
|
resp.address = connection->get_address();
|
||||||
if (param->read.status != ESP_GATT_OK) {
|
api::BluetoothGATTService service_resp;
|
||||||
ESP_LOGW(TAG, "Error reading char/descriptor at handle 0x%2X, status=%d", param->read.handle,
|
service_resp.uuid = {service->uuid.get_128bit_high(), service->uuid.get_128bit_low()};
|
||||||
param->read.status);
|
service_resp.handle = service->start_handle;
|
||||||
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->read.handle, param->read.status);
|
for (auto &characteristic : service->characteristics) {
|
||||||
break;
|
api::BluetoothGATTCharacteristic characteristic_resp;
|
||||||
|
characteristic_resp.uuid = {characteristic->uuid.get_128bit_high(), characteristic->uuid.get_128bit_low()};
|
||||||
|
characteristic_resp.handle = characteristic->handle;
|
||||||
|
characteristic_resp.properties = characteristic->properties;
|
||||||
|
for (auto &descriptor : characteristic->descriptors) {
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
api::BluetoothGATTReadResponse resp;
|
resp.services.push_back(std::move(service_resp));
|
||||||
resp.address = this->address_;
|
api::global_api_server->send_bluetooth_gatt_services(resp);
|
||||||
resp.handle = param->read.handle;
|
connection->send_service_++;
|
||||||
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, "Error writing char/descriptor at handle 0x%2X, status=%d", 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 (param->unreg_for_notify.status != ESP_GATT_OK) {
|
|
||||||
ESP_LOGW(TAG, "Error unregistering notifications for handle 0x%2X, status=%d", 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 (param->reg_for_notify.status != ESP_GATT_OK) {
|
|
||||||
ESP_LOGW(TAG, "Error registering notifications for handle 0x%2X, status=%d", 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, "ESP_GATTC_NOTIFY_EVT: handle=0x%2X", 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); }
|
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
|
||||||
|
for (auto *connection : this->connections_) {
|
||||||
|
if (connection->get_address() == address)
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
void BluetoothProxy::loop() {
|
if (!reserve)
|
||||||
BLEClientBase::loop();
|
return nullptr;
|
||||||
if (this->state_ != espbt::ClientState::IDLE && !api::global_api_server->is_connected()) {
|
|
||||||
ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str());
|
for (auto *connection : this->connections_) {
|
||||||
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
|
if (connection->get_address() == 0) {
|
||||||
if (err != ERR_OK) {
|
connection->set_address(address);
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err);
|
return connection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->send_service_ == this->services_.size()) {
|
return nullptr;
|
||||||
this->send_service_ = -1;
|
|
||||||
api::global_api_server->send_bluetooth_gatt_services_done(this->address_);
|
|
||||||
} else if (this->send_service_ >= 0) {
|
|
||||||
auto &service = this->services_[this->send_service_];
|
|
||||||
api::BluetoothGATTGetServicesResponse resp;
|
|
||||||
resp.address = this->address_;
|
|
||||||
api::BluetoothGATTService service_resp;
|
|
||||||
service_resp.uuid = {service->uuid.get_128bit_high(), service->uuid.get_128bit_low()};
|
|
||||||
service_resp.handle = service->start_handle;
|
|
||||||
for (auto &characteristic : service->characteristics) {
|
|
||||||
api::BluetoothGATTCharacteristic characteristic_resp;
|
|
||||||
characteristic_resp.uuid = {characteristic->uuid.get_128bit_high(), characteristic->uuid.get_128bit_low()};
|
|
||||||
characteristic_resp.handle = characteristic->handle;
|
|
||||||
characteristic_resp.properties = characteristic->properties;
|
|
||||||
for (auto &descriptor : characteristic->descriptors) {
|
|
||||||
api::BluetoothGATTDescriptor descriptor_resp;
|
|
||||||
descriptor_resp.uuid = {descriptor->uuid.get_128bit_high(), descriptor->uuid.get_128bit_low()};
|
|
||||||
descriptor_resp.handle = descriptor->handle;
|
|
||||||
characteristic_resp.descriptors.push_back(std::move(descriptor_resp));
|
|
||||||
}
|
|
||||||
service_resp.characteristics.push_back(std::move(characteristic_resp));
|
|
||||||
}
|
|
||||||
resp.services.push_back(std::move(service_resp));
|
|
||||||
api::global_api_server->send_bluetooth_gatt_services(resp);
|
|
||||||
this->send_service_++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
|
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
|
||||||
switch (msg.request_type) {
|
switch (msg.request_type) {
|
||||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
|
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
|
||||||
this->address_ = msg.address;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: {
|
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: {
|
||||||
if (this->state() != espbt::ClientState::IDLE) {
|
auto *connection = this->get_connection_(msg.address, false);
|
||||||
ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str());
|
if (connection == nullptr) {
|
||||||
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
|
api::global_api_server->send_bluetooth_device_connection(msg.address, false);
|
||||||
if (err != ERR_OK) {
|
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
@@ -228,170 +152,88 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
|
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
|
||||||
if (this->state_ != espbt::ClientState::ESTABLISHED) {
|
auto *connection = this->get_connection_(msg.address, false);
|
||||||
ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected.");
|
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);
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->address_ != msg.address) {
|
|
||||||
ESP_LOGW(TAG, "Address mismatch for read GATT characteristic request");
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *characteristic = this->get_characteristic(msg.handle);
|
auto err = connection->read_characteristic(msg.handle);
|
||||||
if (characteristic == nullptr) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "Cannot read GATT characteristic, not found.");
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Reading GATT characteristic %s", characteristic->uuid.to_string().c_str());
|
|
||||||
|
|
||||||
esp_err_t err =
|
|
||||||
esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, characteristic->handle, ESP_GATT_AUTH_REQ_NONE);
|
|
||||||
if (err != ERR_OK) {
|
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err);
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) {
|
void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) {
|
||||||
if (this->state_ != espbt::ClientState::ESTABLISHED) {
|
auto *connection = this->get_connection_(msg.address, false);
|
||||||
ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected.");
|
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);
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->address_ != msg.address) {
|
|
||||||
ESP_LOGW(TAG, "Address mismatch for write GATT characteristic request");
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *characteristic = this->get_characteristic(msg.handle);
|
auto err = connection->write_characteristic(msg.handle, msg.data, msg.response);
|
||||||
if (characteristic == nullptr) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "Cannot write GATT characteristic, not found.");
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Writing GATT characteristic %s", characteristic->uuid.to_string().c_str());
|
|
||||||
auto err = characteristic->write_value((uint8_t *) msg.data.data(), msg.data.size(),
|
|
||||||
msg.response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP);
|
|
||||||
if (err != ERR_OK) {
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) {
|
void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) {
|
||||||
if (this->state_ != espbt::ClientState::ESTABLISHED) {
|
auto *connection = this->get_connection_(msg.address, false);
|
||||||
ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not connected.");
|
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);
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->address_ != msg.address) {
|
|
||||||
ESP_LOGW(TAG, "Address mismatch for read GATT characteristic descriptor request");
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *descriptor = this->get_descriptor(msg.handle);
|
auto err = connection->read_descriptor(msg.handle);
|
||||||
if (descriptor == nullptr) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not found.");
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Reading GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(),
|
|
||||||
descriptor->uuid.to_string().c_str());
|
|
||||||
|
|
||||||
esp_err_t err =
|
|
||||||
esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, ESP_GATT_AUTH_REQ_NONE);
|
|
||||||
if (err != ERR_OK) {
|
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err);
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) {
|
void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) {
|
||||||
if (this->state_ != espbt::ClientState::ESTABLISHED) {
|
auto *connection = this->get_connection_(msg.address, false);
|
||||||
ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not connected.");
|
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);
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->address_ != msg.address) {
|
|
||||||
ESP_LOGW(TAG, "Address mismatch for write GATT characteristic descriptor request");
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *descriptor = this->get_descriptor(msg.handle);
|
auto err = connection->write_descriptor(msg.handle, msg.data);
|
||||||
if (descriptor == nullptr) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not found.");
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Writing GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(),
|
|
||||||
descriptor->uuid.to_string().c_str());
|
|
||||||
|
|
||||||
esp_err_t err =
|
|
||||||
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, msg.data.size(),
|
|
||||||
(uint8_t *) msg.data.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
|
||||||
if (err != ERR_OK) {
|
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, err=%d", err);
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) {
|
void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) {
|
||||||
if (this->state_ != espbt::ClientState::ESTABLISHED) {
|
auto *connection = this->get_connection_(msg.address, false);
|
||||||
ESP_LOGW(TAG, "Cannot get GATT services, not connected.");
|
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);
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->address_ != msg.address) {
|
if (connection->services_.empty()) {
|
||||||
ESP_LOGW(TAG, "Address mismatch for service list request");
|
ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str());
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_WRONG_ADDRESS);
|
api::global_api_server->send_bluetooth_gatt_services_done(msg.address);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->send_service_ = 0;
|
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) {
|
void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) {
|
||||||
if (this->state_ != espbt::ClientState::ESTABLISHED) {
|
auto *connection = this->get_connection_(msg.address, false);
|
||||||
ESP_LOGW(TAG, "Cannot configure notify, not connected.");
|
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);
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->address_ != msg.address) {
|
auto err = connection->notify_characteristic(msg.handle, msg.enable);
|
||||||
ESP_LOGW(TAG, "Address mismatch for notify");
|
if (err != ESP_OK) {
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_WRONG_ADDRESS);
|
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *characteristic = this->get_characteristic(msg.handle);
|
|
||||||
|
|
||||||
if (characteristic == nullptr) {
|
|
||||||
ESP_LOGW(TAG, "Cannot notify GATT characteristic, not found.");
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_INVALID_HANDLE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t err;
|
|
||||||
if (msg.enable) {
|
|
||||||
err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, err=%d", err);
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_unregister_for_notify failed, err=%d", err);
|
|
||||||
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,22 +11,26 @@
|
|||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
#include <map>
|
#include "bluetooth_connection.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace bluetooth_proxy {
|
namespace bluetooth_proxy {
|
||||||
|
|
||||||
|
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
|
||||||
|
|
||||||
using namespace esp32_ble_client;
|
using namespace esp32_ble_client;
|
||||||
|
|
||||||
class BluetoothProxy : public BLEClientBase {
|
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||||
public:
|
public:
|
||||||
BluetoothProxy();
|
BluetoothProxy();
|
||||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
void register_connection(BluetoothConnection *connection) {
|
||||||
esp_ble_gattc_cb_param_t *param) override;
|
this->connections_.push_back(connection);
|
||||||
|
connection->proxy_ = this;
|
||||||
|
}
|
||||||
|
|
||||||
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
|
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
|
||||||
void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg);
|
void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg);
|
||||||
@@ -36,8 +40,16 @@ class BluetoothProxy : public BLEClientBase {
|
|||||||
void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg);
|
void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg);
|
||||||
void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg);
|
void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg);
|
||||||
|
|
||||||
int get_bluetooth_connections_free() { return this->state_ == espbt::ClientState::IDLE ? 1 : 0; }
|
int get_bluetooth_connections_free() {
|
||||||
int get_bluetooth_connections_limit() { return 1; }
|
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; }
|
void set_active(bool active) { this->active_ = active; }
|
||||||
bool has_active() { return this->active_; }
|
bool has_active() { return this->active_; }
|
||||||
@@ -45,8 +57,12 @@ class BluetoothProxy : public BLEClientBase {
|
|||||||
protected:
|
protected:
|
||||||
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
|
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
|
||||||
|
|
||||||
|
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
|
||||||
|
|
||||||
int16_t send_service_{-1};
|
int16_t send_service_{-1};
|
||||||
bool active_;
|
bool active_;
|
||||||
|
|
||||||
|
std::vector<BluetoothConnection *> connections_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.final_validate as fv
|
|
||||||
from esphome.components import web_server_base
|
from esphome.components import web_server_base
|
||||||
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
|
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
|
||||||
from esphome.const import CONF_ID, CONF_NETWORKS, CONF_PASSWORD, CONF_SSID, CONF_WIFI
|
from esphome.const import CONF_ID
|
||||||
from esphome.core import coroutine_with_priority, CORE
|
from esphome.core import coroutine_with_priority, CORE
|
||||||
|
|
||||||
AUTO_LOAD = ["web_server_base"]
|
AUTO_LOAD = ["web_server_base"]
|
||||||
@@ -13,7 +12,6 @@ CODEOWNERS = ["@OttoWinter"]
|
|||||||
captive_portal_ns = cg.esphome_ns.namespace("captive_portal")
|
captive_portal_ns = cg.esphome_ns.namespace("captive_portal")
|
||||||
CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component)
|
CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component)
|
||||||
|
|
||||||
CONF_KEEP_USER_CREDENTIALS = "keep_user_credentials"
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
@@ -21,29 +19,13 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
|
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
|
||||||
web_server_base.WebServerBase
|
web_server_base.WebServerBase
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_KEEP_USER_CREDENTIALS, default=False): cv.boolean,
|
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.only_with_arduino,
|
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)
|
@coroutine_with_priority(64.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
|
||||||
@@ -57,6 +39,3 @@ async def to_code(config):
|
|||||||
cg.add_library("WiFi", None)
|
cg.add_library("WiFi", None)
|
||||||
if CORE.is_esp8266:
|
if CORE.is_esp8266:
|
||||||
cg.add_library("DNSServer", None)
|
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() {
|
CoverTraits CurrentBasedCover::get_traits() {
|
||||||
auto traits = CoverTraits();
|
auto traits = CoverTraits();
|
||||||
traits.set_supports_position(true);
|
traits.set_supports_position(true);
|
||||||
|
traits.set_supports_toggle(true);
|
||||||
traits.set_is_assumed_state(false);
|
traits.set_is_assumed_state(false);
|
||||||
return traits;
|
return traits;
|
||||||
}
|
}
|
||||||
@@ -20,6 +21,20 @@ void CurrentBasedCover::control(const CoverCall &call) {
|
|||||||
if (call.get_stop()) {
|
if (call.get_stop()) {
|
||||||
this->direction_idle_();
|
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()) {
|
if (call.get_position().has_value()) {
|
||||||
auto pos = *call.get_position();
|
auto pos = *call.get_position();
|
||||||
if (pos == this->position) {
|
if (pos == this->position) {
|
||||||
@@ -202,9 +217,11 @@ void CurrentBasedCover::start_direction_(CoverOperation dir) {
|
|||||||
trig = this->stop_trigger_;
|
trig = this->stop_trigger_;
|
||||||
break;
|
break;
|
||||||
case COVER_OPERATION_OPENING:
|
case COVER_OPERATION_OPENING:
|
||||||
|
this->last_operation_ = dir;
|
||||||
trig = this->open_trigger_;
|
trig = this->open_trigger_;
|
||||||
break;
|
break;
|
||||||
case COVER_OPERATION_CLOSING:
|
case COVER_OPERATION_CLOSING:
|
||||||
|
this->last_operation_ = dir;
|
||||||
trig = this->close_trigger_;
|
trig = this->close_trigger_;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ class CurrentBasedCover : public cover::Cover, public Component {
|
|||||||
uint32_t start_dir_time_{0};
|
uint32_t start_dir_time_{0};
|
||||||
uint32_t last_publish_time_{0};
|
uint32_t last_publish_time_{0};
|
||||||
float target_position_{0};
|
float target_position_{0};
|
||||||
|
|
||||||
|
cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace current_based
|
} // namespace current_based
|
||||||
|
|||||||
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):
|
def validate_gpio_pin(value):
|
||||||
value = _translate_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]
|
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||||
if variant not in _esp32_validations:
|
if variant not in _esp32_validations:
|
||||||
raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
|
raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace esp32_ble_client {
|
namespace esp32_ble_client {
|
||||||
|
|
||||||
static const char *const TAG = "esp32_ble_client.characteristic";
|
static const char *const TAG = "esp32_ble_client";
|
||||||
|
|
||||||
BLECharacteristic::~BLECharacteristic() {
|
BLECharacteristic::~BLECharacteristic() {
|
||||||
for (auto &desc : this->descriptors)
|
for (auto &desc : this->descriptors)
|
||||||
@@ -29,7 +29,8 @@ void BLECharacteristic::parse_descriptors() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (status != ESP_GATT_OK) {
|
if (status != ESP_GATT_OK) {
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status);
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d",
|
||||||
|
this->service->client->get_connection_index(), this->service->client->address_str().c_str(), status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
@@ -41,7 +42,8 @@ void BLECharacteristic::parse_descriptors() {
|
|||||||
desc->handle = result.handle;
|
desc->handle = result.handle;
|
||||||
desc->characteristic = this;
|
desc->characteristic = this;
|
||||||
this->descriptors.push_back(desc);
|
this->descriptors.push_back(desc);
|
||||||
ESP_LOGV(TAG, " descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle);
|
ESP_LOGV(TAG, "[%d] [%s] descriptor %s, handle 0x%x", this->service->client->get_connection_index(),
|
||||||
|
this->service->client->address_str().c_str(), desc->uuid.to_string().c_str(), desc->handle);
|
||||||
offset++;
|
offset++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,7 +71,8 @@ esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size,
|
|||||||
auto status = esp_ble_gattc_write_char(client->get_gattc_if(), client->get_conn_id(), this->handle, new_val_size,
|
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);
|
new_val, write_type, ESP_GATT_AUTH_REQ_NONE);
|
||||||
if (status) {
|
if (status) {
|
||||||
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
|
ESP_LOGW(TAG, "[%d] [%s] Error sending write value to BLE gattc server, status=%d",
|
||||||
|
this->service->client->get_connection_index(), this->service->client->address_str().c_str(), status);
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ namespace esp32_ble_client {
|
|||||||
static const char *const TAG = "esp32_ble_client";
|
static const char *const TAG = "esp32_ble_client";
|
||||||
|
|
||||||
void BLEClientBase::setup() {
|
void BLEClientBase::setup() {
|
||||||
|
static uint8_t connection_index = 0;
|
||||||
|
this->connection_index_ = connection_index++;
|
||||||
|
|
||||||
auto ret = esp_ble_gattc_app_register(this->app_id);
|
auto ret = esp_ble_gattc_app_register(this->app_id);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
|
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
|
||||||
@@ -30,10 +33,10 @@ float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_B
|
|||||||
bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
|
bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
|
||||||
if (device.address_uint64() != this->address_)
|
if (device.address_uint64() != this->address_)
|
||||||
return false;
|
return false;
|
||||||
if (this->state_ != espbt::ClientState::IDLE)
|
if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str());
|
ESP_LOGD(TAG, "[%d] [%s] Found device", this->connection_index_, this->address_str_.c_str());
|
||||||
this->set_state(espbt::ClientState::DISCOVERED);
|
this->set_state(espbt::ClientState::DISCOVERED);
|
||||||
|
|
||||||
auto addr = device.address_uint64();
|
auto addr = device.address_uint64();
|
||||||
@@ -47,80 +50,88 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string BLEClientBase::address_str() const {
|
|
||||||
return str_snprintf("%02x:%02x:%02x:%02x:%02x:%02x", 17, (uint8_t)(this->address_ >> 40) & 0xff,
|
|
||||||
(uint8_t)(this->address_ >> 32) & 0xff, (uint8_t)(this->address_ >> 24) & 0xff,
|
|
||||||
(uint8_t)(this->address_ >> 16) & 0xff, (uint8_t)(this->address_ >> 8) & 0xff,
|
|
||||||
(uint8_t)(this->address_ >> 0) & 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BLEClientBase::connect() {
|
void BLEClientBase::connect() {
|
||||||
ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str());
|
ESP_LOGI(TAG, "[%d] [%s] Attempting BLE connection", this->connection_index_, this->address_str_.c_str());
|
||||||
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
|
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), ret);
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
ret);
|
||||||
this->set_state(espbt::ClientState::IDLE);
|
this->set_state(espbt::ClientState::IDLE);
|
||||||
} else {
|
} else {
|
||||||
this->set_state(espbt::ClientState::CONNECTING);
|
this->set_state(espbt::ClientState::CONNECTING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
void BLEClientBase::disconnect() {
|
||||||
|
ESP_LOGI(TAG, "[%d] [%s] Disconnecting.", this->connection_index_, this->address_str_.c_str());
|
||||||
|
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_close error, err=%d", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->state_ == espbt::ClientState::SEARCHING) {
|
||||||
|
this->set_address(0);
|
||||||
|
this->set_state(espbt::ClientState::IDLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
||||||
esp_ble_gattc_cb_param_t *param) {
|
esp_ble_gattc_cb_param_t *param) {
|
||||||
if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
|
if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
|
||||||
return;
|
return false;
|
||||||
if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_)
|
if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "[%d] [%s] gattc_event_handler: event=%d gattc_if=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), event, esp_gattc_if);
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case ESP_GATTC_REG_EVT: {
|
case ESP_GATTC_REG_EVT: {
|
||||||
if (param->reg.status == ESP_GATT_OK) {
|
if (param->reg.status == ESP_GATT_OK) {
|
||||||
ESP_LOGV(TAG, "gattc registered app id %d", this->app_id);
|
ESP_LOGV(TAG, "[%d] [%s] gattc registered app id %d", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
this->app_id);
|
||||||
this->gattc_if_ = esp_gattc_if;
|
this->gattc_if_ = esp_gattc_if;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGE(TAG, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status);
|
ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), param->reg.app_id, param->reg.status);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_OPEN_EVT: {
|
case ESP_GATTC_OPEN_EVT: {
|
||||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
|
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT", this->connection_index_, this->address_str_.c_str());
|
||||||
this->conn_id_ = param->open.conn_id;
|
this->conn_id_ = param->open.conn_id;
|
||||||
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
||||||
ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status);
|
ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
param->open.status);
|
||||||
this->set_state(espbt::ClientState::IDLE);
|
this->set_state(espbt::ClientState::IDLE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id);
|
||||||
}
|
|
||||||
case ESP_GATTC_CONNECT_EVT: {
|
|
||||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str());
|
|
||||||
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) {
|
if (ret) {
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret);
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), ret);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_CFG_MTU_EVT: {
|
case ESP_GATTC_CFG_MTU_EVT: {
|
||||||
if (param->cfg_mtu.status != ESP_GATT_OK) {
|
if (param->cfg_mtu.status != ESP_GATT_OK) {
|
||||||
ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu,
|
ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_,
|
||||||
param->cfg_mtu.status);
|
this->address_str_.c_str(), param->cfg_mtu.mtu, param->cfg_mtu.status);
|
||||||
this->set_state(espbt::ClientState::IDLE);
|
this->set_state(espbt::ClientState::IDLE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ESP_LOGV(TAG, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu);
|
ESP_LOGV(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
param->cfg_mtu.status, param->cfg_mtu.mtu);
|
||||||
this->mtu_ = param->cfg_mtu.mtu;
|
this->mtu_ = param->cfg_mtu.mtu;
|
||||||
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
|
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_DISCONNECT_EVT: {
|
case ESP_GATTC_DISCONNECT_EVT: {
|
||||||
if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0) {
|
if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0)
|
||||||
return;
|
return false;
|
||||||
}
|
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_,
|
||||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason);
|
this->address_str_.c_str(), param->disconnect.reason);
|
||||||
for (auto &svc : this->services_)
|
for (auto &svc : this->services_)
|
||||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
this->services_.clear();
|
this->services_.clear();
|
||||||
@@ -137,10 +148,12 @@ void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str());
|
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_SEARCH_CMPL_EVT", this->connection_index_, this->address_str_.c_str());
|
||||||
for (auto &svc : this->services_) {
|
for (auto &svc : this->services_) {
|
||||||
ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str());
|
ESP_LOGI(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
|
||||||
ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle);
|
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();
|
svc->parse_characteristics();
|
||||||
}
|
}
|
||||||
this->set_state(espbt::ClientState::CONNECTED);
|
this->set_state(espbt::ClientState::CONNECTED);
|
||||||
@@ -149,14 +162,10 @@ void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
}
|
}
|
||||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||||
auto *descr = this->get_config_descriptor(param->reg_for_notify.handle);
|
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 ||
|
if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
|
||||||
descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
|
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,
|
ESP_LOGW(TAG, "[%d] [%s] Handle 0x%x (uuid %s) is not a client config char uuid", this->connection_index_,
|
||||||
descr->uuid.to_string().c_str());
|
this->address_str_.c_str(), param->reg_for_notify.handle, descr->uuid.to_string().c_str());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
uint16_t notify_en = 1;
|
uint16_t notify_en = 1;
|
||||||
@@ -164,7 +173,8 @@ void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descr->handle, sizeof(notify_en),
|
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);
|
(uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||||
if (status) {
|
if (status) {
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", status);
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, status=%d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), status);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -172,24 +182,28 @@ void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
// This event is sent by the server when it requests security
|
// This event is sent by the server when it requests security
|
||||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||||
ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event);
|
ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event);
|
||||||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||||||
break;
|
break;
|
||||||
// This event is sent once authentication has completed
|
// This event is sent once authentication has completed
|
||||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||||||
esp_bd_addr_t bd_addr;
|
esp_bd_addr_t bd_addr;
|
||||||
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||||
ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str());
|
ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
format_hex(bd_addr, 6).c_str());
|
||||||
if (!param->ble_security.auth_cmpl.success) {
|
if (!param->ble_security.auth_cmpl.success) {
|
||||||
ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason);
|
ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(),
|
||||||
|
param->ble_security.auth_cmpl.fail_reason);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type,
|
ESP_LOGV(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type,
|
||||||
param->ble_security.auth_cmpl.auth_mode);
|
param->ble_security.auth_cmpl.auth_mode);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -245,7 +259,8 @@ float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
|
|||||||
(int32_t)(value[4]));
|
(int32_t)(value[4]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length);
|
ESP_LOGW(TAG, "[%d] [%s] Cannot parse characteristic value of type 0x%x length %d", this->connection_index_,
|
||||||
|
this->address_str_.c_str(), value[0], length);
|
||||||
return NAN;
|
return NAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,13 +28,27 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
|||||||
|
|
||||||
bool parse_device(const espbt::ESPBTDevice &device) override;
|
bool parse_device(const espbt::ESPBTDevice &device) override;
|
||||||
void on_scan_end() override {}
|
void on_scan_end() override {}
|
||||||
void 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;
|
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 gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||||
void connect() override;
|
void connect() override;
|
||||||
|
void disconnect();
|
||||||
|
|
||||||
void set_address(uint64_t address) { this->address_ = address; }
|
bool connected() { return this->state_ == espbt::ClientState::ESTABLISHED; }
|
||||||
std::string address_str() const;
|
|
||||||
|
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(espbt::ESPBTUUID uuid);
|
||||||
BLEService *get_service(uint16_t uuid);
|
BLEService *get_service(uint16_t uuid);
|
||||||
@@ -55,12 +69,16 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
|||||||
uint16_t get_conn_id() const { return this->conn_id_; }
|
uint16_t get_conn_id() const { return this->conn_id_; }
|
||||||
uint64_t get_address() const { return this->address_; }
|
uint64_t get_address() const { return this->address_; }
|
||||||
|
|
||||||
|
uint8_t get_connection_index() const { return this->connection_index_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int gattc_if_;
|
int gattc_if_;
|
||||||
esp_bd_addr_t remote_bda_;
|
esp_bd_addr_t remote_bda_;
|
||||||
esp_ble_addr_type_t remote_addr_type_;
|
esp_ble_addr_type_t remote_addr_type_;
|
||||||
uint16_t conn_id_;
|
uint16_t conn_id_{0xFFFF};
|
||||||
uint64_t address_;
|
uint64_t address_{0};
|
||||||
|
std::string address_str_{};
|
||||||
|
uint8_t connection_index_;
|
||||||
uint16_t mtu_{23};
|
uint16_t mtu_{23};
|
||||||
|
|
||||||
std::vector<BLEService *> services_;
|
std::vector<BLEService *> services_;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace esp32_ble_client {
|
namespace esp32_ble_client {
|
||||||
|
|
||||||
static const char *const TAG = "esp32_ble_client.service";
|
static const char *const TAG = "esp32_ble_client";
|
||||||
|
|
||||||
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
|
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
|
||||||
for (auto &chr : this->characteristics) {
|
for (auto &chr : this->characteristics) {
|
||||||
@@ -40,7 +40,8 @@ void BLEService::parse_characteristics() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (status != ESP_GATT_OK) {
|
if (status != ESP_GATT_OK) {
|
||||||
ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status);
|
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->client->get_connection_index(),
|
||||||
|
this->client->address_str().c_str(), status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
@@ -53,8 +54,9 @@ void BLEService::parse_characteristics() {
|
|||||||
characteristic->handle = result.char_handle;
|
characteristic->handle = result.char_handle;
|
||||||
characteristic->service = this;
|
characteristic->service = this;
|
||||||
this->characteristics.push_back(characteristic);
|
this->characteristics.push_back(characteristic);
|
||||||
ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(),
|
ESP_LOGI(TAG, "[%d] [%s] characteristic %s, handle 0x%x, properties 0x%x", this->client->get_connection_index(),
|
||||||
characteristic->handle, characteristic->properties);
|
this->client->address_str().c_str(), characteristic->uuid.to_string().c_str(), characteristic->handle,
|
||||||
|
characteristic->properties);
|
||||||
characteristic->parse_descriptors();
|
characteristic->parse_descriptors();
|
||||||
offset++;
|
offset++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,8 @@ class ESPBTDeviceListener {
|
|||||||
enum class ClientState {
|
enum class ClientState {
|
||||||
// Connection is idle, no device detected.
|
// Connection is idle, no device detected.
|
||||||
IDLE,
|
IDLE,
|
||||||
|
// Searching for device.
|
||||||
|
SEARCHING,
|
||||||
// Device advertisement found.
|
// Device advertisement found.
|
||||||
DISCOVERED,
|
DISCOVERED,
|
||||||
// Connection in progress.
|
// Connection in progress.
|
||||||
@@ -157,7 +159,7 @@ enum class ClientState {
|
|||||||
|
|
||||||
class ESPBTClient : public ESPBTDeviceListener {
|
class ESPBTClient : public ESPBTDeviceListener {
|
||||||
public:
|
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;
|
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 gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
|
||||||
virtual void connect() = 0;
|
virtual void connect() = 0;
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ async def to_code(config):
|
|||||||
cg.add(ble_server.register_service_component(var))
|
cg.add(ble_server.register_service_component(var))
|
||||||
|
|
||||||
cg.add_define("USE_IMPROV")
|
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_identify_duration(config[CONF_IDENTIFY_DURATION]))
|
||||||
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))
|
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))
|
||||||
|
|||||||
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 esphome {
|
||||||
namespace ezo {
|
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 char *const EZO_CALIBRATION_TYPE_STRINGS[] = {"LOW", "MID", "HIGH"};
|
||||||
static const uint16_t EZO_STATE_SEND_TEMP = 2;
|
|
||||||
static const uint16_t EZO_STATE_WAIT_TEMP = 4;
|
|
||||||
|
|
||||||
void EZOSensor::dump_config() {
|
void EZOSensor::dump_config() {
|
||||||
LOG_SENSOR("", "EZO", this);
|
LOG_SENSOR("", "EZO", this);
|
||||||
@@ -20,37 +20,75 @@ void EZOSensor::dump_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EZOSensor::update() {
|
void EZOSensor::update() {
|
||||||
if (this->state_ & EZO_STATE_WAIT) {
|
// Check if a read is in there already and if not insert on in the second position
|
||||||
ESP_LOGE(TAG, "update overrun, still waiting for previous response");
|
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
uint8_t c = 'R';
|
|
||||||
this->write(&c, 1);
|
this->get_state();
|
||||||
this->state_ |= EZO_STATE_WAIT;
|
|
||||||
this->start_time_ = millis();
|
|
||||||
this->wait_time_ = 900;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EZOSensor::loop() {
|
void EZOSensor::loop() {
|
||||||
uint8_t buf[21];
|
if (this->commands_.empty()) {
|
||||||
if (!(this->state_ & EZO_STATE_WAIT)) {
|
return;
|
||||||
if (this->state_ & EZO_STATE_SEND_TEMP) {
|
}
|
||||||
int len = sprintf((char *) buf, "T,%0.3f", this->tempcomp_);
|
|
||||||
this->write(buf, len);
|
EzoCommand *to_run = this->commands_.front().get();
|
||||||
this->state_ = EZO_STATE_WAIT | EZO_STATE_WAIT_TEMP;
|
|
||||||
this->start_time_ = millis();
|
if (!to_run->command_sent) {
|
||||||
this->wait_time_ = 300;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (millis() - this->start_time_ < this->wait_time_)
|
|
||||||
|
if (millis() - this->start_time_ < to_run->delay_ms)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
uint8_t buf[32];
|
||||||
|
|
||||||
buf[0] = 0;
|
buf[0] = 0;
|
||||||
if (!this->read_bytes_raw(buf, 20)) {
|
|
||||||
|
if (!this->read_bytes_raw(buf, 32)) {
|
||||||
ESP_LOGE(TAG, "read error");
|
ESP_LOGE(TAG, "read error");
|
||||||
this->state_ = 0;
|
this->commands_.pop_front();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (buf[0]) {
|
switch (buf[0]) {
|
||||||
case 1:
|
case 1:
|
||||||
break;
|
break;
|
||||||
@@ -66,28 +104,142 @@ void EZOSensor::loop() {
|
|||||||
ESP_LOGE(TAG, "device returned an unknown response: %d", buf[0]);
|
ESP_LOGE(TAG, "device returned an unknown response: %d", buf[0]);
|
||||||
break;
|
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
|
ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", buf, EZO_COMMAND_TYPE_STRINGS[to_run->command_type]);
|
||||||
for (size_t i = 1; i < sizeof(buf) - 1; i++) {
|
|
||||||
if (buf[i] == ',')
|
if ((buf[0] == 1) || (to_run->command_type == EzoCommandType::EZO_CALIBRATION)) { // EZO_CALIBRATION returns 0-3
|
||||||
buf[i] = '\0';
|
// 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->commands_.pop_front();
|
||||||
this->publish_state(val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EZOSensor::set_tempcomp_value(float temp) {
|
void EZOSensor::add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms) {
|
||||||
this->tempcomp_ = temp;
|
std::unique_ptr<EzoCommand> ezo_command(new EzoCommand);
|
||||||
this->state_ |= EZO_STATE_SEND_TEMP;
|
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 ezo
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -3,10 +3,35 @@
|
|||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ezo {
|
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
|
/// This class implements support for the EZO circuits in i2c mode
|
||||||
class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||||
public:
|
public:
|
||||||
@@ -15,13 +40,71 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2
|
|||||||
void update() override;
|
void update() override;
|
||||||
float get_setup_priority() const override { return setup_priority::DATA; };
|
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:
|
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 start_time_ = 0;
|
||||||
uint32_t wait_time_ = 0;
|
|
||||||
uint16_t state_ = 0;
|
|
||||||
float tempcomp_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ezo
|
} // namespace ezo
|
||||||
|
|||||||
@@ -1,22 +1,81 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
from esphome import automation
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID, CONF_TRIGGER_ID
|
||||||
|
|
||||||
CODEOWNERS = ["@ssieb"]
|
CODEOWNERS = ["@ssieb"]
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
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")
|
ezo_ns = cg.esphome_ns.namespace("ezo")
|
||||||
|
|
||||||
EZOSensor = ezo_ns.class_(
|
EZOSensor = ezo_ns.class_(
|
||||||
"EZOSensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
|
"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 = (
|
CONFIG_SCHEMA = (
|
||||||
sensor.SENSOR_SCHEMA.extend(
|
sensor.SENSOR_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(EZOSensor),
|
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"))
|
.extend(cv.polling_component_schema("60s"))
|
||||||
@@ -29,3 +88,27 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await sensor.register_sensor(var, config)
|
await sensor.register_sensor(var, config)
|
||||||
await i2c.register_i2c_device(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)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from esphome.const import (
|
|||||||
CONF_PROTOCOL,
|
CONF_PROTOCOL,
|
||||||
CONF_VISUAL,
|
CONF_VISUAL,
|
||||||
)
|
)
|
||||||
|
from esphome.core import CORE
|
||||||
|
|
||||||
CODEOWNERS = ["@rob-deutsch"]
|
CODEOWNERS = ["@rob-deutsch"]
|
||||||
|
|
||||||
@@ -115,3 +116,6 @@ def to_code(config):
|
|||||||
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
||||||
|
|
||||||
cg.add_library("tonia/HeatpumpIR", "1.0.20")
|
cg.add_library("tonia/HeatpumpIR", "1.0.20")
|
||||||
|
|
||||||
|
if CORE.is_esp8266 or CORE.is_esp32:
|
||||||
|
cg.add_library("crankyoldgit/IRremoteESP8266", "2.7.12")
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ static const char *const TAG = "i2c.arduino";
|
|||||||
void ArduinoI2CBus::setup() {
|
void ArduinoI2CBus::setup() {
|
||||||
recover_();
|
recover_();
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#if defined(USE_ESP32)
|
||||||
static uint8_t next_bus_num = 0;
|
static uint8_t next_bus_num = 0;
|
||||||
if (next_bus_num == 0) {
|
if (next_bus_num == 0) {
|
||||||
wire_ = &Wire;
|
wire_ = &Wire;
|
||||||
@@ -22,11 +22,25 @@ void ArduinoI2CBus::setup() {
|
|||||||
wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory)
|
wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
}
|
}
|
||||||
next_bus_num++;
|
next_bus_num++;
|
||||||
#else
|
#elif defined(USE_ESP8266)
|
||||||
wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer)
|
wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer)
|
||||||
|
#elif defined(USE_RP2040)
|
||||||
|
static bool first = true;
|
||||||
|
if (first) {
|
||||||
|
wire_ = &Wire;
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
wire_ = &Wire1; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
wire_->setSDA(this->sda_pin_);
|
||||||
|
wire_->setSCL(this->scl_pin_);
|
||||||
|
wire_->begin();
|
||||||
|
#else
|
||||||
wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_));
|
wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_));
|
||||||
|
#endif
|
||||||
wire_->setClock(frequency_);
|
wire_->setClock(frequency_);
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
if (this->scan_) {
|
if (this->scan_) {
|
||||||
|
|||||||
@@ -37,4 +37,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger
|
|||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
cg.add_library("esphome/Improv", "1.2.1")
|
cg.add_library("esphome/Improv", "1.2.3")
|
||||||
|
|||||||
74
esphome/components/lcd_menu/__init__.py
Normal file
74
esphome/components/lcd_menu/__init__.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_DIMENSIONS,
|
||||||
|
)
|
||||||
|
from esphome.core.entity_helpers import inherit_property_from
|
||||||
|
from esphome.components import lcd_base
|
||||||
|
from esphome.components.display_menu_base import (
|
||||||
|
DISPLAY_MENU_BASE_SCHEMA,
|
||||||
|
DisplayMenuComponent,
|
||||||
|
display_menu_to_code,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@numo68"]
|
||||||
|
|
||||||
|
AUTO_LOAD = ["display_menu_base"]
|
||||||
|
|
||||||
|
lcd_menu_ns = cg.esphome_ns.namespace("lcd_menu")
|
||||||
|
|
||||||
|
CONF_DISPLAY_ID = "display_id"
|
||||||
|
|
||||||
|
CONF_MARK_SELECTED = "mark_selected"
|
||||||
|
CONF_MARK_EDITING = "mark_editing"
|
||||||
|
CONF_MARK_SUBMENU = "mark_submenu"
|
||||||
|
CONF_MARK_BACK = "mark_back"
|
||||||
|
|
||||||
|
MINIMUM_COLUMNS = 12
|
||||||
|
|
||||||
|
LCDCharacterMenuComponent = lcd_menu_ns.class_(
|
||||||
|
"LCDCharacterMenuComponent", DisplayMenuComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
MULTI_CONF = True
|
||||||
|
|
||||||
|
|
||||||
|
def validate_lcd_dimensions(config):
|
||||||
|
if config[CONF_DIMENSIONS][0] < MINIMUM_COLUMNS:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"LCD display must have at least {MINIMUM_COLUMNS} columns to be usable with the menu"
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = DISPLAY_MENU_BASE_SCHEMA.extend(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(LCDCharacterMenuComponent),
|
||||||
|
cv.GenerateID(CONF_DISPLAY_ID): cv.use_id(lcd_base.LCDDisplay),
|
||||||
|
cv.Optional(CONF_MARK_SELECTED, default=0x3E): cv.uint8_t,
|
||||||
|
cv.Optional(CONF_MARK_EDITING, default=0x2A): cv.uint8_t,
|
||||||
|
cv.Optional(CONF_MARK_SUBMENU, default=0x7E): cv.uint8_t,
|
||||||
|
cv.Optional(CONF_MARK_BACK, default=0x5E): cv.uint8_t,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||||
|
inherit_property_from(CONF_DIMENSIONS, CONF_DISPLAY_ID),
|
||||||
|
validate_lcd_dimensions,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
disp = await cg.get_variable(config[CONF_DISPLAY_ID])
|
||||||
|
cg.add(var.set_display(disp))
|
||||||
|
cg.add(var.set_dimensions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1]))
|
||||||
|
await display_menu_to_code(var, config)
|
||||||
|
cg.add(var.set_mark_selected(config[CONF_MARK_SELECTED]))
|
||||||
|
cg.add(var.set_mark_editing(config[CONF_MARK_EDITING]))
|
||||||
|
cg.add(var.set_mark_submenu(config[CONF_MARK_SUBMENU]))
|
||||||
|
cg.add(var.set_mark_back(config[CONF_MARK_BACK]))
|
||||||
74
esphome/components/lcd_menu/lcd_menu.cpp
Normal file
74
esphome/components/lcd_menu/lcd_menu.cpp
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#include "lcd_menu.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace lcd_menu {
|
||||||
|
|
||||||
|
static const char *const TAG = "lcd_menu";
|
||||||
|
|
||||||
|
void LCDCharacterMenuComponent::setup() {
|
||||||
|
if (this->display_->is_failed()) {
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
display_menu_base::DisplayMenuComponent::setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
float LCDCharacterMenuComponent::get_setup_priority() const { return setup_priority::PROCESSOR - 1.0f; }
|
||||||
|
|
||||||
|
void LCDCharacterMenuComponent::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "LCD Menu");
|
||||||
|
ESP_LOGCONFIG(TAG, " Columns: %u, Rows: %u", this->columns_, this->rows_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Mark characters: %02x, %02x, %02x, %02x", this->mark_selected_, this->mark_editing_,
|
||||||
|
this->mark_submenu_, this->mark_back_);
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, "The connected display failed, the menu is disabled!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCDCharacterMenuComponent::draw_item(const display_menu_base::MenuItem *item, uint8_t row, bool selected) {
|
||||||
|
char data[this->columns_ + 1]; // Bounded to 65 through the config
|
||||||
|
|
||||||
|
memset(data, ' ', this->columns_);
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
data[0] = (this->editing_ || (this->mode_ == display_menu_base::MENU_MODE_JOYSTICK && item->get_immediate_edit()))
|
||||||
|
? this->mark_editing_
|
||||||
|
: this->mark_selected_;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (item->get_type()) {
|
||||||
|
case display_menu_base::MENU_ITEM_MENU:
|
||||||
|
data[this->columns_ - 1] = this->mark_submenu_;
|
||||||
|
break;
|
||||||
|
case display_menu_base::MENU_ITEM_BACK:
|
||||||
|
data[this->columns_ - 1] = this->mark_back_;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto text = item->get_text();
|
||||||
|
size_t n = std::min(text.size(), (size_t) this->columns_ - 2);
|
||||||
|
memcpy(data + 1, item->get_text().c_str(), n);
|
||||||
|
|
||||||
|
if (item->has_value()) {
|
||||||
|
std::string value = item->get_value_text();
|
||||||
|
|
||||||
|
// Maximum: start mark, at least two chars of label, space, '[', value, ']',
|
||||||
|
// end mark. Config guarantees columns >= 12
|
||||||
|
size_t val_width = std::min((size_t) this->columns_ - 7, value.length());
|
||||||
|
memcpy(data + this->columns_ - val_width - 4, " [", 2);
|
||||||
|
memcpy(data + this->columns_ - val_width - 2, value.c_str(), val_width);
|
||||||
|
data[this->columns_ - 2] = ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
data[this->columns_] = '\0';
|
||||||
|
|
||||||
|
this->display_->print(0, row, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace lcd_menu
|
||||||
|
} // namespace esphome
|
||||||
45
esphome/components/lcd_menu/lcd_menu.h
Normal file
45
esphome/components/lcd_menu/lcd_menu.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/lcd_base/lcd_display.h"
|
||||||
|
#include "esphome/components/display_menu_base/display_menu_base.h"
|
||||||
|
|
||||||
|
#include <forward_list>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace lcd_menu {
|
||||||
|
|
||||||
|
/** Class to display a hierarchical menu.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class LCDCharacterMenuComponent : public display_menu_base::DisplayMenuComponent {
|
||||||
|
public:
|
||||||
|
void set_display(lcd_base::LCDDisplay *display) { this->display_ = display; }
|
||||||
|
void set_dimensions(uint8_t columns, uint8_t rows) {
|
||||||
|
this->columns_ = columns;
|
||||||
|
set_rows(rows);
|
||||||
|
}
|
||||||
|
void set_mark_selected(uint8_t c) { this->mark_selected_ = c; }
|
||||||
|
void set_mark_editing(uint8_t c) { this->mark_editing_ = c; }
|
||||||
|
void set_mark_submenu(uint8_t c) { this->mark_submenu_ = c; }
|
||||||
|
void set_mark_back(uint8_t c) { this->mark_back_ = c; }
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void draw_item(const display_menu_base::MenuItem *item, uint8_t row, bool selected) override;
|
||||||
|
void update() override { this->display_->update(); }
|
||||||
|
|
||||||
|
lcd_base::LCDDisplay *display_;
|
||||||
|
uint8_t columns_;
|
||||||
|
char mark_selected_;
|
||||||
|
char mark_editing_;
|
||||||
|
char mark_submenu_;
|
||||||
|
char mark_back_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace lcd_menu
|
||||||
|
} // namespace esphome
|
||||||
@@ -77,6 +77,8 @@ UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1]
|
|||||||
|
|
||||||
ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG]
|
ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG]
|
||||||
|
|
||||||
|
UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1]
|
||||||
|
|
||||||
HARDWARE_UART_TO_UART_SELECTION = {
|
HARDWARE_UART_TO_UART_SELECTION = {
|
||||||
UART0: logger_ns.UART_SELECTION_UART0,
|
UART0: logger_ns.UART_SELECTION_UART0,
|
||||||
UART0_SWAP: logger_ns.UART_SELECTION_UART0_SWAP,
|
UART0_SWAP: logger_ns.UART_SELECTION_UART0_SWAP,
|
||||||
@@ -97,15 +99,16 @@ is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
|
|||||||
|
|
||||||
|
|
||||||
def uart_selection(value):
|
def uart_selection(value):
|
||||||
if value.upper() in ESP_IDF_UARTS:
|
|
||||||
if not CORE.using_esp_idf:
|
|
||||||
raise cv.Invalid(f"Only esp-idf framework supports {value}.")
|
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
|
if value.upper() in ESP_IDF_UARTS and not CORE.using_esp_idf:
|
||||||
|
raise cv.Invalid(f"Only esp-idf framework supports {value}.")
|
||||||
variant = get_esp32_variant()
|
variant = get_esp32_variant()
|
||||||
if variant in UART_SELECTION_ESP32:
|
if variant in UART_SELECTION_ESP32:
|
||||||
return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
|
return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
|
||||||
if CORE.is_esp8266:
|
if CORE.is_esp8266:
|
||||||
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
|
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
|
||||||
|
if CORE.is_rp2040:
|
||||||
|
return cv.one_of(*UART_SELECTION_RP2040, upper=True)(value)
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@@ -133,7 +136,12 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
|
cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
|
||||||
cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes,
|
cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes,
|
||||||
cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
|
cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_HARDWARE_UART, default=UART0): uart_selection,
|
cv.SplitDefault(
|
||||||
|
CONF_HARDWARE_UART,
|
||||||
|
esp8266=UART0,
|
||||||
|
esp32=UART0,
|
||||||
|
rp2040=USB_CDC,
|
||||||
|
): uart_selection,
|
||||||
cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level,
|
cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level,
|
||||||
cv.Optional(CONF_LOGS, default={}): cv.Schema(
|
cv.Optional(CONF_LOGS, default={}): cv.Schema(
|
||||||
{
|
{
|
||||||
@@ -158,12 +166,13 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
@coroutine_with_priority(90.0)
|
@coroutine_with_priority(90.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
baud_rate = config[CONF_BAUD_RATE]
|
baud_rate = config[CONF_BAUD_RATE]
|
||||||
rhs = Logger.new(
|
log = cg.new_Pvariable(config[CONF_ID], baud_rate, config[CONF_TX_BUFFER_SIZE])
|
||||||
baud_rate,
|
if CONF_HARDWARE_UART in config:
|
||||||
config[CONF_TX_BUFFER_SIZE],
|
cg.add(
|
||||||
HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]],
|
log.set_uart_selection(
|
||||||
)
|
HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]]
|
||||||
log = cg.Pvariable(config[CONF_ID], rhs)
|
)
|
||||||
|
)
|
||||||
cg.add(log.pre_setup())
|
cg.add(log.pre_setup())
|
||||||
|
|
||||||
for tag, level in config[CONF_LOGS].items():
|
for tag, level in config[CONF_LOGS].items():
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
#ifdef USE_ESP_IDF
|
#ifdef USE_ESP_IDF
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include <driver/uart.h>
|
#include <driver/uart.h>
|
||||||
#endif
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#endif // USE_ESP_IDF
|
||||||
|
|
||||||
#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF)
|
#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF)
|
||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
#endif
|
#endif // USE_ESP32_FRAMEWORK_ARDUINO || USE_ESP_IDF
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace logger {
|
namespace logger {
|
||||||
@@ -148,8 +148,7 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
|
|||||||
this->log_callback_.call(level, tag, msg);
|
this->log_callback_.call(level, tag, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart)
|
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) {
|
||||||
: baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size), uart_(uart) {
|
|
||||||
// add 1 to buffer size for null terminator
|
// add 1 to buffer size for null terminator
|
||||||
this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
|
this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
|
||||||
}
|
}
|
||||||
@@ -162,8 +161,13 @@ void Logger::pre_setup() {
|
|||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
case UART_SELECTION_UART0_SWAP:
|
case UART_SELECTION_UART0_SWAP:
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
this->hw_serial_ = &Serial1;
|
||||||
|
Serial1.begin(this->baud_rate_);
|
||||||
|
#else
|
||||||
this->hw_serial_ = &Serial;
|
this->hw_serial_ = &Serial;
|
||||||
Serial.begin(this->baud_rate_);
|
Serial.begin(this->baud_rate_);
|
||||||
|
#endif
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||||
Serial.swap();
|
Serial.swap();
|
||||||
@@ -172,8 +176,13 @@ void Logger::pre_setup() {
|
|||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case UART_SELECTION_UART1:
|
case UART_SELECTION_UART1:
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
this->hw_serial_ = &Serial2;
|
||||||
|
Serial2.begin(this->baud_rate_);
|
||||||
|
#else
|
||||||
this->hw_serial_ = &Serial1;
|
this->hw_serial_ = &Serial1;
|
||||||
Serial1.begin(this->baud_rate_);
|
Serial1.begin(this->baud_rate_);
|
||||||
|
#endif
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||||
#endif
|
#endif
|
||||||
@@ -184,6 +193,12 @@ void Logger::pre_setup() {
|
|||||||
this->hw_serial_ = &Serial2;
|
this->hw_serial_ = &Serial2;
|
||||||
Serial2.begin(this->baud_rate_);
|
Serial2.begin(this->baud_rate_);
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
case UART_SELECTION_USB_CDC:
|
||||||
|
this->hw_serial_ = &Serial;
|
||||||
|
Serial.begin(this->baud_rate_);
|
||||||
|
break;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif // USE_ARDUINO
|
#endif // USE_ARDUINO
|
||||||
@@ -270,6 +285,9 @@ const char *const UART_SELECTIONS[] = {
|
|||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
|
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"};
|
||||||
#endif // USE_ESP8266
|
#endif // USE_ESP8266
|
||||||
void Logger::dump_config() {
|
void Logger::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "Logger:");
|
ESP_LOGCONFIG(TAG, "Logger:");
|
||||||
|
|||||||
@@ -7,11 +7,18 @@
|
|||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
#ifdef USE_ARDUINO
|
||||||
|
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#endif
|
#endif // USE_ESP8266 || USE_ESP32
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
#include <HardwareSerial.h>
|
||||||
|
#include <SerialUSB.h>
|
||||||
|
#endif // USE_RP2040
|
||||||
|
#endif // USE_ARDUINO
|
||||||
|
|
||||||
#ifdef USE_ESP_IDF
|
#ifdef USE_ESP_IDF
|
||||||
#include <driver/uart.h>
|
#include <driver/uart.h>
|
||||||
#endif
|
#endif // USE_ESP_IDF
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
@@ -27,24 +34,27 @@ enum UARTSelection {
|
|||||||
#if defined(USE_ESP32)
|
#if defined(USE_ESP32)
|
||||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
|
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
|
||||||
UART_SELECTION_UART2,
|
UART_SELECTION_UART2,
|
||||||
#endif
|
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
|
||||||
#ifdef USE_ESP_IDF
|
#ifdef USE_ESP_IDF
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||||
UART_SELECTION_USB_CDC,
|
UART_SELECTION_USB_CDC,
|
||||||
#endif
|
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||||
UART_SELECTION_USB_SERIAL_JTAG,
|
UART_SELECTION_USB_SERIAL_JTAG,
|
||||||
#endif
|
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
|
||||||
#endif
|
#endif // USE_ESP_IDF
|
||||||
#endif
|
#endif // USE_ESP32
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
UART_SELECTION_UART0_SWAP,
|
UART_SELECTION_UART0_SWAP,
|
||||||
#endif
|
#endif // USE_ESP8266
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
UART_SELECTION_USB_CDC,
|
||||||
|
#endif // USE_RP2040
|
||||||
};
|
};
|
||||||
|
|
||||||
class Logger : public Component {
|
class Logger : public Component {
|
||||||
public:
|
public:
|
||||||
explicit Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart);
|
explicit Logger(uint32_t baud_rate, size_t tx_buffer_size);
|
||||||
|
|
||||||
/// Manually set the baud rate for serial, set to 0 to disable.
|
/// Manually set the baud rate for serial, set to 0 to disable.
|
||||||
void set_baud_rate(uint32_t baud_rate);
|
void set_baud_rate(uint32_t baud_rate);
|
||||||
@@ -56,6 +66,7 @@ class Logger : public Component {
|
|||||||
uart_port_t get_uart_num() const { return uart_num_; }
|
uart_port_t get_uart_num() const { return uart_num_; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
|
||||||
/// Get the UART used by the logger.
|
/// Get the UART used by the logger.
|
||||||
UARTSelection get_uart() const;
|
UARTSelection get_uart() const;
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace md5 {
|
namespace md5 {
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
#if defined(USE_ARDUINO) && !defined(USE_RP2040)
|
||||||
void MD5Digest::init() {
|
void MD5Digest::init() {
|
||||||
memset(this->digest_, 0, 16);
|
memset(this->digest_, 0, 16);
|
||||||
MD5Init(&this->ctx_);
|
MD5Init(&this->ctx_);
|
||||||
@@ -15,7 +15,7 @@ void MD5Digest::init() {
|
|||||||
void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); }
|
void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); }
|
||||||
|
|
||||||
void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); }
|
void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); }
|
||||||
#endif // USE_ARDUINO
|
#endif // USE_ARDUINO && !USE_RP2040
|
||||||
|
|
||||||
#ifdef USE_ESP_IDF
|
#ifdef USE_ESP_IDF
|
||||||
void MD5Digest::init() {
|
void MD5Digest::init() {
|
||||||
@@ -28,6 +28,17 @@ void MD5Digest::add(const uint8_t *data, size_t len) { esp_rom_md5_update(&this-
|
|||||||
void MD5Digest::calculate() { esp_rom_md5_final(this->digest_, &this->ctx_); }
|
void MD5Digest::calculate() { esp_rom_md5_final(this->digest_, &this->ctx_); }
|
||||||
#endif // USE_ESP_IDF
|
#endif // USE_ESP_IDF
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
void MD5Digest::init() {
|
||||||
|
memset(this->digest_, 0, 16);
|
||||||
|
br_md5_init(&this->ctx_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MD5Digest::add(const uint8_t *data, size_t len) { br_md5_update(&this->ctx_, data, len); }
|
||||||
|
|
||||||
|
void MD5Digest::calculate() { br_md5_out(&this->ctx_, this->digest_); }
|
||||||
|
#endif // USE_RP2040
|
||||||
|
|
||||||
void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); }
|
void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); }
|
||||||
|
|
||||||
void MD5Digest::get_hex(char *output) {
|
void MD5Digest::get_hex(char *output) {
|
||||||
|
|||||||
@@ -17,6 +17,11 @@
|
|||||||
#define MD5_CTX_TYPE md5_context_t
|
#define MD5_CTX_TYPE md5_context_t
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
#include <MD5Builder.h>
|
||||||
|
#define MD5_CTX_TYPE br_md5_context
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace md5 {
|
namespace md5 {
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ async def to_code(config):
|
|||||||
cg.add_library("ESPmDNS", None)
|
cg.add_library("ESPmDNS", None)
|
||||||
elif CORE.is_esp8266:
|
elif CORE.is_esp8266:
|
||||||
cg.add_library("ESP8266mDNS", None)
|
cg.add_library("ESP8266mDNS", None)
|
||||||
|
elif CORE.is_rp2040:
|
||||||
|
cg.add_library("LEAmDNS", None)
|
||||||
|
|
||||||
if config[CONF_DISABLED]:
|
if config[CONF_DISABLED]:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ void MDNSComponent::compile_records_() {
|
|||||||
#endif
|
#endif
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
platform = "ESP32";
|
platform = "ESP32";
|
||||||
|
#endif
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
platform = "RP2040";
|
||||||
#endif
|
#endif
|
||||||
if (platform != nullptr) {
|
if (platform != nullptr) {
|
||||||
service.txt_records.push_back({"platform", platform});
|
service.txt_records.push_back({"platform", platform});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class MDNSComponent : public Component {
|
|||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
#if defined(USE_ESP8266) && defined(USE_ARDUINO)
|
#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_ARDUINO)
|
||||||
void loop() override;
|
void loop() override;
|
||||||
#endif
|
#endif
|
||||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||||
|
|||||||
44
esphome/components/mdns/mdns_rp2040.cpp
Normal file
44
esphome/components/mdns/mdns_rp2040.cpp
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#ifdef USE_RP2040
|
||||||
|
|
||||||
|
#include "esphome/components/network/ip_address.h"
|
||||||
|
#include "esphome/components/network/util.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "mdns_component.h"
|
||||||
|
|
||||||
|
#include <ESP8266mDNS.h>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace mdns {
|
||||||
|
|
||||||
|
void MDNSComponent::setup() {
|
||||||
|
this->compile_records_();
|
||||||
|
|
||||||
|
network::IPAddress addr = network::get_ip_address();
|
||||||
|
MDNS.begin(this->hostname_.c_str(), (uint32_t) addr);
|
||||||
|
|
||||||
|
for (const auto &service : this->services_) {
|
||||||
|
// Strip the leading underscore from the proto and service_type. While it is
|
||||||
|
// part of the wire protocol to have an underscore, and for example ESP-IDF
|
||||||
|
// expects the underscore to be there, the ESP8266 implementation always adds
|
||||||
|
// the underscore itself.
|
||||||
|
auto *proto = service.proto.c_str();
|
||||||
|
while (*proto == '_') {
|
||||||
|
proto++;
|
||||||
|
}
|
||||||
|
auto *service_type = service.service_type.c_str();
|
||||||
|
while (*service_type == '_') {
|
||||||
|
service_type++;
|
||||||
|
}
|
||||||
|
MDNS.addService(service_type, proto, service.port);
|
||||||
|
for (const auto &record : service.txt_records) {
|
||||||
|
MDNS.addServiceTxt(service_type, proto, record.key.c_str(), record.value.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MDNSComponent::loop() { MDNS.update(); }
|
||||||
|
|
||||||
|
} // namespace mdns
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -250,6 +250,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
validate_config,
|
validate_config,
|
||||||
|
cv.only_on(["esp32", "esp8266"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(OTAComponent),
|
cv.GenerateID(): cv.declare_id(OTAComponent),
|
||||||
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
|
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
|
||||||
cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232): cv.port,
|
cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232, rp2040=2040): cv.port,
|
||||||
cv.Optional(CONF_PASSWORD): cv.string,
|
cv.Optional(CONF_PASSWORD): cv.string,
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_REBOOT_TIMEOUT, default="5min"
|
CONF_REBOOT_TIMEOUT, default="5min"
|
||||||
@@ -94,6 +94,9 @@ async def to_code(config):
|
|||||||
if CORE.is_esp32 and CORE.using_arduino:
|
if CORE.is_esp32 and CORE.using_arduino:
|
||||||
cg.add_library("Update", None)
|
cg.add_library("Update", None)
|
||||||
|
|
||||||
|
if CORE.is_rp2040 and CORE.using_arduino:
|
||||||
|
cg.add_library("Updater", None)
|
||||||
|
|
||||||
use_state_callback = False
|
use_state_callback = False
|
||||||
for conf in config.get(CONF_ON_STATE_CHANGE, []):
|
for conf in config.get(CONF_ON_STATE_CHANGE, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
|||||||
59
esphome/components/ota/ota_backend_arduino_rp2040.cpp
Normal file
59
esphome/components/ota/ota_backend_arduino_rp2040.cpp
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_ARDUINO
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
|
||||||
|
#include "esphome/components/rp2040/preferences.h"
|
||||||
|
#include "ota_backend.h"
|
||||||
|
#include "ota_backend_arduino_rp2040.h"
|
||||||
|
#include "ota_component.h"
|
||||||
|
|
||||||
|
#include <Updater.h>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ota {
|
||||||
|
|
||||||
|
OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) {
|
||||||
|
bool ret = Update.begin(image_size, U_FLASH);
|
||||||
|
if (ret) {
|
||||||
|
rp2040::preferences_prevent_write(true);
|
||||||
|
return OTA_RESPONSE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t error = Update.getError();
|
||||||
|
if (error == UPDATE_ERROR_BOOTSTRAP)
|
||||||
|
return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING;
|
||||||
|
if (error == UPDATE_ERROR_NEW_FLASH_CONFIG)
|
||||||
|
return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG;
|
||||||
|
if (error == UPDATE_ERROR_FLASH_CONFIG)
|
||||||
|
return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG;
|
||||||
|
if (error == UPDATE_ERROR_SPACE)
|
||||||
|
return OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE;
|
||||||
|
return OTA_RESPONSE_ERROR_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoRP2040OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); }
|
||||||
|
|
||||||
|
OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) {
|
||||||
|
size_t written = Update.write(data, len);
|
||||||
|
if (written != len) {
|
||||||
|
return OTA_RESPONSE_ERROR_WRITING_FLASH;
|
||||||
|
}
|
||||||
|
return OTA_RESPONSE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
OTAResponseTypes ArduinoRP2040OTABackend::end() {
|
||||||
|
if (!Update.end())
|
||||||
|
return OTA_RESPONSE_ERROR_UPDATE_END;
|
||||||
|
return OTA_RESPONSE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoRP2040OTABackend::abort() {
|
||||||
|
Update.end();
|
||||||
|
rp2040::preferences_prevent_write(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ota
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_RP2040
|
||||||
|
#endif // USE_ARDUINO
|
||||||
27
esphome/components/ota/ota_backend_arduino_rp2040.h
Normal file
27
esphome/components/ota/ota_backend_arduino_rp2040.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_ARDUINO
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
|
||||||
|
#include "esphome/core/macros.h"
|
||||||
|
#include "ota_backend.h"
|
||||||
|
#include "ota_component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ota {
|
||||||
|
|
||||||
|
class ArduinoRP2040OTABackend : public OTABackend {
|
||||||
|
public:
|
||||||
|
OTAResponseTypes begin(size_t image_size) override;
|
||||||
|
void set_update_md5(const char *md5) override;
|
||||||
|
OTAResponseTypes write(uint8_t *data, size_t len) override;
|
||||||
|
OTAResponseTypes end() override;
|
||||||
|
void abort() override;
|
||||||
|
bool supports_compression() override { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ota
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_RP2040
|
||||||
|
#endif // USE_ARDUINO
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "ota_backend.h"
|
#include "ota_backend.h"
|
||||||
#include "ota_backend_arduino_esp32.h"
|
#include "ota_backend_arduino_esp32.h"
|
||||||
#include "ota_backend_arduino_esp8266.h"
|
#include "ota_backend_arduino_esp8266.h"
|
||||||
|
#include "ota_backend_arduino_rp2040.h"
|
||||||
#include "ota_backend_esp_idf.h"
|
#include "ota_backend_esp_idf.h"
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
@@ -35,6 +36,9 @@ std::unique_ptr<OTABackend> make_ota_backend() {
|
|||||||
#ifdef USE_ESP_IDF
|
#ifdef USE_ESP_IDF
|
||||||
return make_unique<IDFOTABackend>();
|
return make_unique<IDFOTABackend>();
|
||||||
#endif // USE_ESP_IDF
|
#endif // USE_ESP_IDF
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
return make_unique<ArduinoRP2040OTABackend>();
|
||||||
|
#endif // USE_RP2040
|
||||||
}
|
}
|
||||||
|
|
||||||
OTAComponent::OTAComponent() { global_ota_component = this; }
|
OTAComponent::OTAComponent() { global_ota_component = this; }
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ enum OTAResponseTypes {
|
|||||||
OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137,
|
OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137,
|
||||||
OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138,
|
OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138,
|
||||||
OTA_RESPONSE_ERROR_MD5_MISMATCH = 139,
|
OTA_RESPONSE_ERROR_MD5_MISMATCH = 139,
|
||||||
|
OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 140,
|
||||||
OTA_RESPONSE_ERROR_UNKNOWN = 255,
|
OTA_RESPONSE_ERROR_UNKNOWN = 255,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ CONFIG_SCHEMA = (
|
|||||||
|
|
||||||
|
|
||||||
def auto_data_rate(config):
|
def auto_data_rate(config):
|
||||||
interval_sec = config[CONF_UPDATE_INTERVAL].seconds
|
interval_sec = config[CONF_UPDATE_INTERVAL].total_milliseconds / 1000
|
||||||
interval_hz = 1.0 / interval_sec
|
interval_hz = 1.0 / interval_sec
|
||||||
for datarate in sorted(QMC5883LDatarates.keys()):
|
for datarate in sorted(QMC5883LDatarates.keys()):
|
||||||
if float(datarate) >= interval_hz:
|
if float(datarate) >= interval_hz:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID
|
|||||||
|
|
||||||
DEPENDENCIES = ["uart"]
|
DEPENDENCIES = ["uart"]
|
||||||
AUTO_LOAD = ["binary_sensor"]
|
AUTO_LOAD = ["binary_sensor"]
|
||||||
|
MULTI_CONF = True
|
||||||
|
|
||||||
rdm6300_ns = cg.esphome_ns.namespace("rdm6300")
|
rdm6300_ns = cg.esphome_ns.namespace("rdm6300")
|
||||||
RDM6300Component = rdm6300_ns.class_("RDM6300Component", cg.Component, uart.UARTDevice)
|
RDM6300Component = rdm6300_ns.class_("RDM6300Component", cg.Component, uart.UARTDevice)
|
||||||
|
|||||||
161
esphome/components/rp2040/__init__.py
Normal file
161
esphome/components/rp2040/__init__.py
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_BOARD,
|
||||||
|
CONF_FRAMEWORK,
|
||||||
|
CONF_SOURCE,
|
||||||
|
CONF_VERSION,
|
||||||
|
KEY_CORE,
|
||||||
|
KEY_FRAMEWORK_VERSION,
|
||||||
|
KEY_TARGET_FRAMEWORK,
|
||||||
|
KEY_TARGET_PLATFORM,
|
||||||
|
)
|
||||||
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
|
from .const import KEY_BOARD, KEY_RP2040, rp2040_ns
|
||||||
|
|
||||||
|
# force import gpio to register pin schema
|
||||||
|
from .gpio import rp2040_pin_to_code # noqa
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
CODEOWNERS = ["@jesserockz"]
|
||||||
|
AUTO_LOAD = []
|
||||||
|
|
||||||
|
|
||||||
|
def set_core_data(config):
|
||||||
|
CORE.data[KEY_RP2040] = {}
|
||||||
|
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "rp2040"
|
||||||
|
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
|
||||||
|
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
|
||||||
|
config[CONF_FRAMEWORK][CONF_VERSION]
|
||||||
|
)
|
||||||
|
CORE.data[KEY_RP2040][KEY_BOARD] = config[CONF_BOARD]
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def _format_framework_arduino_version(ver: cv.Version) -> str:
|
||||||
|
# The most recent releases have not been uploaded to platformio so grabbing them directly from
|
||||||
|
# the GitHub release is one path forward for now.
|
||||||
|
return f"https://github.com/earlephilhower/arduino-pico/releases/download/{ver}/rp2040-{ver}.zip"
|
||||||
|
|
||||||
|
# format the given arduino (https://github.com/earlephilhower/arduino-pico/releases) version to
|
||||||
|
# a PIO earlephilhower/framework-arduinopico value
|
||||||
|
# List of package versions: https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico
|
||||||
|
# return f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: Keep this in mind when updating the recommended version:
|
||||||
|
# * The new version needs to be thoroughly validated before changing the
|
||||||
|
# recommended version as otherwise a bunch of devices could be bricked
|
||||||
|
# * For all constants below, update platformio.ini (in this repo)
|
||||||
|
# and platformio.ini/platformio-lint.ini in the esphome-docker-base repository
|
||||||
|
|
||||||
|
# The default/recommended arduino framework version
|
||||||
|
# - https://github.com/earlephilhower/arduino-pico/releases
|
||||||
|
# - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico
|
||||||
|
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 6, 2)
|
||||||
|
|
||||||
|
# The platformio/raspberrypi version to use for arduino frameworks
|
||||||
|
# - https://github.com/platformio/platform-raspberrypi/releases
|
||||||
|
# - https://api.registry.platformio.org/v3/packages/platformio/platform/raspberrypi
|
||||||
|
ARDUINO_PLATFORM_VERSION = cv.Version(1, 7, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def _arduino_check_versions(value):
|
||||||
|
value = value.copy()
|
||||||
|
lookups = {
|
||||||
|
"dev": (cv.Version(2, 6, 2), "https://github.com/earlephilhower/arduino-pico"),
|
||||||
|
"latest": (cv.Version(2, 6, 2), None),
|
||||||
|
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
|
||||||
|
}
|
||||||
|
|
||||||
|
if value[CONF_VERSION] in lookups:
|
||||||
|
if CONF_SOURCE in value:
|
||||||
|
raise cv.Invalid(
|
||||||
|
"Framework version needs to be explicitly specified when custom source is used."
|
||||||
|
)
|
||||||
|
|
||||||
|
version, source = lookups[value[CONF_VERSION]]
|
||||||
|
else:
|
||||||
|
version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
|
||||||
|
source = value.get(CONF_SOURCE, None)
|
||||||
|
|
||||||
|
value[CONF_VERSION] = str(version)
|
||||||
|
value[CONF_SOURCE] = source or _format_framework_arduino_version(version)
|
||||||
|
|
||||||
|
value[CONF_PLATFORM_VERSION] = value.get(
|
||||||
|
CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION))
|
||||||
|
)
|
||||||
|
|
||||||
|
if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"The selected Arduino framework version is not the recommended one."
|
||||||
|
)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_platform_version(value):
|
||||||
|
try:
|
||||||
|
# if platform version is a valid version constraint, prefix the default package
|
||||||
|
cv.platformio_version_constraint(value)
|
||||||
|
return f"platformio/raspberrypi @ {value}"
|
||||||
|
except cv.Invalid:
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
CONF_PLATFORM_VERSION = "platform_version"
|
||||||
|
|
||||||
|
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
|
||||||
|
cv.Optional(CONF_SOURCE): cv.string_strict,
|
||||||
|
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
_arduino_check_versions,
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_BOARD): cv.string_strict,
|
||||||
|
cv.Optional(CONF_FRAMEWORK, default={}): ARDUINO_FRAMEWORK_SCHEMA,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
set_core_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(1000)
|
||||||
|
async def to_code(config):
|
||||||
|
cg.add(rp2040_ns.setup_preferences())
|
||||||
|
|
||||||
|
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||||
|
cg.add_build_flag("-DUSE_RP2040")
|
||||||
|
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||||
|
cg.add_define("ESPHOME_VARIANT", "RP2040")
|
||||||
|
|
||||||
|
conf = config[CONF_FRAMEWORK]
|
||||||
|
cg.add_platformio_option("framework", "arduino")
|
||||||
|
cg.add_build_flag("-DUSE_ARDUINO")
|
||||||
|
cg.add_build_flag("-DUSE_RP2040_FRAMEWORK_ARDUINO")
|
||||||
|
# cg.add_build_flag("-DPICO_BOARD=pico_w")
|
||||||
|
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||||
|
cg.add_platformio_option(
|
||||||
|
"platform_packages",
|
||||||
|
[f"earlephilhower/framework-arduinopico @ {conf[CONF_SOURCE]}"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cg.add_platformio_option("board_build.core", "earlephilhower")
|
||||||
|
cg.add_platformio_option("board_build.filesystem_size", "1m")
|
||||||
|
|
||||||
|
ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||||
|
cg.add_define(
|
||||||
|
"USE_ARDUINO_VERSION_CODE",
|
||||||
|
cg.RawExpression(f"VERSION_CODE({ver.major}, {ver.minor}, {ver.patch})"),
|
||||||
|
)
|
||||||
19
esphome/components/rp2040/boards.py
Normal file
19
esphome/components/rp2040/boards.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
RP2040_BASE_PINS = {}
|
||||||
|
|
||||||
|
RP2040_BOARD_PINS = {
|
||||||
|
"pico": {
|
||||||
|
"SDA": 4,
|
||||||
|
"SCL": 5,
|
||||||
|
"LED": 25,
|
||||||
|
"SDA1": 26,
|
||||||
|
"SCL1": 27,
|
||||||
|
},
|
||||||
|
"rpipico": "pico",
|
||||||
|
"rpipicow": {
|
||||||
|
"SDA": 4,
|
||||||
|
"SCL": 5,
|
||||||
|
"LED": 32,
|
||||||
|
"SDA1": 26,
|
||||||
|
"SCL1": 27,
|
||||||
|
},
|
||||||
|
}
|
||||||
6
esphome/components/rp2040/const.py
Normal file
6
esphome/components/rp2040/const.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
|
||||||
|
KEY_BOARD = "board"
|
||||||
|
KEY_RP2040 = "rp2040"
|
||||||
|
|
||||||
|
rp2040_ns = cg.esphome_ns.namespace("rp2040")
|
||||||
33
esphome/components/rp2040/core.cpp
Normal file
33
esphome/components/rp2040/core.cpp
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#ifdef USE_RP2040
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
#include "hardware/watchdog.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
|
||||||
|
void IRAM_ATTR HOT yield() { ::yield(); }
|
||||||
|
uint32_t IRAM_ATTR HOT millis() { return ::millis(); }
|
||||||
|
void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
|
||||||
|
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
|
||||||
|
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||||
|
void arch_restart() {
|
||||||
|
watchdog_reboot(0, 0, 10);
|
||||||
|
while (1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void arch_init() { watchdog_enable(0x7fffff, false); }
|
||||||
|
void IRAM_ATTR HOT arch_feed_wdt() { watchdog_update(); }
|
||||||
|
|
||||||
|
uint8_t progmem_read_byte(const uint8_t *addr) {
|
||||||
|
return pgm_read_byte(addr); // NOLINT
|
||||||
|
}
|
||||||
|
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return ulMainGetRunTimeCounterValue(); }
|
||||||
|
uint32_t arch_get_cpu_freq_hz() { return RP2040::f_cpu(); }
|
||||||
|
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_RP2040
|
||||||
14
esphome/components/rp2040/core.h
Normal file
14
esphome/components/rp2040/core.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <pico.h>
|
||||||
|
|
||||||
|
extern "C" unsigned long ulMainGetRunTimeCounterValue();
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace rp2040 {} // namespace rp2040
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_RP2040
|
||||||
103
esphome/components/rp2040/gpio.cpp
Normal file
103
esphome/components/rp2040/gpio.cpp
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#ifdef USE_RP2040
|
||||||
|
|
||||||
|
#include "gpio.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace rp2040 {
|
||||||
|
|
||||||
|
static const char *const TAG = "rp2040";
|
||||||
|
|
||||||
|
static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
|
||||||
|
if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone)
|
||||||
|
return INPUT;
|
||||||
|
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||||
|
return OUTPUT;
|
||||||
|
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||||
|
return INPUT_PULLUP;
|
||||||
|
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
|
||||||
|
return INPUT_PULLDOWN;
|
||||||
|
// } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
|
||||||
|
// return OpenDrain;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ISRPinArg {
|
||||||
|
uint8_t pin;
|
||||||
|
bool inverted;
|
||||||
|
};
|
||||||
|
|
||||||
|
ISRInternalGPIOPin RP2040GPIOPin::to_isr() const {
|
||||||
|
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
arg->pin = pin_;
|
||||||
|
arg->inverted = inverted_;
|
||||||
|
return ISRInternalGPIOPin((void *) arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RP2040GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
|
||||||
|
PinStatus arduino_mode = LOW;
|
||||||
|
switch (type) {
|
||||||
|
case gpio::INTERRUPT_RISING_EDGE:
|
||||||
|
arduino_mode = inverted_ ? FALLING : RISING;
|
||||||
|
break;
|
||||||
|
case gpio::INTERRUPT_FALLING_EDGE:
|
||||||
|
arduino_mode = inverted_ ? RISING : FALLING;
|
||||||
|
break;
|
||||||
|
case gpio::INTERRUPT_ANY_EDGE:
|
||||||
|
arduino_mode = CHANGE;
|
||||||
|
break;
|
||||||
|
case gpio::INTERRUPT_LOW_LEVEL:
|
||||||
|
arduino_mode = inverted_ ? HIGH : LOW;
|
||||||
|
break;
|
||||||
|
case gpio::INTERRUPT_HIGH_LEVEL:
|
||||||
|
arduino_mode = inverted_ ? LOW : HIGH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
attachInterrupt(pin_, func, arduino_mode, arg);
|
||||||
|
}
|
||||||
|
void RP2040GPIOPin::pin_mode(gpio::Flags flags) {
|
||||||
|
pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RP2040GPIOPin::dump_summary() const {
|
||||||
|
char buffer[32];
|
||||||
|
snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RP2040GPIOPin::digital_read() {
|
||||||
|
return bool(digitalRead(pin_)) != inverted_; // NOLINT
|
||||||
|
}
|
||||||
|
void RP2040GPIOPin::digital_write(bool value) {
|
||||||
|
digitalWrite(pin_, value != inverted_ ? 1 : 0); // NOLINT
|
||||||
|
}
|
||||||
|
void RP2040GPIOPin::detach_interrupt() const { detachInterrupt(pin_); }
|
||||||
|
|
||||||
|
} // namespace rp2040
|
||||||
|
|
||||||
|
using namespace rp2040;
|
||||||
|
|
||||||
|
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
|
||||||
|
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||||
|
return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT
|
||||||
|
}
|
||||||
|
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
|
||||||
|
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||||
|
digitalWrite(arg->pin, value != arg->inverted ? 1 : 0); // NOLINT
|
||||||
|
}
|
||||||
|
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
|
||||||
|
// TODO: implement
|
||||||
|
// auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||||
|
// GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin);
|
||||||
|
}
|
||||||
|
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||||
|
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||||
|
pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_RP2040
|
||||||
38
esphome/components/rp2040/gpio.h
Normal file
38
esphome/components/rp2040/gpio.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace rp2040 {
|
||||||
|
|
||||||
|
class RP2040GPIOPin : public InternalGPIOPin {
|
||||||
|
public:
|
||||||
|
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||||
|
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||||
|
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||||
|
|
||||||
|
void setup() override { pin_mode(flags_); }
|
||||||
|
void pin_mode(gpio::Flags flags) override;
|
||||||
|
bool digital_read() override;
|
||||||
|
void digital_write(bool value) override;
|
||||||
|
std::string dump_summary() const override;
|
||||||
|
void detach_interrupt() const override;
|
||||||
|
ISRInternalGPIOPin to_isr() const override;
|
||||||
|
uint8_t get_pin() const override { return pin_; }
|
||||||
|
bool is_inverted() const override { return inverted_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||||
|
|
||||||
|
uint8_t pin_;
|
||||||
|
bool inverted_;
|
||||||
|
gpio::Flags flags_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rp2040
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_RP2040
|
||||||
110
esphome/components/rp2040/gpio.py
Normal file
110
esphome/components/rp2040/gpio.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_INPUT,
|
||||||
|
CONF_INVERTED,
|
||||||
|
CONF_MODE,
|
||||||
|
CONF_NUMBER,
|
||||||
|
CONF_OPEN_DRAIN,
|
||||||
|
CONF_OUTPUT,
|
||||||
|
CONF_PULLDOWN,
|
||||||
|
CONF_PULLUP,
|
||||||
|
)
|
||||||
|
from esphome.core import CORE
|
||||||
|
from esphome import pins
|
||||||
|
|
||||||
|
from . import boards
|
||||||
|
from .const import KEY_BOARD, KEY_RP2040, rp2040_ns
|
||||||
|
|
||||||
|
RP2040GPIOPin = rp2040_ns.class_("RP2040GPIOPin", cg.InternalGPIOPin)
|
||||||
|
|
||||||
|
|
||||||
|
def _lookup_pin(value):
|
||||||
|
board = CORE.data[KEY_RP2040][KEY_BOARD]
|
||||||
|
board_pins = boards.RP2040_BOARD_PINS.get(board, {})
|
||||||
|
|
||||||
|
while isinstance(board_pins, str):
|
||||||
|
board_pins = boards.RP2040_BOARD_PINS[board_pins]
|
||||||
|
|
||||||
|
if value in board_pins:
|
||||||
|
return board_pins[value]
|
||||||
|
if value in boards.RP2040_BASE_PINS:
|
||||||
|
return boards.RP2040_BASE_PINS[value]
|
||||||
|
raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {board}.")
|
||||||
|
|
||||||
|
|
||||||
|
def _translate_pin(value):
|
||||||
|
if isinstance(value, dict) or value is None:
|
||||||
|
raise cv.Invalid(
|
||||||
|
"This variable only supports pin numbers, not full pin schemas "
|
||||||
|
"(with inverted and mode)."
|
||||||
|
)
|
||||||
|
if isinstance(value, int):
|
||||||
|
return value
|
||||||
|
try:
|
||||||
|
return int(value)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if value.startswith("GPIO"):
|
||||||
|
return cv.int_(value[len("GPIO") :].strip())
|
||||||
|
return _lookup_pin(value)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_gpio_pin(value):
|
||||||
|
value = _translate_pin(value)
|
||||||
|
board = CORE.data[KEY_RP2040][KEY_BOARD]
|
||||||
|
if board == "rpipicow" and value == 32:
|
||||||
|
return value # Special case for Pico-w LED pin
|
||||||
|
if value < 0 or value > 29:
|
||||||
|
raise cv.Invalid(f"RP2040: Invalid pin number: {value}")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def validate_supports(value):
|
||||||
|
board = CORE.data[KEY_RP2040][KEY_BOARD]
|
||||||
|
if board != "rpipicow" or value[CONF_NUMBER] != 32:
|
||||||
|
return value
|
||||||
|
mode = value[CONF_MODE]
|
||||||
|
is_input = mode[CONF_INPUT]
|
||||||
|
is_output = mode[CONF_OUTPUT]
|
||||||
|
is_open_drain = mode[CONF_OPEN_DRAIN]
|
||||||
|
is_pullup = mode[CONF_PULLUP]
|
||||||
|
is_pulldown = mode[CONF_PULLDOWN]
|
||||||
|
if not is_output or is_input or is_open_drain or is_pullup or is_pulldown:
|
||||||
|
raise cv.Invalid("Only output mode is supported for Pico-w LED pin")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
CONF_ANALOG = "analog"
|
||||||
|
|
||||||
|
RP2040_PIN_SCHEMA = cv.All(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(RP2040GPIOPin),
|
||||||
|
cv.Required(CONF_NUMBER): validate_gpio_pin,
|
||||||
|
cv.Optional(CONF_MODE, default={}): cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_ANALOG, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_INPUT, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_PULLDOWN, default=False): cv.boolean,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
validate_supports,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pins.PIN_SCHEMA_REGISTRY.register("rp2040", RP2040_PIN_SCHEMA)
|
||||||
|
async def rp2040_pin_to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
num = config[CONF_NUMBER]
|
||||||
|
cg.add(var.set_pin(num))
|
||||||
|
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||||
|
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||||
|
return var
|
||||||
158
esphome/components/rp2040/preferences.cpp
Normal file
158
esphome/components/rp2040/preferences.cpp
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
#ifdef USE_RP2040
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#include <hardware/flash.h>
|
||||||
|
#include <hardware/sync.h>
|
||||||
|
|
||||||
|
#include "preferences.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace rp2040 {
|
||||||
|
|
||||||
|
static const char *const TAG = "rp2040.preferences";
|
||||||
|
|
||||||
|
static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
static uint8_t *s_flash_storage = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
|
static const uint32_t RP2040_FLASH_STORAGE_SIZE = 512;
|
||||||
|
|
||||||
|
extern "C" uint8_t _EEPROM_start;
|
||||||
|
|
||||||
|
template<class It> uint8_t calculate_crc(It first, It last, uint32_t type) {
|
||||||
|
std::array<uint8_t, 4> type_array = decode_value(type);
|
||||||
|
uint8_t crc = type_array[0] ^ type_array[1] ^ type_array[2] ^ type_array[3];
|
||||||
|
while (first != last) {
|
||||||
|
crc ^= (*first++);
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RP2040PreferenceBackend : public ESPPreferenceBackend {
|
||||||
|
public:
|
||||||
|
size_t offset = 0;
|
||||||
|
uint32_t type = 0;
|
||||||
|
|
||||||
|
bool save(const uint8_t *data, size_t len) override {
|
||||||
|
std::vector<uint8_t> buffer;
|
||||||
|
buffer.resize(len + 1);
|
||||||
|
memcpy(buffer.data(), data, len);
|
||||||
|
buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < len + 1; i++) {
|
||||||
|
uint32_t j = offset + i;
|
||||||
|
if (j >= RP2040_FLASH_STORAGE_SIZE)
|
||||||
|
return false;
|
||||||
|
uint8_t v = buffer[i];
|
||||||
|
uint8_t *ptr = &s_flash_storage[j];
|
||||||
|
if (*ptr != v)
|
||||||
|
s_flash_dirty = true;
|
||||||
|
*ptr = v;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool load(uint8_t *data, size_t len) override {
|
||||||
|
std::vector<uint8_t> buffer;
|
||||||
|
buffer.resize(len + 1);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len + 1; i++) {
|
||||||
|
uint32_t j = offset + i;
|
||||||
|
if (j >= RP2040_FLASH_STORAGE_SIZE)
|
||||||
|
return false;
|
||||||
|
buffer[i] = s_flash_storage[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t crc = calculate_crc(buffer.begin(), buffer.end() - 1, type);
|
||||||
|
if (buffer[buffer.size() - 1] != crc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(data, buffer.data(), len);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class RP2040Preferences : public ESPPreferences {
|
||||||
|
public:
|
||||||
|
uint32_t current_flash_offset = 0;
|
||||||
|
|
||||||
|
RP2040Preferences() : eeprom_sector_(&_EEPROM_start) {}
|
||||||
|
void setup() {
|
||||||
|
s_flash_storage = new uint8_t[RP2040_FLASH_STORAGE_SIZE]; // NOLINT
|
||||||
|
ESP_LOGVV(TAG, "Loading preferences from flash...");
|
||||||
|
memcpy(s_flash_storage, this->eeprom_sector_, RP2040_FLASH_STORAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
|
||||||
|
return make_preference(length, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
|
||||||
|
uint32_t start = this->current_flash_offset;
|
||||||
|
uint32_t end = start + length + 1;
|
||||||
|
if (end > RP2040_FLASH_STORAGE_SIZE) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto *pref = new RP2040PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
pref->offset = start;
|
||||||
|
pref->type = type;
|
||||||
|
current_flash_offset = end;
|
||||||
|
return {pref};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sync() override {
|
||||||
|
if (!s_flash_dirty)
|
||||||
|
return true;
|
||||||
|
if (s_prevent_write)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Saving preferences to flash...");
|
||||||
|
|
||||||
|
{
|
||||||
|
InterruptLock lock;
|
||||||
|
::rp2040.idleOtherCore();
|
||||||
|
flash_range_erase((intptr_t) eeprom_sector_ - (intptr_t) XIP_BASE, 4096);
|
||||||
|
flash_range_program((intptr_t) eeprom_sector_ - (intptr_t) XIP_BASE, s_flash_storage, RP2040_FLASH_STORAGE_SIZE);
|
||||||
|
::rp2040.resumeOtherCore();
|
||||||
|
}
|
||||||
|
|
||||||
|
s_flash_dirty = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool reset() override {
|
||||||
|
ESP_LOGD(TAG, "Cleaning up preferences in flash...");
|
||||||
|
{
|
||||||
|
InterruptLock lock;
|
||||||
|
::rp2040.idleOtherCore();
|
||||||
|
flash_range_erase((intptr_t) eeprom_sector_ - (intptr_t) XIP_BASE, 4096);
|
||||||
|
::rp2040.resumeOtherCore();
|
||||||
|
}
|
||||||
|
s_prevent_write = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t *eeprom_sector_;
|
||||||
|
};
|
||||||
|
|
||||||
|
void setup_preferences() {
|
||||||
|
auto *prefs = new RP2040Preferences(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
prefs->setup();
|
||||||
|
global_preferences = prefs;
|
||||||
|
}
|
||||||
|
void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; }
|
||||||
|
|
||||||
|
} // namespace rp2040
|
||||||
|
|
||||||
|
ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_RP2040
|
||||||
14
esphome/components/rp2040/preferences.h
Normal file
14
esphome/components/rp2040/preferences.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace rp2040 {
|
||||||
|
|
||||||
|
void setup_preferences();
|
||||||
|
void preferences_prevent_write(bool prevent);
|
||||||
|
|
||||||
|
} // namespace rp2040
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_RP2040
|
||||||
0
esphome/components/rp2040_pwm/__init__.py
Normal file
0
esphome/components/rp2040_pwm/__init__.py
Normal file
55
esphome/components/rp2040_pwm/output.py
Normal file
55
esphome/components/rp2040_pwm/output.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
from esphome import pins, automation
|
||||||
|
from esphome.components import output
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_FREQUENCY,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_PIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@jesserockz"]
|
||||||
|
DEPENDENCIES = ["rp2040"]
|
||||||
|
|
||||||
|
|
||||||
|
rp2040_pwm_ns = cg.esphome_ns.namespace("rp2040_pwm")
|
||||||
|
RP2040PWM = rp2040_pwm_ns.class_("RP2040PWM", output.FloatOutput, cg.Component)
|
||||||
|
SetFrequencyAction = rp2040_pwm_ns.class_("SetFrequencyAction", automation.Action)
|
||||||
|
validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6))
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_ID): cv.declare_id(RP2040PWM),
|
||||||
|
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||||
|
cv.Optional(CONF_FREQUENCY, default="1kHz"): validate_frequency,
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await output.register_output(var, config)
|
||||||
|
|
||||||
|
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||||
|
cg.add(var.set_pin(pin))
|
||||||
|
|
||||||
|
cg.add(var.set_frequency(config[CONF_FREQUENCY]))
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"output.rp2040_pwm.set_frequency",
|
||||||
|
SetFrequencyAction,
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_ID): cv.use_id(RP2040PWM),
|
||||||
|
cv.Required(CONF_FREQUENCY): cv.templatable(validate_frequency),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def rp2040_set_frequency_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_FREQUENCY], args, float)
|
||||||
|
cg.add(var.set_frequency(template_))
|
||||||
|
return var
|
||||||
45
esphome/components/rp2040_pwm/rp2040_pwm.cpp
Normal file
45
esphome/components/rp2040_pwm/rp2040_pwm.cpp
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#ifdef USE_RP2040
|
||||||
|
|
||||||
|
#include "rp2040_pwm.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/macros.h"
|
||||||
|
|
||||||
|
#include <PinNames.h>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace rp2040_pwm {
|
||||||
|
|
||||||
|
static const char *const TAG = "rp2040_pwm";
|
||||||
|
|
||||||
|
void RP2040PWM::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up RP2040 PWM Output...");
|
||||||
|
this->pin_->setup();
|
||||||
|
this->pwm_ = new mbed::PwmOut((PinName) this->pin_->get_pin());
|
||||||
|
this->turn_off();
|
||||||
|
}
|
||||||
|
void RP2040PWM::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "RP2040 PWM:");
|
||||||
|
LOG_PIN(" Pin: ", this->pin_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_);
|
||||||
|
LOG_FLOAT_OUTPUT(this);
|
||||||
|
}
|
||||||
|
void HOT RP2040PWM::write_state(float state) {
|
||||||
|
this->last_output_ = state;
|
||||||
|
|
||||||
|
// Also check pin inversion
|
||||||
|
if (this->pin_->is_inverted()) {
|
||||||
|
state = 1.0f - state;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto total_time_us = static_cast<uint32_t>(roundf(1e6f / this->frequency_));
|
||||||
|
|
||||||
|
this->pwm_->period_us(total_time_us);
|
||||||
|
this->pwm_->write(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rp2040_pwm
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
||||||
57
esphome/components/rp2040_pwm/rp2040_pwm.h
Normal file
57
esphome/components/rp2040_pwm/rp2040_pwm.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
|
||||||
|
#include "esphome/components/output/float_output.h"
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
#include "drivers/PwmOut.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace rp2040_pwm {
|
||||||
|
|
||||||
|
class RP2040PWM : public output::FloatOutput, public Component {
|
||||||
|
public:
|
||||||
|
void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
|
||||||
|
void set_frequency(float frequency) { this->frequency_ = frequency; }
|
||||||
|
/// Dynamically update frequency
|
||||||
|
void update_frequency(float frequency) override {
|
||||||
|
this->set_frequency(frequency);
|
||||||
|
this->write_state(this->last_output_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize pin
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
/// HARDWARE setup_priority
|
||||||
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void write_state(float state) override;
|
||||||
|
|
||||||
|
InternalGPIOPin *pin_;
|
||||||
|
mbed::PwmOut *pwm_;
|
||||||
|
float frequency_{1000.0};
|
||||||
|
/// Cache last output level for dynamic frequency updating
|
||||||
|
float last_output_{0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SetFrequencyAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
SetFrequencyAction(RP2040PWM *parent) : parent_(parent) {}
|
||||||
|
TEMPLATABLE_VALUE(float, frequency);
|
||||||
|
|
||||||
|
void play(Ts... x) {
|
||||||
|
float freq = this->frequency_.value(x...);
|
||||||
|
this->parent_->update_frequency(freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
RP2040PWM *parent_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rp2040_pwm
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_RP2040
|
||||||
@@ -2,7 +2,8 @@ import esphome.codegen as cg
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.automation import maybe_simple_id
|
from esphome.automation import maybe_simple_id
|
||||||
from esphome.const import CONF_ID, CONF_MODE
|
from esphome.const import CONF_ID, CONF_MODE, CONF_PARAMETERS
|
||||||
|
from esphome.core import CORE, EsphomeError
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
script_ns = cg.esphome_ns.namespace("script")
|
script_ns = cg.esphome_ns.namespace("script")
|
||||||
@@ -16,6 +17,7 @@ RestartScript = script_ns.class_("RestartScript", Script)
|
|||||||
QueueingScript = script_ns.class_("QueueingScript", Script, cg.Component)
|
QueueingScript = script_ns.class_("QueueingScript", Script, cg.Component)
|
||||||
ParallelScript = script_ns.class_("ParallelScript", Script)
|
ParallelScript = script_ns.class_("ParallelScript", Script)
|
||||||
|
|
||||||
|
CONF_SCRIPT = "script"
|
||||||
CONF_SINGLE = "single"
|
CONF_SINGLE = "single"
|
||||||
CONF_RESTART = "restart"
|
CONF_RESTART = "restart"
|
||||||
CONF_QUEUED = "queued"
|
CONF_QUEUED = "queued"
|
||||||
@@ -29,6 +31,18 @@ SCRIPT_MODES = {
|
|||||||
CONF_PARALLEL: ParallelScript,
|
CONF_PARALLEL: ParallelScript,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PARAMETER_TYPE_TRANSLATIONS = {
|
||||||
|
"string": "std::string",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_script(script_id):
|
||||||
|
scripts = CORE.config.get(CONF_SCRIPT, {})
|
||||||
|
for script in scripts:
|
||||||
|
if script.get(CONF_ID, None) == script_id:
|
||||||
|
return script
|
||||||
|
raise cv.Invalid(f"Script id '{script_id}' not found")
|
||||||
|
|
||||||
|
|
||||||
def check_max_runs(value):
|
def check_max_runs(value):
|
||||||
if CONF_MAX_RUNS not in value:
|
if CONF_MAX_RUNS not in value:
|
||||||
@@ -47,6 +61,44 @@ def assign_declare_id(value):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def parameters_to_template(args):
|
||||||
|
|
||||||
|
template_args = []
|
||||||
|
func_args = []
|
||||||
|
script_arg_names = []
|
||||||
|
for name, type_ in args.items():
|
||||||
|
array = False
|
||||||
|
if type_.endswith("[]"):
|
||||||
|
array = True
|
||||||
|
type_ = type_[:-2]
|
||||||
|
type_ = PARAMETER_TYPE_TRANSLATIONS.get(type_, type_)
|
||||||
|
if array:
|
||||||
|
type_ = f"std::vector<{type_}>"
|
||||||
|
type_ = cg.esphome_ns.namespace(type_)
|
||||||
|
template_args.append(type_)
|
||||||
|
func_args.append((type_, name))
|
||||||
|
script_arg_names.append(name)
|
||||||
|
template = cg.TemplateArguments(*template_args)
|
||||||
|
return template, func_args
|
||||||
|
|
||||||
|
|
||||||
|
def validate_parameter_name(value):
|
||||||
|
value = cv.string(value)
|
||||||
|
if value != CONF_ID:
|
||||||
|
return value
|
||||||
|
raise cv.Invalid(f"Script's parameter name cannot be {CONF_ID}")
|
||||||
|
|
||||||
|
|
||||||
|
ALLOWED_PARAM_TYPE_CHARSET = set("abcdefghijklmnopqrstuvwxyz0123456789_:*&[]")
|
||||||
|
|
||||||
|
|
||||||
|
def validate_parameter_type(value):
|
||||||
|
value = cv.string_strict(value)
|
||||||
|
if set(value.lower()) <= ALLOWED_PARAM_TYPE_CHARSET:
|
||||||
|
return value
|
||||||
|
raise cv.Invalid("Parameter type contains invalid characters")
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = automation.validate_automation(
|
CONFIG_SCHEMA = automation.validate_automation(
|
||||||
{
|
{
|
||||||
# Don't declare id as cv.declare_id yet, because the ID type
|
# Don't declare id as cv.declare_id yet, because the ID type
|
||||||
@@ -56,6 +108,11 @@ CONFIG_SCHEMA = automation.validate_automation(
|
|||||||
*SCRIPT_MODES, lower=True
|
*SCRIPT_MODES, lower=True
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_MAX_RUNS): cv.positive_int,
|
cv.Optional(CONF_MAX_RUNS): cv.positive_int,
|
||||||
|
cv.Optional(CONF_PARAMETERS, default={}): cv.Schema(
|
||||||
|
{
|
||||||
|
validate_parameter_name: validate_parameter_type,
|
||||||
|
}
|
||||||
|
),
|
||||||
},
|
},
|
||||||
extra_validators=cv.All(check_max_runs, assign_declare_id),
|
extra_validators=cv.All(check_max_runs, assign_declare_id),
|
||||||
)
|
)
|
||||||
@@ -65,7 +122,8 @@ async def to_code(config):
|
|||||||
# Register all variables first, so that scripts can use other scripts
|
# Register all variables first, so that scripts can use other scripts
|
||||||
triggers = []
|
triggers = []
|
||||||
for conf in config:
|
for conf in config:
|
||||||
trigger = cg.new_Pvariable(conf[CONF_ID])
|
template, func_args = parameters_to_template(conf[CONF_PARAMETERS])
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_ID], template)
|
||||||
# Add a human-readable name to the script
|
# Add a human-readable name to the script
|
||||||
cg.add(trigger.set_name(conf[CONF_ID].id))
|
cg.add(trigger.set_name(conf[CONF_ID].id))
|
||||||
|
|
||||||
@@ -75,10 +133,10 @@ async def to_code(config):
|
|||||||
if conf[CONF_MODE] == CONF_QUEUED:
|
if conf[CONF_MODE] == CONF_QUEUED:
|
||||||
await cg.register_component(trigger, conf)
|
await cg.register_component(trigger, conf)
|
||||||
|
|
||||||
triggers.append((trigger, conf))
|
triggers.append((trigger, func_args, conf))
|
||||||
|
|
||||||
for trigger, conf in triggers:
|
for trigger, func_args, conf in triggers:
|
||||||
await automation.build_automation(trigger, [], conf)
|
await automation.build_automation(trigger, func_args, conf)
|
||||||
|
|
||||||
|
|
||||||
@automation.register_action(
|
@automation.register_action(
|
||||||
@@ -87,12 +145,39 @@ async def to_code(config):
|
|||||||
maybe_simple_id(
|
maybe_simple_id(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_ID): cv.use_id(Script),
|
cv.Required(CONF_ID): cv.use_id(Script),
|
||||||
}
|
cv.Optional(validate_parameter_name): cv.templatable(cv.valid),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
async def script_execute_action_to_code(config, action_id, template_arg, args):
|
async def script_execute_action_to_code(config, action_id, template_arg, args):
|
||||||
|
async def get_ordered_args(config, script_params):
|
||||||
|
config_args = config.copy()
|
||||||
|
config_args.pop(CONF_ID)
|
||||||
|
|
||||||
|
# match script_args to the formal parameter order
|
||||||
|
script_args = []
|
||||||
|
for type, name in script_params:
|
||||||
|
if name not in config_args:
|
||||||
|
raise EsphomeError(
|
||||||
|
f"Missing parameter: '{name}' in script.execute {config[CONF_ID]}"
|
||||||
|
)
|
||||||
|
arg = await cg.templatable(config_args[name], args, type)
|
||||||
|
script_args.append(arg)
|
||||||
|
return script_args
|
||||||
|
|
||||||
|
script = get_script(config[CONF_ID])
|
||||||
|
params = script.get(CONF_PARAMETERS, [])
|
||||||
|
template, script_params = parameters_to_template(params)
|
||||||
|
script_args = await get_ordered_args(config, script_params)
|
||||||
|
|
||||||
|
# We need to use the parent class 'Script' as the template argument
|
||||||
|
# to match the partial specialization of the ScriptExecuteAction template
|
||||||
|
template_arg = cg.TemplateArguments(Script.template(template), *template_arg)
|
||||||
|
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
cg.add(var.set_args(*script_args))
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
@automation.register_action(
|
@automation.register_action(
|
||||||
@@ -101,7 +186,8 @@ async def script_execute_action_to_code(config, action_id, template_arg, args):
|
|||||||
maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}),
|
maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}),
|
||||||
)
|
)
|
||||||
async def script_stop_action_to_code(config, action_id, template_arg, args):
|
async def script_stop_action_to_code(config, action_id, template_arg, args):
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
|
||||||
|
template_arg = cg.TemplateArguments(full_id.type, *template_arg)
|
||||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
|
||||||
@@ -111,7 +197,8 @@ async def script_stop_action_to_code(config, action_id, template_arg, args):
|
|||||||
maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}),
|
maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}),
|
||||||
)
|
)
|
||||||
async def script_wait_action_to_code(config, action_id, template_arg, args):
|
async def script_wait_action_to_code(config, action_id, template_arg, args):
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
|
||||||
|
template_arg = cg.TemplateArguments(full_id.type, *template_arg)
|
||||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
await cg.register_component(var, {})
|
await cg.register_component(var, {})
|
||||||
return var
|
return var
|
||||||
@@ -123,5 +210,6 @@ async def script_wait_action_to_code(config, action_id, template_arg, args):
|
|||||||
automation.maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}),
|
automation.maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}),
|
||||||
)
|
)
|
||||||
async def script_is_running_to_code(config, condition_id, template_arg, args):
|
async def script_is_running_to_code(config, condition_id, template_arg, args):
|
||||||
paren = await cg.get_variable(config[CONF_ID])
|
full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
|
||||||
|
template_arg = cg.TemplateArguments(full_id.type, *template_arg)
|
||||||
return cg.new_Pvariable(condition_id, template_arg, paren)
|
return cg.new_Pvariable(condition_id, template_arg, paren)
|
||||||
|
|||||||
@@ -6,61 +6,8 @@ namespace script {
|
|||||||
|
|
||||||
static const char *const TAG = "script";
|
static const char *const TAG = "script";
|
||||||
|
|
||||||
void SingleScript::execute() {
|
void ScriptLogger::esp_log_(int level, int line, const char *format, const char *param) {
|
||||||
if (this->is_action_running()) {
|
esp_log_printf_(level, TAG, line, format, param);
|
||||||
ESP_LOGW(TAG, "Script '%s' is already running! (mode: single)", this->name_.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->trigger();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RestartScript::execute() {
|
|
||||||
if (this->is_action_running()) {
|
|
||||||
ESP_LOGD(TAG, "Script '%s' restarting (mode: restart)", this->name_.c_str());
|
|
||||||
this->stop_action();
|
|
||||||
}
|
|
||||||
|
|
||||||
this->trigger();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QueueingScript::execute() {
|
|
||||||
if (this->is_action_running()) {
|
|
||||||
// num_runs_ is the number of *queued* instances, so total number of instances is
|
|
||||||
// num_runs_ + 1
|
|
||||||
if (this->max_runs_ != 0 && this->num_runs_ + 1 >= this->max_runs_) {
|
|
||||||
ESP_LOGW(TAG, "Script '%s' maximum number of queued runs exceeded!", this->name_.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Script '%s' queueing new instance (mode: queued)", this->name_.c_str());
|
|
||||||
this->num_runs_++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->trigger();
|
|
||||||
// Check if the trigger was immediate and we can continue right away.
|
|
||||||
this->loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QueueingScript::stop() {
|
|
||||||
this->num_runs_ = 0;
|
|
||||||
Script::stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QueueingScript::loop() {
|
|
||||||
if (this->num_runs_ != 0 && !this->is_action_running()) {
|
|
||||||
this->num_runs_--;
|
|
||||||
this->trigger();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ParallelScript::execute() {
|
|
||||||
if (this->max_runs_ != 0 && this->automation_parent_->num_running() >= this->max_runs_) {
|
|
||||||
ESP_LOGW(TAG, "Script '%s' maximum number of parallel runs exceeded!", this->name_.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->trigger();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace script
|
} // namespace script
|
||||||
|
|||||||
@@ -2,27 +2,48 @@
|
|||||||
|
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace script {
|
namespace script {
|
||||||
|
|
||||||
|
class ScriptLogger {
|
||||||
|
protected:
|
||||||
|
void esp_logw_(int line, const char *format, const char *param) {
|
||||||
|
esp_log_(ESPHOME_LOG_LEVEL_WARN, line, format, param);
|
||||||
|
}
|
||||||
|
void esp_logd_(int line, const char *format, const char *param) {
|
||||||
|
esp_log_(ESPHOME_LOG_LEVEL_DEBUG, line, format, param);
|
||||||
|
}
|
||||||
|
void esp_log_(int level, int line, const char *format, const char *param);
|
||||||
|
};
|
||||||
|
|
||||||
/// The abstract base class for all script types.
|
/// The abstract base class for all script types.
|
||||||
class Script : public Trigger<> {
|
template<typename... Ts> class Script : public ScriptLogger, public Trigger<Ts...> {
|
||||||
public:
|
public:
|
||||||
/** Execute a new instance of this script.
|
/** Execute a new instance of this script.
|
||||||
*
|
*
|
||||||
* The behavior of this function when a script is already running is defined by the subtypes
|
* The behavior of this function when a script is already running is defined by the subtypes
|
||||||
*/
|
*/
|
||||||
virtual void execute() = 0;
|
virtual void execute(Ts...) = 0;
|
||||||
/// Check if any instance of this script is currently running.
|
/// Check if any instance of this script is currently running.
|
||||||
virtual bool is_running() { return this->is_action_running(); }
|
virtual bool is_running() { return this->is_action_running(); }
|
||||||
/// Stop all instances of this script.
|
/// Stop all instances of this script.
|
||||||
virtual void stop() { this->stop_action(); }
|
virtual void stop() { this->stop_action(); }
|
||||||
|
|
||||||
|
// execute this script using a tuple that contains the arguments
|
||||||
|
void execute_tuple(const std::tuple<Ts...> &tuple) {
|
||||||
|
this->execute_tuple_(tuple, typename gens<sizeof...(Ts)>::type());
|
||||||
|
}
|
||||||
|
|
||||||
// Internal function to give scripts readable names.
|
// Internal function to give scripts readable names.
|
||||||
void set_name(const std::string &name) { name_ = name; }
|
void set_name(const std::string &name) { name_ = name; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
template<int... S> void execute_tuple_(const std::tuple<Ts...> &tuple, seq<S...> /*unused*/) {
|
||||||
|
this->execute(std::get<S>(tuple)...);
|
||||||
|
}
|
||||||
|
|
||||||
std::string name_;
|
std::string name_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,9 +52,16 @@ class Script : public Trigger<> {
|
|||||||
* If a new instance is executed while the previous one hasn't finished yet,
|
* If a new instance is executed while the previous one hasn't finished yet,
|
||||||
* a warning is printed and the new instance is discarded.
|
* a warning is printed and the new instance is discarded.
|
||||||
*/
|
*/
|
||||||
class SingleScript : public Script {
|
template<typename... Ts> class SingleScript : public Script<Ts...> {
|
||||||
public:
|
public:
|
||||||
void execute() override;
|
void execute(Ts... x) override {
|
||||||
|
if (this->is_action_running()) {
|
||||||
|
this->esp_logw_(__LINE__, "Script '%s' is already running! (mode: single)", this->name_.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->trigger(x...);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** A script type that restarts scripts from the beginning when a new instance is started.
|
/** A script type that restarts scripts from the beginning when a new instance is started.
|
||||||
@@ -41,20 +69,55 @@ class SingleScript : public Script {
|
|||||||
* If a new instance is started but another one is already running, the existing
|
* If a new instance is started but another one is already running, the existing
|
||||||
* script is stopped and the new instance starts from the beginning.
|
* script is stopped and the new instance starts from the beginning.
|
||||||
*/
|
*/
|
||||||
class RestartScript : public Script {
|
template<typename... Ts> class RestartScript : public Script<Ts...> {
|
||||||
public:
|
public:
|
||||||
void execute() override;
|
void execute(Ts... x) override {
|
||||||
|
if (this->is_action_running()) {
|
||||||
|
this->esp_logd_(__LINE__, "Script '%s' restarting (mode: restart)", this->name_.c_str());
|
||||||
|
this->stop_action();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->trigger(x...);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** A script type that queues new instances that are created.
|
/** A script type that queues new instances that are created.
|
||||||
*
|
*
|
||||||
* Only one instance of the script can be active at a time.
|
* Only one instance of the script can be active at a time.
|
||||||
*/
|
*/
|
||||||
class QueueingScript : public Script, public Component {
|
template<typename... Ts> class QueueingScript : public Script<Ts...>, public Component {
|
||||||
public:
|
public:
|
||||||
void execute() override;
|
void execute(Ts... x) override {
|
||||||
void stop() override;
|
if (this->is_action_running()) {
|
||||||
void loop() override;
|
// num_runs_ is the number of *queued* instances, so total number of instances is
|
||||||
|
// num_runs_ + 1
|
||||||
|
if (this->max_runs_ != 0 && this->num_runs_ + 1 >= this->max_runs_) {
|
||||||
|
this->esp_logw_(__LINE__, "Script '%s' maximum number of queued runs exceeded!", this->name_.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->esp_logd_(__LINE__, "Script '%s' queueing new instance (mode: queued)", this->name_.c_str());
|
||||||
|
this->num_runs_++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->trigger(x...);
|
||||||
|
// Check if the trigger was immediate and we can continue right away.
|
||||||
|
this->loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() override {
|
||||||
|
this->num_runs_ = 0;
|
||||||
|
Script<Ts...>::stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() override {
|
||||||
|
if (this->num_runs_ != 0 && !this->is_action_running()) {
|
||||||
|
this->num_runs_--;
|
||||||
|
this->trigger();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void set_max_runs(int max_runs) { max_runs_ = max_runs; }
|
void set_max_runs(int max_runs) { max_runs_ = max_runs; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -67,48 +130,84 @@ class QueueingScript : public Script, public Component {
|
|||||||
* If a new instance is started while previous ones haven't finished yet,
|
* If a new instance is started while previous ones haven't finished yet,
|
||||||
* the new one is executed in parallel to the other instances.
|
* the new one is executed in parallel to the other instances.
|
||||||
*/
|
*/
|
||||||
class ParallelScript : public Script {
|
template<typename... Ts> class ParallelScript : public Script<Ts...> {
|
||||||
public:
|
public:
|
||||||
void execute() override;
|
void execute(Ts... x) override {
|
||||||
|
if (this->max_runs_ != 0 && this->automation_parent_->num_running() >= this->max_runs_) {
|
||||||
|
this->esp_logw_(__LINE__, "Script '%s' maximum number of parallel runs exceeded!", this->name_.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->trigger(x...);
|
||||||
|
}
|
||||||
void set_max_runs(int max_runs) { max_runs_ = max_runs; }
|
void set_max_runs(int max_runs) { max_runs_ = max_runs; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int max_runs_ = 0;
|
int max_runs_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class ScriptExecuteAction : public Action<Ts...> {
|
template<class S, typename... Ts> class ScriptExecuteAction;
|
||||||
public:
|
|
||||||
ScriptExecuteAction(Script *script) : script_(script) {}
|
|
||||||
|
|
||||||
void play(Ts... x) override { this->script_->execute(); }
|
template<class... As, typename... Ts> class ScriptExecuteAction<Script<As...>, Ts...> : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
ScriptExecuteAction(Script<As...> *script) : script_(script) {}
|
||||||
|
|
||||||
|
using Args = std::tuple<TemplatableValue<As, Ts...>...>;
|
||||||
|
|
||||||
|
template<typename... F> void set_args(F... x) { args_ = Args{x...}; }
|
||||||
|
|
||||||
|
void play(Ts... x) override { this->script_->execute_tuple(this->eval_args_(x...)); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Script *script_;
|
// NOTE:
|
||||||
|
// `eval_args_impl` functions evaluates `I`th the functions in `args` member.
|
||||||
|
// and then recursively calls `eval_args_impl` for the `I+1`th arg.
|
||||||
|
// if `I` = `N` all args have been stored, and nothing is done.
|
||||||
|
|
||||||
|
template<std::size_t N>
|
||||||
|
void eval_args_impl_(std::tuple<As...> & /*unused*/, std::integral_constant<std::size_t, N> /*unused*/,
|
||||||
|
std::integral_constant<std::size_t, N> /*unused*/, Ts... /*unused*/) {}
|
||||||
|
|
||||||
|
template<std::size_t I, std::size_t N>
|
||||||
|
void eval_args_impl_(std::tuple<As...> &evaled_args, std::integral_constant<std::size_t, I> /*unused*/,
|
||||||
|
std::integral_constant<std::size_t, N> n, Ts... x) {
|
||||||
|
std::get<I>(evaled_args) = std::get<I>(args_).value(x...); // NOTE: evaluate `i`th arg, and store in tuple.
|
||||||
|
eval_args_impl_(evaled_args, std::integral_constant<std::size_t, I + 1>{}, n,
|
||||||
|
x...); // NOTE: recurse to next index.
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<As...> eval_args_(Ts... x) {
|
||||||
|
std::tuple<As...> evaled_args;
|
||||||
|
eval_args_impl_(evaled_args, std::integral_constant<std::size_t, 0>{}, std::tuple_size<Args>{}, x...);
|
||||||
|
return evaled_args;
|
||||||
|
}
|
||||||
|
|
||||||
|
Script<As...> *script_;
|
||||||
|
Args args_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class ScriptStopAction : public Action<Ts...> {
|
template<class C, typename... Ts> class ScriptStopAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
ScriptStopAction(Script *script) : script_(script) {}
|
ScriptStopAction(C *script) : script_(script) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->script_->stop(); }
|
void play(Ts... x) override { this->script_->stop(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Script *script_;
|
C *script_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class IsRunningCondition : public Condition<Ts...> {
|
template<class C, typename... Ts> class IsRunningCondition : public Condition<Ts...> {
|
||||||
public:
|
public:
|
||||||
explicit IsRunningCondition(Script *parent) : parent_(parent) {}
|
explicit IsRunningCondition(C *parent) : parent_(parent) {}
|
||||||
|
|
||||||
bool check(Ts... x) override { return this->parent_->is_running(); }
|
bool check(Ts... x) override { return this->parent_->is_running(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Script *parent_;
|
C *parent_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class ScriptWaitAction : public Action<Ts...>, public Component {
|
template<class C, typename... Ts> class ScriptWaitAction : public Action<Ts...>, public Component {
|
||||||
public:
|
public:
|
||||||
ScriptWaitAction(Script *script) : script_(script) {}
|
ScriptWaitAction(C *script) : script_(script) {}
|
||||||
|
|
||||||
void play_complex(Ts... x) override {
|
void play_complex(Ts... x) override {
|
||||||
this->num_running_++;
|
this->num_running_++;
|
||||||
@@ -137,7 +236,7 @@ template<typename... Ts> class ScriptWaitAction : public Action<Ts...>, public C
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Script *script_;
|
C *script_;
|
||||||
std::tuple<Ts...> var_{};
|
std::tuple<Ts...> var_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_PM25,
|
DEVICE_CLASS_PM25,
|
||||||
DEVICE_CLASS_POWER,
|
DEVICE_CLASS_POWER,
|
||||||
DEVICE_CLASS_POWER_FACTOR,
|
DEVICE_CLASS_POWER_FACTOR,
|
||||||
|
DEVICE_CLASS_PRECIPITATION_INTENSITY,
|
||||||
DEVICE_CLASS_PRESSURE,
|
DEVICE_CLASS_PRESSURE,
|
||||||
DEVICE_CLASS_REACTIVE_POWER,
|
DEVICE_CLASS_REACTIVE_POWER,
|
||||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
@@ -65,6 +66,8 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
DEVICE_CLASS_VOLUME,
|
DEVICE_CLASS_VOLUME,
|
||||||
|
DEVICE_CLASS_WATER,
|
||||||
|
DEVICE_CLASS_WIND_SPEED,
|
||||||
DEVICE_CLASS_WEIGHT,
|
DEVICE_CLASS_WEIGHT,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
@@ -100,6 +103,7 @@ DEVICE_CLASSES = [
|
|||||||
DEVICE_CLASS_PM25,
|
DEVICE_CLASS_PM25,
|
||||||
DEVICE_CLASS_POWER,
|
DEVICE_CLASS_POWER,
|
||||||
DEVICE_CLASS_POWER_FACTOR,
|
DEVICE_CLASS_POWER_FACTOR,
|
||||||
|
DEVICE_CLASS_PRECIPITATION_INTENSITY,
|
||||||
DEVICE_CLASS_PRESSURE,
|
DEVICE_CLASS_PRESSURE,
|
||||||
DEVICE_CLASS_REACTIVE_POWER,
|
DEVICE_CLASS_REACTIVE_POWER,
|
||||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
@@ -110,6 +114,8 @@ DEVICE_CLASSES = [
|
|||||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
DEVICE_CLASS_VOLUME,
|
DEVICE_CLASS_VOLUME,
|
||||||
|
DEVICE_CLASS_WATER,
|
||||||
|
DEVICE_CLASS_WIND_SPEED,
|
||||||
DEVICE_CLASS_WEIGHT,
|
DEVICE_CLASS_WEIGHT,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ void SM300D2Sensor::update() {
|
|||||||
|
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
|
|
||||||
ESP_LOGW(TAG, "Successfully read SM300D2 data");
|
ESP_LOGD(TAG, "Successfully read SM300D2 data");
|
||||||
|
|
||||||
const uint16_t co2 = (response[2] * 256) + response[3];
|
const uint16_t co2 = (response[2] * 256) + response[3];
|
||||||
const uint16_t formaldehyde = (response[4] * 256) + response[5];
|
const uint16_t formaldehyde = (response[4] * 256) + response[5];
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
#include "sntp.h"
|
#include "sntp.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
#include "lwip/apps/sntp.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
// Yes, the server names are leaked, but that's fine.
|
// Yes, the server names are leaked, but that's fine.
|
||||||
#ifdef CLANG_TIDY
|
#ifdef CLANG_TIDY
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
CONF_IMPLEMENTATION,
|
CONF_IMPLEMENTATION,
|
||||||
esp8266=IMPLEMENTATION_LWIP_TCP,
|
esp8266=IMPLEMENTATION_LWIP_TCP,
|
||||||
esp32=IMPLEMENTATION_BSD_SOCKETS,
|
esp32=IMPLEMENTATION_BSD_SOCKETS,
|
||||||
|
rp2040=IMPLEMENTATION_LWIP_TCP,
|
||||||
): cv.one_of(
|
): cv.one_of(
|
||||||
IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_"
|
IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_"
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ struct iovec {
|
|||||||
size_t iov_len;
|
size_t iov_len;
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef USE_ESP8266
|
#if defined(USE_ESP8266) || defined(USE_RP2040)
|
||||||
// arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define
|
// arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define
|
||||||
#ifdef INADDR_ANY
|
#ifdef INADDR_ANY
|
||||||
#undef INADDR_ANY
|
#undef INADDR_ANY
|
||||||
|
|||||||
@@ -46,9 +46,7 @@ async def to_code(config):
|
|||||||
mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN])
|
mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN])
|
||||||
cg.add(var.set_mosi(mosi))
|
cg.add(var.set_mosi(mosi))
|
||||||
|
|
||||||
if CORE.is_esp32 and CORE.using_arduino:
|
if CORE.using_arduino:
|
||||||
cg.add_library("SPI", None)
|
|
||||||
if CORE.is_esp8266:
|
|
||||||
cg.add_library("SPI", None)
|
cg.add_library("SPI", None)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,11 @@ class SPIComponent : public Component {
|
|||||||
void write_byte(uint8_t data) {
|
void write_byte(uint8_t data) {
|
||||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||||
if (this->hw_spi_ != nullptr) {
|
if (this->hw_spi_ != nullptr) {
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
this->hw_spi_->transfer(data);
|
||||||
|
#else
|
||||||
this->hw_spi_->write(data);
|
this->hw_spi_->write(data);
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif // USE_SPI_ARDUINO_BACKEND
|
#endif // USE_SPI_ARDUINO_BACKEND
|
||||||
@@ -116,7 +120,11 @@ class SPIComponent : public Component {
|
|||||||
void write_byte16(const uint16_t data) {
|
void write_byte16(const uint16_t data) {
|
||||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||||
if (this->hw_spi_ != nullptr) {
|
if (this->hw_spi_ != nullptr) {
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
this->hw_spi_->transfer16(data);
|
||||||
|
#else
|
||||||
this->hw_spi_->write16(data);
|
this->hw_spi_->write16(data);
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif // USE_SPI_ARDUINO_BACKEND
|
#endif // USE_SPI_ARDUINO_BACKEND
|
||||||
@@ -130,7 +138,11 @@ class SPIComponent : public Component {
|
|||||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||||
if (this->hw_spi_ != nullptr) {
|
if (this->hw_spi_ != nullptr) {
|
||||||
for (size_t i = 0; i < length; i++) {
|
for (size_t i = 0; i < length; i++) {
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
this->hw_spi_->transfer16(data[i]);
|
||||||
|
#else
|
||||||
this->hw_spi_->write16(data[i]);
|
this->hw_spi_->write16(data[i]);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -145,7 +157,11 @@ class SPIComponent : public Component {
|
|||||||
#ifdef USE_SPI_ARDUINO_BACKEND
|
#ifdef USE_SPI_ARDUINO_BACKEND
|
||||||
if (this->hw_spi_ != nullptr) {
|
if (this->hw_spi_ != nullptr) {
|
||||||
auto *data_c = const_cast<uint8_t *>(data);
|
auto *data_c = const_cast<uint8_t *>(data);
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
this->hw_spi_->transfer(data_c, length);
|
||||||
|
#else
|
||||||
this->hw_spi_->writeBytes(data_c, length);
|
this->hw_spi_->writeBytes(data_c, length);
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif // USE_SPI_ARDUINO_BACKEND
|
#endif // USE_SPI_ARDUINO_BACKEND
|
||||||
@@ -178,7 +194,11 @@ class SPIComponent : public Component {
|
|||||||
if (this->miso_ != nullptr) {
|
if (this->miso_ != nullptr) {
|
||||||
this->hw_spi_->transfer(data, length);
|
this->hw_spi_->transfer(data, length);
|
||||||
} else {
|
} else {
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
this->hw_spi_->transfer(data, length);
|
||||||
|
#else
|
||||||
this->hw_spi_->writeBytes(data, length);
|
this->hw_spi_->writeBytes(data, length);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -205,7 +225,11 @@ class SPIComponent : public Component {
|
|||||||
} else if (CLOCK_POLARITY && CLOCK_PHASE) {
|
} else if (CLOCK_POLARITY && CLOCK_PHASE) {
|
||||||
data_mode = SPI_MODE3;
|
data_mode = SPI_MODE3;
|
||||||
}
|
}
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
SPISettings settings(DATA_RATE, static_cast<BitOrder>(BIT_ORDER), data_mode);
|
||||||
|
#else
|
||||||
SPISettings settings(DATA_RATE, BIT_ORDER, data_mode);
|
SPISettings settings(DATA_RATE, BIT_ORDER, data_mode);
|
||||||
|
#endif
|
||||||
this->hw_spi_->beginTransaction(settings);
|
this->hw_spi_->beginTransaction(settings);
|
||||||
} else {
|
} else {
|
||||||
#endif // USE_SPI_ARDUINO_BACKEND
|
#endif // USE_SPI_ARDUINO_BACKEND
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ optional<std::string> ToUpperFilter::new_value(std::string value) {
|
|||||||
// ToLowerFilter
|
// ToLowerFilter
|
||||||
optional<std::string> ToLowerFilter::new_value(std::string value) {
|
optional<std::string> ToLowerFilter::new_value(std::string value) {
|
||||||
for (char &c : value)
|
for (char &c : value)
|
||||||
c = ::toupper(c);
|
c = ::tolower(c);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
#include "sys/time.h"
|
#include "sys/time.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
#include <sys/time.h>
|
||||||
|
#endif
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ ESP32ArduinoUARTComponent = uart_ns.class_(
|
|||||||
ESP8266UartComponent = uart_ns.class_(
|
ESP8266UartComponent = uart_ns.class_(
|
||||||
"ESP8266UartComponent", UARTComponent, cg.Component
|
"ESP8266UartComponent", UARTComponent, cg.Component
|
||||||
)
|
)
|
||||||
|
RP2040UartComponent = uart_ns.class_("RP2040UartComponent", UARTComponent, cg.Component)
|
||||||
|
|
||||||
UARTDevice = uart_ns.class_("UARTDevice")
|
UARTDevice = uart_ns.class_("UARTDevice")
|
||||||
UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action)
|
UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action)
|
||||||
@@ -89,6 +90,8 @@ def _uart_declare_type(value):
|
|||||||
return cv.declare_id(ESP32ArduinoUARTComponent)(value)
|
return cv.declare_id(ESP32ArduinoUARTComponent)(value)
|
||||||
if CORE.using_esp_idf:
|
if CORE.using_esp_idf:
|
||||||
return cv.declare_id(IDFUARTComponent)(value)
|
return cv.declare_id(IDFUARTComponent)(value)
|
||||||
|
if CORE.is_rp2040:
|
||||||
|
return cv.declare_id(RP2040UartComponent)(value)
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ void ESP32ArduinoUARTComponent::setup() {
|
|||||||
this->hw_serial_ = &Serial;
|
this->hw_serial_ = &Serial;
|
||||||
} else {
|
} else {
|
||||||
static uint8_t next_uart_num = 1;
|
static uint8_t next_uart_num = 1;
|
||||||
|
this->number_ = next_uart_num;
|
||||||
this->hw_serial_ = new HardwareSerial(next_uart_num++); // NOLINT(cppcoreguidelines-owning-memory)
|
this->hw_serial_ = new HardwareSerial(next_uart_num++); // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
}
|
}
|
||||||
int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1;
|
int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1;
|
||||||
@@ -104,7 +105,7 @@ void ESP32ArduinoUARTComponent::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ESP32ArduinoUARTComponent::dump_config() {
|
void ESP32ArduinoUARTComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "UART Bus:");
|
ESP_LOGCONFIG(TAG, "UART Bus %d:", this->number_);
|
||||||
LOG_PIN(" TX Pin: ", tx_pin_);
|
LOG_PIN(" TX Pin: ", tx_pin_);
|
||||||
LOG_PIN(" RX Pin: ", rx_pin_);
|
LOG_PIN(" RX Pin: ", rx_pin_);
|
||||||
if (this->rx_pin_ != nullptr) {
|
if (this->rx_pin_ != nullptr) {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class ESP32ArduinoUARTComponent : public UARTComponent, public Component {
|
|||||||
void check_logger_conflict() override;
|
void check_logger_conflict() override;
|
||||||
|
|
||||||
HardwareSerial *hw_serial_{nullptr};
|
HardwareSerial *hw_serial_{nullptr};
|
||||||
|
uint8_t number_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace uart
|
} // namespace uart
|
||||||
|
|||||||
184
esphome/components/uart/uart_component_rp2040.cpp
Normal file
184
esphome/components/uart/uart_component_rp2040.cpp
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
#ifdef USE_RP2040
|
||||||
|
#include "uart_component_rp2040.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <hardware/uart.h>
|
||||||
|
|
||||||
|
#ifdef USE_LOGGER
|
||||||
|
#include "esphome/components/logger/logger.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace uart {
|
||||||
|
|
||||||
|
static const char *const TAG = "uart.arduino_rp2040";
|
||||||
|
|
||||||
|
uint16_t RP2040UartComponent::get_config() {
|
||||||
|
uint16_t config = 0;
|
||||||
|
|
||||||
|
if (this->parity_ == UART_CONFIG_PARITY_NONE) {
|
||||||
|
config |= UART_PARITY_NONE;
|
||||||
|
} else if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
|
||||||
|
config |= UART_PARITY_EVEN;
|
||||||
|
} else if (this->parity_ == UART_CONFIG_PARITY_ODD) {
|
||||||
|
config |= UART_PARITY_ODD;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this->data_bits_) {
|
||||||
|
case 5:
|
||||||
|
config |= SERIAL_DATA_5;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
config |= SERIAL_DATA_6;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
config |= SERIAL_DATA_7;
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
config |= SERIAL_DATA_8;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->stop_bits_ == 1) {
|
||||||
|
config |= SERIAL_STOP_BIT_1;
|
||||||
|
} else {
|
||||||
|
config |= SERIAL_STOP_BIT_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RP2040UartComponent::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up UART bus...");
|
||||||
|
|
||||||
|
uint16_t config = get_config();
|
||||||
|
|
||||||
|
constexpr uint32_t valid_tx_uart_0 = __bitset({0, 12, 16, 28});
|
||||||
|
constexpr uint32_t valid_tx_uart_1 = __bitset({4, 8, 20, 24});
|
||||||
|
|
||||||
|
constexpr uint32_t valid_rx_uart_0 = __bitset({1, 13, 17, 29});
|
||||||
|
constexpr uint32_t valid_rx_uart_1 = __bitset({5, 9, 21, 25});
|
||||||
|
|
||||||
|
int8_t tx_hw = -1;
|
||||||
|
int8_t rx_hw = -1;
|
||||||
|
|
||||||
|
if (this->tx_pin_ != nullptr) {
|
||||||
|
if (this->tx_pin_->is_inverted()) {
|
||||||
|
ESP_LOGD(TAG, "An inverted TX pin %u can only be used with SerialPIO", this->tx_pin_->get_pin());
|
||||||
|
} else {
|
||||||
|
if (((1 << this->tx_pin_->get_pin()) & valid_tx_uart_0) != 0) {
|
||||||
|
tx_hw = 0;
|
||||||
|
} else if (((1 << this->tx_pin_->get_pin()) & valid_tx_uart_1) != 0) {
|
||||||
|
tx_hw = 1;
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "TX pin %u can only be used with SerialPIO", this->tx_pin_->get_pin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->rx_pin_ != nullptr) {
|
||||||
|
if (this->rx_pin_->is_inverted()) {
|
||||||
|
ESP_LOGD(TAG, "An inverted RX pin %u can only be used with SerialPIO", this->rx_pin_->get_pin());
|
||||||
|
} else {
|
||||||
|
if (((1 << this->rx_pin_->get_pin()) & valid_rx_uart_0) != 0) {
|
||||||
|
rx_hw = 0;
|
||||||
|
} else if (((1 << this->rx_pin_->get_pin()) & valid_rx_uart_1) != 0) {
|
||||||
|
rx_hw = 1;
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "RX pin %u can only be used with SerialPIO", this->rx_pin_->get_pin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_LOGGER
|
||||||
|
if (tx_hw == rx_hw && logger::global_logger->get_uart() == tx_hw) {
|
||||||
|
ESP_LOGD(TAG, "Using SerialPIO as UART%d is taken by the logger", tx_hw);
|
||||||
|
tx_hw = -1;
|
||||||
|
rx_hw = -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (tx_hw == -1 || rx_hw == -1 || tx_hw != rx_hw) {
|
||||||
|
ESP_LOGV(TAG, "Using SerialPIO");
|
||||||
|
pin_size_t tx = this->tx_pin_ == nullptr ? SerialPIO::NOPIN : this->tx_pin_->get_pin();
|
||||||
|
pin_size_t rx = this->rx_pin_ == nullptr ? SerialPIO::NOPIN : this->rx_pin_->get_pin();
|
||||||
|
auto *serial = new SerialPIO(tx, rx, this->rx_buffer_size_); // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
serial->begin(this->baud_rate_, config);
|
||||||
|
if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted())
|
||||||
|
gpio_set_outover(tx, GPIO_OVERRIDE_INVERT);
|
||||||
|
if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted())
|
||||||
|
gpio_set_inover(rx, GPIO_OVERRIDE_INVERT);
|
||||||
|
this->serial_ = serial;
|
||||||
|
} else {
|
||||||
|
ESP_LOGV(TAG, "Using Hardware Serial");
|
||||||
|
SerialUART *serial;
|
||||||
|
if (tx_hw == 0) {
|
||||||
|
serial = &Serial1;
|
||||||
|
} else {
|
||||||
|
serial = &Serial2;
|
||||||
|
}
|
||||||
|
serial->setTX(this->tx_pin_->get_pin());
|
||||||
|
serial->setRX(this->rx_pin_->get_pin());
|
||||||
|
serial->setFIFOSize(this->rx_buffer_size_);
|
||||||
|
serial->begin(this->baud_rate_, config);
|
||||||
|
this->serial_ = serial;
|
||||||
|
this->hw_serial_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RP2040UartComponent::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "UART Bus:");
|
||||||
|
LOG_PIN(" TX Pin: ", tx_pin_);
|
||||||
|
LOG_PIN(" RX Pin: ", rx_pin_);
|
||||||
|
if (this->rx_pin_ != nullptr) {
|
||||||
|
ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_);
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Data Bits: %u", this->data_bits_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Parity: %s", LOG_STR_ARG(parity_to_str(this->parity_)));
|
||||||
|
ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_);
|
||||||
|
if (this->hw_serial_) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Using hardware serial");
|
||||||
|
} else {
|
||||||
|
ESP_LOGCONFIG(TAG, " Using SerialPIO");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RP2040UartComponent::write_array(const uint8_t *data, size_t len) {
|
||||||
|
this->serial_->write(data, len);
|
||||||
|
#ifdef USE_UART_DEBUGGER
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
bool RP2040UartComponent::peek_byte(uint8_t *data) {
|
||||||
|
if (!this->check_read_timeout_())
|
||||||
|
return false;
|
||||||
|
*data = this->serial_->peek();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool RP2040UartComponent::read_array(uint8_t *data, size_t len) {
|
||||||
|
if (!this->check_read_timeout_(len))
|
||||||
|
return false;
|
||||||
|
this->serial_->readBytes(data, len);
|
||||||
|
#ifdef USE_UART_DEBUGGER
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
int RP2040UartComponent::available() { return this->serial_->available(); }
|
||||||
|
void RP2040UartComponent::flush() {
|
||||||
|
ESP_LOGVV(TAG, " Flushing...");
|
||||||
|
this->serial_->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace uart
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_RP2040
|
||||||
43
esphome/components/uart/uart_component_rp2040.h
Normal file
43
esphome/components/uart/uart_component_rp2040.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
|
||||||
|
#include <SerialPIO.h>
|
||||||
|
#include <SerialUART.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "uart_component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace uart {
|
||||||
|
|
||||||
|
class RP2040UartComponent : public UARTComponent, public Component {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||||
|
|
||||||
|
void write_array(const uint8_t *data, size_t len) override;
|
||||||
|
|
||||||
|
bool peek_byte(uint8_t *data) override;
|
||||||
|
bool read_array(uint8_t *data, size_t len) override;
|
||||||
|
|
||||||
|
int available() override;
|
||||||
|
void flush() override;
|
||||||
|
|
||||||
|
uint16_t get_config();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void check_logger_conflict() override {}
|
||||||
|
bool hw_serial_{false};
|
||||||
|
|
||||||
|
HardwareSerial *serial_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace uart
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_RP2040
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user