mirror of
https://github.com/esphome/esphome.git
synced 2025-03-21 18:18:13 +00:00
commit
c4de9e87e4
@ -31,7 +31,7 @@
|
||||
"ms-python.python",
|
||||
"ms-python.pylint",
|
||||
"ms-python.flake8",
|
||||
"ms-python.black-formatter",
|
||||
"charliermarsh.ruff",
|
||||
"visualstudioexptteam.vscodeintellicode",
|
||||
// yaml
|
||||
"redhat.vscode-yaml",
|
||||
@ -49,14 +49,11 @@
|
||||
"flake8.args": [
|
||||
"--config=${workspaceFolder}/.flake8"
|
||||
],
|
||||
"black-formatter.args": [
|
||||
"--config",
|
||||
"${workspaceFolder}/pyproject.toml"
|
||||
],
|
||||
"ruff.configuration": "${workspaceFolder}/pyproject.toml",
|
||||
"[python]": {
|
||||
// VS will say "Value is not accepted" before building the devcontainer, but the warning
|
||||
// should go away after build is completed.
|
||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||
"editor.defaultFormatter": "charliermarsh.ruff"
|
||||
},
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
|
4
.github/actions/build-image/action.yaml
vendored
4
.github/actions/build-image/action.yaml
vendored
@ -46,7 +46,7 @@ runs:
|
||||
|
||||
- name: Build and push to ghcr by digest
|
||||
id: build-ghcr
|
||||
uses: docker/build-push-action@v6.13.0
|
||||
uses: docker/build-push-action@v6.15.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
@ -72,7 +72,7 @@ runs:
|
||||
|
||||
- name: Build and push to dockerhub by digest
|
||||
id: build-dockerhub
|
||||
uses: docker/build-push-action@v6.13.0
|
||||
uses: docker/build-push-action@v6.15.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
|
2
.github/actions/restore-python/action.yml
vendored
2
.github/actions/restore-python/action.yml
vendored
@ -22,7 +22,7 @@ runs:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
|
10
.github/workflows/ci-docker.yml
vendored
10
.github/workflows/ci-docker.yml
vendored
@ -33,11 +33,11 @@ concurrency:
|
||||
jobs:
|
||||
check-docker:
|
||||
name: Build docker containers
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [amd64, aarch64]
|
||||
os: ["ubuntu-latest", "ubuntu-24.04-arm"]
|
||||
build_type: ["ha-addon", "docker", "lint"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
@ -46,9 +46,7 @@ jobs:
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.9.0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.4.0
|
||||
uses: docker/setup-buildx-action@v3.10.0
|
||||
|
||||
- name: Set TAG
|
||||
run: |
|
||||
@ -58,6 +56,6 @@ jobs:
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${TAG}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--arch "${{ matrix.os == 'ubuntu-24.04-arm' && 'aarch64' || 'amd64' }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
build
|
||||
|
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
@ -47,7 +47,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.0
|
||||
uses: actions/cache@v4.2.2
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
@ -61,8 +61,8 @@ jobs:
|
||||
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
|
||||
pip install -e .
|
||||
|
||||
black:
|
||||
name: Check black
|
||||
ruff:
|
||||
name: Check ruff
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
@ -74,10 +74,10 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Run black
|
||||
- name: Run Ruff
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
black --verbose esphome tests
|
||||
ruff format esphome tests
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
if: always()
|
||||
@ -255,7 +255,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
- black
|
||||
- ruff
|
||||
- ci-custom
|
||||
- clang-format
|
||||
- flake8
|
||||
@ -303,14 +303,14 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache@v4.2.0
|
||||
uses: actions/cache@v4.2.2
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref != 'refs/heads/dev'
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}
|
||||
@ -482,7 +482,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
- black
|
||||
- ruff
|
||||
- ci-custom
|
||||
- clang-format
|
||||
- flake8
|
||||
|
4
.github/workflows/matchers/lint-python.json
vendored
4
.github/workflows/matchers/lint-python.json
vendored
@ -1,11 +1,11 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "black",
|
||||
"owner": "ruff",
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*): (Please format this file with the black formatter)",
|
||||
"regexp": "^(.*): (Please format this file with the ruff formatter)",
|
||||
"file": 1,
|
||||
"message": 2
|
||||
}
|
||||
|
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@ -89,10 +89,10 @@ jobs:
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.9.0
|
||||
uses: docker/setup-buildx-action@v3.10.0
|
||||
- name: Set up QEMU
|
||||
if: matrix.platform != 'linux/amd64'
|
||||
uses: docker/setup-qemu-action@v3.4.0
|
||||
uses: docker/setup-qemu-action@v3.6.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v3.3.0
|
||||
@ -140,7 +140,7 @@ jobs:
|
||||
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload digests
|
||||
uses: actions/upload-artifact@v4.6.0
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
with:
|
||||
name: digests-${{ steps.sanitize.outputs.name }}
|
||||
path: /tmp/digests
|
||||
@ -176,14 +176,14 @@ jobs:
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
uses: actions/download-artifact@v4.1.9
|
||||
with:
|
||||
pattern: digests-*
|
||||
path: /tmp/digests
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.9.0
|
||||
uses: docker/setup-buildx-action@v3.10.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
if: matrix.registry == 'dockerhub'
|
||||
|
2
.github/workflows/sync-device-classes.yml
vendored
2
.github/workflows/sync-device-classes.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
||||
python ./script/sync-device_class.py
|
||||
|
||||
- name: Commit changes
|
||||
uses: peter-evans/create-pull-request@v7.0.6
|
||||
uses: peter-evans/create-pull-request@v7.0.7
|
||||
with:
|
||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||
committer: esphomebot <esphome@nabucasa.com>
|
||||
|
@ -4,7 +4,7 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.5.4
|
||||
rev: v0.9.2
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
@ -93,6 +93,7 @@ esphome/components/captive_portal/* @OttoWinter
|
||||
esphome/components/ccs811/* @habbie
|
||||
esphome/components/cd74hc4067/* @asoehlke
|
||||
esphome/components/ch422g/* @clydebarrow @jesterret
|
||||
esphome/components/chsc6x/* @kkosik20
|
||||
esphome/components/climate/* @esphome/core
|
||||
esphome/components/climate_ir/* @glmnet
|
||||
esphome/components/color_temperature/* @jesserockz
|
||||
@ -234,6 +235,7 @@ esphome/components/kuntze/* @ssieb
|
||||
esphome/components/lcd_menu/* @numo68
|
||||
esphome/components/ld2410/* @regevbr @sebcaps
|
||||
esphome/components/ld2420/* @descipher
|
||||
esphome/components/ld2450/* @hareeshmu
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/libretiny/* @kuba2k2
|
||||
esphome/components/libretiny_pwm/* @kuba2k2
|
||||
@ -297,6 +299,7 @@ esphome/components/mopeka_std_check/* @Fabian-Schmidt
|
||||
esphome/components/mpl3115a2/* @kbickar
|
||||
esphome/components/mpu6886/* @fabaff
|
||||
esphome/components/ms8607/* @e28eta
|
||||
esphome/components/msa3xx/* @latonita
|
||||
esphome/components/nau7802/* @cujomalainey
|
||||
esphome/components/network/* @esphome/core
|
||||
esphome/components/nextion/* @edwardtfn @senexcrenshaw
|
||||
@ -445,6 +448,7 @@ esphome/components/tmp102/* @timsavage
|
||||
esphome/components/tmp1075/* @sybrenstuvel
|
||||
esphome/components/tmp117/* @Azimath
|
||||
esphome/components/tof10120/* @wstrzalka
|
||||
esphome/components/tormatic/* @ti-mo
|
||||
esphome/components/toshiba/* @kbx81
|
||||
esphome/components/touchscreen/* @jesserockz @nielsnl68
|
||||
esphome/components/tsl2591/* @wjcarpenter
|
||||
|
@ -33,9 +33,9 @@ RUN \
|
||||
python3-venv=3.11.2-1+b1 \
|
||||
python3-wheel=0.38.4-2 \
|
||||
iputils-ping=3:20221126-1+deb12u1 \
|
||||
git=1:2.39.5-0+deb12u1 \
|
||||
curl=7.88.1-10+deb12u8 \
|
||||
openssh-client=1:9.2p1-2+deb12u4 \
|
||||
git=1:2.39.5-0+deb12u2 \
|
||||
curl=7.88.1-10+deb12u12 \
|
||||
openssh-client=1:9.2p1-2+deb12u5 \
|
||||
python3-cffi=1.15.1-5 \
|
||||
libcairo2=1.16.0-7 \
|
||||
libmagic1=1:5.44-3 \
|
||||
@ -76,7 +76,7 @@ BUILD_DEPS="
|
||||
python3-dev=3.11.2-1+b1
|
||||
zlib1g-dev=1:1.2.13.dfsg-1
|
||||
libjpeg-dev=1:2.1.5-2
|
||||
libfreetype-dev=2.12.1+dfsg-5+deb12u3
|
||||
libfreetype-dev=2.12.1+dfsg-5+deb12u4
|
||||
libssl-dev=3.0.15-1~deb12u1
|
||||
libffi-dev=3.4.4-1
|
||||
cargo=0.66.0+ds1-1
|
||||
@ -84,7 +84,7 @@ BUILD_DEPS="
|
||||
"
|
||||
LIB_DEPS="
|
||||
libtiff6=4.5.0-6+deb12u1
|
||||
libopenjp2-7=2.5.0-2
|
||||
libopenjp2-7=2.5.0-2+deb12u1
|
||||
"
|
||||
if [ "$TARGETARCH$TARGETVARIANT" = "arm64" ]
|
||||
then
|
||||
@ -160,7 +160,7 @@ RUN \
|
||||
apt-get update \
|
||||
# Use pinned versions so that we get updates with build caching
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
nginx-light=1.22.1-9 \
|
||||
nginx-light=1.22.1-9+deb12u1 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
/var/{cache,log}/* \
|
||||
|
@ -23,10 +23,6 @@ if bashio::config.true 'streamer_mode'; then
|
||||
export ESPHOME_STREAMER_MODE=true
|
||||
fi
|
||||
|
||||
if bashio::config.true 'status_use_ping'; then
|
||||
export ESPHOME_DASHBOARD_USE_PING=true
|
||||
fi
|
||||
|
||||
if bashio::config.has_value 'relative_url'; then
|
||||
export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url')
|
||||
fi
|
||||
|
@ -2,6 +2,7 @@
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
import functools
|
||||
import importlib
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
@ -66,7 +67,7 @@ def choose_prompt(options, purpose: str = None):
|
||||
return options[0][1]
|
||||
|
||||
safe_print(
|
||||
f'Found multiple options{f" for {purpose}" if purpose else ""}, please choose one:'
|
||||
f"Found multiple options{f' for {purpose}' if purpose else ''}, please choose one:"
|
||||
)
|
||||
for i, (desc, _) in enumerate(options):
|
||||
safe_print(f" [{i + 1}] {desc}")
|
||||
@ -336,6 +337,13 @@ def check_permissions(port):
|
||||
|
||||
|
||||
def upload_program(config, args, host):
|
||||
try:
|
||||
module = importlib.import_module("esphome.components." + CORE.target_platform)
|
||||
if getattr(module, "upload_program")(config, args, host):
|
||||
return 0
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if get_port_type(host) == "SERIAL":
|
||||
check_permissions(host)
|
||||
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
|
||||
|
@ -227,6 +227,9 @@ message DeviceInfoResponse {
|
||||
uint32 voice_assistant_feature_flags = 17;
|
||||
|
||||
string suggested_area = 16;
|
||||
|
||||
// The Bluetooth mac address of the device. For example "AC:BC:32:89:0E:AA"
|
||||
string bluetooth_mac_address = 18;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
@ -1564,6 +1567,8 @@ message VoiceAssistantAnnounceRequest {
|
||||
|
||||
string media_id = 1;
|
||||
string text = 2;
|
||||
string preannounce_media_id = 3;
|
||||
bool start_conversation = 4;
|
||||
}
|
||||
|
||||
message VoiceAssistantAnnounceFinished {
|
||||
|
@ -28,8 +28,38 @@ namespace api {
|
||||
static const char *const TAG = "api.connection";
|
||||
static const int ESP32_CAMERA_STOP_STREAM = 5000;
|
||||
|
||||
// helper for allowing only unique entries in the queue
|
||||
void DeferredMessageQueue::dmq_push_back_with_dedup_(void *source, send_message_t *send_message) {
|
||||
DeferredMessage item(source, send_message);
|
||||
|
||||
auto iter = std::find_if(this->deferred_queue_.begin(), this->deferred_queue_.end(),
|
||||
[&item](const DeferredMessage &test) -> bool { return test == item; });
|
||||
|
||||
if (iter != this->deferred_queue_.end()) {
|
||||
(*iter) = item;
|
||||
} else {
|
||||
this->deferred_queue_.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
void DeferredMessageQueue::process_queue() {
|
||||
while (!deferred_queue_.empty()) {
|
||||
DeferredMessage &de = deferred_queue_.front();
|
||||
if (de.send_message_(this->api_connection_, de.source_)) {
|
||||
// O(n) but memory efficiency is more important than speed here which is why std::vector was chosen
|
||||
deferred_queue_.erase(deferred_queue_.begin());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeferredMessageQueue::defer(void *source, send_message_t *send_message) {
|
||||
this->dmq_push_back_with_dedup_(source, send_message);
|
||||
}
|
||||
|
||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
|
||||
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
|
||||
: parent_(parent), deferred_message_queue_(this), initial_state_iterator_(this), list_entities_iterator_(this) {
|
||||
this->proto_write_buffer_.reserve(64);
|
||||
|
||||
#if defined(USE_API_PLAINTEXT)
|
||||
@ -116,7 +146,11 @@ void APIConnection::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
this->deferred_message_queue_.process_queue();
|
||||
|
||||
if (!this->list_entities_iterator_.completed())
|
||||
this->list_entities_iterator_.advance();
|
||||
if (!this->initial_state_iterator_.completed() && this->list_entities_iterator_.completed())
|
||||
this->initial_state_iterator_.advance();
|
||||
|
||||
static uint32_t keepalive = 60000;
|
||||
@ -210,13 +244,31 @@ bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_binary_sensor_state(this, binary_sensor, state)) {
|
||||
this->deferred_message_queue_.defer(binary_sensor, try_send_binary_sensor_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) {
|
||||
if (!APIConnection::try_send_binary_sensor_info(this, binary_sensor)) {
|
||||
this->deferred_message_queue_.defer(binary_sensor, try_send_binary_sensor_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_binary_sensor_state(APIConnection *api, void *v_binary_sensor) {
|
||||
binary_sensor::BinarySensor *binary_sensor = reinterpret_cast<binary_sensor::BinarySensor *>(v_binary_sensor);
|
||||
return APIConnection::try_send_binary_sensor_state(api, binary_sensor, binary_sensor->state);
|
||||
}
|
||||
bool APIConnection::try_send_binary_sensor_state(APIConnection *api, binary_sensor::BinarySensor *binary_sensor,
|
||||
bool state) {
|
||||
BinarySensorStateResponse resp;
|
||||
resp.key = binary_sensor->get_object_id_hash();
|
||||
resp.state = state;
|
||||
resp.missing_state = !binary_sensor->has_state();
|
||||
return this->send_binary_sensor_state_response(resp);
|
||||
return api->send_binary_sensor_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor) {
|
||||
bool APIConnection::try_send_binary_sensor_info(APIConnection *api, void *v_binary_sensor) {
|
||||
binary_sensor::BinarySensor *binary_sensor = reinterpret_cast<binary_sensor::BinarySensor *>(v_binary_sensor);
|
||||
ListEntitiesBinarySensorResponse msg;
|
||||
msg.object_id = binary_sensor->get_object_id();
|
||||
msg.key = binary_sensor->get_object_id_hash();
|
||||
@ -228,7 +280,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
|
||||
msg.disabled_by_default = binary_sensor->is_disabled_by_default();
|
||||
msg.icon = binary_sensor->get_icon();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(binary_sensor->get_entity_category());
|
||||
return this->send_list_entities_binary_sensor_response(msg);
|
||||
return api->send_list_entities_binary_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -237,6 +289,19 @@ bool APIConnection::send_cover_state(cover::Cover *cover) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_cover_state(this, cover)) {
|
||||
this->deferred_message_queue_.defer(cover, try_send_cover_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_cover_info(cover::Cover *cover) {
|
||||
if (!APIConnection::try_send_cover_info(this, cover)) {
|
||||
this->deferred_message_queue_.defer(cover, try_send_cover_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_cover_state(APIConnection *api, void *v_cover) {
|
||||
cover::Cover *cover = reinterpret_cast<cover::Cover *>(v_cover);
|
||||
auto traits = cover->get_traits();
|
||||
CoverStateResponse resp{};
|
||||
resp.key = cover->get_object_id_hash();
|
||||
@ -246,9 +311,10 @@ bool APIConnection::send_cover_state(cover::Cover *cover) {
|
||||
if (traits.get_supports_tilt())
|
||||
resp.tilt = cover->tilt;
|
||||
resp.current_operation = static_cast<enums::CoverOperation>(cover->current_operation);
|
||||
return this->send_cover_state_response(resp);
|
||||
return api->send_cover_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_cover_info(cover::Cover *cover) {
|
||||
bool APIConnection::try_send_cover_info(APIConnection *api, void *v_cover) {
|
||||
cover::Cover *cover = reinterpret_cast<cover::Cover *>(v_cover);
|
||||
auto traits = cover->get_traits();
|
||||
ListEntitiesCoverResponse msg;
|
||||
msg.key = cover->get_object_id_hash();
|
||||
@ -264,7 +330,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
|
||||
msg.disabled_by_default = cover->is_disabled_by_default();
|
||||
msg.icon = cover->get_icon();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(cover->get_entity_category());
|
||||
return this->send_list_entities_cover_response(msg);
|
||||
return api->send_list_entities_cover_response(msg);
|
||||
}
|
||||
void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||
cover::Cover *cover = App.get_cover_by_key(msg.key);
|
||||
@ -300,6 +366,19 @@ bool APIConnection::send_fan_state(fan::Fan *fan) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_fan_state(this, fan)) {
|
||||
this->deferred_message_queue_.defer(fan, try_send_fan_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_fan_info(fan::Fan *fan) {
|
||||
if (!APIConnection::try_send_fan_info(this, fan)) {
|
||||
this->deferred_message_queue_.defer(fan, try_send_fan_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_fan_state(APIConnection *api, void *v_fan) {
|
||||
fan::Fan *fan = reinterpret_cast<fan::Fan *>(v_fan);
|
||||
auto traits = fan->get_traits();
|
||||
FanStateResponse resp{};
|
||||
resp.key = fan->get_object_id_hash();
|
||||
@ -313,9 +392,10 @@ bool APIConnection::send_fan_state(fan::Fan *fan) {
|
||||
resp.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||
if (traits.supports_preset_modes())
|
||||
resp.preset_mode = fan->preset_mode;
|
||||
return this->send_fan_state_response(resp);
|
||||
return api->send_fan_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_fan_info(fan::Fan *fan) {
|
||||
bool APIConnection::try_send_fan_info(APIConnection *api, void *v_fan) {
|
||||
fan::Fan *fan = reinterpret_cast<fan::Fan *>(v_fan);
|
||||
auto traits = fan->get_traits();
|
||||
ListEntitiesFanResponse msg;
|
||||
msg.key = fan->get_object_id_hash();
|
||||
@ -332,7 +412,7 @@ bool APIConnection::send_fan_info(fan::Fan *fan) {
|
||||
msg.disabled_by_default = fan->is_disabled_by_default();
|
||||
msg.icon = fan->get_icon();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(fan->get_entity_category());
|
||||
return this->send_list_entities_fan_response(msg);
|
||||
return api->send_list_entities_fan_response(msg);
|
||||
}
|
||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
fan::Fan *fan = App.get_fan_by_key(msg.key);
|
||||
@ -361,6 +441,19 @@ bool APIConnection::send_light_state(light::LightState *light) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_light_state(this, light)) {
|
||||
this->deferred_message_queue_.defer(light, try_send_light_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_light_info(light::LightState *light) {
|
||||
if (!APIConnection::try_send_light_info(this, light)) {
|
||||
this->deferred_message_queue_.defer(light, try_send_light_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_light_state(APIConnection *api, void *v_light) {
|
||||
light::LightState *light = reinterpret_cast<light::LightState *>(v_light);
|
||||
auto traits = light->get_traits();
|
||||
auto values = light->remote_values;
|
||||
auto color_mode = values.get_color_mode();
|
||||
@ -380,9 +473,10 @@ bool APIConnection::send_light_state(light::LightState *light) {
|
||||
resp.warm_white = values.get_warm_white();
|
||||
if (light->supports_effects())
|
||||
resp.effect = light->get_effect_name();
|
||||
return this->send_light_state_response(resp);
|
||||
return api->send_light_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_light_info(light::LightState *light) {
|
||||
bool APIConnection::try_send_light_info(APIConnection *api, void *v_light) {
|
||||
light::LightState *light = reinterpret_cast<light::LightState *>(v_light);
|
||||
auto traits = light->get_traits();
|
||||
ListEntitiesLightResponse msg;
|
||||
msg.key = light->get_object_id_hash();
|
||||
@ -415,7 +509,7 @@ bool APIConnection::send_light_info(light::LightState *light) {
|
||||
for (auto *effect : light->get_effects())
|
||||
msg.effects.push_back(effect->get_name());
|
||||
}
|
||||
return this->send_list_entities_light_response(msg);
|
||||
return api->send_list_entities_light_response(msg);
|
||||
}
|
||||
void APIConnection::light_command(const LightCommandRequest &msg) {
|
||||
light::LightState *light = App.get_light_by_key(msg.key);
|
||||
@ -459,13 +553,30 @@ bool APIConnection::send_sensor_state(sensor::Sensor *sensor, float state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_sensor_state(this, sensor, state)) {
|
||||
this->deferred_message_queue_.defer(sensor, try_send_sensor_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
||||
if (!APIConnection::try_send_sensor_info(this, sensor)) {
|
||||
this->deferred_message_queue_.defer(sensor, try_send_sensor_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_sensor_state(APIConnection *api, void *v_sensor) {
|
||||
sensor::Sensor *sensor = reinterpret_cast<sensor::Sensor *>(v_sensor);
|
||||
return APIConnection::try_send_sensor_state(api, sensor, sensor->state);
|
||||
}
|
||||
bool APIConnection::try_send_sensor_state(APIConnection *api, sensor::Sensor *sensor, float state) {
|
||||
SensorStateResponse resp{};
|
||||
resp.key = sensor->get_object_id_hash();
|
||||
resp.state = state;
|
||||
resp.missing_state = !sensor->has_state();
|
||||
return this->send_sensor_state_response(resp);
|
||||
return api->send_sensor_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
||||
bool APIConnection::try_send_sensor_info(APIConnection *api, void *v_sensor) {
|
||||
sensor::Sensor *sensor = reinterpret_cast<sensor::Sensor *>(v_sensor);
|
||||
ListEntitiesSensorResponse msg;
|
||||
msg.key = sensor->get_object_id_hash();
|
||||
msg.object_id = sensor->get_object_id();
|
||||
@ -482,7 +593,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
||||
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
|
||||
msg.disabled_by_default = sensor->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(sensor->get_entity_category());
|
||||
return this->send_list_entities_sensor_response(msg);
|
||||
return api->send_list_entities_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -491,12 +602,29 @@ bool APIConnection::send_switch_state(switch_::Switch *a_switch, bool state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_switch_state(this, a_switch, state)) {
|
||||
this->deferred_message_queue_.defer(a_switch, try_send_switch_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_switch_info(switch_::Switch *a_switch) {
|
||||
if (!APIConnection::try_send_switch_info(this, a_switch)) {
|
||||
this->deferred_message_queue_.defer(a_switch, try_send_switch_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_switch_state(APIConnection *api, void *v_a_switch) {
|
||||
switch_::Switch *a_switch = reinterpret_cast<switch_::Switch *>(v_a_switch);
|
||||
return APIConnection::try_send_switch_state(api, a_switch, a_switch->state);
|
||||
}
|
||||
bool APIConnection::try_send_switch_state(APIConnection *api, switch_::Switch *a_switch, bool state) {
|
||||
SwitchStateResponse resp{};
|
||||
resp.key = a_switch->get_object_id_hash();
|
||||
resp.state = state;
|
||||
return this->send_switch_state_response(resp);
|
||||
return api->send_switch_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
|
||||
bool APIConnection::try_send_switch_info(APIConnection *api, void *v_a_switch) {
|
||||
switch_::Switch *a_switch = reinterpret_cast<switch_::Switch *>(v_a_switch);
|
||||
ListEntitiesSwitchResponse msg;
|
||||
msg.key = a_switch->get_object_id_hash();
|
||||
msg.object_id = a_switch->get_object_id();
|
||||
@ -508,7 +636,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
|
||||
msg.disabled_by_default = a_switch->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category());
|
||||
msg.device_class = a_switch->get_device_class();
|
||||
return this->send_list_entities_switch_response(msg);
|
||||
return api->send_list_entities_switch_response(msg);
|
||||
}
|
||||
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
|
||||
switch_::Switch *a_switch = App.get_switch_by_key(msg.key);
|
||||
@ -528,13 +656,31 @@ bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor,
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_text_sensor_state(this, text_sensor, std::move(state))) {
|
||||
this->deferred_message_queue_.defer(text_sensor, try_send_text_sensor_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) {
|
||||
if (!APIConnection::try_send_text_sensor_info(this, text_sensor)) {
|
||||
this->deferred_message_queue_.defer(text_sensor, try_send_text_sensor_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_text_sensor_state(APIConnection *api, void *v_text_sensor) {
|
||||
text_sensor::TextSensor *text_sensor = reinterpret_cast<text_sensor::TextSensor *>(v_text_sensor);
|
||||
return APIConnection::try_send_text_sensor_state(api, text_sensor, text_sensor->state);
|
||||
}
|
||||
bool APIConnection::try_send_text_sensor_state(APIConnection *api, text_sensor::TextSensor *text_sensor,
|
||||
std::string state) {
|
||||
TextSensorStateResponse resp{};
|
||||
resp.key = text_sensor->get_object_id_hash();
|
||||
resp.state = std::move(state);
|
||||
resp.missing_state = !text_sensor->has_state();
|
||||
return this->send_text_sensor_state_response(resp);
|
||||
return api->send_text_sensor_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) {
|
||||
bool APIConnection::try_send_text_sensor_info(APIConnection *api, void *v_text_sensor) {
|
||||
text_sensor::TextSensor *text_sensor = reinterpret_cast<text_sensor::TextSensor *>(v_text_sensor);
|
||||
ListEntitiesTextSensorResponse msg;
|
||||
msg.key = text_sensor->get_object_id_hash();
|
||||
msg.object_id = text_sensor->get_object_id();
|
||||
@ -546,7 +692,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
|
||||
msg.disabled_by_default = text_sensor->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category());
|
||||
msg.device_class = text_sensor->get_device_class();
|
||||
return this->send_list_entities_text_sensor_response(msg);
|
||||
return api->send_list_entities_text_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -555,6 +701,19 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_climate_state(this, climate)) {
|
||||
this->deferred_message_queue_.defer(climate, try_send_climate_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
if (!APIConnection::try_send_climate_info(this, climate)) {
|
||||
this->deferred_message_queue_.defer(climate, try_send_climate_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_climate_state(APIConnection *api, void *v_climate) {
|
||||
climate::Climate *climate = reinterpret_cast<climate::Climate *>(v_climate);
|
||||
auto traits = climate->get_traits();
|
||||
ClimateStateResponse resp{};
|
||||
resp.key = climate->get_object_id_hash();
|
||||
@ -583,9 +742,10 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
|
||||
resp.current_humidity = climate->current_humidity;
|
||||
if (traits.get_supports_target_humidity())
|
||||
resp.target_humidity = climate->target_humidity;
|
||||
return this->send_climate_state_response(resp);
|
||||
return api->send_climate_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
bool APIConnection::try_send_climate_info(APIConnection *api, void *v_climate) {
|
||||
climate::Climate *climate = reinterpret_cast<climate::Climate *>(v_climate);
|
||||
auto traits = climate->get_traits();
|
||||
ListEntitiesClimateResponse msg;
|
||||
msg.key = climate->get_object_id_hash();
|
||||
@ -626,7 +786,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
msg.supported_custom_presets.push_back(custom_preset);
|
||||
for (auto swing_mode : traits.get_supported_swing_modes())
|
||||
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
|
||||
return this->send_list_entities_climate_response(msg);
|
||||
return api->send_list_entities_climate_response(msg);
|
||||
}
|
||||
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||
climate::Climate *climate = App.get_climate_by_key(msg.key);
|
||||
@ -663,13 +823,30 @@ bool APIConnection::send_number_state(number::Number *number, float state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_number_state(this, number, state)) {
|
||||
this->deferred_message_queue_.defer(number, try_send_number_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_number_info(number::Number *number) {
|
||||
if (!APIConnection::try_send_number_info(this, number)) {
|
||||
this->deferred_message_queue_.defer(number, try_send_number_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_number_state(APIConnection *api, void *v_number) {
|
||||
number::Number *number = reinterpret_cast<number::Number *>(v_number);
|
||||
return APIConnection::try_send_number_state(api, number, number->state);
|
||||
}
|
||||
bool APIConnection::try_send_number_state(APIConnection *api, number::Number *number, float state) {
|
||||
NumberStateResponse resp{};
|
||||
resp.key = number->get_object_id_hash();
|
||||
resp.state = state;
|
||||
resp.missing_state = !number->has_state();
|
||||
return this->send_number_state_response(resp);
|
||||
return api->send_number_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_number_info(number::Number *number) {
|
||||
bool APIConnection::try_send_number_info(APIConnection *api, void *v_number) {
|
||||
number::Number *number = reinterpret_cast<number::Number *>(v_number);
|
||||
ListEntitiesNumberResponse msg;
|
||||
msg.key = number->get_object_id_hash();
|
||||
msg.object_id = number->get_object_id();
|
||||
@ -687,7 +864,7 @@ bool APIConnection::send_number_info(number::Number *number) {
|
||||
msg.max_value = number->traits.get_max_value();
|
||||
msg.step = number->traits.get_step();
|
||||
|
||||
return this->send_list_entities_number_response(msg);
|
||||
return api->send_list_entities_number_response(msg);
|
||||
}
|
||||
void APIConnection::number_command(const NumberCommandRequest &msg) {
|
||||
number::Number *number = App.get_number_by_key(msg.key);
|
||||
@ -705,15 +882,29 @@ bool APIConnection::send_date_state(datetime::DateEntity *date) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_date_state(this, date)) {
|
||||
this->deferred_message_queue_.defer(date, try_send_date_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_date_info(datetime::DateEntity *date) {
|
||||
if (!APIConnection::try_send_date_info(this, date)) {
|
||||
this->deferred_message_queue_.defer(date, try_send_date_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_date_state(APIConnection *api, void *v_date) {
|
||||
datetime::DateEntity *date = reinterpret_cast<datetime::DateEntity *>(v_date);
|
||||
DateStateResponse resp{};
|
||||
resp.key = date->get_object_id_hash();
|
||||
resp.missing_state = !date->has_state();
|
||||
resp.year = date->year;
|
||||
resp.month = date->month;
|
||||
resp.day = date->day;
|
||||
return this->send_date_state_response(resp);
|
||||
return api->send_date_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_date_info(datetime::DateEntity *date) {
|
||||
bool APIConnection::try_send_date_info(APIConnection *api, void *v_date) {
|
||||
datetime::DateEntity *date = reinterpret_cast<datetime::DateEntity *>(v_date);
|
||||
ListEntitiesDateResponse msg;
|
||||
msg.key = date->get_object_id_hash();
|
||||
msg.object_id = date->get_object_id();
|
||||
@ -724,7 +915,7 @@ bool APIConnection::send_date_info(datetime::DateEntity *date) {
|
||||
msg.disabled_by_default = date->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(date->get_entity_category());
|
||||
|
||||
return this->send_list_entities_date_response(msg);
|
||||
return api->send_list_entities_date_response(msg);
|
||||
}
|
||||
void APIConnection::date_command(const DateCommandRequest &msg) {
|
||||
datetime::DateEntity *date = App.get_date_by_key(msg.key);
|
||||
@ -742,15 +933,29 @@ bool APIConnection::send_time_state(datetime::TimeEntity *time) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_time_state(this, time)) {
|
||||
this->deferred_message_queue_.defer(time, try_send_time_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_time_info(datetime::TimeEntity *time) {
|
||||
if (!APIConnection::try_send_time_info(this, time)) {
|
||||
this->deferred_message_queue_.defer(time, try_send_time_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_time_state(APIConnection *api, void *v_time) {
|
||||
datetime::TimeEntity *time = reinterpret_cast<datetime::TimeEntity *>(v_time);
|
||||
TimeStateResponse resp{};
|
||||
resp.key = time->get_object_id_hash();
|
||||
resp.missing_state = !time->has_state();
|
||||
resp.hour = time->hour;
|
||||
resp.minute = time->minute;
|
||||
resp.second = time->second;
|
||||
return this->send_time_state_response(resp);
|
||||
return api->send_time_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_time_info(datetime::TimeEntity *time) {
|
||||
bool APIConnection::try_send_time_info(APIConnection *api, void *v_time) {
|
||||
datetime::TimeEntity *time = reinterpret_cast<datetime::TimeEntity *>(v_time);
|
||||
ListEntitiesTimeResponse msg;
|
||||
msg.key = time->get_object_id_hash();
|
||||
msg.object_id = time->get_object_id();
|
||||
@ -761,7 +966,7 @@ bool APIConnection::send_time_info(datetime::TimeEntity *time) {
|
||||
msg.disabled_by_default = time->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(time->get_entity_category());
|
||||
|
||||
return this->send_list_entities_time_response(msg);
|
||||
return api->send_list_entities_time_response(msg);
|
||||
}
|
||||
void APIConnection::time_command(const TimeCommandRequest &msg) {
|
||||
datetime::TimeEntity *time = App.get_time_by_key(msg.key);
|
||||
@ -779,6 +984,19 @@ bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_datetime_state(this, datetime)) {
|
||||
this->deferred_message_queue_.defer(datetime, try_send_datetime_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) {
|
||||
if (!APIConnection::try_send_datetime_info(this, datetime)) {
|
||||
this->deferred_message_queue_.defer(datetime, try_send_datetime_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_datetime_state(APIConnection *api, void *v_datetime) {
|
||||
datetime::DateTimeEntity *datetime = reinterpret_cast<datetime::DateTimeEntity *>(v_datetime);
|
||||
DateTimeStateResponse resp{};
|
||||
resp.key = datetime->get_object_id_hash();
|
||||
resp.missing_state = !datetime->has_state();
|
||||
@ -786,9 +1004,10 @@ bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
|
||||
ESPTime state = datetime->state_as_esptime();
|
||||
resp.epoch_seconds = state.timestamp;
|
||||
}
|
||||
return this->send_date_time_state_response(resp);
|
||||
return api->send_date_time_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) {
|
||||
bool APIConnection::try_send_datetime_info(APIConnection *api, void *v_datetime) {
|
||||
datetime::DateTimeEntity *datetime = reinterpret_cast<datetime::DateTimeEntity *>(v_datetime);
|
||||
ListEntitiesDateTimeResponse msg;
|
||||
msg.key = datetime->get_object_id_hash();
|
||||
msg.object_id = datetime->get_object_id();
|
||||
@ -799,7 +1018,7 @@ bool APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) {
|
||||
msg.disabled_by_default = datetime->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(datetime->get_entity_category());
|
||||
|
||||
return this->send_list_entities_date_time_response(msg);
|
||||
return api->send_list_entities_date_time_response(msg);
|
||||
}
|
||||
void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
|
||||
datetime::DateTimeEntity *datetime = App.get_datetime_by_key(msg.key);
|
||||
@ -817,13 +1036,30 @@ bool APIConnection::send_text_state(text::Text *text, std::string state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_text_state(this, text, std::move(state))) {
|
||||
this->deferred_message_queue_.defer(text, try_send_text_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_text_info(text::Text *text) {
|
||||
if (!APIConnection::try_send_text_info(this, text)) {
|
||||
this->deferred_message_queue_.defer(text, try_send_text_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_text_state(APIConnection *api, void *v_text) {
|
||||
text::Text *text = reinterpret_cast<text::Text *>(v_text);
|
||||
return APIConnection::try_send_text_state(api, text, text->state);
|
||||
}
|
||||
bool APIConnection::try_send_text_state(APIConnection *api, text::Text *text, std::string state) {
|
||||
TextStateResponse resp{};
|
||||
resp.key = text->get_object_id_hash();
|
||||
resp.state = std::move(state);
|
||||
resp.missing_state = !text->has_state();
|
||||
return this->send_text_state_response(resp);
|
||||
return api->send_text_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_text_info(text::Text *text) {
|
||||
bool APIConnection::try_send_text_info(APIConnection *api, void *v_text) {
|
||||
text::Text *text = reinterpret_cast<text::Text *>(v_text);
|
||||
ListEntitiesTextResponse msg;
|
||||
msg.key = text->get_object_id_hash();
|
||||
msg.object_id = text->get_object_id();
|
||||
@ -837,7 +1073,7 @@ bool APIConnection::send_text_info(text::Text *text) {
|
||||
msg.max_length = text->traits.get_max_length();
|
||||
msg.pattern = text->traits.get_pattern();
|
||||
|
||||
return this->send_list_entities_text_response(msg);
|
||||
return api->send_list_entities_text_response(msg);
|
||||
}
|
||||
void APIConnection::text_command(const TextCommandRequest &msg) {
|
||||
text::Text *text = App.get_text_by_key(msg.key);
|
||||
@ -855,13 +1091,30 @@ bool APIConnection::send_select_state(select::Select *select, std::string state)
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_select_state(this, select, std::move(state))) {
|
||||
this->deferred_message_queue_.defer(select, try_send_select_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_select_info(select::Select *select) {
|
||||
if (!APIConnection::try_send_select_info(this, select)) {
|
||||
this->deferred_message_queue_.defer(select, try_send_select_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_select_state(APIConnection *api, void *v_select) {
|
||||
select::Select *select = reinterpret_cast<select::Select *>(v_select);
|
||||
return APIConnection::try_send_select_state(api, select, select->state);
|
||||
}
|
||||
bool APIConnection::try_send_select_state(APIConnection *api, select::Select *select, std::string state) {
|
||||
SelectStateResponse resp{};
|
||||
resp.key = select->get_object_id_hash();
|
||||
resp.state = std::move(state);
|
||||
resp.missing_state = !select->has_state();
|
||||
return this->send_select_state_response(resp);
|
||||
return api->send_select_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_select_info(select::Select *select) {
|
||||
bool APIConnection::try_send_select_info(APIConnection *api, void *v_select) {
|
||||
select::Select *select = reinterpret_cast<select::Select *>(v_select);
|
||||
ListEntitiesSelectResponse msg;
|
||||
msg.key = select->get_object_id_hash();
|
||||
msg.object_id = select->get_object_id();
|
||||
@ -875,7 +1128,7 @@ bool APIConnection::send_select_info(select::Select *select) {
|
||||
for (const auto &option : select->traits.get_options())
|
||||
msg.options.push_back(option);
|
||||
|
||||
return this->send_list_entities_select_response(msg);
|
||||
return api->send_list_entities_select_response(msg);
|
||||
}
|
||||
void APIConnection::select_command(const SelectCommandRequest &msg) {
|
||||
select::Select *select = App.get_select_by_key(msg.key);
|
||||
@ -889,7 +1142,13 @@ void APIConnection::select_command(const SelectCommandRequest &msg) {
|
||||
#endif
|
||||
|
||||
#ifdef USE_BUTTON
|
||||
bool APIConnection::send_button_info(button::Button *button) {
|
||||
void APIConnection::send_button_info(button::Button *button) {
|
||||
if (!APIConnection::try_send_button_info(this, button)) {
|
||||
this->deferred_message_queue_.defer(button, try_send_button_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_button_info(APIConnection *api, void *v_button) {
|
||||
button::Button *button = reinterpret_cast<button::Button *>(v_button);
|
||||
ListEntitiesButtonResponse msg;
|
||||
msg.key = button->get_object_id_hash();
|
||||
msg.object_id = button->get_object_id();
|
||||
@ -900,7 +1159,7 @@ bool APIConnection::send_button_info(button::Button *button) {
|
||||
msg.disabled_by_default = button->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(button->get_entity_category());
|
||||
msg.device_class = button->get_device_class();
|
||||
return this->send_list_entities_button_response(msg);
|
||||
return api->send_list_entities_button_response(msg);
|
||||
}
|
||||
void APIConnection::button_command(const ButtonCommandRequest &msg) {
|
||||
button::Button *button = App.get_button_by_key(msg.key);
|
||||
@ -916,12 +1175,29 @@ bool APIConnection::send_lock_state(lock::Lock *a_lock, lock::LockState state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_lock_state(this, a_lock, state)) {
|
||||
this->deferred_message_queue_.defer(a_lock, try_send_lock_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_lock_info(lock::Lock *a_lock) {
|
||||
if (!APIConnection::try_send_lock_info(this, a_lock)) {
|
||||
this->deferred_message_queue_.defer(a_lock, try_send_lock_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_lock_state(APIConnection *api, void *v_a_lock) {
|
||||
lock::Lock *a_lock = reinterpret_cast<lock::Lock *>(v_a_lock);
|
||||
return APIConnection::try_send_lock_state(api, a_lock, a_lock->state);
|
||||
}
|
||||
bool APIConnection::try_send_lock_state(APIConnection *api, lock::Lock *a_lock, lock::LockState state) {
|
||||
LockStateResponse resp{};
|
||||
resp.key = a_lock->get_object_id_hash();
|
||||
resp.state = static_cast<enums::LockState>(state);
|
||||
return this->send_lock_state_response(resp);
|
||||
return api->send_lock_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_lock_info(lock::Lock *a_lock) {
|
||||
bool APIConnection::try_send_lock_info(APIConnection *api, void *v_a_lock) {
|
||||
lock::Lock *a_lock = reinterpret_cast<lock::Lock *>(v_a_lock);
|
||||
ListEntitiesLockResponse msg;
|
||||
msg.key = a_lock->get_object_id_hash();
|
||||
msg.object_id = a_lock->get_object_id();
|
||||
@ -934,7 +1210,7 @@ bool APIConnection::send_lock_info(lock::Lock *a_lock) {
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(a_lock->get_entity_category());
|
||||
msg.supports_open = a_lock->traits.get_supports_open();
|
||||
msg.requires_code = a_lock->traits.get_requires_code();
|
||||
return this->send_list_entities_lock_response(msg);
|
||||
return api->send_list_entities_lock_response(msg);
|
||||
}
|
||||
void APIConnection::lock_command(const LockCommandRequest &msg) {
|
||||
lock::Lock *a_lock = App.get_lock_by_key(msg.key);
|
||||
@ -960,13 +1236,27 @@ bool APIConnection::send_valve_state(valve::Valve *valve) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_valve_state(this, valve)) {
|
||||
this->deferred_message_queue_.defer(valve, try_send_valve_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_valve_info(valve::Valve *valve) {
|
||||
if (!APIConnection::try_send_valve_info(this, valve)) {
|
||||
this->deferred_message_queue_.defer(valve, try_send_valve_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_valve_state(APIConnection *api, void *v_valve) {
|
||||
valve::Valve *valve = reinterpret_cast<valve::Valve *>(v_valve);
|
||||
ValveStateResponse resp{};
|
||||
resp.key = valve->get_object_id_hash();
|
||||
resp.position = valve->position;
|
||||
resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation);
|
||||
return this->send_valve_state_response(resp);
|
||||
return api->send_valve_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_valve_info(valve::Valve *valve) {
|
||||
bool APIConnection::try_send_valve_info(APIConnection *api, void *v_valve) {
|
||||
valve::Valve *valve = reinterpret_cast<valve::Valve *>(v_valve);
|
||||
auto traits = valve->get_traits();
|
||||
ListEntitiesValveResponse msg;
|
||||
msg.key = valve->get_object_id_hash();
|
||||
@ -981,7 +1271,7 @@ bool APIConnection::send_valve_info(valve::Valve *valve) {
|
||||
msg.assumed_state = traits.get_is_assumed_state();
|
||||
msg.supports_position = traits.get_supports_position();
|
||||
msg.supports_stop = traits.get_supports_stop();
|
||||
return this->send_list_entities_valve_response(msg);
|
||||
return api->send_list_entities_valve_response(msg);
|
||||
}
|
||||
void APIConnection::valve_command(const ValveCommandRequest &msg) {
|
||||
valve::Valve *valve = App.get_valve_by_key(msg.key);
|
||||
@ -1002,6 +1292,19 @@ bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_pla
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_media_player_state(this, media_player)) {
|
||||
this->deferred_message_queue_.defer(media_player, try_send_media_player_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) {
|
||||
if (!APIConnection::try_send_media_player_info(this, media_player)) {
|
||||
this->deferred_message_queue_.defer(media_player, try_send_media_player_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_media_player_state(APIConnection *api, void *v_media_player) {
|
||||
media_player::MediaPlayer *media_player = reinterpret_cast<media_player::MediaPlayer *>(v_media_player);
|
||||
MediaPlayerStateResponse resp{};
|
||||
resp.key = media_player->get_object_id_hash();
|
||||
|
||||
@ -1011,9 +1314,10 @@ bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_pla
|
||||
resp.state = static_cast<enums::MediaPlayerState>(report_state);
|
||||
resp.volume = media_player->volume;
|
||||
resp.muted = media_player->is_muted();
|
||||
return this->send_media_player_state_response(resp);
|
||||
return api->send_media_player_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) {
|
||||
bool APIConnection::try_send_media_player_info(APIConnection *api, void *v_media_player) {
|
||||
media_player::MediaPlayer *media_player = reinterpret_cast<media_player::MediaPlayer *>(v_media_player);
|
||||
ListEntitiesMediaPlayerResponse msg;
|
||||
msg.key = media_player->get_object_id_hash();
|
||||
msg.object_id = media_player->get_object_id();
|
||||
@ -1037,7 +1341,7 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play
|
||||
msg.supported_formats.push_back(media_format);
|
||||
}
|
||||
|
||||
return this->send_list_entities_media_player_response(msg);
|
||||
return api->send_list_entities_media_player_response(msg);
|
||||
}
|
||||
void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
|
||||
media_player::MediaPlayer *media_player = App.get_media_player_by_key(msg.key);
|
||||
@ -1062,7 +1366,7 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
void APIConnection::set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
if (!this->state_subscription_)
|
||||
return;
|
||||
if (this->image_reader_.available())
|
||||
@ -1071,7 +1375,13 @@ void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage>
|
||||
image->was_requested_by(esphome::esp32_camera::IDLE))
|
||||
this->image_reader_.set_image(std::move(image));
|
||||
}
|
||||
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
|
||||
void APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
|
||||
if (!APIConnection::try_send_camera_info(this, camera)) {
|
||||
this->deferred_message_queue_.defer(camera, try_send_camera_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_camera_info(APIConnection *api, void *v_camera) {
|
||||
esp32_camera::ESP32Camera *camera = reinterpret_cast<esp32_camera::ESP32Camera *>(v_camera);
|
||||
ListEntitiesCameraResponse msg;
|
||||
msg.key = camera->get_object_id_hash();
|
||||
msg.object_id = camera->get_object_id();
|
||||
@ -1081,7 +1391,7 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
|
||||
msg.disabled_by_default = camera->is_disabled_by_default();
|
||||
msg.icon = camera->get_icon();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(camera->get_entity_category());
|
||||
return this->send_list_entities_camera_response(msg);
|
||||
return api->send_list_entities_camera_response(msg);
|
||||
}
|
||||
void APIConnection::camera_image(const CameraImageRequest &msg) {
|
||||
if (esp32_camera::global_esp32_camera == nullptr)
|
||||
@ -1268,12 +1578,28 @@ bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmCon
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_alarm_control_panel_state(this, a_alarm_control_panel)) {
|
||||
this->deferred_message_queue_.defer(a_alarm_control_panel, try_send_alarm_control_panel_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
|
||||
if (!APIConnection::try_send_alarm_control_panel_info(this, a_alarm_control_panel)) {
|
||||
this->deferred_message_queue_.defer(a_alarm_control_panel, try_send_alarm_control_panel_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_alarm_control_panel_state(APIConnection *api, void *v_a_alarm_control_panel) {
|
||||
alarm_control_panel::AlarmControlPanel *a_alarm_control_panel =
|
||||
reinterpret_cast<alarm_control_panel::AlarmControlPanel *>(v_a_alarm_control_panel);
|
||||
AlarmControlPanelStateResponse resp{};
|
||||
resp.key = a_alarm_control_panel->get_object_id_hash();
|
||||
resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state());
|
||||
return this->send_alarm_control_panel_state_response(resp);
|
||||
return api->send_alarm_control_panel_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
|
||||
bool APIConnection::try_send_alarm_control_panel_info(APIConnection *api, void *v_a_alarm_control_panel) {
|
||||
alarm_control_panel::AlarmControlPanel *a_alarm_control_panel =
|
||||
reinterpret_cast<alarm_control_panel::AlarmControlPanel *>(v_a_alarm_control_panel);
|
||||
ListEntitiesAlarmControlPanelResponse msg;
|
||||
msg.key = a_alarm_control_panel->get_object_id_hash();
|
||||
msg.object_id = a_alarm_control_panel->get_object_id();
|
||||
@ -1285,7 +1611,7 @@ bool APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmCont
|
||||
msg.supported_features = a_alarm_control_panel->get_supported_features();
|
||||
msg.requires_code = a_alarm_control_panel->get_requires_code();
|
||||
msg.requires_code_to_arm = a_alarm_control_panel->get_requires_code_to_arm();
|
||||
return this->send_list_entities_alarm_control_panel_response(msg);
|
||||
return api->send_list_entities_alarm_control_panel_response(msg);
|
||||
}
|
||||
void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) {
|
||||
alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key);
|
||||
@ -1322,13 +1648,28 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
bool APIConnection::send_event(event::Event *event, std::string event_type) {
|
||||
void APIConnection::send_event(event::Event *event, std::string event_type) {
|
||||
if (!APIConnection::try_send_event(this, event, std::move(event_type))) {
|
||||
this->deferred_message_queue_.defer(event, try_send_event);
|
||||
}
|
||||
}
|
||||
void APIConnection::send_event_info(event::Event *event) {
|
||||
if (!APIConnection::try_send_event_info(this, event)) {
|
||||
this->deferred_message_queue_.defer(event, try_send_event_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_event(APIConnection *api, void *v_event) {
|
||||
event::Event *event = reinterpret_cast<event::Event *>(v_event);
|
||||
return APIConnection::try_send_event(api, event, *(event->last_event_type));
|
||||
}
|
||||
bool APIConnection::try_send_event(APIConnection *api, event::Event *event, std::string event_type) {
|
||||
EventResponse resp{};
|
||||
resp.key = event->get_object_id_hash();
|
||||
resp.event_type = std::move(event_type);
|
||||
return this->send_event_response(resp);
|
||||
return api->send_event_response(resp);
|
||||
}
|
||||
bool APIConnection::send_event_info(event::Event *event) {
|
||||
bool APIConnection::try_send_event_info(APIConnection *api, void *v_event) {
|
||||
event::Event *event = reinterpret_cast<event::Event *>(v_event);
|
||||
ListEntitiesEventResponse msg;
|
||||
msg.key = event->get_object_id_hash();
|
||||
msg.object_id = event->get_object_id();
|
||||
@ -1341,7 +1682,7 @@ bool APIConnection::send_event_info(event::Event *event) {
|
||||
msg.device_class = event->get_device_class();
|
||||
for (const auto &event_type : event->get_event_types())
|
||||
msg.event_types.push_back(event_type);
|
||||
return this->send_list_entities_event_response(msg);
|
||||
return api->send_list_entities_event_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -1350,6 +1691,19 @@ bool APIConnection::send_update_state(update::UpdateEntity *update) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
if (!APIConnection::try_send_update_state(this, update)) {
|
||||
this->deferred_message_queue_.defer(update, try_send_update_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void APIConnection::send_update_info(update::UpdateEntity *update) {
|
||||
if (!APIConnection::try_send_update_info(this, update)) {
|
||||
this->deferred_message_queue_.defer(update, try_send_update_info);
|
||||
}
|
||||
}
|
||||
bool APIConnection::try_send_update_state(APIConnection *api, void *v_update) {
|
||||
update::UpdateEntity *update = reinterpret_cast<update::UpdateEntity *>(v_update);
|
||||
UpdateStateResponse resp{};
|
||||
resp.key = update->get_object_id_hash();
|
||||
resp.missing_state = !update->has_state();
|
||||
@ -1366,9 +1720,10 @@ bool APIConnection::send_update_state(update::UpdateEntity *update) {
|
||||
resp.release_url = update->update_info.release_url;
|
||||
}
|
||||
|
||||
return this->send_update_state_response(resp);
|
||||
return api->send_update_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_update_info(update::UpdateEntity *update) {
|
||||
bool APIConnection::try_send_update_info(APIConnection *api, void *v_update) {
|
||||
update::UpdateEntity *update = reinterpret_cast<update::UpdateEntity *>(v_update);
|
||||
ListEntitiesUpdateResponse msg;
|
||||
msg.key = update->get_object_id_hash();
|
||||
msg.object_id = update->get_object_id();
|
||||
@ -1379,7 +1734,7 @@ bool APIConnection::send_update_info(update::UpdateEntity *update) {
|
||||
msg.disabled_by_default = update->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(update->get_entity_category());
|
||||
msg.device_class = update->get_device_class();
|
||||
return this->send_list_entities_update_response(msg);
|
||||
return api->send_list_entities_update_response(msg);
|
||||
}
|
||||
void APIConnection::update_command(const UpdateCommandRequest &msg) {
|
||||
update::UpdateEntity *update = App.get_update_by_key(msg.key);
|
||||
@ -1403,7 +1758,7 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
|
||||
}
|
||||
#endif
|
||||
|
||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
|
||||
bool APIConnection::try_send_log_message(int level, const char *tag, const char *line) {
|
||||
if (this->log_subscription_ < level)
|
||||
return false;
|
||||
|
||||
@ -1488,6 +1843,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
resp.legacy_bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->get_legacy_version();
|
||||
resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
|
||||
resp.bluetooth_mac_address = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty();
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
resp.legacy_voice_assistant_version = voice_assistant::global_voice_assistant->get_legacy_version();
|
||||
|
@ -14,6 +14,46 @@
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
using send_message_t = bool(APIConnection *, void *);
|
||||
|
||||
/*
|
||||
This class holds a pointer to the source component that wants to publish a message, and a pointer to a function that
|
||||
will lazily publish that message. The two pointers allow dedup in the deferred queue if multiple publishes for the
|
||||
same component are backed up, and take up only 8 bytes of memory. The entry in the deferred queue (a std::vector) is
|
||||
the DeferredMessage instance itself (not a pointer to one elsewhere in heap) so still only 8 bytes per entry. Even
|
||||
100 backed up messages (you'd have to have at least 100 sensors publishing because of dedup) would take up only 0.8
|
||||
kB.
|
||||
*/
|
||||
class DeferredMessageQueue {
|
||||
struct DeferredMessage {
|
||||
friend class DeferredMessageQueue;
|
||||
|
||||
protected:
|
||||
void *source_;
|
||||
send_message_t *send_message_;
|
||||
|
||||
public:
|
||||
DeferredMessage(void *source, send_message_t *send_message) : source_(source), send_message_(send_message) {}
|
||||
bool operator==(const DeferredMessage &test) const {
|
||||
return (source_ == test.source_ && send_message_ == test.send_message_);
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
protected:
|
||||
// vector is used very specifically for its zero memory overhead even though items are popped from the front (memory
|
||||
// footprint is more important than speed here)
|
||||
std::vector<DeferredMessage> deferred_queue_;
|
||||
APIConnection *api_connection_;
|
||||
|
||||
// helper for allowing only unique entries in the queue
|
||||
void dmq_push_back_with_dedup_(void *source, send_message_t *send_message);
|
||||
|
||||
public:
|
||||
DeferredMessageQueue(APIConnection *api_connection) : api_connection_(api_connection) {}
|
||||
void process_queue();
|
||||
void defer(void *source, send_message_t *send_message);
|
||||
};
|
||||
|
||||
class APIConnection : public APIServerConnection {
|
||||
public:
|
||||
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
|
||||
@ -28,96 +68,140 @@ class APIConnection : public APIServerConnection {
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state);
|
||||
bool send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor);
|
||||
void send_binary_sensor_info(binary_sensor::BinarySensor *binary_sensor);
|
||||
static bool try_send_binary_sensor_state(APIConnection *api, void *v_binary_sensor);
|
||||
static bool try_send_binary_sensor_state(APIConnection *api, binary_sensor::BinarySensor *binary_sensor, bool state);
|
||||
static bool try_send_binary_sensor_info(APIConnection *api, void *v_binary_sensor);
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool send_cover_state(cover::Cover *cover);
|
||||
bool send_cover_info(cover::Cover *cover);
|
||||
void send_cover_info(cover::Cover *cover);
|
||||
static bool try_send_cover_state(APIConnection *api, void *v_cover);
|
||||
static bool try_send_cover_info(APIConnection *api, void *v_cover);
|
||||
void cover_command(const CoverCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool send_fan_state(fan::Fan *fan);
|
||||
bool send_fan_info(fan::Fan *fan);
|
||||
void send_fan_info(fan::Fan *fan);
|
||||
static bool try_send_fan_state(APIConnection *api, void *v_fan);
|
||||
static bool try_send_fan_info(APIConnection *api, void *v_fan);
|
||||
void fan_command(const FanCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool send_light_state(light::LightState *light);
|
||||
bool send_light_info(light::LightState *light);
|
||||
void send_light_info(light::LightState *light);
|
||||
static bool try_send_light_state(APIConnection *api, void *v_light);
|
||||
static bool try_send_light_info(APIConnection *api, void *v_light);
|
||||
void light_command(const LightCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool send_sensor_state(sensor::Sensor *sensor, float state);
|
||||
bool send_sensor_info(sensor::Sensor *sensor);
|
||||
void send_sensor_info(sensor::Sensor *sensor);
|
||||
static bool try_send_sensor_state(APIConnection *api, void *v_sensor);
|
||||
static bool try_send_sensor_state(APIConnection *api, sensor::Sensor *sensor, float state);
|
||||
static bool try_send_sensor_info(APIConnection *api, void *v_sensor);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool send_switch_state(switch_::Switch *a_switch, bool state);
|
||||
bool send_switch_info(switch_::Switch *a_switch);
|
||||
void send_switch_info(switch_::Switch *a_switch);
|
||||
static bool try_send_switch_state(APIConnection *api, void *v_a_switch);
|
||||
static bool try_send_switch_state(APIConnection *api, switch_::Switch *a_switch, bool state);
|
||||
static bool try_send_switch_info(APIConnection *api, void *v_a_switch);
|
||||
void switch_command(const SwitchCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state);
|
||||
bool send_text_sensor_info(text_sensor::TextSensor *text_sensor);
|
||||
void send_text_sensor_info(text_sensor::TextSensor *text_sensor);
|
||||
static bool try_send_text_sensor_state(APIConnection *api, void *v_text_sensor);
|
||||
static bool try_send_text_sensor_state(APIConnection *api, text_sensor::TextSensor *text_sensor, std::string state);
|
||||
static bool try_send_text_sensor_info(APIConnection *api, void *v_text_sensor);
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
|
||||
bool send_camera_info(esp32_camera::ESP32Camera *camera);
|
||||
void set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
|
||||
void send_camera_info(esp32_camera::ESP32Camera *camera);
|
||||
static bool try_send_camera_info(APIConnection *api, void *v_camera);
|
||||
void camera_image(const CameraImageRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool send_climate_state(climate::Climate *climate);
|
||||
bool send_climate_info(climate::Climate *climate);
|
||||
void send_climate_info(climate::Climate *climate);
|
||||
static bool try_send_climate_state(APIConnection *api, void *v_climate);
|
||||
static bool try_send_climate_info(APIConnection *api, void *v_climate);
|
||||
void climate_command(const ClimateCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool send_number_state(number::Number *number, float state);
|
||||
bool send_number_info(number::Number *number);
|
||||
void send_number_info(number::Number *number);
|
||||
static bool try_send_number_state(APIConnection *api, void *v_number);
|
||||
static bool try_send_number_state(APIConnection *api, number::Number *number, float state);
|
||||
static bool try_send_number_info(APIConnection *api, void *v_number);
|
||||
void number_command(const NumberCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool send_date_state(datetime::DateEntity *date);
|
||||
bool send_date_info(datetime::DateEntity *date);
|
||||
void send_date_info(datetime::DateEntity *date);
|
||||
static bool try_send_date_state(APIConnection *api, void *v_date);
|
||||
static bool try_send_date_info(APIConnection *api, void *v_date);
|
||||
void date_command(const DateCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
bool send_time_state(datetime::TimeEntity *time);
|
||||
bool send_time_info(datetime::TimeEntity *time);
|
||||
void send_time_info(datetime::TimeEntity *time);
|
||||
static bool try_send_time_state(APIConnection *api, void *v_time);
|
||||
static bool try_send_time_info(APIConnection *api, void *v_time);
|
||||
void time_command(const TimeCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
bool send_datetime_state(datetime::DateTimeEntity *datetime);
|
||||
bool send_datetime_info(datetime::DateTimeEntity *datetime);
|
||||
void send_datetime_info(datetime::DateTimeEntity *datetime);
|
||||
static bool try_send_datetime_state(APIConnection *api, void *v_datetime);
|
||||
static bool try_send_datetime_info(APIConnection *api, void *v_datetime);
|
||||
void datetime_command(const DateTimeCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool send_text_state(text::Text *text, std::string state);
|
||||
bool send_text_info(text::Text *text);
|
||||
void send_text_info(text::Text *text);
|
||||
static bool try_send_text_state(APIConnection *api, void *v_text);
|
||||
static bool try_send_text_state(APIConnection *api, text::Text *text, std::string state);
|
||||
static bool try_send_text_info(APIConnection *api, void *v_text);
|
||||
void text_command(const TextCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool send_select_state(select::Select *select, std::string state);
|
||||
bool send_select_info(select::Select *select);
|
||||
void send_select_info(select::Select *select);
|
||||
static bool try_send_select_state(APIConnection *api, void *v_select);
|
||||
static bool try_send_select_state(APIConnection *api, select::Select *select, std::string state);
|
||||
static bool try_send_select_info(APIConnection *api, void *v_select);
|
||||
void select_command(const SelectCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool send_button_info(button::Button *button);
|
||||
void send_button_info(button::Button *button);
|
||||
static bool try_send_button_info(APIConnection *api, void *v_button);
|
||||
void button_command(const ButtonCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool send_lock_state(lock::Lock *a_lock, lock::LockState state);
|
||||
bool send_lock_info(lock::Lock *a_lock);
|
||||
void send_lock_info(lock::Lock *a_lock);
|
||||
static bool try_send_lock_state(APIConnection *api, void *v_a_lock);
|
||||
static bool try_send_lock_state(APIConnection *api, lock::Lock *a_lock, lock::LockState state);
|
||||
static bool try_send_lock_info(APIConnection *api, void *v_a_lock);
|
||||
void lock_command(const LockCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
bool send_valve_state(valve::Valve *valve);
|
||||
bool send_valve_info(valve::Valve *valve);
|
||||
void send_valve_info(valve::Valve *valve);
|
||||
static bool try_send_valve_state(APIConnection *api, void *v_valve);
|
||||
static bool try_send_valve_info(APIConnection *api, void *v_valve);
|
||||
void valve_command(const ValveCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
bool send_media_player_state(media_player::MediaPlayer *media_player);
|
||||
bool send_media_player_info(media_player::MediaPlayer *media_player);
|
||||
void send_media_player_info(media_player::MediaPlayer *media_player);
|
||||
static bool try_send_media_player_state(APIConnection *api, void *v_media_player);
|
||||
static bool try_send_media_player_info(APIConnection *api, void *v_media_player);
|
||||
void media_player_command(const MediaPlayerCommandRequest &msg) override;
|
||||
#endif
|
||||
bool send_log_message(int level, const char *tag, const char *line);
|
||||
bool try_send_log_message(int level, const char *tag, const char *line);
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
if (!this->service_call_subscription_)
|
||||
return;
|
||||
@ -160,18 +244,25 @@ class APIConnection : public APIServerConnection {
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
|
||||
bool send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
|
||||
void send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
|
||||
static bool try_send_alarm_control_panel_state(APIConnection *api, void *v_a_alarm_control_panel);
|
||||
static bool try_send_alarm_control_panel_info(APIConnection *api, void *v_a_alarm_control_panel);
|
||||
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
bool send_event(event::Event *event, std::string event_type);
|
||||
bool send_event_info(event::Event *event);
|
||||
void send_event(event::Event *event, std::string event_type);
|
||||
void send_event_info(event::Event *event);
|
||||
static bool try_send_event(APIConnection *api, void *v_event);
|
||||
static bool try_send_event(APIConnection *api, event::Event *event, std::string event_type);
|
||||
static bool try_send_event_info(APIConnection *api, void *v_event);
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
bool send_update_state(update::UpdateEntity *update);
|
||||
bool send_update_info(update::UpdateEntity *update);
|
||||
void send_update_info(update::UpdateEntity *update);
|
||||
static bool try_send_update_state(APIConnection *api, void *v_update);
|
||||
static bool try_send_update_info(APIConnection *api, void *v_update);
|
||||
void update_command(const UpdateCommandRequest &msg) override;
|
||||
#endif
|
||||
|
||||
@ -262,6 +353,7 @@ class APIConnection : public APIServerConnection {
|
||||
bool service_call_subscription_{false};
|
||||
bool next_close_ = false;
|
||||
APIServer *parent_;
|
||||
DeferredMessageQueue deferred_message_queue_;
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
ListEntitiesIterator list_entities_iterator_;
|
||||
int state_subs_at_ = -1;
|
||||
|
@ -838,6 +838,10 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
|
||||
this->suggested_area = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 18: {
|
||||
this->bluetooth_mac_address = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -860,6 +864,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(14, this->legacy_voice_assistant_version);
|
||||
buffer.encode_uint32(17, this->voice_assistant_feature_flags);
|
||||
buffer.encode_string(16, this->suggested_area);
|
||||
buffer.encode_string(18, this->bluetooth_mac_address);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
@ -937,6 +942,10 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
out.append(" suggested_area: ");
|
||||
out.append("'").append(this->suggested_area).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" bluetooth_mac_address: ");
|
||||
out.append("'").append(this->bluetooth_mac_address).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -7085,6 +7094,16 @@ void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool VoiceAssistantAnnounceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 4: {
|
||||
this->start_conversation = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
@ -7095,6 +7114,10 @@ bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLength
|
||||
this->text = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->preannounce_media_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -7102,6 +7125,8 @@ bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLength
|
||||
void VoiceAssistantAnnounceRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->media_id);
|
||||
buffer.encode_string(2, this->text);
|
||||
buffer.encode_string(3, this->preannounce_media_id);
|
||||
buffer.encode_bool(4, this->start_conversation);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const {
|
||||
@ -7114,6 +7139,14 @@ void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const {
|
||||
out.append(" text: ");
|
||||
out.append("'").append(this->text).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" preannounce_media_id: ");
|
||||
out.append("'").append(this->preannounce_media_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" start_conversation: ");
|
||||
out.append(YESNO(this->start_conversation));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -354,6 +354,7 @@ class DeviceInfoResponse : public ProtoMessage {
|
||||
uint32_t legacy_voice_assistant_version{0};
|
||||
uint32_t voice_assistant_feature_flags{0};
|
||||
std::string suggested_area{};
|
||||
std::string bluetooth_mac_address{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@ -1831,6 +1832,8 @@ class VoiceAssistantAnnounceRequest : public ProtoMessage {
|
||||
public:
|
||||
std::string media_id{};
|
||||
std::string text{};
|
||||
std::string preannounce_media_id{};
|
||||
bool start_conversation{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@ -1838,6 +1841,7 @@ class VoiceAssistantAnnounceRequest : public ProtoMessage {
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class VoiceAssistantAnnounceFinished : public ProtoMessage {
|
||||
public:
|
||||
|
@ -72,7 +72,7 @@ void APIServer::setup() {
|
||||
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->remove_)
|
||||
c->send_log_message(level, tag, message);
|
||||
c->try_send_log_message(level, tag, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -86,7 +86,7 @@ void APIServer::setup() {
|
||||
[this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->remove_)
|
||||
c->send_camera_state(image);
|
||||
c->set_camera_state(image);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -10,37 +10,63 @@ namespace api {
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
|
||||
return this->client_->send_binary_sensor_info(binary_sensor);
|
||||
this->client_->send_binary_sensor_info(binary_sensor);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); }
|
||||
bool ListEntitiesIterator::on_cover(cover::Cover *cover) {
|
||||
this->client_->send_cover_info(cover);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool ListEntitiesIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_info(fan); }
|
||||
bool ListEntitiesIterator::on_fan(fan::Fan *fan) {
|
||||
this->client_->send_fan_info(fan);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); }
|
||||
bool ListEntitiesIterator::on_light(light::LightState *light) {
|
||||
this->client_->send_light_info(light);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_info(sensor); }
|
||||
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) {
|
||||
this->client_->send_sensor_info(sensor);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); }
|
||||
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) {
|
||||
this->client_->send_switch_info(a_switch);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); }
|
||||
bool ListEntitiesIterator::on_button(button::Button *button) {
|
||||
this->client_->send_button_info(button);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
|
||||
return this->client_->send_text_sensor_info(text_sensor);
|
||||
this->client_->send_text_sensor_info(text_sensor);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_info(a_lock); }
|
||||
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) {
|
||||
this->client_->send_lock_info(a_lock);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
bool ListEntitiesIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_info(valve); }
|
||||
bool ListEntitiesIterator::on_valve(valve::Valve *valve) {
|
||||
this->client_->send_valve_info(valve);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
|
||||
@ -52,55 +78,83 @@ bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
|
||||
return this->client_->send_camera_info(camera);
|
||||
this->client_->send_camera_info(camera);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_info(climate); }
|
||||
bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
|
||||
this->client_->send_climate_info(climate);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); }
|
||||
bool ListEntitiesIterator::on_number(number::Number *number) {
|
||||
this->client_->send_number_info(number);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_info(date); }
|
||||
bool ListEntitiesIterator::on_date(datetime::DateEntity *date) {
|
||||
this->client_->send_date_info(date);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_TIME
|
||||
bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_info(time); }
|
||||
bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) {
|
||||
this->client_->send_time_info(time);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) {
|
||||
return this->client_->send_datetime_info(datetime);
|
||||
this->client_->send_datetime_info(datetime);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); }
|
||||
bool ListEntitiesIterator::on_text(text::Text *text) {
|
||||
this->client_->send_text_info(text);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
bool ListEntitiesIterator::on_select(select::Select *select) { return this->client_->send_select_info(select); }
|
||||
bool ListEntitiesIterator::on_select(select::Select *select) {
|
||||
this->client_->send_select_info(select);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_player) {
|
||||
return this->client_->send_media_player_info(media_player);
|
||||
this->client_->send_media_player_info(media_player);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
|
||||
return this->client_->send_alarm_control_panel_info(a_alarm_control_panel);
|
||||
this->client_->send_alarm_control_panel_info(a_alarm_control_panel);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); }
|
||||
bool ListEntitiesIterator::on_event(event::Event *event) {
|
||||
this->client_->send_event_info(event);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_info(update); }
|
||||
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) {
|
||||
this->client_->send_update_info(update);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
|
@ -80,6 +80,7 @@ class ListEntitiesIterator : public ComponentIterator {
|
||||
bool on_update(update::UpdateEntity *update) override;
|
||||
#endif
|
||||
bool on_end() override;
|
||||
bool completed() { return this->state_ == IteratorState::NONE; }
|
||||
|
||||
protected:
|
||||
APIConnection *client_;
|
||||
|
@ -76,6 +76,8 @@ class InitialStateIterator : public ComponentIterator {
|
||||
#ifdef USE_UPDATE
|
||||
bool on_update(update::UpdateEntity *update) override;
|
||||
#endif
|
||||
bool completed() { return this->state_ == IteratorState::NONE; }
|
||||
|
||||
protected:
|
||||
APIConnection *client_;
|
||||
};
|
||||
|
@ -118,4 +118,4 @@ def final_validate_audio_schema(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_library("esphome/esp-audio-libs", "1.1.1")
|
||||
cg.add_library("esphome/esp-audio-libs", "1.1.3")
|
||||
|
@ -66,19 +66,30 @@ esp_err_t AudioDecoder::start(AudioFileType audio_file_type) {
|
||||
case AudioFileType::FLAC:
|
||||
this->flac_decoder_ = make_unique<esp_audio_libs::flac::FLACDecoder>();
|
||||
this->free_buffer_required_ =
|
||||
this->output_transfer_buffer_->capacity(); // We'll revise this after reading the header
|
||||
this->output_transfer_buffer_->capacity(); // Adjusted and reallocated after reading the header
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_AUDIO_MP3_SUPPORT
|
||||
case AudioFileType::MP3:
|
||||
this->mp3_decoder_ = esp_audio_libs::helix_decoder::MP3InitDecoder();
|
||||
|
||||
// MP3 always has 1152 samples per chunk
|
||||
this->free_buffer_required_ = 1152 * sizeof(int16_t) * 2; // samples * size per sample * channels
|
||||
|
||||
// Always reallocate the output transfer buffer to the smallest necessary size
|
||||
this->output_transfer_buffer_->reallocate(this->free_buffer_required_);
|
||||
break;
|
||||
#endif
|
||||
case AudioFileType::WAV:
|
||||
this->wav_decoder_ = make_unique<esp_audio_libs::wav_decoder::WAVDecoder>();
|
||||
this->wav_decoder_->reset();
|
||||
|
||||
// Processing WAVs doesn't actually require a specific amount of buffer size, as it is already in PCM format.
|
||||
// Thus, we don't reallocate to a minimum size.
|
||||
this->free_buffer_required_ = 1024;
|
||||
if (this->output_transfer_buffer_->capacity() < this->free_buffer_required_) {
|
||||
this->output_transfer_buffer_->reallocate(this->free_buffer_required_);
|
||||
}
|
||||
break;
|
||||
case AudioFileType::NONE:
|
||||
default:
|
||||
@ -116,10 +127,18 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
|
||||
|
||||
uint32_t decoding_start = millis();
|
||||
|
||||
bool first_loop_iteration = true;
|
||||
|
||||
size_t bytes_processed = 0;
|
||||
size_t bytes_available_before_processing = 0;
|
||||
|
||||
while (state == FileDecoderState::MORE_TO_PROCESS) {
|
||||
// Transfer decoded out
|
||||
if (!this->pause_output_) {
|
||||
size_t bytes_written = this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS));
|
||||
// Never shift the data in the output transfer buffer to avoid unnecessary, slow data moves
|
||||
size_t bytes_written =
|
||||
this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS), false);
|
||||
|
||||
if (this->audio_stream_info_.has_value()) {
|
||||
this->accumulated_frames_written_ += this->audio_stream_info_.value().bytes_to_frames(bytes_written);
|
||||
this->playback_ms_ +=
|
||||
@ -138,12 +157,24 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
|
||||
|
||||
// Decode more audio
|
||||
|
||||
size_t bytes_read = this->input_transfer_buffer_->transfer_data_from_source(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS));
|
||||
// Only shift data on the first loop iteration to avoid unnecessary, slow moves
|
||||
size_t bytes_read = this->input_transfer_buffer_->transfer_data_from_source(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS),
|
||||
first_loop_iteration);
|
||||
|
||||
if ((this->potentially_failed_count_ > 0) && (bytes_read == 0)) {
|
||||
if (!first_loop_iteration && (this->input_transfer_buffer_->available() < bytes_processed)) {
|
||||
// Less data is available than what was processed in last iteration, so don't attempt to decode.
|
||||
// This attempts to avoid the decoder from consistently trying to decode an incomplete frame. The transfer buffer
|
||||
// will shift the remaining data to the start and copy more from the source the next time the decode function is
|
||||
// called
|
||||
break;
|
||||
}
|
||||
|
||||
bytes_available_before_processing = this->input_transfer_buffer_->available();
|
||||
|
||||
if ((this->potentially_failed_count_ > 10) && (bytes_read == 0)) {
|
||||
// Failed to decode in last attempt and there is no new data
|
||||
|
||||
if (this->input_transfer_buffer_->free() == 0) {
|
||||
if ((this->input_transfer_buffer_->free() == 0) && first_loop_iteration) {
|
||||
// The input buffer is full. Since it previously failed on the exact same data, we can never recover
|
||||
state = FileDecoderState::FAILED;
|
||||
} else {
|
||||
@ -175,6 +206,9 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
|
||||
}
|
||||
}
|
||||
|
||||
first_loop_iteration = false;
|
||||
bytes_processed = bytes_available_before_processing - this->input_transfer_buffer_->available();
|
||||
|
||||
if (state == FileDecoderState::POTENTIALLY_FAILED) {
|
||||
++this->potentially_failed_count_;
|
||||
} else if (state == FileDecoderState::END_OF_FILE) {
|
||||
@ -207,14 +241,12 @@ FileDecoderState AudioDecoder::decode_flac_() {
|
||||
size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
|
||||
this->input_transfer_buffer_->decrease_buffer_length(bytes_consumed);
|
||||
|
||||
// Reallocate the output transfer buffer to the smallest necessary size
|
||||
this->free_buffer_required_ = flac_decoder_->get_output_buffer_size_bytes();
|
||||
if (this->output_transfer_buffer_->capacity() < this->free_buffer_required_) {
|
||||
// Output buffer is not big enough
|
||||
if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) {
|
||||
// Couldn't reallocate output buffer
|
||||
return FileDecoderState::FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
this->audio_stream_info_ =
|
||||
audio::AudioStreamInfo(this->flac_decoder_->get_sample_depth(), this->flac_decoder_->get_num_channels(),
|
||||
|
@ -259,14 +259,14 @@ AudioReaderState AudioReader::file_read_() {
|
||||
}
|
||||
|
||||
AudioReaderState AudioReader::http_read_() {
|
||||
this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS));
|
||||
this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS), false);
|
||||
|
||||
if (esp_http_client_is_complete_data_received(this->client_)) {
|
||||
if (this->output_transfer_buffer_->available() == 0) {
|
||||
this->cleanup_connection_();
|
||||
return AudioReaderState::FINISHED;
|
||||
}
|
||||
} else {
|
||||
} else if (this->output_transfer_buffer_->free() > 0) {
|
||||
size_t bytes_to_read = this->output_transfer_buffer_->free();
|
||||
int received_len =
|
||||
esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(), bytes_to_read);
|
||||
|
@ -93,8 +93,9 @@ AudioResamplerState AudioResampler::resample(bool stop_gracefully, int32_t *ms_d
|
||||
}
|
||||
|
||||
if (!this->pause_output_) {
|
||||
// Move audio data to the sink
|
||||
this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS));
|
||||
// Move audio data to the sink without shifting the data in the output transfer buffer to avoid unnecessary, slow
|
||||
// data moves
|
||||
this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS), false);
|
||||
} else {
|
||||
// If paused, block to avoid wasting CPU resources
|
||||
delay(READ_WRITE_TIMEOUT_MS);
|
||||
@ -115,6 +116,7 @@ AudioResamplerState AudioResampler::resample(bool stop_gracefully, int32_t *ms_d
|
||||
|
||||
if ((this->input_stream_info_.get_sample_rate() != this->output_stream_info_.get_sample_rate()) ||
|
||||
(this->input_stream_info_.get_bits_per_sample() != this->output_stream_info_.get_bits_per_sample())) {
|
||||
// Adjust gain by -3 dB to avoid clipping due to the resampling process
|
||||
esp_audio_libs::resampler::ResamplerResults results =
|
||||
this->resampler_->resample(this->input_transfer_buffer_->get_buffer_start(),
|
||||
this->output_transfer_buffer_->get_buffer_end(), frames_available, frames_free, -3);
|
||||
|
@ -33,12 +33,17 @@ size_t AudioTransferBuffer::free() const {
|
||||
if (this->buffer_size_ == 0) {
|
||||
return 0;
|
||||
}
|
||||
return this->buffer_size_ - (this->buffer_length_ - (this->data_start_ - this->buffer_));
|
||||
return this->buffer_size_ - (this->buffer_length_ + (this->data_start_ - this->buffer_));
|
||||
}
|
||||
|
||||
void AudioTransferBuffer::decrease_buffer_length(size_t bytes) {
|
||||
this->buffer_length_ -= bytes;
|
||||
if (this->buffer_length_ > 0) {
|
||||
this->data_start_ += bytes;
|
||||
} else {
|
||||
// All the data in the buffer has been consumed, reset the start pointer
|
||||
this->data_start_ = this->buffer_;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioTransferBuffer::increase_buffer_length(size_t bytes) { this->buffer_length_ += bytes; }
|
||||
@ -71,7 +76,7 @@ bool AudioTransferBuffer::has_buffered_data() const {
|
||||
|
||||
bool AudioTransferBuffer::reallocate(size_t new_buffer_size) {
|
||||
if (this->buffer_length_ > 0) {
|
||||
// Already has data in the buffer, fail
|
||||
// Buffer currently has data, so reallocation is impossible
|
||||
return false;
|
||||
}
|
||||
this->deallocate_buffer_();
|
||||
@ -106,12 +111,14 @@ void AudioTransferBuffer::deallocate_buffer_() {
|
||||
this->buffer_length_ = 0;
|
||||
}
|
||||
|
||||
size_t AudioSourceTransferBuffer::transfer_data_from_source(TickType_t ticks_to_wait) {
|
||||
size_t AudioSourceTransferBuffer::transfer_data_from_source(TickType_t ticks_to_wait, bool pre_shift) {
|
||||
if (pre_shift) {
|
||||
// Shift data in buffer to start
|
||||
if (this->buffer_length_ > 0) {
|
||||
memmove(this->buffer_, this->data_start_, this->buffer_length_);
|
||||
}
|
||||
this->data_start_ = this->buffer_;
|
||||
}
|
||||
|
||||
size_t bytes_to_read = this->free();
|
||||
size_t bytes_read = 0;
|
||||
@ -125,7 +132,7 @@ size_t AudioSourceTransferBuffer::transfer_data_from_source(TickType_t ticks_to_
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
size_t AudioSinkTransferBuffer::transfer_data_to_sink(TickType_t ticks_to_wait) {
|
||||
size_t AudioSinkTransferBuffer::transfer_data_to_sink(TickType_t ticks_to_wait, bool post_shift) {
|
||||
size_t bytes_written = 0;
|
||||
if (this->available()) {
|
||||
#ifdef USE_SPEAKER
|
||||
@ -139,11 +146,14 @@ size_t AudioSinkTransferBuffer::transfer_data_to_sink(TickType_t ticks_to_wait)
|
||||
}
|
||||
|
||||
this->decrease_buffer_length(bytes_written);
|
||||
}
|
||||
|
||||
if (post_shift) {
|
||||
// Shift unwritten data to the start of the buffer
|
||||
memmove(this->buffer_, this->data_start_, this->buffer_length_);
|
||||
this->data_start_ = this->buffer_;
|
||||
}
|
||||
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,7 @@ class AudioTransferBuffer {
|
||||
|
||||
protected:
|
||||
/// @brief Allocates the transfer buffer in external memory, if available.
|
||||
/// @param buffer_size The number of bytes to allocate
|
||||
/// @return True is successful, false otherwise.
|
||||
bool allocate_buffer_(size_t buffer_size);
|
||||
|
||||
@ -89,8 +90,10 @@ class AudioSinkTransferBuffer : public AudioTransferBuffer {
|
||||
|
||||
/// @brief Writes any available data in the transfer buffer to the sink.
|
||||
/// @param ticks_to_wait FreeRTOS ticks to block while waiting for the sink to have enough space
|
||||
/// @param post_shift If true, all remaining data is moved to the start of the buffer after transferring to the sink.
|
||||
/// Defaults to true.
|
||||
/// @return Number of bytes written
|
||||
size_t transfer_data_to_sink(TickType_t ticks_to_wait);
|
||||
size_t transfer_data_to_sink(TickType_t ticks_to_wait, bool post_shift = true);
|
||||
|
||||
/// @brief Adds a ring buffer as the transfer buffer's sink.
|
||||
/// @param ring_buffer weak_ptr to the allocated ring buffer
|
||||
@ -125,8 +128,10 @@ class AudioSourceTransferBuffer : public AudioTransferBuffer {
|
||||
|
||||
/// @brief Reads any available data from the sink into the transfer buffer.
|
||||
/// @param ticks_to_wait FreeRTOS ticks to block while waiting for the source to have enough data
|
||||
/// @param pre_shift If true, any unwritten data is moved to the start of the buffer before transferring from the
|
||||
/// source. Defaults to true.
|
||||
/// @return Number of bytes read
|
||||
size_t transfer_data_from_source(TickType_t ticks_to_wait);
|
||||
size_t transfer_data_from_source(TickType_t ticks_to_wait, bool pre_shift = true);
|
||||
|
||||
/// @brief Adds a ring buffer as the transfer buffer's source.
|
||||
/// @param ring_buffer weak_ptr to the allocated ring buffer
|
||||
|
@ -15,6 +15,9 @@
|
||||
|
||||
#include "bluetooth_connection.h"
|
||||
|
||||
#include <esp_bt.h>
|
||||
#include <esp_bt_device.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace bluetooth_proxy {
|
||||
|
||||
@ -114,6 +117,11 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
||||
return flags;
|
||||
}
|
||||
|
||||
std::string get_bluetooth_mac_address_pretty() {
|
||||
const uint8_t *mac = esp_bt_dev_get_address();
|
||||
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
}
|
||||
|
||||
protected:
|
||||
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
|
||||
|
||||
|
@ -95,7 +95,7 @@ void BMP085Component::read_pressure_() {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t value = (uint32_t(buffer[0]) << 16) | (uint32_t(buffer[1]) << 8) | uint32_t(buffer[0]);
|
||||
uint32_t value = (uint32_t(buffer[0]) << 16) | (uint32_t(buffer[1]) << 8) | uint32_t(buffer[2]);
|
||||
if ((value >> 5) == 0) {
|
||||
ESP_LOGW(TAG, "Invalid pressure!");
|
||||
this->status_set_warning();
|
||||
|
2
esphome/components/chsc6x/__init__.py
Normal file
2
esphome/components/chsc6x/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
CODEOWNERS = ["@kkosik20"]
|
||||
DEPENDENCIES = ["i2c"]
|
47
esphome/components/chsc6x/chsc6x_touchscreen.cpp
Normal file
47
esphome/components/chsc6x/chsc6x_touchscreen.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include "chsc6x_touchscreen.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace chsc6x {
|
||||
|
||||
void CHSC6XTouchscreen::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up CHSC6X Touchscreen...");
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
this->interrupt_pin_->setup();
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
}
|
||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||
this->y_raw_max_ = this->display_->get_native_height();
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, "CHSC6X Touchscreen setup complete");
|
||||
}
|
||||
|
||||
void CHSC6XTouchscreen::update_touches() {
|
||||
uint8_t data[CHSC6X_REG_STATUS_LEN];
|
||||
if (!this->read_bytes(CHSC6X_REG_STATUS, data, sizeof(data))) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t num_of_touches = data[CHSC6X_REG_STATUS_TOUCH];
|
||||
|
||||
if (num_of_touches == 1) {
|
||||
uint16_t x = data[CHSC6X_REG_STATUS_X_COR];
|
||||
uint16_t y = data[CHSC6X_REG_STATUS_Y_COR];
|
||||
this->add_raw_touch_position_(0, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void CHSC6XTouchscreen::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CHSC6X Touchscreen:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Touch timeout: %d", this->touch_timeout_);
|
||||
ESP_LOGCONFIG(TAG, " x_raw_max_: %d", this->x_raw_max_);
|
||||
ESP_LOGCONFIG(TAG, " y_raw_max_: %d", this->y_raw_max_);
|
||||
}
|
||||
|
||||
} // namespace chsc6x
|
||||
} // namespace esphome
|
34
esphome/components/chsc6x/chsc6x_touchscreen.h
Normal file
34
esphome/components/chsc6x/chsc6x_touchscreen.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace chsc6x {
|
||||
|
||||
static const char *const TAG = "chsc6x.touchscreen";
|
||||
|
||||
static const uint8_t CHSC6X_REG_STATUS = 0x00;
|
||||
static const uint8_t CHSC6X_REG_STATUS_TOUCH = 0x00;
|
||||
static const uint8_t CHSC6X_REG_STATUS_X_COR = 0x02;
|
||||
static const uint8_t CHSC6X_REG_STATUS_Y_COR = 0x04;
|
||||
static const uint8_t CHSC6X_REG_STATUS_LEN = 0x05;
|
||||
static const uint8_t CHSC6X_CHIP_ID = 0x2e;
|
||||
|
||||
class CHSC6XTouchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update_touches() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
|
||||
protected:
|
||||
InternalGPIOPin *interrupt_pin_{};
|
||||
};
|
||||
|
||||
} // namespace chsc6x
|
||||
} // namespace esphome
|
33
esphome/components/chsc6x/touchscreen.py
Normal file
33
esphome/components/chsc6x/touchscreen.py
Normal file
@ -0,0 +1,33 @@
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, touchscreen
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN
|
||||
|
||||
chsc6x_ns = cg.esphome_ns.namespace("chsc6x")
|
||||
|
||||
CHSC6XTouchscreen = chsc6x_ns.class_(
|
||||
"CHSC6XTouchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
touchscreen.touchscreen_schema("100ms")
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CHSC6XTouchscreen),
|
||||
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
|
||||
}
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x2E))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await touchscreen.register_touchscreen(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
|
||||
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
|
@ -128,7 +128,6 @@ VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
def visual_temperature_step(value):
|
||||
|
||||
# Allow defining target/current temperature steps separately
|
||||
if isinstance(value, dict):
|
||||
return VISUAL_TEMPERATURE_STEP_SCHEMA(value)
|
||||
|
@ -1,28 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
|
||||
from .. import cst816_ns
|
||||
from ..touchscreen import CST816Touchscreen, CST816ButtonListener
|
||||
|
||||
CONF_CST816_ID = "cst816_id"
|
||||
|
||||
CST816Button = cst816_ns.class_(
|
||||
"CST816Button",
|
||||
binary_sensor.BinarySensor,
|
||||
cg.Component,
|
||||
CST816ButtonListener,
|
||||
cg.Parented.template(CST816Touchscreen),
|
||||
CONFIG_SCHEMA = cv.invalid(
|
||||
"The CST816 binary sensor has been removed. Instead use the touchscreen binary sensor with the 'use_raw' flag set."
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(CST816Button).extend(
|
||||
{
|
||||
cv.GenerateID(CONF_CST816_ID): cv.use_id(CST816Touchscreen),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await binary_sensor.new_binary_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_parented(var, config[CONF_CST816_ID])
|
||||
|
@ -1,27 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/cst816/touchscreen/cst816_touchscreen.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cst816 {
|
||||
|
||||
class CST816Button : public binary_sensor::BinarySensor,
|
||||
public Component,
|
||||
public CST816ButtonListener,
|
||||
public Parented<CST816Touchscreen> {
|
||||
public:
|
||||
void setup() override {
|
||||
this->parent_->register_button_listener(this);
|
||||
this->publish_initial_state(false);
|
||||
}
|
||||
|
||||
void dump_config() override { LOG_BINARY_SENSOR("", "CST816 Button", this); }
|
||||
|
||||
void update_button(bool state) override { this->publish_state(state); }
|
||||
};
|
||||
|
||||
} // namespace cst816
|
||||
} // namespace esphome
|
@ -37,14 +37,6 @@ void CST816Touchscreen::continue_setup_() {
|
||||
ESP_LOGCONFIG(TAG, "CST816 Touchscreen setup complete");
|
||||
}
|
||||
|
||||
void CST816Touchscreen::update_button_state_(bool state) {
|
||||
if (this->button_touched_ == state)
|
||||
return;
|
||||
this->button_touched_ = state;
|
||||
for (auto *listener : this->button_listeners_)
|
||||
listener->update_button(state);
|
||||
}
|
||||
|
||||
void CST816Touchscreen::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up CST816 Touchscreen...");
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
@ -68,18 +60,13 @@ void CST816Touchscreen::update_touches() {
|
||||
}
|
||||
uint8_t num_of_touches = data[REG_TOUCH_NUM] & 3;
|
||||
if (num_of_touches == 0) {
|
||||
this->update_button_state_(false);
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t x = encode_uint16(data[REG_XPOS_HIGH] & 0xF, data[REG_XPOS_LOW]);
|
||||
uint16_t y = encode_uint16(data[REG_YPOS_HIGH] & 0xF, data[REG_YPOS_LOW]);
|
||||
ESP_LOGV(TAG, "Read touch %d/%d", x, y);
|
||||
if (x >= this->x_raw_max_) {
|
||||
this->update_button_state_(true);
|
||||
} else {
|
||||
this->add_raw_touch_position_(0, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void CST816Touchscreen::dump_config() {
|
||||
@ -87,6 +74,8 @@ void CST816Touchscreen::dump_config() {
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
ESP_LOGCONFIG(TAG, " X Raw Min: %d, X Raw Max: %d", this->x_raw_min_, this->x_raw_max_);
|
||||
ESP_LOGCONFIG(TAG, " Y Raw Min: %d, Y Raw Max: %d", this->y_raw_min_, this->y_raw_max_);
|
||||
const char *name;
|
||||
switch (this->chip_id_) {
|
||||
case CST820_CHIP_ID:
|
||||
|
@ -40,7 +40,6 @@ class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
|
||||
public:
|
||||
void setup() override;
|
||||
void update_touches() override;
|
||||
void register_button_listener(CST816ButtonListener *listener) { this->button_listeners_.push_back(listener); }
|
||||
void dump_config() override;
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
@ -49,14 +48,11 @@ class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
|
||||
|
||||
protected:
|
||||
void continue_setup_();
|
||||
void update_button_state_(bool state);
|
||||
|
||||
InternalGPIOPin *interrupt_pin_{};
|
||||
GPIOPin *reset_pin_{};
|
||||
uint8_t chip_id_{};
|
||||
bool skip_probe_{}; // if set, do not expect to be able to probe the controller on the i2c bus.
|
||||
std::vector<CST816ButtonListener *> button_listeners_;
|
||||
bool button_touched_{};
|
||||
};
|
||||
|
||||
} // namespace cst816
|
||||
|
@ -66,7 +66,9 @@ FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant
|
||||
|
||||
async def to_code(config):
|
||||
uuid = config[CONF_UUID].hex
|
||||
uuid_arr = [cg.RawExpression(f"0x{uuid[i:i + 2]}") for i in range(0, len(uuid), 2)]
|
||||
uuid_arr = [
|
||||
cg.RawExpression(f"0x{uuid[i : i + 2]}") for i in range(0, len(uuid), 2)
|
||||
]
|
||||
var = cg.new_Pvariable(config[CONF_ID], uuid_arr)
|
||||
|
||||
parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID])
|
||||
|
@ -112,8 +112,7 @@ def validate_supports(value):
|
||||
)
|
||||
if is_pullup and num == 16:
|
||||
raise cv.Invalid(
|
||||
"GPIO Pin 16 does not support pullup pin mode. "
|
||||
"Please choose another pin.",
|
||||
"GPIO Pin 16 does not support pullup pin mode. Please choose another pin.",
|
||||
[CONF_MODE, CONF_PULLUP],
|
||||
)
|
||||
if is_pulldown and num != 16:
|
||||
|
@ -1,3 +1,4 @@
|
||||
from collections.abc import MutableMapping
|
||||
import functools
|
||||
import hashlib
|
||||
import logging
|
||||
@ -6,10 +7,10 @@ from pathlib import Path
|
||||
import re
|
||||
|
||||
import esphome_glyphsets as glyphsets
|
||||
import freetype
|
||||
from freetype import Face, ft_pixel_mode_grays, ft_pixel_mode_mono
|
||||
import requests
|
||||
|
||||
from esphome import core, external_files
|
||||
from esphome import external_files
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@ -26,7 +27,7 @@ from esphome.const import (
|
||||
CONF_WEIGHT,
|
||||
)
|
||||
from esphome.core import CORE, HexInt
|
||||
from esphome.helpers import copy_file_if_changed, cpp_string_escape
|
||||
from esphome.helpers import cpp_string_escape
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -49,13 +50,42 @@ CONF_IGNORE_MISSING_GLYPHS = "ignore_missing_glyphs"
|
||||
|
||||
|
||||
# Cache loaded freetype fonts
|
||||
class FontCache(dict):
|
||||
def __missing__(self, key):
|
||||
try:
|
||||
res = self[key] = freetype.Face(key)
|
||||
return res
|
||||
except freetype.FT_Exception as e:
|
||||
raise cv.Invalid(f"Could not load Font file {key}: {e}") from e
|
||||
class FontCache(MutableMapping):
|
||||
@staticmethod
|
||||
def get_name(value):
|
||||
if CONF_FAMILY in value:
|
||||
return (
|
||||
f"{value[CONF_FAMILY]}:{int(value[CONF_ITALIC])}:{value[CONF_WEIGHT]}"
|
||||
)
|
||||
if CONF_URL in value:
|
||||
return value[CONF_URL]
|
||||
return value[CONF_PATH]
|
||||
|
||||
@staticmethod
|
||||
def _keytransform(value):
|
||||
if CONF_FAMILY in value:
|
||||
return f"gfont:{value[CONF_FAMILY]}:{int(value[CONF_ITALIC])}:{value[CONF_WEIGHT]}"
|
||||
if CONF_URL in value:
|
||||
return f"url:{value[CONF_URL]}"
|
||||
return f"file:{value[CONF_PATH]}"
|
||||
|
||||
def __init__(self):
|
||||
self.store = {}
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self.store[self._keytransform(key)]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.store)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.store)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.store[self._keytransform(item)]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.store[self._keytransform(key)] = Face(str(value))
|
||||
|
||||
|
||||
FONT_CACHE = FontCache()
|
||||
@ -109,14 +139,21 @@ def check_missing_glyphs(file, codepoints, warning: bool = False):
|
||||
)
|
||||
if count > 10:
|
||||
missing_str += f"\n and {count - 10} more."
|
||||
message = f"Font {Path(file).name} is missing {count} glyph{'s' if count != 1 else ''}:\n {missing_str}"
|
||||
message = f"Font {FontCache.get_name(file)} is missing {count} glyph{'s' if count != 1 else ''}:\n {missing_str}"
|
||||
if warning:
|
||||
_LOGGER.warning(message)
|
||||
else:
|
||||
raise cv.Invalid(message)
|
||||
|
||||
|
||||
def validate_glyphs(config):
|
||||
def pt_to_px(pt):
|
||||
"""
|
||||
Convert a point size to pixels, rounding up to the nearest pixel
|
||||
"""
|
||||
return (pt + 63) // 64
|
||||
|
||||
|
||||
def validate_font_config(config):
|
||||
"""
|
||||
Check for duplicate codepoints, then check that all requested codepoints actually
|
||||
have glyphs defined in the appropriate font file.
|
||||
@ -142,43 +179,51 @@ def validate_glyphs(config):
|
||||
)
|
||||
# Make setpoints and glyphspoints disjoint
|
||||
setpoints.difference_update(glyphspoints)
|
||||
if fileconf[CONF_TYPE] == TYPE_LOCAL_BITMAP:
|
||||
# Pillow only allows 256 glyphs per bitmap font. Not sure if that is a Pillow limitation
|
||||
# or a file format limitation
|
||||
if any(x >= 256 for x in setpoints.copy().union(glyphspoints)):
|
||||
raise cv.Invalid("Codepoints in bitmap fonts must be in the range 0-255")
|
||||
else:
|
||||
# for TT fonts, check that glyphs are actually present
|
||||
# check that glyphs are actually present
|
||||
# Check extras against their own font, exclude from parent font codepoints
|
||||
for extra in config[CONF_EXTRAS]:
|
||||
points = {ord(x) for x in flatten(extra[CONF_GLYPHS])}
|
||||
glyphspoints.difference_update(points)
|
||||
setpoints.difference_update(points)
|
||||
check_missing_glyphs(extra[CONF_FILE][CONF_PATH], points)
|
||||
check_missing_glyphs(extra[CONF_FILE], points)
|
||||
|
||||
# A named glyph that can't be provided is an error
|
||||
check_missing_glyphs(fileconf[CONF_PATH], glyphspoints)
|
||||
|
||||
check_missing_glyphs(fileconf, glyphspoints)
|
||||
# A missing glyph from a set is a warning.
|
||||
if not config[CONF_IGNORE_MISSING_GLYPHS]:
|
||||
check_missing_glyphs(fileconf[CONF_PATH], setpoints, warning=True)
|
||||
check_missing_glyphs(fileconf, setpoints, warning=True)
|
||||
|
||||
# Populate the default after the above checks so that use of the default doesn't trigger errors
|
||||
font = FONT_CACHE[fileconf]
|
||||
if not config[CONF_GLYPHS] and not config[CONF_GLYPHSETS]:
|
||||
if fileconf[CONF_TYPE] == TYPE_LOCAL_BITMAP:
|
||||
config[CONF_GLYPHS] = [DEFAULT_GLYPHS]
|
||||
else:
|
||||
# set a default glyphset, intersected with what the font actually offers
|
||||
font = FONT_CACHE[fileconf[CONF_PATH]]
|
||||
config[CONF_GLYPHS] = [
|
||||
chr(x)
|
||||
for x in glyphsets.unicodes_per_glyphset(DEFAULT_GLYPHSET)
|
||||
if font.get_char_index(x) != 0
|
||||
]
|
||||
|
||||
if font.has_fixed_sizes:
|
||||
sizes = [pt_to_px(x.size) for x in font.available_sizes]
|
||||
if not sizes:
|
||||
raise cv.Invalid(
|
||||
f"Font {FontCache.get_name(fileconf)} has no available sizes"
|
||||
)
|
||||
if CONF_SIZE not in config:
|
||||
config[CONF_SIZE] = sizes[0]
|
||||
elif config[CONF_SIZE] not in sizes:
|
||||
sizes = ", ".join(str(x) for x in sizes)
|
||||
raise cv.Invalid(
|
||||
f"Font {FontCache.get_name(fileconf)} only has size{'s' if len(sizes) != 1 else ''} {sizes} available"
|
||||
)
|
||||
elif CONF_SIZE not in config:
|
||||
config[CONF_SIZE] = 20
|
||||
|
||||
return config
|
||||
|
||||
|
||||
FONT_EXTENSIONS = (".ttf", ".woff", ".otf")
|
||||
FONT_EXTENSIONS = (".ttf", ".woff", ".otf", "bdf", ".pcf")
|
||||
|
||||
|
||||
def validate_truetype_file(value):
|
||||
@ -187,24 +232,30 @@ def validate_truetype_file(value):
|
||||
f"Please unzip the font archive '{value}' first and then use the .ttf files inside."
|
||||
)
|
||||
if not any(map(value.lower().endswith, FONT_EXTENSIONS)):
|
||||
raise cv.Invalid(f"Only {FONT_EXTENSIONS} files are supported.")
|
||||
raise cv.Invalid(f"Only {', '.join(FONT_EXTENSIONS)} files are supported.")
|
||||
return CORE.relative_config_path(cv.file_(value))
|
||||
|
||||
|
||||
def add_local_file(value):
|
||||
if value in FONT_CACHE:
|
||||
return value
|
||||
path = value[CONF_PATH]
|
||||
if not os.path.isfile(path):
|
||||
raise cv.Invalid(f"File '{path}' not found.")
|
||||
FONT_CACHE[value] = path
|
||||
return value
|
||||
|
||||
|
||||
TYPE_LOCAL = "local"
|
||||
TYPE_LOCAL_BITMAP = "local_bitmap"
|
||||
TYPE_GFONTS = "gfonts"
|
||||
TYPE_WEB = "web"
|
||||
LOCAL_SCHEMA = cv.Schema(
|
||||
LOCAL_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PATH): validate_truetype_file,
|
||||
}
|
||||
)
|
||||
|
||||
LOCAL_BITMAP_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PATH): cv.file_,
|
||||
}
|
||||
),
|
||||
add_local_file,
|
||||
)
|
||||
|
||||
FULLPATH_SCHEMA = cv.maybe_simple_value(
|
||||
@ -235,27 +286,23 @@ def _compute_local_font_path(value: dict) -> Path:
|
||||
h.update(url.encode())
|
||||
key = h.hexdigest()[:8]
|
||||
base_dir = external_files.compute_local_file_dir(DOMAIN)
|
||||
_LOGGER.debug("_compute_local_font_path: base_dir=%s", base_dir / key)
|
||||
_LOGGER.debug("_compute_local_font_path: %s", base_dir / key)
|
||||
return base_dir / key
|
||||
|
||||
|
||||
def get_font_path(value, font_type) -> Path:
|
||||
if font_type == TYPE_GFONTS:
|
||||
name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1"
|
||||
return external_files.compute_local_file_dir(DOMAIN) / f"{name}.ttf"
|
||||
if font_type == TYPE_WEB:
|
||||
return _compute_local_font_path(value) / "font.ttf"
|
||||
assert False
|
||||
|
||||
|
||||
def download_gfont(value):
|
||||
if value in FONT_CACHE:
|
||||
return value
|
||||
name = (
|
||||
f"{value[CONF_FAMILY]}:ital,wght@{int(value[CONF_ITALIC])},{value[CONF_WEIGHT]}"
|
||||
)
|
||||
url = f"https://fonts.googleapis.com/css2?family={name}"
|
||||
path = get_font_path(value, TYPE_GFONTS)
|
||||
path = (
|
||||
external_files.compute_local_file_dir(DOMAIN)
|
||||
/ f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1.ttf"
|
||||
)
|
||||
if not external_files.is_file_recent(str(path), value[CONF_REFRESH]):
|
||||
_LOGGER.debug("download_gfont: path=%s", path)
|
||||
|
||||
try:
|
||||
req = requests.get(url, timeout=external_files.NETWORK_TIMEOUT)
|
||||
req.raise_for_status()
|
||||
@ -275,16 +322,23 @@ def download_gfont(value):
|
||||
_LOGGER.debug("download_gfont: ttf_url=%s", ttf_url)
|
||||
|
||||
external_files.download_content(ttf_url, path)
|
||||
return FULLPATH_SCHEMA(path)
|
||||
# In case the remote file is not modified, the download_content function will return the existing file,
|
||||
# so update the modification time to now.
|
||||
path.touch()
|
||||
FONT_CACHE[value] = path
|
||||
return value
|
||||
|
||||
|
||||
def download_web_font(value):
|
||||
if value in FONT_CACHE:
|
||||
return value
|
||||
url = value[CONF_URL]
|
||||
path = get_font_path(value, TYPE_WEB)
|
||||
path = _compute_local_font_path(value) / "font.ttf"
|
||||
|
||||
external_files.download_content(url, path)
|
||||
_LOGGER.debug("download_web_font: path=%s", path)
|
||||
return FULLPATH_SCHEMA(path)
|
||||
FONT_CACHE[value] = path
|
||||
return value
|
||||
|
||||
|
||||
EXTERNAL_FONT_SCHEMA = cv.Schema(
|
||||
@ -340,15 +394,6 @@ def validate_file_shorthand(value):
|
||||
}
|
||||
)
|
||||
|
||||
if value.endswith(".pcf") or value.endswith(".bdf"):
|
||||
value = convert_bitmap_to_pillow_font(
|
||||
CORE.relative_config_path(cv.file_(value))
|
||||
)
|
||||
return {
|
||||
CONF_TYPE: TYPE_LOCAL_BITMAP,
|
||||
CONF_PATH: value,
|
||||
}
|
||||
|
||||
return font_file_schema(
|
||||
{
|
||||
CONF_TYPE: TYPE_LOCAL,
|
||||
@ -361,7 +406,6 @@ TYPED_FILE_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
TYPE_LOCAL: LOCAL_SCHEMA,
|
||||
TYPE_GFONTS: GFONTS_SCHEMA,
|
||||
TYPE_LOCAL_BITMAP: LOCAL_BITMAP_SCHEMA,
|
||||
TYPE_WEB: WEB_FONT_SCHEMA,
|
||||
}
|
||||
)
|
||||
@ -391,7 +435,7 @@ FONT_SCHEMA = cv.Schema(
|
||||
cv.one_of(*glyphsets.defined_glyphsets())
|
||||
),
|
||||
cv.Optional(CONF_IGNORE_MISSING_GLYPHS, default=False): cv.boolean,
|
||||
cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1),
|
||||
cv.Optional(CONF_SIZE): cv.int_range(min=1),
|
||||
cv.Optional(CONF_BPP, default=1): cv.one_of(1, 2, 4, 8),
|
||||
cv.Optional(CONF_EXTRAS, default=[]): cv.ensure_list(
|
||||
cv.Schema(
|
||||
@ -406,114 +450,19 @@ FONT_SCHEMA = cv.Schema(
|
||||
},
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(FONT_SCHEMA, validate_glyphs)
|
||||
|
||||
|
||||
# PIL doesn't provide a consistent interface for both TrueType and bitmap
|
||||
# fonts. So, we use our own wrappers to give us the consistency that we need.
|
||||
|
||||
|
||||
class TrueTypeFontWrapper:
|
||||
def __init__(self, font):
|
||||
self.font = font
|
||||
|
||||
def getoffset(self, glyph):
|
||||
_, (offset_x, offset_y) = self.font.font.getsize(glyph)
|
||||
return offset_x, offset_y
|
||||
|
||||
def getmask(self, glyph, **kwargs):
|
||||
return self.font.getmask(str(glyph), **kwargs)
|
||||
|
||||
def getmetrics(self, glyphs):
|
||||
return self.font.getmetrics()
|
||||
|
||||
|
||||
class BitmapFontWrapper:
|
||||
def __init__(self, font):
|
||||
self.font = font
|
||||
self.max_height = 0
|
||||
|
||||
def getoffset(self, glyph):
|
||||
return 0, 0
|
||||
|
||||
def getmask(self, glyph, **kwargs):
|
||||
return self.font.getmask(str(glyph), **kwargs)
|
||||
|
||||
def getmetrics(self, glyphs):
|
||||
max_height = 0
|
||||
for glyph in glyphs:
|
||||
mask = self.getmask(glyph, mode="1")
|
||||
_, height = mask.size
|
||||
max_height = max(max_height, height)
|
||||
return max_height, 0
|
||||
CONFIG_SCHEMA = cv.All(FONT_SCHEMA, validate_font_config)
|
||||
|
||||
|
||||
class EFont:
|
||||
def __init__(self, file, size, codepoints):
|
||||
def __init__(self, file, codepoints):
|
||||
self.codepoints = codepoints
|
||||
path = file[CONF_PATH]
|
||||
self.name = Path(path).name
|
||||
ftype = file[CONF_TYPE]
|
||||
if ftype == TYPE_LOCAL_BITMAP:
|
||||
self.font = load_bitmap_font(path)
|
||||
else:
|
||||
self.font = load_ttf_font(path, size)
|
||||
self.ascent, self.descent = self.font.getmetrics(codepoints)
|
||||
|
||||
|
||||
def convert_bitmap_to_pillow_font(filepath):
|
||||
from PIL import BdfFontFile, PcfFontFile
|
||||
|
||||
local_bitmap_font_file = external_files.compute_local_file_dir(
|
||||
DOMAIN,
|
||||
) / os.path.basename(filepath)
|
||||
|
||||
copy_file_if_changed(filepath, local_bitmap_font_file)
|
||||
|
||||
local_pil_font_file = local_bitmap_font_file.with_suffix(".pil")
|
||||
with open(local_bitmap_font_file, "rb") as fp:
|
||||
try:
|
||||
try:
|
||||
p = PcfFontFile.PcfFontFile(fp)
|
||||
except SyntaxError:
|
||||
fp.seek(0)
|
||||
p = BdfFontFile.BdfFontFile(fp)
|
||||
|
||||
# Convert to pillow-formatted fonts, which have a .pil and .pbm extension.
|
||||
p.save(local_pil_font_file)
|
||||
except (SyntaxError, OSError) as err:
|
||||
raise core.EsphomeError(
|
||||
f"Failed to parse as bitmap font: '{filepath}': {err}"
|
||||
)
|
||||
|
||||
return str(local_pil_font_file)
|
||||
|
||||
|
||||
def load_bitmap_font(filepath):
|
||||
from PIL import ImageFont
|
||||
|
||||
try:
|
||||
font = ImageFont.load(str(filepath))
|
||||
except Exception as e:
|
||||
raise core.EsphomeError(f"Failed to load bitmap font file: {filepath}: {e}")
|
||||
|
||||
return BitmapFontWrapper(font)
|
||||
|
||||
|
||||
def load_ttf_font(path, size):
|
||||
from PIL import ImageFont
|
||||
|
||||
try:
|
||||
font = ImageFont.truetype(str(path), size)
|
||||
except Exception as e:
|
||||
raise core.EsphomeError(f"Could not load TrueType file {path}: {e}")
|
||||
|
||||
return TrueTypeFontWrapper(font)
|
||||
self.font: Face = FONT_CACHE[file]
|
||||
|
||||
|
||||
class GlyphInfo:
|
||||
def __init__(self, data_len, offset_x, offset_y, width, height):
|
||||
def __init__(self, data_len, advance, offset_x, offset_y, width, height):
|
||||
self.data_len = data_len
|
||||
self.advance = advance
|
||||
self.offset_x = offset_x
|
||||
self.offset_y = offset_y
|
||||
self.width = width
|
||||
@ -537,15 +486,14 @@ async def to_code(config):
|
||||
}
|
||||
# get the codepoints from the glyphs key, flatten to a list of chrs and combine with the points from glyphsets
|
||||
point_set.update(flatten(config[CONF_GLYPHS]))
|
||||
size = config[CONF_SIZE]
|
||||
# Create the codepoint to font file map
|
||||
base_font = EFont(config[CONF_FILE], size, point_set)
|
||||
point_font_map: dict[str, EFont] = {c: base_font for c in point_set}
|
||||
base_font = FONT_CACHE[config[CONF_FILE]]
|
||||
point_font_map: dict[str, Face] = {c: base_font for c in point_set}
|
||||
# process extras, updating the map and extending the codepoint list
|
||||
for extra in config[CONF_EXTRAS]:
|
||||
extra_points = flatten(extra[CONF_GLYPHS])
|
||||
point_set.update(extra_points)
|
||||
extra_font = EFont(extra[CONF_FILE], size, extra_points)
|
||||
extra_font = FONT_CACHE[extra[CONF_FILE]]
|
||||
point_font_map.update({c: extra_font for c in extra_points})
|
||||
|
||||
codepoints = list(point_set)
|
||||
@ -553,28 +501,54 @@ async def to_code(config):
|
||||
glyph_args = {}
|
||||
data = []
|
||||
bpp = config[CONF_BPP]
|
||||
if bpp == 1:
|
||||
mode = "1"
|
||||
scale = 1
|
||||
else:
|
||||
mode = "L"
|
||||
mode = ft_pixel_mode_grays
|
||||
scale = 256 // (1 << bpp)
|
||||
size = config[CONF_SIZE]
|
||||
# create the data array for all glyphs
|
||||
for codepoint in codepoints:
|
||||
font = point_font_map[codepoint]
|
||||
mask = font.font.getmask(codepoint, mode=mode)
|
||||
offset_x, offset_y = font.font.getoffset(codepoint)
|
||||
width, height = mask.size
|
||||
format = font.get_format().decode("utf-8")
|
||||
if format != "PCF":
|
||||
font.set_pixel_sizes(size, 0)
|
||||
font.load_char(codepoint)
|
||||
font.glyph.render(mode)
|
||||
width = font.glyph.bitmap.width
|
||||
height = font.glyph.bitmap.rows
|
||||
buffer = font.glyph.bitmap.buffer
|
||||
pitch = font.glyph.bitmap.pitch
|
||||
glyph_data = [0] * ((height * width * bpp + 7) // 8)
|
||||
src_mode = font.glyph.bitmap.pixel_mode
|
||||
pos = 0
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
pixel = mask.getpixel((x, y)) // scale
|
||||
if src_mode == ft_pixel_mode_mono:
|
||||
pixel = (
|
||||
(1 << bpp) - 1
|
||||
if buffer[y * pitch + x // 8] & (1 << (7 - x % 8))
|
||||
else 0
|
||||
)
|
||||
else:
|
||||
pixel = buffer[y * pitch + x] // scale
|
||||
for bit_num in range(bpp):
|
||||
if pixel & (1 << (bpp - bit_num - 1)):
|
||||
glyph_data[pos // 8] |= 0x80 >> (pos % 8)
|
||||
pos += 1
|
||||
glyph_args[codepoint] = GlyphInfo(len(data), offset_x, offset_y, width, height)
|
||||
ascender = pt_to_px(font.size.ascender)
|
||||
if ascender == 0:
|
||||
if font.has_fixed_sizes:
|
||||
ascender = size
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"Unable to determine ascender of font %s", config[CONF_FILE]
|
||||
)
|
||||
glyph_args[codepoint] = GlyphInfo(
|
||||
len(data),
|
||||
pt_to_px(font.glyph.metrics.horiAdvance),
|
||||
font.glyph.bitmap_left,
|
||||
ascender - font.glyph.bitmap_top,
|
||||
width,
|
||||
height,
|
||||
)
|
||||
data += glyph_data
|
||||
|
||||
rhs = [HexInt(x) for x in data]
|
||||
@ -598,6 +572,7 @@ async def to_code(config):
|
||||
f"{str(prog_arr)} + {str(glyph_args[codepoint].data_len)}"
|
||||
),
|
||||
),
|
||||
("advance", glyph_args[codepoint].advance),
|
||||
("offset_x", glyph_args[codepoint].offset_x),
|
||||
("offset_y", glyph_args[codepoint].offset_y),
|
||||
("width", glyph_args[codepoint].width),
|
||||
@ -607,11 +582,19 @@ async def to_code(config):
|
||||
|
||||
glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer)
|
||||
|
||||
font_height = pt_to_px(base_font.size.height)
|
||||
ascender = pt_to_px(base_font.size.ascender)
|
||||
if font_height == 0:
|
||||
if base_font.has_fixed_sizes:
|
||||
font_height = size
|
||||
ascender = font_height
|
||||
else:
|
||||
_LOGGER.error("Unable to determine height of font %s", config[CONF_FILE])
|
||||
cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
glyphs,
|
||||
len(glyph_initializer),
|
||||
base_font.ascent,
|
||||
base_font.ascent + base_font.descent,
|
||||
ascender,
|
||||
font_height,
|
||||
bpp,
|
||||
)
|
||||
|
@ -81,7 +81,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in
|
||||
if (glyph_n < 0) {
|
||||
// Unknown char, skip
|
||||
if (!this->get_glyphs().empty())
|
||||
x += this->get_glyphs()[0].glyph_data_->width;
|
||||
x += this->get_glyphs()[0].glyph_data_->advance;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
@ -92,7 +92,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in
|
||||
} else {
|
||||
min_x = std::min(min_x, x + glyph.glyph_data_->offset_x);
|
||||
}
|
||||
x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;
|
||||
x += glyph.glyph_data_->advance;
|
||||
|
||||
i += match_length;
|
||||
has_char = true;
|
||||
@ -111,7 +111,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
|
||||
// Unknown char, skip
|
||||
ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]);
|
||||
if (!this->get_glyphs().empty()) {
|
||||
uint8_t glyph_width = this->get_glyphs()[0].glyph_data_->width;
|
||||
uint8_t glyph_width = this->get_glyphs()[0].glyph_data_->advance;
|
||||
display->filled_rectangle(x_at, y_start, glyph_width, this->height_, color);
|
||||
x_at += glyph_width;
|
||||
}
|
||||
@ -161,7 +161,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
|
||||
}
|
||||
}
|
||||
}
|
||||
x_at += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;
|
||||
x_at += glyph.glyph_data_->advance;
|
||||
|
||||
i += match_length;
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ class Font;
|
||||
struct GlyphData {
|
||||
const uint8_t *a_char;
|
||||
const uint8_t *data;
|
||||
int advance;
|
||||
int offset_x;
|
||||
int offset_y;
|
||||
int width;
|
||||
|
@ -132,6 +132,10 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo
|
||||
yrange = ymax - ymin;
|
||||
}
|
||||
|
||||
// Store graph limts
|
||||
this->graph_limit_max_ = ymax;
|
||||
this->graph_limit_min_ = ymin;
|
||||
|
||||
/// Draw grid
|
||||
if (!std::isnan(this->gridspacing_y_)) {
|
||||
for (int y = yn; y <= ym; y++) {
|
||||
|
@ -161,11 +161,15 @@ class Graph : public Component {
|
||||
uint32_t get_duration() { return duration_; }
|
||||
uint32_t get_width() { return width_; }
|
||||
uint32_t get_height() { return height_; }
|
||||
float get_graph_limit_min() { return graph_limit_min_; }
|
||||
float get_graph_limit_max() { return graph_limit_max_; }
|
||||
|
||||
protected:
|
||||
uint32_t duration_; /// in seconds
|
||||
uint32_t width_; /// in pixels
|
||||
uint32_t height_; /// in pixels
|
||||
float graph_limit_min_{NAN};
|
||||
float graph_limit_max_{NAN};
|
||||
float min_value_{NAN};
|
||||
float max_value_{NAN};
|
||||
float min_range_{1.0};
|
||||
|
@ -1,9 +1,15 @@
|
||||
import logging
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
from esphome.components import uart, climate, logger
|
||||
import logging
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import climate, logger, uart
|
||||
from esphome.components.climate import (
|
||||
CONF_CURRENT_TEMPERATURE,
|
||||
ClimateMode,
|
||||
ClimatePreset,
|
||||
ClimateSwingMode,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BEEPER,
|
||||
CONF_DISPLAY,
|
||||
@ -24,12 +30,7 @@ from esphome.const import (
|
||||
CONF_VISUAL,
|
||||
CONF_WIFI,
|
||||
)
|
||||
from esphome.components.climate import (
|
||||
ClimateMode,
|
||||
ClimatePreset,
|
||||
ClimateSwingMode,
|
||||
CONF_CURRENT_TEMPERATURE,
|
||||
)
|
||||
import esphome.final_validate as fv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -12,7 +12,7 @@ float HBridgeSwitch::get_setup_priority() const { return setup_priority::HARDWAR
|
||||
void HBridgeSwitch::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up H-Bridge Switch '%s'...", this->name_.c_str());
|
||||
|
||||
optional<bool> initial_state = this->get_initial_state_with_restore_mode().value_or(false);
|
||||
optional<bool> initial_state = this->get_initial_state_with_restore_mode();
|
||||
|
||||
// Like GPIOSwitch does, set the pin state both before and after pin setup()
|
||||
this->on_pin_->digital_write(false);
|
||||
@ -24,7 +24,7 @@ void HBridgeSwitch::setup() {
|
||||
this->off_pin_->digital_write(false);
|
||||
|
||||
if (initial_state.has_value())
|
||||
this->write_state(initial_state);
|
||||
this->write_state(initial_state.value());
|
||||
}
|
||||
|
||||
void HBridgeSwitch::dump_config() {
|
||||
|
@ -53,6 +53,7 @@ PROTOCOLS = {
|
||||
"mitsubishi_sez": Protocol.PROTOCOL_MITSUBISHI_SEZ,
|
||||
"panasonic_ckp": Protocol.PROTOCOL_PANASONIC_CKP,
|
||||
"panasonic_dke": Protocol.PROTOCOL_PANASONIC_DKE,
|
||||
"panasonic_eke": Protocol.PROTOCOL_PANASONIC_EKE,
|
||||
"panasonic_jke": Protocol.PROTOCOL_PANASONIC_JKE,
|
||||
"panasonic_lke": Protocol.PROTOCOL_PANASONIC_LKE,
|
||||
"panasonic_nke": Protocol.PROTOCOL_PANASONIC_NKE,
|
||||
@ -127,6 +128,6 @@ def to_code(config):
|
||||
cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
|
||||
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
||||
|
||||
cg.add_library("tonia/HeatpumpIR", "1.0.27")
|
||||
cg.add_library("tonia/HeatpumpIR", "1.0.32")
|
||||
if CORE.is_libretiny:
|
||||
CORE.add_platformio_option("lib_ignore", "IRremoteESP8266")
|
||||
|
@ -47,6 +47,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
|
||||
{PROTOCOL_MITSUBISHI_SEZ, []() { return new MitsubishiSEZKDXXHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_PANASONIC_CKP, []() { return new PanasonicCKPHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_PANASONIC_DKE, []() { return new PanasonicDKEHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_PANASONIC_EKE, []() { return new PanasonicEKEHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_PANASONIC_JKE, []() { return new PanasonicJKEHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_PANASONIC_LKE, []() { return new PanasonicLKEHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_PANASONIC_NKE, []() { return new PanasonicNKEHeatpumpIR(); }}, // NOLINT
|
||||
|
@ -47,6 +47,7 @@ enum Protocol {
|
||||
PROTOCOL_MITSUBISHI_SEZ,
|
||||
PROTOCOL_PANASONIC_CKP,
|
||||
PROTOCOL_PANASONIC_DKE,
|
||||
PROTOCOL_PANASONIC_EKE,
|
||||
PROTOCOL_PANASONIC_JKE,
|
||||
PROTOCOL_PANASONIC_LKE,
|
||||
PROTOCOL_PANASONIC_NKE,
|
||||
|
@ -1,23 +1,23 @@
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
from esphome import pins
|
||||
from esphome.const import (
|
||||
CONF_ADDRESS,
|
||||
CONF_FREQUENCY,
|
||||
CONF_TIMEOUT,
|
||||
CONF_I2C_ID,
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_OUTPUT,
|
||||
CONF_SCAN,
|
||||
CONF_SCL,
|
||||
CONF_SDA,
|
||||
CONF_ADDRESS,
|
||||
CONF_I2C_ID,
|
||||
CONF_TIMEOUT,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
)
|
||||
from esphome.core import coroutine_with_priority, CORE
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
import esphome.final_validate as fv
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
i2c_ns = cg.esphome_ns.namespace("i2c")
|
||||
|
@ -8,6 +8,10 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||
#define SOC_HP_I2C_NUM SOC_I2C_NUM
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace i2c {
|
||||
|
||||
@ -17,14 +21,14 @@ void IDFI2CBus::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up I2C bus...");
|
||||
static i2c_port_t next_port = I2C_NUM_0;
|
||||
port_ = next_port;
|
||||
#if SOC_I2C_NUM > 1
|
||||
#if SOC_HP_I2C_NUM > 1
|
||||
next_port = (next_port == I2C_NUM_0) ? I2C_NUM_1 : I2C_NUM_MAX;
|
||||
#else
|
||||
next_port = I2C_NUM_MAX;
|
||||
#endif
|
||||
|
||||
if (port_ == I2C_NUM_MAX) {
|
||||
ESP_LOGE(TAG, "Too many I2C buses configured. Max %u supported.", SOC_I2C_NUM);
|
||||
ESP_LOGE(TAG, "Too many I2C buses configured. Max %u supported.", SOC_HP_I2C_NUM);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
@ -203,7 +203,7 @@ size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length, TickType_t tick
|
||||
this->start();
|
||||
}
|
||||
|
||||
if ((this->state_ != speaker::STATE_RUNNING) || (this->audio_ring_buffer_.use_count() == 1)) {
|
||||
if ((this->state_ != speaker::STATE_RUNNING) || (this->audio_ring_buffer_.use_count() != 1)) {
|
||||
// Unable to write data to a running speaker, so delay the max amount of time so it can get ready
|
||||
vTaskDelay(ticks_to_wait);
|
||||
ticks_to_wait = 0;
|
||||
|
@ -57,6 +57,7 @@ ColorOrder = display.display_ns.enum("ColorMode")
|
||||
|
||||
MODELS = {
|
||||
"GC9A01A": ili9xxx_ns.class_("ILI9XXXGC9A01A", ILI9XXXDisplay),
|
||||
"GC9D01N": ili9xxx_ns.class_("ILI9XXXGC9D01N", ILI9XXXDisplay),
|
||||
"M5STACK": ili9xxx_ns.class_("ILI9XXXM5Stack", ILI9XXXDisplay),
|
||||
"M5CORE": ili9xxx_ns.class_("ILI9XXXM5CORE", ILI9XXXDisplay),
|
||||
"TFT_2.4": ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay),
|
||||
|
@ -272,6 +272,11 @@ class ILI9XXXGC9A01A : public ILI9XXXDisplay {
|
||||
ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240) {}
|
||||
};
|
||||
|
||||
class ILI9XXXGC9D01N : public ILI9XXXDisplay {
|
||||
public:
|
||||
ILI9XXXGC9D01N() : ILI9XXXDisplay(INITCMD_GC9D01N, 160, 160) {}
|
||||
};
|
||||
|
||||
//----------- ILI9XXX_24_TFT display --------------
|
||||
class ILI9XXXST7735 : public ILI9XXXDisplay {
|
||||
public:
|
||||
|
@ -367,6 +367,65 @@ static const uint8_t PROGMEM INITCMD_GC9A01A[] = {
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_GC9D01N[] = {
|
||||
// Enable Inter_command
|
||||
0xFE, 0, // Inter Register Enable 1 (FEh)
|
||||
0xEF, 0, // Inter Register Enable 2 (EFh)
|
||||
// Inter_command is now enabled
|
||||
0x80, 1, 0xFF,
|
||||
0x81, 1, 0xFF,
|
||||
0x82, 1, 0xFF,
|
||||
0x83, 1, 0xFF,
|
||||
0x84, 1, 0xFF,
|
||||
0x85, 1, 0xFF,
|
||||
0x86, 1, 0xFF,
|
||||
0x87, 1, 0xFF,
|
||||
0x88, 1, 0xFF,
|
||||
0x89, 1, 0xFF,
|
||||
0x8A, 1, 0xFF,
|
||||
0x8B, 1, 0xFF,
|
||||
0x8C, 1, 0xFF,
|
||||
0x8D, 1, 0xFF,
|
||||
0x8E, 1, 0xFF,
|
||||
0x8F, 1, 0xFF,
|
||||
0X3A, 1, 0x05, // COLMOD: Pixel Format Set (3Ah) MCU interface, 16 bits / pixel
|
||||
0xEC, 1, 0x01, // Inversion (ECh) DINV=1+2H1V column for Dual Gate (BFh=0)
|
||||
// According to datasheet Inversion (ECh) value 0x01 isn't valid, but Lilygo uses it everywhere
|
||||
0x74, 7, 0x02, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x98, 1, 0x3e,
|
||||
0x99, 1, 0x3e,
|
||||
0xB5, 2, 0x0D, 0x0D, // Blanking Porch Control (B5h) VFP=14 VBP=14 HBP=Off
|
||||
0x60, 4, 0x38, 0x0F, 0x79, 0x67,
|
||||
0x61, 4, 0x38, 0x11, 0x79, 0x67,
|
||||
0x64, 6, 0x38, 0x17, 0x71, 0x5F, 0x79, 0x67,
|
||||
0x65, 6, 0x38, 0x13, 0x71, 0x5B, 0x79, 0x67,
|
||||
0x6A, 2, 0x00, 0x00,
|
||||
0x6C, 7, 0x22, 0x02, 0x22, 0x02, 0x22, 0x22, 0x50,
|
||||
0x6E, 32, 0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x0F, 0x0F,
|
||||
0x0D, 0x0D, 0x0B, 0x0B, 0x09, 0x09, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0A, 0x0A, 0x0C, 0x0C, 0x0E, 0x0E,
|
||||
0x10, 0x10, 0x00, 0x00, 0x02, 0x02, 0x04, 0x04,
|
||||
0xBF, 1, 0x01, // Dual-Single gate select (BFh) 01h = dual gate mode
|
||||
0xF9, 1, 0x40,
|
||||
0x9B, 5, 0x3B, 0x93, 0x33, 0x7F, 0x00,
|
||||
0x7E, 1, 0x30,
|
||||
0x70, 6, 0x0D, 0x02, 0x08, 0x0D, 0x02, 0x08,
|
||||
0x71, 3, 0x0D, 0x02, 0x08,
|
||||
0x91, 2, 0x0E, 0x09,
|
||||
// Set VREG1A, VREG1B, VREG2A, VREG2B voltage
|
||||
// According to datasheet set either 0xC3/0xC4 or 0xC9 only, but Lilygo sets both of them
|
||||
0xC3, 5, 0x19, 0xC4, 0x19, 0xC9, 0x3C,
|
||||
0xF0, 6, 0x53, 0x15, 0x0A, 0x04, 0x00, 0x3E, // SET_GAMMA1 (F0h)
|
||||
0xF1, 6, 0x56, 0xA8, 0x7F, 0x33, 0x34, 0x5F, // SET_GAMMA2 (F1h)
|
||||
0xF2, 6, 0x53, 0x15, 0x0A, 0x04, 0x00, 0x3A, // SET_GAMMA3 (F2h)
|
||||
0xF3, 6, 0x52, 0xA4, 0x7F, 0x33, 0x34, 0xDF, // SET_GAMMA4 (F3h)
|
||||
ILI9XXX_SLPOUT, 0, // Sleep Out Mode (11h)
|
||||
ILI9XXX_DELAY(10),
|
||||
ILI9XXX_DISPON, 0, // Display ON (29h)
|
||||
ILI9XXX_DELAY(20),
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_ST7735[] = {
|
||||
ILI9XXX_SWRESET, 0, // Soft reset, then delay 10ms
|
||||
ILI9XXX_DELAY(10),
|
||||
|
51
esphome/components/ld2450/__init__.py
Normal file
51
esphome/components/ld2450/__init__.py
Normal file
@ -0,0 +1,51 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_THROTTLE,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
CODEOWNERS = ["@hareeshmu"]
|
||||
MULTI_CONF = True
|
||||
|
||||
ld2450_ns = cg.esphome_ns.namespace("ld2450")
|
||||
LD2450Component = ld2450_ns.class_("LD2450Component", cg.Component, uart.UARTDevice)
|
||||
|
||||
CONF_LD2450_ID = "ld2450_id"
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LD2450Component),
|
||||
cv.Optional(CONF_THROTTLE, default="1000ms"): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(min=cv.TimePeriod(milliseconds=1)),
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
LD2450BaseSchema = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
},
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"ld2450",
|
||||
require_tx=True,
|
||||
require_rx=True,
|
||||
parity="NONE",
|
||||
stop_bits=1,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
cg.add(var.set_throttle(config[CONF_THROTTLE]))
|
47
esphome/components/ld2450/binary_sensor.py
Normal file
47
esphome/components/ld2450/binary_sensor.py
Normal file
@ -0,0 +1,47 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_HAS_MOVING_TARGET,
|
||||
CONF_HAS_STILL_TARGET,
|
||||
CONF_HAS_TARGET,
|
||||
DEVICE_CLASS_MOTION,
|
||||
DEVICE_CLASS_OCCUPANCY,
|
||||
)
|
||||
|
||||
from . import CONF_LD2450_ID, LD2450Component
|
||||
|
||||
DEPENDENCIES = ["ld2450"]
|
||||
|
||||
ICON_MEDITATION = "mdi:meditation"
|
||||
ICON_SHIELD_ACCOUNT = "mdi:shield-account"
|
||||
ICON_TARGET_ACCOUNT = "mdi:target-account"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_OCCUPANCY,
|
||||
icon=ICON_SHIELD_ACCOUNT,
|
||||
),
|
||||
cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_MOTION,
|
||||
icon=ICON_TARGET_ACCOUNT,
|
||||
),
|
||||
cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_OCCUPANCY,
|
||||
icon=ICON_MEDITATION,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
if has_target_config := config.get(CONF_HAS_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_target_config)
|
||||
cg.add(ld2450_component.set_target_binary_sensor(sens))
|
||||
if has_moving_target_config := config.get(CONF_HAS_MOVING_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_moving_target_config)
|
||||
cg.add(ld2450_component.set_moving_target_binary_sensor(sens))
|
||||
if has_still_target_config := config.get(CONF_HAS_STILL_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_still_target_config)
|
||||
cg.add(ld2450_component.set_still_target_binary_sensor(sens))
|
45
esphome/components/ld2450/button/__init__.py
Normal file
45
esphome/components/ld2450/button/__init__.py
Normal file
@ -0,0 +1,45 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import button
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_FACTORY_RESET,
|
||||
CONF_RESTART,
|
||||
DEVICE_CLASS_RESTART,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_RESTART,
|
||||
ICON_RESTART_ALERT,
|
||||
)
|
||||
|
||||
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
|
||||
|
||||
ResetButton = ld2450_ns.class_("ResetButton", button.Button)
|
||||
RestartButton = ld2450_ns.class_("RestartButton", button.Button)
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
||||
ResetButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART_ALERT,
|
||||
),
|
||||
cv.Optional(CONF_RESTART): button.button_schema(
|
||||
RestartButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_RESTART,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
if factory_reset_config := config.get(CONF_FACTORY_RESET):
|
||||
b = await button.new_button(factory_reset_config)
|
||||
await cg.register_parented(b, config[CONF_LD2450_ID])
|
||||
cg.add(ld2450_component.set_reset_button(b))
|
||||
if restart_config := config.get(CONF_RESTART):
|
||||
b = await button.new_button(restart_config)
|
||||
await cg.register_parented(b, config[CONF_LD2450_ID])
|
||||
cg.add(ld2450_component.set_restart_button(b))
|
9
esphome/components/ld2450/button/reset_button.cpp
Normal file
9
esphome/components/ld2450/button/reset_button.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
#include "reset_button.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
void ResetButton::press_action() { this->parent_->factory_reset(); }
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
18
esphome/components/ld2450/button/reset_button.h
Normal file
18
esphome/components/ld2450/button/reset_button.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2450.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
class ResetButton : public button::Button, public Parented<LD2450Component> {
|
||||
public:
|
||||
ResetButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
9
esphome/components/ld2450/button/restart_button.cpp
Normal file
9
esphome/components/ld2450/button/restart_button.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
#include "restart_button.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); }
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
18
esphome/components/ld2450/button/restart_button.h
Normal file
18
esphome/components/ld2450/button/restart_button.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2450.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
class RestartButton : public button::Button, public Parented<LD2450Component> {
|
||||
public:
|
||||
RestartButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
876
esphome/components/ld2450/ld2450.cpp
Normal file
876
esphome/components/ld2450/ld2450.cpp
Normal file
@ -0,0 +1,876 @@
|
||||
#include "ld2450.h"
|
||||
#include <utility>
|
||||
#ifdef USE_NUMBER
|
||||
#include "esphome/components/number/number.h"
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#define highbyte(val) (uint8_t)((val) >> 8)
|
||||
#define lowbyte(val) (uint8_t)((val) &0xff)
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
static const char *const TAG = "ld2450";
|
||||
static const char *const UNKNOWN_MAC("unknown");
|
||||
|
||||
// LD2450 UART Serial Commands
|
||||
static const uint8_t CMD_ENABLE_CONF = 0x00FF;
|
||||
static const uint8_t CMD_DISABLE_CONF = 0x00FE;
|
||||
static const uint8_t CMD_VERSION = 0x00A0;
|
||||
static const uint8_t CMD_MAC = 0x00A5;
|
||||
static const uint8_t CMD_RESET = 0x00A2;
|
||||
static const uint8_t CMD_RESTART = 0x00A3;
|
||||
static const uint8_t CMD_BLUETOOTH = 0x00A4;
|
||||
static const uint8_t CMD_SINGLE_TARGET_MODE = 0x0080;
|
||||
static const uint8_t CMD_MULTI_TARGET_MODE = 0x0090;
|
||||
static const uint8_t CMD_QUERY_TARGET_MODE = 0x0091;
|
||||
static const uint8_t CMD_SET_BAUD_RATE = 0x00A1;
|
||||
static const uint8_t CMD_QUERY_ZONE = 0x00C1;
|
||||
static const uint8_t CMD_SET_ZONE = 0x00C2;
|
||||
|
||||
static inline uint16_t convert_seconds_to_ms(uint16_t value) { return value * 1000; };
|
||||
|
||||
static inline std::string convert_signed_int_to_hex(int value) {
|
||||
auto value_as_str = str_snprintf("%04x", 4, value & 0xFFFF);
|
||||
return value_as_str;
|
||||
}
|
||||
|
||||
static inline void convert_int_values_to_hex(const int *values, uint8_t *bytes) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
std::string temp_hex = convert_signed_int_to_hex(values[i]);
|
||||
bytes[i * 2] = std::stoi(temp_hex.substr(2, 2), nullptr, 16); // Store high byte
|
||||
bytes[i * 2 + 1] = std::stoi(temp_hex.substr(0, 2), nullptr, 16); // Store low byte
|
||||
}
|
||||
}
|
||||
|
||||
static inline int16_t decode_coordinate(uint8_t low_byte, uint8_t high_byte) {
|
||||
int16_t coordinate = (high_byte & 0x7F) << 8 | low_byte;
|
||||
if ((high_byte & 0x80) == 0) {
|
||||
coordinate = -coordinate;
|
||||
}
|
||||
return coordinate; // mm
|
||||
}
|
||||
|
||||
static inline int16_t decode_speed(uint8_t low_byte, uint8_t high_byte) {
|
||||
int16_t speed = (high_byte & 0x7F) << 8 | low_byte;
|
||||
if ((high_byte & 0x80) == 0) {
|
||||
speed = -speed;
|
||||
}
|
||||
return speed * 10; // mm/s
|
||||
}
|
||||
|
||||
static inline int16_t hex_to_signed_int(const uint8_t *buffer, uint8_t offset) {
|
||||
uint16_t hex_val = (buffer[offset + 1] << 8) | buffer[offset];
|
||||
int16_t dec_val = static_cast<int16_t>(hex_val);
|
||||
if (dec_val & 0x8000) {
|
||||
dec_val -= 65536;
|
||||
}
|
||||
return dec_val;
|
||||
}
|
||||
|
||||
static inline float calculate_angle(float base, float hypotenuse) {
|
||||
if (base < 0.0 || hypotenuse <= 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
float angle_radians = std::acos(base / hypotenuse);
|
||||
float angle_degrees = angle_radians * (180.0 / M_PI);
|
||||
return angle_degrees;
|
||||
}
|
||||
|
||||
static inline std::string get_direction(int16_t speed) {
|
||||
static const char *const APPROACHING = "Approaching";
|
||||
static const char *const MOVING_AWAY = "Moving away";
|
||||
static const char *const STATIONARY = "Stationary";
|
||||
|
||||
if (speed > 0) {
|
||||
return MOVING_AWAY;
|
||||
}
|
||||
if (speed < 0) {
|
||||
return APPROACHING;
|
||||
}
|
||||
return STATIONARY;
|
||||
}
|
||||
|
||||
static inline std::string format_mac(uint8_t *buffer) {
|
||||
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14],
|
||||
buffer[15]);
|
||||
}
|
||||
|
||||
static inline std::string format_version(uint8_t *buffer) {
|
||||
return str_sprintf("%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15],
|
||||
buffer[14]);
|
||||
}
|
||||
|
||||
LD2450Component::LD2450Component() {}
|
||||
|
||||
void LD2450Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up HLK-LD2450...");
|
||||
#ifdef USE_NUMBER
|
||||
if (this->presence_timeout_number_ != nullptr) {
|
||||
this->pref_ = global_preferences->make_preference<float>(this->presence_timeout_number_->get_object_id_hash());
|
||||
this->set_presence_timeout();
|
||||
}
|
||||
#endif
|
||||
this->restart_and_read_all_info();
|
||||
}
|
||||
|
||||
void LD2450Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "HLK-LD2450 Human motion tracking radar module:");
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
LOG_BINARY_SENSOR(" ", "TargetBinarySensor", this->target_binary_sensor_);
|
||||
LOG_BINARY_SENSOR(" ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_);
|
||||
LOG_BINARY_SENSOR(" ", "StillTargetBinarySensor", this->still_target_binary_sensor_);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
LOG_SWITCH(" ", "BluetoothSwitch", this->bluetooth_switch_);
|
||||
LOG_SWITCH(" ", "MultiTargetSwitch", this->multi_target_switch_);
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
LOG_BUTTON(" ", "ResetButton", this->reset_button_);
|
||||
LOG_BUTTON(" ", "RestartButton", this->restart_button_);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "TargetCountSensor", this->target_count_sensor_);
|
||||
LOG_SENSOR(" ", "StillTargetCountSensor", this->still_target_count_sensor_);
|
||||
LOG_SENSOR(" ", "MovingTargetCountSensor", this->moving_target_count_sensor_);
|
||||
for (sensor::Sensor *s : this->move_x_sensors_) {
|
||||
LOG_SENSOR(" ", "NthTargetXSensor", s);
|
||||
}
|
||||
for (sensor::Sensor *s : this->move_y_sensors_) {
|
||||
LOG_SENSOR(" ", "NthTargetYSensor", s);
|
||||
}
|
||||
for (sensor::Sensor *s : this->move_speed_sensors_) {
|
||||
LOG_SENSOR(" ", "NthTargetSpeedSensor", s);
|
||||
}
|
||||
for (sensor::Sensor *s : this->move_angle_sensors_) {
|
||||
LOG_SENSOR(" ", "NthTargetAngleSensor", s);
|
||||
}
|
||||
for (sensor::Sensor *s : this->move_distance_sensors_) {
|
||||
LOG_SENSOR(" ", "NthTargetDistanceSensor", s);
|
||||
}
|
||||
for (sensor::Sensor *s : this->move_resolution_sensors_) {
|
||||
LOG_SENSOR(" ", "NthTargetResolutionSensor", s);
|
||||
}
|
||||
for (sensor::Sensor *s : this->zone_target_count_sensors_) {
|
||||
LOG_SENSOR(" ", "NthZoneTargetCountSensor", s);
|
||||
}
|
||||
for (sensor::Sensor *s : this->zone_still_target_count_sensors_) {
|
||||
LOG_SENSOR(" ", "NthZoneStillTargetCountSensor", s);
|
||||
}
|
||||
for (sensor::Sensor *s : this->zone_moving_target_count_sensors_) {
|
||||
LOG_SENSOR(" ", "NthZoneMovingTargetCountSensor", s);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
LOG_TEXT_SENSOR(" ", "VersionTextSensor", this->version_text_sensor_);
|
||||
LOG_TEXT_SENSOR(" ", "MacTextSensor", this->mac_text_sensor_);
|
||||
for (text_sensor::TextSensor *s : this->direction_text_sensors_) {
|
||||
LOG_TEXT_SENSOR(" ", "NthDirectionTextSensor", s);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
for (auto n : this->zone_numbers_) {
|
||||
LOG_NUMBER(" ", "ZoneX1Number", n.x1);
|
||||
LOG_NUMBER(" ", "ZoneY1Number", n.y1);
|
||||
LOG_NUMBER(" ", "ZoneX2Number", n.x2);
|
||||
LOG_NUMBER(" ", "ZoneY2Number", n.y2);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
LOG_SELECT(" ", "BaudRateSelect", this->baud_rate_select_);
|
||||
LOG_SELECT(" ", "ZoneTypeSelect", this->zone_type_select_);
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
LOG_NUMBER(" ", "PresenceTimeoutNumber", this->presence_timeout_number_);
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, " Throttle : %ums", this->throttle_);
|
||||
ESP_LOGCONFIG(TAG, " MAC Address : %s", const_cast<char *>(this->mac_.c_str()));
|
||||
ESP_LOGCONFIG(TAG, " Firmware version : %s", const_cast<char *>(this->version_.c_str()));
|
||||
}
|
||||
|
||||
void LD2450Component::loop() {
|
||||
while (this->available()) {
|
||||
this->readline_(read(), this->buffer_data_, MAX_LINE_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
// Count targets in zone
|
||||
uint8_t LD2450Component::count_targets_in_zone_(const Zone &zone, bool is_moving) {
|
||||
uint8_t count = 0;
|
||||
for (auto &index : this->target_info_) {
|
||||
if (index.x > zone.x1 && index.x < zone.x2 && index.y > zone.y1 && index.y < zone.y2 &&
|
||||
index.is_moving == is_moving) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Service reset_radar_zone
|
||||
void LD2450Component::reset_radar_zone() {
|
||||
this->zone_type_ = 0;
|
||||
for (auto &i : this->zone_config_) {
|
||||
i.x1 = 0;
|
||||
i.y1 = 0;
|
||||
i.x2 = 0;
|
||||
i.y2 = 0;
|
||||
}
|
||||
this->send_set_zone_command_();
|
||||
}
|
||||
|
||||
void LD2450Component::set_radar_zone(int32_t zone_type, int32_t zone1_x1, int32_t zone1_y1, int32_t zone1_x2,
|
||||
int32_t zone1_y2, int32_t zone2_x1, int32_t zone2_y1, int32_t zone2_x2,
|
||||
int32_t zone2_y2, int32_t zone3_x1, int32_t zone3_y1, int32_t zone3_x2,
|
||||
int32_t zone3_y2) {
|
||||
this->zone_type_ = zone_type;
|
||||
int zone_parameters[12] = {zone1_x1, zone1_y1, zone1_x2, zone1_y2, zone2_x1, zone2_y1,
|
||||
zone2_x2, zone2_y2, zone3_x1, zone3_y1, zone3_x2, zone3_y2};
|
||||
for (int i = 0; i < MAX_ZONES; i++) {
|
||||
this->zone_config_[i].x1 = zone_parameters[i * 4];
|
||||
this->zone_config_[i].y1 = zone_parameters[i * 4 + 1];
|
||||
this->zone_config_[i].x2 = zone_parameters[i * 4 + 2];
|
||||
this->zone_config_[i].y2 = zone_parameters[i * 4 + 3];
|
||||
}
|
||||
this->send_set_zone_command_();
|
||||
}
|
||||
|
||||
// Set Zone on LD2450 Sensor
|
||||
void LD2450Component::send_set_zone_command_() {
|
||||
uint8_t cmd_value[26] = {};
|
||||
uint8_t zone_type_bytes[2] = {static_cast<uint8_t>(this->zone_type_), 0x00};
|
||||
uint8_t area_config[24] = {};
|
||||
for (int i = 0; i < MAX_ZONES; i++) {
|
||||
int values[4] = {this->zone_config_[i].x1, this->zone_config_[i].y1, this->zone_config_[i].x2,
|
||||
this->zone_config_[i].y2};
|
||||
ld2450::convert_int_values_to_hex(values, area_config + (i * 8));
|
||||
}
|
||||
std::memcpy(cmd_value, zone_type_bytes, 2);
|
||||
std::memcpy(cmd_value + 2, area_config, 24);
|
||||
this->set_config_mode_(true);
|
||||
this->send_command_(CMD_SET_ZONE, cmd_value, 26);
|
||||
this->set_config_mode_(false);
|
||||
}
|
||||
|
||||
// Check presense timeout to reset presence status
|
||||
bool LD2450Component::get_timeout_status_(uint32_t check_millis) {
|
||||
if (check_millis == 0) {
|
||||
return true;
|
||||
}
|
||||
if (this->timeout_ == 0) {
|
||||
this->timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT);
|
||||
}
|
||||
auto current_millis = millis();
|
||||
return current_millis - check_millis >= this->timeout_;
|
||||
}
|
||||
|
||||
// Extract, store and publish zone details LD2450 buffer
|
||||
void LD2450Component::process_zone_(uint8_t *buffer) {
|
||||
uint8_t index, start;
|
||||
for (index = 0; index < MAX_ZONES; index++) {
|
||||
start = 12 + index * 8;
|
||||
this->zone_config_[index].x1 = ld2450::hex_to_signed_int(buffer, start);
|
||||
this->zone_config_[index].y1 = ld2450::hex_to_signed_int(buffer, start + 2);
|
||||
this->zone_config_[index].x2 = ld2450::hex_to_signed_int(buffer, start + 4);
|
||||
this->zone_config_[index].y2 = ld2450::hex_to_signed_int(buffer, start + 6);
|
||||
#ifdef USE_NUMBER
|
||||
// only one null check as all coordinates are required for a single zone
|
||||
if (this->zone_numbers_[index].x1 != nullptr) {
|
||||
this->zone_numbers_[index].x1->publish_state(this->zone_config_[index].x1);
|
||||
this->zone_numbers_[index].y1->publish_state(this->zone_config_[index].y1);
|
||||
this->zone_numbers_[index].x2->publish_state(this->zone_config_[index].x2);
|
||||
this->zone_numbers_[index].y2->publish_state(this->zone_config_[index].y2);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Read all info from LD2450 buffer
|
||||
void LD2450Component::read_all_info() {
|
||||
this->set_config_mode_(true);
|
||||
this->get_version_();
|
||||
this->get_mac_();
|
||||
this->query_target_tracking_mode_();
|
||||
this->query_zone_();
|
||||
this->set_config_mode_(false);
|
||||
#ifdef USE_SELECT
|
||||
const auto baud_rate = std::to_string(this->parent_->get_baud_rate());
|
||||
if (this->baud_rate_select_ != nullptr && this->baud_rate_select_->state != baud_rate) {
|
||||
this->baud_rate_select_->publish_state(baud_rate);
|
||||
}
|
||||
this->publish_zone_type();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Read zone info from LD2450 buffer
|
||||
void LD2450Component::query_zone_info() {
|
||||
this->set_config_mode_(true);
|
||||
this->query_zone_();
|
||||
this->set_config_mode_(false);
|
||||
}
|
||||
|
||||
// Restart LD2450 and read all info from buffer
|
||||
void LD2450Component::restart_and_read_all_info() {
|
||||
this->set_config_mode_(true);
|
||||
this->restart_();
|
||||
this->set_timeout(1500, [this]() { this->read_all_info(); });
|
||||
}
|
||||
|
||||
// Send command with values to LD2450
|
||||
void LD2450Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) {
|
||||
ESP_LOGV(TAG, "Sending command %02X", command);
|
||||
// frame header
|
||||
this->write_array(CMD_FRAME_HEADER, 4);
|
||||
// length bytes
|
||||
int len = 2;
|
||||
if (command_value != nullptr) {
|
||||
len += command_value_len;
|
||||
}
|
||||
this->write_byte(lowbyte(len));
|
||||
this->write_byte(highbyte(len));
|
||||
// command
|
||||
this->write_byte(lowbyte(command));
|
||||
this->write_byte(highbyte(command));
|
||||
// command value bytes
|
||||
if (command_value != nullptr) {
|
||||
for (int i = 0; i < command_value_len; i++) {
|
||||
this->write_byte(command_value[i]);
|
||||
}
|
||||
}
|
||||
// footer
|
||||
this->write_array(CMD_FRAME_END, 4);
|
||||
// FIXME to remove
|
||||
delay(50); // NOLINT
|
||||
}
|
||||
|
||||
// LD2450 Radar data message:
|
||||
// [AA FF 03 00] [0E 03 B1 86 10 00 40 01] [00 00 00 00 00 00 00 00] [00 00 00 00 00 00 00 00] [55 CC]
|
||||
// Header Target 1 Target 2 Target 3 End
|
||||
void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
|
||||
if (len < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes)
|
||||
ESP_LOGE(TAG, "Periodic data: invalid message length");
|
||||
return;
|
||||
}
|
||||
if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) { // header
|
||||
ESP_LOGE(TAG, "Periodic data: invalid message header");
|
||||
return;
|
||||
}
|
||||
if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) { // footer
|
||||
ESP_LOGE(TAG, "Periodic data: invalid message footer");
|
||||
return;
|
||||
}
|
||||
|
||||
auto current_millis = millis();
|
||||
if (current_millis - this->last_periodic_millis_ < this->throttle_) {
|
||||
ESP_LOGV(TAG, "Throttling: %d", this->throttle_);
|
||||
return;
|
||||
}
|
||||
|
||||
this->last_periodic_millis_ = current_millis;
|
||||
|
||||
int16_t target_count = 0;
|
||||
int16_t still_target_count = 0;
|
||||
int16_t moving_target_count = 0;
|
||||
int16_t start = 0;
|
||||
int16_t val = 0;
|
||||
uint8_t index = 0;
|
||||
int16_t tx = 0;
|
||||
int16_t ty = 0;
|
||||
int16_t td = 0;
|
||||
int16_t ts = 0;
|
||||
int16_t angle = 0;
|
||||
std::string direction{};
|
||||
bool is_moving = false;
|
||||
|
||||
#if defined(USE_BINARY_SENSOR) || defined(USE_SENSOR) || defined(USE_TEXT_SENSOR)
|
||||
// Loop thru targets
|
||||
for (index = 0; index < MAX_TARGETS; index++) {
|
||||
#ifdef USE_SENSOR
|
||||
// X
|
||||
start = TARGET_X + index * 8;
|
||||
is_moving = false;
|
||||
sensor::Sensor *sx = this->move_x_sensors_[index];
|
||||
if (sx != nullptr) {
|
||||
val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]);
|
||||
tx = val;
|
||||
sx->publish_state(val);
|
||||
}
|
||||
// Y
|
||||
start = TARGET_Y + index * 8;
|
||||
sensor::Sensor *sy = this->move_y_sensors_[index];
|
||||
if (sy != nullptr) {
|
||||
val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]);
|
||||
ty = val;
|
||||
sy->publish_state(val);
|
||||
}
|
||||
// RESOLUTION
|
||||
start = TARGET_RESOLUTION + index * 8;
|
||||
sensor::Sensor *sr = this->move_resolution_sensors_[index];
|
||||
if (sr != nullptr) {
|
||||
val = (buffer[start + 1] << 8) | buffer[start];
|
||||
sr->publish_state(val);
|
||||
}
|
||||
#endif
|
||||
// SPEED
|
||||
start = TARGET_SPEED + index * 8;
|
||||
val = ld2450::decode_speed(buffer[start], buffer[start + 1]);
|
||||
ts = val;
|
||||
if (val) {
|
||||
is_moving = true;
|
||||
moving_target_count++;
|
||||
}
|
||||
#ifdef USE_SENSOR
|
||||
sensor::Sensor *ss = this->move_speed_sensors_[index];
|
||||
if (ss != nullptr) {
|
||||
ss->publish_state(val);
|
||||
}
|
||||
#endif
|
||||
// DISTANCE
|
||||
val = (uint16_t) sqrt(
|
||||
pow(ld2450::decode_coordinate(buffer[TARGET_X + index * 8], buffer[(TARGET_X + index * 8) + 1]), 2) +
|
||||
pow(ld2450::decode_coordinate(buffer[TARGET_Y + index * 8], buffer[(TARGET_Y + index * 8) + 1]), 2));
|
||||
td = val;
|
||||
if (val > 0) {
|
||||
target_count++;
|
||||
}
|
||||
#ifdef USE_SENSOR
|
||||
sensor::Sensor *sd = this->move_distance_sensors_[index];
|
||||
if (sd != nullptr) {
|
||||
sd->publish_state(val);
|
||||
}
|
||||
// ANGLE
|
||||
angle = calculate_angle(static_cast<float>(ty), static_cast<float>(td));
|
||||
if (tx > 0) {
|
||||
angle = angle * -1;
|
||||
}
|
||||
sensor::Sensor *sa = this->move_angle_sensors_[index];
|
||||
if (sa != nullptr) {
|
||||
sa->publish_state(angle);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
// DIRECTION
|
||||
direction = get_direction(ts);
|
||||
if (td == 0) {
|
||||
direction = "NA";
|
||||
}
|
||||
text_sensor::TextSensor *tsd = this->direction_text_sensors_[index];
|
||||
if (tsd != nullptr) {
|
||||
tsd->publish_state(direction);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Store target info for zone target count
|
||||
this->target_info_[index].x = tx;
|
||||
this->target_info_[index].y = ty;
|
||||
this->target_info_[index].is_moving = is_moving;
|
||||
|
||||
} // End loop thru targets
|
||||
|
||||
still_target_count = target_count - moving_target_count;
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
// Loop thru zones
|
||||
uint8_t zone_still_targets = 0;
|
||||
uint8_t zone_moving_targets = 0;
|
||||
uint8_t zone_all_targets = 0;
|
||||
for (index = 0; index < MAX_ZONES; index++) {
|
||||
zone_still_targets = this->count_targets_in_zone_(this->zone_config_[index], false);
|
||||
zone_moving_targets = this->count_targets_in_zone_(this->zone_config_[index], true);
|
||||
zone_all_targets = zone_still_targets + zone_moving_targets;
|
||||
|
||||
// Publish Still Target Count in Zones
|
||||
sensor::Sensor *szstc = this->zone_still_target_count_sensors_[index];
|
||||
if (szstc != nullptr) {
|
||||
szstc->publish_state(zone_still_targets);
|
||||
}
|
||||
// Publish Moving Target Count in Zones
|
||||
sensor::Sensor *szmtc = this->zone_moving_target_count_sensors_[index];
|
||||
if (szmtc != nullptr) {
|
||||
szmtc->publish_state(zone_moving_targets);
|
||||
}
|
||||
// Publish All Target Count in Zones
|
||||
sensor::Sensor *sztc = this->zone_target_count_sensors_[index];
|
||||
if (sztc != nullptr) {
|
||||
sztc->publish_state(zone_all_targets);
|
||||
}
|
||||
|
||||
} // End loop thru zones
|
||||
|
||||
// Target Count
|
||||
if (this->target_count_sensor_ != nullptr) {
|
||||
this->target_count_sensor_->publish_state(target_count);
|
||||
}
|
||||
// Still Target Count
|
||||
if (this->still_target_count_sensor_ != nullptr) {
|
||||
this->still_target_count_sensor_->publish_state(still_target_count);
|
||||
}
|
||||
// Moving Target Count
|
||||
if (this->moving_target_count_sensor_ != nullptr) {
|
||||
this->moving_target_count_sensor_->publish_state(moving_target_count);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
// Target Presence
|
||||
if (this->target_binary_sensor_ != nullptr) {
|
||||
if (target_count > 0) {
|
||||
this->target_binary_sensor_->publish_state(true);
|
||||
} else {
|
||||
if (this->get_timeout_status_(this->presence_millis_)) {
|
||||
this->target_binary_sensor_->publish_state(false);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Clear presence waiting timeout: %d", this->timeout_);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Moving Target Presence
|
||||
if (this->moving_target_binary_sensor_ != nullptr) {
|
||||
if (moving_target_count > 0) {
|
||||
this->moving_target_binary_sensor_->publish_state(true);
|
||||
} else {
|
||||
if (this->get_timeout_status_(this->moving_presence_millis_)) {
|
||||
this->moving_target_binary_sensor_->publish_state(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Still Target Presence
|
||||
if (this->still_target_binary_sensor_ != nullptr) {
|
||||
if (still_target_count > 0) {
|
||||
this->still_target_binary_sensor_->publish_state(true);
|
||||
} else {
|
||||
if (this->get_timeout_status_(this->still_presence_millis_)) {
|
||||
this->still_target_binary_sensor_->publish_state(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
// For presence timeout check
|
||||
if (target_count > 0) {
|
||||
this->presence_millis_ = millis();
|
||||
}
|
||||
if (moving_target_count > 0) {
|
||||
this->moving_presence_millis_ = millis();
|
||||
}
|
||||
if (still_target_count > 0) {
|
||||
this->still_presence_millis_ = millis();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
||||
ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]);
|
||||
if (len < 10) {
|
||||
ESP_LOGE(TAG, "Ack data: invalid length");
|
||||
return true;
|
||||
}
|
||||
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // frame header
|
||||
ESP_LOGE(TAG, "Ack data: invalid header (command %02X)", buffer[COMMAND]);
|
||||
return true;
|
||||
}
|
||||
if (buffer[COMMAND_STATUS] != 0x01) {
|
||||
ESP_LOGE(TAG, "Ack data: invalid status");
|
||||
return true;
|
||||
}
|
||||
if (buffer[8] || buffer[9]) {
|
||||
ESP_LOGE(TAG, "Ack data: last buffer was %u, %u", buffer[8], buffer[9]);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (buffer[COMMAND]) {
|
||||
case lowbyte(CMD_ENABLE_CONF):
|
||||
ESP_LOGV(TAG, "Got enable conf command");
|
||||
break;
|
||||
case lowbyte(CMD_DISABLE_CONF):
|
||||
ESP_LOGV(TAG, "Got disable conf command");
|
||||
break;
|
||||
case lowbyte(CMD_SET_BAUD_RATE):
|
||||
ESP_LOGV(TAG, "Got baud rate change command");
|
||||
#ifdef USE_SELECT
|
||||
if (this->baud_rate_select_ != nullptr) {
|
||||
ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str());
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case lowbyte(CMD_VERSION):
|
||||
this->version_ = ld2450::format_version(buffer);
|
||||
ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str());
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->version_text_sensor_ != nullptr) {
|
||||
this->version_text_sensor_->publish_state(this->version_);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case lowbyte(CMD_MAC):
|
||||
if (len < 20) {
|
||||
return false;
|
||||
}
|
||||
this->mac_ = ld2450::format_mac(buffer);
|
||||
ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->mac_text_sensor_ != nullptr) {
|
||||
this->mac_text_sensor_->publish_state(this->mac_);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
if (this->bluetooth_switch_ != nullptr) {
|
||||
this->bluetooth_switch_->publish_state(this->mac_ != UNKNOWN_MAC);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case lowbyte(CMD_BLUETOOTH):
|
||||
ESP_LOGV(TAG, "Got Bluetooth command");
|
||||
break;
|
||||
case lowbyte(CMD_SINGLE_TARGET_MODE):
|
||||
ESP_LOGV(TAG, "Got single target conf command");
|
||||
#ifdef USE_SWITCH
|
||||
if (this->multi_target_switch_ != nullptr) {
|
||||
this->multi_target_switch_->publish_state(false);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case lowbyte(CMD_MULTI_TARGET_MODE):
|
||||
ESP_LOGV(TAG, "Got multi target conf command");
|
||||
#ifdef USE_SWITCH
|
||||
if (this->multi_target_switch_ != nullptr) {
|
||||
this->multi_target_switch_->publish_state(true);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case lowbyte(CMD_QUERY_TARGET_MODE):
|
||||
ESP_LOGV(TAG, "Got query target tracking mode command");
|
||||
#ifdef USE_SWITCH
|
||||
if (this->multi_target_switch_ != nullptr) {
|
||||
this->multi_target_switch_->publish_state(buffer[10] == 0x02);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case lowbyte(CMD_QUERY_ZONE):
|
||||
ESP_LOGV(TAG, "Got query zone conf command");
|
||||
this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16);
|
||||
this->publish_zone_type();
|
||||
#ifdef USE_SELECT
|
||||
if (this->zone_type_select_ != nullptr) {
|
||||
ESP_LOGV(TAG, "Change zone type to: %s", this->zone_type_select_->state.c_str());
|
||||
}
|
||||
#endif
|
||||
if (buffer[10] == 0x00) {
|
||||
ESP_LOGV(TAG, "Zone: Disabled");
|
||||
}
|
||||
if (buffer[10] == 0x01) {
|
||||
ESP_LOGV(TAG, "Zone: Area detection");
|
||||
}
|
||||
if (buffer[10] == 0x02) {
|
||||
ESP_LOGV(TAG, "Zone: Area filter");
|
||||
}
|
||||
this->process_zone_(buffer);
|
||||
break;
|
||||
case lowbyte(CMD_SET_ZONE):
|
||||
ESP_LOGV(TAG, "Got set zone conf command");
|
||||
this->query_zone_info();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read LD2450 buffer data
|
||||
void LD2450Component::readline_(int readch, uint8_t *buffer, uint8_t len) {
|
||||
if (readch < 0) {
|
||||
return;
|
||||
}
|
||||
if (this->buffer_pos_ < len - 1) {
|
||||
buffer[this->buffer_pos_++] = readch;
|
||||
buffer[this->buffer_pos_] = 0;
|
||||
} else {
|
||||
this->buffer_pos_ = 0;
|
||||
}
|
||||
if (this->buffer_pos_ < 4) {
|
||||
return;
|
||||
}
|
||||
if (buffer[this->buffer_pos_ - 2] == 0x55 && buffer[this->buffer_pos_ - 1] == 0xCC) {
|
||||
ESP_LOGV(TAG, "Handle periodic radar data");
|
||||
this->handle_periodic_data_(buffer, this->buffer_pos_);
|
||||
this->buffer_pos_ = 0; // Reset position index for next frame
|
||||
} else if (buffer[this->buffer_pos_ - 4] == 0x04 && buffer[this->buffer_pos_ - 3] == 0x03 &&
|
||||
buffer[this->buffer_pos_ - 2] == 0x02 && buffer[this->buffer_pos_ - 1] == 0x01) {
|
||||
ESP_LOGV(TAG, "Handle command ack data");
|
||||
if (this->handle_ack_data_(buffer, this->buffer_pos_)) {
|
||||
this->buffer_pos_ = 0; // Reset position index for next frame
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Command ack data invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set Config Mode - Pre-requisite sending commands
|
||||
void LD2450Component::set_config_mode_(bool enable) {
|
||||
uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
|
||||
uint8_t cmd_value[2] = {0x01, 0x00};
|
||||
this->send_command_(cmd, enable ? cmd_value : nullptr, 2);
|
||||
}
|
||||
|
||||
// Set Bluetooth Enable/Disable
|
||||
void LD2450Component::set_bluetooth(bool enable) {
|
||||
this->set_config_mode_(true);
|
||||
uint8_t enable_cmd_value[2] = {0x01, 0x00};
|
||||
uint8_t disable_cmd_value[2] = {0x00, 0x00};
|
||||
this->send_command_(CMD_BLUETOOTH, enable ? enable_cmd_value : disable_cmd_value, 2);
|
||||
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
|
||||
}
|
||||
|
||||
// Set Baud rate
|
||||
void LD2450Component::set_baud_rate(const std::string &state) {
|
||||
this->set_config_mode_(true);
|
||||
uint8_t cmd_value[2] = {BAUD_RATE_ENUM_TO_INT.at(state), 0x00};
|
||||
this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2);
|
||||
this->set_timeout(200, [this]() { this->restart_(); });
|
||||
}
|
||||
|
||||
// Set Zone Type - one of: Disabled, Detection, Filter
|
||||
void LD2450Component::set_zone_type(const std::string &state) {
|
||||
ESP_LOGV(TAG, "Set zone type: %s", state.c_str());
|
||||
uint8_t zone_type = ZONE_TYPE_ENUM_TO_INT.at(state);
|
||||
this->zone_type_ = zone_type;
|
||||
this->send_set_zone_command_();
|
||||
}
|
||||
|
||||
// Publish Zone Type to Select component
|
||||
void LD2450Component::publish_zone_type() {
|
||||
#ifdef USE_SELECT
|
||||
std::string zone_type = ZONE_TYPE_INT_TO_ENUM.at(static_cast<ZoneTypeStructure>(this->zone_type_));
|
||||
if (this->zone_type_select_ != nullptr) {
|
||||
this->zone_type_select_->publish_state(zone_type);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Set Single/Multiplayer target detection
|
||||
void LD2450Component::set_multi_target(bool enable) {
|
||||
this->set_config_mode_(true);
|
||||
uint8_t cmd = enable ? CMD_MULTI_TARGET_MODE : CMD_SINGLE_TARGET_MODE;
|
||||
this->send_command_(cmd, nullptr, 0);
|
||||
this->set_config_mode_(false);
|
||||
}
|
||||
|
||||
// LD2450 factory reset
|
||||
void LD2450Component::factory_reset() {
|
||||
this->set_config_mode_(true);
|
||||
this->send_command_(CMD_RESET, nullptr, 0);
|
||||
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
|
||||
}
|
||||
|
||||
// Restart LD2450 module
|
||||
void LD2450Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); }
|
||||
|
||||
// Get LD2450 firmware version
|
||||
void LD2450Component::get_version_() { this->send_command_(CMD_VERSION, nullptr, 0); }
|
||||
|
||||
// Get LD2450 mac address
|
||||
void LD2450Component::get_mac_() {
|
||||
uint8_t cmd_value[2] = {0x01, 0x00};
|
||||
this->send_command_(CMD_MAC, cmd_value, 2);
|
||||
}
|
||||
|
||||
// Query for target tracking mode
|
||||
void LD2450Component::query_target_tracking_mode_() { this->send_command_(CMD_QUERY_TARGET_MODE, nullptr, 0); }
|
||||
|
||||
// Query for zone info
|
||||
void LD2450Component::query_zone_() { this->send_command_(CMD_QUERY_ZONE, nullptr, 0); }
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void LD2450Component::set_move_x_sensor(uint8_t target, sensor::Sensor *s) { this->move_x_sensors_[target] = s; }
|
||||
void LD2450Component::set_move_y_sensor(uint8_t target, sensor::Sensor *s) { this->move_y_sensors_[target] = s; }
|
||||
void LD2450Component::set_move_speed_sensor(uint8_t target, sensor::Sensor *s) {
|
||||
this->move_speed_sensors_[target] = s;
|
||||
}
|
||||
void LD2450Component::set_move_angle_sensor(uint8_t target, sensor::Sensor *s) {
|
||||
this->move_angle_sensors_[target] = s;
|
||||
}
|
||||
void LD2450Component::set_move_distance_sensor(uint8_t target, sensor::Sensor *s) {
|
||||
this->move_distance_sensors_[target] = s;
|
||||
}
|
||||
void LD2450Component::set_move_resolution_sensor(uint8_t target, sensor::Sensor *s) {
|
||||
this->move_resolution_sensors_[target] = s;
|
||||
}
|
||||
void LD2450Component::set_zone_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
|
||||
this->zone_target_count_sensors_[zone] = s;
|
||||
}
|
||||
void LD2450Component::set_zone_still_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
|
||||
this->zone_still_target_count_sensors_[zone] = s;
|
||||
}
|
||||
void LD2450Component::set_zone_moving_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
|
||||
this->zone_moving_target_count_sensors_[zone] = s;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void LD2450Component::set_direction_text_sensor(uint8_t target, text_sensor::TextSensor *s) {
|
||||
this->direction_text_sensors_[target] = s;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Send Zone coordinates data to LD2450
|
||||
#ifdef USE_NUMBER
|
||||
void LD2450Component::set_zone_coordinate(uint8_t zone) {
|
||||
number::Number *x1sens = this->zone_numbers_[zone].x1;
|
||||
number::Number *y1sens = this->zone_numbers_[zone].y1;
|
||||
number::Number *x2sens = this->zone_numbers_[zone].x2;
|
||||
number::Number *y2sens = this->zone_numbers_[zone].y2;
|
||||
if (!x1sens->has_state() || !y1sens->has_state() || !x2sens->has_state() || !y2sens->has_state()) {
|
||||
return;
|
||||
}
|
||||
this->zone_config_[zone].x1 = static_cast<int>(x1sens->state);
|
||||
this->zone_config_[zone].y1 = static_cast<int>(y1sens->state);
|
||||
this->zone_config_[zone].x2 = static_cast<int>(x2sens->state);
|
||||
this->zone_config_[zone].y2 = static_cast<int>(y2sens->state);
|
||||
this->send_set_zone_command_();
|
||||
}
|
||||
|
||||
void LD2450Component::set_zone_numbers(uint8_t zone, number::Number *x1, number::Number *y1, number::Number *x2,
|
||||
number::Number *y2) {
|
||||
if (zone < MAX_ZONES) {
|
||||
this->zone_numbers_[zone].x1 = x1;
|
||||
this->zone_numbers_[zone].y1 = y1;
|
||||
this->zone_numbers_[zone].x2 = x2;
|
||||
this->zone_numbers_[zone].y2 = y2;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Set Presence Timeout load and save from flash
|
||||
#ifdef USE_NUMBER
|
||||
void LD2450Component::set_presence_timeout() {
|
||||
if (this->presence_timeout_number_ != nullptr) {
|
||||
if (this->presence_timeout_number_->state == 0) {
|
||||
float timeout = this->restore_from_flash_();
|
||||
this->presence_timeout_number_->publish_state(timeout);
|
||||
this->timeout_ = ld2450::convert_seconds_to_ms(timeout);
|
||||
}
|
||||
if (this->presence_timeout_number_->has_state()) {
|
||||
this->save_to_flash_(this->presence_timeout_number_->state);
|
||||
this->timeout_ = ld2450::convert_seconds_to_ms(this->presence_timeout_number_->state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save Presence Timeout to flash
|
||||
void LD2450Component::save_to_flash_(float value) { this->pref_.save(&value); }
|
||||
|
||||
// Load Presence Timeout from flash
|
||||
float LD2450Component::restore_from_flash_() {
|
||||
float value;
|
||||
if (!this->pref_.load(&value)) {
|
||||
value = DEFAULT_PRESENCE_TIMEOUT;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
234
esphome/components/ld2450/ld2450.h
Normal file
234
esphome/components/ld2450/ld2450.h
Normal file
@ -0,0 +1,234 @@
|
||||
#pragma once
|
||||
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
#include "esphome/components/number/number.h"
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
#include "esphome/components/button/button.h"
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
#include "esphome/components/select/select.h"
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
// Constants
|
||||
static const uint8_t DEFAULT_PRESENCE_TIMEOUT = 5; // Timeout to reset presense status 5 sec.
|
||||
static const uint8_t MAX_LINE_LENGTH = 60; // Max characters for serial buffer
|
||||
static const uint8_t MAX_TARGETS = 3; // Max 3 Targets in LD2450
|
||||
static const uint8_t MAX_ZONES = 3; // Max 3 Zones in LD2450
|
||||
|
||||
// Target coordinate struct
|
||||
struct Target {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
bool is_moving;
|
||||
};
|
||||
|
||||
// Zone coordinate struct
|
||||
struct Zone {
|
||||
int16_t x1 = 0;
|
||||
int16_t y1 = 0;
|
||||
int16_t x2 = 0;
|
||||
int16_t y2 = 0;
|
||||
};
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
struct ZoneOfNumbers {
|
||||
number::Number *x1 = nullptr;
|
||||
number::Number *y1 = nullptr;
|
||||
number::Number *x2 = nullptr;
|
||||
number::Number *y2 = nullptr;
|
||||
};
|
||||
#endif
|
||||
|
||||
enum BaudRateStructure : uint8_t {
|
||||
BAUD_RATE_9600 = 1,
|
||||
BAUD_RATE_19200 = 2,
|
||||
BAUD_RATE_38400 = 3,
|
||||
BAUD_RATE_57600 = 4,
|
||||
BAUD_RATE_115200 = 5,
|
||||
BAUD_RATE_230400 = 6,
|
||||
BAUD_RATE_256000 = 7,
|
||||
BAUD_RATE_460800 = 8
|
||||
};
|
||||
|
||||
// Convert baud rate enum to int
|
||||
static const std::map<std::string, uint8_t> BAUD_RATE_ENUM_TO_INT{
|
||||
{"9600", BAUD_RATE_9600}, {"19200", BAUD_RATE_19200}, {"38400", BAUD_RATE_38400},
|
||||
{"57600", BAUD_RATE_57600}, {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400},
|
||||
{"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800}};
|
||||
|
||||
// Zone type struct
|
||||
enum ZoneTypeStructure : uint8_t { ZONE_DISABLED = 0, ZONE_DETECTION = 1, ZONE_FILTER = 2 };
|
||||
|
||||
// Convert zone type int to enum
|
||||
static const std::map<ZoneTypeStructure, std::string> ZONE_TYPE_INT_TO_ENUM{
|
||||
{ZONE_DISABLED, "Disabled"}, {ZONE_DETECTION, "Detection"}, {ZONE_FILTER, "Filter"}};
|
||||
|
||||
// Convert zone type enum to int
|
||||
static const std::map<std::string, uint8_t> ZONE_TYPE_ENUM_TO_INT{
|
||||
{"Disabled", ZONE_DISABLED}, {"Detection", ZONE_DETECTION}, {"Filter", ZONE_FILTER}};
|
||||
|
||||
// LD2450 serial command header & footer
|
||||
static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA};
|
||||
static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01};
|
||||
|
||||
enum PeriodicDataStructure : uint8_t {
|
||||
TARGET_X = 4,
|
||||
TARGET_Y = 6,
|
||||
TARGET_SPEED = 8,
|
||||
TARGET_RESOLUTION = 10,
|
||||
};
|
||||
|
||||
enum PeriodicDataValue : uint8_t { HEAD = 0XAA, END = 0x55, CHECK = 0x00 };
|
||||
|
||||
enum AckDataStructure : uint8_t { COMMAND = 6, COMMAND_STATUS = 7 };
|
||||
|
||||
class LD2450Component : public Component, public uart::UARTDevice {
|
||||
#ifdef USE_SENSOR
|
||||
SUB_SENSOR(target_count)
|
||||
SUB_SENSOR(still_target_count)
|
||||
SUB_SENSOR(moving_target_count)
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
SUB_BINARY_SENSOR(target)
|
||||
SUB_BINARY_SENSOR(moving_target)
|
||||
SUB_BINARY_SENSOR(still_target)
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
SUB_TEXT_SENSOR(version)
|
||||
SUB_TEXT_SENSOR(mac)
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
SUB_SELECT(baud_rate)
|
||||
SUB_SELECT(zone_type)
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
SUB_SWITCH(bluetooth)
|
||||
SUB_SWITCH(multi_target)
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
SUB_BUTTON(reset)
|
||||
SUB_BUTTON(restart)
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
SUB_NUMBER(presence_timeout)
|
||||
#endif
|
||||
|
||||
public:
|
||||
LD2450Component();
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
void set_presence_timeout();
|
||||
void set_throttle(uint16_t value) { this->throttle_ = value; };
|
||||
void read_all_info();
|
||||
void query_zone_info();
|
||||
void restart_and_read_all_info();
|
||||
void set_bluetooth(bool enable);
|
||||
void set_multi_target(bool enable);
|
||||
void set_baud_rate(const std::string &state);
|
||||
void set_zone_type(const std::string &state);
|
||||
void publish_zone_type();
|
||||
void factory_reset();
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void set_direction_text_sensor(uint8_t target, text_sensor::TextSensor *s);
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
void set_zone_coordinate(uint8_t zone);
|
||||
void set_zone_numbers(uint8_t zone, number::Number *x1, number::Number *y1, number::Number *x2, number::Number *y2);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
void set_move_x_sensor(uint8_t target, sensor::Sensor *s);
|
||||
void set_move_y_sensor(uint8_t target, sensor::Sensor *s);
|
||||
void set_move_speed_sensor(uint8_t target, sensor::Sensor *s);
|
||||
void set_move_angle_sensor(uint8_t target, sensor::Sensor *s);
|
||||
void set_move_distance_sensor(uint8_t target, sensor::Sensor *s);
|
||||
void set_move_resolution_sensor(uint8_t target, sensor::Sensor *s);
|
||||
void set_zone_target_count_sensor(uint8_t zone, sensor::Sensor *s);
|
||||
void set_zone_still_target_count_sensor(uint8_t zone, sensor::Sensor *s);
|
||||
void set_zone_moving_target_count_sensor(uint8_t zone, sensor::Sensor *s);
|
||||
#endif
|
||||
void reset_radar_zone();
|
||||
void set_radar_zone(int32_t zone_type, int32_t zone1_x1, int32_t zone1_y1, int32_t zone1_x2, int32_t zone1_y2,
|
||||
int32_t zone2_x1, int32_t zone2_y1, int32_t zone2_x2, int32_t zone2_y2, int32_t zone3_x1,
|
||||
int32_t zone3_y1, int32_t zone3_x2, int32_t zone3_y2);
|
||||
|
||||
protected:
|
||||
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len);
|
||||
void set_config_mode_(bool enable);
|
||||
void handle_periodic_data_(uint8_t *buffer, uint8_t len);
|
||||
bool handle_ack_data_(uint8_t *buffer, uint8_t len);
|
||||
void process_zone_(uint8_t *buffer);
|
||||
void readline_(int readch, uint8_t *buffer, uint8_t len);
|
||||
void get_version_();
|
||||
void get_mac_();
|
||||
void query_target_tracking_mode_();
|
||||
void query_zone_();
|
||||
void restart_();
|
||||
void send_set_zone_command_();
|
||||
void save_to_flash_(float value);
|
||||
float restore_from_flash_();
|
||||
bool get_timeout_status_(uint32_t check_millis);
|
||||
uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving);
|
||||
|
||||
Target target_info_[MAX_TARGETS];
|
||||
Zone zone_config_[MAX_ZONES];
|
||||
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
|
||||
uint8_t buffer_data_[MAX_LINE_LENGTH];
|
||||
uint32_t last_periodic_millis_ = 0;
|
||||
uint32_t presence_millis_ = 0;
|
||||
uint32_t still_presence_millis_ = 0;
|
||||
uint32_t moving_presence_millis_ = 0;
|
||||
uint16_t throttle_ = 0;
|
||||
uint16_t timeout_ = 5;
|
||||
uint8_t zone_type_ = 0;
|
||||
std::string version_{};
|
||||
std::string mac_{};
|
||||
#ifdef USE_NUMBER
|
||||
ESPPreferenceObject pref_; // only used when numbers are in use
|
||||
ZoneOfNumbers zone_numbers_[MAX_ZONES];
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
std::vector<sensor::Sensor *> move_x_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS);
|
||||
std::vector<sensor::Sensor *> move_y_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS);
|
||||
std::vector<sensor::Sensor *> move_speed_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS);
|
||||
std::vector<sensor::Sensor *> move_angle_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS);
|
||||
std::vector<sensor::Sensor *> move_distance_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS);
|
||||
std::vector<sensor::Sensor *> move_resolution_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS);
|
||||
std::vector<sensor::Sensor *> zone_target_count_sensors_ = std::vector<sensor::Sensor *>(MAX_ZONES);
|
||||
std::vector<sensor::Sensor *> zone_still_target_count_sensors_ = std::vector<sensor::Sensor *>(MAX_ZONES);
|
||||
std::vector<sensor::Sensor *> zone_moving_target_count_sensors_ = std::vector<sensor::Sensor *>(MAX_ZONES);
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
std::vector<text_sensor::TextSensor *> direction_text_sensors_ = std::vector<text_sensor::TextSensor *>(3);
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
121
esphome/components/ld2450/number/__init__.py
Normal file
121
esphome/components/ld2450/number/__init__.py
Normal file
@ -0,0 +1,121 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import number
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_TIMELAPSE,
|
||||
UNIT_MILLIMETER,
|
||||
UNIT_SECOND,
|
||||
)
|
||||
|
||||
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
|
||||
|
||||
CONF_PRESENCE_TIMEOUT = "presence_timeout"
|
||||
CONF_X1 = "x1"
|
||||
CONF_X2 = "x2"
|
||||
CONF_Y1 = "y1"
|
||||
CONF_Y2 = "y2"
|
||||
ICON_ARROW_BOTTOM_RIGHT = "mdi:arrow-bottom-right"
|
||||
ICON_ARROW_BOTTOM_RIGHT_BOLD_BOX_OUTLINE = "mdi:arrow-bottom-right-bold-box-outline"
|
||||
ICON_ARROW_TOP_LEFT = "mdi:arrow-top-left"
|
||||
ICON_ARROW_TOP_LEFT_BOLD_BOX_OUTLINE = "mdi:arrow-top-left-bold-box-outline"
|
||||
MAX_ZONES = 3
|
||||
|
||||
PresenceTimeoutNumber = ld2450_ns.class_("PresenceTimeoutNumber", number.Number)
|
||||
ZoneCoordinateNumber = ld2450_ns.class_("ZoneCoordinateNumber", number.Number)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Required(CONF_PRESENCE_TIMEOUT): number.number_schema(
|
||||
PresenceTimeoutNumber,
|
||||
unit_of_measurement=UNIT_SECOND,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_TIMELAPSE,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(f"zone_{n + 1}"): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_X1): number.number_schema(
|
||||
ZoneCoordinateNumber,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_ARROW_TOP_LEFT_BOLD_BOX_OUTLINE,
|
||||
),
|
||||
cv.Required(CONF_Y1): number.number_schema(
|
||||
ZoneCoordinateNumber,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_ARROW_TOP_LEFT,
|
||||
),
|
||||
cv.Required(CONF_X2): number.number_schema(
|
||||
ZoneCoordinateNumber,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_ARROW_BOTTOM_RIGHT_BOLD_BOX_OUTLINE,
|
||||
),
|
||||
cv.Required(CONF_Y2): number.number_schema(
|
||||
ZoneCoordinateNumber,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_ARROW_BOTTOM_RIGHT,
|
||||
),
|
||||
}
|
||||
)
|
||||
for n in range(MAX_ZONES)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
if presence_timeout_config := config.get(CONF_PRESENCE_TIMEOUT):
|
||||
n = await number.new_number(
|
||||
presence_timeout_config,
|
||||
min_value=0,
|
||||
max_value=3600,
|
||||
step=1,
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2450_ID])
|
||||
cg.add(ld2450_component.set_presence_timeout_number(n))
|
||||
for zone_num in range(MAX_ZONES):
|
||||
if zone_conf := config.get(f"zone_{zone_num + 1}"):
|
||||
zone_x1_config = zone_conf.get(CONF_X1)
|
||||
x1 = cg.new_Pvariable(zone_x1_config[CONF_ID], zone_num)
|
||||
await number.register_number(
|
||||
x1, zone_x1_config, min_value=-4860, max_value=4860, step=1
|
||||
)
|
||||
await cg.register_parented(x1, config[CONF_LD2450_ID])
|
||||
|
||||
zone_y1_config = zone_conf.get(CONF_Y1)
|
||||
y1 = cg.new_Pvariable(zone_y1_config[CONF_ID], zone_num)
|
||||
await number.register_number(
|
||||
y1, zone_y1_config, min_value=0, max_value=7560, step=1
|
||||
)
|
||||
await cg.register_parented(y1, config[CONF_LD2450_ID])
|
||||
|
||||
zone_x2_config = zone_conf.get(CONF_X2)
|
||||
x2 = cg.new_Pvariable(zone_x2_config[CONF_ID], zone_num)
|
||||
await number.register_number(
|
||||
x2, zone_x2_config, min_value=-4860, max_value=4860, step=1
|
||||
)
|
||||
await cg.register_parented(x2, config[CONF_LD2450_ID])
|
||||
|
||||
zone_y2_config = zone_conf.get(CONF_Y2)
|
||||
y2 = cg.new_Pvariable(zone_y2_config[CONF_ID], zone_num)
|
||||
await number.register_number(
|
||||
y2, zone_y2_config, min_value=0, max_value=7560, step=1
|
||||
)
|
||||
await cg.register_parented(y2, config[CONF_LD2450_ID])
|
||||
|
||||
cg.add(ld2450_component.set_zone_numbers(zone_num, x1, y1, x2, y2))
|
12
esphome/components/ld2450/number/presence_timeout_number.cpp
Normal file
12
esphome/components/ld2450/number/presence_timeout_number.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include "presence_timeout_number.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
void PresenceTimeoutNumber::control(float value) {
|
||||
this->publish_state(value);
|
||||
this->parent_->set_presence_timeout();
|
||||
}
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
18
esphome/components/ld2450/number/presence_timeout_number.h
Normal file
18
esphome/components/ld2450/number/presence_timeout_number.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "../ld2450.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
class PresenceTimeoutNumber : public number::Number, public Parented<LD2450Component> {
|
||||
public:
|
||||
PresenceTimeoutNumber() = default;
|
||||
|
||||
protected:
|
||||
void control(float value) override;
|
||||
};
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
14
esphome/components/ld2450/number/zone_coordinate_number.cpp
Normal file
14
esphome/components/ld2450/number/zone_coordinate_number.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
#include "zone_coordinate_number.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
ZoneCoordinateNumber::ZoneCoordinateNumber(uint8_t zone) : zone_(zone) {}
|
||||
|
||||
void ZoneCoordinateNumber::control(float value) {
|
||||
this->publish_state(value);
|
||||
this->parent_->set_zone_coordinate(this->zone_);
|
||||
}
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
19
esphome/components/ld2450/number/zone_coordinate_number.h
Normal file
19
esphome/components/ld2450/number/zone_coordinate_number.h
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "../ld2450.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
class ZoneCoordinateNumber : public number::Number, public Parented<LD2450Component> {
|
||||
public:
|
||||
ZoneCoordinateNumber(uint8_t zone);
|
||||
|
||||
protected:
|
||||
uint8_t zone_;
|
||||
void control(float value) override;
|
||||
};
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
56
esphome/components/ld2450/select/__init__.py
Normal file
56
esphome/components/ld2450/select/__init__.py
Normal file
@ -0,0 +1,56 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import select
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BAUD_RATE, ENTITY_CATEGORY_CONFIG, ICON_THERMOMETER
|
||||
|
||||
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
|
||||
|
||||
CONF_ZONE_TYPE = "zone_type"
|
||||
|
||||
BaudRateSelect = ld2450_ns.class_("BaudRateSelect", select.Select)
|
||||
ZoneTypeSelect = ld2450_ns.class_("ZoneTypeSelect", select.Select)
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Optional(CONF_BAUD_RATE): select.select_schema(
|
||||
BaudRateSelect,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_THERMOMETER,
|
||||
),
|
||||
cv.Optional(CONF_ZONE_TYPE): select.select_schema(
|
||||
ZoneTypeSelect,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_THERMOMETER,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
if baud_rate_config := config.get(CONF_BAUD_RATE):
|
||||
s = await select.new_select(
|
||||
baud_rate_config,
|
||||
options=[
|
||||
"9600",
|
||||
"19200",
|
||||
"38400",
|
||||
"57600",
|
||||
"115200",
|
||||
"230400",
|
||||
"256000",
|
||||
"460800",
|
||||
],
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2450_ID])
|
||||
cg.add(ld2450_component.set_baud_rate_select(s))
|
||||
if zone_type_config := config.get(CONF_ZONE_TYPE):
|
||||
s = await select.new_select(
|
||||
zone_type_config,
|
||||
options=[
|
||||
"Disabled",
|
||||
"Detection",
|
||||
"Filter",
|
||||
],
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2450_ID])
|
||||
cg.add(ld2450_component.set_zone_type_select(s))
|
12
esphome/components/ld2450/select/baud_rate_select.cpp
Normal file
12
esphome/components/ld2450/select/baud_rate_select.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include "baud_rate_select.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
void BaudRateSelect::control(const std::string &value) {
|
||||
this->publish_state(value);
|
||||
this->parent_->set_baud_rate(state);
|
||||
}
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
18
esphome/components/ld2450/select/baud_rate_select.h
Normal file
18
esphome/components/ld2450/select/baud_rate_select.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/select/select.h"
|
||||
#include "../ld2450.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
class BaudRateSelect : public select::Select, public Parented<LD2450Component> {
|
||||
public:
|
||||
BaudRateSelect() = default;
|
||||
|
||||
protected:
|
||||
void control(const std::string &value) override;
|
||||
};
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
12
esphome/components/ld2450/select/zone_type_select.cpp
Normal file
12
esphome/components/ld2450/select/zone_type_select.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include "zone_type_select.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
void ZoneTypeSelect::control(const std::string &value) {
|
||||
this->publish_state(value);
|
||||
this->parent_->set_zone_type(state);
|
||||
}
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
18
esphome/components/ld2450/select/zone_type_select.h
Normal file
18
esphome/components/ld2450/select/zone_type_select.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/select/select.h"
|
||||
#include "../ld2450.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
class ZoneTypeSelect : public select::Select, public Parented<LD2450Component> {
|
||||
public:
|
||||
ZoneTypeSelect() = default;
|
||||
|
||||
protected:
|
||||
void control(const std::string &value) override;
|
||||
};
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
156
esphome/components/ld2450/sensor.py
Normal file
156
esphome/components/ld2450/sensor.py
Normal file
@ -0,0 +1,156 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ANGLE,
|
||||
CONF_DISTANCE,
|
||||
CONF_RESOLUTION,
|
||||
CONF_SPEED,
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
DEVICE_CLASS_SPEED,
|
||||
UNIT_DEGREES,
|
||||
UNIT_MILLIMETER,
|
||||
)
|
||||
|
||||
from . import CONF_LD2450_ID, LD2450Component
|
||||
|
||||
DEPENDENCIES = ["ld2450"]
|
||||
|
||||
CONF_MOVING_TARGET_COUNT = "moving_target_count"
|
||||
CONF_STILL_TARGET_COUNT = "still_target_count"
|
||||
CONF_TARGET_COUNT = "target_count"
|
||||
CONF_X = "x"
|
||||
CONF_Y = "y"
|
||||
|
||||
ICON_ACCOUNT_GROUP = "mdi:account-group"
|
||||
ICON_ACCOUNT_SWITCH = "mdi:account-switch"
|
||||
ICON_ALPHA_X_BOX_OUTLINE = "mdi:alpha-x-box-outline"
|
||||
ICON_ALPHA_Y_BOX_OUTLINE = "mdi:alpha-y-box-outline"
|
||||
ICON_FORMAT_TEXT_ROTATION_ANGLE_UP = "mdi:format-text-rotation-angle-up"
|
||||
ICON_HUMAN_GREETING_PROXIMITY = "mdi:human-greeting-proximity"
|
||||
ICON_MAP_MARKER_ACCOUNT = "mdi:map-marker-account"
|
||||
ICON_MAP_MARKER_DISTANCE = "mdi:map-marker-distance"
|
||||
ICON_RELATION_ZERO_OR_ONE_TO_ZERO_OR_ONE = "mdi:relation-zero-or-one-to-zero-or-one"
|
||||
ICON_SPEEDOMETER_SLOW = "mdi:speedometer-slow"
|
||||
|
||||
MAX_TARGETS = 3
|
||||
MAX_ZONES = 3
|
||||
|
||||
UNIT_MILLIMETER_PER_SECOND = "mm/s"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema(
|
||||
icon=ICON_ACCOUNT_GROUP,
|
||||
),
|
||||
cv.Optional(CONF_STILL_TARGET_COUNT): sensor.sensor_schema(
|
||||
icon=ICON_HUMAN_GREETING_PROXIMITY,
|
||||
),
|
||||
cv.Optional(CONF_MOVING_TARGET_COUNT): sensor.sensor_schema(
|
||||
icon=ICON_ACCOUNT_SWITCH,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(f"target_{n + 1}"): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_X): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
icon=ICON_ALPHA_X_BOX_OUTLINE,
|
||||
),
|
||||
cv.Optional(CONF_Y): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
icon=ICON_ALPHA_Y_BOX_OUTLINE,
|
||||
),
|
||||
cv.Optional(CONF_SPEED): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_SPEED,
|
||||
unit_of_measurement=UNIT_MILLIMETER_PER_SECOND,
|
||||
icon=ICON_SPEEDOMETER_SLOW,
|
||||
),
|
||||
cv.Optional(CONF_ANGLE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_DEGREES,
|
||||
icon=ICON_FORMAT_TEXT_ROTATION_ANGLE_UP,
|
||||
),
|
||||
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
icon=ICON_MAP_MARKER_DISTANCE,
|
||||
),
|
||||
cv.Optional(CONF_RESOLUTION): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
icon=ICON_RELATION_ZERO_OR_ONE_TO_ZERO_OR_ONE,
|
||||
),
|
||||
}
|
||||
)
|
||||
for n in range(MAX_TARGETS)
|
||||
},
|
||||
{
|
||||
cv.Optional(f"zone_{n + 1}"): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema(
|
||||
icon=ICON_MAP_MARKER_ACCOUNT,
|
||||
),
|
||||
cv.Optional(CONF_STILL_TARGET_COUNT): sensor.sensor_schema(
|
||||
icon=ICON_MAP_MARKER_ACCOUNT,
|
||||
),
|
||||
cv.Optional(CONF_MOVING_TARGET_COUNT): sensor.sensor_schema(
|
||||
icon=ICON_MAP_MARKER_ACCOUNT,
|
||||
),
|
||||
}
|
||||
)
|
||||
for n in range(MAX_ZONES)
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
|
||||
if target_count_config := config.get(CONF_TARGET_COUNT):
|
||||
sens = await sensor.new_sensor(target_count_config)
|
||||
cg.add(ld2450_component.set_target_count_sensor(sens))
|
||||
|
||||
if still_target_count_config := config.get(CONF_STILL_TARGET_COUNT):
|
||||
sens = await sensor.new_sensor(still_target_count_config)
|
||||
cg.add(ld2450_component.set_still_target_count_sensor(sens))
|
||||
|
||||
if moving_target_count_config := config.get(CONF_MOVING_TARGET_COUNT):
|
||||
sens = await sensor.new_sensor(moving_target_count_config)
|
||||
cg.add(ld2450_component.set_moving_target_count_sensor(sens))
|
||||
for n in range(MAX_TARGETS):
|
||||
if target_conf := config.get(f"target_{n + 1}"):
|
||||
if x_config := target_conf.get(CONF_X):
|
||||
sens = await sensor.new_sensor(x_config)
|
||||
cg.add(ld2450_component.set_move_x_sensor(n, sens))
|
||||
if y_config := target_conf.get(CONF_Y):
|
||||
sens = await sensor.new_sensor(y_config)
|
||||
cg.add(ld2450_component.set_move_y_sensor(n, sens))
|
||||
if speed_config := target_conf.get(CONF_SPEED):
|
||||
sens = await sensor.new_sensor(speed_config)
|
||||
cg.add(ld2450_component.set_move_speed_sensor(n, sens))
|
||||
if angle_config := target_conf.get(CONF_ANGLE):
|
||||
sens = await sensor.new_sensor(angle_config)
|
||||
cg.add(ld2450_component.set_move_angle_sensor(n, sens))
|
||||
if distance_config := target_conf.get(CONF_DISTANCE):
|
||||
sens = await sensor.new_sensor(distance_config)
|
||||
cg.add(ld2450_component.set_move_distance_sensor(n, sens))
|
||||
if resolution_config := target_conf.get(CONF_RESOLUTION):
|
||||
sens = await sensor.new_sensor(resolution_config)
|
||||
cg.add(ld2450_component.set_move_resolution_sensor(n, sens))
|
||||
for n in range(MAX_ZONES):
|
||||
if zone_config := config.get(f"zone_{n + 1}"):
|
||||
if target_count_config := zone_config.get(CONF_TARGET_COUNT):
|
||||
sens = await sensor.new_sensor(target_count_config)
|
||||
cg.add(ld2450_component.set_zone_target_count_sensor(n, sens))
|
||||
if still_target_count_config := zone_config.get(CONF_STILL_TARGET_COUNT):
|
||||
sens = await sensor.new_sensor(still_target_count_config)
|
||||
cg.add(ld2450_component.set_zone_still_target_count_sensor(n, sens))
|
||||
if moving_target_count_config := zone_config.get(CONF_MOVING_TARGET_COUNT):
|
||||
sens = await sensor.new_sensor(moving_target_count_config)
|
||||
cg.add(ld2450_component.set_zone_moving_target_count_sensor(n, sens))
|
45
esphome/components/ld2450/switch/__init__.py
Normal file
45
esphome/components/ld2450/switch/__init__.py
Normal file
@ -0,0 +1,45 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import switch
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_SWITCH,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_BLUETOOTH,
|
||||
ICON_PULSE,
|
||||
)
|
||||
|
||||
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
|
||||
|
||||
BluetoothSwitch = ld2450_ns.class_("BluetoothSwitch", switch.Switch)
|
||||
MultiTargetSwitch = ld2450_ns.class_("MultiTargetSwitch", switch.Switch)
|
||||
|
||||
CONF_BLUETOOTH = "bluetooth"
|
||||
CONF_MULTI_TARGET = "multi_target"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Optional(CONF_BLUETOOTH): switch.switch_schema(
|
||||
BluetoothSwitch,
|
||||
device_class=DEVICE_CLASS_SWITCH,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_BLUETOOTH,
|
||||
),
|
||||
cv.Optional(CONF_MULTI_TARGET): switch.switch_schema(
|
||||
MultiTargetSwitch,
|
||||
device_class=DEVICE_CLASS_SWITCH,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_PULSE,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
if bluetooth_config := config.get(CONF_BLUETOOTH):
|
||||
s = await switch.new_switch(bluetooth_config)
|
||||
await cg.register_parented(s, config[CONF_LD2450_ID])
|
||||
cg.add(ld2450_component.set_bluetooth_switch(s))
|
||||
if multi_target_config := config.get(CONF_MULTI_TARGET):
|
||||
s = await switch.new_switch(multi_target_config)
|
||||
await cg.register_parented(s, config[CONF_LD2450_ID])
|
||||
cg.add(ld2450_component.set_multi_target_switch(s))
|
12
esphome/components/ld2450/switch/bluetooth_switch.cpp
Normal file
12
esphome/components/ld2450/switch/bluetooth_switch.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include "bluetooth_switch.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
void BluetoothSwitch::write_state(bool state) {
|
||||
this->publish_state(state);
|
||||
this->parent_->set_bluetooth(state);
|
||||
}
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
18
esphome/components/ld2450/switch/bluetooth_switch.h
Normal file
18
esphome/components/ld2450/switch/bluetooth_switch.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#include "../ld2450.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
class BluetoothSwitch : public switch_::Switch, public Parented<LD2450Component> {
|
||||
public:
|
||||
BluetoothSwitch() = default;
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
12
esphome/components/ld2450/switch/multi_target_switch.cpp
Normal file
12
esphome/components/ld2450/switch/multi_target_switch.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include "multi_target_switch.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
void MultiTargetSwitch::write_state(bool state) {
|
||||
this->publish_state(state);
|
||||
this->parent_->set_multi_target(state);
|
||||
}
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
18
esphome/components/ld2450/switch/multi_target_switch.h
Normal file
18
esphome/components/ld2450/switch/multi_target_switch.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#include "../ld2450.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2450 {
|
||||
|
||||
class MultiTargetSwitch : public switch_::Switch, public Parented<LD2450Component> {
|
||||
public:
|
||||
MultiTargetSwitch() = default;
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
} // namespace ld2450
|
||||
} // namespace esphome
|
62
esphome/components/ld2450/text_sensor.py
Normal file
62
esphome/components/ld2450/text_sensor.py
Normal file
@ -0,0 +1,62 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DIRECTION,
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_VERSION,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ENTITY_CATEGORY_NONE,
|
||||
ICON_BLUETOOTH,
|
||||
ICON_CHIP,
|
||||
ICON_SIGN_DIRECTION,
|
||||
)
|
||||
|
||||
from . import CONF_LD2450_ID, LD2450Component
|
||||
|
||||
DEPENDENCIES = ["ld2450"]
|
||||
|
||||
MAX_TARGETS = 3
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_CHIP,
|
||||
),
|
||||
cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_BLUETOOTH,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(f"target_{n + 1}"): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_DIRECTION): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_NONE,
|
||||
icon=ICON_SIGN_DIRECTION,
|
||||
),
|
||||
}
|
||||
)
|
||||
for n in range(MAX_TARGETS)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
|
||||
if version_config := config.get(CONF_VERSION):
|
||||
sens = await text_sensor.new_text_sensor(version_config)
|
||||
cg.add(ld2450_component.set_version_text_sensor(sens))
|
||||
if mac_address_config := config.get(CONF_MAC_ADDRESS):
|
||||
sens = await text_sensor.new_text_sensor(mac_address_config)
|
||||
cg.add(ld2450_component.set_mac_text_sensor(sens))
|
||||
for n in range(MAX_TARGETS):
|
||||
if direction_conf := config.get(f"target_{n + 1}"):
|
||||
if direction_config := direction_conf.get(CONF_DIRECTION):
|
||||
sens = await text_sensor.new_text_sensor(direction_config)
|
||||
cg.add(ld2450_component.set_direction_text_sensor(n, sens))
|
@ -462,8 +462,6 @@ CONF_LVGL_ID = "lvgl_id"
|
||||
CONF_LONG_MODE = "long_mode"
|
||||
CONF_MSGBOXES = "msgboxes"
|
||||
CONF_OBJ = "obj"
|
||||
CONF_OFFSET_X = "offset_x"
|
||||
CONF_OFFSET_Y = "offset_y"
|
||||
CONF_ONE_CHECKED = "one_checked"
|
||||
CONF_ONE_LINE = "one_line"
|
||||
CONF_ON_PAUSE = "on_pause"
|
||||
|
@ -19,7 +19,7 @@ static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, ui
|
||||
const auto *gd = fe->get_glyph_data(unicode_letter);
|
||||
if (gd == nullptr)
|
||||
return false;
|
||||
dsc->adv_w = gd->offset_x + gd->width;
|
||||
dsc->adv_w = gd->advance;
|
||||
dsc->ofs_x = gd->offset_x;
|
||||
dsc->ofs_y = fe->height - gd->height - gd->offset_y - fe->baseline;
|
||||
dsc->box_w = gd->width;
|
||||
|
@ -416,37 +416,45 @@ LvglComponent::LvglComponent(std::vector<display::Display *> displays, float buf
|
||||
buffer_frac_(buffer_frac),
|
||||
full_refresh_(full_refresh),
|
||||
resume_on_input_(resume_on_input) {
|
||||
auto *display = this->displays_[0];
|
||||
size_t buffer_pixels = display->get_width() * display->get_height() / this->buffer_frac_;
|
||||
auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8;
|
||||
this->rotation = display->get_rotation();
|
||||
if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) {
|
||||
this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(buf_bytes)); // NOLINT
|
||||
if (this->rotate_buf_ == nullptr)
|
||||
return;
|
||||
}
|
||||
auto *buf = lv_custom_mem_alloc(buf_bytes); // NOLINT
|
||||
if (buf == nullptr)
|
||||
return;
|
||||
lv_disp_draw_buf_init(&this->draw_buf_, buf, nullptr, buffer_pixels);
|
||||
lv_disp_draw_buf_init(&this->draw_buf_, nullptr, nullptr, 0);
|
||||
lv_disp_drv_init(&this->disp_drv_);
|
||||
this->disp_drv_.draw_buf = &this->draw_buf_;
|
||||
this->disp_drv_.user_data = this;
|
||||
this->disp_drv_.full_refresh = this->full_refresh_;
|
||||
this->disp_drv_.flush_cb = static_flush_cb;
|
||||
this->disp_drv_.rounder_cb = rounder_cb;
|
||||
this->disp_drv_.hor_res = (lv_coord_t) display->get_width();
|
||||
this->disp_drv_.ver_res = (lv_coord_t) display->get_height();
|
||||
this->disp_drv_.hor_res = 0;
|
||||
this->disp_drv_.ver_res = 0;
|
||||
this->disp_ = lv_disp_drv_register(&this->disp_drv_);
|
||||
}
|
||||
|
||||
void LvglComponent::setup() {
|
||||
if (this->draw_buf_.buf1 == nullptr) {
|
||||
ESP_LOGCONFIG(TAG, "LVGL Setup starts");
|
||||
auto *display = this->displays_[0];
|
||||
auto width = display->get_width();
|
||||
auto height = display->get_height();
|
||||
size_t buffer_pixels = width * height / this->buffer_frac_;
|
||||
auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8;
|
||||
auto *buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT
|
||||
if (buffer == nullptr) {
|
||||
this->mark_failed();
|
||||
this->status_set_error("Memory allocation failure");
|
||||
return;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "LVGL Setup starts");
|
||||
lv_disp_draw_buf_init(&this->draw_buf_, buffer, nullptr, buf_bytes);
|
||||
this->disp_drv_.hor_res = width;
|
||||
this->disp_drv_.ver_res = height;
|
||||
// this->setup_driver_(display->get_width(), display->get_height());
|
||||
lv_disp_drv_update(this->disp_, &this->disp_drv_);
|
||||
this->rotation = display->get_rotation();
|
||||
if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) {
|
||||
this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(this->draw_buf_.size)); // NOLINT
|
||||
if (this->rotate_buf_ == nullptr) {
|
||||
this->mark_failed();
|
||||
this->status_set_error("Memory allocation failure");
|
||||
return;
|
||||
}
|
||||
}
|
||||
#if LV_USE_LOG
|
||||
lv_log_register_print_cb([](const char *buf) {
|
||||
auto next = strchr(buf, ')');
|
||||
@ -458,8 +466,8 @@ void LvglComponent::setup() {
|
||||
});
|
||||
#endif
|
||||
// Rotation will be handled by our drawing function, so reset the display rotation.
|
||||
for (auto *display : this->displays_)
|
||||
display->set_rotation(display::DISPLAY_ROTATION_0_DEGREES);
|
||||
for (auto *disp : this->displays_)
|
||||
disp->set_rotation(display::DISPLAY_ROTATION_0_DEGREES);
|
||||
this->show_page(0, LV_SCR_LOAD_ANIM_NONE, 0);
|
||||
lv_disp_trig_activity(this->disp_);
|
||||
ESP_LOGCONFIG(TAG, "LVGL Setup complete");
|
||||
|
@ -1,11 +1,9 @@
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ANGLE, CONF_MODE
|
||||
from esphome.const import CONF_ANGLE, CONF_MODE, CONF_OFFSET_X, CONF_OFFSET_Y
|
||||
|
||||
from ..defines import (
|
||||
CONF_ANTIALIAS,
|
||||
CONF_MAIN,
|
||||
CONF_OFFSET_X,
|
||||
CONF_OFFSET_Y,
|
||||
CONF_PIVOT_X,
|
||||
CONF_PIVOT_Y,
|
||||
CONF_SRC,
|
||||
|
@ -550,6 +550,7 @@ canbus::Error MCP2515::set_bitrate_(canbus::CanSpeed can_speed, CanClock can_clo
|
||||
cfg3 = MCP_16MHZ_40KBPS_CFG3;
|
||||
break;
|
||||
case (canbus::CAN_50KBPS): // 50Kbps
|
||||
cfg1 = MCP_16MHZ_50KBPS_CFG1;
|
||||
cfg2 = MCP_16MHZ_50KBPS_CFG2;
|
||||
cfg3 = MCP_16MHZ_50KBPS_CFG3;
|
||||
break;
|
||||
|
@ -91,7 +91,7 @@ async def to_code(config):
|
||||
add_idf_component(
|
||||
name="mdns",
|
||||
repo="https://github.com/espressif/esp-protocols.git",
|
||||
ref="mdns-v1.5.1",
|
||||
ref="mdns-v1.8.0",
|
||||
path="components/mdns",
|
||||
)
|
||||
|
||||
|
@ -177,11 +177,15 @@ void SourceSpeaker::set_mute_state(bool mute_state) {
|
||||
this->parent_->get_output_speaker()->set_mute_state(mute_state);
|
||||
}
|
||||
|
||||
bool SourceSpeaker::get_mute_state() { return this->parent_->get_output_speaker()->get_mute_state(); }
|
||||
|
||||
void SourceSpeaker::set_volume(float volume) {
|
||||
this->volume_ = volume;
|
||||
this->parent_->get_output_speaker()->set_volume(volume);
|
||||
}
|
||||
|
||||
float SourceSpeaker::get_volume() { return this->parent_->get_output_speaker()->get_volume(); }
|
||||
|
||||
size_t SourceSpeaker::process_data_from_source(TickType_t ticks_to_wait) {
|
||||
if (!this->transfer_buffer_.use_count()) {
|
||||
return 0;
|
||||
@ -490,7 +494,8 @@ void MixerSpeaker::audio_mixer_task(void *params) {
|
||||
break;
|
||||
}
|
||||
|
||||
output_transfer_buffer->transfer_data_to_sink(pdMS_TO_TICKS(TASK_DELAY_MS));
|
||||
// Never shift the data in the output transfer buffer to avoid unnecessary, slow data moves
|
||||
output_transfer_buffer->transfer_data_to_sink(pdMS_TO_TICKS(TASK_DELAY_MS), false);
|
||||
|
||||
const uint32_t output_frames_free =
|
||||
this_mixer->audio_stream_info_.value().bytes_to_frames(output_transfer_buffer->free());
|
||||
|
@ -53,9 +53,11 @@ class SourceSpeaker : public speaker::Speaker, public Component {
|
||||
|
||||
/// @brief Mute state changes are passed to the parent's output speaker
|
||||
void set_mute_state(bool mute_state) override;
|
||||
bool get_mute_state() override;
|
||||
|
||||
/// @brief Volume state changes are passed to the parent's output speaker
|
||||
void set_volume(float volume) override;
|
||||
float get_volume() override;
|
||||
|
||||
void set_pause_state(bool pause_state) override { this->pause_state_ = pause_state; }
|
||||
bool get_pause_state() const override { return this->pause_state_; }
|
||||
|
@ -1,20 +1,21 @@
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_FILTER,
|
||||
CONF_GAIN,
|
||||
CONF_ID,
|
||||
UNIT_MICROTESLA,
|
||||
UNIT_CELSIUS,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
CONF_OVERSAMPLING,
|
||||
CONF_RESOLUTION,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_TEMPERATURE_COMPENSATION,
|
||||
ICON_MAGNET,
|
||||
ICON_THERMOMETER,
|
||||
CONF_GAIN,
|
||||
CONF_RESOLUTION,
|
||||
CONF_OVERSAMPLING,
|
||||
CONF_FILTER,
|
||||
CONF_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_MICROTESLA,
|
||||
)
|
||||
from esphome import pins
|
||||
|
||||
CODEOWNERS = ["@functionpointer"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
@ -26,30 +27,46 @@ MLX90393Component = mlx90393_ns.class_(
|
||||
)
|
||||
|
||||
GAIN = {
|
||||
"1X": 7,
|
||||
"1_33X": 6,
|
||||
"1_67X": 5,
|
||||
"2X": 4,
|
||||
"2_5X": 3,
|
||||
"3X": 2,
|
||||
"4X": 1,
|
||||
"5X": 0,
|
||||
"1X": 0,
|
||||
"1_25X": 1,
|
||||
"1_67X": 2,
|
||||
"2X": 3,
|
||||
"2_5X": 4,
|
||||
"3X": 5,
|
||||
"3_75X": 6,
|
||||
"5X": 7,
|
||||
}
|
||||
|
||||
RESOLUTION = {
|
||||
"16BIT": 0,
|
||||
"17BIT": 1,
|
||||
"18BIT": 2,
|
||||
"19BIT": 3,
|
||||
"DIV_8": 3,
|
||||
"DIV_4": 2,
|
||||
"DIV_2": 1,
|
||||
"DIV_1": 0,
|
||||
}
|
||||
|
||||
CONF_X_AXIS = "x_axis"
|
||||
CONF_Y_AXIS = "y_axis"
|
||||
CONF_Z_AXIS = "z_axis"
|
||||
CONF_DRDY_PIN = "drdy_pin"
|
||||
CONF_HALLCONF = "hallconf"
|
||||
|
||||
|
||||
def mlx90393_axis_schema(default_resolution: str):
|
||||
def _validate(config):
|
||||
if config[CONF_TEMPERATURE_COMPENSATION]:
|
||||
for axis in [CONF_X_AXIS, CONF_Y_AXIS, CONF_Z_AXIS]:
|
||||
if axis not in config:
|
||||
continue
|
||||
if (res := config[axis][CONF_RESOLUTION]) in [
|
||||
"DIV_8",
|
||||
"DIV_4",
|
||||
]:
|
||||
raise cv.Invalid(
|
||||
f"{axis}: {CONF_RESOLUTION} cannot be {res} with {CONF_TEMPERATURE_COMPENSATION} enabled"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def mlx90393_axis_schema():
|
||||
return sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MICROTESLA,
|
||||
accuracy_decimals=0,
|
||||
@ -58,7 +75,7 @@ def mlx90393_axis_schema(default_resolution: str):
|
||||
).extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_RESOLUTION, default=default_resolution): cv.enum(
|
||||
cv.Optional(CONF_RESOLUTION, default="DIV_4"): cv.enum(
|
||||
RESOLUTION, upper=True, space="_"
|
||||
)
|
||||
}
|
||||
@ -66,19 +83,19 @@ def mlx90393_axis_schema(default_resolution: str):
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MLX90393Component),
|
||||
cv.Optional(CONF_GAIN, default="2_5X"): cv.enum(
|
||||
GAIN, upper=True, space="_"
|
||||
),
|
||||
cv.Optional(CONF_GAIN, default="1X"): cv.enum(GAIN, upper=True, space="_"),
|
||||
cv.Optional(CONF_DRDY_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Optional(CONF_OVERSAMPLING, default=2): cv.int_range(min=0, max=3),
|
||||
cv.Optional(CONF_OVERSAMPLING, default=0): cv.int_range(min=0, max=3),
|
||||
cv.Optional(CONF_FILTER, default=6): cv.int_range(min=0, max=7),
|
||||
cv.Optional(CONF_X_AXIS): mlx90393_axis_schema("19BIT"),
|
||||
cv.Optional(CONF_Y_AXIS): mlx90393_axis_schema("19BIT"),
|
||||
cv.Optional(CONF_Z_AXIS): mlx90393_axis_schema("16BIT"),
|
||||
cv.Optional(CONF_X_AXIS): mlx90393_axis_schema(),
|
||||
cv.Optional(CONF_Y_AXIS): mlx90393_axis_schema(),
|
||||
cv.Optional(CONF_Z_AXIS): mlx90393_axis_schema(),
|
||||
cv.Optional(CONF_TEMPERATURE_COMPENSATION, default=False): bool,
|
||||
cv.Optional(CONF_HALLCONF, default=0xC): cv.one_of(0xC, 0x0),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
@ -96,7 +113,8 @@ CONFIG_SCHEMA = (
|
||||
},
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x0C))
|
||||
.extend(i2c.i2c_device_schema(0x0C)),
|
||||
_validate,
|
||||
)
|
||||
|
||||
|
||||
@ -111,6 +129,8 @@ async def to_code(config):
|
||||
cg.add(var.set_gain(GAIN[config[CONF_GAIN]]))
|
||||
cg.add(var.set_oversampling(config[CONF_OVERSAMPLING]))
|
||||
cg.add(var.set_filter(config[CONF_FILTER]))
|
||||
cg.add(var.set_temperature_compensation(config[CONF_TEMPERATURE_COMPENSATION]))
|
||||
cg.add(var.set_hallconf(config[CONF_HALLCONF]))
|
||||
|
||||
if CONF_X_AXIS in config:
|
||||
sens = await sensor.new_sensor(config[CONF_X_AXIS])
|
||||
|
@ -43,6 +43,10 @@ void MLX90393Cls::setup() {
|
||||
this->mlx_.setDigitalFiltering(this->filter_);
|
||||
|
||||
this->mlx_.setTemperatureOverSampling(this->temperature_oversampling_);
|
||||
|
||||
this->mlx_.setTemperatureCompensation(this->temperature_compensation_);
|
||||
|
||||
this->mlx_.setHallConf(this->hallconf_);
|
||||
}
|
||||
|
||||
void MLX90393Cls::dump_config() {
|
||||
|
@ -29,7 +29,10 @@ class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90
|
||||
void set_resolution(uint8_t xyz, uint8_t res) { resolutions_[xyz] = res; }
|
||||
void set_filter(uint8_t filter) { filter_ = filter; }
|
||||
void set_gain(uint8_t gain_sel) { gain_ = gain_sel; }
|
||||
|
||||
void set_temperature_compensation(bool temperature_compensation) {
|
||||
temperature_compensation_ = temperature_compensation;
|
||||
}
|
||||
void set_hallconf(uint8_t hallconf) { hallconf_ = hallconf; }
|
||||
// overrides for MLX library
|
||||
|
||||
// disable lint because it keeps suggesting const uint8_t *response.
|
||||
@ -49,9 +52,11 @@ class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90
|
||||
sensor::Sensor *t_sensor_{nullptr};
|
||||
uint8_t gain_;
|
||||
uint8_t oversampling_;
|
||||
uint8_t temperature_oversampling_ = 0;
|
||||
uint8_t temperature_oversampling_{0};
|
||||
uint8_t filter_;
|
||||
uint8_t resolutions_[3] = {0};
|
||||
uint8_t resolutions_[3]{0};
|
||||
bool temperature_compensation_{false};
|
||||
uint8_t hallconf_{0xC};
|
||||
GPIOPin *drdy_pin_{nullptr};
|
||||
};
|
||||
|
||||
|
@ -36,6 +36,7 @@ from esphome.const import (
|
||||
CONF_PAYLOAD_AVAILABLE,
|
||||
CONF_PAYLOAD_NOT_AVAILABLE,
|
||||
CONF_PORT,
|
||||
CONF_PUBLISH_NAN_AS_NONE,
|
||||
CONF_QOS,
|
||||
CONF_REBOOT_TIMEOUT,
|
||||
CONF_RETAIN,
|
||||
@ -49,7 +50,6 @@ from esphome.const import (
|
||||
CONF_USE_ABBREVIATIONS,
|
||||
CONF_USERNAME,
|
||||
CONF_WILL_MESSAGE,
|
||||
CONF_PUBLISH_NAN_AS_NONE,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
@ -406,7 +406,7 @@ async def to_code(config):
|
||||
if CONF_SSL_FINGERPRINTS in config:
|
||||
for fingerprint in config[CONF_SSL_FINGERPRINTS]:
|
||||
arr = [
|
||||
cg.RawExpression(f"0x{fingerprint[i:i + 2]}") for i in range(0, 40, 2)
|
||||
cg.RawExpression(f"0x{fingerprint[i : i + 2]}") for i in range(0, 40, 2)
|
||||
]
|
||||
cg.add(var.add_ssl_fingerprint(arr))
|
||||
cg.add_build_flag("-DASYNC_TCP_SSL_ENABLED=1")
|
||||
|
189
esphome/components/msa3xx/__init__.py
Normal file
189
esphome/components/msa3xx/__init__.py
Normal file
@ -0,0 +1,189 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_CALIBRATION,
|
||||
CONF_ID,
|
||||
CONF_MIRROR_X,
|
||||
CONF_MIRROR_Y,
|
||||
CONF_OFFSET_X,
|
||||
CONF_OFFSET_Y,
|
||||
CONF_OFFSET_Z,
|
||||
CONF_RANGE,
|
||||
CONF_RESOLUTION,
|
||||
CONF_SWAP_XY,
|
||||
CONF_TRANSFORM,
|
||||
CONF_TYPE,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@latonita"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_MSA3XX_ID = "msa3xx_id"
|
||||
|
||||
CONF_MIRROR_Z = "mirror_z"
|
||||
CONF_ON_ACTIVE = "on_active"
|
||||
CONF_ON_DOUBLE_TAP = "on_double_tap"
|
||||
CONF_ON_FREEFALL = "on_freefall"
|
||||
CONF_ON_ORIENTATION = "on_orientation"
|
||||
CONF_ON_TAP = "on_tap"
|
||||
|
||||
MODEL_MSA301 = "MSA301"
|
||||
MODEL_MSA311 = "MSA311"
|
||||
|
||||
msa3xx_ns = cg.esphome_ns.namespace("msa3xx")
|
||||
MSA3xxComponent = msa3xx_ns.class_(
|
||||
"MSA3xxComponent", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
MSAModels = msa3xx_ns.enum("Model", True)
|
||||
MSA_MODELS = {
|
||||
MODEL_MSA301: MSAModels.MSA301,
|
||||
MODEL_MSA311: MSAModels.MSA311,
|
||||
}
|
||||
|
||||
MSARange = msa3xx_ns.enum("Range", True)
|
||||
MSA_RANGES = {
|
||||
"2G": MSARange.RANGE_2G,
|
||||
"4G": MSARange.RANGE_4G,
|
||||
"8G": MSARange.RANGE_8G,
|
||||
"16G": MSARange.RANGE_16G,
|
||||
}
|
||||
|
||||
MSAResolution = msa3xx_ns.enum("Resolution", True)
|
||||
RESOLUTIONS_MSA301 = {
|
||||
14: MSAResolution.RES_14BIT,
|
||||
12: MSAResolution.RES_12BIT,
|
||||
10: MSAResolution.RES_10BIT,
|
||||
8: MSAResolution.RES_8BIT,
|
||||
}
|
||||
|
||||
RESOLUTIONS_MSA311 = {
|
||||
12: MSAResolution.RES_12BIT,
|
||||
}
|
||||
|
||||
_COMMON_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MSA3xxComponent),
|
||||
cv.Optional(CONF_RANGE, default="2G"): cv.enum(MSA_RANGES, upper=True),
|
||||
cv.Optional(CONF_CALIBRATION): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_OFFSET_X, default=0): cv.float_range(
|
||||
min=-4.5, max=4.5
|
||||
),
|
||||
cv.Optional(CONF_OFFSET_Y, default=0): cv.float_range(
|
||||
min=-4.5, max=4.5
|
||||
),
|
||||
cv.Optional(CONF_OFFSET_Z, default=0): cv.float_range(
|
||||
min=-4.5, max=4.5
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_TRANSFORM): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MIRROR_Z, default=False): cv.boolean,
|
||||
cv.Optional(CONF_SWAP_XY, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_ACTIVE): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_ON_TAP): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_ON_DOUBLE_TAP): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_ON_FREEFALL): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_ON_ORIENTATION): automation.validate_automation(single=True),
|
||||
}
|
||||
).extend(cv.polling_component_schema("10s"))
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
MODEL_MSA301: _COMMON_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_RESOLUTION, default=14): cv.enum(RESOLUTIONS_MSA301),
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x26)),
|
||||
MODEL_MSA311: _COMMON_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_RESOLUTION, default=12): cv.enum(RESOLUTIONS_MSA311),
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x62)),
|
||||
},
|
||||
upper=True,
|
||||
enum=MSA_MODELS,
|
||||
)
|
||||
|
||||
MSA_SENSOR_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_MSA3XX_ID): cv.use_id(MSA3xxComponent),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_model(config[CONF_TYPE]))
|
||||
cg.add(var.set_range(MSA_RANGES[config[CONF_RANGE]]))
|
||||
cg.add(var.set_resolution(RESOLUTIONS_MSA301[config[CONF_RESOLUTION]]))
|
||||
|
||||
if transform := config.get(CONF_TRANSFORM):
|
||||
cg.add(
|
||||
var.set_transform(
|
||||
transform[CONF_MIRROR_X],
|
||||
transform[CONF_MIRROR_Y],
|
||||
transform[CONF_MIRROR_Z],
|
||||
transform[CONF_SWAP_XY],
|
||||
)
|
||||
)
|
||||
|
||||
if calibration_config := config.get(CONF_CALIBRATION):
|
||||
cg.add(
|
||||
var.set_offset(
|
||||
calibration_config[CONF_OFFSET_X],
|
||||
calibration_config[CONF_OFFSET_Y],
|
||||
calibration_config[CONF_OFFSET_Z],
|
||||
)
|
||||
)
|
||||
|
||||
# Triggers secton
|
||||
|
||||
if CONF_ON_ORIENTATION in config:
|
||||
await automation.build_automation(
|
||||
var.get_orientation_trigger(),
|
||||
[],
|
||||
config[CONF_ON_ORIENTATION],
|
||||
)
|
||||
|
||||
if CONF_ON_TAP in config:
|
||||
await automation.build_automation(
|
||||
var.get_tap_trigger(),
|
||||
[],
|
||||
config[CONF_ON_TAP],
|
||||
)
|
||||
|
||||
if CONF_ON_DOUBLE_TAP in config:
|
||||
await automation.build_automation(
|
||||
var.get_double_tap_trigger(),
|
||||
[],
|
||||
config[CONF_ON_DOUBLE_TAP],
|
||||
)
|
||||
|
||||
if CONF_ON_ACTIVE in config:
|
||||
await automation.build_automation(
|
||||
var.get_active_trigger(),
|
||||
[],
|
||||
config[CONF_ON_ACTIVE],
|
||||
)
|
||||
|
||||
if CONF_ON_FREEFALL in config:
|
||||
await automation.build_automation(
|
||||
var.get_freefall_trigger(),
|
||||
[],
|
||||
config[CONF_ON_FREEFALL],
|
||||
)
|
40
esphome/components/msa3xx/binary_sensor.py
Normal file
40
esphome/components/msa3xx/binary_sensor.py
Normal file
@ -0,0 +1,40 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ACTIVE, CONF_NAME, DEVICE_CLASS_VIBRATION, ICON_VIBRATE
|
||||
|
||||
from . import CONF_MSA3XX_ID, MSA_SENSOR_SCHEMA
|
||||
|
||||
CODEOWNERS = ["@latonita"]
|
||||
DEPENDENCIES = ["msa3xx"]
|
||||
|
||||
CONF_TAP = "tap"
|
||||
CONF_DOUBLE_TAP = "double_tap"
|
||||
|
||||
ICON_TAP = "mdi:gesture-tap"
|
||||
ICON_DOUBLE_TAP = "mdi:gesture-double-tap"
|
||||
|
||||
EVENT_SENSORS = (CONF_TAP, CONF_DOUBLE_TAP, CONF_ACTIVE)
|
||||
ICONS = (ICON_TAP, ICON_DOUBLE_TAP, ICON_VIBRATE)
|
||||
|
||||
CONFIG_SCHEMA = MSA_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(event): cv.maybe_simple_value(
|
||||
binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_VIBRATION,
|
||||
icon=icon,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
)
|
||||
for event, icon in zip(EVENT_SENSORS, ICONS)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_MSA3XX_ID])
|
||||
|
||||
for sensor in EVENT_SENSORS:
|
||||
if sensor in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[sensor])
|
||||
cg.add(getattr(hub, f"set_{sensor}_binary_sensor")(sens))
|
417
esphome/components/msa3xx/msa3xx.cpp
Normal file
417
esphome/components/msa3xx/msa3xx.cpp
Normal file
@ -0,0 +1,417 @@
|
||||
#include "msa3xx.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace msa3xx {
|
||||
|
||||
static const char *const TAG = "msa3xx";
|
||||
|
||||
const uint8_t MSA_3XX_PART_ID = 0x13;
|
||||
|
||||
const float GRAVITY_EARTH = 9.80665f;
|
||||
const float LSB_COEFF = 1000.0f / (GRAVITY_EARTH * 3.9); // LSB to 1 LSB = 3.9mg = 0.0039g
|
||||
const float G_OFFSET_MIN = -4.5f; // -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe
|
||||
const float G_OFFSET_MAX = 4.5f; // -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe
|
||||
|
||||
const uint8_t RESOLUTION[] = {14, 12, 10, 8};
|
||||
|
||||
const uint32_t TAP_COOLDOWN_MS = 500;
|
||||
const uint32_t DOUBLE_TAP_COOLDOWN_MS = 500;
|
||||
const uint32_t ACTIVITY_COOLDOWN_MS = 500;
|
||||
|
||||
const char *model_to_string(Model model) {
|
||||
switch (model) {
|
||||
case Model::MSA301:
|
||||
return "MSA301";
|
||||
case Model::MSA311:
|
||||
return "MSA311";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char *power_mode_to_string(PowerMode power_mode) {
|
||||
switch (power_mode) {
|
||||
case PowerMode::NORMAL:
|
||||
return "Normal";
|
||||
case PowerMode::LOW_POWER:
|
||||
return "Low Power";
|
||||
case PowerMode::SUSPEND:
|
||||
return "Suspend";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char *res_to_string(Resolution resolution) {
|
||||
switch (resolution) {
|
||||
case Resolution::RES_14BIT:
|
||||
return "14-bit";
|
||||
case Resolution::RES_12BIT:
|
||||
return "12-bit";
|
||||
case Resolution::RES_10BIT:
|
||||
return "10-bit";
|
||||
case Resolution::RES_8BIT:
|
||||
return "8-bit";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char *range_to_string(Range range) {
|
||||
switch (range) {
|
||||
case Range::RANGE_2G:
|
||||
return "±2g";
|
||||
case Range::RANGE_4G:
|
||||
return "±4g";
|
||||
case Range::RANGE_8G:
|
||||
return "±8g";
|
||||
case Range::RANGE_16G:
|
||||
return "±16g";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char *bandwidth_to_string(Bandwidth bandwidth) {
|
||||
switch (bandwidth) {
|
||||
case Bandwidth::BW_1_95HZ:
|
||||
return "1.95 Hz";
|
||||
case Bandwidth::BW_3_9HZ:
|
||||
return "3.9 Hz";
|
||||
case Bandwidth::BW_7_81HZ:
|
||||
return "7.81 Hz";
|
||||
case Bandwidth::BW_15_63HZ:
|
||||
return "15.63 Hz";
|
||||
case Bandwidth::BW_31_25HZ:
|
||||
return "31.25 Hz";
|
||||
case Bandwidth::BW_62_5HZ:
|
||||
return "62.5 Hz";
|
||||
case Bandwidth::BW_125HZ:
|
||||
return "125 Hz";
|
||||
case Bandwidth::BW_250HZ:
|
||||
return "250 Hz";
|
||||
case Bandwidth::BW_500HZ:
|
||||
return "500 Hz";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char *orientation_xy_to_string(OrientationXY orientation) {
|
||||
switch (orientation) {
|
||||
case OrientationXY::PORTRAIT_UPRIGHT:
|
||||
return "Portrait Upright";
|
||||
case OrientationXY::PORTRAIT_UPSIDE_DOWN:
|
||||
return "Portrait Upside Down";
|
||||
case OrientationXY::LANDSCAPE_LEFT:
|
||||
return "Landscape Left";
|
||||
case OrientationXY::LANDSCAPE_RIGHT:
|
||||
return "Landscape Right";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char *orientation_z_to_string(bool orientation) { return orientation ? "Downwards looking" : "Upwards looking"; }
|
||||
|
||||
void MSA3xxComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up MSA3xx...");
|
||||
|
||||
uint8_t part_id{0xff};
|
||||
if (!this->read_byte(static_cast<uint8_t>(RegisterMap::PART_ID), &part_id) || (part_id != MSA_3XX_PART_ID)) {
|
||||
ESP_LOGE(TAG, "Part ID is wrong or missing. Got 0x%02X", part_id);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolution LSB/g
|
||||
// Range : MSA301 : MSA311
|
||||
// S2g : 1024 (2^10) : 4096 (2^12)
|
||||
// S4g : 512 (2^9) : 2048 (2^11)
|
||||
// S8g : 256 (2^8) : 1024 (2^10)
|
||||
// S16g : 128 (2^7) : 512 (2^9)
|
||||
if (this->model_ == Model::MSA301) {
|
||||
this->device_params_.accel_data_width = 14;
|
||||
this->device_params_.scale_factor_exp = static_cast<uint8_t>(this->range_) - 12;
|
||||
} else if (this->model_ == Model::MSA311) {
|
||||
this->device_params_.accel_data_width = 12;
|
||||
this->device_params_.scale_factor_exp = static_cast<uint8_t>(this->range_) - 10;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown model");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->setup_odr_(this->data_rate_);
|
||||
this->setup_power_mode_bandwidth_(this->power_mode_, this->bandwidth_);
|
||||
this->setup_range_resolution_(this->range_, this->resolution_); // 2g...16g, 14...8 bit
|
||||
this->setup_offset_(this->offset_x_, this->offset_y_, this->offset_z_); // calibration offsets
|
||||
this->write_byte(static_cast<uint8_t>(RegisterMap::TAP_DURATION), 0b11000100); // set tap duration 250ms
|
||||
this->write_byte(static_cast<uint8_t>(RegisterMap::SWAP_POLARITY), this->swap_.raw); // set axes polarity
|
||||
this->write_byte(static_cast<uint8_t>(RegisterMap::INT_SET_0), 0b01110111); // enable all interrupts
|
||||
this->write_byte(static_cast<uint8_t>(RegisterMap::INT_SET_1), 0b00011000); // including orientation
|
||||
}
|
||||
|
||||
void MSA3xxComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MSA3xx:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with MSA3xx failed!");
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Model: %s", model_to_string(this->model_));
|
||||
ESP_LOGCONFIG(TAG, " Power Mode: %s", power_mode_to_string(this->power_mode_));
|
||||
ESP_LOGCONFIG(TAG, " Bandwidth: %s", bandwidth_to_string(this->bandwidth_));
|
||||
ESP_LOGCONFIG(TAG, " Range: %s", range_to_string(this->range_));
|
||||
ESP_LOGCONFIG(TAG, " Resolution: %s", res_to_string(this->resolution_));
|
||||
ESP_LOGCONFIG(TAG, " Offsets: {%.3f m/s², %.3f m/s², %.3f m/s²}", this->offset_x_, this->offset_y_, this->offset_z_);
|
||||
ESP_LOGCONFIG(TAG, " Transform: {mirror_x=%s, mirror_y=%s, mirror_z=%s, swap_xy=%s}", YESNO(this->swap_.x_polarity),
|
||||
YESNO(this->swap_.y_polarity), YESNO(this->swap_.z_polarity), YESNO(this->swap_.x_y_swap));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
LOG_BINARY_SENSOR(" ", "Tap", this->tap_binary_sensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Double Tap", this->double_tap_binary_sensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Active", this->active_binary_sensor_);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "Acceleration X", this->acceleration_x_sensor_);
|
||||
LOG_SENSOR(" ", "Acceleration Y", this->acceleration_y_sensor_);
|
||||
LOG_SENSOR(" ", "Acceleration Z", this->acceleration_z_sensor_);
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
LOG_TEXT_SENSOR(" ", "Orientation XY", this->orientation_xy_text_sensor_);
|
||||
LOG_TEXT_SENSOR(" ", "Orientation Z", this->orientation_z_text_sensor_);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MSA3xxComponent::read_data_() {
|
||||
uint8_t accel_data[6];
|
||||
if (!this->read_bytes(static_cast<uint8_t>(RegisterMap::ACC_X_LSB), accel_data, 6)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto raw_to_x_bit = [](uint16_t lsb, uint16_t msb, uint8_t data_bits) -> uint16_t {
|
||||
return ((msb << 8) | lsb) >> (16 - data_bits);
|
||||
};
|
||||
|
||||
auto lpf = [](float new_value, float old_value, float alpha = 0.5f) {
|
||||
return alpha * new_value + (1.0f - alpha) * old_value;
|
||||
};
|
||||
|
||||
this->data_.lsb_x =
|
||||
this->twos_complement_(raw_to_x_bit(accel_data[0], accel_data[1], this->device_params_.accel_data_width),
|
||||
this->device_params_.accel_data_width);
|
||||
this->data_.lsb_y =
|
||||
this->twos_complement_(raw_to_x_bit(accel_data[2], accel_data[3], this->device_params_.accel_data_width),
|
||||
this->device_params_.accel_data_width);
|
||||
this->data_.lsb_z =
|
||||
this->twos_complement_(raw_to_x_bit(accel_data[4], accel_data[5], this->device_params_.accel_data_width),
|
||||
this->device_params_.accel_data_width);
|
||||
|
||||
this->data_.x = lpf(ldexp(this->data_.lsb_x, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.x);
|
||||
this->data_.y = lpf(ldexp(this->data_.lsb_y, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.y);
|
||||
this->data_.z = lpf(ldexp(this->data_.lsb_z, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.z);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MSA3xxComponent::read_motion_status_() {
|
||||
if (!this->read_byte(static_cast<uint8_t>(RegisterMap::MOTION_INTERRUPT), &this->status_.motion_int.raw)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this->read_byte(static_cast<uint8_t>(RegisterMap::ORIENTATION_STATUS), &this->status_.orientation.raw)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MSA3xxComponent::loop() {
|
||||
if (!this->is_ready()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RegMotionInterrupt old_motion_int = this->status_.motion_int;
|
||||
|
||||
if (!this->read_data_() || !this->read_motion_status_()) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->process_motions_(old_motion_int);
|
||||
}
|
||||
|
||||
void MSA3xxComponent::update() {
|
||||
ESP_LOGV(TAG, "Updating MSA3xx...");
|
||||
|
||||
if (!this->is_ready()) {
|
||||
ESP_LOGV(TAG, "Component MSA3xx not ready for update");
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, "Acceleration: {x = %+1.3f m/s², y = %+1.3f m/s², z = %+1.3f m/s²}; ", this->data_.x, this->data_.y,
|
||||
this->data_.z);
|
||||
|
||||
ESP_LOGV(TAG, "Orientation: {XY = %s, Z = %s}", orientation_xy_to_string(this->status_.orientation.orient_xy),
|
||||
orientation_z_to_string(this->status_.orientation.orient_z));
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
if (this->acceleration_x_sensor_ != nullptr)
|
||||
this->acceleration_x_sensor_->publish_state(this->data_.x);
|
||||
if (this->acceleration_y_sensor_ != nullptr)
|
||||
this->acceleration_y_sensor_->publish_state(this->data_.y);
|
||||
if (this->acceleration_z_sensor_ != nullptr)
|
||||
this->acceleration_z_sensor_->publish_state(this->data_.z);
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->orientation_xy_text_sensor_ != nullptr &&
|
||||
(this->status_.orientation.orient_xy != this->status_.orientation_old.orient_xy ||
|
||||
this->status_.never_published)) {
|
||||
this->orientation_xy_text_sensor_->publish_state(orientation_xy_to_string(this->status_.orientation.orient_xy));
|
||||
}
|
||||
if (this->orientation_z_text_sensor_ != nullptr &&
|
||||
(this->status_.orientation.orient_z != this->status_.orientation_old.orient_z || this->status_.never_published)) {
|
||||
this->orientation_z_text_sensor_->publish_state(orientation_z_to_string(this->status_.orientation.orient_z));
|
||||
}
|
||||
this->status_.orientation_old = this->status_.orientation;
|
||||
#endif
|
||||
|
||||
this->status_.never_published = false;
|
||||
this->status_clear_warning();
|
||||
}
|
||||
float MSA3xxComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void MSA3xxComponent::set_offset(float offset_x, float offset_y, float offset_z) {
|
||||
this->offset_x_ = offset_x;
|
||||
this->offset_y_ = offset_y;
|
||||
this->offset_z_ = offset_z;
|
||||
}
|
||||
|
||||
void MSA3xxComponent::set_transform(bool mirror_x, bool mirror_y, bool mirror_z, bool swap_xy) {
|
||||
this->swap_.x_polarity = mirror_x;
|
||||
this->swap_.y_polarity = mirror_y;
|
||||
this->swap_.z_polarity = mirror_z;
|
||||
this->swap_.x_y_swap = swap_xy;
|
||||
}
|
||||
|
||||
void MSA3xxComponent::setup_odr_(DataRate rate) {
|
||||
RegOutputDataRate reg_odr;
|
||||
auto reg = this->read_byte(static_cast<uint8_t>(RegisterMap::ODR));
|
||||
if (reg.has_value()) {
|
||||
reg_odr.raw = reg.value();
|
||||
} else {
|
||||
reg_odr.raw = 0x0F; // defaut from datasheet
|
||||
}
|
||||
|
||||
reg_odr.x_axis_disable = false;
|
||||
reg_odr.y_axis_disable = false;
|
||||
reg_odr.z_axis_disable = false;
|
||||
reg_odr.odr = rate;
|
||||
|
||||
this->write_byte(static_cast<uint8_t>(RegisterMap::ODR), reg_odr.raw);
|
||||
}
|
||||
|
||||
void MSA3xxComponent::setup_power_mode_bandwidth_(PowerMode power_mode, Bandwidth bandwidth) {
|
||||
// 0x11 POWER_MODE_BANDWIDTH
|
||||
auto reg = this->read_byte(static_cast<uint8_t>(RegisterMap::POWER_MODE_BANDWIDTH));
|
||||
|
||||
RegPowerModeBandwidth power_mode_bandwidth;
|
||||
if (reg.has_value()) {
|
||||
power_mode_bandwidth.raw = reg.value();
|
||||
} else {
|
||||
power_mode_bandwidth.raw = 0xde; // defaut from datasheet
|
||||
}
|
||||
|
||||
power_mode_bandwidth.power_mode = power_mode;
|
||||
power_mode_bandwidth.low_power_bandwidth = bandwidth;
|
||||
|
||||
this->write_byte(static_cast<uint8_t>(RegisterMap::POWER_MODE_BANDWIDTH), power_mode_bandwidth.raw);
|
||||
}
|
||||
|
||||
void MSA3xxComponent::setup_range_resolution_(Range range, Resolution resolution) {
|
||||
RegRangeResolution reg;
|
||||
reg.raw = this->read_byte(static_cast<uint8_t>(RegisterMap::RANGE_RESOLUTION)).value_or(0x00);
|
||||
reg.range = range;
|
||||
if (this->model_ == Model::MSA301) {
|
||||
reg.resolution = resolution;
|
||||
}
|
||||
this->write_byte(static_cast<uint8_t>(RegisterMap::RANGE_RESOLUTION), reg.raw);
|
||||
}
|
||||
|
||||
void MSA3xxComponent::setup_offset_(float offset_x, float offset_y, float offset_z) {
|
||||
uint8_t offset[3];
|
||||
|
||||
auto offset_g_to_lsb = [](float accel) -> int8_t {
|
||||
float acccel_clamped = clamp(accel, G_OFFSET_MIN, G_OFFSET_MAX);
|
||||
return static_cast<int8_t>(acccel_clamped * LSB_COEFF);
|
||||
};
|
||||
|
||||
offset[0] = offset_g_to_lsb(offset_x);
|
||||
offset[1] = offset_g_to_lsb(offset_y);
|
||||
offset[2] = offset_g_to_lsb(offset_z);
|
||||
|
||||
ESP_LOGV(TAG, "Offset (%.3f, %.3f, %.3f)=>LSB(%d, %d, %d)", offset_x, offset_y, offset_z, offset[0], offset[1],
|
||||
offset[2]);
|
||||
|
||||
this->write_bytes(static_cast<uint8_t>(RegisterMap::OFFSET_COMP_X), (uint8_t *) &offset, 3);
|
||||
}
|
||||
|
||||
int64_t MSA3xxComponent::twos_complement_(uint64_t value, uint8_t bits) {
|
||||
if (value > (1ULL << (bits - 1))) {
|
||||
return (int64_t) (value - (1ULL << bits));
|
||||
} else {
|
||||
return (int64_t) value;
|
||||
}
|
||||
}
|
||||
|
||||
void binary_event_debounce(bool state, bool old_state, uint32_t now, uint32_t &last_ms, Trigger<> &trigger,
|
||||
uint32_t cooldown_ms, void *bs, const char *desc) {
|
||||
if (state && now - last_ms > cooldown_ms) {
|
||||
ESP_LOGV(TAG, "%s detected", desc);
|
||||
trigger.trigger();
|
||||
last_ms = now;
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if (bs != nullptr) {
|
||||
static_cast<binary_sensor::BinarySensor *>(bs)->publish_state(true);
|
||||
}
|
||||
#endif
|
||||
} else if (!state && now - last_ms > cooldown_ms && bs != nullptr) {
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
static_cast<binary_sensor::BinarySensor *>(bs)->publish_state(false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#define BS_OPTIONAL_PTR(x) ((void *) (x))
|
||||
#else
|
||||
#define BS_OPTIONAL_PTR(x) (nullptr)
|
||||
#endif
|
||||
|
||||
void MSA3xxComponent::process_motions_(RegMotionInterrupt old) {
|
||||
uint32_t now = millis();
|
||||
|
||||
binary_event_debounce(this->status_.motion_int.single_tap_interrupt, old.single_tap_interrupt, now,
|
||||
this->status_.last_tap_ms, this->tap_trigger_, TAP_COOLDOWN_MS,
|
||||
BS_OPTIONAL_PTR(this->tap_binary_sensor_), "Tap");
|
||||
binary_event_debounce(this->status_.motion_int.double_tap_interrupt, old.double_tap_interrupt, now,
|
||||
this->status_.last_double_tap_ms, this->double_tap_trigger_, DOUBLE_TAP_COOLDOWN_MS,
|
||||
BS_OPTIONAL_PTR(this->double_tap_binary_sensor_), "Double Tap");
|
||||
binary_event_debounce(this->status_.motion_int.active_interrupt, old.active_interrupt, now,
|
||||
this->status_.last_action_ms, this->active_trigger_, ACTIVITY_COOLDOWN_MS,
|
||||
BS_OPTIONAL_PTR(this->active_binary_sensor_), "Activity");
|
||||
|
||||
if (this->status_.motion_int.orientation_interrupt) {
|
||||
ESP_LOGVV(TAG, "Orientation changed");
|
||||
this->orientation_trigger_.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace msa3xx
|
||||
} // namespace esphome
|
311
esphome/components/msa3xx/msa3xx.h
Normal file
311
esphome/components/msa3xx/msa3xx.h
Normal file
@ -0,0 +1,311 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace msa3xx {
|
||||
|
||||
// Combined register map of MSA301 and MSA311
|
||||
// Differences
|
||||
// What | MSA301 | MSA11 |
|
||||
// - Resolution | 14-bit | 12-bit |
|
||||
//
|
||||
|
||||
// I2c address
|
||||
enum class Model : uint8_t {
|
||||
MSA301 = 0x26,
|
||||
MSA311 = 0x62,
|
||||
};
|
||||
|
||||
// Combined MSA301 and MSA311 register map
|
||||
enum class RegisterMap : uint8_t {
|
||||
SOFT_RESET = 0x00,
|
||||
PART_ID = 0x01,
|
||||
ACC_X_LSB = 0x02,
|
||||
ACC_X_MSB = 0x03,
|
||||
ACC_Y_LSB = 0x04,
|
||||
ACC_Y_MSB = 0x05,
|
||||
ACC_Z_LSB = 0x06,
|
||||
ACC_Z_MSB = 0x07,
|
||||
MOTION_INTERRUPT = 0x09,
|
||||
DATA_INTERRUPT = 0x0A,
|
||||
TAP_ACTIVE_STATUS = 0x0B,
|
||||
ORIENTATION_STATUS = 0x0C,
|
||||
RESOLUTION_RANGE_CONFIG = 0x0D,
|
||||
RANGE_RESOLUTION = 0x0F,
|
||||
ODR = 0x10,
|
||||
POWER_MODE_BANDWIDTH = 0x11,
|
||||
SWAP_POLARITY = 0x12,
|
||||
INT_SET_0 = 0x16,
|
||||
INT_SET_1 = 0x17,
|
||||
INT_MAP_0 = 0x19,
|
||||
INT_MAP_1 = 0x1A,
|
||||
INT_CONFIG = 0x20,
|
||||
INT_LATCH = 0x21,
|
||||
FREEFALL_DURATION = 0x22,
|
||||
FREEFALL_THRESHOLD = 0x23,
|
||||
FREEFALL_HYSTERESIS = 0x24,
|
||||
ACTIVE_DURATION = 0x27,
|
||||
ACTIVE_THRESHOLD = 0x28,
|
||||
TAP_DURATION = 0x2A,
|
||||
TAP_THRESHOLD = 0x2B,
|
||||
ORIENTATION_CONFIG = 0x2C,
|
||||
Z_BLOCK = 0x2D,
|
||||
OFFSET_COMP_X = 0x38,
|
||||
OFFSET_COMP_Y = 0x39,
|
||||
OFFSET_COMP_Z = 0x3A,
|
||||
};
|
||||
|
||||
enum class Range : uint8_t {
|
||||
RANGE_2G = 0b00,
|
||||
RANGE_4G = 0b01,
|
||||
RANGE_8G = 0b10,
|
||||
RANGE_16G = 0b11,
|
||||
};
|
||||
|
||||
enum class Resolution : uint8_t {
|
||||
RES_14BIT = 0b00,
|
||||
RES_12BIT = 0b01,
|
||||
RES_10BIT = 0b10,
|
||||
RES_8BIT = 0b11,
|
||||
};
|
||||
|
||||
enum class PowerMode : uint8_t {
|
||||
NORMAL = 0b00,
|
||||
LOW_POWER = 0b01,
|
||||
SUSPEND = 0b11,
|
||||
};
|
||||
|
||||
enum class Bandwidth : uint8_t {
|
||||
BW_1_95HZ = 0b0000,
|
||||
BW_3_9HZ = 0b0011,
|
||||
BW_7_81HZ = 0b0100,
|
||||
BW_15_63HZ = 0b0101,
|
||||
BW_31_25HZ = 0b0110,
|
||||
BW_62_5HZ = 0b0111,
|
||||
BW_125HZ = 0b1000,
|
||||
BW_250HZ = 0b1001,
|
||||
BW_500HZ = 0b1010,
|
||||
};
|
||||
|
||||
enum class DataRate : uint8_t {
|
||||
ODR_1HZ = 0b0000, // not available in normal mode
|
||||
ODR_1_95HZ = 0b0001, // not available in normal mode
|
||||
ODR_3_9HZ = 0b0010,
|
||||
ODR_7_81HZ = 0b0011,
|
||||
ODR_15_63HZ = 0b0100,
|
||||
ODR_31_25HZ = 0b0101,
|
||||
ODR_62_5HZ = 0b0110,
|
||||
ODR_125HZ = 0b0111,
|
||||
ODR_250HZ = 0b1000,
|
||||
ODR_500HZ = 0b1001, // not available in low power mode
|
||||
ODR_1000HZ = 0b1010, // not available in low power mode
|
||||
};
|
||||
|
||||
enum class OrientationXY : uint8_t {
|
||||
PORTRAIT_UPRIGHT = 0b00,
|
||||
PORTRAIT_UPSIDE_DOWN = 0b01,
|
||||
LANDSCAPE_LEFT = 0b10,
|
||||
LANDSCAPE_RIGHT = 0b11,
|
||||
};
|
||||
|
||||
union Orientation {
|
||||
struct {
|
||||
OrientationXY xy : 2;
|
||||
bool z : 1;
|
||||
uint8_t reserved : 5;
|
||||
} __attribute__((packed));
|
||||
uint8_t raw;
|
||||
};
|
||||
|
||||
// 0x09
|
||||
union RegMotionInterrupt {
|
||||
struct {
|
||||
bool freefall_interrupt : 1;
|
||||
bool reserved_1 : 1;
|
||||
bool active_interrupt : 1;
|
||||
bool reserved_3 : 1;
|
||||
bool double_tap_interrupt : 1;
|
||||
bool single_tap_interrupt : 1;
|
||||
bool orientation_interrupt : 1;
|
||||
bool reserved_7 : 1;
|
||||
} __attribute__((packed));
|
||||
uint8_t raw;
|
||||
};
|
||||
|
||||
// 0x0C
|
||||
union RegOrientationStatus {
|
||||
struct {
|
||||
uint8_t reserved_0_3 : 4;
|
||||
OrientationXY orient_xy : 2;
|
||||
bool orient_z : 1;
|
||||
uint8_t reserved_7 : 1;
|
||||
} __attribute__((packed));
|
||||
uint8_t raw{0x00};
|
||||
};
|
||||
|
||||
// 0x0f
|
||||
union RegRangeResolution {
|
||||
struct {
|
||||
Range range : 2;
|
||||
Resolution resolution : 2;
|
||||
uint8_t reserved_2 : 4;
|
||||
} __attribute__((packed));
|
||||
uint8_t raw{0x00};
|
||||
};
|
||||
|
||||
// 0x10
|
||||
union RegOutputDataRate {
|
||||
struct {
|
||||
DataRate odr : 4;
|
||||
uint8_t reserved_4 : 1;
|
||||
bool z_axis_disable : 1;
|
||||
bool y_axis_disable : 1;
|
||||
bool x_axis_disable : 1;
|
||||
} __attribute__((packed));
|
||||
uint8_t raw{0xde};
|
||||
};
|
||||
|
||||
// 0x11
|
||||
union RegPowerModeBandwidth {
|
||||
struct {
|
||||
uint8_t reserved_0 : 1;
|
||||
Bandwidth low_power_bandwidth : 4;
|
||||
uint8_t reserved_5 : 1;
|
||||
PowerMode power_mode : 2;
|
||||
} __attribute__((packed));
|
||||
uint8_t raw{0xde};
|
||||
};
|
||||
|
||||
// 0x12
|
||||
union RegSwapPolarity {
|
||||
struct {
|
||||
bool x_y_swap : 1;
|
||||
bool z_polarity : 1;
|
||||
bool y_polarity : 1;
|
||||
bool x_polarity : 1;
|
||||
uint8_t reserved : 4;
|
||||
} __attribute__((packed));
|
||||
uint8_t raw{0};
|
||||
};
|
||||
|
||||
// 0x2a
|
||||
union RegTapDuration {
|
||||
struct {
|
||||
uint8_t duration : 3;
|
||||
uint8_t reserved : 3;
|
||||
bool tap_shock : 1;
|
||||
bool tap_quiet : 1;
|
||||
} __attribute__((packed));
|
||||
uint8_t raw{0x04};
|
||||
};
|
||||
|
||||
class MSA3xxComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void loop() override;
|
||||
void update() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void set_model(Model model) { this->model_ = model; }
|
||||
void set_offset(float offset_x, float offset_y, float offset_z);
|
||||
void set_range(Range range) { this->range_ = range; }
|
||||
void set_bandwidth(Bandwidth bandwidth) { this->bandwidth_ = bandwidth; }
|
||||
void set_resolution(Resolution resolution) { this->resolution_ = resolution; }
|
||||
void set_transform(bool mirror_x, bool mirror_y, bool mirror_z, bool swap_xy);
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
SUB_BINARY_SENSOR(tap)
|
||||
SUB_BINARY_SENSOR(double_tap)
|
||||
SUB_BINARY_SENSOR(active)
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
SUB_SENSOR(acceleration_x)
|
||||
SUB_SENSOR(acceleration_y)
|
||||
SUB_SENSOR(acceleration_z)
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
SUB_TEXT_SENSOR(orientation_xy)
|
||||
SUB_TEXT_SENSOR(orientation_z)
|
||||
#endif
|
||||
|
||||
Trigger<> *get_tap_trigger() { return &this->tap_trigger_; }
|
||||
Trigger<> *get_double_tap_trigger() { return &this->double_tap_trigger_; }
|
||||
Trigger<> *get_orientation_trigger() { return &this->orientation_trigger_; }
|
||||
Trigger<> *get_freefall_trigger() { return &this->freefall_trigger_; }
|
||||
Trigger<> *get_active_trigger() { return &this->active_trigger_; }
|
||||
|
||||
protected:
|
||||
Model model_{Model::MSA311};
|
||||
|
||||
PowerMode power_mode_{PowerMode::NORMAL};
|
||||
DataRate data_rate_{DataRate::ODR_250HZ};
|
||||
Bandwidth bandwidth_{Bandwidth::BW_250HZ};
|
||||
Range range_{Range::RANGE_2G};
|
||||
Resolution resolution_{Resolution::RES_14BIT};
|
||||
float offset_x_, offset_y_, offset_z_; // in m/s²
|
||||
RegSwapPolarity swap_;
|
||||
|
||||
struct {
|
||||
int scale_factor_exp;
|
||||
uint8_t accel_data_width;
|
||||
} device_params_{};
|
||||
|
||||
struct {
|
||||
int16_t lsb_x, lsb_y, lsb_z;
|
||||
float x, y, z;
|
||||
} data_{};
|
||||
|
||||
struct {
|
||||
RegMotionInterrupt motion_int;
|
||||
RegOrientationStatus orientation;
|
||||
RegOrientationStatus orientation_old;
|
||||
|
||||
uint32_t last_tap_ms{0};
|
||||
uint32_t last_double_tap_ms{0};
|
||||
uint32_t last_action_ms{0};
|
||||
|
||||
bool never_published{true};
|
||||
} status_{};
|
||||
|
||||
void setup_odr_(DataRate rate);
|
||||
void setup_power_mode_bandwidth_(PowerMode power_mode, Bandwidth bandwidth);
|
||||
void setup_range_resolution_(Range range, Resolution resolution);
|
||||
void setup_offset_(float offset_x, float offset_y, float offset_z);
|
||||
|
||||
bool read_data_();
|
||||
bool read_motion_status_();
|
||||
|
||||
int64_t twos_complement_(uint64_t value, uint8_t bits);
|
||||
|
||||
//
|
||||
// Actons / Triggers
|
||||
//
|
||||
Trigger<> tap_trigger_;
|
||||
Trigger<> double_tap_trigger_;
|
||||
Trigger<> orientation_trigger_;
|
||||
Trigger<> freefall_trigger_;
|
||||
Trigger<> active_trigger_;
|
||||
|
||||
void process_motions_(RegMotionInterrupt old);
|
||||
};
|
||||
|
||||
} // namespace msa3xx
|
||||
} // namespace esphome
|
42
esphome/components/msa3xx/sensor.py
Normal file
42
esphome/components/msa3xx/sensor.py
Normal file
@ -0,0 +1,42 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACCELERATION_X,
|
||||
CONF_ACCELERATION_Y,
|
||||
CONF_ACCELERATION_Z,
|
||||
CONF_NAME,
|
||||
ICON_BRIEFCASE_DOWNLOAD,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_METER_PER_SECOND_SQUARED,
|
||||
)
|
||||
|
||||
from . import CONF_MSA3XX_ID, MSA_SENSOR_SCHEMA
|
||||
|
||||
CODEOWNERS = ["@latonita"]
|
||||
DEPENDENCIES = ["msa3xx"]
|
||||
|
||||
ACCELERATION_SENSORS = (CONF_ACCELERATION_X, CONF_ACCELERATION_Y, CONF_ACCELERATION_Z)
|
||||
|
||||
accel_schema = cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_METER_PER_SECOND_SQUARED,
|
||||
icon=ICON_BRIEFCASE_DOWNLOAD,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = MSA_SENSOR_SCHEMA.extend(
|
||||
{cv.Optional(sensor): accel_schema for sensor in ACCELERATION_SENSORS}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_MSA3XX_ID])
|
||||
for accel_key in ACCELERATION_SENSORS:
|
||||
if accel_key in config:
|
||||
sens = await sensor.new_sensor(config[accel_key])
|
||||
cg.add(getattr(hub, f"set_{accel_key}_sensor")(sens))
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user