Compare commits
202 Commits
2021.10.2
...
bump-zeroc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f273de2404 | ||
|
|
ceb9b1d1ff | ||
|
|
ccfa1e23f0 | ||
|
|
290da8df2d | ||
|
|
4b1d73791d | ||
|
|
7e8012c1a0 | ||
|
|
15cd602e8b | ||
|
|
598f5b241f | ||
|
|
335e69e6cd | ||
|
|
05fe5db030 | ||
|
|
710096b1c6 | ||
|
|
07b882c801 | ||
|
|
3e5331a263 | ||
|
|
897277992b | ||
|
|
1424091ee5 | ||
|
|
61ec16cdfc | ||
|
|
e5cb5756aa | ||
|
|
9e1c3e8f01 | ||
|
|
448e1690aa | ||
|
|
8267f01ccd | ||
|
|
6f9439e1bc | ||
|
|
06994c0dfc | ||
|
|
dee5d639e2 | ||
|
|
df6730be55 | ||
|
|
6c1ef398bb | ||
|
|
0469e19f54 | ||
|
|
dbcfa7b599 | ||
|
|
df68403b6d | ||
|
|
57bdc2b885 | ||
|
|
f565ff5def | ||
|
|
8ece639987 | ||
|
|
b35f509784 | ||
|
|
f1954df573 | ||
|
|
9e4fa5dcf1 | ||
|
|
0809673ba9 | ||
|
|
b386284180 | ||
|
|
515519bc87 | ||
|
|
5404163be0 | ||
|
|
0b193eee43 | ||
|
|
7333123ba4 | ||
|
|
d99c5ed890 | ||
|
|
04740fbcbb | ||
|
|
6a7440f7d3 | ||
|
|
14299bb2cc | ||
|
|
66cebfc992 | ||
|
|
108b8e6705 | ||
|
|
4eaa6afa4d | ||
|
|
f643a46bbf | ||
|
|
aae63a7ff3 | ||
|
|
582567696e | ||
|
|
2e0c89409d | ||
|
|
7bb7456a8b | ||
|
|
0372e12b81 | ||
|
|
a6873c1520 | ||
|
|
f11220da3a | ||
|
|
bb9793d5b7 | ||
|
|
e99af991ec | ||
|
|
abf3708cc2 | ||
|
|
4395d6156d | ||
|
|
04ba53c870 | ||
|
|
f310cacd41 | ||
|
|
5ff7c8418c | ||
|
|
0bdb48bcac | ||
|
|
99c775d8cb | ||
|
|
4d43396835 | ||
|
|
92321e219a | ||
|
|
c422b2fb0b | ||
|
|
8aa72f4c1e | ||
|
|
d8e33c5a69 | ||
|
|
15f9677d33 | ||
|
|
219b225ac0 | ||
|
|
2ac232e634 | ||
|
|
710866ff4e | ||
|
|
662773b075 | ||
|
|
875b803483 | ||
|
|
6e5cfac927 | ||
|
|
97eaf3d4a1 | ||
|
|
366552a969 | ||
|
|
57b07441a1 | ||
|
|
fb57ab0add | ||
|
|
d6717c0032 | ||
|
|
f72389147d | ||
|
|
a509f6ccd2 | ||
|
|
add484a2ea | ||
|
|
a17a6d5346 | ||
|
|
be9439f10d | ||
|
|
96a50f5c6b | ||
|
|
3c0414c420 | ||
|
|
b450d4c734 | ||
|
|
d536509a63 | ||
|
|
11f1e28139 | ||
|
|
379c3e98f5 | ||
|
|
5ea77894b7 | ||
|
|
d54b4e7c44 | ||
|
|
d8b3af3815 | ||
|
|
2b04152482 | ||
|
|
331a3ac387 | ||
|
|
7eee3cdc7f | ||
|
|
b12c7432e0 | ||
|
|
696643d037 | ||
|
|
7e54f97003 | ||
|
|
77dbf84e55 | ||
|
|
2350c5054c | ||
|
|
73accf747f | ||
|
|
0d3e6b2c4c | ||
|
|
b3d7cc637b | ||
|
|
2147bcbc29 | ||
|
|
980c2d4cae | ||
|
|
68316cbcf9 | ||
|
|
2f4b9263c3 | ||
|
|
c2623a08e3 | ||
|
|
9f625ee7d1 | ||
|
|
2f85c27a05 | ||
|
|
c612a3bf60 | ||
|
|
a01f5f5cf1 | ||
|
|
87328686a0 | ||
|
|
81c11ba1f7 | ||
|
|
49b17c5a2d | ||
|
|
de06a781ff | ||
|
|
8e77e3c685 | ||
|
|
a687b083ae | ||
|
|
b9e5c7eb35 | ||
|
|
1a6a063e04 | ||
|
|
d85b7a6bd0 | ||
|
|
1c4700f447 | ||
|
|
83400d0417 | ||
|
|
77a6461c9d | ||
|
|
6db9d1122f | ||
|
|
83bef85415 | ||
|
|
b5b3914bbf | ||
|
|
0d90ef94ae | ||
|
|
c08b21b7cd | ||
|
|
be3cb9ef00 | ||
|
|
f7b3f52731 | ||
|
|
9220d9fc52 | ||
|
|
68c8547067 | ||
|
|
1468acfced | ||
|
|
b141aea4c0 | ||
|
|
a88c022406 | ||
|
|
5389382798 | ||
|
|
4765173778 | ||
|
|
07a9cb910f | ||
|
|
f408f074c4 | ||
|
|
f1f2640d0e | ||
|
|
27d7d7ca69 | ||
|
|
c0fc5b48ae | ||
|
|
8735d3b83e | ||
|
|
ca59dd1302 | ||
|
|
eccdef8211 | ||
|
|
f2ebfe7aef | ||
|
|
cac5b356db | ||
|
|
156104d5f5 | ||
|
|
c248ba4043 | ||
|
|
c615dc573a | ||
|
|
1caabb6419 | ||
|
|
f41f7994a3 | ||
|
|
e39f314e7a | ||
|
|
7f34561e53 | ||
|
|
34606b0f1f | ||
|
|
c51b509501 | ||
|
|
15b5968418 | ||
|
|
64a45dc6a6 | ||
|
|
7cfede5b83 | ||
|
|
e4d17e0b15 | ||
|
|
e79f7ce290 | ||
|
|
bcc77c73e1 | ||
|
|
8b11e5aeb1 | ||
|
|
cb48394e8a | ||
|
|
f5441a87e3 | ||
|
|
e2a812fa4b | ||
|
|
5b5ead872b | ||
|
|
03cfd78c59 | ||
|
|
ced11bc707 | ||
|
|
6b9c084162 | ||
|
|
644ce2a26c | ||
|
|
5425e45851 | ||
|
|
12fce7a08d | ||
|
|
0991ab3543 | ||
|
|
65d2b37496 | ||
|
|
94d518a418 | ||
|
|
7cca673902 | ||
|
|
384f8d97d8 | ||
|
|
c82d5d63e3 | ||
|
|
653a3d5d11 | ||
|
|
884b7201de | ||
|
|
85d2f24447 | ||
|
|
935992bcb3 | ||
|
|
dc15d1c8ec | ||
|
|
6beb9e568a | ||
|
|
7178f10bda | ||
|
|
1308236429 | ||
|
|
63d6b610b8 | ||
|
|
8823024509 | ||
|
|
4896f870f0 | ||
|
|
7e482901d9 | ||
|
|
07b309e65d | ||
|
|
6bbb5e9b56 | ||
|
|
867fecd157 | ||
|
|
05388d2dfc | ||
|
|
e06b6d7140 | ||
|
|
859e508392 | ||
|
|
534ce11d54 |
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -16,6 +16,7 @@ Quick description and explanation of changes
|
||||
## Test Environment
|
||||
|
||||
- [ ] ESP32
|
||||
- [ ] ESP32 IDF
|
||||
- [ ] ESP8266
|
||||
|
||||
## Example entry for `config.yaml`:
|
||||
|
||||
7
.github/issue-close-app.yml
vendored
7
.github/issue-close-app.yml
vendored
@@ -1,7 +0,0 @@
|
||||
comment: >-
|
||||
https://github.com/esphome/esphome/issues/430
|
||||
issueConfigs:
|
||||
- content:
|
||||
- "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY"
|
||||
|
||||
caseInsensitive: false
|
||||
36
.github/lock.yml
vendored
36
.github/lock.yml
vendored
@@ -1,36 +0,0 @@
|
||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
||||
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 7
|
||||
|
||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||
skipCreatedBefore: false
|
||||
|
||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- keep-open
|
||||
|
||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||
lockLabel: false
|
||||
|
||||
# Comment to post before locking. Set to `false` to disable
|
||||
lockComment: false
|
||||
|
||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||
setLockReason: false
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Optionally, specify configuration settings just for `issues` or `pulls`
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - help-wanted
|
||||
# lockLabel: outdated
|
||||
|
||||
# pulls:
|
||||
# daysUntilLock: 30
|
||||
|
||||
# Repository to extend settings from
|
||||
# _extends: repo
|
||||
4
.github/workflows/ci-docker.yml
vendored
4
.github/workflows/ci-docker.yml
vendored
@@ -17,6 +17,10 @@ on:
|
||||
- 'requirements*.txt'
|
||||
- 'platformio.ini'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
|
||||
jobs:
|
||||
check-docker:
|
||||
name: Build docker containers
|
||||
|
||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -8,6 +8,9 @@ on:
|
||||
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: ${{ matrix.name }}
|
||||
|
||||
14
.github/workflows/lock.yml
vendored
14
.github/workflows/lock.yml
vendored
@@ -9,13 +9,19 @@ permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: lock
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2
|
||||
- uses: dessant/lock-threads@v3
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
pr-lock-inactive-days: "1"
|
||||
pr-inactive-days: "1"
|
||||
pr-lock-reason: ""
|
||||
process-only: prs
|
||||
exclude-any-pr-labels: keep-open
|
||||
|
||||
issue-inactive-days: "7"
|
||||
issue-lock-reason: ""
|
||||
exclude-any-issue-labels: keep-open
|
||||
|
||||
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@@ -7,6 +7,9 @@ on:
|
||||
schedule:
|
||||
- cron: "0 2 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
init:
|
||||
name: Initialize build
|
||||
@@ -52,6 +55,9 @@ jobs:
|
||||
deploy-docker:
|
||||
name: Build and publish docker containers
|
||||
if: github.repository == 'esphome/esphome'
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
needs: [init]
|
||||
strategy:
|
||||
@@ -93,6 +99,9 @@ jobs:
|
||||
|
||||
deploy-docker-manifest:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
needs: [init, deploy-docker]
|
||||
strategy:
|
||||
|
||||
20
.github/workflows/stale.yml
vendored
20
.github/workflows/stale.yml
vendored
@@ -9,13 +9,15 @@ permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: lock
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v4
|
||||
with:
|
||||
repo-token: ${{ github.token }}
|
||||
days-before-pr-stale: 90
|
||||
days-before-pr-close: 7
|
||||
days-before-issue-stale: -1
|
||||
@@ -28,3 +30,19 @@ jobs:
|
||||
pull request has been automatically marked as stale because of that
|
||||
and will be closed if no further activity occurs within 7 days.
|
||||
Thank you for your contributions.
|
||||
|
||||
# Use stale to automatically close issues with a reference to the issue tracker
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v4
|
||||
with:
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
days-before-issue-stale: 1
|
||||
days-before-issue-close: 1
|
||||
remove-stale-when-updated: true
|
||||
stale-issue-label: "stale"
|
||||
exempt-issue-labels: "not-stale"
|
||||
stale-issue-message: >
|
||||
https://github.com/esphome/esphome/issues/430
|
||||
|
||||
@@ -31,6 +31,7 @@ esphome/components/binary_sensor/* @esphome/core
|
||||
esphome/components/ble_client/* @buxtronix
|
||||
esphome/components/bme680_bsec/* @trvrnrth
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/cap1188/* @MrEditor97
|
||||
esphome/components/captive_portal/* @OttoWinter
|
||||
esphome/components/ccs811/* @habbie
|
||||
esphome/components/climate/* @esphome/core
|
||||
@@ -39,6 +40,7 @@ esphome/components/color_temperature/* @jesserockz
|
||||
esphome/components/coolix/* @glmnet
|
||||
esphome/components/cover/* @esphome/core
|
||||
esphome/components/cs5460a/* @balrog-kun
|
||||
esphome/components/cse7761/* @berfenger
|
||||
esphome/components/ct_clamp/* @jesserockz
|
||||
esphome/components/current_based/* @djwmarcx
|
||||
esphome/components/daly_bms/* @s1lvi0
|
||||
@@ -51,6 +53,7 @@ esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/esp32/* @esphome/core
|
||||
esphome/components/esp32_ble/* @jesserockz
|
||||
esphome/components/esp32_ble_server/* @jesserockz
|
||||
esphome/components/esp32_camera_web_server/* @ayufan
|
||||
esphome/components/esp32_improv/* @jesserockz
|
||||
esphome/components/esp8266/* @esphome/core
|
||||
esphome/components/exposure_notifications/* @OttoWinter
|
||||
@@ -69,7 +72,7 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||
esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/improv/* @jesserockz
|
||||
esphome/components/improv_serial/* @esphome/core
|
||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||
esphome/components/inkplate6/* @jesserockz
|
||||
esphome/components/integration/* @OttoWinter
|
||||
@@ -89,6 +92,7 @@ esphome/components/mcp23x17_base/* @jesserockz
|
||||
esphome/components/mcp23xxx_base/* @jesserockz
|
||||
esphome/components/mcp2515/* @danielschramm @mvturnho
|
||||
esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/md5/* @esphome/core
|
||||
esphome/components/mdns/* @esphome/core
|
||||
esphome/components/midea/* @dudanov
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
# One of "docker", "hassio"
|
||||
ARG BASEIMGTYPE=docker
|
||||
|
||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.0 AS base-hassio-amd64
|
||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.0 AS base-hassio-arm64
|
||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7
|
||||
FROM debian:bullseye-20210902-slim AS base-docker-amd64
|
||||
FROM debian:bullseye-20210902-slim AS base-docker-arm64
|
||||
FROM debian:bullseye-20210902-slim AS base-docker-armv7
|
||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.1 AS base-hassio-amd64
|
||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.1 AS base-hassio-arm64
|
||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.1 AS base-hassio-armv7
|
||||
FROM debian:bullseye-20211011-slim AS base-docker-amd64
|
||||
FROM debian:bullseye-20211011-slim AS base-docker-arm64
|
||||
FROM debian:bullseye-20211011-slim AS base-docker-armv7
|
||||
|
||||
# Use TARGETARCH/TARGETVARIANT defined by docker
|
||||
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
|
||||
@@ -43,7 +43,7 @@ RUN \
|
||||
# Ubuntu python3-pip is missing wheel
|
||||
pip3 install --no-cache-dir \
|
||||
wheel==0.36.2 \
|
||||
platformio==5.2.0 \
|
||||
platformio==5.2.2 \
|
||||
# Change some platformio settings
|
||||
&& platformio settings set enable_telemetry No \
|
||||
&& platformio settings set check_libraries_interval 1000000 \
|
||||
@@ -147,9 +147,9 @@ RUN \
|
||||
/var/{cache,log}/* \
|
||||
/var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
|
||||
COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
|
||||
VOLUME ["/esphome"]
|
||||
|
||||
@@ -8,6 +8,23 @@ import sys
|
||||
|
||||
config = configparser.ConfigParser(inline_comment_prefixes=(';', ))
|
||||
config.read(sys.argv[1])
|
||||
libs = [x for x in config['common']['lib_deps'].splitlines() if len(x) != 0]
|
||||
|
||||
libs = []
|
||||
# Extract from every lib_deps key in all sections
|
||||
for section in config.sections():
|
||||
conf = config[section]
|
||||
if "lib_deps" not in conf:
|
||||
continue
|
||||
for lib_dep in conf["lib_deps"].splitlines():
|
||||
if not lib_dep:
|
||||
# Empty line or comment
|
||||
continue
|
||||
if lib_dep.startswith("${"):
|
||||
# Extending from another section
|
||||
continue
|
||||
if "@" not in lib_dep:
|
||||
# No version pinned, this is an internal lib
|
||||
continue
|
||||
libs.append(lib_dep)
|
||||
|
||||
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])
|
||||
|
||||
@@ -18,6 +18,7 @@ from esphome.const import (
|
||||
CONF_PORT,
|
||||
CONF_ESPHOME,
|
||||
CONF_PLATFORMIO_OPTIONS,
|
||||
SECRETS_FILES,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError, coroutine
|
||||
from esphome.helpers import indent
|
||||
@@ -180,7 +181,11 @@ def compile_program(args, config):
|
||||
from esphome import platformio_api
|
||||
|
||||
_LOGGER.info("Compiling app...")
|
||||
return platformio_api.run_compile(config, CORE.verbose)
|
||||
rc = platformio_api.run_compile(config, CORE.verbose)
|
||||
if rc != 0:
|
||||
return rc
|
||||
idedata = platformio_api.get_idedata(config)
|
||||
return 0 if idedata is not None else 1
|
||||
|
||||
|
||||
def upload_using_esptool(config, port):
|
||||
@@ -196,8 +201,7 @@ def upload_using_esptool(config, port):
|
||||
firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
|
||||
flash_images = [
|
||||
platformio_api.FlashImage(
|
||||
path=idedata.firmware_bin_path,
|
||||
offset=firmware_offset,
|
||||
path=idedata.firmware_bin_path, offset=firmware_offset
|
||||
),
|
||||
*idedata.extra_flash_images,
|
||||
]
|
||||
@@ -222,6 +226,8 @@ def upload_using_esptool(config, port):
|
||||
mcu,
|
||||
"write_flash",
|
||||
"-z",
|
||||
"--flash_size",
|
||||
"detect",
|
||||
]
|
||||
for img in flash_images:
|
||||
cmd += [img.offset, img.path]
|
||||
@@ -458,6 +464,21 @@ def command_update_all(args):
|
||||
return failed
|
||||
|
||||
|
||||
def command_idedata(args, config):
|
||||
from esphome import platformio_api
|
||||
import json
|
||||
|
||||
logging.disable(logging.INFO)
|
||||
logging.disable(logging.WARNING)
|
||||
|
||||
idedata = platformio_api.get_idedata(config)
|
||||
if idedata is None:
|
||||
return 1
|
||||
|
||||
print(json.dumps(idedata.raw, indent=2) + "\n")
|
||||
return 0
|
||||
|
||||
|
||||
PRE_CONFIG_ACTIONS = {
|
||||
"wizard": command_wizard,
|
||||
"version": command_version,
|
||||
@@ -475,6 +496,7 @@ POST_CONFIG_ACTIONS = {
|
||||
"clean-mqtt": command_clean_mqtt,
|
||||
"mqtt-fingerprint": command_mqtt_fingerprint,
|
||||
"clean": command_clean,
|
||||
"idedata": command_idedata,
|
||||
}
|
||||
|
||||
|
||||
@@ -585,10 +607,7 @@ def parse_args(argv):
|
||||
"wizard",
|
||||
help="A helpful setup wizard that will guide you through setting up ESPHome.",
|
||||
)
|
||||
parser_wizard.add_argument(
|
||||
"configuration",
|
||||
help="Your YAML configuration file.",
|
||||
)
|
||||
parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
|
||||
|
||||
parser_fingerprint = subparsers.add_parser(
|
||||
"mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
|
||||
@@ -610,8 +629,7 @@ def parse_args(argv):
|
||||
"dashboard", help="Create a simple web server for a dashboard."
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"configuration",
|
||||
help="Your YAML configuration file directory.",
|
||||
"configuration", help="Your YAML configuration file directory."
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--port",
|
||||
@@ -619,6 +637,12 @@ def parse_args(argv):
|
||||
type=int,
|
||||
default=6052,
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--address",
|
||||
help="The address to bind to.",
|
||||
type=str,
|
||||
default="0.0.0.0",
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--username",
|
||||
help="The optional username to require for authentication.",
|
||||
@@ -650,6 +674,11 @@ def parse_args(argv):
|
||||
"configuration", help="Your YAML configuration file directories.", nargs="+"
|
||||
)
|
||||
|
||||
parser_idedata = subparsers.add_parser("idedata")
|
||||
parser_idedata.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs=1
|
||||
)
|
||||
|
||||
# Keep backward compatibility with the old command line format of
|
||||
# esphome <config> <command>.
|
||||
#
|
||||
@@ -733,7 +762,12 @@ def run_esphome(argv):
|
||||
args = parse_args(argv)
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
setup_log(args.verbose, args.quiet)
|
||||
setup_log(
|
||||
args.verbose,
|
||||
args.quiet,
|
||||
# Show timestamp for dashboard access logs
|
||||
args.command == "dashboard",
|
||||
)
|
||||
if args.deprecated_argv_suggestion is not None and args.command != "vscode":
|
||||
_LOGGER.warning(
|
||||
"Calling ESPHome with the configuration before the command is deprecated "
|
||||
@@ -757,12 +791,16 @@ def run_esphome(argv):
|
||||
return 1
|
||||
|
||||
for conf_path in args.configuration:
|
||||
if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
|
||||
_LOGGER.warning("Skipping secrets file %s", conf_path)
|
||||
continue
|
||||
|
||||
CORE.config_path = conf_path
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
config = read_config(dict(args.substitution) if args.substitution else {})
|
||||
if config is None:
|
||||
return 1
|
||||
return 2
|
||||
CORE.config = config
|
||||
|
||||
if args.command not in POST_CONFIG_ACTIONS:
|
||||
|
||||
@@ -3,6 +3,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_AUTOMATION_ID,
|
||||
CONF_CONDITION,
|
||||
CONF_COUNT,
|
||||
CONF_ELSE,
|
||||
CONF_ID,
|
||||
CONF_THEN,
|
||||
@@ -66,6 +67,7 @@ DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
|
||||
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
|
||||
IfAction = cg.esphome_ns.class_("IfAction", Action)
|
||||
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
|
||||
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
|
||||
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
|
||||
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
|
||||
Automation = cg.esphome_ns.class_("Automation")
|
||||
@@ -241,6 +243,25 @@ async def while_action_to_code(config, action_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
@register_action(
|
||||
"repeat",
|
||||
RepeatAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_COUNT): cv.templatable(cv.positive_not_null_int),
|
||||
cv.Required(CONF_THEN): validate_action_list,
|
||||
}
|
||||
),
|
||||
)
|
||||
async def repeat_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
|
||||
cg.add(var.set_count(count_template))
|
||||
actions = await build_action_list(config[CONF_THEN], template_arg, args)
|
||||
cg.add(var.add_then(actions))
|
||||
return var
|
||||
|
||||
|
||||
def validate_wait_until(value):
|
||||
schema = cv.Schema(
|
||||
{
|
||||
|
||||
@@ -81,4 +81,5 @@ from esphome.cpp_types import ( # noqa
|
||||
GPIOPin,
|
||||
InternalGPIOPin,
|
||||
gpio_Flags,
|
||||
EntityCategory,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "adc_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
@@ -15,50 +16,6 @@ namespace adc {
|
||||
|
||||
static const char *const TAG = "adc";
|
||||
|
||||
#ifdef USE_ESP32
|
||||
void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; }
|
||||
|
||||
inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
switch (pin) {
|
||||
case 36:
|
||||
return ADC1_CHANNEL_0;
|
||||
case 37:
|
||||
return ADC1_CHANNEL_1;
|
||||
case 38:
|
||||
return ADC1_CHANNEL_2;
|
||||
case 39:
|
||||
return ADC1_CHANNEL_3;
|
||||
case 32:
|
||||
return ADC1_CHANNEL_4;
|
||||
case 33:
|
||||
return ADC1_CHANNEL_5;
|
||||
case 34:
|
||||
return ADC1_CHANNEL_6;
|
||||
case 35:
|
||||
return ADC1_CHANNEL_7;
|
||||
default:
|
||||
return ADC1_CHANNEL_MAX;
|
||||
}
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
|
||||
switch (pin) {
|
||||
case 0:
|
||||
return ADC1_CHANNEL_0;
|
||||
case 1:
|
||||
return ADC1_CHANNEL_1;
|
||||
case 2:
|
||||
return ADC1_CHANNEL_2;
|
||||
case 3:
|
||||
return ADC1_CHANNEL_3;
|
||||
case 4:
|
||||
return ADC1_CHANNEL_4;
|
||||
default:
|
||||
return ADC1_CHANNEL_MAX;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
void ADCSensor::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
|
||||
#ifndef USE_ADC_SENSOR_VCC
|
||||
@@ -66,13 +23,36 @@ void ADCSensor::setup() {
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32
|
||||
adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_);
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2
|
||||
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_->get_pin()));
|
||||
#endif
|
||||
if (!autorange_) {
|
||||
adc1_config_channel_atten(channel_, attenuation_);
|
||||
}
|
||||
|
||||
// load characteristics for each attenuation
|
||||
for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) {
|
||||
auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_BIT_12,
|
||||
1100, // default vref
|
||||
&cal_characteristics_[i]);
|
||||
switch (cal_value) {
|
||||
case ESP_ADC_CAL_VAL_EFUSE_VREF:
|
||||
ESP_LOGV(TAG, "Using eFuse Vref for calibration");
|
||||
break;
|
||||
case ESP_ADC_CAL_VAL_EFUSE_TP:
|
||||
ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration");
|
||||
break;
|
||||
case ESP_ADC_CAL_VAL_DEFAULT_VREF:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// adc_gpio_init doesn't exist on ESP32-C3 or ESP32-H2
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_);
|
||||
#endif
|
||||
#endif // USE_ESP32
|
||||
}
|
||||
|
||||
void ADCSensor::dump_config() {
|
||||
LOG_SENSOR("", "ADC Sensor", this);
|
||||
#ifdef USE_ESP8266
|
||||
@@ -81,84 +61,107 @@ void ADCSensor::dump_config() {
|
||||
#else
|
||||
LOG_PIN(" Pin: ", pin_);
|
||||
#endif
|
||||
#endif
|
||||
#endif // USE_ESP8266
|
||||
|
||||
#ifdef USE_ESP32
|
||||
LOG_PIN(" Pin: ", pin_);
|
||||
switch (this->attenuation_) {
|
||||
case ADC_ATTEN_DB_0:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
|
||||
break;
|
||||
case ADC_ATTEN_DB_2_5:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
|
||||
break;
|
||||
case ADC_ATTEN_DB_6:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
|
||||
break;
|
||||
case ADC_ATTEN_DB_11:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
|
||||
break;
|
||||
default: // This is to satisfy the unused ADC_ATTEN_MAX
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
if (autorange_)
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: auto");
|
||||
else
|
||||
switch (this->attenuation_) {
|
||||
case ADC_ATTEN_DB_0:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
|
||||
break;
|
||||
case ADC_ATTEN_DB_2_5:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
|
||||
break;
|
||||
case ADC_ATTEN_DB_6:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
|
||||
break;
|
||||
case ADC_ATTEN_DB_11:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
|
||||
break;
|
||||
default: // This is to satisfy the unused ADC_ATTEN_MAX
|
||||
break;
|
||||
}
|
||||
#endif // USE_ESP32
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void ADCSensor::update() {
|
||||
float value_v = this->sample();
|
||||
ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v);
|
||||
ESP_LOGD(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
|
||||
this->publish_state(value_v);
|
||||
}
|
||||
float ADCSensor::sample() {
|
||||
#ifdef USE_ESP32
|
||||
int raw = adc1_get_raw(gpio_to_adc1(pin_->get_pin()));
|
||||
float value_v = raw / 4095.0f;
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
switch (this->attenuation_) {
|
||||
case ADC_ATTEN_DB_0:
|
||||
value_v *= 1.1;
|
||||
break;
|
||||
case ADC_ATTEN_DB_2_5:
|
||||
value_v *= 1.5;
|
||||
break;
|
||||
case ADC_ATTEN_DB_6:
|
||||
value_v *= 2.2;
|
||||
break;
|
||||
case ADC_ATTEN_DB_11:
|
||||
value_v *= 3.9;
|
||||
break;
|
||||
default: // This is to satisfy the unused ADC_ATTEN_MAX
|
||||
break;
|
||||
}
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
|
||||
switch (this->attenuation_) {
|
||||
case ADC_ATTEN_DB_0:
|
||||
value_v *= 0.84;
|
||||
break;
|
||||
case ADC_ATTEN_DB_2_5:
|
||||
value_v *= 1.13;
|
||||
break;
|
||||
case ADC_ATTEN_DB_6:
|
||||
value_v *= 1.56;
|
||||
break;
|
||||
case ADC_ATTEN_DB_11:
|
||||
value_v *= 3.0;
|
||||
break;
|
||||
default: // This is to satisfy the unused ADC_ATTEN_MAX
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
return value_v;
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
float ADCSensor::sample() {
|
||||
#ifdef USE_ADC_SENSOR_VCC
|
||||
return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance)
|
||||
int raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
|
||||
#else
|
||||
return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT
|
||||
#endif
|
||||
int raw = analogRead(this->pin_->get_pin()); // NOLINT
|
||||
#endif
|
||||
if (output_raw_) {
|
||||
return raw;
|
||||
}
|
||||
return raw / 1024.0f;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32
|
||||
float ADCSensor::sample() {
|
||||
if (!autorange_) {
|
||||
int raw = adc1_get_raw(channel_);
|
||||
if (raw == -1) {
|
||||
return NAN;
|
||||
}
|
||||
if (output_raw_) {
|
||||
return raw;
|
||||
}
|
||||
uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]);
|
||||
return mv / 1000.0f;
|
||||
}
|
||||
|
||||
int raw11, raw6 = 4095, raw2 = 4095, raw0 = 4095;
|
||||
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11);
|
||||
raw11 = adc1_get_raw(channel_);
|
||||
if (raw11 < 4095) {
|
||||
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6);
|
||||
raw6 = adc1_get_raw(channel_);
|
||||
if (raw6 < 4095) {
|
||||
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5);
|
||||
raw2 = adc1_get_raw(channel_);
|
||||
if (raw2 < 4095) {
|
||||
adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0);
|
||||
raw0 = adc1_get_raw(channel_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) {
|
||||
return NAN;
|
||||
}
|
||||
|
||||
uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]);
|
||||
uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]);
|
||||
uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]);
|
||||
uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]);
|
||||
|
||||
// Contribution of each value, in range 0-2048
|
||||
uint32_t c11 = std::min(raw11, 2048);
|
||||
uint32_t c6 = 2048 - std::abs(raw6 - 2048);
|
||||
uint32_t c2 = 2048 - std::abs(raw2 - 2048);
|
||||
uint32_t c0 = std::min(4095 - raw0, 2048);
|
||||
// max theoretical csum value is 2048*4 = 8192
|
||||
uint32_t csum = c11 + c6 + c2 + c0;
|
||||
|
||||
// each mv is max 3900; so max value is 3900*2048*4, fits in unsigned
|
||||
uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
|
||||
return mv_scaled / (float) (csum * 1000U);
|
||||
}
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
|
||||
#endif
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "driver/adc.h"
|
||||
#include <esp_adc_cal.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
@@ -17,7 +18,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
||||
public:
|
||||
#ifdef USE_ESP32
|
||||
/// Set the attenuation for this pin. Only available on the ESP32.
|
||||
void set_attenuation(adc_atten_t attenuation);
|
||||
void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; }
|
||||
void set_channel(adc1_channel_t channel) { channel_ = channel; }
|
||||
void set_autorange(bool autorange) { autorange_ = autorange; }
|
||||
#endif
|
||||
|
||||
/// Update adc values.
|
||||
@@ -28,6 +31,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
||||
/// `HARDWARE_LATE` setup priority.
|
||||
float get_setup_priority() const override;
|
||||
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
|
||||
void set_output_raw(bool output_raw) { output_raw_ = output_raw; }
|
||||
float sample() override;
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
@@ -36,9 +40,13 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
||||
|
||||
protected:
|
||||
InternalGPIOPin *pin_;
|
||||
bool output_raw_{false};
|
||||
|
||||
#ifdef USE_ESP32
|
||||
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
|
||||
adc1_channel_t channel_{};
|
||||
bool autorange_{false};
|
||||
esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {};
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -4,14 +4,24 @@ from esphome import pins
|
||||
from esphome.components import sensor, voltage_sampler
|
||||
from esphome.const import (
|
||||
CONF_ATTENUATION,
|
||||
CONF_RAW,
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_NUMBER,
|
||||
CONF_PIN,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
|
||||
|
||||
AUTO_LOAD = ["voltage_sampler"]
|
||||
@@ -21,6 +31,62 @@ ATTENUATION_MODES = {
|
||||
"2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
|
||||
"6db": cg.global_ns.ADC_ATTEN_DB_6,
|
||||
"11db": cg.global_ns.ADC_ATTEN_DB_11,
|
||||
"auto": "auto",
|
||||
}
|
||||
|
||||
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
|
||||
|
||||
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
|
||||
# pin to adc1 channel mapping
|
||||
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
|
||||
VARIANT_ESP32: {
|
||||
36: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
37: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
38: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
39: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
32: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
33: adc1_channel_t.ADC1_CHANNEL_5,
|
||||
34: adc1_channel_t.ADC1_CHANNEL_6,
|
||||
35: adc1_channel_t.ADC1_CHANNEL_7,
|
||||
},
|
||||
VARIANT_ESP32S2: {
|
||||
1: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
5: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
6: adc1_channel_t.ADC1_CHANNEL_5,
|
||||
7: adc1_channel_t.ADC1_CHANNEL_6,
|
||||
8: adc1_channel_t.ADC1_CHANNEL_7,
|
||||
9: adc1_channel_t.ADC1_CHANNEL_8,
|
||||
10: adc1_channel_t.ADC1_CHANNEL_9,
|
||||
},
|
||||
VARIANT_ESP32S3: {
|
||||
1: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
5: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
6: adc1_channel_t.ADC1_CHANNEL_5,
|
||||
7: adc1_channel_t.ADC1_CHANNEL_6,
|
||||
8: adc1_channel_t.ADC1_CHANNEL_7,
|
||||
9: adc1_channel_t.ADC1_CHANNEL_8,
|
||||
10: adc1_channel_t.ADC1_CHANNEL_9,
|
||||
},
|
||||
VARIANT_ESP32C3: {
|
||||
0: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
1: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
},
|
||||
VARIANT_ESP32H2: {
|
||||
0: adc1_channel_t.ADC1_CHANNEL_0,
|
||||
1: adc1_channel_t.ADC1_CHANNEL_1,
|
||||
2: adc1_channel_t.ADC1_CHANNEL_2,
|
||||
3: adc1_channel_t.ADC1_CHANNEL_3,
|
||||
4: adc1_channel_t.ADC1_CHANNEL_4,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -29,15 +95,16 @@ def validate_adc_pin(value):
|
||||
return cv.only_on_esp8266("VCC")
|
||||
|
||||
if CORE.is_esp32:
|
||||
from esphome.components.esp32 import is_esp32c3
|
||||
|
||||
value = pins.internal_gpio_input_pin_number(value)
|
||||
if is_esp32c3():
|
||||
if not (0 <= value <= 4): # ADC1
|
||||
raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.")
|
||||
if not (32 <= value <= 39): # ADC1
|
||||
raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.")
|
||||
elif CORE.is_esp8266:
|
||||
variant = get_esp32_variant()
|
||||
if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
|
||||
raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
|
||||
|
||||
if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
|
||||
raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
|
||||
return pins.internal_gpio_input_pin_schema(value)
|
||||
|
||||
if CORE.is_esp8266:
|
||||
from esphome.components.esp8266.gpio import CONF_ANALOG
|
||||
|
||||
value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
|
||||
@@ -49,10 +116,14 @@ def validate_adc_pin(value):
|
||||
return pins.gpio_pin_schema(
|
||||
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
|
||||
)(value)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
return pins.internal_gpio_input_pin_schema(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def validate_config(config):
|
||||
if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
|
||||
raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.")
|
||||
return config
|
||||
|
||||
|
||||
adc_ns = cg.esphome_ns.namespace("adc")
|
||||
@@ -60,7 +131,7 @@ ADCSensor = adc_ns.class_(
|
||||
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
@@ -71,12 +142,14 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADCSensor),
|
||||
cv.Required(CONF_PIN): validate_adc_pin,
|
||||
cv.Optional(CONF_RAW, default=False): cv.boolean,
|
||||
cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
|
||||
cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(cv.polling_component_schema("60s")),
|
||||
validate_config,
|
||||
)
|
||||
|
||||
|
||||
@@ -91,5 +164,17 @@ async def to_code(config):
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
cg.add(var.set_pin(pin))
|
||||
|
||||
if CONF_RAW in config:
|
||||
cg.add(var.set_output_raw(config[CONF_RAW]))
|
||||
|
||||
if CONF_ATTENUATION in config:
|
||||
cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
|
||||
if config[CONF_ATTENUATION] == "auto":
|
||||
cg.add(var.set_autorange(cg.global_ns.true))
|
||||
else:
|
||||
cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
|
||||
|
||||
if CORE.is_esp32:
|
||||
variant = get_esp32_variant()
|
||||
pin_num = config[CONF_PIN][CONF_NUMBER]
|
||||
chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
|
||||
cg.add(var.set_channel(chan))
|
||||
|
||||
@@ -76,9 +76,9 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
|
||||
return err;
|
||||
*value = 0;
|
||||
*value |= ((uint32_t) recv[0]) << 24;
|
||||
*value |= ((uint32_t) recv[1]) << 24;
|
||||
*value |= ((uint32_t) recv[2]) << 24;
|
||||
*value |= ((uint32_t) recv[3]) << 24;
|
||||
*value |= ((uint32_t) recv[1]) << 16;
|
||||
*value |= ((uint32_t) recv[2]) << 8;
|
||||
*value |= ((uint32_t) recv[3]);
|
||||
return i2c::ERROR_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,13 +73,6 @@ void AHT10Component::update() {
|
||||
bool success = false;
|
||||
for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
|
||||
ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
|
||||
delay_microseconds_accurate(4);
|
||||
|
||||
uint8_t reg = 0;
|
||||
if (this->write(®, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
|
||||
continue;
|
||||
}
|
||||
delay(delay_ms);
|
||||
if (this->read(data, 6) != i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
|
||||
|
||||
@@ -4,7 +4,8 @@ from esphome.components import sensor, ble_client
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_BATTERY_LEVEL,
|
||||
ICON_BATTERY,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
CONF_ILLUMINANCE,
|
||||
ICON_BRIGHTNESS_5,
|
||||
UNIT_PERCENT,
|
||||
@@ -20,10 +21,15 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Am43),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_BATTERY, 0
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
accuracy_decimals=0,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
|
||||
UNIT_PERCENT, ICON_BRIGHTNESS_5, 0
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_BRIGHTNESS_5,
|
||||
accuracy_decimals=0,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -44,8 +44,9 @@ async def to_code(config):
|
||||
width, height = image.size
|
||||
frames = image.n_frames
|
||||
if CONF_RESIZE in config:
|
||||
image.thumbnail(config[CONF_RESIZE])
|
||||
width, height = image.size
|
||||
new_width_max, new_height_max = config[CONF_RESIZE]
|
||||
ratio = min(new_width_max / width, new_height_max / height)
|
||||
width, height = int(width * ratio), int(height * ratio)
|
||||
else:
|
||||
if width > 500 or height > 500:
|
||||
_LOGGER.warning(
|
||||
@@ -59,7 +60,13 @@ async def to_code(config):
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert("L", dither=Image.NONE)
|
||||
if CONF_RESIZE in config:
|
||||
frame = frame.resize([width, height])
|
||||
pixels = list(frame.getdata())
|
||||
if len(pixels) != height * width:
|
||||
raise core.EsphomeError(
|
||||
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
|
||||
)
|
||||
for pix in pixels:
|
||||
data[pos] = pix
|
||||
pos += 1
|
||||
@@ -70,7 +77,13 @@ async def to_code(config):
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert("RGB")
|
||||
if CONF_RESIZE in config:
|
||||
frame = frame.resize([width, height])
|
||||
pixels = list(frame.getdata())
|
||||
if len(pixels) != height * width:
|
||||
raise core.EsphomeError(
|
||||
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
|
||||
)
|
||||
for pix in pixels:
|
||||
data[pos] = pix[0]
|
||||
pos += 1
|
||||
@@ -85,6 +98,8 @@ async def to_code(config):
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
frame = image.convert("1", dither=Image.NONE)
|
||||
if CONF_RESIZE in config:
|
||||
frame = frame.resize([width, height])
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if frame.getpixel((x, y)):
|
||||
|
||||
@@ -103,21 +103,21 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
|
||||
break;
|
||||
}
|
||||
case READ_TARGET_TEMPERATURE: {
|
||||
this->target_temp_ = strtof(this->buf_, nullptr);
|
||||
this->target_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
|
||||
if (this->fahrenheit_)
|
||||
this->target_temp_ = ftoc(this->target_temp_);
|
||||
this->has_target_temp_ = true;
|
||||
break;
|
||||
}
|
||||
case SET_TARGET_TEMPERATURE: {
|
||||
this->target_temp_ = strtof(this->buf_, nullptr);
|
||||
this->target_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
|
||||
if (this->fahrenheit_)
|
||||
this->target_temp_ = ftoc(this->target_temp_);
|
||||
this->has_target_temp_ = true;
|
||||
break;
|
||||
}
|
||||
case READ_CURRENT_TEMPERATURE: {
|
||||
this->current_temp_ = strtof(this->buf_, nullptr);
|
||||
this->current_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
|
||||
if (this->fahrenheit_)
|
||||
this->current_temp_ = ftoc(this->current_temp_);
|
||||
this->has_current_temp_ = true;
|
||||
|
||||
@@ -121,7 +121,7 @@ async def to_code(config):
|
||||
decoded = base64.b64decode(conf[CONF_KEY])
|
||||
cg.add(var.set_noise_psk(list(decoded)))
|
||||
cg.add_define("USE_API_NOISE")
|
||||
cg.add_library("esphome/noise-c", "0.1.3")
|
||||
cg.add_library("esphome/noise-c", "0.1.4")
|
||||
else:
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
|
||||
|
||||
@@ -182,6 +182,8 @@ message DeviceInfoResponse {
|
||||
// The esphome project details if set
|
||||
string project_name = 8;
|
||||
string project_version = 9;
|
||||
|
||||
uint32 webserver_port = 10;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
@@ -201,6 +203,14 @@ message SubscribeStatesRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
// ==================== COMMON =====================
|
||||
|
||||
enum EntityCategory {
|
||||
ENTITY_CATEGORY_NONE = 0;
|
||||
ENTITY_CATEGORY_CONFIG = 1;
|
||||
ENTITY_CATEGORY_DIAGNOSTIC = 2;
|
||||
}
|
||||
|
||||
// ==================== BINARY SENSOR ====================
|
||||
message ListEntitiesBinarySensorResponse {
|
||||
option (id) = 12;
|
||||
@@ -216,6 +226,7 @@ message ListEntitiesBinarySensorResponse {
|
||||
bool is_status_binary_sensor = 6;
|
||||
bool disabled_by_default = 7;
|
||||
string icon = 8;
|
||||
EntityCategory entity_category = 9;
|
||||
}
|
||||
message BinarySensorStateResponse {
|
||||
option (id) = 21;
|
||||
@@ -247,6 +258,7 @@ message ListEntitiesCoverResponse {
|
||||
string device_class = 8;
|
||||
bool disabled_by_default = 9;
|
||||
string icon = 10;
|
||||
EntityCategory entity_category = 11;
|
||||
}
|
||||
|
||||
enum LegacyCoverState {
|
||||
@@ -316,6 +328,7 @@ message ListEntitiesFanResponse {
|
||||
int32 supported_speed_count = 8;
|
||||
bool disabled_by_default = 9;
|
||||
string icon = 10;
|
||||
EntityCategory entity_category = 11;
|
||||
}
|
||||
enum FanSpeed {
|
||||
FAN_SPEED_LOW = 0;
|
||||
@@ -392,6 +405,7 @@ message ListEntitiesLightResponse {
|
||||
repeated string effects = 11;
|
||||
bool disabled_by_default = 13;
|
||||
string icon = 14;
|
||||
EntityCategory entity_category = 15;
|
||||
}
|
||||
message LightStateResponse {
|
||||
option (id) = 24;
|
||||
@@ -480,6 +494,7 @@ message ListEntitiesSensorResponse {
|
||||
// Last reset type removed in 2021.9.0
|
||||
SensorLastResetType legacy_last_reset_type = 11;
|
||||
bool disabled_by_default = 12;
|
||||
EntityCategory entity_category = 13;
|
||||
}
|
||||
message SensorStateResponse {
|
||||
option (id) = 25;
|
||||
@@ -508,6 +523,7 @@ message ListEntitiesSwitchResponse {
|
||||
string icon = 5;
|
||||
bool assumed_state = 6;
|
||||
bool disabled_by_default = 7;
|
||||
EntityCategory entity_category = 8;
|
||||
}
|
||||
message SwitchStateResponse {
|
||||
option (id) = 26;
|
||||
@@ -541,6 +557,7 @@ message ListEntitiesTextSensorResponse {
|
||||
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
}
|
||||
message TextSensorStateResponse {
|
||||
option (id) = 27;
|
||||
@@ -701,6 +718,8 @@ message ListEntitiesCameraResponse {
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
bool disabled_by_default = 5;
|
||||
string icon = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
}
|
||||
|
||||
message CameraImageResponse {
|
||||
@@ -795,6 +814,7 @@ message ListEntitiesClimateResponse {
|
||||
repeated string supported_custom_presets = 17;
|
||||
bool disabled_by_default = 18;
|
||||
string icon = 19;
|
||||
EntityCategory entity_category = 20;
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
@@ -863,6 +883,7 @@ message ListEntitiesNumberResponse {
|
||||
float max_value = 7;
|
||||
float step = 8;
|
||||
bool disabled_by_default = 9;
|
||||
EntityCategory entity_category = 10;
|
||||
}
|
||||
message NumberStateResponse {
|
||||
option (id) = 50;
|
||||
@@ -900,6 +921,7 @@ message ListEntitiesSelectResponse {
|
||||
string icon = 5;
|
||||
repeated string options = 6;
|
||||
bool disabled_by_default = 7;
|
||||
EntityCategory entity_category = 8;
|
||||
}
|
||||
message SelectStateResponse {
|
||||
option (id) = 53;
|
||||
|
||||
@@ -78,6 +78,8 @@ void APIConnection::loop() {
|
||||
on_fatal_error();
|
||||
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
|
||||
ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str());
|
||||
} else if (err == APIError::CONNECTION_CLOSED) {
|
||||
ESP_LOGW(TAG, "%s: Connection closed", client_info_.c_str());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
}
|
||||
@@ -182,6 +184,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
|
||||
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
@@ -215,6 +218,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
|
||||
msg.device_class = cover->get_device_class();
|
||||
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);
|
||||
}
|
||||
void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||
@@ -281,6 +285,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
|
||||
msg.supported_speed_count = traits.supported_speed_count();
|
||||
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);
|
||||
}
|
||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
@@ -344,6 +349,7 @@ bool APIConnection::send_light_info(light::LightState *light) {
|
||||
|
||||
msg.disabled_by_default = light->is_disabled_by_default();
|
||||
msg.icon = light->get_icon();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(light->get_entity_category());
|
||||
|
||||
for (auto mode : traits.get_supported_color_modes())
|
||||
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
|
||||
@@ -430,7 +436,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
||||
msg.device_class = sensor->get_device_class();
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
@@ -454,6 +460,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
|
||||
msg.icon = a_switch->get_icon();
|
||||
msg.assumed_state = a_switch->assumed_state();
|
||||
msg.disabled_by_default = a_switch->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category());
|
||||
return this->send_list_entities_switch_response(msg);
|
||||
}
|
||||
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
|
||||
@@ -489,6 +496,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
|
||||
msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
|
||||
msg.icon = text_sensor->get_icon();
|
||||
msg.disabled_by_default = text_sensor->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category());
|
||||
return this->send_list_entities_text_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
@@ -535,6 +543,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||
|
||||
msg.disabled_by_default = climate->is_disabled_by_default();
|
||||
msg.icon = climate->get_icon();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(climate->get_entity_category());
|
||||
|
||||
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
||||
@@ -609,6 +618,7 @@ bool APIConnection::send_number_info(number::Number *number) {
|
||||
msg.unique_id = get_default_unique_id("number", number);
|
||||
msg.icon = number->get_icon();
|
||||
msg.disabled_by_default = number->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category());
|
||||
|
||||
msg.min_value = number->traits.get_min_value();
|
||||
msg.max_value = number->traits.get_max_value();
|
||||
@@ -646,6 +656,7 @@ bool APIConnection::send_select_info(select::Select *select) {
|
||||
msg.unique_id = get_default_unique_id("select", select);
|
||||
msg.icon = select->get_icon();
|
||||
msg.disabled_by_default = select->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(select->get_entity_category());
|
||||
|
||||
for (const auto &option : select->traits.get_options())
|
||||
msg.options.push_back(option);
|
||||
@@ -678,6 +689,8 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
|
||||
msg.name = camera->get_name();
|
||||
msg.unique_id = get_default_unique_id("camera", 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);
|
||||
}
|
||||
void APIConnection::camera_image(const CameraImageRequest &msg) {
|
||||
@@ -756,6 +769,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
#ifdef ESPHOME_PROJECT_NAME
|
||||
resp.project_name = ESPHOME_PROJECT_NAME;
|
||||
resp.project_version = ESPHOME_PROJECT_VERSION;
|
||||
#endif
|
||||
#ifdef USE_WEBSERVER
|
||||
resp.webserver_port = WEBSERVER_PORT;
|
||||
#endif
|
||||
return resp;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace api {
|
||||
|
||||
static const char *const TAG = "api.socket";
|
||||
|
||||
/// Is the given return value (from read/write syscalls) a wouldblock error?
|
||||
/// Is the given return value (from write syscalls) a wouldblock error?
|
||||
bool is_would_block(ssize_t ret) {
|
||||
if (ret == -1) {
|
||||
return errno == EWOULDBLOCK || errno == EAGAIN;
|
||||
@@ -64,6 +64,8 @@ const char *api_error_to_str(APIError err) {
|
||||
return "HANDSHAKESTATE_SPLIT_FAILED";
|
||||
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
|
||||
return "BAD_HANDSHAKE_ERROR_BYTE";
|
||||
} else if (err == APIError::CONNECTION_CLOSED) {
|
||||
return "CONNECTION_CLOSED";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
@@ -185,12 +187,17 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
// no header information yet
|
||||
size_t to_read = 3 - rx_header_buf_len_;
|
||||
ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
|
||||
if (is_would_block(received)) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
} else if (received == -1) {
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
} else if (received == 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Connection closed");
|
||||
return APIError::CONNECTION_CLOSED;
|
||||
}
|
||||
rx_header_buf_len_ += received;
|
||||
if (received != to_read) {
|
||||
@@ -227,12 +234,17 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
// more data to read
|
||||
size_t to_read = msg_size - rx_buf_len_;
|
||||
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||
if (is_would_block(received)) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
} else if (received == -1) {
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
} else if (received == 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Connection closed");
|
||||
return APIError::CONNECTION_CLOSED;
|
||||
}
|
||||
rx_buf_len_ += received;
|
||||
if (received != to_read) {
|
||||
@@ -778,12 +790,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
while (!rx_header_parsed_) {
|
||||
uint8_t data;
|
||||
ssize_t received = socket_->read(&data, 1);
|
||||
if (is_would_block(received)) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
} else if (received == -1) {
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
} else if (received == 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Connection closed");
|
||||
return APIError::CONNECTION_CLOSED;
|
||||
}
|
||||
rx_header_buf_.push_back(data);
|
||||
|
||||
@@ -824,12 +841,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
// more data to read
|
||||
size_t to_read = rx_header_parsed_len_ - rx_buf_len_;
|
||||
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||
if (is_would_block(received)) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
} else if (received == -1) {
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
} else if (received == 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Connection closed");
|
||||
return APIError::CONNECTION_CLOSED;
|
||||
}
|
||||
rx_buf_len_ += received;
|
||||
if (received != to_read) {
|
||||
|
||||
@@ -53,6 +53,7 @@ enum class APIError : int {
|
||||
HANDSHAKESTATE_SETUP_FAILED = 1019,
|
||||
HANDSHAKESTATE_SPLIT_FAILED = 1020,
|
||||
BAD_HANDSHAKE_ERROR_BYTE = 1021,
|
||||
CONNECTION_CLOSED = 1022,
|
||||
};
|
||||
|
||||
const char *api_error_to_str(APIError err);
|
||||
|
||||
@@ -6,6 +6,18 @@
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::EntityCategory value) {
|
||||
switch (value) {
|
||||
case enums::ENTITY_CATEGORY_NONE:
|
||||
return "ENTITY_CATEGORY_NONE";
|
||||
case enums::ENTITY_CATEGORY_CONFIG:
|
||||
return "ENTITY_CATEGORY_CONFIG";
|
||||
case enums::ENTITY_CATEGORY_DIAGNOSTIC:
|
||||
return "ENTITY_CATEGORY_DIAGNOSTIC";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::LegacyCoverState>(enums::LegacyCoverState value) {
|
||||
switch (value) {
|
||||
case enums::LEGACY_COVER_STATE_OPEN:
|
||||
@@ -396,6 +408,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->has_deep_sleep = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->webserver_port = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -444,6 +460,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(7, this->has_deep_sleep);
|
||||
buffer.encode_string(8, this->project_name);
|
||||
buffer.encode_string(9, this->project_version);
|
||||
buffer.encode_uint32(10, this->webserver_port);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
@@ -484,6 +501,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
out.append(" project_version: ");
|
||||
out.append("'").append(this->project_version).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" webserver_port: ");
|
||||
sprintf(buffer, "%u", this->webserver_port);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -509,6 +531,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -558,6 +584,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(6, this->is_status_binary_sensor);
|
||||
buffer.encode_bool(7, this->disabled_by_default);
|
||||
buffer.encode_string(8, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(9, this->entity_category);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
|
||||
@@ -595,6 +622,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -664,6 +695,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -715,6 +750,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(8, this->device_class);
|
||||
buffer.encode_bool(9, this->disabled_by_default);
|
||||
buffer.encode_string(10, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
|
||||
@@ -760,6 +796,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -948,6 +988,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -995,6 +1039,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_int32(8, this->supported_speed_count);
|
||||
buffer.encode_bool(9, this->disabled_by_default);
|
||||
buffer.encode_string(10, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
@@ -1041,6 +1086,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -1267,6 +1316,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 15: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1334,6 +1387,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
}
|
||||
buffer.encode_bool(13, this->disabled_by_default);
|
||||
buffer.encode_string(14, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(15, this->entity_category);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesLightResponse::dump_to(std::string &out) const {
|
||||
@@ -1401,6 +1455,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -1860,6 +1918,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 13: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1917,6 +1979,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_enum<enums::SensorStateClass>(10, this->state_class);
|
||||
buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type);
|
||||
buffer.encode_bool(12, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(13, this->entity_category);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
@@ -1971,6 +2034,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -2033,6 +2100,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2077,6 +2148,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->assumed_state);
|
||||
buffer.encode_bool(7, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
|
||||
@@ -2110,6 +2182,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -2197,6 +2273,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2240,6 +2320,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(4, this->unique_id);
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
|
||||
@@ -2269,6 +2350,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -2892,6 +2977,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2910,6 +2999,10 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
||||
this->unique_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 6: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2930,6 +3023,8 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_string(4, this->unique_id);
|
||||
buffer.encode_bool(5, this->disabled_by_default);
|
||||
buffer.encode_string(6, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
|
||||
@@ -2955,6 +3050,14 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -3082,6 +3185,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 20: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -3170,6 +3277,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
}
|
||||
buffer.encode_bool(18, this->disabled_by_default);
|
||||
buffer.encode_string(19, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(20, this->entity_category);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
@@ -3266,6 +3374,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -3642,6 +3754,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -3700,6 +3816,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_float(7, this->max_value);
|
||||
buffer.encode_float(8, this->step);
|
||||
buffer.encode_bool(9, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
|
||||
@@ -3744,6 +3861,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -3836,6 +3957,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -3886,6 +4011,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(6, it, true);
|
||||
}
|
||||
buffer.encode_bool(7, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesSelectResponse::dump_to(std::string &out) const {
|
||||
@@ -3921,6 +4047,10 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -9,6 +9,11 @@ namespace api {
|
||||
|
||||
namespace enums {
|
||||
|
||||
enum EntityCategory : uint32_t {
|
||||
ENTITY_CATEGORY_NONE = 0,
|
||||
ENTITY_CATEGORY_CONFIG = 1,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC = 2,
|
||||
};
|
||||
enum LegacyCoverState : uint32_t {
|
||||
LEGACY_COVER_STATE_OPEN = 0,
|
||||
LEGACY_COVER_STATE_CLOSED = 1,
|
||||
@@ -224,6 +229,7 @@ class DeviceInfoResponse : public ProtoMessage {
|
||||
bool has_deep_sleep{false};
|
||||
std::string project_name{};
|
||||
std::string project_version{};
|
||||
uint32_t webserver_port{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -270,6 +276,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
|
||||
bool is_status_binary_sensor{false};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -306,6 +313,7 @@ class ListEntitiesCoverResponse : public ProtoMessage {
|
||||
std::string device_class{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -363,6 +371,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
||||
int32_t supported_speed_count{0};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -428,6 +437,7 @@ class ListEntitiesLightResponse : public ProtoMessage {
|
||||
std::vector<std::string> effects{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -516,6 +526,7 @@ class ListEntitiesSensorResponse : public ProtoMessage {
|
||||
enums::SensorStateClass state_class{};
|
||||
enums::SensorLastResetType legacy_last_reset_type{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -549,6 +560,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
|
||||
std::string icon{};
|
||||
bool assumed_state{false};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -593,6 +605,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -803,6 +816,8 @@ class ListEntitiesCameraResponse : public ProtoMessage {
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -861,6 +876,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
||||
std::vector<std::string> supported_custom_presets{};
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -940,6 +956,7 @@ class ListEntitiesNumberResponse : public ProtoMessage {
|
||||
float max_value{0.0f};
|
||||
float step{0.0f};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -985,6 +1002,7 @@ class ListEntitiesSelectResponse : public ProtoMessage {
|
||||
std::string icon{};
|
||||
std::vector<std::string> options{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -77,7 +77,7 @@ void APIServer::setup() {
|
||||
this->last_connected_ = millis();
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
if (esp32_camera::global_esp32_camera != nullptr) {
|
||||
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
|
||||
esp32_camera::global_esp32_camera->add_image_callback(
|
||||
[this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
|
||||
for (auto &c : this->clients_)
|
||||
|
||||
@@ -23,7 +23,6 @@ async def async_run_logs(config, address):
|
||||
_LOGGER.info("Starting log output from %s using esphome API", address)
|
||||
zc = zeroconf.Zeroconf()
|
||||
cli = APIClient(
|
||||
asyncio.get_event_loop(),
|
||||
address,
|
||||
port,
|
||||
password,
|
||||
|
||||
@@ -8,6 +8,18 @@
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> {
|
||||
public:
|
||||
TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
|
||||
|
||||
template<typename F, enable_if_t<!is_callable<F, X...>::value, int> = 0>
|
||||
TemplatableStringValue(F value) : TemplatableValue<std::string, X...>(value) {}
|
||||
|
||||
template<typename F, enable_if_t<is_callable<F, X...>::value, int> = 0>
|
||||
TemplatableStringValue(F f)
|
||||
: TemplatableValue<std::string, X...>([f](X... x) -> std::string { return to_string(f(x...)); }) {}
|
||||
};
|
||||
|
||||
template<typename... Ts> class TemplatableKeyValuePair {
|
||||
public:
|
||||
template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
|
||||
@@ -19,7 +31,8 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||
public:
|
||||
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
|
||||
|
||||
TEMPLATABLE_STRING_VALUE(service);
|
||||
template<typename T> void set_service(T service) { this->service_ = service; }
|
||||
|
||||
template<typename T> void add_data(std::string key, T value) {
|
||||
this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||
}
|
||||
@@ -58,6 +71,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||
protected:
|
||||
APIServer *parent_;
|
||||
bool is_event_;
|
||||
TemplatableStringValue<Ts...> service_{};
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> data_;
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
|
||||
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
|
||||
|
||||
@@ -12,6 +12,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
@@ -49,12 +50,14 @@ CONFIG_SCHEMA = (
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_POWER_FACTOR,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_LIGHTBULB,
|
||||
ICON_CURRENT_AC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
@@ -125,6 +126,7 @@ CONFIG_SCHEMA = (
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
|
||||
cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(
|
||||
|
||||
@@ -13,6 +13,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_LUX,
|
||||
@@ -51,6 +52,7 @@ CONFIG_SCHEMA = (
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
|
||||
@@ -79,6 +79,9 @@ void BH1750Sensor::read_data_() {
|
||||
|
||||
float lx = float(raw_value) / 1.2f;
|
||||
lx *= 69.0f / this->measurement_duration_;
|
||||
if (this->resolution_ == BH1750_RESOLUTION_0P5_LX) {
|
||||
lx /= 2.0f;
|
||||
}
|
||||
ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
|
||||
this->publish_state(lx);
|
||||
this->status_clear_warning();
|
||||
|
||||
@@ -44,9 +44,11 @@ from esphome.const import (
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_PRESENCE,
|
||||
DEVICE_CLASS_PROBLEM,
|
||||
DEVICE_CLASS_RUNNING,
|
||||
DEVICE_CLASS_SAFETY,
|
||||
DEVICE_CLASS_SMOKE,
|
||||
DEVICE_CLASS_SOUND,
|
||||
DEVICE_CLASS_TAMPER,
|
||||
DEVICE_CLASS_UPDATE,
|
||||
DEVICE_CLASS_VIBRATION,
|
||||
DEVICE_CLASS_WINDOW,
|
||||
@@ -76,9 +78,11 @@ DEVICE_CLASSES = [
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_PRESENCE,
|
||||
DEVICE_CLASS_PROBLEM,
|
||||
DEVICE_CLASS_RUNNING,
|
||||
DEVICE_CLASS_SAFETY,
|
||||
DEVICE_CLASS_SMOKE,
|
||||
DEVICE_CLASS_SOUND,
|
||||
DEVICE_CLASS_TAMPER,
|
||||
DEVICE_CLASS_UPDATE,
|
||||
DEVICE_CLASS_VIBRATION,
|
||||
DEVICE_CLASS_WINDOW,
|
||||
@@ -313,6 +317,7 @@ def validate_multi_click_timing(value):
|
||||
|
||||
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||
|
||||
|
||||
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BinarySensor),
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace ble_client {
|
||||
|
||||
static const char *const TAG = "ble_client";
|
||||
|
||||
float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
|
||||
|
||||
void BLEClient::setup() {
|
||||
auto ret = esp_ble_gattc_app_register(this->app_id);
|
||||
if (ret) {
|
||||
@@ -386,6 +388,15 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
|
||||
}
|
||||
|
||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
|
||||
auto client = this->service->client;
|
||||
auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val,
|
||||
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class BLECharacteristic {
|
||||
void parse_descriptors();
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
|
||||
BLEDescriptor *get_descriptor(uint16_t uuid);
|
||||
|
||||
void write_value(uint8_t *new_val, int16_t new_val_size);
|
||||
BLEService *service;
|
||||
};
|
||||
|
||||
@@ -81,6 +81,7 @@ class BLEClient : public espbt::ESPBTClient, public Component {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
|
||||
67
esphome/components/ble_client/output/__init__.py
Normal file
67
esphome/components/ble_client/output/__init__.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output, ble_client, esp32_ble_tracker
|
||||
from esphome.const import CONF_ID, CONF_SERVICE_UUID
|
||||
from .. import ble_client_ns
|
||||
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
|
||||
|
||||
BLEBinaryOutput = ble_client_ns.class_(
|
||||
"BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
output.BINARY_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput),
|
||||
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
|
||||
cg.add(
|
||||
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
|
||||
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_char_uuid16(
|
||||
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid32_format
|
||||
):
|
||||
cg.add(
|
||||
var.set_char_uuid32(
|
||||
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid128_format
|
||||
):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
|
||||
config[CONF_CHARACTERISTIC_UUID]
|
||||
)
|
||||
cg.add(var.set_char_uuid128(uuid128))
|
||||
|
||||
yield output.register_output(var, config)
|
||||
yield ble_client.register_ble_node(var, config)
|
||||
yield cg.register_component(var, config)
|
||||
71
esphome/components/ble_client/output/ble_binary_output.cpp
Normal file
71
esphome/components/ble_client/output/ble_binary_output.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "ble_binary_output.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
static const char *const TAG = "ble_binary_output";
|
||||
|
||||
void BLEBinaryOutput::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BLE Binary Output:");
|
||||
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent_->address_str().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
|
||||
LOG_BINARY_OUTPUT(this);
|
||||
}
|
||||
|
||||
void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_OPEN_EVT:
|
||||
this->client_state_ = espbt::ClientState::ESTABLISHED;
|
||||
ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str());
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str());
|
||||
this->client_state_ = espbt::ClientState::IDLE;
|
||||
break;
|
||||
case ESP_GATTC_WRITE_CHAR_EVT: {
|
||||
if (param->write.status == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
if (param->write.handle == chr->handle) {
|
||||
ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BLEBinaryOutput::write_state(bool state) {
|
||||
if (this->client_state_ != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.",
|
||||
this->char_uuid_.to_string().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] Characteristic not found. State update can not be written.",
|
||||
this->char_uuid_.to_string().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t state_as_uint = (uint8_t) state;
|
||||
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
|
||||
chr->write_value(&state_as_uint, sizeof(state_as_uint));
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
||||
39
esphome/components/ble_client/output/ble_binary_output.h
Normal file
39
esphome/components/ble_client/output/ble_binary_output.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void loop() override {}
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
espbt::ESPBTUUID service_uuid_;
|
||||
espbt::ESPBTUUID char_uuid_;
|
||||
espbt::ClientState client_state_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
@@ -67,7 +67,7 @@ async def to_code(config):
|
||||
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
|
||||
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
@@ -87,7 +87,9 @@ async def to_code(config):
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid128_format
|
||||
):
|
||||
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID])
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
|
||||
config[CONF_CHARACTERISTIC_UUID]
|
||||
)
|
||||
cg.add(var.set_char_uuid128(uuid128))
|
||||
|
||||
if CONF_DESCRIPTOR_UUID in config:
|
||||
@@ -108,7 +110,9 @@ async def to_code(config):
|
||||
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid128_format
|
||||
):
|
||||
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_DESCRIPTOR_UUID])
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
|
||||
config[CONF_DESCRIPTOR_UUID]
|
||||
)
|
||||
cg.add(var.set_descr_uuid128(uuid128))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
|
||||
@@ -44,7 +44,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
cv.Optional(
|
||||
CONF_STATE_SAVE_INTERVAL, default="6hours"
|
||||
): cv.positive_time_period_minutes,
|
||||
}
|
||||
},
|
||||
cv.only_with_arduino,
|
||||
).extend(i2c.i2c_device_schema(0x76))
|
||||
|
||||
|
||||
@@ -60,5 +61,8 @@ async def to_code(config):
|
||||
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
|
||||
)
|
||||
|
||||
# Although this component does not use SPI, the BSEC library requires the SPI library
|
||||
cg.add_library("SPI", None)
|
||||
|
||||
cg.add_define("USE_BSEC")
|
||||
cg.add_library("BSEC Software Library", "1.6.1480")
|
||||
cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480")
|
||||
|
||||
45
esphome/components/cap1188/__init__.py
Normal file
45
esphome/components/cap1188/__init__.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ID, CONF_RESET_PIN
|
||||
from esphome import pins
|
||||
|
||||
CONF_TOUCH_THRESHOLD = "touch_threshold"
|
||||
CONF_ALLOW_MULTIPLE_TOUCHES = "allow_multiple_touches"
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["binary_sensor", "output"]
|
||||
CODEOWNERS = ["@MrEditor97"]
|
||||
|
||||
cap1188_ns = cg.esphome_ns.namespace("cap1188")
|
||||
CONF_CAP1188_ID = "cap1188_id"
|
||||
CAP1188Component = cap1188_ns.class_("CAP1188Component", cg.Component, i2c.I2CDevice)
|
||||
|
||||
MULTI_CONF = True
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CAP1188Component),
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_TOUCH_THRESHOLD, default=0x20): cv.int_range(
|
||||
min=0x01, max=0x80
|
||||
),
|
||||
cv.Optional(CONF_ALLOW_MULTIPLE_TOUCHES, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(i2c.i2c_device_schema(0x29))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD]))
|
||||
cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES]))
|
||||
|
||||
if CONF_RESET_PIN in config:
|
||||
pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
|
||||
cg.add(var.set_reset_pin(pin))
|
||||
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
25
esphome/components/cap1188/binary_sensor.py
Normal file
25
esphome/components/cap1188/binary_sensor.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID
|
||||
from . import cap1188_ns, CAP1188Component, CONF_CAP1188_ID
|
||||
|
||||
DEPENDENCIES = ["cap1188"]
|
||||
CAP1188Channel = cap1188_ns.class_("CAP1188Channel", binary_sensor.BinarySensor)
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CAP1188Channel),
|
||||
cv.GenerateID(CONF_CAP1188_ID): cv.use_id(CAP1188Component),
|
||||
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await binary_sensor.register_binary_sensor(var, config)
|
||||
hub = await cg.get_variable(config[CONF_CAP1188_ID])
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
|
||||
cg.add(hub.register_channel(var))
|
||||
88
esphome/components/cap1188/cap1188.cpp
Normal file
88
esphome/components/cap1188/cap1188.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "cap1188.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cap1188 {
|
||||
|
||||
static const char *const TAG = "cap1188";
|
||||
|
||||
void CAP1188Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up CAP1188...");
|
||||
|
||||
// Reset device using the reset pin
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(100); // NOLINT
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(100); // NOLINT
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(100); // NOLINT
|
||||
}
|
||||
|
||||
// Check if CAP1188 is actually connected
|
||||
this->read_byte(CAP1188_PRODUCT_ID, &this->cap1188_product_id_);
|
||||
this->read_byte(CAP1188_MANUFACTURE_ID, &this->cap1188_manufacture_id_);
|
||||
this->read_byte(CAP1188_REVISION, &this->cap1188_revision_);
|
||||
|
||||
if ((this->cap1188_product_id_ != 0x50) || (this->cap1188_manufacture_id_ != 0x5D)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set sensitivity
|
||||
uint8_t sensitivity = 0;
|
||||
this->read_byte(CAP1188_SENSITVITY, &sensitivity);
|
||||
sensitivity = sensitivity & 0x0f;
|
||||
this->write_byte(CAP1188_SENSITVITY, sensitivity | this->touch_threshold_);
|
||||
|
||||
// Allow multiple touches
|
||||
this->write_byte(CAP1188_MULTI_TOUCH, this->allow_multiple_touches_);
|
||||
|
||||
// Have LEDs follow touches
|
||||
this->write_byte(CAP1188_LED_LINK, 0xFF);
|
||||
|
||||
// Speed up a bit
|
||||
this->write_byte(CAP1188_STAND_BY_CONFIGURATION, 0x30);
|
||||
}
|
||||
|
||||
void CAP1188Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CAP1188:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Product ID: 0x%x", this->cap1188_product_id_);
|
||||
ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->cap1188_manufacture_id_);
|
||||
ESP_LOGCONFIG(TAG, " Revision ID: 0x%x", this->cap1188_revision_);
|
||||
|
||||
switch (this->error_code_) {
|
||||
case COMMUNICATION_FAILED:
|
||||
ESP_LOGE(TAG, "Product ID or Manufacture ID of the connected device does not match a known CAP1188.");
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CAP1188Component::loop() {
|
||||
uint8_t touched = 0;
|
||||
|
||||
this->read_register(CAP1188_SENSOR_INPUT_STATUS, &touched, 1);
|
||||
|
||||
if (touched) {
|
||||
uint8_t data = 0;
|
||||
this->read_register(CAP1188_MAIN, &data, 1);
|
||||
data = data & ~CAP1188_MAIN_INT;
|
||||
|
||||
this->write_register(CAP1188_MAIN, &data, 2);
|
||||
}
|
||||
|
||||
for (auto *channel : this->channels_) {
|
||||
channel->process(touched);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cap1188
|
||||
} // namespace esphome
|
||||
68
esphome/components/cap1188/cap1188.h
Normal file
68
esphome/components/cap1188/cap1188.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cap1188 {
|
||||
|
||||
enum {
|
||||
CAP1188_I2CADDR = 0x29,
|
||||
CAP1188_SENSOR_INPUT_STATUS = 0x3,
|
||||
CAP1188_MULTI_TOUCH = 0x2A,
|
||||
CAP1188_LED_LINK = 0x72,
|
||||
CAP1188_PRODUCT_ID = 0xFD,
|
||||
CAP1188_MANUFACTURE_ID = 0xFE,
|
||||
CAP1188_STAND_BY_CONFIGURATION = 0x41,
|
||||
CAP1188_REVISION = 0xFF,
|
||||
CAP1188_MAIN = 0x00,
|
||||
CAP1188_MAIN_INT = 0x01,
|
||||
CAP1188_LEDPOL = 0x73,
|
||||
CAP1188_INTERUPT_REPEAT = 0x28,
|
||||
CAP1188_SENSITVITY = 0x1f,
|
||||
};
|
||||
|
||||
class CAP1188Channel : public binary_sensor::BinarySensor {
|
||||
public:
|
||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
||||
void process(uint8_t data) { this->publish_state(static_cast<bool>(data & (1 << this->channel_))); }
|
||||
|
||||
protected:
|
||||
uint8_t channel_{0};
|
||||
};
|
||||
|
||||
class CAP1188Component : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void register_channel(CAP1188Channel *channel) { this->channels_.push_back(channel); }
|
||||
void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; };
|
||||
void set_allow_multiple_touches(bool allow_multiple_touches) {
|
||||
this->allow_multiple_touches_ = allow_multiple_touches ? 0x41 : 0x80;
|
||||
};
|
||||
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void loop() override;
|
||||
|
||||
protected:
|
||||
std::vector<CAP1188Channel *> channels_{};
|
||||
uint8_t touch_threshold_{0x20};
|
||||
uint8_t allow_multiple_touches_{0x80};
|
||||
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
|
||||
uint8_t cap1188_product_id_{0};
|
||||
uint8_t cap1188_manufacture_id_{0};
|
||||
uint8_t cap1188_revision_{0};
|
||||
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
COMMUNICATION_FAILED,
|
||||
} error_code_{NONE};
|
||||
};
|
||||
|
||||
} // namespace cap1188
|
||||
} // namespace esphome
|
||||
@@ -36,3 +36,5 @@ async def to_code(config):
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("DNSServer", None)
|
||||
cg.add_library("WiFi", None)
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("DNSServer", None)
|
||||
|
||||
@@ -67,6 +67,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
|
||||
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
|
||||
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
|
||||
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
|
||||
wifi::global_wifi_component->start_scanning();
|
||||
request->redirect("/?save=true");
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ void CCS811Component::setup() {
|
||||
|
||||
if (this->baseline_.has_value()) {
|
||||
// baseline available, write to sensor
|
||||
this->write_bytes(0x11, decode_uint16(*this->baseline_));
|
||||
this->write_bytes(0x11, decode_value(*this->baseline_));
|
||||
}
|
||||
|
||||
auto hardware_version_data = this->read_bytes<1>(0x21);
|
||||
|
||||
@@ -20,6 +20,7 @@ from esphome.const import (
|
||||
CONF_MODE,
|
||||
CONF_MODE_COMMAND_TOPIC,
|
||||
CONF_MODE_STATE_TOPIC,
|
||||
CONF_ON_STATE,
|
||||
CONF_PRESET,
|
||||
CONF_SWING_MODE,
|
||||
CONF_SWING_MODE_COMMAND_TOPIC,
|
||||
@@ -34,6 +35,7 @@ from esphome.const import (
|
||||
CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC,
|
||||
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC,
|
||||
CONF_TEMPERATURE_STEP,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_VISUAL,
|
||||
CONF_MQTT_ID,
|
||||
)
|
||||
@@ -101,6 +103,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
|
||||
|
||||
# Actions
|
||||
ControlAction = climate_ns.class_("ControlAction", automation.Action)
|
||||
StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template())
|
||||
|
||||
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
@@ -161,6 +164,11 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -256,6 +264,10 @@ async def setup_climate_core_(var, config):
|
||||
)
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
|
||||
async def register_climate(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
|
||||
@@ -42,5 +42,12 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||
Climate *climate_;
|
||||
};
|
||||
|
||||
class StateTrigger : public Trigger<> {
|
||||
public:
|
||||
StateTrigger(Climate *climate) {
|
||||
climate->add_on_state_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace climate
|
||||
} // namespace esphome
|
||||
|
||||
@@ -440,7 +440,11 @@ void Climate::set_visual_max_temperature_override(float visual_max_temperature_o
|
||||
void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) {
|
||||
this->visual_temperature_step_override_ = visual_temperature_step_override;
|
||||
}
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
Climate::Climate(const std::string &name) : EntityBase(name) {}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
Climate::Climate() : Climate("") {}
|
||||
ClimateCall Climate::make_call() { return ClimateCall(this); }
|
||||
|
||||
|
||||
0
esphome/components/cse7761/__init__.py
Normal file
0
esphome/components/cse7761/__init__.py
Normal file
244
esphome/components/cse7761/cse7761.cpp
Normal file
244
esphome/components/cse7761/cse7761.cpp
Normal file
@@ -0,0 +1,244 @@
|
||||
#include "cse7761.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cse7761 {
|
||||
|
||||
static const char *const TAG = "cse7761";
|
||||
|
||||
/*********************************************************************************************\
|
||||
* CSE7761 - Energy (Sonoff Dual R3 Pow v1.x)
|
||||
*
|
||||
* Based on Tasmota source code
|
||||
* See https://github.com/arendst/Tasmota/discussions/10793
|
||||
* https://github.com/arendst/Tasmota/blob/development/tasmota/xnrg_19_cse7761.ino
|
||||
\*********************************************************************************************/
|
||||
|
||||
static const int CSE7761_UREF = 42563; // RmsUc
|
||||
static const int CSE7761_IREF = 52241; // RmsIAC
|
||||
static const int CSE7761_PREF = 44513; // PowerPAC
|
||||
|
||||
static const uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04)
|
||||
static const uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000)
|
||||
static const uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001)
|
||||
static const uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210)
|
||||
|
||||
static const uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000)
|
||||
static const uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000)
|
||||
static const uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000)
|
||||
static const uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000)
|
||||
static const uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000)
|
||||
static const uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register
|
||||
|
||||
static const uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum
|
||||
static const uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient
|
||||
|
||||
static const uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command
|
||||
static const uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets
|
||||
static const uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation
|
||||
static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation
|
||||
|
||||
enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC };
|
||||
|
||||
void CSE7761Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up CSE7761...");
|
||||
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET);
|
||||
uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04
|
||||
if ((0x0A04 == syscon) && this->chip_init_()) {
|
||||
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_CLOSE_WRITE);
|
||||
ESP_LOGD(TAG, "CSE7761 found");
|
||||
this->data_.ready = true;
|
||||
} else {
|
||||
this->mark_failed();
|
||||
}
|
||||
}
|
||||
|
||||
void CSE7761Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CSE7761:");
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with CSE7761 failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8);
|
||||
}
|
||||
|
||||
float CSE7761Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void CSE7761Component::update() {
|
||||
if (this->data_.ready) {
|
||||
this->get_data_();
|
||||
}
|
||||
}
|
||||
|
||||
void CSE7761Component::write_(uint8_t reg, uint16_t data) {
|
||||
uint8_t buffer[5];
|
||||
|
||||
buffer[0] = 0xA5;
|
||||
buffer[1] = reg;
|
||||
uint32_t len = 2;
|
||||
if (data) {
|
||||
if (data < 0xFF) {
|
||||
buffer[2] = data & 0xFF;
|
||||
len = 3;
|
||||
} else {
|
||||
buffer[2] = (data >> 8) & 0xFF;
|
||||
buffer[3] = data & 0xFF;
|
||||
len = 4;
|
||||
}
|
||||
uint8_t crc = 0;
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
crc += buffer[i];
|
||||
}
|
||||
buffer[len] = ~crc;
|
||||
len++;
|
||||
}
|
||||
|
||||
this->write_array(buffer, len);
|
||||
}
|
||||
|
||||
bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) {
|
||||
while (this->available()) {
|
||||
this->read();
|
||||
}
|
||||
|
||||
this->write_(reg, 0);
|
||||
|
||||
uint8_t buffer[8] = {0};
|
||||
uint32_t rcvd = 0;
|
||||
|
||||
for (uint32_t i = 0; i <= size; i++) {
|
||||
int value = this->read();
|
||||
if (value > -1 && rcvd < sizeof(buffer) - 1) {
|
||||
buffer[rcvd++] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rcvd) {
|
||||
ESP_LOGD(TAG, "Received 0 bytes for register %hhu", reg);
|
||||
return false;
|
||||
}
|
||||
|
||||
rcvd--;
|
||||
uint32_t result = 0;
|
||||
// CRC check
|
||||
uint8_t crc = 0xA5 + reg;
|
||||
for (uint32_t i = 0; i < rcvd; i++) {
|
||||
result = (result << 8) | buffer[i];
|
||||
crc += buffer[i];
|
||||
}
|
||||
crc = ~crc;
|
||||
if (crc != buffer[rcvd]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*value = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) {
|
||||
bool result = false; // Start loop
|
||||
uint8_t retry = 3; // Retry up to three times
|
||||
uint32_t value = 0; // Default no value
|
||||
while (!result && retry > 0) {
|
||||
retry--;
|
||||
if (this->read_once_(reg, size, &value))
|
||||
return value;
|
||||
}
|
||||
ESP_LOGE(TAG, "Reading register %hhu failed!", reg);
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t CSE7761Component::coefficient_by_unit_(uint32_t unit) {
|
||||
switch (unit) {
|
||||
case RMS_UC:
|
||||
return 0x400000 * 100 / this->data_.coefficient[RMS_UC];
|
||||
case RMS_IAC:
|
||||
return (0x800000 * 100 / this->data_.coefficient[RMS_IAC]) * 10; // Stay within 32 bits
|
||||
case POWER_PAC:
|
||||
return 0x80000000 / this->data_.coefficient[POWER_PAC];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool CSE7761Component::chip_init_() {
|
||||
uint16_t calc_chksum = 0xFFFF;
|
||||
for (uint32_t i = 0; i < 8; i++) {
|
||||
this->data_.coefficient[i] = this->read_(CSE7761_REG_RMSIAC + i, 2);
|
||||
calc_chksum += this->data_.coefficient[i];
|
||||
}
|
||||
calc_chksum = ~calc_chksum;
|
||||
uint16_t coeff_chksum = this->read_(CSE7761_REG_COEFFCHKSUM, 2);
|
||||
if ((calc_chksum != coeff_chksum) || (!calc_chksum)) {
|
||||
ESP_LOGD(TAG, "Default calibration");
|
||||
this->data_.coefficient[RMS_IAC] = CSE7761_IREF;
|
||||
this->data_.coefficient[RMS_UC] = CSE7761_UREF;
|
||||
this->data_.coefficient[POWER_PAC] = CSE7761_PREF;
|
||||
}
|
||||
|
||||
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE);
|
||||
|
||||
uint8_t sys_status = this->read_(CSE7761_REG_SYSSTATUS, 1);
|
||||
if (sys_status & 0x10) { // Write enable to protected registers (WREN)
|
||||
this->write_(CSE7761_REG_SYSCON | 0x80, 0xFF04);
|
||||
this->write_(CSE7761_REG_EMUCON | 0x80, 0x1183);
|
||||
this->write_(CSE7761_REG_EMUCON2 | 0x80, 0x0FC1);
|
||||
this->write_(CSE7761_REG_PULSE1SEL | 0x80, 0x3290);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Write failed at chip_init");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CSE7761Component::get_data_() {
|
||||
// The effective value of current and voltage Rms is a 24-bit signed number,
|
||||
// the highest bit is 0 for valid data,
|
||||
// and when the highest bit is 1, the reading will be processed as zero
|
||||
// The active power parameter PowerA/B is in two’s complement format, 32-bit
|
||||
// data, the highest bit is Sign bit.
|
||||
uint32_t value = this->read_(CSE7761_REG_RMSU, 3);
|
||||
this->data_.voltage_rms = (value >= 0x800000) ? 0 : value;
|
||||
|
||||
value = this->read_(CSE7761_REG_RMSIA, 3);
|
||||
this->data_.current_rms[0] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
|
||||
value = this->read_(CSE7761_REG_POWERPA, 4);
|
||||
this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : ((uint32_t) abs((int) value));
|
||||
|
||||
value = this->read_(CSE7761_REG_RMSIB, 3);
|
||||
this->data_.current_rms[1] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
|
||||
value = this->read_(CSE7761_REG_POWERPB, 4);
|
||||
this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : ((uint32_t) abs((int) value));
|
||||
|
||||
// convert values and publish to sensors
|
||||
|
||||
float voltage = (float) this->data_.voltage_rms / this->coefficient_by_unit_(RMS_UC);
|
||||
if (this->voltage_sensor_ != nullptr) {
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
}
|
||||
|
||||
for (uint32_t channel = 0; channel < 2; channel++) {
|
||||
// Active power = PowerPA * PowerPAC * 1000 / 0x80000000
|
||||
float active_power = (float) this->data_.active_power[channel] / this->coefficient_by_unit_(POWER_PAC); // W
|
||||
float amps = (float) this->data_.current_rms[channel] / this->coefficient_by_unit_(RMS_IAC); // A
|
||||
ESP_LOGD(TAG, "Channel %d power %f W, current %f A", channel + 1, active_power, amps);
|
||||
if (channel == 0) {
|
||||
if (this->power_sensor_1_ != nullptr) {
|
||||
this->power_sensor_1_->publish_state(active_power);
|
||||
}
|
||||
if (this->current_sensor_1_ != nullptr) {
|
||||
this->current_sensor_1_->publish_state(amps);
|
||||
}
|
||||
} else if (channel == 1) {
|
||||
if (this->power_sensor_2_ != nullptr) {
|
||||
this->power_sensor_2_->publish_state(active_power);
|
||||
}
|
||||
if (this->current_sensor_2_ != nullptr) {
|
||||
this->current_sensor_2_->publish_state(amps);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cse7761
|
||||
} // namespace esphome
|
||||
52
esphome/components/cse7761/cse7761.h
Normal file
52
esphome/components/cse7761/cse7761.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cse7761 {
|
||||
|
||||
struct CSE7761DataStruct {
|
||||
uint32_t frequency = 0;
|
||||
uint32_t voltage_rms = 0;
|
||||
uint32_t current_rms[2] = {0};
|
||||
uint32_t energy[2] = {0};
|
||||
uint32_t active_power[2] = {0};
|
||||
uint16_t coefficient[8] = {0};
|
||||
uint8_t energy_update = 0;
|
||||
bool ready = false;
|
||||
};
|
||||
|
||||
/// This class implements support for the CSE7761 UART power sensor.
|
||||
class CSE7761Component : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_active_power_1_sensor(sensor::Sensor *power_sensor_1) { power_sensor_1_ = power_sensor_1; }
|
||||
void set_current_1_sensor(sensor::Sensor *current_sensor_1) { current_sensor_1_ = current_sensor_1; }
|
||||
void set_active_power_2_sensor(sensor::Sensor *power_sensor_2) { power_sensor_2_ = power_sensor_2; }
|
||||
void set_current_2_sensor(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
// Sensors
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *power_sensor_1_{nullptr};
|
||||
sensor::Sensor *current_sensor_1_{nullptr};
|
||||
sensor::Sensor *power_sensor_2_{nullptr};
|
||||
sensor::Sensor *current_sensor_2_{nullptr};
|
||||
CSE7761DataStruct data_;
|
||||
|
||||
void write_(uint8_t reg, uint16_t data);
|
||||
bool read_once_(uint8_t reg, uint8_t size, uint32_t *value);
|
||||
uint32_t read_(uint8_t reg, uint8_t size);
|
||||
uint32_t coefficient_by_unit_(uint32_t unit);
|
||||
bool chip_init_();
|
||||
void get_data_();
|
||||
};
|
||||
|
||||
} // namespace cse7761
|
||||
} // namespace esphome
|
||||
90
esphome/components/cse7761/sensor.py
Normal file
90
esphome/components/cse7761/sensor.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, uart
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_WATT,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@berfenger"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
cse7761_ns = cg.esphome_ns.namespace("cse7761")
|
||||
CSE7761Component = cse7761_ns.class_(
|
||||
"CSE7761Component", cg.PollingComponent, uart.UARTDevice
|
||||
)
|
||||
|
||||
CONF_CURRENT_1 = "current_1"
|
||||
CONF_CURRENT_2 = "current_2"
|
||||
CONF_ACTIVE_POWER_1 = "active_power_1"
|
||||
CONF_ACTIVE_POWER_2 = "active_power_2"
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CSE7761Component),
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_1): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ACTIVE_POWER_1): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ACTIVE_POWER_2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"cse7761", baud_rate=38400, require_rx=True, require_tx=True
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
for key in [
|
||||
CONF_VOLTAGE,
|
||||
CONF_CURRENT_1,
|
||||
CONF_CURRENT_2,
|
||||
CONF_ACTIVE_POWER_1,
|
||||
CONF_ACTIVE_POWER_2,
|
||||
]:
|
||||
if key not in config:
|
||||
continue
|
||||
conf = config[key]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(getattr(var, f"set_{key}_sensor")(sens))
|
||||
@@ -6,26 +6,20 @@ from esphome.const import CONF_ID, CONF_PIN
|
||||
MULTI_CONF = True
|
||||
AUTO_LOAD = ["sensor"]
|
||||
|
||||
CONF_ONE_WIRE_ID = "one_wire_id"
|
||||
dallas_ns = cg.esphome_ns.namespace("dallas")
|
||||
DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent)
|
||||
ESPOneWire = dallas_ns.class_("ESPOneWire")
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DallasComponent),
|
||||
cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s")),
|
||||
# pin_mode call logs in esp-idf, but InterruptLock is active -> crash
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DallasComponent),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s"))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin)
|
||||
var = cg.new_Pvariable(config[CONF_ID], one_wire)
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
cg.add(var.set_pin(pin))
|
||||
|
||||
@@ -31,12 +31,11 @@ uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion() const {
|
||||
void DallasComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up DallasComponent...");
|
||||
|
||||
yield();
|
||||
pin_->setup();
|
||||
one_wire_ = new ESPOneWire(pin_); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
|
||||
std::vector<uint64_t> raw_sensors;
|
||||
{
|
||||
InterruptLock lock;
|
||||
raw_sensors = this->one_wire_->search_vec();
|
||||
}
|
||||
raw_sensors = this->one_wire_->search_vec();
|
||||
|
||||
for (auto &address : raw_sensors) {
|
||||
std::string s = uint64_to_string(address);
|
||||
@@ -70,7 +69,7 @@ void DallasComponent::setup() {
|
||||
}
|
||||
void DallasComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "DallasComponent:");
|
||||
LOG_PIN(" Pin: ", this->one_wire_->get_pin());
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
if (this->found_sensors_.empty()) {
|
||||
@@ -102,15 +101,12 @@ void DallasComponent::update() {
|
||||
this->status_clear_warning();
|
||||
|
||||
bool result;
|
||||
{
|
||||
InterruptLock lock;
|
||||
if (!this->one_wire_->reset()) {
|
||||
result = false;
|
||||
} else {
|
||||
result = true;
|
||||
this->one_wire_->skip();
|
||||
this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
|
||||
}
|
||||
if (!this->one_wire_->reset()) {
|
||||
result = false;
|
||||
} else {
|
||||
result = true;
|
||||
this->one_wire_->skip();
|
||||
this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
@@ -121,11 +117,7 @@ void DallasComponent::update() {
|
||||
|
||||
for (auto *sensor : this->sensors_) {
|
||||
this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] {
|
||||
bool res;
|
||||
{
|
||||
InterruptLock lock;
|
||||
res = sensor->read_scratch_pad();
|
||||
}
|
||||
bool res = sensor->read_scratch_pad();
|
||||
|
||||
if (!res) {
|
||||
ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str());
|
||||
@@ -146,7 +138,6 @@ void DallasComponent::update() {
|
||||
});
|
||||
}
|
||||
}
|
||||
DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {}
|
||||
|
||||
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
|
||||
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
|
||||
@@ -162,7 +153,7 @@ const std::string &DallasTemperatureSensor::get_address_name() {
|
||||
return this->address_name_;
|
||||
}
|
||||
bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
|
||||
ESPOneWire *wire = this->parent_->one_wire_;
|
||||
auto *wire = this->parent_->one_wire_;
|
||||
if (!wire->reset()) {
|
||||
return false;
|
||||
}
|
||||
@@ -176,11 +167,7 @@ bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
|
||||
return true;
|
||||
}
|
||||
bool DallasTemperatureSensor::setup_sensor() {
|
||||
bool r;
|
||||
{
|
||||
InterruptLock lock;
|
||||
r = this->read_scratch_pad();
|
||||
}
|
||||
bool r = this->read_scratch_pad();
|
||||
|
||||
if (!r) {
|
||||
ESP_LOGE(TAG, "Reading scratchpad failed: reset");
|
||||
@@ -214,21 +201,18 @@ bool DallasTemperatureSensor::setup_sensor() {
|
||||
break;
|
||||
}
|
||||
|
||||
ESPOneWire *wire = this->parent_->one_wire_;
|
||||
{
|
||||
InterruptLock lock;
|
||||
if (wire->reset()) {
|
||||
wire->select(this->address_);
|
||||
wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
|
||||
wire->write8(this->scratch_pad_[2]); // high alarm temp
|
||||
wire->write8(this->scratch_pad_[3]); // low alarm temp
|
||||
wire->write8(this->scratch_pad_[4]); // resolution
|
||||
wire->reset();
|
||||
auto *wire = this->parent_->one_wire_;
|
||||
if (wire->reset()) {
|
||||
wire->select(this->address_);
|
||||
wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
|
||||
wire->write8(this->scratch_pad_[2]); // high alarm temp
|
||||
wire->write8(this->scratch_pad_[3]); // low alarm temp
|
||||
wire->write8(this->scratch_pad_[4]); // resolution
|
||||
wire->reset();
|
||||
|
||||
// write value to EEPROM
|
||||
wire->select(this->address_);
|
||||
wire->write8(0x48);
|
||||
}
|
||||
// write value to EEPROM
|
||||
wire->select(this->address_);
|
||||
wire->write8(0x48);
|
||||
}
|
||||
|
||||
delay(20); // allow it to finish operation
|
||||
|
||||
@@ -11,8 +11,7 @@ class DallasTemperatureSensor;
|
||||
|
||||
class DallasComponent : public PollingComponent {
|
||||
public:
|
||||
explicit DallasComponent(ESPOneWire *one_wire);
|
||||
|
||||
void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
|
||||
void register_sensor(DallasTemperatureSensor *sensor);
|
||||
|
||||
void setup() override;
|
||||
@@ -24,6 +23,7 @@ class DallasComponent : public PollingComponent {
|
||||
protected:
|
||||
friend DallasTemperatureSensor;
|
||||
|
||||
InternalGPIOPin *pin_;
|
||||
ESPOneWire *one_wire_;
|
||||
std::vector<DallasTemperatureSensor *> sensors_;
|
||||
std::vector<uint64_t> found_sensors_;
|
||||
|
||||
@@ -10,115 +10,123 @@ static const char *const TAG = "dallas.one_wire";
|
||||
const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
|
||||
const int ONE_WIRE_ROM_SEARCH = 0xF0;
|
||||
|
||||
ESPOneWire::ESPOneWire(GPIOPin *pin) : pin_(pin) {}
|
||||
ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); }
|
||||
|
||||
bool HOT IRAM_ATTR ESPOneWire::reset() {
|
||||
uint8_t retries = 125;
|
||||
// See reset here:
|
||||
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
|
||||
InterruptLock lock;
|
||||
|
||||
// Wait for communication to clear
|
||||
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
// Wait for communication to clear (delay G)
|
||||
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
uint8_t retries = 125;
|
||||
do {
|
||||
if (--retries == 0)
|
||||
return false;
|
||||
delayMicroseconds(2);
|
||||
} while (!this->pin_->digital_read());
|
||||
} while (!pin_.digital_read());
|
||||
|
||||
// Send 480µs LOW TX reset pulse
|
||||
this->pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->pin_->digital_write(false);
|
||||
// Send 480µs LOW TX reset pulse (drive bus low, delay H)
|
||||
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
||||
pin_.digital_write(false);
|
||||
delayMicroseconds(480);
|
||||
|
||||
// Switch into RX mode, letting the pin float
|
||||
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
// after 15µs-60µs wait time, responder pulls low for 60µs-240µs
|
||||
// let's have 70µs just in case
|
||||
// Release the bus, delay I
|
||||
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
delayMicroseconds(70);
|
||||
|
||||
bool r = !this->pin_->digital_read();
|
||||
// sample bus, 0=device(s) present, 1=no device present
|
||||
bool r = !pin_.digital_read();
|
||||
// delay J
|
||||
delayMicroseconds(410);
|
||||
return r;
|
||||
}
|
||||
|
||||
void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) {
|
||||
// Initiate write/read by pulling low.
|
||||
this->pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->pin_->digital_write(false);
|
||||
// See write 1/0 bit here:
|
||||
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
|
||||
InterruptLock lock;
|
||||
|
||||
// bus sampled within 15µs and 60µs after pulling LOW.
|
||||
if (bit) {
|
||||
// pull high/release within 15µs
|
||||
delayMicroseconds(10);
|
||||
this->pin_->digital_write(true);
|
||||
// in total minimum of 60µs long
|
||||
delayMicroseconds(55);
|
||||
} else {
|
||||
// continue pulling LOW for at least 60µs
|
||||
delayMicroseconds(65);
|
||||
this->pin_->digital_write(true);
|
||||
// grace period, 1µs recovery time
|
||||
delayMicroseconds(5);
|
||||
}
|
||||
// drive bus low
|
||||
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
||||
pin_.digital_write(false);
|
||||
|
||||
uint32_t delay0 = bit ? 10 : 65;
|
||||
uint32_t delay1 = bit ? 55 : 5;
|
||||
|
||||
// delay A/C
|
||||
delayMicroseconds(delay0);
|
||||
// release bus
|
||||
pin_.digital_write(true);
|
||||
// delay B/D
|
||||
delayMicroseconds(delay1);
|
||||
}
|
||||
|
||||
bool HOT IRAM_ATTR ESPOneWire::read_bit() {
|
||||
// Initiate read slot by pulling LOW for at least 1µs
|
||||
this->pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->pin_->digital_write(false);
|
||||
// See read bit here:
|
||||
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
|
||||
InterruptLock lock;
|
||||
|
||||
// drive bus low, delay A
|
||||
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
||||
pin_.digital_write(false);
|
||||
delayMicroseconds(3);
|
||||
|
||||
// release bus, we have to sample within 15µs of pulling low
|
||||
this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
// release bus, delay E
|
||||
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
delayMicroseconds(10);
|
||||
|
||||
bool r = this->pin_->digital_read();
|
||||
// read time slot at least 60µs long + 1µs recovery time between slots
|
||||
// sample bus to read bit from peer
|
||||
bool r = pin_.digital_read();
|
||||
|
||||
// delay F
|
||||
delayMicroseconds(53);
|
||||
return r;
|
||||
}
|
||||
|
||||
void IRAM_ATTR ESPOneWire::write8(uint8_t val) {
|
||||
void ESPOneWire::write8(uint8_t val) {
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
this->write_bit(bool((1u << i) & val));
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR ESPOneWire::write64(uint64_t val) {
|
||||
void ESPOneWire::write64(uint64_t val) {
|
||||
for (uint8_t i = 0; i < 64; i++) {
|
||||
this->write_bit(bool((1ULL << i) & val));
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t IRAM_ATTR ESPOneWire::read8() {
|
||||
uint8_t ESPOneWire::read8() {
|
||||
uint8_t ret = 0;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
ret |= (uint8_t(this->read_bit()) << i);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
uint64_t IRAM_ATTR ESPOneWire::read64() {
|
||||
uint64_t ESPOneWire::read64() {
|
||||
uint64_t ret = 0;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
ret |= (uint64_t(this->read_bit()) << i);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
void IRAM_ATTR ESPOneWire::select(uint64_t address) {
|
||||
void ESPOneWire::select(uint64_t address) {
|
||||
this->write8(ONE_WIRE_ROM_SELECT);
|
||||
this->write64(address);
|
||||
}
|
||||
void IRAM_ATTR ESPOneWire::reset_search() {
|
||||
void ESPOneWire::reset_search() {
|
||||
this->last_discrepancy_ = 0;
|
||||
this->last_device_flag_ = false;
|
||||
this->last_family_discrepancy_ = 0;
|
||||
this->rom_number_ = 0;
|
||||
}
|
||||
uint64_t HOT IRAM_ATTR ESPOneWire::search() {
|
||||
uint64_t ESPOneWire::search() {
|
||||
if (this->last_device_flag_) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
if (!this->reset()) {
|
||||
// Reset failed
|
||||
// Reset failed or no devices present
|
||||
this->reset_search();
|
||||
return 0u;
|
||||
}
|
||||
@@ -196,7 +204,7 @@ uint64_t HOT IRAM_ATTR ESPOneWire::search() {
|
||||
|
||||
return this->rom_number_;
|
||||
}
|
||||
std::vector<uint64_t> IRAM_ATTR ESPOneWire::search_vec() {
|
||||
std::vector<uint64_t> ESPOneWire::search_vec() {
|
||||
std::vector<uint64_t> res;
|
||||
|
||||
this->reset_search();
|
||||
@@ -206,10 +214,9 @@ std::vector<uint64_t> IRAM_ATTR ESPOneWire::search_vec() {
|
||||
|
||||
return res;
|
||||
}
|
||||
void IRAM_ATTR ESPOneWire::skip() {
|
||||
void ESPOneWire::skip() {
|
||||
this->write8(0xCC); // skip ROM
|
||||
}
|
||||
GPIOPin *ESPOneWire::get_pin() { return this->pin_; }
|
||||
|
||||
uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast<uint8_t *>(&this->rom_number_); }
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <vector>
|
||||
|
||||
@@ -12,7 +11,7 @@ extern const int ONE_WIRE_ROM_SEARCH;
|
||||
|
||||
class ESPOneWire {
|
||||
public:
|
||||
explicit ESPOneWire(GPIOPin *pin);
|
||||
explicit ESPOneWire(InternalGPIOPin *pin);
|
||||
|
||||
/** Reset the bus, should be done before all write operations.
|
||||
*
|
||||
@@ -55,13 +54,11 @@ class ESPOneWire {
|
||||
/// Helper that wraps search in a std::vector.
|
||||
std::vector<uint64_t> search_vec();
|
||||
|
||||
GPIOPin *get_pin();
|
||||
|
||||
protected:
|
||||
/// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer.
|
||||
inline uint8_t *rom_number8_();
|
||||
|
||||
GPIOPin *pin_;
|
||||
ISRInternalGPIOPin pin_;
|
||||
uint8_t last_discrepancy_{0};
|
||||
uint8_t last_family_discrepancy_{0};
|
||||
bool last_device_flag_{false};
|
||||
|
||||
@@ -29,6 +29,14 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
WIFI_MESSAGE = """
|
||||
|
||||
# Do not forget to add your own wifi configuration before installing this configuration
|
||||
# wifi:
|
||||
# ssid: !secret wifi_ssid
|
||||
# password: !secret wifi_password
|
||||
"""
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_DASHBOARD_IMPORT")
|
||||
@@ -41,5 +49,12 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
|
||||
if p.exists():
|
||||
raise FileExistsError
|
||||
|
||||
config = {"substitutions": {"name": name}, "packages": {project_name: import_url}}
|
||||
p.write_text(dump(config), encoding="utf8")
|
||||
config = {
|
||||
"substitutions": {"name": name},
|
||||
"packages": {project_name: import_url},
|
||||
"esphome": {"name_add_mac_suffix": False},
|
||||
}
|
||||
p.write_text(
|
||||
dump(config) + WIFI_MESSAGE,
|
||||
encoding="utf8",
|
||||
)
|
||||
|
||||
@@ -4,20 +4,23 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_system.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#if ESP_IDF_VERSION_MAJOR >= 4
|
||||
#include <esp32/rom/rtc.h>
|
||||
#else
|
||||
#include <rom/rtc.h>
|
||||
#include <esp_idf_version.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <Esp.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_system.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace debug {
|
||||
|
||||
|
||||
@@ -78,8 +78,9 @@ void DeepSleepComponent::begin_sleep(bool manual) {
|
||||
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
|
||||
if (this->wakeup_pin_ != nullptr) {
|
||||
bool level = this->wakeup_pin_->is_inverted();
|
||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read())
|
||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && !this->wakeup_pin_->digital_read()) {
|
||||
level = !level;
|
||||
}
|
||||
esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level);
|
||||
}
|
||||
if (this->ext1_wakeup_.has_value()) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import esphome.config_validation as cv
|
||||
from esphome import core, automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.const import (
|
||||
CONF_AUTO_CLEAR_ENABLED,
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_PAGES,
|
||||
@@ -79,6 +80,7 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
|
||||
cv.Optional(CONF_TO): cv.use_id(DisplayPage),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_AUTO_CLEAR_ENABLED, default=True): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -86,6 +88,10 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
|
||||
async def setup_display_core_(var, config):
|
||||
if CONF_ROTATION in config:
|
||||
cg.add(var.set_rotation(DISPLAY_ROTATIONS[config[CONF_ROTATION]]))
|
||||
|
||||
if CONF_AUTO_CLEAR_ENABLED in config:
|
||||
cg.add(var.set_auto_clear(config[CONF_AUTO_CLEAR_ENABLED]))
|
||||
|
||||
if CONF_PAGES in config:
|
||||
pages = []
|
||||
for conf in config[CONF_PAGES]:
|
||||
|
||||
@@ -336,7 +336,9 @@ void DisplayBuffer::show_page(DisplayPage *page) {
|
||||
void DisplayBuffer::show_next_page() { this->page_->show_next(); }
|
||||
void DisplayBuffer::show_prev_page() { this->page_->show_prev(); }
|
||||
void DisplayBuffer::do_update_() {
|
||||
this->clear();
|
||||
if (this->auto_clear_enabled_) {
|
||||
this->clear();
|
||||
}
|
||||
if (this->page_ != nullptr) {
|
||||
this->page_->get_writer()(*this);
|
||||
} else if (this->writer_.has_value()) {
|
||||
|
||||
@@ -333,6 +333,9 @@ class DisplayBuffer {
|
||||
/// Internal method to set the display rotation with.
|
||||
void set_rotation(DisplayRotation rotation);
|
||||
|
||||
// Internal method to set display auto clearing.
|
||||
void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
|
||||
|
||||
protected:
|
||||
void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
|
||||
|
||||
@@ -352,6 +355,7 @@ class DisplayBuffer {
|
||||
DisplayPage *page_{nullptr};
|
||||
DisplayPage *previous_page_{nullptr};
|
||||
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
|
||||
bool auto_clear_enabled_{true};
|
||||
};
|
||||
|
||||
class DisplayPage {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import uart
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
@@ -11,10 +12,13 @@ CODEOWNERS = ["@glmnet", "@zuidwijk"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
AUTO_LOAD = ["sensor", "text_sensor"]
|
||||
|
||||
CONF_DSMR_ID = "dsmr_id"
|
||||
CONF_DECRYPTION_KEY = "decryption_key"
|
||||
CONF_CRC_CHECK = "crc_check"
|
||||
CONF_DECRYPTION_KEY = "decryption_key"
|
||||
CONF_DSMR_ID = "dsmr_id"
|
||||
CONF_GAS_MBUS_ID = "gas_mbus_id"
|
||||
CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length"
|
||||
CONF_REQUEST_INTERVAL = "request_interval"
|
||||
CONF_REQUEST_PIN = "request_pin"
|
||||
|
||||
# Hack to prevent compile error due to ambiguity with lib namespace
|
||||
dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
|
||||
@@ -46,6 +50,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_DECRYPTION_KEY): _validate_key,
|
||||
cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
|
||||
cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
|
||||
cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_,
|
||||
cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_REQUEST_INTERVAL): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
).extend(uart.UART_DEVICE_SCHEMA),
|
||||
cv.only_with_arduino,
|
||||
@@ -55,10 +62,19 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config):
|
||||
uart_component = await cg.get_variable(config[CONF_UART_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK])
|
||||
cg.add(var.set_max_telegram_length(config[CONF_MAX_TELEGRAM_LENGTH]))
|
||||
if CONF_DECRYPTION_KEY in config:
|
||||
cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY]))
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if CONF_REQUEST_PIN in config:
|
||||
request_pin = await cg.gpio_pin_expression(config[CONF_REQUEST_PIN])
|
||||
cg.add(var.set_request_pin(request_pin))
|
||||
if CONF_REQUEST_INTERVAL in config:
|
||||
cg.add(
|
||||
var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds)
|
||||
)
|
||||
|
||||
cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID])
|
||||
|
||||
# DSMR Parser
|
||||
|
||||
@@ -12,156 +12,252 @@ namespace dsmr {
|
||||
|
||||
static const char *const TAG = "dsmr";
|
||||
|
||||
void Dsmr::setup() {
|
||||
this->telegram_ = new char[this->max_telegram_len_]; // NOLINT
|
||||
if (this->request_pin_ != nullptr) {
|
||||
this->request_pin_->setup();
|
||||
}
|
||||
}
|
||||
|
||||
void Dsmr::loop() {
|
||||
if (this->decryption_key_.empty())
|
||||
this->receive_telegram_();
|
||||
else
|
||||
this->receive_encrypted_();
|
||||
if (this->ready_to_request_data_()) {
|
||||
if (this->decryption_key_.empty()) {
|
||||
this->receive_telegram_();
|
||||
} else {
|
||||
this->receive_encrypted_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Dsmr::ready_to_request_data_() {
|
||||
// When using a request pin, then wait for the next request interval.
|
||||
if (this->request_pin_ != nullptr) {
|
||||
if (!this->requesting_data_ && this->request_interval_reached_()) {
|
||||
this->start_requesting_data_();
|
||||
}
|
||||
}
|
||||
// Otherwise, sink serial data until next request interval.
|
||||
else {
|
||||
if (this->request_interval_reached_()) {
|
||||
this->start_requesting_data_();
|
||||
}
|
||||
if (!this->requesting_data_) {
|
||||
while (this->available()) {
|
||||
this->read();
|
||||
}
|
||||
}
|
||||
}
|
||||
return this->requesting_data_;
|
||||
}
|
||||
|
||||
bool Dsmr::request_interval_reached_() {
|
||||
if (this->last_request_time_ == 0) {
|
||||
return true;
|
||||
}
|
||||
return millis() - this->last_request_time_ > this->request_interval_;
|
||||
}
|
||||
|
||||
bool Dsmr::available_within_timeout_() {
|
||||
uint8_t tries = READ_TIMEOUT_MS / 5;
|
||||
while (tries--) {
|
||||
delay(5);
|
||||
if (this->available()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Dsmr::start_requesting_data_() {
|
||||
if (!this->requesting_data_) {
|
||||
if (this->request_pin_ != nullptr) {
|
||||
ESP_LOGV(TAG, "Start requesting data from P1 port");
|
||||
this->request_pin_->digital_write(true);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Start reading data from P1 port");
|
||||
}
|
||||
this->requesting_data_ = true;
|
||||
this->last_request_time_ = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void Dsmr::stop_requesting_data_() {
|
||||
if (this->requesting_data_) {
|
||||
if (this->request_pin_ != nullptr) {
|
||||
ESP_LOGV(TAG, "Stop requesting data from P1 port");
|
||||
this->request_pin_->digital_write(false);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Stop reading data from P1 port");
|
||||
}
|
||||
while (this->available()) {
|
||||
this->read();
|
||||
}
|
||||
this->requesting_data_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Dsmr::receive_telegram_() {
|
||||
int count = MAX_BYTES_PER_LOOP;
|
||||
while (available() && count-- > 0) {
|
||||
const char c = read();
|
||||
while (true) {
|
||||
if (!this->available()) {
|
||||
if (!this->header_found_ || !this->available_within_timeout_()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const char c = this->read();
|
||||
|
||||
// Find a new telegram header, i.e. forward slash.
|
||||
if (c == '/') {
|
||||
ESP_LOGV(TAG, "Header found");
|
||||
header_found_ = true;
|
||||
footer_found_ = false;
|
||||
telegram_len_ = 0;
|
||||
ESP_LOGV(TAG, "Header of telegram found");
|
||||
this->header_found_ = true;
|
||||
this->footer_found_ = false;
|
||||
this->telegram_len_ = 0;
|
||||
}
|
||||
if (!header_found_)
|
||||
if (!this->header_found_)
|
||||
continue;
|
||||
|
||||
// Check for buffer overflow.
|
||||
if (telegram_len_ >= MAX_TELEGRAM_LENGTH) {
|
||||
header_found_ = false;
|
||||
footer_found_ = false;
|
||||
ESP_LOGE(TAG, "Error: Message larger than buffer");
|
||||
if (this->telegram_len_ >= this->max_telegram_len_) {
|
||||
this->header_found_ = false;
|
||||
this->footer_found_ = false;
|
||||
ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Some v2.2 or v3 meters will send a new value which starts with '('
|
||||
// in a new line while the value belongs to the previous ObisId. For
|
||||
// proper parsing remove these new line characters
|
||||
while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r'))
|
||||
telegram_len_--;
|
||||
// in a new line, while the value belongs to the previous ObisId. For
|
||||
// proper parsing, remove these new line characters.
|
||||
if (c == '(') {
|
||||
while (true) {
|
||||
auto previous_char = this->telegram_[this->telegram_len_ - 1];
|
||||
if (previous_char == '\n' || previous_char == '\r') {
|
||||
this->telegram_len_--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the byte in the buffer.
|
||||
telegram_[telegram_len_] = c;
|
||||
telegram_len_++;
|
||||
this->telegram_[this->telegram_len_] = c;
|
||||
this->telegram_len_++;
|
||||
|
||||
// Check for a footer, i.e. exlamation mark, followed by a hex checksum.
|
||||
if (c == '!') {
|
||||
ESP_LOGV(TAG, "Footer found");
|
||||
footer_found_ = true;
|
||||
ESP_LOGV(TAG, "Footer of telegram found");
|
||||
this->footer_found_ = true;
|
||||
continue;
|
||||
}
|
||||
// Check for the end of the hex checksum, i.e. a newline.
|
||||
if (footer_found_ && c == '\n') {
|
||||
header_found_ = false;
|
||||
if (this->footer_found_ && c == '\n') {
|
||||
// Parse the telegram and publish sensor values.
|
||||
if (parse_telegram())
|
||||
return;
|
||||
this->parse_telegram();
|
||||
|
||||
this->header_found_ = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Dsmr::receive_encrypted_() {
|
||||
// Encrypted buffer
|
||||
uint8_t buffer[MAX_TELEGRAM_LENGTH];
|
||||
size_t buffer_length = 0;
|
||||
|
||||
this->encrypted_telegram_len_ = 0;
|
||||
size_t packet_size = 0;
|
||||
while (available()) {
|
||||
const char c = read();
|
||||
|
||||
if (!header_found_) {
|
||||
if ((uint8_t) c == 0xdb) {
|
||||
ESP_LOGV(TAG, "Start byte 0xDB found");
|
||||
header_found_ = true;
|
||||
while (true) {
|
||||
if (!this->available()) {
|
||||
if (!this->header_found_) {
|
||||
return;
|
||||
}
|
||||
if (!this->available_within_timeout_()) {
|
||||
ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (!header_found_ || buffer_length >= MAX_TELEGRAM_LENGTH) {
|
||||
if (buffer_length == 0) {
|
||||
ESP_LOGE(TAG, "First byte of encrypted telegram should be 0xDB, aborting.");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unexpected data");
|
||||
const char c = this->read();
|
||||
|
||||
// Find a new telegram start byte.
|
||||
if (!this->header_found_) {
|
||||
if ((uint8_t) c != 0xDB) {
|
||||
continue;
|
||||
}
|
||||
this->status_momentary_warning("unexpected_data");
|
||||
this->flush();
|
||||
while (available())
|
||||
read();
|
||||
ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
|
||||
this->header_found_ = true;
|
||||
}
|
||||
|
||||
// Check for buffer overflow.
|
||||
if (this->encrypted_telegram_len_ >= this->max_telegram_len_) {
|
||||
this->header_found_ = false;
|
||||
ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_);
|
||||
return;
|
||||
}
|
||||
|
||||
buffer[buffer_length++] = c;
|
||||
this->encrypted_telegram_[this->encrypted_telegram_len_++] = c;
|
||||
|
||||
if (packet_size == 0 && buffer_length > 20) {
|
||||
// Complete header + a few bytes of data
|
||||
packet_size = buffer[11] << 8 | buffer[12];
|
||||
if (packet_size == 0 && this->encrypted_telegram_len_ > 20) {
|
||||
// Complete header + data bytes
|
||||
packet_size = 13 + (this->encrypted_telegram_[11] << 8 | this->encrypted_telegram_[12]);
|
||||
ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size);
|
||||
}
|
||||
if (buffer_length == packet_size + 13 && packet_size > 0) {
|
||||
ESP_LOGV(TAG, "Encrypted data: %d bytes", buffer_length);
|
||||
|
||||
if (this->encrypted_telegram_len_ == packet_size && packet_size > 0) {
|
||||
ESP_LOGV(TAG, "End of encrypted telegram found");
|
||||
GCM<AES128> *gcmaes128{new GCM<AES128>()};
|
||||
gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
|
||||
// the iv is 8 bytes of the system title + 4 bytes frame counter
|
||||
// system title is at byte 2 and frame counter at byte 15
|
||||
for (int i = 10; i < 14; i++)
|
||||
buffer[i] = buffer[i + 4];
|
||||
this->encrypted_telegram_[i] = this->encrypted_telegram_[i + 4];
|
||||
constexpr uint16_t iv_size{12};
|
||||
gcmaes128->setIV(&buffer[2], iv_size);
|
||||
gcmaes128->setIV(&this->encrypted_telegram_[2], iv_size);
|
||||
gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_),
|
||||
// the ciphertext start at byte 18
|
||||
&buffer[18],
|
||||
&this->encrypted_telegram_[18],
|
||||
// cipher size
|
||||
buffer_length - 17);
|
||||
this->encrypted_telegram_len_ - 17);
|
||||
delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
|
||||
telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_));
|
||||
ESP_LOGV(TAG, "Decrypted data length: %d", telegram_len_);
|
||||
ESP_LOGVV(TAG, "Decrypted data %s", this->telegram_);
|
||||
this->telegram_len_ = strnlen(this->telegram_, this->max_telegram_len_);
|
||||
ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->telegram_len_);
|
||||
ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
|
||||
|
||||
parse_telegram();
|
||||
telegram_len_ = 0;
|
||||
this->parse_telegram();
|
||||
|
||||
this->header_found_ = false;
|
||||
this->telegram_len_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!available()) {
|
||||
// baud rate is 115200 for encrypted data, this means a few byte should arrive every time
|
||||
// program runs faster than buffer loading then available() might return false in the middle
|
||||
delay(4); // Wait for data
|
||||
}
|
||||
}
|
||||
if (buffer_length > 0) {
|
||||
ESP_LOGW(TAG, "Timeout while waiting for encrypted data or invalid data received.");
|
||||
}
|
||||
}
|
||||
|
||||
bool Dsmr::parse_telegram() {
|
||||
MyData data;
|
||||
ESP_LOGV(TAG, "Trying to parse");
|
||||
ESP_LOGV(TAG, "Trying to parse telegram");
|
||||
this->stop_requesting_data_();
|
||||
::dsmr::ParseResult<void> res =
|
||||
::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false,
|
||||
::dsmr::P1Parser::parse(&data, this->telegram_, this->telegram_len_, false,
|
||||
this->crc_check_); // Parse telegram according to data definition. Ignore unknown values.
|
||||
if (res.err) {
|
||||
// Parsing error, show it
|
||||
auto err_str = res.fullError(telegram_, telegram_ + telegram_len_);
|
||||
auto err_str = res.fullError(this->telegram_, this->telegram_ + this->telegram_len_);
|
||||
ESP_LOGE(TAG, "%s", err_str.c_str());
|
||||
return false;
|
||||
} else {
|
||||
this->status_clear_warning();
|
||||
publish_sensors(data);
|
||||
this->publish_sensors(data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void Dsmr::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "dsmr:");
|
||||
ESP_LOGCONFIG(TAG, "DSMR:");
|
||||
ESP_LOGCONFIG(TAG, " Max telegram length: %d", this->max_telegram_len_);
|
||||
|
||||
if (this->request_pin_ != nullptr) {
|
||||
LOG_PIN(" Request Pin: ", this->request_pin_);
|
||||
}
|
||||
if (this->request_interval_ > 0) {
|
||||
ESP_LOGCONFIG(TAG, " Request Interval: %.1fs", this->request_interval_ / 1e3f);
|
||||
}
|
||||
|
||||
#define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_);
|
||||
DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
|
||||
@@ -174,26 +270,36 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
|
||||
if (decryption_key.length() == 0) {
|
||||
ESP_LOGI(TAG, "Disabling decryption");
|
||||
this->decryption_key_.clear();
|
||||
if (this->encrypted_telegram_ != nullptr) {
|
||||
delete[] this->encrypted_telegram_;
|
||||
this->encrypted_telegram_ = nullptr;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (decryption_key.length() != 32) {
|
||||
ESP_LOGE(TAG, "Error, decryption key must be 32 character long.");
|
||||
ESP_LOGE(TAG, "Error, decryption key must be 32 character long");
|
||||
return;
|
||||
}
|
||||
this->decryption_key_.clear();
|
||||
|
||||
ESP_LOGI(TAG, "Decryption key is set.");
|
||||
ESP_LOGI(TAG, "Decryption key is set");
|
||||
// Verbose level prints decryption key
|
||||
ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str());
|
||||
|
||||
char temp[3] = {0};
|
||||
for (int i = 0; i < 16; i++) {
|
||||
strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
|
||||
decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
|
||||
this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
|
||||
}
|
||||
|
||||
if (this->encrypted_telegram_ == nullptr) {
|
||||
this->encrypted_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
void Dsmr::set_max_telegram_length(size_t length) { max_telegram_len_ = length; }
|
||||
|
||||
} // namespace dsmr
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -16,9 +16,7 @@
|
||||
namespace esphome {
|
||||
namespace dsmr {
|
||||
|
||||
static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500;
|
||||
static constexpr uint32_t MAX_BYTES_PER_LOOP = 50;
|
||||
static constexpr uint32_t POLL_TIMEOUT = 1000;
|
||||
static constexpr uint32_t READ_TIMEOUT_MS = 200;
|
||||
|
||||
using namespace ::dsmr::fields;
|
||||
|
||||
@@ -53,6 +51,7 @@ class Dsmr : public Component, public uart::UARTDevice {
|
||||
public:
|
||||
Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {}
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
|
||||
bool parse_telegram();
|
||||
@@ -73,6 +72,11 @@ class Dsmr : public Component, public uart::UARTDevice {
|
||||
|
||||
void set_decryption_key(const std::string &decryption_key);
|
||||
|
||||
void set_max_telegram_length(size_t length);
|
||||
|
||||
void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
|
||||
void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
|
||||
|
||||
// Sensor setters
|
||||
#define DSMR_SET_SENSOR(s) \
|
||||
void set_##s(sensor::Sensor *sensor) { s_##s##_ = sensor; }
|
||||
@@ -86,9 +90,33 @@ class Dsmr : public Component, public uart::UARTDevice {
|
||||
void receive_telegram_();
|
||||
void receive_encrypted_();
|
||||
|
||||
/// Wait for UART data to become available within the read timeout.
|
||||
///
|
||||
/// The smart meter might provide data in chunks, causing available() to
|
||||
/// return 0. When we're already reading a telegram, then we don't return
|
||||
/// right away (to handle further data in an upcoming loop) but wait a
|
||||
/// little while using this method to see if more data are incoming.
|
||||
/// By not returning, we prevent other components from taking so much
|
||||
/// time that the UART RX buffer overflows and bytes of the telegram get
|
||||
/// lost in the process.
|
||||
bool available_within_timeout_();
|
||||
|
||||
// Data request
|
||||
GPIOPin *request_pin_{nullptr};
|
||||
uint32_t request_interval_{0};
|
||||
uint32_t last_request_time_{0};
|
||||
bool requesting_data_{false};
|
||||
bool ready_to_request_data_();
|
||||
bool request_interval_reached_();
|
||||
void start_requesting_data_();
|
||||
void stop_requesting_data_();
|
||||
|
||||
// Telegram buffer
|
||||
char telegram_[MAX_TELEGRAM_LENGTH];
|
||||
size_t max_telegram_len_;
|
||||
char *telegram_{nullptr};
|
||||
int telegram_len_{0};
|
||||
uint8_t *encrypted_telegram_{nullptr};
|
||||
int encrypted_telegram_len_{0};
|
||||
|
||||
// Serial parser
|
||||
bool header_found_{false};
|
||||
|
||||
@@ -75,14 +75,14 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional("total_exported_energy"): sensor.sensor_schema(
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_NONE,
|
||||
),
|
||||
cv.Optional("power_delivered"): sensor.sensor_schema(
|
||||
@@ -166,42 +166,42 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema(
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema(
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema(
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema(
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema(
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE,
|
||||
ICON_EMPTY,
|
||||
3,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional("voltage_l1"): sensor.sensor_schema(
|
||||
|
||||
@@ -12,7 +12,6 @@ void DutyCycleSensor::setup() {
|
||||
this->pin_->setup();
|
||||
this->store_.pin = this->pin_->to_isr();
|
||||
this->store_.last_level = this->pin_->digital_read();
|
||||
this->last_update_ = micros();
|
||||
this->store_.last_interrupt = micros();
|
||||
|
||||
this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE);
|
||||
@@ -24,19 +23,20 @@ void DutyCycleSensor::dump_config() {
|
||||
}
|
||||
void DutyCycleSensor::update() {
|
||||
const uint32_t now = micros();
|
||||
const bool level = this->store_.last_level;
|
||||
const uint32_t last_interrupt = this->store_.last_interrupt;
|
||||
uint32_t on_time = this->store_.on_time;
|
||||
if (this->last_update_ != 0) {
|
||||
const bool level = this->store_.last_level;
|
||||
const uint32_t last_interrupt = this->store_.last_interrupt;
|
||||
uint32_t on_time = this->store_.on_time;
|
||||
|
||||
if (level)
|
||||
on_time += now - last_interrupt;
|
||||
if (level)
|
||||
on_time += now - last_interrupt;
|
||||
|
||||
const float total_time = float(now - this->last_update_);
|
||||
|
||||
const float value = (on_time / total_time) * 100.0f;
|
||||
ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value);
|
||||
this->publish_state(value);
|
||||
const float total_time = float(now - this->last_update_);
|
||||
|
||||
const float value = (on_time / total_time) * 100.0f;
|
||||
ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value);
|
||||
this->publish_state(value);
|
||||
}
|
||||
this->store_.on_time = 0;
|
||||
this->store_.last_interrupt = now;
|
||||
this->last_update_ = now;
|
||||
|
||||
@@ -30,7 +30,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent {
|
||||
InternalGPIOPin *pin_;
|
||||
|
||||
DutyCycleSensorStore store_{};
|
||||
uint32_t last_update_;
|
||||
uint32_t last_update_{0};
|
||||
};
|
||||
|
||||
} // namespace duty_cycle
|
||||
|
||||
@@ -7,6 +7,7 @@ from esphome.helpers import write_file_if_changed
|
||||
from esphome.const import (
|
||||
CONF_BOARD,
|
||||
CONF_FRAMEWORK,
|
||||
CONF_SOURCE,
|
||||
CONF_TYPE,
|
||||
CONF_VARIANT,
|
||||
CONF_VERSION,
|
||||
@@ -21,14 +22,16 @@ from esphome.core import CORE, HexInt
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from .const import (
|
||||
from .const import ( # noqa
|
||||
KEY_BOARD,
|
||||
KEY_ESP32,
|
||||
KEY_SDKCONFIG_OPTIONS,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_FRIENDLY,
|
||||
VARIANTS,
|
||||
)
|
||||
from .boards import BOARD_TO_VARIANT
|
||||
|
||||
# force import gpio to register pin schema
|
||||
from .gpio import esp32_pin_to_code # noqa
|
||||
@@ -49,7 +52,7 @@ def set_core_data(config):
|
||||
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
|
||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
|
||||
config[CONF_FRAMEWORK][CONF_VERSION_HINT]
|
||||
config[CONF_FRAMEWORK][CONF_VERSION]
|
||||
)
|
||||
CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD]
|
||||
CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT]
|
||||
@@ -90,6 +93,13 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
|
||||
return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
|
||||
|
||||
def _format_framework_espidf_version(ver: cv.Version) -> str:
|
||||
# format the given arduino (https://github.com/espressif/esp-idf/releases) version to
|
||||
# a PIO platformio/framework-espidf value
|
||||
# List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
|
||||
return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
|
||||
|
||||
# NOTE: Keep this in mind when updating the recommended version:
|
||||
# * New framework historically have had some regressions, especially for WiFi.
|
||||
# The new version needs to be thoroughly validated before changing the
|
||||
@@ -119,119 +129,123 @@ ESP_IDF_PLATFORM_VERSION = cv.Version(3, 3, 2)
|
||||
def _arduino_check_versions(value):
|
||||
value = value.copy()
|
||||
lookups = {
|
||||
"dev": ("https://github.com/espressif/arduino-esp32.git", cv.Version(2, 0, 0)),
|
||||
"latest": ("", cv.Version(1, 0, 3)),
|
||||
"recommended": (
|
||||
_format_framework_arduino_version(RECOMMENDED_ARDUINO_FRAMEWORK_VERSION),
|
||||
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION,
|
||||
),
|
||||
"dev": (cv.Version(2, 0, 0), "https://github.com/espressif/arduino-esp32.git"),
|
||||
"latest": (cv.Version(1, 0, 6), None),
|
||||
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
|
||||
}
|
||||
ver_value = value[CONF_VERSION]
|
||||
default_ver_hint = None
|
||||
if ver_value.lower() in lookups:
|
||||
default_ver_hint = str(lookups[ver_value.lower()][1])
|
||||
ver_value = lookups[ver_value.lower()][0]
|
||||
|
||||
if value[CONF_VERSION] in lookups:
|
||||
if CONF_SOURCE in value:
|
||||
raise cv.Invalid(
|
||||
"Framework version needs to be explicitly specified when custom source is used."
|
||||
)
|
||||
|
||||
version, source = lookups[value[CONF_VERSION]]
|
||||
else:
|
||||
with cv.suppress_invalid():
|
||||
ver = cv.Version.parse(cv.version_number(value))
|
||||
if ver <= cv.Version(1, 0, 3):
|
||||
ver_value = f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
else:
|
||||
ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
default_ver_hint = str(ver)
|
||||
value[CONF_VERSION] = ver_value
|
||||
version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
|
||||
source = value.get(CONF_SOURCE, None)
|
||||
|
||||
if CONF_VERSION_HINT not in value and default_ver_hint is None:
|
||||
raise cv.Invalid("Needs a version hint to understand the framework version")
|
||||
value[CONF_VERSION] = str(version)
|
||||
value[CONF_SOURCE] = source or _format_framework_arduino_version(version)
|
||||
|
||||
ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint)
|
||||
value[CONF_VERSION_HINT] = ver_hint_s
|
||||
plat_ver = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION)
|
||||
value[CONF_PLATFORM_VERSION] = str(plat_ver)
|
||||
value[CONF_PLATFORM_VERSION] = value.get(
|
||||
CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION))
|
||||
)
|
||||
|
||||
if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
|
||||
if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
|
||||
_LOGGER.warning(
|
||||
"The selected arduino framework version is not the recommended one"
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"If there are connectivity or build issues please remove the manual version"
|
||||
"The selected Arduino framework version is not the recommended one. "
|
||||
"If there are connectivity or build issues please remove the manual version."
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def _format_framework_espidf_version(ver: cv.Version) -> str:
|
||||
# format the given arduino (https://github.com/espressif/esp-idf/releases) version to
|
||||
# a PIO platformio/framework-espidf value
|
||||
# List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
|
||||
return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
|
||||
|
||||
def _esp_idf_check_versions(value):
|
||||
value = value.copy()
|
||||
lookups = {
|
||||
"dev": ("https://github.com/espressif/esp-idf.git", cv.Version(4, 3, 1)),
|
||||
"latest": ("", cv.Version(4, 3, 0)),
|
||||
"recommended": (
|
||||
_format_framework_espidf_version(RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION),
|
||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION,
|
||||
),
|
||||
"dev": (cv.Version(4, 3, 1), "https://github.com/espressif/esp-idf.git"),
|
||||
"latest": (cv.Version(4, 3, 0), None),
|
||||
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
|
||||
}
|
||||
ver_value = value[CONF_VERSION]
|
||||
default_ver_hint = None
|
||||
if ver_value.lower() in lookups:
|
||||
default_ver_hint = str(lookups[ver_value.lower()][1])
|
||||
ver_value = lookups[ver_value.lower()][0]
|
||||
|
||||
if value[CONF_VERSION] in lookups:
|
||||
if CONF_SOURCE in value:
|
||||
raise cv.Invalid(
|
||||
"Framework version needs to be explicitly specified when custom source is used."
|
||||
)
|
||||
|
||||
version, source = lookups[value[CONF_VERSION]]
|
||||
else:
|
||||
with cv.suppress_invalid():
|
||||
ver = cv.Version.parse(cv.version_number(value))
|
||||
ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
default_ver_hint = str(ver)
|
||||
value[CONF_VERSION] = ver_value
|
||||
version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
|
||||
source = value.get(CONF_SOURCE, None)
|
||||
|
||||
if CONF_VERSION_HINT not in value and default_ver_hint is None:
|
||||
raise cv.Invalid("Needs a version hint to understand the framework version")
|
||||
if version < cv.Version(4, 0, 0):
|
||||
raise cv.Invalid("Only ESP-IDF 4.0+ is supported.")
|
||||
|
||||
ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint)
|
||||
value[CONF_VERSION_HINT] = ver_hint_s
|
||||
if cv.Version.parse(ver_hint_s) < cv.Version(4, 0, 0):
|
||||
raise cv.Invalid("Only ESP-IDF 4.0+ is supported")
|
||||
if cv.Version.parse(ver_hint_s) != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION:
|
||||
value[CONF_VERSION] = str(version)
|
||||
value[CONF_SOURCE] = source or _format_framework_espidf_version(version)
|
||||
|
||||
value[CONF_PLATFORM_VERSION] = value.get(
|
||||
CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION))
|
||||
)
|
||||
|
||||
if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION:
|
||||
_LOGGER.warning(
|
||||
"The selected esp-idf framework version is not the recommended one"
|
||||
"The selected ESP-IDF framework version is not the recommended one. "
|
||||
"If there are connectivity or build issues please remove the manual version."
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"If there are connectivity or build issues please remove the manual version"
|
||||
)
|
||||
|
||||
plat_ver = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION)
|
||||
value[CONF_PLATFORM_VERSION] = str(plat_ver)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
CONF_VERSION_HINT = "version_hint"
|
||||
def _parse_platform_version(value):
|
||||
try:
|
||||
# if platform version is a valid version constraint, prefix the default package
|
||||
cv.platformio_version_constraint(value)
|
||||
return f"platformio/espressif32 @ {value}"
|
||||
except cv.Invalid:
|
||||
return value
|
||||
|
||||
|
||||
def _detect_variant(value):
|
||||
if CONF_VARIANT not in value:
|
||||
board = value[CONF_BOARD]
|
||||
if board not in BOARD_TO_VARIANT:
|
||||
raise cv.Invalid(
|
||||
"This board is unknown, please set the variant manually",
|
||||
path=[CONF_BOARD],
|
||||
)
|
||||
|
||||
value = value.copy()
|
||||
value[CONF_VARIANT] = BOARD_TO_VARIANT[board]
|
||||
|
||||
return value
|
||||
|
||||
|
||||
CONF_PLATFORM_VERSION = "platform_version"
|
||||
|
||||
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
|
||||
cv.Optional(CONF_VERSION_HINT): cv.version_number,
|
||||
cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
|
||||
cv.Optional(CONF_SOURCE): cv.string_strict,
|
||||
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
|
||||
}
|
||||
),
|
||||
_arduino_check_versions,
|
||||
)
|
||||
|
||||
CONF_SDKCONFIG_OPTIONS = "sdkconfig_options"
|
||||
ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
|
||||
cv.Optional(CONF_VERSION_HINT): cv.version_number,
|
||||
cv.Optional(CONF_SOURCE): cv.string_strict,
|
||||
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
|
||||
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
|
||||
cv.string_strict: cv.string_strict
|
||||
},
|
||||
cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
|
||||
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
|
||||
@@ -260,12 +274,11 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_BOARD): cv.string_strict,
|
||||
cv.Optional(CONF_VARIANT, default="ESP32"): cv.one_of(
|
||||
*VARIANTS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_VARIANT): cv.one_of(*VARIANTS, upper=True),
|
||||
cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA,
|
||||
}
|
||||
),
|
||||
_detect_variant,
|
||||
set_core_data,
|
||||
)
|
||||
|
||||
@@ -275,21 +288,21 @@ async def to_code(config):
|
||||
cg.add_build_flag("-DUSE_ESP32")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
|
||||
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
|
||||
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||
|
||||
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
||||
cg.add_platformio_option(
|
||||
"platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}"
|
||||
)
|
||||
cg.add_platformio_option("framework", "espidf")
|
||||
cg.add_build_flag("-DUSE_ESP_IDF")
|
||||
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
|
||||
cg.add_build_flag("-Wno-nonnull-compare")
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
[f"platformio/framework-espidf @ {conf[CONF_VERSION]}"],
|
||||
[f"platformio/framework-espidf @ {conf[CONF_SOURCE]}"],
|
||||
)
|
||||
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
|
||||
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
|
||||
@@ -298,6 +311,8 @@ async def to_code(config):
|
||||
)
|
||||
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False)
|
||||
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True)
|
||||
# Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms
|
||||
add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000)
|
||||
|
||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||
|
||||
@@ -311,15 +326,12 @@ async def to_code(config):
|
||||
)
|
||||
|
||||
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||
cg.add_platformio_option(
|
||||
"platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}"
|
||||
)
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
[f"platformio/framework-arduinoespressif32 @ {conf[CONF_VERSION]}"],
|
||||
[f"platformio/framework-arduinoespressif32 @ {conf[CONF_SOURCE]}"],
|
||||
)
|
||||
|
||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from .const import VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32C3
|
||||
|
||||
ESP32_BASE_PINS = {
|
||||
"TX": 1,
|
||||
"RX": 3,
|
||||
@@ -925,3 +927,125 @@ ESP32_BOARD_PINS = {
|
||||
},
|
||||
"xinabox_cw02": {"LED": 27},
|
||||
}
|
||||
|
||||
"""
|
||||
BOARD_TO_VARIANT generated with:
|
||||
|
||||
git clone https://github.com/platformio/platform-espressif32
|
||||
for x in platform-espressif32/boards/*.json; do
|
||||
mcu=$(jq -r .build.mcu <"$x");
|
||||
fname=$(basename "$x")
|
||||
board="${fname%.*}"
|
||||
variant=$(echo "$mcu" | tr '[:lower:]' '[:upper:]')
|
||||
echo " \"$board\": VARIANT_${variant},"
|
||||
done | sort
|
||||
"""
|
||||
|
||||
BOARD_TO_VARIANT = {
|
||||
"alksesp32": VARIANT_ESP32,
|
||||
"az-delivery-devkit-v4": VARIANT_ESP32,
|
||||
"bpi-bit": VARIANT_ESP32,
|
||||
"briki_abc_esp32": VARIANT_ESP32,
|
||||
"briki_mbc-wb_esp32": VARIANT_ESP32,
|
||||
"d-duino-32": VARIANT_ESP32,
|
||||
"esp320": VARIANT_ESP32,
|
||||
"esp32-c3-devkitm-1": VARIANT_ESP32C3,
|
||||
"esp32cam": VARIANT_ESP32,
|
||||
"esp32-devkitlipo": VARIANT_ESP32,
|
||||
"esp32dev": VARIANT_ESP32,
|
||||
"esp32doit-devkit-v1": VARIANT_ESP32,
|
||||
"esp32doit-espduino": VARIANT_ESP32,
|
||||
"esp32-evb": VARIANT_ESP32,
|
||||
"esp32-gateway": VARIANT_ESP32,
|
||||
"esp32-poe-iso": VARIANT_ESP32,
|
||||
"esp32-poe": VARIANT_ESP32,
|
||||
"esp32-pro": VARIANT_ESP32,
|
||||
"esp32-s2-kaluga-1": VARIANT_ESP32S2,
|
||||
"esp32-s2-saola-1": VARIANT_ESP32S2,
|
||||
"esp32thing_plus": VARIANT_ESP32,
|
||||
"esp32thing": VARIANT_ESP32,
|
||||
"esp32vn-iot-uno": VARIANT_ESP32,
|
||||
"espea32": VARIANT_ESP32,
|
||||
"espectro32": VARIANT_ESP32,
|
||||
"espino32": VARIANT_ESP32,
|
||||
"esp-wrover-kit": VARIANT_ESP32,
|
||||
"etboard": VARIANT_ESP32,
|
||||
"featheresp32-s2": VARIANT_ESP32S2,
|
||||
"featheresp32": VARIANT_ESP32,
|
||||
"firebeetle32": VARIANT_ESP32,
|
||||
"fm-devkit": VARIANT_ESP32,
|
||||
"frogboard": VARIANT_ESP32,
|
||||
"healthypi4": VARIANT_ESP32,
|
||||
"heltec_wifi_kit_32_v2": VARIANT_ESP32,
|
||||
"heltec_wifi_kit_32": VARIANT_ESP32,
|
||||
"heltec_wifi_lora_32_V2": VARIANT_ESP32,
|
||||
"heltec_wifi_lora_32": VARIANT_ESP32,
|
||||
"heltec_wireless_stick_lite": VARIANT_ESP32,
|
||||
"heltec_wireless_stick": VARIANT_ESP32,
|
||||
"honeylemon": VARIANT_ESP32,
|
||||
"hornbill32dev": VARIANT_ESP32,
|
||||
"hornbill32minima": VARIANT_ESP32,
|
||||
"imbrios-logsens-v1p1": VARIANT_ESP32,
|
||||
"inex_openkb": VARIANT_ESP32,
|
||||
"intorobot": VARIANT_ESP32,
|
||||
"iotaap_magnolia": VARIANT_ESP32,
|
||||
"iotbusio": VARIANT_ESP32,
|
||||
"iotbusproteus": VARIANT_ESP32,
|
||||
"kits-edu": VARIANT_ESP32,
|
||||
"labplus_mpython": VARIANT_ESP32,
|
||||
"lolin32_lite": VARIANT_ESP32,
|
||||
"lolin32": VARIANT_ESP32,
|
||||
"lolin_d32_pro": VARIANT_ESP32,
|
||||
"lolin_d32": VARIANT_ESP32,
|
||||
"lopy4": VARIANT_ESP32,
|
||||
"lopy": VARIANT_ESP32,
|
||||
"m5stack-atom": VARIANT_ESP32,
|
||||
"m5stack-core2": VARIANT_ESP32,
|
||||
"m5stack-core-esp32": VARIANT_ESP32,
|
||||
"m5stack-coreink": VARIANT_ESP32,
|
||||
"m5stack-fire": VARIANT_ESP32,
|
||||
"m5stack-grey": VARIANT_ESP32,
|
||||
"m5stack-timer-cam": VARIANT_ESP32,
|
||||
"m5stick-c": VARIANT_ESP32,
|
||||
"magicbit": VARIANT_ESP32,
|
||||
"mgbot-iotik32a": VARIANT_ESP32,
|
||||
"mgbot-iotik32b": VARIANT_ESP32,
|
||||
"mhetesp32devkit": VARIANT_ESP32,
|
||||
"mhetesp32minikit": VARIANT_ESP32,
|
||||
"microduino-core-esp32": VARIANT_ESP32,
|
||||
"nano32": VARIANT_ESP32,
|
||||
"nina_w10": VARIANT_ESP32,
|
||||
"node32s": VARIANT_ESP32,
|
||||
"nodemcu-32s": VARIANT_ESP32,
|
||||
"nscreen-32": VARIANT_ESP32,
|
||||
"odroid_esp32": VARIANT_ESP32,
|
||||
"onehorse32dev": VARIANT_ESP32,
|
||||
"oroca_edubot": VARIANT_ESP32,
|
||||
"pico32": VARIANT_ESP32,
|
||||
"piranha_esp32": VARIANT_ESP32,
|
||||
"pocket_32": VARIANT_ESP32,
|
||||
"pycom_gpy": VARIANT_ESP32,
|
||||
"qchip": VARIANT_ESP32,
|
||||
"quantum": VARIANT_ESP32,
|
||||
"sensesiot_weizen": VARIANT_ESP32,
|
||||
"sg-o_airMon": VARIANT_ESP32,
|
||||
"s_odi_ultra": VARIANT_ESP32,
|
||||
"sparkfun_lora_gateway_1-channel": VARIANT_ESP32,
|
||||
"tinypico": VARIANT_ESP32,
|
||||
"ttgo-lora32-v1": VARIANT_ESP32,
|
||||
"ttgo-lora32-v21": VARIANT_ESP32,
|
||||
"ttgo-lora32-v2": VARIANT_ESP32,
|
||||
"ttgo-t1": VARIANT_ESP32,
|
||||
"ttgo-t7-v13-mini32": VARIANT_ESP32,
|
||||
"ttgo-t7-v14-mini32": VARIANT_ESP32,
|
||||
"ttgo-t-beam": VARIANT_ESP32,
|
||||
"ttgo-t-watch": VARIANT_ESP32,
|
||||
"turta_iot_node": VARIANT_ESP32,
|
||||
"vintlabs-devkit-v1": VARIANT_ESP32,
|
||||
"wemosbat": VARIANT_ESP32,
|
||||
"wemos_d1_mini32": VARIANT_ESP32,
|
||||
"wesp32": VARIANT_ESP32,
|
||||
"widora-air": VARIANT_ESP32,
|
||||
"wifiduino32": VARIANT_ESP32,
|
||||
"xinabox_cw02": VARIANT_ESP32,
|
||||
}
|
||||
|
||||
@@ -18,4 +18,12 @@ VARIANTS = [
|
||||
VARIANT_ESP32H2,
|
||||
]
|
||||
|
||||
VARIANT_FRIENDLY = {
|
||||
VARIANT_ESP32: "ESP32",
|
||||
VARIANT_ESP32S2: "ESP32-S2",
|
||||
VARIANT_ESP32S3: "ESP32-S3",
|
||||
VARIANT_ESP32C3: "ESP32-C3",
|
||||
VARIANT_ESP32H2: "ESP32-H2",
|
||||
}
|
||||
|
||||
esp32_ns = cg.esphome_ns.namespace("esp32")
|
||||
|
||||
@@ -21,11 +21,7 @@ void IRAM_ATTR HOT yield() { vPortYield(); }
|
||||
uint32_t IRAM_ATTR HOT millis() { return (uint32_t)(esp_timer_get_time() / 1000ULL); }
|
||||
void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); }
|
||||
uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); }
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) {
|
||||
auto start = (uint64_t) esp_timer_get_time();
|
||||
while (((uint64_t) esp_timer_get_time()) - start < us)
|
||||
;
|
||||
}
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||
void arch_restart() {
|
||||
esp_restart();
|
||||
// restart() doesn't always end execution
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
@@ -17,10 +18,24 @@ import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from . import boards
|
||||
from .const import KEY_BOARD, KEY_ESP32, esp32_ns
|
||||
from .const import (
|
||||
KEY_BOARD,
|
||||
KEY_ESP32,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_ESP32H2,
|
||||
esp32_ns,
|
||||
)
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports
|
||||
from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports
|
||||
from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports
|
||||
from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports
|
||||
from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports
|
||||
|
||||
|
||||
IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin)
|
||||
@@ -59,65 +74,61 @@ def _translate_pin(value):
|
||||
return _lookup_pin(value)
|
||||
|
||||
|
||||
_ESP_SDIO_PINS = {
|
||||
6: "Flash Clock",
|
||||
7: "Flash Data 0",
|
||||
8: "Flash Data 1",
|
||||
11: "Flash Command",
|
||||
@dataclass
|
||||
class ESP32ValidationFunctions:
|
||||
pin_validation: Any
|
||||
usage_validation: Any
|
||||
|
||||
|
||||
_esp32_validations = {
|
||||
VARIANT_ESP32: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_validate_gpio_pin, usage_validation=esp32_validate_supports
|
||||
),
|
||||
VARIANT_ESP32S2: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_s2_validate_gpio_pin,
|
||||
usage_validation=esp32_s2_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32C3: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_c3_validate_gpio_pin,
|
||||
usage_validation=esp32_c3_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32S3: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_s3_validate_gpio_pin,
|
||||
usage_validation=esp32_s3_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32H2: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_h2_validate_gpio_pin,
|
||||
usage_validation=esp32_h2_validate_supports,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def validate_gpio_pin(value):
|
||||
value = _translate_pin(value)
|
||||
if value < 0 or value > 39:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
|
||||
if value in _ESP_SDIO_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})"
|
||||
)
|
||||
if 9 <= value <= 10:
|
||||
_LOGGER.warning(
|
||||
"Pin %s (9-10) might already be used by the "
|
||||
"flash interface in QUAD IO flash mode.",
|
||||
value,
|
||||
)
|
||||
if value in (20, 24, 28, 29, 30, 31):
|
||||
# These pins are not exposed in GPIO mux (reason unknown)
|
||||
# but they're missing from IO_MUX list in datasheet
|
||||
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.")
|
||||
return value
|
||||
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||
if variant not in _esp32_validations:
|
||||
raise cv.Invalid("Unsupported ESP32 variant {variant}")
|
||||
|
||||
return _esp32_validations[variant].pin_validation(value)
|
||||
|
||||
|
||||
def validate_supports(value):
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
is_output = mode[CONF_OUTPUT]
|
||||
is_open_drain = mode[CONF_OPEN_DRAIN]
|
||||
is_pullup = mode[CONF_PULLUP]
|
||||
is_pulldown = mode[CONF_PULLDOWN]
|
||||
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||
if variant not in _esp32_validations:
|
||||
raise cv.Invalid("Unsupported ESP32 variant {variant}")
|
||||
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
if is_output and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support output pin mode.",
|
||||
[CONF_MODE, CONF_OUTPUT],
|
||||
)
|
||||
if is_open_drain and not is_output:
|
||||
raise cv.Invalid(
|
||||
"Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN]
|
||||
)
|
||||
if is_pullup and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP]
|
||||
)
|
||||
if is_pulldown and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
|
||||
)
|
||||
|
||||
value = _esp32_validations[variant].usage_validation(value)
|
||||
if CORE.using_arduino:
|
||||
# (input, output, open_drain, pullup, pulldown)
|
||||
supported_modes = {
|
||||
@@ -138,7 +149,6 @@ def validate_supports(value):
|
||||
"This pin mode is not supported on ESP32 for arduino frameworks",
|
||||
[CONF_MODE],
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,22 @@ namespace esp32 {
|
||||
|
||||
static const char *const TAG = "esp32";
|
||||
|
||||
static int IRAM_ATTR flags_to_mode(gpio::Flags flags) {
|
||||
if (flags == gpio::FLAG_INPUT) {
|
||||
return INPUT;
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
return OUTPUT;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||
return INPUT_PULLUP;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
|
||||
return INPUT_PULLDOWN;
|
||||
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
|
||||
return OUTPUT_OPEN_DRAIN;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct ISRPinArg {
|
||||
uint8_t pin;
|
||||
bool inverted;
|
||||
@@ -43,22 +59,9 @@ void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, g
|
||||
|
||||
attachInterruptArg(pin_, func, arg, arduino_mode);
|
||||
}
|
||||
|
||||
void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
uint8_t mode;
|
||||
if (flags == gpio::FLAG_INPUT) {
|
||||
mode = INPUT;
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
mode = OUTPUT;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||
mode = INPUT_PULLUP;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
|
||||
mode = INPUT_PULLDOWN;
|
||||
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
|
||||
mode = OUTPUT_OPEN_DRAIN;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
pinMode(pin_, mode); // NOLINT
|
||||
pinMode(pin_, flags_to_mode(flags)); // NOLINT
|
||||
}
|
||||
|
||||
std::string ArduinoInternalGPIOPin::dump_summary() const {
|
||||
@@ -101,6 +104,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
pinMode(arg->pin, flags_to_mode(flags)); // NOLINT
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
77
esphome/components/esp32/gpio_esp32.py
Normal file
77
esphome/components/esp32/gpio_esp32.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import logging
|
||||
|
||||
from esphome.const import (
|
||||
CONF_INPUT,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
|
||||
|
||||
_ESP_SDIO_PINS = {
|
||||
6: "Flash Clock",
|
||||
7: "Flash Data 0",
|
||||
8: "Flash Data 1",
|
||||
11: "Flash Command",
|
||||
}
|
||||
|
||||
_ESP32_STRAPPING_PINS = {0, 2, 4, 15}
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_validate_gpio_pin(value):
|
||||
if value < 0 or value > 39:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
|
||||
if value in _ESP_SDIO_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})"
|
||||
)
|
||||
if 9 <= value <= 10:
|
||||
_LOGGER.warning(
|
||||
"Pin %s (9-10) might already be used by the "
|
||||
"flash interface in QUAD IO flash mode.",
|
||||
value,
|
||||
)
|
||||
if value in _ESP32_STRAPPING_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is a Strapping PIN and should be avoided.\n"
|
||||
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
|
||||
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
|
||||
value,
|
||||
)
|
||||
if value in (20, 24, 28, 29, 30, 31):
|
||||
# These pins are not exposed in GPIO mux (reason unknown)
|
||||
# but they're missing from IO_MUX list in datasheet
|
||||
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.")
|
||||
return value
|
||||
|
||||
|
||||
def esp32_validate_supports(value):
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
is_output = mode[CONF_OUTPUT]
|
||||
is_pullup = mode[CONF_PULLUP]
|
||||
is_pulldown = mode[CONF_PULLDOWN]
|
||||
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
if is_output and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support output pin mode.",
|
||||
[CONF_MODE, CONF_OUTPUT],
|
||||
)
|
||||
if is_pullup and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP]
|
||||
)
|
||||
if is_pulldown and 34 <= num <= 39:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
|
||||
)
|
||||
|
||||
return value
|
||||
53
esphome/components/esp32/gpio_esp32_c3.py
Normal file
53
esphome/components/esp32/gpio_esp32_c3.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import logging
|
||||
|
||||
from esphome.const import (
|
||||
CONF_INPUT,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
|
||||
_ESP32C3_SPI_PSRAM_PINS = {
|
||||
12: "SPIHD",
|
||||
13: "SPIWP",
|
||||
14: "SPICS0",
|
||||
15: "SPICLK",
|
||||
16: "SPID",
|
||||
17: "SPIQ",
|
||||
}
|
||||
|
||||
_ESP32C3_STRAPPING_PINS = {2, 8, 9}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_c3_validate_gpio_pin(value):
|
||||
if value < 0 or value > 21:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
|
||||
if value in _ESP32C3_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-C3s and is already used by the SPI/PSRAM interface (function: {_ESP32C3_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
if value in _ESP32C3_STRAPPING_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is a Strapping PIN and should be avoided.\n"
|
||||
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
|
||||
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
|
||||
value,
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_c3_validate_supports(value):
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 21:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
|
||||
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
return value
|
||||
11
esphome/components/esp32/gpio_esp32_h2.py
Normal file
11
esphome/components/esp32/gpio_esp32_h2.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import esphome.config_validation as cv
|
||||
|
||||
|
||||
def esp32_h2_validate_gpio_pin(value):
|
||||
# ESP32-H2 not yet supported
|
||||
raise cv.Invalid("ESP32-H2 isn't supported yet")
|
||||
|
||||
|
||||
def esp32_h2_validate_supports(value):
|
||||
# ESP32-H2 not yet supported
|
||||
raise cv.Invalid("ESP32-H2 isn't supported yet")
|
||||
80
esphome/components/esp32/gpio_esp32_s2.py
Normal file
80
esphome/components/esp32/gpio_esp32_s2.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import logging
|
||||
|
||||
from esphome.const import (
|
||||
CONF_INPUT,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
|
||||
import esphome.config_validation as cv
|
||||
|
||||
_ESP32S2_SPI_PSRAM_PINS = {
|
||||
26: "SPICS1",
|
||||
27: "SPIHD",
|
||||
28: "SPIWP",
|
||||
29: "SPICS0",
|
||||
30: "SPICLK",
|
||||
31: "SPIQ",
|
||||
32: "SPID",
|
||||
}
|
||||
|
||||
_ESP32S2_STRAPPING_PINS = {0, 45, 46}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_s2_validate_gpio_pin(value):
|
||||
if value < 0 or value > 46:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)")
|
||||
|
||||
if value in _ESP32S2_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-S2s and is already used by the SPI/PSRAM interface (function: {_ESP32S2_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
if value in _ESP32S2_STRAPPING_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is a Strapping PIN and should be avoided.\n"
|
||||
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
|
||||
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
|
||||
value,
|
||||
)
|
||||
|
||||
if value in (22, 23, 24, 25):
|
||||
# These pins are not exposed in GPIO mux (reason unknown)
|
||||
# but they're missing from IO_MUX list in datasheet
|
||||
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S2s.")
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_s2_validate_supports(value):
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
is_output = mode[CONF_OUTPUT]
|
||||
is_pullup = mode[CONF_PULLUP]
|
||||
is_pulldown = mode[CONF_PULLDOWN]
|
||||
|
||||
if num < 0 or num > 46:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)")
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
if is_output and num == 46:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} does not support output pin mode.",
|
||||
[CONF_MODE, CONF_OUTPUT],
|
||||
)
|
||||
if is_pullup and num == 46:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} does not support pullups.", [CONF_MODE, CONF_PULLUP]
|
||||
)
|
||||
if is_pulldown and num == 46:
|
||||
raise cv.Invalid(
|
||||
f"GPIO{num} does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
|
||||
)
|
||||
|
||||
return value
|
||||
74
esphome/components/esp32/gpio_esp32_s3.py
Normal file
74
esphome/components/esp32/gpio_esp32_s3.py
Normal file
@@ -0,0 +1,74 @@
|
||||
import logging
|
||||
|
||||
from esphome.const import (
|
||||
CONF_INPUT,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
)
|
||||
|
||||
import esphome.config_validation as cv
|
||||
|
||||
_ESP_32S3_SPI_PSRAM_PINS = {
|
||||
26: "SPICS1",
|
||||
27: "SPIHD",
|
||||
28: "SPIWP",
|
||||
29: "SPICS0",
|
||||
30: "SPICLK",
|
||||
31: "SPIQ",
|
||||
32: "SPID",
|
||||
}
|
||||
|
||||
_ESP_32_ESP32_S3R8_PSRAM_PINS = {
|
||||
33: "SPIIO4",
|
||||
34: "SPIIO5",
|
||||
35: "SPIIO6",
|
||||
36: "SPIIO7",
|
||||
37: "SPIDQS",
|
||||
}
|
||||
|
||||
_ESP_32S3_STRAPPING_PINS = {0, 3, 45, 46}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_s3_validate_gpio_pin(value):
|
||||
if value < 0 or value > 48:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)")
|
||||
|
||||
if value in _ESP_32S3_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-S3s and is already used by the SPI/PSRAM interface(function: {_ESP_32S3_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
if value in _ESP_32_ESP32_S3R8_PSRAM_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is used by the PSRAM interface on ESP32-S3R8 / ESP32-S3R8V and should be avoided on these models",
|
||||
value,
|
||||
)
|
||||
|
||||
if value in _ESP_32S3_STRAPPING_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is a Strapping PIN and should be avoided.\n"
|
||||
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
|
||||
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
|
||||
value,
|
||||
)
|
||||
|
||||
if value in (22, 23, 24, 25):
|
||||
# These pins are not exposed in GPIO mux (reason unknown)
|
||||
# but they're missing from IO_MUX list in datasheet
|
||||
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S3s.")
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_s3_validate_supports(value):
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 48:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)")
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
return value
|
||||
@@ -10,38 +10,7 @@ static const char *const TAG = "esp32";
|
||||
|
||||
bool IDFInternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
struct ISRPinArg {
|
||||
gpio_num_t pin;
|
||||
bool inverted;
|
||||
};
|
||||
|
||||
ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const {
|
||||
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
arg->pin = pin_;
|
||||
arg->inverted = inverted_;
|
||||
return ISRInternalGPIOPin((void *) arg);
|
||||
}
|
||||
|
||||
void IDFInternalGPIOPin::setup() {
|
||||
pin_mode(flags_);
|
||||
gpio_set_drive_capability(pin_, drive_strength_);
|
||||
}
|
||||
|
||||
void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
gpio_config_t conf{};
|
||||
conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(pin_);
|
||||
conf.mode = flags_to_mode(flags);
|
||||
conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||
conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
|
||||
conf.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_config(&conf);
|
||||
}
|
||||
|
||||
bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
|
||||
|
||||
void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
|
||||
|
||||
gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) {
|
||||
static gpio_mode_t IRAM_ATTR flags_to_mode(gpio::Flags flags) {
|
||||
flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN));
|
||||
if (flags == gpio::FLAG_NONE) {
|
||||
return GPIO_MODE_DISABLE;
|
||||
@@ -61,6 +30,18 @@ gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) {
|
||||
}
|
||||
}
|
||||
|
||||
struct ISRPinArg {
|
||||
gpio_num_t pin;
|
||||
bool inverted;
|
||||
};
|
||||
|
||||
ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const {
|
||||
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
arg->pin = pin_;
|
||||
arg->inverted = inverted_;
|
||||
return ISRInternalGPIOPin((void *) arg);
|
||||
}
|
||||
|
||||
void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
|
||||
gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
|
||||
switch (type) {
|
||||
@@ -99,6 +80,35 @@ std::string IDFInternalGPIOPin::dump_summary() const {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void IDFInternalGPIOPin::setup() {
|
||||
gpio_config_t conf{};
|
||||
conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(pin_);
|
||||
conf.mode = flags_to_mode(flags_);
|
||||
conf.pull_up_en = flags_ & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||
conf.pull_down_en = flags_ & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
|
||||
conf.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_config(&conf);
|
||||
gpio_set_drive_capability(pin_, drive_strength_);
|
||||
}
|
||||
|
||||
void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
// can't call gpio_config here because that logs in esp-idf which may cause issues
|
||||
gpio_set_direction(pin_, flags_to_mode(flags));
|
||||
gpio_pull_mode_t pull_mode = GPIO_FLOATING;
|
||||
if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) {
|
||||
pull_mode = GPIO_PULLUP_PULLDOWN;
|
||||
} else if (flags & gpio::FLAG_PULLUP) {
|
||||
pull_mode = GPIO_PULLUP_ONLY;
|
||||
} else if (flags & gpio::FLAG_PULLDOWN) {
|
||||
pull_mode = GPIO_PULLDOWN_ONLY;
|
||||
}
|
||||
gpio_set_pull_mode(pin_, pull_mode);
|
||||
}
|
||||
|
||||
bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
|
||||
void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
|
||||
void IDFInternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); }
|
||||
|
||||
} // namespace esp32
|
||||
|
||||
using namespace esp32;
|
||||
@@ -114,6 +124,19 @@ void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
|
||||
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
|
||||
// not supported
|
||||
}
|
||||
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
gpio_set_direction(arg->pin, flags_to_mode(flags));
|
||||
gpio_pull_mode_t pull_mode = GPIO_FLOATING;
|
||||
if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) {
|
||||
pull_mode = GPIO_PULLUP_PULLDOWN;
|
||||
} else if (flags & gpio::FLAG_PULLUP) {
|
||||
pull_mode = GPIO_PULLUP_ONLY;
|
||||
} else if (flags & gpio::FLAG_PULLDOWN) {
|
||||
pull_mode = GPIO_PULLDOWN_ONLY;
|
||||
}
|
||||
gpio_set_pull_mode(arg->pin, pull_mode);
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -18,13 +18,12 @@ class IDFInternalGPIOPin : public InternalGPIOPin {
|
||||
bool digital_read() override;
|
||||
void digital_write(bool value) override;
|
||||
std::string dump_summary() const override;
|
||||
void detach_interrupt() const override { gpio_intr_disable(pin_); }
|
||||
void detach_interrupt() const override;
|
||||
ISRInternalGPIOPin to_isr() const override;
|
||||
uint8_t get_pin() const override { return (uint8_t) pin_; }
|
||||
bool is_inverted() const override { return inverted_; }
|
||||
|
||||
protected:
|
||||
static gpio_mode_t flags_to_mode(gpio::Flags flags);
|
||||
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||
|
||||
gpio_num_t pin_;
|
||||
|
||||
@@ -57,7 +57,7 @@ void ESP32BLEBeacon::setup() {
|
||||
);
|
||||
}
|
||||
|
||||
float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::DATA; }
|
||||
float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::BLUETOOTH; }
|
||||
void ESP32BLEBeacon::ble_core_task(void *params) {
|
||||
ble_setup();
|
||||
|
||||
|
||||
@@ -98,19 +98,20 @@ bool BLEServer::create_device_characteristics_() {
|
||||
return true;
|
||||
}
|
||||
|
||||
BLEService *BLEServer::create_service(const uint8_t *uuid, bool advertise) {
|
||||
std::shared_ptr<BLEService> BLEServer::create_service(const uint8_t *uuid, bool advertise) {
|
||||
return this->create_service(ESPBTUUID::from_raw(uuid), advertise);
|
||||
}
|
||||
BLEService *BLEServer::create_service(uint16_t uuid, bool advertise) {
|
||||
std::shared_ptr<BLEService> BLEServer::create_service(uint16_t uuid, bool advertise) {
|
||||
return this->create_service(ESPBTUUID::from_uint16(uuid), advertise);
|
||||
}
|
||||
BLEService *BLEServer::create_service(const std::string &uuid, bool advertise) {
|
||||
std::shared_ptr<BLEService> BLEServer::create_service(const std::string &uuid, bool advertise) {
|
||||
return this->create_service(ESPBTUUID::from_raw(uuid), advertise);
|
||||
}
|
||||
BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles, uint8_t inst_id) {
|
||||
std::shared_ptr<BLEService> BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles,
|
||||
uint8_t inst_id) {
|
||||
ESP_LOGV(TAG, "Creating service - %s", uuid.to_string().c_str());
|
||||
BLEService *service = new BLEService(uuid, num_handles, inst_id); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->services_.push_back(service);
|
||||
std::shared_ptr<BLEService> service = std::make_shared<BLEService>(uuid, num_handles, inst_id);
|
||||
this->services_.emplace_back(service);
|
||||
if (advertise) {
|
||||
esp32_ble::global_ble->get_advertising()->add_service_uuid(uuid);
|
||||
}
|
||||
@@ -149,12 +150,12 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto *service : this->services_) {
|
||||
for (const auto &service : this->services_) {
|
||||
service->gatts_event_handler(event, gatts_if, param);
|
||||
}
|
||||
}
|
||||
|
||||
float BLEServer::get_setup_priority() const { return setup_priority::BLUETOOTH - 10; }
|
||||
float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH + 10; }
|
||||
|
||||
void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); }
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "esphome/core/preferences.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -43,10 +44,11 @@ class BLEServer : public Component {
|
||||
void set_manufacturer(const std::string &manufacturer) { this->manufacturer_ = manufacturer; }
|
||||
void set_model(const std::string &model) { this->model_ = model; }
|
||||
|
||||
BLEService *create_service(const uint8_t *uuid, bool advertise = false);
|
||||
BLEService *create_service(uint16_t uuid, bool advertise = false);
|
||||
BLEService *create_service(const std::string &uuid, bool advertise = false);
|
||||
BLEService *create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15, uint8_t inst_id = 0);
|
||||
std::shared_ptr<BLEService> create_service(const uint8_t *uuid, bool advertise = false);
|
||||
std::shared_ptr<BLEService> create_service(uint16_t uuid, bool advertise = false);
|
||||
std::shared_ptr<BLEService> create_service(const std::string &uuid, bool advertise = false);
|
||||
std::shared_ptr<BLEService> create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15,
|
||||
uint8_t inst_id = 0);
|
||||
|
||||
esp_gatt_if_t get_gatts_if() { return this->gatts_if_; }
|
||||
uint32_t get_connected_client_count() { return this->connected_clients_; }
|
||||
@@ -74,8 +76,8 @@ class BLEServer : public Component {
|
||||
uint32_t connected_clients_{0};
|
||||
std::map<uint16_t, void *> clients_;
|
||||
|
||||
std::vector<BLEService *> services_;
|
||||
BLEService *device_information_service_;
|
||||
std::vector<std::shared_ptr<BLEService>> services_;
|
||||
std::shared_ptr<BLEService> device_information_service_;
|
||||
|
||||
std::vector<BLEServiceComponent *> service_components_;
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ from esphome.core import CORE
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
AUTO_LOAD = ["xiaomi_ble", "ruuvi_ble"]
|
||||
|
||||
CONF_ESP32_BLE_ID = "esp32_ble_id"
|
||||
CONF_SCAN_PARAMETERS = "scan_parameters"
|
||||
|
||||
@@ -40,6 +40,8 @@ uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) {
|
||||
return u;
|
||||
}
|
||||
|
||||
float ESP32BLETracker::get_setup_priority() const { return setup_priority::BLUETOOTH; }
|
||||
|
||||
void ESP32BLETracker::setup() {
|
||||
global_esp32_ble_tracker = this;
|
||||
this->scan_result_lock_ = xSemaphoreCreateMutex();
|
||||
|
||||
@@ -171,6 +171,7 @@ class ESP32BLETracker : public Component {
|
||||
/// Setup the FreeRTOS task and the Bluetooth stack.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void loop() override;
|
||||
|
||||
|
||||
@@ -2,10 +2,8 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import (
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_FREQUENCY,
|
||||
CONF_ID,
|
||||
CONF_NAME,
|
||||
CONF_PIN,
|
||||
CONF_SCL,
|
||||
CONF_SDA,
|
||||
@@ -17,8 +15,9 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
DEPENDENCIES = ["esp32", "api"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
|
||||
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
|
||||
@@ -63,11 +62,9 @@ CONF_TEST_PATTERN = "test_pattern"
|
||||
|
||||
camera_range_param = cv.int_range(min=-2, max=2)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESP32Camera),
|
||||
cv.Required(CONF_NAME): cv.string,
|
||||
cv.Optional(CONF_DISABLED_BY_DEFAULT, default=False): cv.boolean,
|
||||
cv.Required(CONF_DATA_PINS): cv.All(
|
||||
[pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8)
|
||||
),
|
||||
@@ -127,8 +124,8 @@ SETTERS = {
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME])
|
||||
cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await setup_entity(var, config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
for key, setter in SETTERS.items():
|
||||
|
||||
@@ -45,6 +45,7 @@ void ESP32Camera::dump_config() {
|
||||
auto conf = this->config_;
|
||||
ESP_LOGCONFIG(TAG, "ESP32 Camera:");
|
||||
ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str());
|
||||
ESP_LOGCONFIG(TAG, " Internal: %s", YESNO(this->internal_));
|
||||
#ifdef USE_ARDUINO
|
||||
ESP_LOGCONFIG(TAG, " Board Has PSRAM: %s", YESNO(psramFound()));
|
||||
#endif // USE_ARDUINO
|
||||
@@ -185,6 +186,7 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) {
|
||||
|
||||
global_esp32_camera = this;
|
||||
}
|
||||
ESP32Camera::ESP32Camera() : ESP32Camera("") {}
|
||||
void ESP32Camera::set_data_pins(std::array<uint8_t, 8> pins) {
|
||||
this->config_.pin_d0 = pins[0];
|
||||
this->config_.pin_d1 = pins[1];
|
||||
|
||||
@@ -54,6 +54,7 @@ enum ESP32CameraFrameSize {
|
||||
class ESP32Camera : public Component, public EntityBase {
|
||||
public:
|
||||
ESP32Camera(const std::string &name);
|
||||
ESP32Camera();
|
||||
void set_data_pins(std::array<uint8_t, 8> pins);
|
||||
void set_vsync_pin(uint8_t pin);
|
||||
void set_href_pin(uint8_t pin);
|
||||
|
||||
28
esphome/components/esp32_camera_web_server/__init__.py
Normal file
28
esphome/components/esp32_camera_web_server/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_ID, CONF_PORT, CONF_MODE
|
||||
|
||||
CODEOWNERS = ["@ayufan"]
|
||||
DEPENDENCIES = ["esp32_camera"]
|
||||
MULTI_CONF = True
|
||||
|
||||
esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server")
|
||||
CameraWebServer = esp32_camera_web_server_ns.class_("CameraWebServer", cg.Component)
|
||||
Mode = esp32_camera_web_server_ns.enum("Mode")
|
||||
|
||||
MODES = {"STREAM": Mode.STREAM, "SNAPSHOT": Mode.SNAPSHOT}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CameraWebServer),
|
||||
cv.Required(CONF_PORT): cv.port,
|
||||
cv.Required(CONF_MODE): cv.enum(MODES, upper=True),
|
||||
},
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
server = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(server.set_port(config[CONF_PORT]))
|
||||
cg.add(server.set_mode(config[CONF_MODE]))
|
||||
await cg.register_component(server, config)
|
||||
248
esphome/components/esp32_camera_web_server/camera_web_server.cpp
Normal file
248
esphome/components/esp32_camera_web_server/camera_web_server.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "camera_web_server.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <esp_http_server.h>
|
||||
#include <utility>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_camera_web_server {
|
||||
|
||||
static const int IMAGE_REQUEST_TIMEOUT = 2000;
|
||||
static const char *const TAG = "esp32_camera_web_server";
|
||||
|
||||
#define PART_BOUNDARY "123456789000000000000987654321"
|
||||
#define CONTENT_TYPE "image/jpeg"
|
||||
#define CONTENT_LENGTH "Content-Length"
|
||||
|
||||
static const char *const STREAM_HEADER = "HTTP/1.0 200 OK\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n"
|
||||
"Connection: close\r\n"
|
||||
"Content-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "\r\n"
|
||||
"\r\n"
|
||||
"--" PART_BOUNDARY "\r\n";
|
||||
static const char *const STREAM_ERROR = "Content-Type: text/plain\r\n"
|
||||
"\r\n"
|
||||
"No frames send.\r\n"
|
||||
"--" PART_BOUNDARY "\r\n";
|
||||
static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n";
|
||||
static const char *const STREAM_BOUNDARY = "\r\n"
|
||||
"--" PART_BOUNDARY "\r\n";
|
||||
|
||||
CameraWebServer::CameraWebServer() {}
|
||||
|
||||
CameraWebServer::~CameraWebServer() {}
|
||||
|
||||
void CameraWebServer::setup() {
|
||||
if (!esp32_camera::global_esp32_camera || esp32_camera::global_esp32_camera->is_failed()) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->semaphore_ = xSemaphoreCreateBinary();
|
||||
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.server_port = this->port_;
|
||||
config.ctrl_port = this->port_;
|
||||
config.max_open_sockets = 1;
|
||||
config.backlog_conn = 2;
|
||||
config.lru_purge_enable = true;
|
||||
|
||||
if (httpd_start(&this->httpd_, &config) != ESP_OK) {
|
||||
mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
httpd_uri_t uri = {
|
||||
.uri = "/",
|
||||
.method = HTTP_GET,
|
||||
.handler = [](struct httpd_req *req) { return ((CameraWebServer *) req->user_ctx)->handler_(req); },
|
||||
.user_ctx = this};
|
||||
|
||||
httpd_register_uri_handler(this->httpd_, &uri);
|
||||
|
||||
esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
if (this->running_) {
|
||||
this->image_ = std::move(image);
|
||||
xSemaphoreGive(this->semaphore_);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CameraWebServer::on_shutdown() {
|
||||
this->running_ = false;
|
||||
this->image_ = nullptr;
|
||||
httpd_stop(this->httpd_);
|
||||
this->httpd_ = nullptr;
|
||||
vSemaphoreDelete(this->semaphore_);
|
||||
this->semaphore_ = nullptr;
|
||||
}
|
||||
|
||||
void CameraWebServer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ESP32 Camera Web Server:");
|
||||
ESP_LOGCONFIG(TAG, " Port: %d", this->port_);
|
||||
if (this->mode_ == STREAM)
|
||||
ESP_LOGCONFIG(TAG, " Mode: stream");
|
||||
else
|
||||
ESP_LOGCONFIG(TAG, " Mode: snapshot");
|
||||
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, " Setup Failed");
|
||||
}
|
||||
}
|
||||
|
||||
float CameraWebServer::get_setup_priority() const { return setup_priority::LATE; }
|
||||
|
||||
void CameraWebServer::loop() {
|
||||
if (!this->running_) {
|
||||
this->image_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<esphome::esp32_camera::CameraImage> CameraWebServer::wait_for_image_() {
|
||||
std::shared_ptr<esphome::esp32_camera::CameraImage> image;
|
||||
image.swap(this->image_);
|
||||
|
||||
if (!image) {
|
||||
// retry as we might still be fetching image
|
||||
xSemaphoreTake(this->semaphore_, IMAGE_REQUEST_TIMEOUT / portTICK_PERIOD_MS);
|
||||
image.swap(this->image_);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
esp_err_t CameraWebServer::handler_(struct httpd_req *req) {
|
||||
esp_err_t res = ESP_FAIL;
|
||||
|
||||
this->image_ = nullptr;
|
||||
this->running_ = true;
|
||||
|
||||
switch (this->mode_) {
|
||||
case STREAM:
|
||||
res = this->streaming_handler_(req);
|
||||
break;
|
||||
|
||||
case SNAPSHOT:
|
||||
res = this->snapshot_handler_(req);
|
||||
break;
|
||||
}
|
||||
|
||||
this->running_ = false;
|
||||
this->image_ = nullptr;
|
||||
return res;
|
||||
}
|
||||
|
||||
static esp_err_t httpd_send_all(httpd_req_t *r, const char *buf, size_t buf_len) {
|
||||
int ret;
|
||||
|
||||
while (buf_len > 0) {
|
||||
ret = httpd_send(r, buf, buf_len);
|
||||
if (ret < 0) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
buf += ret;
|
||||
buf_len -= ret;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
|
||||
esp_err_t res = ESP_OK;
|
||||
char part_buf[64];
|
||||
|
||||
// This manually constructs HTTP response to avoid chunked encoding
|
||||
// which is not supported by some clients
|
||||
|
||||
res = httpd_send_all(req, STREAM_HEADER, strlen(STREAM_HEADER));
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGW(TAG, "STREAM: failed to set HTTP header");
|
||||
return res;
|
||||
}
|
||||
|
||||
uint32_t last_frame = millis();
|
||||
uint32_t frames = 0;
|
||||
|
||||
while (res == ESP_OK && this->running_) {
|
||||
if (esp32_camera::global_esp32_camera != nullptr) {
|
||||
esp32_camera::global_esp32_camera->request_stream();
|
||||
}
|
||||
|
||||
auto image = this->wait_for_image_();
|
||||
|
||||
if (!image) {
|
||||
ESP_LOGW(TAG, "STREAM: failed to acquire frame");
|
||||
res = ESP_FAIL;
|
||||
}
|
||||
if (res == ESP_OK) {
|
||||
size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length());
|
||||
res = httpd_send_all(req, part_buf, hlen);
|
||||
}
|
||||
if (res == ESP_OK) {
|
||||
res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length());
|
||||
}
|
||||
if (res == ESP_OK) {
|
||||
res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
|
||||
}
|
||||
if (res == ESP_OK) {
|
||||
frames++;
|
||||
int64_t frame_time = millis() - last_frame;
|
||||
last_frame = millis();
|
||||
|
||||
ESP_LOGD(TAG, "MJPG: %uB %ums (%.1ffps)", (uint32_t) image->get_data_length(), (uint32_t) frame_time,
|
||||
1000.0 / (uint32_t) frame_time);
|
||||
}
|
||||
}
|
||||
|
||||
if (!frames) {
|
||||
res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
|
||||
esp_err_t res = ESP_OK;
|
||||
|
||||
if (esp32_camera::global_esp32_camera != nullptr) {
|
||||
esp32_camera::global_esp32_camera->request_image();
|
||||
}
|
||||
|
||||
auto image = this->wait_for_image_();
|
||||
|
||||
if (!image) {
|
||||
ESP_LOGW(TAG, "SNAPSHOT: failed to acquire frame");
|
||||
httpd_resp_send_500(req);
|
||||
res = ESP_FAIL;
|
||||
return res;
|
||||
}
|
||||
|
||||
res = httpd_resp_set_type(req, CONTENT_TYPE);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGW(TAG, "SNAPSHOT: failed to set HTTP response type");
|
||||
return res;
|
||||
}
|
||||
|
||||
httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
|
||||
|
||||
if (res == ESP_OK) {
|
||||
res = httpd_resp_set_hdr(req, CONTENT_LENGTH, esphome::to_string(image->get_data_length()).c_str());
|
||||
}
|
||||
if (res == ESP_OK) {
|
||||
res = httpd_resp_send(req, (const char *) image->get_data_buffer(), image->get_data_length());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace esp32_camera_web_server
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
#include "esphome/components/esp32_camera/esp32_camera.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
|
||||
struct httpd_req;
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_camera_web_server {
|
||||
|
||||
enum Mode { STREAM, SNAPSHOT };
|
||||
|
||||
class CameraWebServer : public Component {
|
||||
public:
|
||||
CameraWebServer();
|
||||
~CameraWebServer();
|
||||
|
||||
void setup() override;
|
||||
void on_shutdown() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void set_port(uint16_t port) { this->port_ = port; }
|
||||
void set_mode(Mode mode) { this->mode_ = mode; }
|
||||
void loop() override;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<esphome::esp32_camera::CameraImage> wait_for_image_();
|
||||
esp_err_t handler_(struct httpd_req *req);
|
||||
esp_err_t streaming_handler_(struct httpd_req *req);
|
||||
esp_err_t snapshot_handler_(struct httpd_req *req);
|
||||
|
||||
protected:
|
||||
uint16_t port_{0};
|
||||
void *httpd_{nullptr};
|
||||
SemaphoreHandle_t semaphore_;
|
||||
std::shared_ptr<esphome::esp32_camera::CameraImage> image_;
|
||||
bool running_{false};
|
||||
Mode mode_{STREAM};
|
||||
};
|
||||
|
||||
} // namespace esp32_camera_web_server
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -4,7 +4,7 @@ from esphome.components import binary_sensor, output, esp32_ble_server
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
|
||||
AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"]
|
||||
AUTO_LOAD = ["binary_sensor", "output", "esp32_ble_server"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
|
||||
DEPENDENCIES = ["wifi", "esp32"]
|
||||
@@ -56,6 +56,7 @@ async def to_code(config):
|
||||
cg.add(ble_server.register_service_component(var))
|
||||
|
||||
cg.add_define("USE_IMPROV")
|
||||
cg.add_library("esphome/Improv", "1.0.0")
|
||||
|
||||
cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
|
||||
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace esphome {
|
||||
namespace esp32_improv {
|
||||
|
||||
static const char *const TAG = "esp32_improv.component";
|
||||
static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
|
||||
|
||||
ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
|
||||
|
||||
@@ -124,8 +125,13 @@ void ESP32ImprovComponent::loop() {
|
||||
this->cancel_timeout("wifi-connect-timeout");
|
||||
this->set_state_(improv::STATE_PROVISIONED);
|
||||
|
||||
std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, {url});
|
||||
std::vector<std::string> urls = {ESPHOME_MY_LINK};
|
||||
#ifdef USE_WEBSERVER
|
||||
auto ip = wifi::global_wifi_component->wifi_sta_ip();
|
||||
std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
|
||||
urls.push_back(webserver_url);
|
||||
#endif
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
|
||||
this->send_response_(data);
|
||||
this->set_timeout("end-service", 1000, [this] {
|
||||
this->service_->stop();
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/esp32_ble_server/ble_server.h"
|
||||
#include "esphome/components/esp32_ble_server/ble_characteristic.h"
|
||||
#include "esphome/components/improv/improv.h"
|
||||
#include "esphome/components/esp32_ble_server/ble_server.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
#include "esphome/components/wifi/wifi_component.h"
|
||||
#include "esphome/core/component.h"
|
||||
@@ -12,6 +11,8 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <improv.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_improv {
|
||||
|
||||
@@ -48,7 +49,7 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent {
|
||||
std::vector<uint8_t> incoming_data_;
|
||||
wifi::WiFiAP connecting_sta_;
|
||||
|
||||
BLEService *service_;
|
||||
std::shared_ptr<BLEService> service_;
|
||||
BLECharacteristic *status_;
|
||||
BLECharacteristic *error_;
|
||||
BLECharacteristic *rpc_;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esp32_touch.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
@@ -93,7 +94,6 @@ void ESP32TouchComponent::dump_config() {
|
||||
|
||||
if (this->iir_filter_enabled_()) {
|
||||
ESP_LOGCONFIG(TAG, " IIR Filter: %ums", this->iir_filter_);
|
||||
touch_pad_filter_start(this->iir_filter_);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " IIR Filter DISABLED");
|
||||
}
|
||||
@@ -125,6 +125,8 @@ void ESP32TouchComponent::loop() {
|
||||
if (should_print) {
|
||||
ESP_LOGD(TAG, "Touch Pad '%s' (T%u): %u", child->get_name().c_str(), child->get_touch_pad(), value);
|
||||
}
|
||||
|
||||
App.feed_wdt();
|
||||
}
|
||||
|
||||
if (should_print) {
|
||||
|
||||
@@ -4,6 +4,7 @@ from esphome.const import (
|
||||
CONF_BOARD,
|
||||
CONF_BOARD_FLASH_MODE,
|
||||
CONF_FRAMEWORK,
|
||||
CONF_SOURCE,
|
||||
CONF_VERSION,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
@@ -31,7 +32,7 @@ def set_core_data(config):
|
||||
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "esp8266"
|
||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
|
||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
|
||||
config[CONF_FRAMEWORK][CONF_VERSION_HINT]
|
||||
config[CONF_FRAMEWORK][CONF_VERSION]
|
||||
)
|
||||
CORE.data[KEY_ESP8266][KEY_BOARD] = config[CONF_BOARD]
|
||||
return config
|
||||
@@ -62,75 +63,68 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 7, 4)
|
||||
# The platformio/espressif8266 version to use for arduino 2 framework versions
|
||||
# - https://github.com/platformio/platform-espressif8266/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266
|
||||
ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 2)
|
||||
ARDUINO_2_PLATFORM_VERSION = cv.Version(2, 6, 3)
|
||||
# for arduino 3 framework versions
|
||||
ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 0, 2)
|
||||
ARDUINO_3_PLATFORM_VERSION = cv.Version(3, 2, 0)
|
||||
|
||||
|
||||
def _arduino_check_versions(value):
|
||||
value = value.copy()
|
||||
lookups = {
|
||||
"dev": ("https://github.com/esp8266/Arduino.git", cv.Version(3, 0, 2)),
|
||||
"latest": ("", cv.Version(3, 0, 2)),
|
||||
"recommended": (
|
||||
_format_framework_arduino_version(RECOMMENDED_ARDUINO_FRAMEWORK_VERSION),
|
||||
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION,
|
||||
),
|
||||
"dev": (cv.Version(3, 0, 2), "https://github.com/esp8266/Arduino.git"),
|
||||
"latest": (cv.Version(3, 0, 2), None),
|
||||
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
|
||||
}
|
||||
ver_value = value[CONF_VERSION]
|
||||
default_ver_hint = None
|
||||
if ver_value.lower() in lookups:
|
||||
default_ver_hint = str(lookups[ver_value.lower()][1])
|
||||
ver_value = lookups[ver_value.lower()][0]
|
||||
|
||||
if value[CONF_VERSION] in lookups:
|
||||
if CONF_SOURCE in value:
|
||||
raise cv.Invalid(
|
||||
"Framework version needs to be explicitly specified when custom source is used."
|
||||
)
|
||||
|
||||
version, source = lookups[value[CONF_VERSION]]
|
||||
else:
|
||||
with cv.suppress_invalid():
|
||||
ver = cv.Version.parse(cv.version_number(value))
|
||||
if ver <= cv.Version(2, 4, 1):
|
||||
ver_value = f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
elif ver <= cv.Version(2, 6, 2):
|
||||
ver_value = f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
else:
|
||||
ver_value = f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
|
||||
default_ver_hint = str(ver)
|
||||
version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
|
||||
source = value.get(CONF_SOURCE, None)
|
||||
|
||||
value[CONF_VERSION] = ver_value
|
||||
value[CONF_VERSION] = str(version)
|
||||
value[CONF_SOURCE] = source or _format_framework_arduino_version(version)
|
||||
|
||||
if CONF_VERSION_HINT not in value and default_ver_hint is None:
|
||||
raise cv.Invalid("Needs a version hint to understand the framework version")
|
||||
|
||||
ver_hint_s = value.get(CONF_VERSION_HINT, default_ver_hint)
|
||||
value[CONF_VERSION_HINT] = ver_hint_s
|
||||
plat_ver = value.get(CONF_PLATFORM_VERSION)
|
||||
|
||||
if plat_ver is None:
|
||||
ver_hint = cv.Version.parse(ver_hint_s)
|
||||
if ver_hint >= cv.Version(3, 0, 0):
|
||||
plat_ver = ARDUINO_3_PLATFORM_VERSION
|
||||
elif ver_hint >= cv.Version(2, 5, 0):
|
||||
plat_ver = ARDUINO_2_PLATFORM_VERSION
|
||||
platform_version = value.get(CONF_PLATFORM_VERSION)
|
||||
if platform_version is None:
|
||||
if version >= cv.Version(3, 0, 0):
|
||||
platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION))
|
||||
elif version >= cv.Version(2, 5, 0):
|
||||
platform_version = _parse_platform_version(str(ARDUINO_2_PLATFORM_VERSION))
|
||||
else:
|
||||
plat_ver = cv.Version(1, 8, 0)
|
||||
value[CONF_PLATFORM_VERSION] = str(plat_ver)
|
||||
platform_version = _parse_platform_version(str(cv.Version(1, 8, 0)))
|
||||
value[CONF_PLATFORM_VERSION] = platform_version
|
||||
|
||||
if cv.Version.parse(ver_hint_s) != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
|
||||
if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
|
||||
_LOGGER.warning(
|
||||
"The selected arduino framework version is not the recommended one"
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"If there are connectivity or build issues please remove the manual version"
|
||||
"The selected Arduino framework version is not the recommended one. "
|
||||
"If there are connectivity or build issues please remove the manual version."
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
CONF_VERSION_HINT = "version_hint"
|
||||
def _parse_platform_version(value):
|
||||
try:
|
||||
# if platform version is a valid version constraint, prefix the default package
|
||||
cv.platformio_version_constraint(value)
|
||||
return f"platformio/espressif8266 @ {value}"
|
||||
except cv.Invalid:
|
||||
return value
|
||||
|
||||
|
||||
CONF_PLATFORM_VERSION = "platform_version"
|
||||
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
|
||||
cv.Optional(CONF_VERSION_HINT): cv.version_number,
|
||||
cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
|
||||
cv.Optional(CONF_SOURCE): cv.string_strict,
|
||||
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
|
||||
}
|
||||
),
|
||||
_arduino_check_versions,
|
||||
@@ -157,20 +151,21 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config):
|
||||
cg.add(esp8266_ns.setup_preferences())
|
||||
|
||||
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||
|
||||
cg.add_platformio_option("board", config[CONF_BOARD])
|
||||
cg.add_build_flag("-DUSE_ESP8266")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_define("ESPHOME_VARIANT", "ESP8266")
|
||||
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO")
|
||||
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
[f"platformio/framework-arduinoespressif8266 @ {conf[CONF_VERSION]}"],
|
||||
)
|
||||
cg.add_platformio_option(
|
||||
"platform", f"platformio/espressif8266 @ {conf[CONF_PLATFORM_VERSION]}"
|
||||
[f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"],
|
||||
)
|
||||
|
||||
# Default for platformio is LWIP2_LOW_MEMORY with:
|
||||
|
||||
@@ -206,61 +206,3 @@ ESP8266_BOARD_PINS = {
|
||||
"wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0},
|
||||
"xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13},
|
||||
}
|
||||
|
||||
FLASH_SIZE_1_MB = 2 ** 20
|
||||
FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2
|
||||
FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB
|
||||
FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB
|
||||
FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB
|
||||
|
||||
ESP8266_FLASH_SIZES = {
|
||||
"d1": FLASH_SIZE_4_MB,
|
||||
"d1_mini": FLASH_SIZE_4_MB,
|
||||
"d1_mini_lite": FLASH_SIZE_1_MB,
|
||||
"d1_mini_pro": FLASH_SIZE_16_MB,
|
||||
"esp01": FLASH_SIZE_512_KB,
|
||||
"esp01_1m": FLASH_SIZE_1_MB,
|
||||
"esp07": FLASH_SIZE_4_MB,
|
||||
"esp12e": FLASH_SIZE_4_MB,
|
||||
"esp210": FLASH_SIZE_4_MB,
|
||||
"esp8285": FLASH_SIZE_1_MB,
|
||||
"esp_wroom_02": FLASH_SIZE_2_MB,
|
||||
"espduino": FLASH_SIZE_4_MB,
|
||||
"espectro": FLASH_SIZE_4_MB,
|
||||
"espino": FLASH_SIZE_4_MB,
|
||||
"espinotee": FLASH_SIZE_4_MB,
|
||||
"espmxdevkit": FLASH_SIZE_1_MB,
|
||||
"espresso_lite_v1": FLASH_SIZE_4_MB,
|
||||
"espresso_lite_v2": FLASH_SIZE_4_MB,
|
||||
"gen4iod": FLASH_SIZE_512_KB,
|
||||
"heltec_wifi_kit_8": FLASH_SIZE_4_MB,
|
||||
"huzzah": FLASH_SIZE_4_MB,
|
||||
"inventone": FLASH_SIZE_4_MB,
|
||||
"modwifi": FLASH_SIZE_2_MB,
|
||||
"nodemcu": FLASH_SIZE_4_MB,
|
||||
"nodemcuv2": FLASH_SIZE_4_MB,
|
||||
"oak": FLASH_SIZE_4_MB,
|
||||
"phoenix_v1": FLASH_SIZE_4_MB,
|
||||
"phoenix_v2": FLASH_SIZE_4_MB,
|
||||
"sonoff_basic": FLASH_SIZE_1_MB,
|
||||
"sonoff_s20": FLASH_SIZE_1_MB,
|
||||
"sonoff_sv": FLASH_SIZE_1_MB,
|
||||
"sonoff_th": FLASH_SIZE_1_MB,
|
||||
"sparkfunBlynk": FLASH_SIZE_4_MB,
|
||||
"thing": FLASH_SIZE_512_KB,
|
||||
"thingdev": FLASH_SIZE_512_KB,
|
||||
"wifi_slot": FLASH_SIZE_1_MB,
|
||||
"wifiduino": FLASH_SIZE_4_MB,
|
||||
"wifinfo": FLASH_SIZE_1_MB,
|
||||
"wio_link": FLASH_SIZE_4_MB,
|
||||
"wio_node": FLASH_SIZE_4_MB,
|
||||
"xinabox_cw01": FLASH_SIZE_4_MB,
|
||||
}
|
||||
|
||||
ESP8266_LD_SCRIPTS = {
|
||||
FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"),
|
||||
FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"),
|
||||
FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"),
|
||||
FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"),
|
||||
FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"),
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ void IRAM_ATTR HOT yield() { ::yield(); }
|
||||
uint32_t IRAM_ATTR HOT millis() { return ::millis(); }
|
||||
void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
|
||||
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); }
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||
void arch_restart() {
|
||||
ESP.restart(); // NOLINT(readability-static-accessed-through-instance)
|
||||
// restart() doesn't always end execution
|
||||
|
||||
@@ -8,6 +8,29 @@ namespace esp8266 {
|
||||
|
||||
static const char *const TAG = "esp8266";
|
||||
|
||||
static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
|
||||
if (flags == gpio::FLAG_INPUT) {
|
||||
return INPUT;
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
return OUTPUT;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||
if (pin == 16) {
|
||||
// GPIO16 doesn't have a pullup, so pinMode would fail.
|
||||
// However, sometimes this method is called with pullup mode anyway
|
||||
// for example from dallas one_wire. For those cases convert this
|
||||
// to a INPUT mode.
|
||||
return INPUT;
|
||||
}
|
||||
return INPUT_PULLUP;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
|
||||
return INPUT_PULLDOWN_16;
|
||||
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
|
||||
return OUTPUT_OPEN_DRAIN;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct ISRPinArg {
|
||||
uint8_t pin;
|
||||
bool inverted;
|
||||
@@ -43,21 +66,7 @@ void ESP8266GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Int
|
||||
attachInterruptArg(pin_, func, arg, arduino_mode);
|
||||
}
|
||||
void ESP8266GPIOPin::pin_mode(gpio::Flags flags) {
|
||||
uint8_t mode;
|
||||
if (flags == gpio::FLAG_INPUT) {
|
||||
mode = INPUT;
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
mode = OUTPUT;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||
mode = INPUT_PULLUP;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
|
||||
mode = INPUT_PULLDOWN_16;
|
||||
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
|
||||
mode = OUTPUT_OPEN_DRAIN;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
pinMode(pin_, mode); // NOLINT
|
||||
pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT
|
||||
}
|
||||
|
||||
std::string ESP8266GPIOPin::dump_summary() const {
|
||||
@@ -90,6 +99,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin);
|
||||
}
|
||||
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -107,9 +107,9 @@ def validate_supports(value):
|
||||
raise cv.Invalid(
|
||||
"Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN]
|
||||
)
|
||||
if is_pullup and num == 0:
|
||||
if is_pullup and num == 16:
|
||||
raise cv.Invalid(
|
||||
"GPIO Pin 0 does not support pullup pin mode. "
|
||||
"GPIO Pin 16 does not support pullup pin mode. "
|
||||
"Please choose another pin.",
|
||||
[CONF_MODE, CONF_PULLUP],
|
||||
)
|
||||
|
||||
@@ -168,7 +168,9 @@ void EthernetComponent::start_connect_() {
|
||||
|
||||
esp_err_t err;
|
||||
err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str());
|
||||
ESPHL_ERROR_CHECK(err, "ETH set hostname error");
|
||||
if (err != ERR_OK) {
|
||||
ESP_LOGW(TAG, "tcpip_adapter_set_hostname failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
tcpip_adapter_ip_info_t info;
|
||||
if (this->manual_ip_.has_value()) {
|
||||
|
||||
@@ -43,19 +43,27 @@ def validate_source_shorthand(value):
|
||||
# Regex for GitHub repo name with optional branch/tag
|
||||
# Note: git allows other branch/tag names as well, but never seen them used before
|
||||
m = re.match(
|
||||
r"github://([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?",
|
||||
r"github://(?:([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?|pr#([0-9]+))",
|
||||
value,
|
||||
)
|
||||
if m is None:
|
||||
raise cv.Invalid(
|
||||
"Source is not a file system path or in expected github://username/name[@branch-or-tag] format!"
|
||||
"Source is not a file system path, in expected github://username/name[@branch-or-tag] or github://pr#1234 format!"
|
||||
)
|
||||
conf = {
|
||||
CONF_TYPE: TYPE_GIT,
|
||||
CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git",
|
||||
}
|
||||
if m.group(3):
|
||||
conf[CONF_REF] = m.group(3)
|
||||
if m.group(4):
|
||||
conf = {
|
||||
CONF_TYPE: TYPE_GIT,
|
||||
CONF_URL: "https://github.com/esphome/esphome.git",
|
||||
CONF_REF: f"pull/{m.group(4)}/head",
|
||||
}
|
||||
else:
|
||||
conf = {
|
||||
CONF_TYPE: TYPE_GIT,
|
||||
CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git",
|
||||
}
|
||||
if m.group(3):
|
||||
conf[CONF_REF] = m.group(3)
|
||||
|
||||
return SOURCE_SCHEMA(conf)
|
||||
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ void EZOSensor::loop() {
|
||||
if (buf[0] != 1)
|
||||
return;
|
||||
|
||||
float val = strtof((char *) &buf[1], nullptr);
|
||||
float val = parse_number<float>((char *) &buf[1], sizeof(buf) - 1).value_or(0);
|
||||
this->publish_state(val);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from esphome.const import (
|
||||
CONF_LAST_FINGER_ID,
|
||||
CONF_SECURITY_LEVEL,
|
||||
CONF_STATUS,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_ACCOUNT,
|
||||
ICON_ACCOUNT_CHECK,
|
||||
ICON_DATABASE,
|
||||
@@ -26,30 +27,36 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
icon=ICON_FINGERPRINT,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_NONE,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_STATUS): sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_NONE,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_CAPACITY): sensor.sensor_schema(
|
||||
icon=ICON_DATABASE,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_NONE,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SECURITY_LEVEL): sensor.sensor_schema(
|
||||
icon=ICON_SECURITY,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_NONE,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_LAST_FINGER_ID): sensor.sensor_schema(
|
||||
icon=ICON_ACCOUNT,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_NONE,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_LAST_CONFIDENCE): sensor.sensor_schema(
|
||||
icon=ICON_ACCOUNT_CHECK,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_NONE,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -93,13 +93,12 @@ PV_SENSORS = {
|
||||
CONF_VOLTAGE_SAMPLED_BY_SECONDARY_CPU: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_INSULATION_OF_P_TO_GROUND: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_KOHM,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
@@ -135,7 +134,6 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema(
|
||||
|
||||
@@ -30,6 +30,7 @@ PROTOCOLS = {
|
||||
"gree": Protocol.PROTOCOL_GREE,
|
||||
"greeya": Protocol.PROTOCOL_GREEYAA,
|
||||
"greeyan": Protocol.PROTOCOL_GREEYAN,
|
||||
"greeyac": Protocol.PROTOCOL_GREEYAC,
|
||||
"hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
|
||||
"hitachi": Protocol.PROTOCOL_HITACHI,
|
||||
"hyundai": Protocol.PROTOCOL_HYUNDAI,
|
||||
@@ -111,4 +112,6 @@ def to_code(config):
|
||||
cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE]))
|
||||
cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE]))
|
||||
|
||||
cg.add_library("tonia/HeatpumpIR", "1.0.15")
|
||||
# PIO isn't updating releases, so referencing the release tag directly. See:
|
||||
# https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
|
||||
cg.add_library("", "", "https://github.com/ToniA/arduino-heatpumpir.git#1.0.18")
|
||||
|
||||
@@ -25,6 +25,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
|
||||
{PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT
|
||||
@@ -61,6 +62,19 @@ void HeatpumpIRClimate::setup() {
|
||||
}
|
||||
this->heatpump_ir_ = protocol_constructor->second();
|
||||
climate_ir::ClimateIR::setup();
|
||||
if (this->sensor_) {
|
||||
this->sensor_->add_on_state_callback([this](float state) {
|
||||
this->current_temperature = state;
|
||||
|
||||
IRSenderESPHome esp_sender(this->transmitter_);
|
||||
this->heatpump_ir_->send(esp_sender, uint8_t(lround(this->current_temperature + 0.5)));
|
||||
|
||||
// current temperature changed, publish state
|
||||
this->publish_state();
|
||||
});
|
||||
this->current_temperature = this->sensor_->state;
|
||||
} else
|
||||
this->current_temperature = NAN;
|
||||
}
|
||||
|
||||
void HeatpumpIRClimate::transmit_state() {
|
||||
@@ -171,8 +185,7 @@ void HeatpumpIRClimate::transmit_state() {
|
||||
|
||||
temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_);
|
||||
|
||||
IRSenderESPHome esp_sender(0, this->transmitter_);
|
||||
|
||||
IRSenderESPHome esp_sender(this->transmitter_);
|
||||
heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd,
|
||||
swing_h_cmd);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ enum Protocol {
|
||||
PROTOCOL_GREE,
|
||||
PROTOCOL_GREEYAA,
|
||||
PROTOCOL_GREEYAN,
|
||||
PROTOCOL_GREEYAC,
|
||||
PROTOCOL_HISENSE_AUD,
|
||||
PROTOCOL_HITACHI,
|
||||
PROTOCOL_HYUNDAI,
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace heatpumpir {
|
||||
|
||||
class IRSenderESPHome : public IRSender {
|
||||
public:
|
||||
IRSenderESPHome(uint8_t pin, remote_transmitter::RemoteTransmitterComponent *transmitter)
|
||||
: IRSender(pin), transmit_(transmitter->transmit()){};
|
||||
IRSenderESPHome(remote_transmitter::RemoteTransmitterComponent *transmitter)
|
||||
: IRSender(0), transmit_(transmitter->transmit()){};
|
||||
void setFrequency(int frequency) override; // NOLINT(readability-identifier-naming)
|
||||
void space(int space_length) override;
|
||||
void mark(int mark_length) override;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
@@ -13,5 +12,3 @@ class AbstractAQICalculator {
|
||||
|
||||
} // namespace hm3301
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "abstract_aqi_calculator.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -33,7 +31,7 @@ class AQICalculator : public AbstractAQICalculator {
|
||||
int conc_lo = array[grid_index][0];
|
||||
int conc_hi = array[grid_index][1];
|
||||
|
||||
return ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo;
|
||||
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
||||
}
|
||||
|
||||
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
||||
@@ -48,5 +46,3 @@ class AQICalculator : public AbstractAQICalculator {
|
||||
|
||||
} // namespace hm3301
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "caqi_calculator.h"
|
||||
#include "aqi_calculator.h"
|
||||
|
||||
@@ -29,5 +27,3 @@ class AQICalculatorFactory {
|
||||
|
||||
} // namespace hm3301
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "abstract_aqi_calculator.h"
|
||||
|
||||
@@ -37,9 +35,7 @@ class CAQICalculator : public AbstractAQICalculator {
|
||||
int conc_lo = array[grid_index][0];
|
||||
int conc_hi = array[grid_index][1];
|
||||
|
||||
int aqi = ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo;
|
||||
|
||||
return aqi;
|
||||
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
||||
}
|
||||
|
||||
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
|
||||
@@ -54,5 +50,3 @@ class CAQICalculator : public AbstractAQICalculator {
|
||||
|
||||
} // namespace hm3301
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "hm3301.h"
|
||||
|
||||
@@ -14,9 +12,8 @@ static const uint8_t PM_10_0_VALUE_INDEX = 7;
|
||||
|
||||
void HM3301Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up HM3301...");
|
||||
hm3301_ = make_unique<HM330X>();
|
||||
error_code_ = hm3301_->init();
|
||||
if (error_code_ != NO_ERROR) {
|
||||
if (i2c::ERROR_OK != this->write(&SELECT_COMM_CMD, 1)) {
|
||||
error_code_ = ERROR_COMM;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -38,7 +35,7 @@ void HM3301Component::dump_config() {
|
||||
float HM3301Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void HM3301Component::update() {
|
||||
if (!this->read_sensor_value_(data_buffer_)) {
|
||||
if (this->read(data_buffer_, 29) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Read result failed");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
@@ -87,8 +84,6 @@ void HM3301Component::update() {
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
bool HM3301Component::read_sensor_value_(uint8_t *data) { return !hm3301_->read_sensor_value(data, 29); }
|
||||
|
||||
bool HM3301Component::validate_checksum_(const uint8_t *data) {
|
||||
uint8_t sum = 0;
|
||||
for (int i = 0; i < 28; i++) {
|
||||
@@ -104,5 +99,3 @@ uint16_t HM3301Component::get_sensor_value_(const uint8_t *data, uint8_t i) {
|
||||
|
||||
} // namespace hm3301
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "aqi_calculator_factory.h"
|
||||
|
||||
#include <Seeed_HM330X.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace hm3301 {
|
||||
|
||||
static const uint8_t SELECT_COMM_CMD = 0X88;
|
||||
|
||||
class HM3301Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
HM3301Component() = default;
|
||||
@@ -29,9 +27,12 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<HM330X> hm3301_;
|
||||
|
||||
HM330XErrorCode error_code_{NO_ERROR};
|
||||
enum {
|
||||
NO_ERROR = 0,
|
||||
ERROR_PARAM = -1,
|
||||
ERROR_COMM = -2,
|
||||
ERROR_OTHERS = -128,
|
||||
} error_code_{NO_ERROR};
|
||||
|
||||
uint8_t data_buffer_[30];
|
||||
|
||||
@@ -43,12 +44,9 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
|
||||
AQICalculatorType aqi_calc_type_;
|
||||
AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory();
|
||||
|
||||
bool read_sensor_value_(uint8_t *);
|
||||
bool validate_checksum_(const uint8_t *);
|
||||
uint16_t get_sensor_value_(const uint8_t *, uint8_t);
|
||||
};
|
||||
|
||||
} // namespace hm3301
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
@@ -84,7 +84,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x40)),
|
||||
_validate,
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
@@ -109,6 +108,3 @@ async def to_code(config):
|
||||
sens = await sensor.new_sensor(config[CONF_AQI])
|
||||
cg.add(var.set_aqi_sensor(sens))
|
||||
cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE]))
|
||||
|
||||
# https://platformio.org/lib/show/6306/Grove%20-%20Laser%20PM2.5%20Sensor%20HM3301
|
||||
cg.add_library("seeed-studio/Grove - Laser PM2.5 Sensor HM3301", "1.0.3")
|
||||
|
||||
@@ -114,8 +114,8 @@ CONFIG_SCHEMA = (
|
||||
|
||||
|
||||
def auto_data_rate(config):
|
||||
interval_sec = config[CONF_UPDATE_INTERVAL].seconds
|
||||
interval_hz = 1.0 / interval_sec
|
||||
interval_msec = config[CONF_UPDATE_INTERVAL].total_milliseconds
|
||||
interval_hz = 1000.0 / interval_msec
|
||||
for datarate in sorted(HMC5883LDatarates.keys()):
|
||||
if float(datarate) >= interval_hz:
|
||||
return HMC5883LDatarates[datarate]
|
||||
|
||||
@@ -10,7 +10,7 @@ static const char *const TAG = "homeassistant.sensor";
|
||||
void HomeassistantSensor::setup() {
|
||||
api::global_api_server->subscribe_home_assistant_state(
|
||||
this->entity_id_, this->attribute_, [this](const std::string &state) {
|
||||
auto val = parse_float(state);
|
||||
auto val = parse_number<float>(state);
|
||||
if (!val.has_value()) {
|
||||
ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str());
|
||||
this->publish_state(NAN);
|
||||
|
||||
@@ -44,7 +44,7 @@ void HrxlMaxsonarWrComponent::check_buffer_() {
|
||||
|
||||
if (this->buffer_.length() == MAX_DATA_LENGTH_BYTES && this->buffer_[0] == 'R' &&
|
||||
this->buffer_.back() == static_cast<char>(ASCII_CR)) {
|
||||
int millimeters = strtol(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2).c_str(), nullptr, 10);
|
||||
int millimeters = parse_number<int>(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2)).value_or(0);
|
||||
float meters = float(millimeters) / 1000.0;
|
||||
ESP_LOGV(TAG, "Distance from sensor: %d mm, %f m", millimeters, meters);
|
||||
this->publish_state(meters);
|
||||
|
||||
@@ -96,6 +96,8 @@ async def to_code(config):
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("WiFiClientSecure", None)
|
||||
cg.add_library("HTTPClient", None)
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("ESP8266HTTPClient", None)
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ static const char *const TAG = "htu21d";
|
||||
|
||||
static const uint8_t HTU21D_ADDRESS = 0x40;
|
||||
static const uint8_t HTU21D_REGISTER_RESET = 0xFE;
|
||||
static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xE3;
|
||||
static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xE5;
|
||||
static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xF3;
|
||||
static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xF5;
|
||||
static const uint8_t HTU21D_REGISTER_STATUS = 0xE7;
|
||||
|
||||
void HTU21DComponent::setup() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "i2c_bus.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/optional.h"
|
||||
#include <array>
|
||||
#include <vector>
|
||||
@@ -32,16 +33,8 @@ class I2CRegister {
|
||||
|
||||
// like ntohs/htons but without including networking headers.
|
||||
// ("i2c" byte order is big-endian)
|
||||
inline uint16_t i2ctohs(uint16_t i2cshort) {
|
||||
union {
|
||||
uint16_t x;
|
||||
uint8_t y[2];
|
||||
} conv;
|
||||
conv.x = i2cshort;
|
||||
return ((uint16_t) conv.y[0] << 8) | ((uint16_t) conv.y[1] << 0);
|
||||
}
|
||||
|
||||
inline uint16_t htoi2cs(uint16_t hostshort) { return i2ctohs(hostshort); }
|
||||
inline uint16_t i2ctohs(uint16_t i2cshort) { return convert_big_endian(i2cshort); }
|
||||
inline uint16_t htoi2cs(uint16_t hostshort) { return convert_big_endian(hostshort); }
|
||||
|
||||
class I2CDevice {
|
||||
public:
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
@@ -1,91 +0,0 @@
|
||||
#include "improv.h"
|
||||
|
||||
namespace improv {
|
||||
|
||||
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data) {
|
||||
return parse_improv_data(data.data(), data.size());
|
||||
}
|
||||
|
||||
ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
|
||||
Command command = (Command) data[0];
|
||||
uint8_t data_length = data[1];
|
||||
|
||||
if (data_length != length - 3) {
|
||||
return {.command = UNKNOWN};
|
||||
}
|
||||
|
||||
uint8_t checksum = data[length - 1];
|
||||
|
||||
uint32_t calculated_checksum = 0;
|
||||
for (uint8_t i = 0; i < length - 1; i++) {
|
||||
calculated_checksum += data[i];
|
||||
}
|
||||
|
||||
if ((uint8_t) calculated_checksum != checksum) {
|
||||
return {.command = BAD_CHECKSUM};
|
||||
}
|
||||
|
||||
if (command == WIFI_SETTINGS) {
|
||||
uint8_t ssid_length = data[2];
|
||||
uint8_t ssid_start = 3;
|
||||
size_t ssid_end = ssid_start + ssid_length;
|
||||
|
||||
uint8_t pass_length = data[ssid_end];
|
||||
size_t pass_start = ssid_end + 1;
|
||||
size_t pass_end = pass_start + pass_length;
|
||||
|
||||
std::string ssid(data + ssid_start, data + ssid_end);
|
||||
std::string password(data + pass_start, data + pass_end);
|
||||
return {.command = command, .ssid = ssid, .password = password};
|
||||
}
|
||||
|
||||
return {
|
||||
.command = command,
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum) {
|
||||
std::vector<uint8_t> out;
|
||||
uint32_t length = 0;
|
||||
out.push_back(command);
|
||||
for (auto str : datum) {
|
||||
uint8_t len = str.length();
|
||||
length += len;
|
||||
out.push_back(len);
|
||||
out.insert(out.end(), str.begin(), str.end());
|
||||
}
|
||||
out.insert(out.begin() + 1, length);
|
||||
|
||||
uint32_t calculated_checksum = 0;
|
||||
|
||||
for (uint8_t byte : out) {
|
||||
calculated_checksum += byte;
|
||||
}
|
||||
out.push_back(calculated_checksum);
|
||||
return out;
|
||||
}
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum) {
|
||||
std::vector<uint8_t> out;
|
||||
uint32_t length = 0;
|
||||
out.push_back(command);
|
||||
for (auto str : datum) {
|
||||
uint8_t len = str.length();
|
||||
length += len;
|
||||
out.push_back(len);
|
||||
out.insert(out.end(), str.begin(), str.end());
|
||||
}
|
||||
out.insert(out.begin() + 1, length);
|
||||
|
||||
uint32_t calculated_checksum = 0;
|
||||
|
||||
for (uint8_t byte : out) {
|
||||
calculated_checksum += byte;
|
||||
}
|
||||
out.push_back(calculated_checksum);
|
||||
return out;
|
||||
}
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
} // namespace improv
|
||||
@@ -1,60 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include "WString.h"
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace improv {
|
||||
|
||||
static const char *const SERVICE_UUID = "00467768-6228-2272-4663-277478268000";
|
||||
static const char *const STATUS_UUID = "00467768-6228-2272-4663-277478268001";
|
||||
static const char *const ERROR_UUID = "00467768-6228-2272-4663-277478268002";
|
||||
static const char *const RPC_COMMAND_UUID = "00467768-6228-2272-4663-277478268003";
|
||||
static const char *const RPC_RESULT_UUID = "00467768-6228-2272-4663-277478268004";
|
||||
static const char *const CAPABILITIES_UUID = "00467768-6228-2272-4663-277478268005";
|
||||
|
||||
enum Error : uint8_t {
|
||||
ERROR_NONE = 0x00,
|
||||
ERROR_INVALID_RPC = 0x01,
|
||||
ERROR_UNKNOWN_RPC = 0x02,
|
||||
ERROR_UNABLE_TO_CONNECT = 0x03,
|
||||
ERROR_NOT_AUTHORIZED = 0x04,
|
||||
ERROR_UNKNOWN = 0xFF,
|
||||
};
|
||||
|
||||
enum State : uint8_t {
|
||||
STATE_STOPPED = 0x00,
|
||||
STATE_AWAITING_AUTHORIZATION = 0x01,
|
||||
STATE_AUTHORIZED = 0x02,
|
||||
STATE_PROVISIONING = 0x03,
|
||||
STATE_PROVISIONED = 0x04,
|
||||
};
|
||||
|
||||
enum Command : uint8_t {
|
||||
UNKNOWN = 0x00,
|
||||
WIFI_SETTINGS = 0x01,
|
||||
IDENTIFY = 0x02,
|
||||
BAD_CHECKSUM = 0xFF,
|
||||
};
|
||||
|
||||
static const uint8_t CAPABILITY_IDENTIFY = 0x01;
|
||||
|
||||
struct ImprovCommand {
|
||||
Command command;
|
||||
std::string ssid;
|
||||
std::string password;
|
||||
};
|
||||
|
||||
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data);
|
||||
ImprovCommand parse_improv_data(const uint8_t *data, size_t length);
|
||||
|
||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum);
|
||||
#ifdef USE_ARDUINO
|
||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum);
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
} // namespace improv
|
||||
33
esphome/components/improv_serial/__init__.py
Normal file
33
esphome/components/improv_serial/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_LOGGER
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEPENDENCIES = ["logger", "wifi"]
|
||||
|
||||
improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
|
||||
|
||||
ImprovSerialComponent = improv_serial_ns.class_("ImprovSerialComponent", cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ImprovSerialComponent),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def validate_logger_baud_rate(config):
|
||||
logger_conf = fv.full_config.get()[CONF_LOGGER]
|
||||
if logger_conf[CONF_BAUD_RATE] == 0:
|
||||
raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0")
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
cg.add_library("esphome/Improv", "1.0.0")
|
||||
266
esphome/components/improv_serial/improv_serial_component.cpp
Normal file
266
esphome/components/improv_serial/improv_serial_component.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
#include "improv_serial_component.h"
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#include "esphome/components/logger/logger.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace improv_serial {
|
||||
|
||||
static const char *const TAG = "improv_serial";
|
||||
|
||||
void ImprovSerialComponent::setup() {
|
||||
global_improv_serial_component = this;
|
||||
#ifdef USE_ARDUINO
|
||||
this->hw_serial_ = logger::global_logger->get_hw_serial();
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
this->uart_num_ = logger::global_logger->get_uart_num();
|
||||
#endif
|
||||
|
||||
if (wifi::global_wifi_component->has_sta()) {
|
||||
this->state_ = improv::STATE_PROVISIONED;
|
||||
}
|
||||
}
|
||||
|
||||
void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
|
||||
|
||||
int ImprovSerialComponent::available_() {
|
||||
#ifdef USE_ARDUINO
|
||||
return this->hw_serial_->available();
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
size_t available;
|
||||
uart_get_buffered_data_len(this->uart_num_, &available);
|
||||
return available;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t ImprovSerialComponent::read_byte_() {
|
||||
uint8_t data;
|
||||
#ifdef USE_ARDUINO
|
||||
this->hw_serial_->readBytes(&data, 1);
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_RATE_MS);
|
||||
#endif
|
||||
return data;
|
||||
}
|
||||
|
||||
void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
|
||||
data.push_back('\n');
|
||||
#ifdef USE_ARDUINO
|
||||
this->hw_serial_->write(data.data(), data.size());
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
uart_write_bytes(this->uart_num_, data.data(), data.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
void ImprovSerialComponent::loop() {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_read_byte_ > 50) {
|
||||
this->rx_buffer_.clear();
|
||||
this->last_read_byte_ = now;
|
||||
}
|
||||
|
||||
while (this->available_()) {
|
||||
uint8_t byte = this->read_byte_();
|
||||
if (this->parse_improv_serial_byte_(byte)) {
|
||||
this->last_read_byte_ = now;
|
||||
} else {
|
||||
this->rx_buffer_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (this->state_ == improv::STATE_PROVISIONING) {
|
||||
if (wifi::global_wifi_component->is_connected()) {
|
||||
wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
|
||||
this->connecting_sta_.get_password());
|
||||
this->connecting_sta_ = {};
|
||||
this->cancel_timeout("wifi-connect-timeout");
|
||||
this->set_state_(improv::STATE_PROVISIONED);
|
||||
|
||||
std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
|
||||
this->send_response_(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
|
||||
std::vector<std::string> urls;
|
||||
#ifdef USE_WEBSERVER
|
||||
auto ip = wifi::global_wifi_component->wifi_sta_ip();
|
||||
std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
|
||||
urls.push_back(webserver_url);
|
||||
#endif
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
|
||||
return data;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
|
||||
std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
|
||||
return data;
|
||||
};
|
||||
|
||||
bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
|
||||
size_t at = this->rx_buffer_.size();
|
||||
this->rx_buffer_.push_back(byte);
|
||||
ESP_LOGD(TAG, "Improv Serial byte: 0x%02X", byte);
|
||||
const uint8_t *raw = &this->rx_buffer_[0];
|
||||
if (at == 0)
|
||||
return byte == 'I';
|
||||
if (at == 1)
|
||||
return byte == 'M';
|
||||
if (at == 2)
|
||||
return byte == 'P';
|
||||
if (at == 3)
|
||||
return byte == 'R';
|
||||
if (at == 4)
|
||||
return byte == 'O';
|
||||
if (at == 5)
|
||||
return byte == 'V';
|
||||
|
||||
if (at == 6)
|
||||
return byte == IMPROV_SERIAL_VERSION;
|
||||
|
||||
if (at == 7)
|
||||
return true;
|
||||
uint8_t type = raw[7];
|
||||
|
||||
if (at == 8)
|
||||
return true;
|
||||
uint8_t data_len = raw[8];
|
||||
|
||||
if (at < 8 + data_len)
|
||||
return true;
|
||||
|
||||
if (at == 8 + data_len)
|
||||
return true;
|
||||
|
||||
if (at == 8 + data_len + 1) {
|
||||
uint8_t checksum = 0x00;
|
||||
for (uint8_t i = 0; i < at; i++)
|
||||
checksum += raw[i];
|
||||
|
||||
if (checksum != byte) {
|
||||
ESP_LOGW(TAG, "Error decoding Improv payload");
|
||||
this->set_error_(improv::ERROR_INVALID_RPC);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == TYPE_RPC) {
|
||||
this->set_error_(improv::ERROR_NONE);
|
||||
auto command = improv::parse_improv_data(&raw[9], data_len, false);
|
||||
return this->parse_improv_payload_(command);
|
||||
}
|
||||
}
|
||||
|
||||
// If we got here then the command coming is is improv, but not an RPC command
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
|
||||
switch (command.command) {
|
||||
case improv::WIFI_SETTINGS: {
|
||||
wifi::WiFiAP sta{};
|
||||
sta.set_ssid(command.ssid);
|
||||
sta.set_password(command.password);
|
||||
this->connecting_sta_ = sta;
|
||||
|
||||
wifi::global_wifi_component->set_sta(sta);
|
||||
wifi::global_wifi_component->start_scanning();
|
||||
this->set_state_(improv::STATE_PROVISIONING);
|
||||
ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
command.password.c_str());
|
||||
|
||||
auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
|
||||
this->set_timeout("wifi-connect-timeout", 30000, f);
|
||||
return true;
|
||||
}
|
||||
case improv::GET_CURRENT_STATE:
|
||||
this->set_state_(this->state_);
|
||||
if (this->state_ == improv::STATE_PROVISIONED) {
|
||||
std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
|
||||
this->send_response_(url);
|
||||
}
|
||||
return true;
|
||||
case improv::GET_DEVICE_INFO: {
|
||||
std::vector<uint8_t> info = this->build_version_info_();
|
||||
this->send_response_(info);
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
ESP_LOGW(TAG, "Unknown Improv payload");
|
||||
this->set_error_(improv::ERROR_UNKNOWN_RPC);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImprovSerialComponent::set_state_(improv::State state) {
|
||||
this->state_ = state;
|
||||
|
||||
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
|
||||
data.resize(11);
|
||||
data[6] = IMPROV_SERIAL_VERSION;
|
||||
data[7] = TYPE_CURRENT_STATE;
|
||||
data[8] = 1;
|
||||
data[9] = state;
|
||||
|
||||
uint8_t checksum = 0x00;
|
||||
for (uint8_t d : data)
|
||||
checksum += d;
|
||||
data[10] = checksum;
|
||||
|
||||
this->write_data_(data);
|
||||
}
|
||||
|
||||
void ImprovSerialComponent::set_error_(improv::Error error) {
|
||||
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
|
||||
data.resize(11);
|
||||
data[6] = IMPROV_SERIAL_VERSION;
|
||||
data[7] = TYPE_ERROR_STATE;
|
||||
data[8] = 1;
|
||||
data[9] = error;
|
||||
|
||||
uint8_t checksum = 0x00;
|
||||
for (uint8_t d : data)
|
||||
checksum += d;
|
||||
data[10] = checksum;
|
||||
this->write_data_(data);
|
||||
}
|
||||
|
||||
void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
|
||||
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
|
||||
data.resize(9);
|
||||
data[6] = IMPROV_SERIAL_VERSION;
|
||||
data[7] = TYPE_RPC_RESPONSE;
|
||||
data[8] = response.size();
|
||||
data.insert(data.end(), response.begin(), response.end());
|
||||
|
||||
uint8_t checksum = 0x00;
|
||||
for (uint8_t d : data)
|
||||
checksum += d;
|
||||
data.push_back(checksum);
|
||||
|
||||
this->write_data_(data);
|
||||
}
|
||||
|
||||
void ImprovSerialComponent::on_wifi_connect_timeout_() {
|
||||
this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
|
||||
this->set_state_(improv::STATE_AUTHORIZED);
|
||||
ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
|
||||
wifi::global_wifi_component->clear_sta();
|
||||
}
|
||||
|
||||
ImprovSerialComponent *global_improv_serial_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace improv_serial
|
||||
} // namespace esphome
|
||||
70
esphome/components/improv_serial/improv_serial_component.h
Normal file
70
esphome/components/improv_serial/improv_serial_component.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/wifi/wifi_component.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <improv.h>
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <HardwareSerial.h>
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
#include <driver/uart.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace improv_serial {
|
||||
|
||||
enum ImprovSerialType : uint8_t {
|
||||
TYPE_CURRENT_STATE = 0x01,
|
||||
TYPE_ERROR_STATE = 0x02,
|
||||
TYPE_RPC = 0x03,
|
||||
TYPE_RPC_RESPONSE = 0x04
|
||||
};
|
||||
|
||||
static const uint8_t IMPROV_SERIAL_VERSION = 1;
|
||||
|
||||
class ImprovSerialComponent : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
protected:
|
||||
bool parse_improv_serial_byte_(uint8_t byte);
|
||||
bool parse_improv_payload_(improv::ImprovCommand &command);
|
||||
|
||||
void set_state_(improv::State state);
|
||||
void set_error_(improv::Error error);
|
||||
void send_response_(std::vector<uint8_t> &response);
|
||||
void on_wifi_connect_timeout_();
|
||||
|
||||
std::vector<uint8_t> build_rpc_settings_response_(improv::Command command);
|
||||
std::vector<uint8_t> build_version_info_();
|
||||
|
||||
int available_();
|
||||
uint8_t read_byte_();
|
||||
void write_data_(std::vector<uint8_t> &data);
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
HardwareSerial *hw_serial_{nullptr};
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
uart_port_t uart_num_;
|
||||
#endif
|
||||
|
||||
std::vector<uint8_t> rx_buffer_;
|
||||
uint32_t last_read_byte_{0};
|
||||
wifi::WiFiAP connecting_sta_;
|
||||
improv::State state_{improv::STATE_AUTHORIZED};
|
||||
};
|
||||
|
||||
extern ImprovSerialComponent
|
||||
*global_improv_serial_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace improv_serial
|
||||
} // namespace esphome
|
||||
@@ -9,6 +9,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
@@ -53,6 +54,7 @@ CONFIG_SCHEMA = (
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -15,17 +15,37 @@ namespace ledc {
|
||||
|
||||
static const char *const TAG = "ledc.output";
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1;
|
||||
#if SOC_LEDC_SUPPORT_HS_MODE
|
||||
// Only ESP32 has LEDC_HIGH_SPEED_MODE
|
||||
inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; }
|
||||
#else
|
||||
// S2, C3, S3 only support LEDC_LOW_SPEED_MODE
|
||||
// See
|
||||
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview
|
||||
inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; }
|
||||
#endif
|
||||
#else
|
||||
static const int MAX_RES_BITS = 20;
|
||||
#endif
|
||||
|
||||
float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); }
|
||||
float ledc_min_frequency_for_bit_depth(uint8_t bit_depth) {
|
||||
const float max_div_num = ((1 << 20) - 1) / 256.0f;
|
||||
|
||||
float ledc_min_frequency_for_bit_depth(uint8_t bit_depth, bool low_frequency) {
|
||||
const float max_div_num = ((1 << MAX_RES_BITS) - 1) / (low_frequency ? 32.0f : 256.0f);
|
||||
return 80e6f / (max_div_num * float(1 << bit_depth));
|
||||
}
|
||||
|
||||
optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
|
||||
for (int i = 20; i >= 1; i--) {
|
||||
const float min_frequency = ledc_min_frequency_for_bit_depth(i);
|
||||
ESP_LOGD(TAG, "Calculating resolution bit-depth for frequency %f", frequency);
|
||||
for (int i = MAX_RES_BITS; i >= 1; i--) {
|
||||
const float min_frequency = ledc_min_frequency_for_bit_depth(i, (frequency < 100));
|
||||
const float max_frequency = ledc_max_frequency_for_bit_depth(i);
|
||||
if (min_frequency <= frequency && frequency <= max_frequency)
|
||||
if (min_frequency <= frequency && frequency <= max_frequency) {
|
||||
ESP_LOGD(TAG, "Resolution calculated as %d", i);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -48,7 +68,7 @@ void LEDCOutput::write_state(float state) {
|
||||
ledcWrite(this->channel_, duty);
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE;
|
||||
auto speed_mode = get_speed_mode(channel_);
|
||||
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
|
||||
ledc_set_duty(speed_mode, chan_num, duty);
|
||||
ledc_update_duty(speed_mode, chan_num);
|
||||
@@ -63,11 +83,15 @@ void LEDCOutput::setup() {
|
||||
ledcAttachPin(this->pin_->get_pin(), this->channel_);
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE;
|
||||
auto speed_mode = get_speed_mode(channel_);
|
||||
auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2);
|
||||
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
|
||||
|
||||
bit_depth_ = *ledc_bit_depth_for_frequency(frequency_);
|
||||
if (bit_depth_ < 1) {
|
||||
ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency_);
|
||||
this->status_set_warning();
|
||||
}
|
||||
|
||||
ledc_timer_config_t timer_conf{};
|
||||
timer_conf.speed_mode = speed_mode;
|
||||
@@ -114,7 +138,7 @@ void LEDCOutput::update_frequency(float frequency) {
|
||||
ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!");
|
||||
return;
|
||||
}
|
||||
auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE;
|
||||
auto speed_mode = get_speed_mode(channel_);
|
||||
auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2);
|
||||
|
||||
ledc_timer_config_t timer_conf{};
|
||||
|
||||
@@ -79,7 +79,7 @@ optional<LightColorValues> AddressableLightTransformer::apply() {
|
||||
// dynamically-calculated alpha values to match the look.
|
||||
|
||||
float denom = (1.0f - smoothed_progress);
|
||||
float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom;
|
||||
float alpha = denom == 0.0f ? 1.0f : (smoothed_progress - this->last_transition_progress_) / denom;
|
||||
|
||||
// We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length
|
||||
// We solve this by accumulating the fractional part of the alpha over time.
|
||||
|
||||
@@ -22,6 +22,12 @@ using ESPColor ESPDEPRECATED("esphome::light::ESPColor is deprecated, use esphom
|
||||
/// Convert the color information from a `LightColorValues` object to a `Color` object (does not apply brightness).
|
||||
Color color_from_light_color_values(LightColorValues val);
|
||||
|
||||
/// Use a custom state class for addressable lights, to allow type system to discriminate between addressable and
|
||||
/// non-addressable lights.
|
||||
class AddressableLightState : public LightState {
|
||||
using LightState::LightState;
|
||||
};
|
||||
|
||||
class AddressableLight : public LightOutput, public Component {
|
||||
public:
|
||||
virtual int32_t size() const = 0;
|
||||
@@ -81,7 +87,7 @@ class AddressableLight : public LightOutput, public Component {
|
||||
void mark_shown_() {
|
||||
#ifdef USE_POWER_SUPPLY
|
||||
for (const auto &c : *this) {
|
||||
if (c.get().is_on()) {
|
||||
if (c.get_red_raw() > 0 || c.get_green_raw() > 0 || c.get_blue_raw() > 0 || c.get_white_raw() > 0) {
|
||||
this->power_.request();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -16,24 +16,94 @@ class AddressableLightWrapper : public light::AddressableLight {
|
||||
|
||||
void clear_effect_data() override { this->wrapper_state_[4] = 0; }
|
||||
|
||||
light::LightTraits get_traits() override { return this->light_state_->get_traits(); }
|
||||
light::LightTraits get_traits() override {
|
||||
LightTraits traits;
|
||||
|
||||
// Choose which color mode to use.
|
||||
// This is ordered by how closely each color mode matches the underlying RGBW data structure used in LightPartition.
|
||||
ColorMode color_mode_precedence[] = {ColorMode::RGB_WHITE,
|
||||
ColorMode::RGB_COLD_WARM_WHITE,
|
||||
ColorMode::RGB_COLOR_TEMPERATURE,
|
||||
ColorMode::RGB,
|
||||
ColorMode::WHITE,
|
||||
ColorMode::COLD_WARM_WHITE,
|
||||
ColorMode::COLOR_TEMPERATURE,
|
||||
ColorMode::BRIGHTNESS,
|
||||
ColorMode::ON_OFF,
|
||||
ColorMode::UNKNOWN};
|
||||
|
||||
LightTraits parent_traits = this->light_state_->get_traits();
|
||||
for (auto cm : color_mode_precedence) {
|
||||
if (parent_traits.supports_color_mode(cm)) {
|
||||
this->color_mode_ = cm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Report a color mode that's compatible with both the partition and the underlying light
|
||||
switch (this->color_mode_) {
|
||||
case ColorMode::RGB_WHITE:
|
||||
case ColorMode::RGB_COLD_WARM_WHITE:
|
||||
case ColorMode::RGB_COLOR_TEMPERATURE:
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB_WHITE});
|
||||
break;
|
||||
|
||||
case ColorMode::RGB:
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB});
|
||||
break;
|
||||
|
||||
case ColorMode::WHITE:
|
||||
case ColorMode::COLD_WARM_WHITE:
|
||||
case ColorMode::COLOR_TEMPERATURE:
|
||||
case ColorMode::BRIGHTNESS:
|
||||
traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS});
|
||||
break;
|
||||
|
||||
case ColorMode::ON_OFF:
|
||||
traits.set_supported_color_modes({light::ColorMode::ON_OFF});
|
||||
break;
|
||||
|
||||
default:
|
||||
traits.set_supported_color_modes({light::ColorMode::UNKNOWN});
|
||||
}
|
||||
|
||||
return traits;
|
||||
}
|
||||
|
||||
void write_state(light::LightState *state) override {
|
||||
// Don't overwrite state if the underlying light is turned on
|
||||
if (this->light_state_->remote_values.is_on()) {
|
||||
this->mark_shown_();
|
||||
return;
|
||||
}
|
||||
|
||||
float gamma = this->light_state_->get_gamma_correct();
|
||||
float r = gamma_uncorrect(this->wrapper_state_[0] / 255.0f, gamma);
|
||||
float g = gamma_uncorrect(this->wrapper_state_[1] / 255.0f, gamma);
|
||||
float b = gamma_uncorrect(this->wrapper_state_[2] / 255.0f, gamma);
|
||||
float w = gamma_uncorrect(this->wrapper_state_[3] / 255.0f, gamma);
|
||||
float brightness = fmaxf(r, fmaxf(g, b));
|
||||
|
||||
auto call = this->light_state_->make_call();
|
||||
call.set_state(true);
|
||||
call.set_brightness_if_supported(1.0f);
|
||||
call.set_color_brightness_if_supported(brightness);
|
||||
call.set_red_if_supported(r);
|
||||
call.set_green_if_supported(g);
|
||||
call.set_blue_if_supported(b);
|
||||
call.set_white_if_supported(w);
|
||||
|
||||
float color_brightness = fmaxf(r, fmaxf(g, b));
|
||||
float brightness = fmaxf(color_brightness, w);
|
||||
if (brightness == 0.0f) {
|
||||
call.set_state(false);
|
||||
} else {
|
||||
color_brightness /= brightness;
|
||||
w /= brightness;
|
||||
|
||||
call.set_state(true);
|
||||
call.set_color_mode_if_supported(this->color_mode_);
|
||||
call.set_brightness_if_supported(brightness);
|
||||
call.set_color_brightness_if_supported(color_brightness);
|
||||
call.set_red_if_supported(r);
|
||||
call.set_green_if_supported(g);
|
||||
call.set_blue_if_supported(b);
|
||||
call.set_white_if_supported(w);
|
||||
call.set_warm_white_if_supported(w);
|
||||
call.set_cold_white_if_supported(w);
|
||||
}
|
||||
call.set_transition_length_if_supported(0);
|
||||
call.set_publish(false);
|
||||
call.set_save(false);
|
||||
@@ -50,6 +120,7 @@ class AddressableLightWrapper : public light::AddressableLight {
|
||||
|
||||
light::LightState *light_state_;
|
||||
uint8_t *wrapper_state_;
|
||||
ColorMode color_mode_{ColorMode::UNKNOWN};
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
|
||||
@@ -39,7 +39,15 @@ class LightTransformer {
|
||||
|
||||
protected:
|
||||
/// The progress of this transition, on a scale of 0 to 1.
|
||||
float get_progress_() { return clamp((millis() - this->start_time_) / float(this->length_), 0.0f, 1.0f); }
|
||||
float get_progress_() {
|
||||
uint32_t now = esphome::millis();
|
||||
if (now < this->start_time_)
|
||||
return 0.0f;
|
||||
if (now >= this->start_time_ + this->length_)
|
||||
return 1.0f;
|
||||
|
||||
return clamp((now - this->start_time_) / float(this->length_), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
uint32_t start_time_;
|
||||
uint32_t length_;
|
||||
|
||||
@@ -18,10 +18,13 @@ class LightTransitionTransformer : public LightTransformer {
|
||||
this->start_values_.set_brightness(0.0f);
|
||||
}
|
||||
|
||||
// When turning light off from on state, use source state and only decrease brightness to zero.
|
||||
// When turning light off from on state, use source state and only decrease brightness to zero. Use a second
|
||||
// variable for transition end state, as overwriting target_values breaks LightState logic.
|
||||
if (this->start_values_.is_on() && !this->target_values_.is_on()) {
|
||||
this->target_values_ = LightColorValues(this->start_values_);
|
||||
this->target_values_.set_brightness(0.0f);
|
||||
this->end_values_ = LightColorValues(this->start_values_);
|
||||
this->end_values_.set_brightness(0.0f);
|
||||
} else {
|
||||
this->end_values_ = LightColorValues(this->target_values_);
|
||||
}
|
||||
|
||||
// When changing color mode, go through off state, as color modes are orthogonal and there can't be two active.
|
||||
@@ -43,7 +46,7 @@ class LightTransitionTransformer : public LightTransformer {
|
||||
}
|
||||
|
||||
LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_;
|
||||
LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_;
|
||||
LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->end_values_;
|
||||
if (this->changing_color_mode_)
|
||||
p = p < 0.5f ? p * 2 : (p - 0.5) * 2;
|
||||
|
||||
@@ -57,6 +60,7 @@ class LightTransitionTransformer : public LightTransformer {
|
||||
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
|
||||
|
||||
bool changing_color_mode_{false};
|
||||
LightColorValues end_values_{};
|
||||
LightColorValues intermediate_values_{};
|
||||
};
|
||||
|
||||
@@ -69,9 +73,7 @@ class LightFlashTransformer : public LightTransformer {
|
||||
if (this->transition_length_ * 2 > this->length_)
|
||||
this->transition_length_ = this->length_ / 2;
|
||||
|
||||
// do not create transition if length is 0
|
||||
if (this->transition_length_ == 0)
|
||||
return;
|
||||
this->begun_lightstate_restore_ = false;
|
||||
|
||||
// first transition to original target
|
||||
this->transformer_ = this->state_.get_output()->create_default_transition();
|
||||
@@ -79,40 +81,45 @@ class LightFlashTransformer : public LightTransformer {
|
||||
}
|
||||
|
||||
optional<LightColorValues> apply() override {
|
||||
// transition transformer does not handle 0 length as progress returns nan
|
||||
if (this->transition_length_ == 0)
|
||||
return this->target_values_;
|
||||
optional<LightColorValues> result = {};
|
||||
|
||||
if (this->transformer_ == nullptr && millis() > this->start_time_ + this->length_ - this->transition_length_) {
|
||||
// second transition back to start value
|
||||
this->transformer_ = this->state_.get_output()->create_default_transition();
|
||||
this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_);
|
||||
this->begun_lightstate_restore_ = true;
|
||||
}
|
||||
|
||||
if (this->transformer_ != nullptr) {
|
||||
if (!this->transformer_->is_finished()) {
|
||||
return this->transformer_->apply();
|
||||
} else {
|
||||
result = this->transformer_->apply();
|
||||
|
||||
if (this->transformer_->is_finished()) {
|
||||
this->transformer_->stop();
|
||||
this->transformer_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (millis() > this->start_time_ + this->length_ - this->transition_length_) {
|
||||
// second transition back to start value
|
||||
this->transformer_ = this->state_.get_output()->create_default_transition();
|
||||
this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_);
|
||||
}
|
||||
|
||||
// once transition is complete, don't change states until next transition
|
||||
return optional<LightColorValues>();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Restore the original values after the flash.
|
||||
void stop() override {
|
||||
if (this->transformer_ != nullptr) {
|
||||
this->transformer_->stop();
|
||||
this->transformer_ = nullptr;
|
||||
}
|
||||
this->state_.current_values = this->get_start_values();
|
||||
this->state_.remote_values = this->get_start_values();
|
||||
this->state_.publish_state();
|
||||
}
|
||||
|
||||
bool is_finished() override { return this->begun_lightstate_restore_ && LightTransformer::is_finished(); }
|
||||
|
||||
protected:
|
||||
LightState &state_;
|
||||
uint32_t transition_length_;
|
||||
std::unique_ptr<LightTransformer> transformer_{nullptr};
|
||||
bool begun_lightstate_restore_;
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
|
||||
@@ -4,10 +4,9 @@ from esphome import automation
|
||||
# Base
|
||||
light_ns = cg.esphome_ns.namespace("light")
|
||||
LightState = light_ns.class_("LightState", cg.EntityBase, cg.Component)
|
||||
# Fake class for addressable lights
|
||||
AddressableLightState = light_ns.class_("LightState", LightState)
|
||||
AddressableLightState = light_ns.class_("AddressableLightState", LightState)
|
||||
LightOutput = light_ns.class_("LightOutput")
|
||||
AddressableLight = light_ns.class_("AddressableLight", cg.Component)
|
||||
AddressableLight = light_ns.class_("AddressableLight", LightOutput, cg.Component)
|
||||
AddressableLightRef = AddressableLight.operator("ref")
|
||||
|
||||
Color = cg.esphome_ns.class_("Color")
|
||||
|
||||
@@ -19,6 +19,8 @@ from esphome.const import (
|
||||
CONF_TX_BUFFER_SIZE,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
from esphome.components.esp32.const import VARIANT_ESP32S2, VARIANT_ESP32C3
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
logger_ns = cg.esphome_ns.namespace("logger")
|
||||
@@ -52,6 +54,10 @@ LOG_LEVEL_SEVERITY = [
|
||||
"VERY_VERBOSE",
|
||||
]
|
||||
|
||||
ESP32_REDUCED_VARIANTS = [VARIANT_ESP32C3, VARIANT_ESP32S2]
|
||||
|
||||
UART_SELECTION_ESP32_REDUCED = ["UART0", "UART1"]
|
||||
|
||||
UART_SELECTION_ESP32 = ["UART0", "UART1", "UART2"]
|
||||
|
||||
UART_SELECTION_ESP8266 = ["UART0", "UART0_SWAP", "UART1"]
|
||||
@@ -75,6 +81,8 @@ is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
|
||||
|
||||
def uart_selection(value):
|
||||
if CORE.is_esp32:
|
||||
if get_esp32_variant() in ESP32_REDUCED_VARIANTS:
|
||||
return cv.one_of(*UART_SELECTION_ESP32_REDUCED, upper=True)(value)
|
||||
return cv.one_of(*UART_SELECTION_ESP32, upper=True)(value)
|
||||
if CORE.is_esp8266:
|
||||
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
|
||||
|
||||
@@ -153,13 +153,9 @@ void Logger::pre_setup() {
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
break;
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2)
|
||||
case UART_SELECTION_UART2:
|
||||
#if !CONFIG_IDF_TARGET_ESP32S2 && !CONFIG_IDF_TARGET_ESP32C3
|
||||
// FIXME: Validate in config that UART2 can't be set for ESP32-S2 (only has
|
||||
// UART0-UART1)
|
||||
this->hw_serial_ = &Serial2;
|
||||
#endif
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
@@ -173,9 +169,11 @@ void Logger::pre_setup() {
|
||||
case UART_SELECTION_UART1:
|
||||
uart_num_ = UART_NUM_1;
|
||||
break;
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2)
|
||||
case UART_SELECTION_UART2:
|
||||
uart_num_ = UART_NUM_2;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
uart_config_t uart_config{};
|
||||
uart_config.baud_rate = (int) baud_rate_;
|
||||
@@ -223,7 +221,7 @@ UARTSelection Logger::get_uart() const { return this->uart_; }
|
||||
void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) {
|
||||
this->log_callback_.add(std::move(callback));
|
||||
}
|
||||
float Logger::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
|
||||
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
|
||||
const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
|
||||
#ifdef USE_ESP32
|
||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART2"};
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace logger {
|
||||
enum UARTSelection {
|
||||
UART_SELECTION_UART0 = 0,
|
||||
UART_SELECTION_UART1,
|
||||
#ifdef USE_ESP32
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32S2)
|
||||
UART_SELECTION_UART2,
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
|
||||
@@ -13,10 +13,20 @@ CONF_SCROLL_DELAY = "scroll_delay"
|
||||
CONF_SCROLL_ENABLE = "scroll_enable"
|
||||
CONF_SCROLL_MODE = "scroll_mode"
|
||||
CONF_REVERSE_ENABLE = "reverse_enable"
|
||||
CONF_NUM_CHIP_LINES = "num_chip_lines"
|
||||
CONF_CHIP_LINES_STYLE = "chip_lines_style"
|
||||
|
||||
integration_ns = cg.esphome_ns.namespace("max7219digit")
|
||||
ChipLinesStyle = integration_ns.enum("ChipLinesStyle")
|
||||
CHIP_LINES_STYLE = {
|
||||
"ZIGZAG": ChipLinesStyle.ZIGZAG,
|
||||
"SNAKE": ChipLinesStyle.SNAKE,
|
||||
}
|
||||
|
||||
ScrollMode = integration_ns.enum("ScrollMode")
|
||||
SCROLL_MODES = {
|
||||
"CONTINUOUS": 0,
|
||||
"STOP": 1,
|
||||
"CONTINUOUS": ScrollMode.CONTINUOUS,
|
||||
"STOP": ScrollMode.STOP,
|
||||
}
|
||||
|
||||
CHIP_MODES = {
|
||||
@@ -37,6 +47,10 @@ CONFIG_SCHEMA = (
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MAX7219Component),
|
||||
cv.Optional(CONF_NUM_CHIPS, default=4): cv.int_range(min=1, max=255),
|
||||
cv.Optional(CONF_NUM_CHIP_LINES, default=1): cv.int_range(min=1, max=255),
|
||||
cv.Optional(CONF_CHIP_LINES_STYLE, default="SNAKE"): cv.enum(
|
||||
CHIP_LINES_STYLE, upper=True
|
||||
),
|
||||
cv.Optional(CONF_INTENSITY, default=15): cv.int_range(min=0, max=15),
|
||||
cv.Optional(CONF_ROTATE_CHIP, default="0"): cv.enum(CHIP_MODES, upper=True),
|
||||
cv.Optional(CONF_SCROLL_MODE, default="CONTINUOUS"): cv.enum(
|
||||
@@ -67,6 +81,8 @@ async def to_code(config):
|
||||
await display.register_display(var, config)
|
||||
|
||||
cg.add(var.set_num_chips(config[CONF_NUM_CHIPS]))
|
||||
cg.add(var.set_num_chip_lines(config[CONF_NUM_CHIP_LINES]))
|
||||
cg.add(var.set_chip_lines_style(config[CONF_CHIP_LINES_STYLE]))
|
||||
cg.add(var.set_intensity(config[CONF_INTENSITY]))
|
||||
cg.add(var.set_chip_orientation(config[CONF_ROTATE_CHIP]))
|
||||
cg.add(var.set_scroll_speed(config[CONF_SCROLL_SPEED]))
|
||||
|
||||
@@ -26,9 +26,12 @@ void MAX7219Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up MAX7219_DIGITS...");
|
||||
this->spi_setup();
|
||||
this->stepsleft_ = 0;
|
||||
this->max_displaybuffer_.reserve(500); // Create base space to write buffer
|
||||
// Initialize buffer with 0 for display so all non written pixels are blank
|
||||
this->max_displaybuffer_.resize(this->num_chips_ * 8, 0);
|
||||
for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
|
||||
std::vector<uint8_t> vec(1);
|
||||
this->max_displaybuffer_.push_back(vec);
|
||||
// Initialize buffer with 0 for display so all non written pixels are blank
|
||||
this->max_displaybuffer_[chip_line].resize(get_width_internal(), 0);
|
||||
}
|
||||
// let's assume the user has all 8 digits connected, only important in daisy chained setups anyway
|
||||
this->send_to_all_(MAX7219_REGISTER_SCAN_LIMIT, 7);
|
||||
// let's use our own ASCII -> led pattern encoding
|
||||
@@ -46,6 +49,8 @@ void MAX7219Component::setup() {
|
||||
void MAX7219Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MAX7219DIGIT:");
|
||||
ESP_LOGCONFIG(TAG, " Number of Chips: %u", this->num_chips_);
|
||||
ESP_LOGCONFIG(TAG, " Number of Chips Lines: %u", this->num_chip_lines_);
|
||||
ESP_LOGCONFIG(TAG, " Chips Lines Style : %u", this->chip_lines_style_);
|
||||
ESP_LOGCONFIG(TAG, " Intensity: %u", this->intensity_);
|
||||
ESP_LOGCONFIG(TAG, " Scroll Mode: %u", this->scroll_mode_);
|
||||
ESP_LOGCONFIG(TAG, " Scroll Speed: %u", this->scroll_speed_);
|
||||
@@ -59,19 +64,19 @@ void MAX7219Component::loop() {
|
||||
uint32_t now = millis();
|
||||
|
||||
// check if the buffer has shrunk past the current position since last update
|
||||
if ((this->max_displaybuffer_.size() >= this->old_buffer_size_ + 3) ||
|
||||
(this->max_displaybuffer_.size() <= this->old_buffer_size_ - 3)) {
|
||||
if ((this->max_displaybuffer_[0].size() >= this->old_buffer_size_ + 3) ||
|
||||
(this->max_displaybuffer_[0].size() <= this->old_buffer_size_ - 3)) {
|
||||
this->stepsleft_ = 0;
|
||||
this->display();
|
||||
this->old_buffer_size_ = this->max_displaybuffer_.size();
|
||||
this->old_buffer_size_ = this->max_displaybuffer_[0].size();
|
||||
}
|
||||
|
||||
// Reset the counter back to 0 when full string has been displayed.
|
||||
if (this->stepsleft_ > this->max_displaybuffer_.size())
|
||||
if (this->stepsleft_ > this->max_displaybuffer_[0].size())
|
||||
this->stepsleft_ = 0;
|
||||
|
||||
// Return if there is no need to scroll or scroll is off
|
||||
if (!this->scroll_ || (this->max_displaybuffer_.size() <= this->num_chips_ * 8)) {
|
||||
if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= get_width_internal())) {
|
||||
this->display();
|
||||
return;
|
||||
}
|
||||
@@ -82,8 +87,8 @@ void MAX7219Component::loop() {
|
||||
}
|
||||
|
||||
// Dwell time at end of string in case of stop at end
|
||||
if (this->scroll_mode_ == 1) {
|
||||
if (this->stepsleft_ >= this->max_displaybuffer_.size() - this->num_chips_ * 8 + 1) {
|
||||
if (this->scroll_mode_ == ScrollMode::STOP) {
|
||||
if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - get_width_internal() + 1) {
|
||||
if (now - this->last_scroll_ >= this->scroll_dwell_) {
|
||||
this->stepsleft_ = 0;
|
||||
this->last_scroll_ = now;
|
||||
@@ -107,30 +112,53 @@ void MAX7219Component::display() {
|
||||
// Run this routine for the rows of every chip 8x row 0 top to 7 bottom
|
||||
// Fill the pixel parameter with display data
|
||||
// Send the data to the chip
|
||||
for (uint8_t i = 0; i < this->num_chips_; i++) {
|
||||
for (uint8_t j = 0; j < 8; j++) {
|
||||
if (this->reverse_) {
|
||||
pixels[j] = this->max_displaybuffer_[(this->num_chips_ - i - 1) * 8 + j];
|
||||
} else {
|
||||
pixels[j] = this->max_displaybuffer_[i * 8 + j];
|
||||
for (uint8_t chip = 0; chip < this->num_chips_ / this->num_chip_lines_; chip++) {
|
||||
for (uint8_t chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
|
||||
for (uint8_t j = 0; j < 8; j++) {
|
||||
bool reverse =
|
||||
chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE ? !this->reverse_ : this->reverse_;
|
||||
if (reverse) {
|
||||
pixels[j] =
|
||||
this->max_displaybuffer_[chip_line][(this->num_chips_ / this->num_chip_lines_ - chip - 1) * 8 + j];
|
||||
} else {
|
||||
pixels[j] = this->max_displaybuffer_[chip_line][chip * 8 + j];
|
||||
}
|
||||
}
|
||||
if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE)
|
||||
this->orientation_ = orientation_180_();
|
||||
this->send64pixels(chip_line * this->num_chips_ / this->num_chip_lines_ + chip, pixels);
|
||||
if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE)
|
||||
this->orientation_ = orientation_180_();
|
||||
}
|
||||
this->send64pixels(i, pixels);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t MAX7219Component::orientation_180_() {
|
||||
switch (this->orientation_) {
|
||||
case 0:
|
||||
return 2;
|
||||
case 1:
|
||||
return 3;
|
||||
case 2:
|
||||
return 0;
|
||||
case 3:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int MAX7219Component::get_height_internal() {
|
||||
return 8; // TO BE DONE -> STACK TWO DISPLAYS ON TOP OF EACH OTHER
|
||||
// TO BE DONE -> CREATE Virtual size of screen and scroll
|
||||
return 8 * this->num_chip_lines_; // TO BE DONE -> CREATE Virtual size of screen and scroll
|
||||
}
|
||||
|
||||
int MAX7219Component::get_width_internal() { return this->num_chips_ * 8; }
|
||||
|
||||
size_t MAX7219Component::get_buffer_length_() { return this->num_chips_ * 8; }
|
||||
int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; }
|
||||
|
||||
void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||
if (x + 1 > this->max_displaybuffer_.size()) { // Extend the display buffer in case required
|
||||
this->max_displaybuffer_.resize(x + 1, this->bckgrnd_);
|
||||
if (x + 1 > this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required
|
||||
for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
|
||||
this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_);
|
||||
}
|
||||
}
|
||||
|
||||
if ((y >= this->get_height_internal()) || (y < 0) || (x < 0)) // If pixel is outside display then dont draw
|
||||
@@ -140,9 +168,9 @@ void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color colo
|
||||
uint8_t subpos = y; // Y is starting at 0 top left
|
||||
|
||||
if (color.is_on()) {
|
||||
this->max_displaybuffer_[pos] |= (1 << subpos);
|
||||
this->max_displaybuffer_[subpos / 8][pos] |= (1 << subpos % 8);
|
||||
} else {
|
||||
this->max_displaybuffer_[pos] &= ~(1 << subpos);
|
||||
this->max_displaybuffer_[subpos / 8][pos] &= ~(1 << subpos % 8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,8 +186,10 @@ void MAX7219Component::send_to_all_(uint8_t a_register, uint8_t data) {
|
||||
}
|
||||
void MAX7219Component::update() {
|
||||
this->update_ = true;
|
||||
this->max_displaybuffer_.clear();
|
||||
this->max_displaybuffer_.resize(this->num_chips_ * 8, this->bckgrnd_);
|
||||
for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
|
||||
this->max_displaybuffer_[chip_line].clear();
|
||||
this->max_displaybuffer_[chip_line].resize(get_width_internal(), this->bckgrnd_);
|
||||
}
|
||||
if (this->writer_local_.has_value()) // insert Labda function if available
|
||||
(*this->writer_local_)(*this);
|
||||
}
|
||||
@@ -175,7 +205,7 @@ void MAX7219Component::turn_on_off(bool on_off) {
|
||||
}
|
||||
}
|
||||
|
||||
void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell) {
|
||||
void MAX7219Component::scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell) {
|
||||
this->set_scroll(on_off);
|
||||
this->set_scroll_mode(mode);
|
||||
this->set_scroll_speed(speed);
|
||||
@@ -183,7 +213,7 @@ void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_
|
||||
this->set_scroll_delay(delay);
|
||||
}
|
||||
|
||||
void MAX7219Component::scroll(bool on_off, uint8_t mode) {
|
||||
void MAX7219Component::scroll(bool on_off, ScrollMode mode) {
|
||||
this->set_scroll(on_off);
|
||||
this->set_scroll_mode(mode);
|
||||
}
|
||||
@@ -196,24 +226,26 @@ void MAX7219Component::intensity(uint8_t intensity) {
|
||||
void MAX7219Component::scroll(bool on_off) { this->set_scroll(on_off); }
|
||||
|
||||
void MAX7219Component::scroll_left() {
|
||||
if (this->update_) {
|
||||
this->max_displaybuffer_.push_back(this->bckgrnd_);
|
||||
for (uint16_t i = 0; i < this->stepsleft_; i++) {
|
||||
this->max_displaybuffer_.push_back(this->max_displaybuffer_.front());
|
||||
this->max_displaybuffer_.erase(this->max_displaybuffer_.begin());
|
||||
this->update_ = false;
|
||||
for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
|
||||
if (this->update_) {
|
||||
this->max_displaybuffer_[chip_line].push_back(this->bckgrnd_);
|
||||
for (uint16_t i = 0; i < this->stepsleft_; i++) {
|
||||
this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front());
|
||||
this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin());
|
||||
}
|
||||
} else {
|
||||
this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front());
|
||||
this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin());
|
||||
}
|
||||
} else {
|
||||
this->max_displaybuffer_.push_back(this->max_displaybuffer_.front());
|
||||
this->max_displaybuffer_.erase(this->max_displaybuffer_.begin());
|
||||
}
|
||||
this->update_ = false;
|
||||
this->stepsleft_++;
|
||||
}
|
||||
|
||||
void MAX7219Component::send_char(uint8_t chip, uint8_t data) {
|
||||
// get this character from PROGMEM
|
||||
for (uint8_t i = 0; i < 8; i++)
|
||||
this->max_displaybuffer_[chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]);
|
||||
this->max_displaybuffer_[0][chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]);
|
||||
} // end of send_char
|
||||
|
||||
// send one character (data) to position (chip)
|
||||
|
||||
@@ -12,6 +12,16 @@
|
||||
namespace esphome {
|
||||
namespace max7219digit {
|
||||
|
||||
enum ChipLinesStyle {
|
||||
ZIGZAG = 0,
|
||||
SNAKE,
|
||||
};
|
||||
|
||||
enum ScrollMode {
|
||||
CONTINUOUS = 0,
|
||||
STOP,
|
||||
};
|
||||
|
||||
class MAX7219Component;
|
||||
|
||||
using max7219_writer_t = std::function<void(MAX7219Component &)>;
|
||||
@@ -46,20 +56,22 @@ class MAX7219Component : public PollingComponent,
|
||||
|
||||
void set_intensity(uint8_t intensity) { this->intensity_ = intensity; };
|
||||
void set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; };
|
||||
void set_num_chip_lines(uint8_t num_chip_lines) { this->num_chip_lines_ = num_chip_lines; };
|
||||
void set_chip_lines_style(ChipLinesStyle chip_lines_style) { this->chip_lines_style_ = chip_lines_style; };
|
||||
void set_chip_orientation(uint8_t rotate) { this->orientation_ = rotate; };
|
||||
void set_scroll_speed(uint16_t speed) { this->scroll_speed_ = speed; };
|
||||
void set_scroll_dwell(uint16_t dwell) { this->scroll_dwell_ = dwell; };
|
||||
void set_scroll_delay(uint16_t delay) { this->scroll_delay_ = delay; };
|
||||
void set_scroll(bool on_off) { this->scroll_ = on_off; };
|
||||
void set_scroll_mode(uint8_t mode) { this->scroll_mode_ = mode; };
|
||||
void set_scroll_mode(ScrollMode mode) { this->scroll_mode_ = mode; };
|
||||
void set_reverse(bool on_off) { this->reverse_ = on_off; };
|
||||
|
||||
void send_char(uint8_t chip, uint8_t data);
|
||||
void send64pixels(uint8_t chip, const uint8_t pixels[8]);
|
||||
|
||||
void scroll_left();
|
||||
void scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell);
|
||||
void scroll(bool on_off, uint8_t mode);
|
||||
void scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell);
|
||||
void scroll(bool on_off, ScrollMode mode);
|
||||
void scroll(bool on_off);
|
||||
void intensity(uint8_t intensity);
|
||||
|
||||
@@ -84,9 +96,12 @@ class MAX7219Component : public PollingComponent,
|
||||
protected:
|
||||
void send_byte_(uint8_t a_register, uint8_t data);
|
||||
void send_to_all_(uint8_t a_register, uint8_t data);
|
||||
uint8_t orientation_180_();
|
||||
|
||||
uint8_t intensity_; /// Intensity of the display from 0 to 15 (most)
|
||||
uint8_t num_chips_;
|
||||
uint8_t num_chip_lines_;
|
||||
ChipLinesStyle chip_lines_style_;
|
||||
bool scroll_;
|
||||
bool reverse_;
|
||||
bool update_{false};
|
||||
@@ -94,11 +109,11 @@ class MAX7219Component : public PollingComponent,
|
||||
uint16_t scroll_delay_;
|
||||
uint16_t scroll_dwell_;
|
||||
uint16_t old_buffer_size_ = 0;
|
||||
uint8_t scroll_mode_;
|
||||
ScrollMode scroll_mode_;
|
||||
bool invert_ = false;
|
||||
uint8_t orientation_;
|
||||
uint8_t bckgrnd_ = 0x0;
|
||||
std::vector<uint8_t> max_displaybuffer_;
|
||||
std::vector<std::vector<uint8_t>> max_displaybuffer_;
|
||||
uint32_t last_scroll_ = 0;
|
||||
uint16_t stepsleft_;
|
||||
size_t get_buffer_length_();
|
||||
|
||||
1
esphome/components/md5/__init__.py
Normal file
1
esphome/components/md5/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
51
esphome/components/md5/md5.cpp
Normal file
51
esphome/components/md5/md5.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include "md5.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace md5 {
|
||||
|
||||
void MD5Digest::init() {
|
||||
memset(this->digest_, 0, 16);
|
||||
MD5Init(&this->ctx_);
|
||||
}
|
||||
|
||||
void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); }
|
||||
|
||||
void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); }
|
||||
|
||||
void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); }
|
||||
|
||||
void MD5Digest::get_hex(char *output) {
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
sprintf(output + i * 2, "%02x", this->digest_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool MD5Digest::equals_bytes(const char *expected) {
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
if (expected[i] != this->digest_[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MD5Digest::equals_hex(const char *expected) {
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
auto high = parse_hex(expected[i * 2]);
|
||||
auto low = parse_hex(expected[i * 2 + 1]);
|
||||
if (!high.has_value() || !low.has_value()) {
|
||||
return false;
|
||||
}
|
||||
auto value = (*high << 4) | *low;
|
||||
if (value != this->digest_[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace md5
|
||||
} // namespace esphome
|
||||
58
esphome/components/md5/md5.h
Normal file
58
esphome/components/md5/md5.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#include "esp32/rom/md5_hash.h"
|
||||
#define MD5_CTX_TYPE MD5Context
|
||||
#endif
|
||||
|
||||
#if defined(USE_ARDUINO) && defined(USE_ESP32)
|
||||
#include "rom/md5_hash.h"
|
||||
#define MD5_CTX_TYPE MD5Context
|
||||
#endif
|
||||
|
||||
#if defined(USE_ARDUINO) && defined(USE_ESP8266)
|
||||
#include <md5.h>
|
||||
#define MD5_CTX_TYPE md5_context_t
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace md5 {
|
||||
|
||||
class MD5Digest {
|
||||
public:
|
||||
MD5Digest() = default;
|
||||
~MD5Digest() = default;
|
||||
|
||||
/// Initialize a new MD5 digest computation.
|
||||
void init();
|
||||
|
||||
/// Add bytes of data for the digest.
|
||||
void add(const uint8_t *data, size_t len);
|
||||
void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); }
|
||||
|
||||
/// Compute the digest, based on the provided data.
|
||||
void calculate();
|
||||
|
||||
/// Retrieve the MD5 digest as bytes.
|
||||
/// The output must be able to hold 16 bytes or more.
|
||||
void get_bytes(uint8_t *output);
|
||||
|
||||
/// Retrieve the MD5 digest as hex characters.
|
||||
/// The output must be able to hold 32 bytes or more.
|
||||
void get_hex(char *output);
|
||||
|
||||
/// Compare the digest against a provided byte-encoded digest (16 bytes).
|
||||
bool equals_bytes(const char *expected);
|
||||
|
||||
/// Compare the digest against a provided hex-encoded digest (32 bytes).
|
||||
bool equals_hex(const char *expected);
|
||||
|
||||
protected:
|
||||
MD5_CTX_TYPE ctx_{};
|
||||
uint8_t digest_[16];
|
||||
};
|
||||
|
||||
} // namespace md5
|
||||
} // namespace esphome
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/version.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_API
|
||||
#include "esphome/components/api/api_server.h"
|
||||
@@ -13,17 +14,20 @@
|
||||
namespace esphome {
|
||||
namespace mdns {
|
||||
|
||||
static const char *const TAG = "mdns";
|
||||
|
||||
#ifndef WEBSERVER_PORT
|
||||
#define WEBSERVER_PORT 80 // NOLINT
|
||||
#endif
|
||||
|
||||
std::vector<MDNSService> MDNSComponent::compile_services_() {
|
||||
std::vector<MDNSService> res;
|
||||
void MDNSComponent::compile_records_() {
|
||||
this->hostname_ = App.get_name();
|
||||
|
||||
this->services_.clear();
|
||||
#ifdef USE_API
|
||||
if (api::global_api_server != nullptr) {
|
||||
MDNSService service{};
|
||||
service.service_type = "esphomelib";
|
||||
service.service_type = "_esphomelib";
|
||||
service.proto = "_tcp";
|
||||
service.port = api::global_api_server->get_port();
|
||||
service.txt_records.push_back({"version", ESPHOME_VERSION});
|
||||
@@ -50,33 +54,43 @@ std::vector<MDNSService> MDNSComponent::compile_services_() {
|
||||
service.txt_records.push_back({"package_import_url", dashboard_import::get_package_import_url()});
|
||||
#endif
|
||||
|
||||
res.push_back(service);
|
||||
this->services_.push_back(service);
|
||||
}
|
||||
#endif // USE_API
|
||||
|
||||
#ifdef USE_PROMETHEUS
|
||||
{
|
||||
MDNSService service{};
|
||||
service.service_type = "prometheus-http";
|
||||
service.service_type = "_prometheus-http";
|
||||
service.proto = "_tcp";
|
||||
service.port = WEBSERVER_PORT;
|
||||
res.push_back(service);
|
||||
this->services_.push_back(service);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (res.empty()) {
|
||||
if (this->services_.empty()) {
|
||||
// Publish "http" service if not using native API
|
||||
// This is just to have *some* mDNS service so that .local resolution works
|
||||
MDNSService service{};
|
||||
service.service_type = "http";
|
||||
service.service_type = "_http";
|
||||
service.proto = "_tcp";
|
||||
service.port = WEBSERVER_PORT;
|
||||
service.txt_records.push_back({"version", ESPHOME_VERSION});
|
||||
res.push_back(service);
|
||||
this->services_.push_back(service);
|
||||
}
|
||||
}
|
||||
|
||||
void MDNSComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "mDNS:");
|
||||
ESP_LOGCONFIG(TAG, " Hostname: %s", this->hostname_.c_str());
|
||||
ESP_LOGV(TAG, " Services:");
|
||||
for (const auto &service : this->services_) {
|
||||
ESP_LOGV(TAG, " - %s, %s, %d", service.service_type.c_str(), service.proto.c_str(), service.port);
|
||||
for (const auto &record : service.txt_records) {
|
||||
ESP_LOGV(TAG, " TXT: %s = %s", record.key.c_str(), record.value.c_str());
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
std::string MDNSComponent::compile_hostname_() { return App.get_name(); }
|
||||
|
||||
} // namespace mdns
|
||||
} // namespace esphome
|
||||
|
||||
@@ -13,7 +13,11 @@ struct MDNSTXTRecord {
|
||||
};
|
||||
|
||||
struct MDNSService {
|
||||
// service name _including_ underscore character prefix
|
||||
// as defined in RFC6763 Section 7
|
||||
std::string service_type;
|
||||
// second label indicating protocol _including_ underscore character prefix
|
||||
// as defined in RFC6763 Section 7, like "_tcp" or "_udp"
|
||||
std::string proto;
|
||||
uint16_t port;
|
||||
std::vector<MDNSTXTRecord> txt_records;
|
||||
@@ -22,6 +26,7 @@ struct MDNSService {
|
||||
class MDNSComponent : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
#if defined(USE_ESP8266) && defined(USE_ARDUINO)
|
||||
void loop() override;
|
||||
@@ -29,8 +34,9 @@ class MDNSComponent : public Component {
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
protected:
|
||||
std::vector<MDNSService> compile_services_();
|
||||
std::string compile_hostname_();
|
||||
std::vector<MDNSService> services_{};
|
||||
std::string hostname_;
|
||||
void compile_records_();
|
||||
};
|
||||
|
||||
} // namespace mdns
|
||||
|
||||
@@ -7,13 +7,12 @@
|
||||
namespace esphome {
|
||||
namespace mdns {
|
||||
|
||||
static const char *const TAG = "mdns";
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
MDNS.begin(compile_hostname_().c_str());
|
||||
this->compile_records_();
|
||||
|
||||
auto services = compile_services_();
|
||||
for (const auto &service : services) {
|
||||
MDNS.begin(this->hostname_.c_str());
|
||||
|
||||
for (const auto &service : this->services_) {
|
||||
MDNS.addService(service.service_type.c_str(), service.proto.c_str(), service.port);
|
||||
for (const auto &record : service.txt_records) {
|
||||
MDNS.addServiceTxt(service.service_type.c_str(), service.proto.c_str(), record.key.c_str(), record.value.c_str());
|
||||
|
||||
@@ -9,17 +9,28 @@
|
||||
namespace esphome {
|
||||
namespace mdns {
|
||||
|
||||
static const char *const TAG = "mdns";
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
network::IPAddress addr = network::get_ip_address();
|
||||
MDNS.begin(compile_hostname_().c_str(), (uint32_t) addr);
|
||||
this->compile_records_();
|
||||
|
||||
auto services = compile_services_();
|
||||
for (const auto &service : services) {
|
||||
MDNS.addService(service.service_type.c_str(), service.proto.c_str(), service.port);
|
||||
network::IPAddress addr = network::get_ip_address();
|
||||
MDNS.begin(this->hostname_.c_str(), (uint32_t) addr);
|
||||
|
||||
for (const auto &service : this->services_) {
|
||||
// Strip the leading underscore from the proto and service_type. While it is
|
||||
// part of the wire protocol to have an underscore, and for example ESP-IDF
|
||||
// expects the underscore to be there, the ESP8266 implementation always adds
|
||||
// the underscore itself.
|
||||
auto proto = service.proto.c_str();
|
||||
while (*proto == '_') {
|
||||
proto++;
|
||||
}
|
||||
auto service_type = service.service_type.c_str();
|
||||
while (*service_type == '_') {
|
||||
service_type++;
|
||||
}
|
||||
MDNS.addService(service_type, proto, service.port);
|
||||
for (const auto &record : service.txt_records) {
|
||||
MDNS.addServiceTxt(service.service_type.c_str(), service.proto.c_str(), record.key.c_str(), record.value.c_str());
|
||||
MDNS.addServiceTxt(service_type, proto, record.key.c_str(), record.value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,18 +11,19 @@ namespace mdns {
|
||||
static const char *const TAG = "mdns";
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
this->compile_records_();
|
||||
|
||||
esp_err_t err = mdns_init();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "MDNS init failed: %s", esp_err_to_name(err));
|
||||
ESP_LOGW(TAG, "mDNS init failed: %s", esp_err_to_name(err));
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
mdns_hostname_set(compile_hostname_().c_str());
|
||||
mdns_instance_name_set(compile_hostname_().c_str());
|
||||
mdns_hostname_set(this->hostname_.c_str());
|
||||
mdns_instance_name_set(this->hostname_.c_str());
|
||||
|
||||
auto services = compile_services_();
|
||||
for (const auto &service : services) {
|
||||
for (const auto &service : this->services_) {
|
||||
std::vector<mdns_txt_item_t> txt_records;
|
||||
for (const auto &record : service.txt_records) {
|
||||
mdns_txt_item_t it{};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "adapter.h"
|
||||
#include "ac_adapter.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace midea {
|
||||
namespace ac {
|
||||
|
||||
const char *const Constants::TAG = "midea";
|
||||
const std::string Constants::FREEZE_PROTECTION = "freeze protection";
|
||||
@@ -171,6 +172,7 @@ void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea::
|
||||
traits.add_supported_custom_preset(Constants::FREEZE_PROTECTION);
|
||||
}
|
||||
|
||||
} // namespace ac
|
||||
} // namespace midea
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
// MideaUART
|
||||
#include <Appliance/AirConditioner/AirConditioner.h>
|
||||
|
||||
#include "esphome/components/climate/climate_traits.h"
|
||||
#include "appliance_base.h"
|
||||
#include "air_conditioner.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace midea {
|
||||
namespace ac {
|
||||
|
||||
using MideaMode = dudanov::midea::ac::Mode;
|
||||
using MideaSwingMode = dudanov::midea::ac::SwingMode;
|
||||
@@ -41,6 +44,7 @@ class Converters {
|
||||
static void to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities);
|
||||
};
|
||||
|
||||
} // namespace ac
|
||||
} // namespace midea
|
||||
} // namespace esphome
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
namespace esphome {
|
||||
namespace midea {
|
||||
namespace ac {
|
||||
|
||||
template<typename... Ts> class MideaActionBase : public Action<Ts...> {
|
||||
public:
|
||||
@@ -55,6 +56,7 @@ template<typename... Ts> class PowerOffAction : public MideaActionBase<Ts...> {
|
||||
void play(Ts... x) override { this->parent_->do_power_off(); }
|
||||
};
|
||||
|
||||
} // namespace ac
|
||||
} // namespace midea
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "air_conditioner.h"
|
||||
#include "adapter.h"
|
||||
#ifdef USE_REMOTE_TRANSMITTER
|
||||
#include "midea_ir.h"
|
||||
#endif
|
||||
#include "ac_adapter.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace midea {
|
||||
namespace ac {
|
||||
|
||||
static void set_sensor(Sensor *sensor, float value) {
|
||||
if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value))
|
||||
@@ -122,7 +120,7 @@ void AirConditioner::dump_config() {
|
||||
void AirConditioner::do_follow_me(float temperature, bool beeper) {
|
||||
#ifdef USE_REMOTE_TRANSMITTER
|
||||
IrFollowMeData data(static_cast<uint8_t>(lroundf(temperature)), beeper);
|
||||
this->transmit_ir(data);
|
||||
this->transmitter_.transmit(data);
|
||||
#else
|
||||
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
|
||||
#endif
|
||||
@@ -131,7 +129,7 @@ void AirConditioner::do_follow_me(float temperature, bool beeper) {
|
||||
void AirConditioner::do_swing_step() {
|
||||
#ifdef USE_REMOTE_TRANSMITTER
|
||||
IrSpecialData data(0x01);
|
||||
this->transmit_ir(data);
|
||||
this->transmitter_.transmit(data);
|
||||
#else
|
||||
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
|
||||
#endif
|
||||
@@ -143,13 +141,14 @@ void AirConditioner::do_display_toggle() {
|
||||
} else {
|
||||
#ifdef USE_REMOTE_TRANSMITTER
|
||||
IrSpecialData data(0x08);
|
||||
this->transmit_ir(data);
|
||||
this->transmitter_.transmit(data);
|
||||
#else
|
||||
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ac
|
||||
} // namespace midea
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -2,17 +2,25 @@
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
// MideaUART
|
||||
#include <Appliance/AirConditioner/AirConditioner.h>
|
||||
|
||||
#include "appliance_base.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace midea {
|
||||
namespace ac {
|
||||
|
||||
using sensor::Sensor;
|
||||
using climate::ClimateCall;
|
||||
using climate::ClimatePreset;
|
||||
using climate::ClimateTraits;
|
||||
using climate::ClimateMode;
|
||||
using climate::ClimateSwingMode;
|
||||
using climate::ClimateFanMode;
|
||||
|
||||
class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner> {
|
||||
class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>, public climate::Climate {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_outdoor_temperature_sensor(Sensor *sensor) { this->outdoor_sensor_ = sensor; }
|
||||
@@ -31,15 +39,26 @@ class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>
|
||||
void do_beeper_off() { this->set_beeper_feedback(false); }
|
||||
void do_power_on() { this->base_.setPowerState(true); }
|
||||
void do_power_off() { this->base_.setPowerState(false); }
|
||||
void set_supported_modes(const std::set<ClimateMode> &modes) { this->supported_modes_ = modes; }
|
||||
void set_supported_swing_modes(const std::set<ClimateSwingMode> &modes) { this->supported_swing_modes_ = modes; }
|
||||
void set_supported_presets(const std::set<ClimatePreset> &presets) { this->supported_presets_ = presets; }
|
||||
void set_custom_presets(const std::set<std::string> &presets) { this->supported_custom_presets_ = presets; }
|
||||
void set_custom_fan_modes(const std::set<std::string> &modes) { this->supported_custom_fan_modes_ = modes; }
|
||||
|
||||
protected:
|
||||
void control(const ClimateCall &call) override;
|
||||
ClimateTraits traits() override;
|
||||
std::set<ClimateMode> supported_modes_{};
|
||||
std::set<ClimateSwingMode> supported_swing_modes_{};
|
||||
std::set<ClimatePreset> supported_presets_{};
|
||||
std::set<std::string> supported_custom_presets_{};
|
||||
std::set<std::string> supported_custom_fan_modes_{};
|
||||
Sensor *outdoor_sensor_{nullptr};
|
||||
Sensor *humidity_sensor_{nullptr};
|
||||
Sensor *power_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace ac
|
||||
} // namespace midea
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -2,84 +2,97 @@
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
// MideaUART
|
||||
#include <Appliance/ApplianceBase.h>
|
||||
#include <Helpers/Logger.h>
|
||||
|
||||
// Include global defines
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#ifdef USE_REMOTE_TRANSMITTER
|
||||
#include "esphome/components/remote_base/midea_protocol.h"
|
||||
#include "esphome/components/remote_transmitter/remote_transmitter.h"
|
||||
#endif
|
||||
#include <Appliance/ApplianceBase.h>
|
||||
#include <Helpers/Logger.h>
|
||||
#include "ir_transmitter.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace midea {
|
||||
|
||||
using climate::ClimatePreset;
|
||||
using climate::ClimateTraits;
|
||||
using climate::ClimateMode;
|
||||
using climate::ClimateSwingMode;
|
||||
using climate::ClimateFanMode;
|
||||
/* Stream from UART component */
|
||||
class UARTStream : public Stream {
|
||||
public:
|
||||
void set_uart(uart::UARTComponent *uart) { this->uart_ = uart; }
|
||||
|
||||
template<typename T>
|
||||
class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate, public Stream {
|
||||
/* Stream interface implementation */
|
||||
|
||||
int available() override { return this->uart_->available(); }
|
||||
int read() override {
|
||||
uint8_t data;
|
||||
this->uart_->read_byte(&data);
|
||||
return data;
|
||||
}
|
||||
int peek() override {
|
||||
uint8_t data;
|
||||
this->uart_->peek_byte(&data);
|
||||
return data;
|
||||
}
|
||||
size_t write(uint8_t data) override {
|
||||
this->uart_->write_byte(data);
|
||||
return 1;
|
||||
}
|
||||
size_t write(const uint8_t *data, size_t size) override {
|
||||
this->uart_->write_array(data, size);
|
||||
return size;
|
||||
}
|
||||
void flush() override { this->uart_->flush(); }
|
||||
|
||||
protected:
|
||||
uart::UARTComponent *uart_;
|
||||
};
|
||||
|
||||
template<typename T> class ApplianceBase : public Component {
|
||||
static_assert(std::is_base_of<dudanov::midea::ApplianceBase, T>::value,
|
||||
"T must derive from dudanov::midea::ApplianceBase class");
|
||||
|
||||
public:
|
||||
ApplianceBase() {
|
||||
this->base_.setStream(this);
|
||||
this->base_.setStream(&this->stream_);
|
||||
this->base_.addOnStateCallback(std::bind(&ApplianceBase::on_status_change, this));
|
||||
dudanov::midea::ApplianceBase::setLogger(
|
||||
[](int level, const char *tag, int line, const String &format, va_list args) {
|
||||
esp_log_vprintf_(level, tag, line, format.c_str(), args);
|
||||
});
|
||||
}
|
||||
bool can_proceed() override {
|
||||
return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS;
|
||||
}
|
||||
float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; }
|
||||
void setup() override { this->base_.setup(); }
|
||||
void loop() override { this->base_.loop(); }
|
||||
|
||||
#ifdef USE_REMOTE_TRANSMITTER
|
||||
void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_.set_transmitter(transmitter); }
|
||||
#endif
|
||||
|
||||
/* UART communication */
|
||||
|
||||
void set_uart_parent(uart::UARTComponent *parent) { this->stream_.set_uart(parent); }
|
||||
void set_period(uint32_t ms) { this->base_.setPeriod(ms); }
|
||||
void set_response_timeout(uint32_t ms) { this->base_.setTimeout(ms); }
|
||||
void set_request_attempts(uint32_t attempts) { this->base_.setNumAttempts(attempts); }
|
||||
|
||||
/* Component methods */
|
||||
|
||||
void setup() override { this->base_.setup(); }
|
||||
void loop() override { this->base_.loop(); }
|
||||
float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; }
|
||||
bool can_proceed() override {
|
||||
return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS;
|
||||
}
|
||||
|
||||
void set_beeper_feedback(bool state) { this->base_.setBeeper(state); }
|
||||
void set_autoconf(bool value) { this->base_.setAutoconf(value); }
|
||||
void set_supported_modes(const std::set<ClimateMode> &modes) { this->supported_modes_ = modes; }
|
||||
void set_supported_swing_modes(const std::set<ClimateSwingMode> &modes) { this->supported_swing_modes_ = modes; }
|
||||
void set_supported_presets(const std::set<ClimatePreset> &presets) { this->supported_presets_ = presets; }
|
||||
void set_custom_presets(const std::set<std::string> &presets) { this->supported_custom_presets_ = presets; }
|
||||
void set_custom_fan_modes(const std::set<std::string> &modes) { this->supported_custom_fan_modes_ = modes; }
|
||||
virtual void on_status_change() = 0;
|
||||
#ifdef USE_REMOTE_TRANSMITTER
|
||||
void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) {
|
||||
this->transmitter_ = transmitter;
|
||||
}
|
||||
void transmit_ir(remote_base::MideaData &data) {
|
||||
data.finalize();
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
remote_base::MideaProtocol().encode(transmit.get_data(), data);
|
||||
transmit.perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
int available() override { return uart::UARTDevice::available(); }
|
||||
int read() override { return uart::UARTDevice::read(); }
|
||||
int peek() override { return uart::UARTDevice::peek(); }
|
||||
void flush() override { uart::UARTDevice::flush(); }
|
||||
size_t write(uint8_t data) override { return uart::UARTDevice::write(data); }
|
||||
|
||||
protected:
|
||||
T base_;
|
||||
std::set<ClimateMode> supported_modes_{};
|
||||
std::set<ClimateSwingMode> supported_swing_modes_{};
|
||||
std::set<ClimatePreset> supported_presets_{};
|
||||
std::set<std::string> supported_custom_presets_{};
|
||||
std::set<std::string> supported_custom_fan_modes_{};
|
||||
UARTStream stream_;
|
||||
#ifdef USE_REMOTE_TRANSMITTER
|
||||
remote_transmitter::RemoteTransmitterComponent *transmitter_{nullptr};
|
||||
IrTransmitter transmitter_;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -40,9 +40,9 @@ AUTO_LOAD = ["sensor"]
|
||||
CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature"
|
||||
CONF_POWER_USAGE = "power_usage"
|
||||
CONF_HUMIDITY_SETPOINT = "humidity_setpoint"
|
||||
midea_ns = cg.esphome_ns.namespace("midea")
|
||||
AirConditioner = midea_ns.class_("AirConditioner", climate.Climate, cg.Component)
|
||||
Capabilities = midea_ns.namespace("Constants")
|
||||
midea_ac_ns = cg.esphome_ns.namespace("midea").namespace("ac")
|
||||
AirConditioner = midea_ac_ns.class_("AirConditioner", climate.Climate, cg.Component)
|
||||
Capabilities = midea_ac_ns.namespace("Constants")
|
||||
|
||||
|
||||
def templatize(value):
|
||||
@@ -156,13 +156,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
# Actions
|
||||
FollowMeAction = midea_ns.class_("FollowMeAction", automation.Action)
|
||||
DisplayToggleAction = midea_ns.class_("DisplayToggleAction", automation.Action)
|
||||
SwingStepAction = midea_ns.class_("SwingStepAction", automation.Action)
|
||||
BeeperOnAction = midea_ns.class_("BeeperOnAction", automation.Action)
|
||||
BeeperOffAction = midea_ns.class_("BeeperOffAction", automation.Action)
|
||||
PowerOnAction = midea_ns.class_("PowerOnAction", automation.Action)
|
||||
PowerOffAction = midea_ns.class_("PowerOffAction", automation.Action)
|
||||
FollowMeAction = midea_ac_ns.class_("FollowMeAction", automation.Action)
|
||||
DisplayToggleAction = midea_ac_ns.class_("DisplayToggleAction", automation.Action)
|
||||
SwingStepAction = midea_ac_ns.class_("SwingStepAction", automation.Action)
|
||||
BeeperOnAction = midea_ac_ns.class_("BeeperOnAction", automation.Action)
|
||||
BeeperOffAction = midea_ac_ns.class_("BeeperOffAction", automation.Action)
|
||||
PowerOnAction = midea_ac_ns.class_("PowerOnAction", automation.Action)
|
||||
PowerOffAction = midea_ac_ns.class_("PowerOffAction", automation.Action)
|
||||
|
||||
MIDEA_ACTION_BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
namespace esphome {
|
||||
namespace midea {
|
||||
|
||||
using remote_base::RemoteTransmitterBase;
|
||||
using IrData = remote_base::MideaData;
|
||||
|
||||
class IrFollowMeData : public IrData {
|
||||
@@ -38,6 +39,20 @@ class IrSpecialData : public IrData {
|
||||
IrSpecialData(uint8_t code) : IrData({MIDEA_TYPE_SPECIAL, code, 0xFF, 0xFF, 0xFF}) {}
|
||||
};
|
||||
|
||||
class IrTransmitter {
|
||||
public:
|
||||
void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; }
|
||||
void transmit(IrData &data) {
|
||||
data.finalize();
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
remote_base::MideaProtocol().encode(transmit.get_data(), data);
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
protected:
|
||||
RemoteTransmitterBase *transmitter_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace midea
|
||||
} // namespace esphome
|
||||
|
||||
@@ -96,23 +96,27 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
|
||||
ESP_LOGW(TAG, "Modbus CRC Check failed! %02X!=%02X", computed_crc, remote_crc);
|
||||
return false;
|
||||
}
|
||||
|
||||
waiting_for_response = 0;
|
||||
std::vector<uint8_t> data(this->rx_buffer_.begin() + data_offset, this->rx_buffer_.begin() + data_offset + data_len);
|
||||
|
||||
bool found = false;
|
||||
for (auto *device : this->devices_) {
|
||||
if (device->address_ == address) {
|
||||
// Is it an error response?
|
||||
if ((function_code & 0x80) == 0x80) {
|
||||
ESP_LOGW(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]);
|
||||
device->on_modbus_error(function_code & 0x7F, raw[2]);
|
||||
ESP_LOGD(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]);
|
||||
if (waiting_for_response != 0) {
|
||||
device->on_modbus_error(function_code & 0x7F, raw[2]);
|
||||
} else {
|
||||
// Ignore modbus exception not related to a pending command
|
||||
ESP_LOGD(TAG, "Ignoring Modbus error - not expecting a response");
|
||||
}
|
||||
} else {
|
||||
device->on_modbus_data(data);
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
waiting_for_response = 0;
|
||||
|
||||
if (!found) {
|
||||
ESP_LOGW(TAG, "Got Modbus frame from unknown address 0x%02X! ", address);
|
||||
}
|
||||
@@ -135,7 +139,9 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
|
||||
uint8_t payload_len, const uint8_t *payload) {
|
||||
static const size_t MAX_VALUES = 128;
|
||||
|
||||
if (number_of_entities > MAX_VALUES) {
|
||||
// Only check max number of registers for standard function codes
|
||||
// Some devices use non standard codes like 0x43
|
||||
if (number_of_entities > MAX_VALUES && function_code <= 0x10) {
|
||||
ESP_LOGE(TAG, "send too many values %d max=%zu", number_of_entities, MAX_VALUES);
|
||||
return;
|
||||
}
|
||||
@@ -196,6 +202,7 @@ void Modbus::send_raw(const std::vector<uint8_t> &payload) {
|
||||
if (this->flow_control_pin_ != nullptr)
|
||||
this->flow_control_pin_->digital_write(false);
|
||||
waiting_for_response = payload[0];
|
||||
ESP_LOGV(TAG, "Modbus write raw: %s", hexencode(payload).c_str());
|
||||
last_send_ = millis();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType")
|
||||
ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType")
|
||||
MODBUS_REGISTER_TYPE = {
|
||||
"coil": ModbusRegisterType.COIL,
|
||||
"discrete_input": ModbusRegisterType.DISCRETE,
|
||||
"discrete_input": ModbusRegisterType.DISCRETE_INPUT,
|
||||
"holding": ModbusRegisterType.HOLDING,
|
||||
"read": ModbusRegisterType.READ,
|
||||
}
|
||||
@@ -61,6 +61,21 @@ SENSOR_VALUE_TYPE = {
|
||||
"FP32_R": SensorValueType.FP32_R,
|
||||
}
|
||||
|
||||
TYPE_REGISTER_MAP = {
|
||||
"RAW": 1,
|
||||
"U_WORD": 1,
|
||||
"S_WORD": 1,
|
||||
"U_DWORD": 2,
|
||||
"U_DWORD_R": 2,
|
||||
"S_DWORD": 2,
|
||||
"S_DWORD_R": 2,
|
||||
"U_QWORD": 4,
|
||||
"U_QWORDU_R": 4,
|
||||
"S_QWORD": 4,
|
||||
"U_QWORD_R": 4,
|
||||
"FP32": 2,
|
||||
"FP32_R": 2,
|
||||
}
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
|
||||
@@ -260,35 +260,11 @@ struct SensorItem {
|
||||
virtual void parse_and_publish(const std::vector<uint8_t> &data) = 0;
|
||||
|
||||
uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); }
|
||||
|
||||
size_t virtual get_register_size() const {
|
||||
size_t size = 0;
|
||||
switch (sensor_value_type) {
|
||||
case SensorValueType::BIT:
|
||||
size = 1;
|
||||
break;
|
||||
case SensorValueType::U_WORD:
|
||||
case SensorValueType::S_WORD:
|
||||
size = 2;
|
||||
break;
|
||||
case SensorValueType::U_DWORD:
|
||||
case SensorValueType::S_DWORD:
|
||||
case SensorValueType::U_DWORD_R:
|
||||
case SensorValueType::S_DWORD_R:
|
||||
case SensorValueType::FP32:
|
||||
case SensorValueType::FP32_R:
|
||||
size = 4;
|
||||
break;
|
||||
case SensorValueType::U_QWORD:
|
||||
case SensorValueType::U_QWORD_R:
|
||||
case SensorValueType::S_QWORD:
|
||||
case SensorValueType::S_QWORD_R:
|
||||
size = 8;
|
||||
break;
|
||||
case SensorValueType::RAW:
|
||||
size = this->register_count * 2;
|
||||
}
|
||||
return size;
|
||||
if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT)
|
||||
return 1;
|
||||
else
|
||||
return register_count * 2;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from .. import (
|
||||
ModbusController,
|
||||
SENSOR_VALUE_TYPE,
|
||||
SensorItem,
|
||||
TYPE_REGISTER_MAP,
|
||||
)
|
||||
|
||||
|
||||
@@ -39,22 +40,6 @@ ModbusNumber = modbus_controller_ns.class_(
|
||||
"ModbusNumber", cg.Component, number.Number, SensorItem
|
||||
)
|
||||
|
||||
TYPE_REGISTER_MAP = {
|
||||
"RAW": 1,
|
||||
"U_WORD": 1,
|
||||
"S_WORD": 1,
|
||||
"U_DWORD": 2,
|
||||
"U_DWORD_R": 2,
|
||||
"S_DWORD": 2,
|
||||
"S_DWORD_R": 2,
|
||||
"U_QWORD": 4,
|
||||
"U_QWORDU_R": 4,
|
||||
"S_QWORD": 4,
|
||||
"U_QWORD_R": 4,
|
||||
"FP32": 2,
|
||||
"FP32_R": 2,
|
||||
}
|
||||
|
||||
|
||||
def validate_min_max(config):
|
||||
if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]:
|
||||
@@ -144,14 +129,14 @@ async def to_code(config):
|
||||
return_type=cg.optional.template(float),
|
||||
)
|
||||
cg.add(var.set_template(template_))
|
||||
if CONF_WRITE_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
config[CONF_WRITE_LAMBDA],
|
||||
[
|
||||
(ModbusNumber.operator("ptr"), "item"),
|
||||
(cg.float_, "x"),
|
||||
(cg.std_vector.template(cg.uint16).operator("ref"), "payload"),
|
||||
],
|
||||
return_type=cg.optional.template(float),
|
||||
)
|
||||
cg.add(var.set_write_template(template_))
|
||||
if CONF_WRITE_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
config[CONF_WRITE_LAMBDA],
|
||||
[
|
||||
(ModbusNumber.operator("ptr"), "item"),
|
||||
(cg.float_, "x"),
|
||||
(cg.std_vector.template(cg.uint16).operator("ref"), "payload"),
|
||||
],
|
||||
return_type=cg.optional.template(float),
|
||||
)
|
||||
cg.add(var.set_write_template(template_))
|
||||
|
||||
@@ -13,11 +13,13 @@ from .. import (
|
||||
SensorItem,
|
||||
modbus_controller_ns,
|
||||
ModbusController,
|
||||
TYPE_REGISTER_MAP,
|
||||
)
|
||||
|
||||
from ..const import (
|
||||
CONF_BYTE_OFFSET,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_COUNT,
|
||||
CONF_VALUE_TYPE,
|
||||
CONF_WRITE_LAMBDA,
|
||||
)
|
||||
@@ -40,6 +42,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
|
||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
|
||||
cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
|
||||
}
|
||||
@@ -54,8 +57,16 @@ async def to_code(config):
|
||||
# A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
|
||||
if CONF_BYTE_OFFSET in config:
|
||||
byte_offset = config[CONF_BYTE_OFFSET]
|
||||
value_type = config[CONF_VALUE_TYPE]
|
||||
reg_count = config[CONF_REGISTER_COUNT]
|
||||
if reg_count == 0:
|
||||
reg_count = TYPE_REGISTER_MAP[value_type]
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID], config[CONF_ADDRESS], byte_offset, config[CONF_VALUE_TYPE]
|
||||
config[CONF_ID],
|
||||
config[CONF_ADDRESS],
|
||||
byte_offset,
|
||||
value_type,
|
||||
reg_count,
|
||||
)
|
||||
await output.register_output(var, config)
|
||||
cg.add(var.set_write_multiply(config[CONF_MULTIPLY]))
|
||||
|
||||
@@ -11,12 +11,13 @@ using value_to_data_t = std::function<float>(float);
|
||||
|
||||
class ModbusOutput : public output::FloatOutput, public Component, public SensorItem {
|
||||
public:
|
||||
ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type)
|
||||
ModbusOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count)
|
||||
: output::FloatOutput(), Component() {
|
||||
this->register_type = ModbusRegisterType::HOLDING;
|
||||
this->start_address = start_address;
|
||||
this->offset = offset;
|
||||
this->bitmask = bitmask;
|
||||
this->register_count = register_count;
|
||||
this->sensor_value_type = value_type;
|
||||
this->skip_updates = 0;
|
||||
this->start_address += offset;
|
||||
|
||||
@@ -9,6 +9,7 @@ from .. import (
|
||||
ModbusController,
|
||||
MODBUS_REGISTER_TYPE,
|
||||
SENSOR_VALUE_TYPE,
|
||||
TYPE_REGISTER_MAP,
|
||||
)
|
||||
from ..const import (
|
||||
CONF_BITMASK,
|
||||
@@ -29,23 +30,6 @@ ModbusSensor = modbus_controller_ns.class_(
|
||||
"ModbusSensor", cg.Component, sensor.Sensor, SensorItem
|
||||
)
|
||||
|
||||
TYPE_REGISTER_MAP = {
|
||||
"RAW": 1,
|
||||
"U_WORD": 1,
|
||||
"S_WORD": 1,
|
||||
"U_DWORD": 2,
|
||||
"U_DWORD_R": 2,
|
||||
"S_DWORD": 2,
|
||||
"S_DWORD_R": 2,
|
||||
"U_QWORD": 4,
|
||||
"U_QWORDU_R": 4,
|
||||
"S_QWORD": 4,
|
||||
"U_QWORD_R": 4,
|
||||
"FP32": 2,
|
||||
"FP32_R": 2,
|
||||
}
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.SENSOR_SCHEMA.extend(
|
||||
{
|
||||
|
||||
@@ -25,13 +25,6 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi
|
||||
this->sensor_value_type = SensorValueType::RAW;
|
||||
this->force_new_range = force_new_range;
|
||||
}
|
||||
size_t get_register_size() const override {
|
||||
if (sensor_value_type == SensorValueType::RAW) {
|
||||
return this->response_bytes_;
|
||||
} else {
|
||||
return SensorItem::get_register_size();
|
||||
}
|
||||
}
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from esphome.const import (
|
||||
CONF_DISCOVERY,
|
||||
CONF_DISCOVERY_PREFIX,
|
||||
CONF_DISCOVERY_RETAIN,
|
||||
CONF_DISCOVERY_UNIQUE_ID_GENERATOR,
|
||||
CONF_ID,
|
||||
CONF_KEEPALIVE,
|
||||
CONF_LEVEL,
|
||||
@@ -34,6 +35,7 @@ from esphome.const import (
|
||||
CONF_TOPIC,
|
||||
CONF_TOPIC_PREFIX,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_USE_ABBREVIATIONS,
|
||||
CONF_USERNAME,
|
||||
CONF_WILL_MESSAGE,
|
||||
)
|
||||
@@ -94,6 +96,12 @@ MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent)
|
||||
MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent)
|
||||
MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
|
||||
|
||||
MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator")
|
||||
MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = {
|
||||
"legacy": MQTTDiscoveryUniqueIdGenerator.MQTT_LEGACY_UNIQUE_ID_GENERATOR,
|
||||
"mac": MQTTDiscoveryUniqueIdGenerator.MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR,
|
||||
}
|
||||
|
||||
|
||||
def validate_config(value):
|
||||
# Populate default fields
|
||||
@@ -152,6 +160,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(
|
||||
CONF_DISCOVERY_PREFIX, default="homeassistant"
|
||||
): cv.publish_topic,
|
||||
cv.Optional(CONF_DISCOVERY_UNIQUE_ID_GENERATOR, default="legacy"): cv.enum(
|
||||
MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS
|
||||
),
|
||||
cv.Optional(CONF_USE_ABBREVIATIONS, default=True): cv.boolean,
|
||||
cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA,
|
||||
cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA,
|
||||
cv.Optional(CONF_SHUTDOWN_MESSAGE): MQTT_MESSAGE_SCHEMA,
|
||||
@@ -215,7 +227,7 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
|
||||
# https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json
|
||||
cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.4")
|
||||
cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6")
|
||||
cg.add_define("USE_MQTT")
|
||||
cg.add_global(mqtt_ns.using)
|
||||
|
||||
@@ -229,16 +241,28 @@ async def to_code(config):
|
||||
discovery = config[CONF_DISCOVERY]
|
||||
discovery_retain = config[CONF_DISCOVERY_RETAIN]
|
||||
discovery_prefix = config[CONF_DISCOVERY_PREFIX]
|
||||
discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR]
|
||||
|
||||
if not discovery:
|
||||
cg.add(var.disable_discovery())
|
||||
elif discovery == "CLEAN":
|
||||
cg.add(var.set_discovery_info(discovery_prefix, discovery_retain, True))
|
||||
cg.add(
|
||||
var.set_discovery_info(
|
||||
discovery_prefix, discovery_unique_id_generator, discovery_retain, True
|
||||
)
|
||||
)
|
||||
elif CONF_DISCOVERY_RETAIN in config or CONF_DISCOVERY_PREFIX in config:
|
||||
cg.add(var.set_discovery_info(discovery_prefix, discovery_retain))
|
||||
cg.add(
|
||||
var.set_discovery_info(
|
||||
discovery_prefix, discovery_unique_id_generator, discovery_retain
|
||||
)
|
||||
)
|
||||
|
||||
cg.add(var.set_topic_prefix(config[CONF_TOPIC_PREFIX]))
|
||||
|
||||
if config[CONF_USE_ABBREVIATIONS]:
|
||||
cg.add_define("USE_MQTT_ABBREVIATIONS")
|
||||
|
||||
birth_message = config[CONF_BIRTH_MESSAGE]
|
||||
if not birth_message:
|
||||
cg.add(var.disable_birth_message())
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "mqtt_binary_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
|
||||
@@ -29,11 +31,11 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor
|
||||
|
||||
void MQTTBinarySensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
|
||||
if (!this->binary_sensor_->get_device_class().empty())
|
||||
root["device_class"] = this->binary_sensor_->get_device_class();
|
||||
root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class();
|
||||
if (this->binary_sensor_->is_status_binary_sensor())
|
||||
root["payload_on"] = mqtt::global_mqtt_client->get_availability().payload_available;
|
||||
root[MQTT_PAYLOAD_ON] = mqtt::global_mqtt_client->get_availability().payload_available;
|
||||
if (this->binary_sensor_->is_status_binary_sensor())
|
||||
root["payload_off"] = mqtt::global_mqtt_client->get_availability().payload_not_available;
|
||||
root[MQTT_PAYLOAD_OFF] = mqtt::global_mqtt_client->get_availability().payload_not_available;
|
||||
config.command_topic = false;
|
||||
}
|
||||
bool MQTTBinarySensorComponent::send_initial_state() {
|
||||
|
||||
@@ -535,8 +535,10 @@ void MQTTClientComponent::set_birth_message(MQTTMessage &&message) {
|
||||
|
||||
void MQTTClientComponent::set_shutdown_message(MQTTMessage &&message) { this->shutdown_message_ = std::move(message); }
|
||||
|
||||
void MQTTClientComponent::set_discovery_info(std::string &&prefix, bool retain, bool clean) {
|
||||
void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator,
|
||||
bool retain, bool clean) {
|
||||
this->discovery_info_.prefix = std::move(prefix);
|
||||
this->discovery_info_.unique_id_generator = unique_id_generator;
|
||||
this->discovery_info_.retain = retain;
|
||||
this->discovery_info_.clean = clean;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,12 @@ struct Availability {
|
||||
std::string payload_not_available;
|
||||
};
|
||||
|
||||
/// available discovery unique_id generators
|
||||
enum MQTTDiscoveryUniqueIdGenerator {
|
||||
MQTT_LEGACY_UNIQUE_ID_GENERATOR = 0,
|
||||
MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR,
|
||||
};
|
||||
|
||||
/** Internal struct for MQTT Home Assistant discovery
|
||||
*
|
||||
* See <a href="https://www.home-assistant.io/docs/mqtt/discovery/">MQTT Discovery</a>.
|
||||
@@ -63,6 +69,7 @@ struct MQTTDiscoveryInfo {
|
||||
std::string prefix; ///< The Home Assistant discovery prefix. Empty means disabled.
|
||||
bool retain; ///< Whether to retain discovery messages.
|
||||
bool clean;
|
||||
MQTTDiscoveryUniqueIdGenerator unique_id_generator;
|
||||
};
|
||||
|
||||
enum MQTTClientState {
|
||||
@@ -98,9 +105,11 @@ class MQTTClientComponent : public Component {
|
||||
*
|
||||
* See <a href="https://www.home-assistant.io/docs/mqtt/discovery/">MQTT Discovery</a>.
|
||||
* @param prefix The Home Assistant discovery prefix.
|
||||
* @param unique_id_generator Controls how UniqueId is generated.
|
||||
* @param retain Whether to retain discovery messages.
|
||||
*/
|
||||
void set_discovery_info(std::string &&prefix, bool retain, bool clean = false);
|
||||
void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, bool retain,
|
||||
bool clean = false);
|
||||
/// Get Home Assistant discovery info.
|
||||
const MQTTDiscoveryInfo &get_discovery_info() const;
|
||||
/// Globally disable Home Assistant discovery.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "mqtt_climate.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_CLIMATE
|
||||
|
||||
@@ -16,14 +18,14 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
|
||||
// current_temperature_topic
|
||||
if (traits.get_supports_current_temperature()) {
|
||||
// current_temperature_topic
|
||||
root["curr_temp_t"] = this->get_current_temperature_state_topic();
|
||||
root[MQTT_CURRENT_TEMPERATURE_TOPIC] = this->get_current_temperature_state_topic();
|
||||
}
|
||||
// mode_command_topic
|
||||
root["mode_cmd_t"] = this->get_mode_command_topic();
|
||||
root[MQTT_MODE_COMMAND_TOPIC] = this->get_mode_command_topic();
|
||||
// mode_state_topic
|
||||
root["mode_stat_t"] = this->get_mode_state_topic();
|
||||
root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic();
|
||||
// modes
|
||||
JsonArray &modes = root.createNestedArray("modes");
|
||||
JsonArray &modes = root.createNestedArray(MQTT_MODES);
|
||||
// sort array for nice UI in HA
|
||||
if (traits.supports_mode(CLIMATE_MODE_AUTO))
|
||||
modes.add("auto");
|
||||
@@ -41,45 +43,45 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
|
||||
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
// temperature_low_command_topic
|
||||
root["temp_lo_cmd_t"] = this->get_target_temperature_low_command_topic();
|
||||
root[MQTT_TEMPERATURE_LOW_COMMAND_TOPIC] = this->get_target_temperature_low_command_topic();
|
||||
// temperature_low_state_topic
|
||||
root["temp_lo_stat_t"] = this->get_target_temperature_low_state_topic();
|
||||
root[MQTT_TEMPERATURE_LOW_STATE_TOPIC] = this->get_target_temperature_low_state_topic();
|
||||
// temperature_high_command_topic
|
||||
root["temp_hi_cmd_t"] = this->get_target_temperature_high_command_topic();
|
||||
root[MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC] = this->get_target_temperature_high_command_topic();
|
||||
// temperature_high_state_topic
|
||||
root["temp_hi_stat_t"] = this->get_target_temperature_high_state_topic();
|
||||
root[MQTT_TEMPERATURE_HIGH_STATE_TOPIC] = this->get_target_temperature_high_state_topic();
|
||||
} else {
|
||||
// temperature_command_topic
|
||||
root["temp_cmd_t"] = this->get_target_temperature_command_topic();
|
||||
root[MQTT_TEMPERATURE_COMMAND_TOPIC] = this->get_target_temperature_command_topic();
|
||||
// temperature_state_topic
|
||||
root["temp_stat_t"] = this->get_target_temperature_state_topic();
|
||||
root[MQTT_TEMPERATURE_STATE_TOPIC] = this->get_target_temperature_state_topic();
|
||||
}
|
||||
|
||||
// min_temp
|
||||
root["min_temp"] = traits.get_visual_min_temperature();
|
||||
root[MQTT_MIN_TEMP] = traits.get_visual_min_temperature();
|
||||
// max_temp
|
||||
root["max_temp"] = traits.get_visual_max_temperature();
|
||||
root[MQTT_MAX_TEMP] = traits.get_visual_max_temperature();
|
||||
// temp_step
|
||||
root["temp_step"] = traits.get_visual_temperature_step();
|
||||
// temperature units are always coerced to Celsius internally
|
||||
root["temp_unit"] = "C";
|
||||
root[MQTT_TEMPERATURE_UNIT] = "C";
|
||||
|
||||
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
|
||||
// away_mode_command_topic
|
||||
root["away_mode_cmd_t"] = this->get_away_command_topic();
|
||||
root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic();
|
||||
// away_mode_state_topic
|
||||
root["away_mode_stat_t"] = this->get_away_state_topic();
|
||||
root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic();
|
||||
}
|
||||
if (traits.get_supports_action()) {
|
||||
// action_topic
|
||||
root["act_t"] = this->get_action_state_topic();
|
||||
root[MQTT_ACTION_TOPIC] = this->get_action_state_topic();
|
||||
}
|
||||
|
||||
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
|
||||
// fan_mode_command_topic
|
||||
root["fan_mode_cmd_t"] = this->get_fan_mode_command_topic();
|
||||
root[MQTT_FAN_MODE_COMMAND_TOPIC] = this->get_fan_mode_command_topic();
|
||||
// fan_mode_state_topic
|
||||
root["fan_mode_stat_t"] = this->get_fan_mode_state_topic();
|
||||
root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic();
|
||||
// fan_modes
|
||||
JsonArray &fan_modes = root.createNestedArray("fan_modes");
|
||||
if (traits.supports_fan_mode(CLIMATE_FAN_ON))
|
||||
@@ -106,9 +108,9 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
|
||||
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
// swing_mode_command_topic
|
||||
root["swing_mode_cmd_t"] = this->get_swing_mode_command_topic();
|
||||
root[MQTT_SWING_MODE_COMMAND_TOPIC] = this->get_swing_mode_command_topic();
|
||||
// swing_mode_state_topic
|
||||
root["swing_mode_stat_t"] = this->get_swing_mode_state_topic();
|
||||
root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic();
|
||||
// swing_modes
|
||||
JsonArray &swing_modes = root.createNestedArray("swing_modes");
|
||||
if (traits.supports_swing_mode(CLIMATE_SWING_OFF))
|
||||
@@ -135,7 +137,7 @@ void MQTTClimateComponent::setup() {
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
this->subscribe(this->get_target_temperature_low_command_topic(),
|
||||
[this](const std::string &topic, const std::string &payload) {
|
||||
auto val = parse_float(payload);
|
||||
auto val = parse_number<float>(payload);
|
||||
if (!val.has_value()) {
|
||||
ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
|
||||
return;
|
||||
@@ -146,7 +148,7 @@ void MQTTClimateComponent::setup() {
|
||||
});
|
||||
this->subscribe(this->get_target_temperature_high_command_topic(),
|
||||
[this](const std::string &topic, const std::string &payload) {
|
||||
auto val = parse_float(payload);
|
||||
auto val = parse_number<float>(payload);
|
||||
if (!val.has_value()) {
|
||||
ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
|
||||
return;
|
||||
@@ -158,7 +160,7 @@ void MQTTClimateComponent::setup() {
|
||||
} else {
|
||||
this->subscribe(this->get_target_temperature_command_topic(),
|
||||
[this](const std::string &topic, const std::string &payload) {
|
||||
auto val = parse_float(payload);
|
||||
auto val = parse_number<float>(payload);
|
||||
if (!val.has_value()) {
|
||||
ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
|
||||
return;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
@@ -15,7 +17,7 @@ static const char *const TAG = "mqtt.component";
|
||||
void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; }
|
||||
|
||||
std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const {
|
||||
std::string sanitized_name = sanitize_string_allowlist(App.get_name(), HOSTNAME_CHARACTER_ALLOWLIST);
|
||||
std::string sanitized_name = str_sanitize(App.get_name());
|
||||
return discovery_info.prefix + "/" + this->component_type() + "/" + sanitized_name + "/" +
|
||||
this->get_default_object_id_() + "/config";
|
||||
}
|
||||
@@ -69,49 +71,68 @@ bool MQTTComponent::send_discovery_() {
|
||||
this->send_discovery(root, config);
|
||||
|
||||
// Fields from EntityBase
|
||||
root["name"] = this->friendly_name();
|
||||
root[MQTT_NAME] = this->friendly_name();
|
||||
if (this->is_disabled_by_default())
|
||||
root["enabled_by_default"] = false;
|
||||
root[MQTT_ENABLED_BY_DEFAULT] = false;
|
||||
if (!this->get_icon().empty())
|
||||
root["icon"] = this->get_icon();
|
||||
root[MQTT_ICON] = this->get_icon();
|
||||
|
||||
switch (this->get_entity()->get_entity_category()) {
|
||||
case ENTITY_CATEGORY_NONE:
|
||||
break;
|
||||
case ENTITY_CATEGORY_CONFIG:
|
||||
root[MQTT_ENTITY_CATEGORY] = "config";
|
||||
break;
|
||||
case ENTITY_CATEGORY_DIAGNOSTIC:
|
||||
root[MQTT_ENTITY_CATEGORY] = "diagnostic";
|
||||
break;
|
||||
}
|
||||
|
||||
if (config.state_topic)
|
||||
root["state_topic"] = this->get_state_topic_();
|
||||
root[MQTT_STATE_TOPIC] = this->get_state_topic_();
|
||||
if (config.command_topic)
|
||||
root["command_topic"] = this->get_command_topic_();
|
||||
root[MQTT_COMMAND_TOPIC] = this->get_command_topic_();
|
||||
|
||||
if (this->availability_ == nullptr) {
|
||||
if (!global_mqtt_client->get_availability().topic.empty()) {
|
||||
root["availability_topic"] = global_mqtt_client->get_availability().topic;
|
||||
root[MQTT_AVAILABILITY_TOPIC] = global_mqtt_client->get_availability().topic;
|
||||
if (global_mqtt_client->get_availability().payload_available != "online")
|
||||
root["payload_available"] = global_mqtt_client->get_availability().payload_available;
|
||||
root[MQTT_PAYLOAD_AVAILABLE] = global_mqtt_client->get_availability().payload_available;
|
||||
if (global_mqtt_client->get_availability().payload_not_available != "offline")
|
||||
root["payload_not_available"] = global_mqtt_client->get_availability().payload_not_available;
|
||||
root[MQTT_PAYLOAD_NOT_AVAILABLE] = global_mqtt_client->get_availability().payload_not_available;
|
||||
}
|
||||
} else if (!this->availability_->topic.empty()) {
|
||||
root["availability_topic"] = this->availability_->topic;
|
||||
root[MQTT_AVAILABILITY_TOPIC] = this->availability_->topic;
|
||||
if (this->availability_->payload_available != "online")
|
||||
root["payload_available"] = this->availability_->payload_available;
|
||||
root[MQTT_PAYLOAD_AVAILABLE] = this->availability_->payload_available;
|
||||
if (this->availability_->payload_not_available != "offline")
|
||||
root["payload_not_available"] = this->availability_->payload_not_available;
|
||||
root[MQTT_PAYLOAD_NOT_AVAILABLE] = this->availability_->payload_not_available;
|
||||
}
|
||||
|
||||
const std::string &node_name = App.get_name();
|
||||
std::string unique_id = this->unique_id();
|
||||
if (!unique_id.empty()) {
|
||||
root["unique_id"] = unique_id;
|
||||
root[MQTT_UNIQUE_ID] = unique_id;
|
||||
} else {
|
||||
// default to almost-unique ID. It's a hack but the only way to get that
|
||||
// gorgeous device registry view.
|
||||
root["unique_id"] = "ESP" + this->component_type() + this->get_default_object_id_();
|
||||
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
|
||||
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
|
||||
char friendly_name_hash[9];
|
||||
sprintf(friendly_name_hash, "%08x", fnv1_hash(this->friendly_name()));
|
||||
friendly_name_hash[8] = 0; // ensure the hash-string ends with null
|
||||
root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash;
|
||||
} else {
|
||||
// default to almost-unique ID. It's a hack but the only way to get that
|
||||
// gorgeous device registry view.
|
||||
root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_();
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject &device_info = root.createNestedObject("device");
|
||||
device_info["identifiers"] = get_mac_address();
|
||||
device_info["name"] = node_name;
|
||||
device_info["sw_version"] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time();
|
||||
device_info["model"] = ESPHOME_BOARD;
|
||||
device_info["manufacturer"] = "espressif";
|
||||
JsonObject &device_info = root.createNestedObject(MQTT_DEVICE);
|
||||
device_info[MQTT_DEVICE_IDENTIFIERS] = get_mac_address();
|
||||
device_info[MQTT_DEVICE_NAME] = node_name;
|
||||
device_info[MQTT_DEVICE_SW_VERSION] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time();
|
||||
device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = "espressif";
|
||||
},
|
||||
0, discovery_info.retain);
|
||||
}
|
||||
@@ -123,7 +144,7 @@ bool MQTTComponent::is_discovery_enabled() const {
|
||||
}
|
||||
|
||||
std::string MQTTComponent::get_default_object_id_() const {
|
||||
return sanitize_string_allowlist(to_lowercase_underscore(this->friendly_name()), HOSTNAME_CHARACTER_ALLOWLIST);
|
||||
return str_sanitize(str_snake_case(this->friendly_name()));
|
||||
}
|
||||
|
||||
void MQTTComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) {
|
||||
|
||||
521
esphome/components/mqtt/mqtt_const.h
Normal file
521
esphome/components/mqtt/mqtt_const.h
Normal file
@@ -0,0 +1,521 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
#ifdef USE_MQTT_ABBREVIATIONS
|
||||
|
||||
constexpr const char *const MQTT_ACTION_TOPIC = "act_t";
|
||||
constexpr const char *const MQTT_ACTION_TEMPLATE = "act_tpl";
|
||||
constexpr const char *const MQTT_AUTOMATION_TYPE = "atype";
|
||||
constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_cmd_t";
|
||||
constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_stat_tpl";
|
||||
constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_stat_t";
|
||||
constexpr const char *const MQTT_AVAILABILITY = "avty";
|
||||
constexpr const char *const MQTT_AVAILABILITY_MODE = "avty_mode";
|
||||
constexpr const char *const MQTT_AVAILABILITY_TOPIC = "avty_t";
|
||||
constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_cmd_t";
|
||||
constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_stat_tpl";
|
||||
constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_stat_t";
|
||||
constexpr const char *const MQTT_BLUE_TEMPLATE = "b_tpl";
|
||||
constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "bri_cmd_t";
|
||||
constexpr const char *const MQTT_BRIGHTNESS_SCALE = "bri_scl";
|
||||
constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "bri_stat_t";
|
||||
constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "bri_tpl";
|
||||
constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "bri_val_tpl";
|
||||
constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "clr_temp_cmd_tpl";
|
||||
constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "bat_lev_t";
|
||||
constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "bat_lev_tpl";
|
||||
constexpr const char *const MQTT_CONFIGURATION_URL = "cu";
|
||||
constexpr const char *const MQTT_CHARGING_TOPIC = "chrg_t";
|
||||
constexpr const char *const MQTT_CHARGING_TEMPLATE = "chrg_tpl";
|
||||
constexpr const char *const MQTT_COLOR_MODE = "clrm";
|
||||
constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "clrm_stat_t";
|
||||
constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "clrm_val_tpl";
|
||||
constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "clr_temp_cmd_t";
|
||||
constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "clr_temp_stat_t";
|
||||
constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "clr_temp_tpl";
|
||||
constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "clr_temp_val_tpl";
|
||||
constexpr const char *const MQTT_CLEANING_TOPIC = "cln_t";
|
||||
constexpr const char *const MQTT_CLEANING_TEMPLATE = "cln_tpl";
|
||||
constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "cmd_off_tpl";
|
||||
constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "cmd_on_tpl";
|
||||
constexpr const char *const MQTT_COMMAND_TOPIC = "cmd_t";
|
||||
constexpr const char *const MQTT_COMMAND_TEMPLATE = "cmd_tpl";
|
||||
constexpr const char *const MQTT_CODE_ARM_REQUIRED = "cod_arm_req";
|
||||
constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "cod_dis_req";
|
||||
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "curr_temp_t";
|
||||
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "curr_temp_tpl";
|
||||
constexpr const char *const MQTT_DEVICE = "dev";
|
||||
constexpr const char *const MQTT_DEVICE_CLASS = "dev_cla";
|
||||
constexpr const char *const MQTT_DOCKED_TOPIC = "dock_t";
|
||||
constexpr const char *const MQTT_DOCKED_TEMPLATE = "dock_tpl";
|
||||
constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "en";
|
||||
constexpr const char *const MQTT_ERROR_TOPIC = "err_t";
|
||||
constexpr const char *const MQTT_ERROR_TEMPLATE = "err_tpl";
|
||||
constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fanspd_t";
|
||||
constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fanspd_tpl";
|
||||
constexpr const char *const MQTT_FAN_SPEED_LIST = "fanspd_lst";
|
||||
constexpr const char *const MQTT_FLASH_TIME_LONG = "flsh_tlng";
|
||||
constexpr const char *const MQTT_FLASH_TIME_SHORT = "flsh_tsht";
|
||||
constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "fx_cmd_t";
|
||||
constexpr const char *const MQTT_EFFECT_LIST = "fx_list";
|
||||
constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "fx_stat_t";
|
||||
constexpr const char *const MQTT_EFFECT_TEMPLATE = "fx_tpl";
|
||||
constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "fx_val_tpl";
|
||||
constexpr const char *const MQTT_EXPIRE_AFTER = "exp_aft";
|
||||
constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_cmd_tpl";
|
||||
constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_cmd_t";
|
||||
constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_stat_tpl";
|
||||
constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_stat_t";
|
||||
constexpr const char *const MQTT_FORCE_UPDATE = "frc_upd";
|
||||
constexpr const char *const MQTT_GREEN_TEMPLATE = "g_tpl";
|
||||
constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_cmd_tpl";
|
||||
constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_cmd_t";
|
||||
constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_stat_tpl";
|
||||
constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_stat_t";
|
||||
constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_cmd_t";
|
||||
constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_stat_t";
|
||||
constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_val_tpl";
|
||||
constexpr const char *const MQTT_ICON = "ic";
|
||||
constexpr const char *const MQTT_INITIAL = "init";
|
||||
constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "hum_cmd_t";
|
||||
constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "hum_cmd_tpl";
|
||||
constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "hum_stat_t";
|
||||
constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "hum_state_tpl";
|
||||
constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attr";
|
||||
constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attr_t";
|
||||
constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attr_tpl";
|
||||
constexpr const char *const MQTT_LAST_RESET_TOPIC = "lrst_t";
|
||||
constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "lrst_val_tpl";
|
||||
constexpr const char *const MQTT_MAX = "max";
|
||||
constexpr const char *const MQTT_MIN = "min";
|
||||
constexpr const char *const MQTT_MAX_HUMIDITY = "max_hum";
|
||||
constexpr const char *const MQTT_MIN_HUMIDITY = "min_hum";
|
||||
constexpr const char *const MQTT_MAX_MIREDS = "max_mirs";
|
||||
constexpr const char *const MQTT_MIN_MIREDS = "min_mirs";
|
||||
constexpr const char *const MQTT_MAX_TEMP = "max_temp";
|
||||
constexpr const char *const MQTT_MIN_TEMP = "min_temp";
|
||||
constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_cmd_tpl";
|
||||
constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_cmd_t";
|
||||
constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_stat_t";
|
||||
constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_stat_tpl";
|
||||
constexpr const char *const MQTT_MODES = "modes";
|
||||
constexpr const char *const MQTT_NAME = "name";
|
||||
constexpr const char *const MQTT_OFF_DELAY = "off_dly";
|
||||
constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_cmd_type";
|
||||
constexpr const char *const MQTT_OPTIONS = "ops";
|
||||
constexpr const char *const MQTT_OPTIMISTIC = "opt";
|
||||
constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "osc_cmd_t";
|
||||
constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "osc_cmd_tpl";
|
||||
constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "osc_stat_t";
|
||||
constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "osc_val_tpl";
|
||||
constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "pct_cmd_t";
|
||||
constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "pct_cmd_tpl";
|
||||
constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "pct_stat_t";
|
||||
constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "pct_val_tpl";
|
||||
constexpr const char *const MQTT_PAYLOAD = "pl";
|
||||
constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "pl_arm_away";
|
||||
constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "pl_arm_home";
|
||||
constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "pl_arm_nite";
|
||||
constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "pl_arm_vacation";
|
||||
constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "pl_arm_custom_b";
|
||||
constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "pl_avail";
|
||||
constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "pl_cln_sp";
|
||||
constexpr const char *const MQTT_PAYLOAD_CLOSE = "pl_cls";
|
||||
constexpr const char *const MQTT_PAYLOAD_DISARM = "pl_disarm";
|
||||
constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "pl_hi_spd";
|
||||
constexpr const char *const MQTT_PAYLOAD_HOME = "pl_home";
|
||||
constexpr const char *const MQTT_PAYLOAD_LOCK = "pl_lock";
|
||||
constexpr const char *const MQTT_PAYLOAD_LOCATE = "pl_loc";
|
||||
constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "pl_lo_spd";
|
||||
constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "pl_med_spd";
|
||||
constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "pl_not_avail";
|
||||
constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "pl_not_home";
|
||||
constexpr const char *const MQTT_PAYLOAD_OFF = "pl_off";
|
||||
constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "pl_off_spd";
|
||||
constexpr const char *const MQTT_PAYLOAD_ON = "pl_on";
|
||||
constexpr const char *const MQTT_PAYLOAD_OPEN = "pl_open";
|
||||
constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "pl_osc_off";
|
||||
constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "pl_osc_on";
|
||||
constexpr const char *const MQTT_PAYLOAD_PAUSE = "pl_paus";
|
||||
constexpr const char *const MQTT_PAYLOAD_RESET = "pl_rst";
|
||||
constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "pl_rst_hum";
|
||||
constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "pl_rst_mode";
|
||||
constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "pl_rst_pct";
|
||||
constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "pl_rst_pr_mode";
|
||||
constexpr const char *const MQTT_PAYLOAD_STOP = "pl_stop";
|
||||
constexpr const char *const MQTT_PAYLOAD_START = "pl_strt";
|
||||
constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "pl_stpa";
|
||||
constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "pl_ret";
|
||||
constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "pl_toff";
|
||||
constexpr const char *const MQTT_PAYLOAD_TURN_ON = "pl_ton";
|
||||
constexpr const char *const MQTT_PAYLOAD_UNLOCK = "pl_unlk";
|
||||
constexpr const char *const MQTT_POSITION_CLOSED = "pos_clsd";
|
||||
constexpr const char *const MQTT_POSITION_OPEN = "pos_open";
|
||||
constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "pow_cmd_t";
|
||||
constexpr const char *const MQTT_POWER_STATE_TOPIC = "pow_stat_t";
|
||||
constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "pow_stat_tpl";
|
||||
constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "pr_mode_cmd_t";
|
||||
constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "pr_mode_cmd_tpl";
|
||||
constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "pr_mode_stat_t";
|
||||
constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "pr_mode_val_tpl";
|
||||
constexpr const char *const MQTT_PRESET_MODES = "pr_modes";
|
||||
constexpr const char *const MQTT_RED_TEMPLATE = "r_tpl";
|
||||
constexpr const char *const MQTT_RETAIN = "ret";
|
||||
constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_cmd_tpl";
|
||||
constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_cmd_t";
|
||||
constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_stat_t";
|
||||
constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_val_tpl";
|
||||
constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_cmd_tpl";
|
||||
constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_cmd_t";
|
||||
constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_stat_t";
|
||||
constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_val_tpl";
|
||||
constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_cmd_tpl";
|
||||
constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_cmd_t";
|
||||
constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_stat_t";
|
||||
constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_val_tpl";
|
||||
constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_cmd_t";
|
||||
constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off";
|
||||
constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_spd_t";
|
||||
constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_pos_tpl";
|
||||
constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_pos_t";
|
||||
constexpr const char *const MQTT_POSITION_TOPIC = "pos_t";
|
||||
constexpr const char *const MQTT_POSITION_TEMPLATE = "pos_tpl";
|
||||
constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "spd_cmd_t";
|
||||
constexpr const char *const MQTT_SPEED_STATE_TOPIC = "spd_stat_t";
|
||||
constexpr const char *const MQTT_SPEED_RANGE_MIN = "spd_rng_min";
|
||||
constexpr const char *const MQTT_SPEED_RANGE_MAX = "spd_rng_max";
|
||||
constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "spd_val_tpl";
|
||||
constexpr const char *const MQTT_SPEEDS = "spds";
|
||||
constexpr const char *const MQTT_SOURCE_TYPE = "src_type";
|
||||
constexpr const char *const MQTT_STATE_CLASS = "stat_cla";
|
||||
constexpr const char *const MQTT_STATE_CLOSED = "stat_clsd";
|
||||
constexpr const char *const MQTT_STATE_CLOSING = "stat_closing";
|
||||
constexpr const char *const MQTT_STATE_OFF = "stat_off";
|
||||
constexpr const char *const MQTT_STATE_ON = "stat_on";
|
||||
constexpr const char *const MQTT_STATE_OPEN = "stat_open";
|
||||
constexpr const char *const MQTT_STATE_OPENING = "stat_opening";
|
||||
constexpr const char *const MQTT_STATE_STOPPED = "stat_stopped";
|
||||
constexpr const char *const MQTT_STATE_LOCKED = "stat_locked";
|
||||
constexpr const char *const MQTT_STATE_UNLOCKED = "stat_unlocked";
|
||||
constexpr const char *const MQTT_STATE_TOPIC = "stat_t";
|
||||
constexpr const char *const MQTT_STATE_TEMPLATE = "stat_tpl";
|
||||
constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "stat_val_tpl";
|
||||
constexpr const char *const MQTT_STEP = "step";
|
||||
constexpr const char *const MQTT_SUBTYPE = "stype";
|
||||
constexpr const char *const MQTT_SUPPORTED_FEATURES = "sup_feat";
|
||||
constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "sup_clrm";
|
||||
constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_cmd_tpl";
|
||||
constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_cmd_t";
|
||||
constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_stat_tpl";
|
||||
constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_stat_t";
|
||||
constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temp_cmd_tpl";
|
||||
constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temp_cmd_t";
|
||||
constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temp_hi_cmd_tpl";
|
||||
constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temp_hi_cmd_t";
|
||||
constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temp_hi_stat_tpl";
|
||||
constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temp_hi_stat_t";
|
||||
constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temp_lo_cmd_tpl";
|
||||
constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temp_lo_cmd_t";
|
||||
constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temp_lo_stat_tpl";
|
||||
constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temp_lo_stat_t";
|
||||
constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temp_stat_tpl";
|
||||
constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temp_stat_t";
|
||||
constexpr const char *const MQTT_TEMPERATURE_UNIT = "temp_unit";
|
||||
constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_clsd_val";
|
||||
constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_cmd_t";
|
||||
constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_cmd_tpl";
|
||||
constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_inv_stat";
|
||||
constexpr const char *const MQTT_TILT_MAX = "tilt_max";
|
||||
constexpr const char *const MQTT_TILT_MIN = "tilt_min";
|
||||
constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opnd_val";
|
||||
constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_opt";
|
||||
constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_t";
|
||||
constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_tpl";
|
||||
constexpr const char *const MQTT_TOPIC = "t";
|
||||
constexpr const char *const MQTT_UNIQUE_ID = "uniq_id";
|
||||
constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_meas";
|
||||
constexpr const char *const MQTT_VALUE_TEMPLATE = "val_tpl";
|
||||
constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "whit_cmd_t";
|
||||
constexpr const char *const MQTT_WHITE_SCALE = "whit_scl";
|
||||
constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "whit_val_cmd_t";
|
||||
constexpr const char *const MQTT_WHITE_VALUE_SCALE = "whit_val_scl";
|
||||
constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "whit_val_stat_t";
|
||||
constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "whit_val_tpl";
|
||||
constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_cmd_t";
|
||||
constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_stat_t";
|
||||
constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_val_tpl";
|
||||
|
||||
constexpr const char *const MQTT_DEVICE_CONNECTIONS = "cns";
|
||||
constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "ids";
|
||||
constexpr const char *const MQTT_DEVICE_NAME = "name";
|
||||
constexpr const char *const MQTT_DEVICE_MANUFACTURER = "mf";
|
||||
constexpr const char *const MQTT_DEVICE_MODEL = "mdl";
|
||||
constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw";
|
||||
constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "sa";
|
||||
|
||||
#else
|
||||
|
||||
constexpr const char *const MQTT_ACTION_TOPIC = "action_topic";
|
||||
constexpr const char *const MQTT_ACTION_TEMPLATE = "action_template";
|
||||
constexpr const char *const MQTT_AUTOMATION_TYPE = "automation_type";
|
||||
constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_command_topic";
|
||||
constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_state_template";
|
||||
constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_state_topic";
|
||||
constexpr const char *const MQTT_AVAILABILITY = "availability";
|
||||
constexpr const char *const MQTT_AVAILABILITY_MODE = "availability_mode";
|
||||
constexpr const char *const MQTT_AVAILABILITY_TOPIC = "availability_topic";
|
||||
constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_command_topic";
|
||||
constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_state_template";
|
||||
constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_state_topic";
|
||||
constexpr const char *const MQTT_BLUE_TEMPLATE = "blue_template";
|
||||
constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "brightness_command_topic";
|
||||
constexpr const char *const MQTT_BRIGHTNESS_SCALE = "brightness_scale";
|
||||
constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "brightness_state_topic";
|
||||
constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "brightness_template";
|
||||
constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "brightness_value_template";
|
||||
constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "color_temp_command_template";
|
||||
constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "battery_level_topic";
|
||||
constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "battery_level_template";
|
||||
constexpr const char *const MQTT_CONFIGURATION_URL = "configuration_url";
|
||||
constexpr const char *const MQTT_CHARGING_TOPIC = "charging_topic";
|
||||
constexpr const char *const MQTT_CHARGING_TEMPLATE = "charging_template";
|
||||
constexpr const char *const MQTT_COLOR_MODE = "color_mode";
|
||||
constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "color_mode_state_topic";
|
||||
constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "color_mode_value_template";
|
||||
constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "color_temp_command_topic";
|
||||
constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "color_temp_state_topic";
|
||||
constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "color_temp_template";
|
||||
constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "color_temp_value_template";
|
||||
constexpr const char *const MQTT_CLEANING_TOPIC = "cleaning_topic";
|
||||
constexpr const char *const MQTT_CLEANING_TEMPLATE = "cleaning_template";
|
||||
constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "command_off_template";
|
||||
constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "command_on_template";
|
||||
constexpr const char *const MQTT_COMMAND_TOPIC = "command_topic";
|
||||
constexpr const char *const MQTT_COMMAND_TEMPLATE = "command_template";
|
||||
constexpr const char *const MQTT_CODE_ARM_REQUIRED = "code_arm_required";
|
||||
constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "code_disarm_required";
|
||||
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "current_temperature_topic";
|
||||
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "current_temperature_template";
|
||||
constexpr const char *const MQTT_DEVICE = "device";
|
||||
constexpr const char *const MQTT_DEVICE_CLASS = "device_class";
|
||||
constexpr const char *const MQTT_DOCKED_TOPIC = "docked_topic";
|
||||
constexpr const char *const MQTT_DOCKED_TEMPLATE = "docked_template";
|
||||
constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "enabled_by_default";
|
||||
constexpr const char *const MQTT_ERROR_TOPIC = "error_topic";
|
||||
constexpr const char *const MQTT_ERROR_TEMPLATE = "error_template";
|
||||
constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fan_speed_topic";
|
||||
constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fan_speed_template";
|
||||
constexpr const char *const MQTT_FAN_SPEED_LIST = "fan_speed_list";
|
||||
constexpr const char *const MQTT_FLASH_TIME_LONG = "flash_time_long";
|
||||
constexpr const char *const MQTT_FLASH_TIME_SHORT = "flash_time_short";
|
||||
constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "effect_command_topic";
|
||||
constexpr const char *const MQTT_EFFECT_LIST = "effect_list";
|
||||
constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "effect_state_topic";
|
||||
constexpr const char *const MQTT_EFFECT_TEMPLATE = "effect_template";
|
||||
constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "effect_value_template";
|
||||
constexpr const char *const MQTT_EXPIRE_AFTER = "expire_after";
|
||||
constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_command_template";
|
||||
constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_command_topic";
|
||||
constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_state_template";
|
||||
constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic";
|
||||
constexpr const char *const MQTT_FORCE_UPDATE = "force_update";
|
||||
constexpr const char *const MQTT_GREEN_TEMPLATE = "green_template";
|
||||
constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_command_template";
|
||||
constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_command_topic";
|
||||
constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_state_template";
|
||||
constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_state_topic";
|
||||
constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_command_topic";
|
||||
constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_state_topic";
|
||||
constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_value_template";
|
||||
constexpr const char *const MQTT_ICON = "icon";
|
||||
constexpr const char *const MQTT_INITIAL = "initial";
|
||||
constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic";
|
||||
constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "target_humidity_command_template";
|
||||
constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic";
|
||||
constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "target_humidity_state_template";
|
||||
constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attributes";
|
||||
constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attributes_topic";
|
||||
constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attributes_template";
|
||||
constexpr const char *const MQTT_LAST_RESET_TOPIC = "last_reset_topic";
|
||||
constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "last_reset_value_template";
|
||||
constexpr const char *const MQTT_MAX = "max";
|
||||
constexpr const char *const MQTT_MIN = "min";
|
||||
constexpr const char *const MQTT_MAX_HUMIDITY = "max_humidity";
|
||||
constexpr const char *const MQTT_MIN_HUMIDITY = "min_humidity";
|
||||
constexpr const char *const MQTT_MAX_MIREDS = "max_mireds";
|
||||
constexpr const char *const MQTT_MIN_MIREDS = "min_mireds";
|
||||
constexpr const char *const MQTT_MAX_TEMP = "max_temp";
|
||||
constexpr const char *const MQTT_MIN_TEMP = "min_temp";
|
||||
constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_command_template";
|
||||
constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_command_topic";
|
||||
constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_state_topic";
|
||||
constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_state_template";
|
||||
constexpr const char *const MQTT_MODES = "modes";
|
||||
constexpr const char *const MQTT_NAME = "name";
|
||||
constexpr const char *const MQTT_OFF_DELAY = "off_delay";
|
||||
constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_command_type";
|
||||
constexpr const char *const MQTT_OPTIONS = "options";
|
||||
constexpr const char *const MQTT_OPTIMISTIC = "optimistic";
|
||||
constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic";
|
||||
constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "oscillation_command_template";
|
||||
constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "oscillation_state_topic";
|
||||
constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "oscillation_value_template";
|
||||
constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic";
|
||||
constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "percentage_command_template";
|
||||
constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "percentage_state_topic";
|
||||
constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "percentage_value_template";
|
||||
constexpr const char *const MQTT_PAYLOAD = "payload";
|
||||
constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "payload_arm_away";
|
||||
constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "payload_arm_home";
|
||||
constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "payload_arm_night";
|
||||
constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "payload_arm_vacation";
|
||||
constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "payload_arm_custom_bypass";
|
||||
constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "payload_available";
|
||||
constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "payload_clean_spot";
|
||||
constexpr const char *const MQTT_PAYLOAD_CLOSE = "payload_close";
|
||||
constexpr const char *const MQTT_PAYLOAD_DISARM = "payload_disarm";
|
||||
constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "payload_high_speed";
|
||||
constexpr const char *const MQTT_PAYLOAD_HOME = "payload_home";
|
||||
constexpr const char *const MQTT_PAYLOAD_LOCK = "payload_lock";
|
||||
constexpr const char *const MQTT_PAYLOAD_LOCATE = "payload_locate";
|
||||
constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "payload_low_speed";
|
||||
constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "payload_medium_speed";
|
||||
constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "payload_not_available";
|
||||
constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "payload_not_home";
|
||||
constexpr const char *const MQTT_PAYLOAD_OFF = "payload_off";
|
||||
constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "payload_off_speed";
|
||||
constexpr const char *const MQTT_PAYLOAD_ON = "payload_on";
|
||||
constexpr const char *const MQTT_PAYLOAD_OPEN = "payload_open";
|
||||
constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "payload_oscillation_off";
|
||||
constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "payload_oscillation_on";
|
||||
constexpr const char *const MQTT_PAYLOAD_PAUSE = "payload_pause";
|
||||
constexpr const char *const MQTT_PAYLOAD_RESET = "payload_reset";
|
||||
constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "payload_reset_humidity";
|
||||
constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "payload_reset_mode";
|
||||
constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "payload_reset_percentage";
|
||||
constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "payload_reset_preset_mode";
|
||||
constexpr const char *const MQTT_PAYLOAD_STOP = "payload_stop";
|
||||
constexpr const char *const MQTT_PAYLOAD_START = "payload_start";
|
||||
constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "payload_start_pause";
|
||||
constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "payload_return_to_base";
|
||||
constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "payload_turn_off";
|
||||
constexpr const char *const MQTT_PAYLOAD_TURN_ON = "payload_turn_on";
|
||||
constexpr const char *const MQTT_PAYLOAD_UNLOCK = "payload_unlock";
|
||||
constexpr const char *const MQTT_POSITION_CLOSED = "position_closed";
|
||||
constexpr const char *const MQTT_POSITION_OPEN = "position_open";
|
||||
constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "power_command_topic";
|
||||
constexpr const char *const MQTT_POWER_STATE_TOPIC = "power_state_topic";
|
||||
constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "power_state_template";
|
||||
constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic";
|
||||
constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "preset_mode_command_template";
|
||||
constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic";
|
||||
constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template";
|
||||
constexpr const char *const MQTT_PRESET_MODES = "preset_modes";
|
||||
constexpr const char *const MQTT_RED_TEMPLATE = "red_template";
|
||||
constexpr const char *const MQTT_RETAIN = "retain";
|
||||
constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_command_template";
|
||||
constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_command_topic";
|
||||
constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_state_topic";
|
||||
constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_value_template";
|
||||
constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_command_template";
|
||||
constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_command_topic";
|
||||
constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_state_topic";
|
||||
constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_value_template";
|
||||
constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_command_template";
|
||||
constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_command_topic";
|
||||
constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_state_topic";
|
||||
constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_value_template";
|
||||
constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_command_topic";
|
||||
constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off";
|
||||
constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_speed_topic";
|
||||
constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_position_template";
|
||||
constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_position_topic";
|
||||
constexpr const char *const MQTT_POSITION_TOPIC = "position_topic";
|
||||
constexpr const char *const MQTT_POSITION_TEMPLATE = "position_template";
|
||||
constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "speed_command_topic";
|
||||
constexpr const char *const MQTT_SPEED_STATE_TOPIC = "speed_state_topic";
|
||||
constexpr const char *const MQTT_SPEED_RANGE_MIN = "speed_range_min";
|
||||
constexpr const char *const MQTT_SPEED_RANGE_MAX = "speed_range_max";
|
||||
constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "speed_value_template";
|
||||
constexpr const char *const MQTT_SPEEDS = "speeds";
|
||||
constexpr const char *const MQTT_SOURCE_TYPE = "source_type";
|
||||
constexpr const char *const MQTT_STATE_CLASS = "state_class";
|
||||
constexpr const char *const MQTT_STATE_CLOSED = "state_closed";
|
||||
constexpr const char *const MQTT_STATE_CLOSING = "state_closing";
|
||||
constexpr const char *const MQTT_STATE_OFF = "state_off";
|
||||
constexpr const char *const MQTT_STATE_ON = "state_on";
|
||||
constexpr const char *const MQTT_STATE_OPEN = "state_open";
|
||||
constexpr const char *const MQTT_STATE_OPENING = "state_opening";
|
||||
constexpr const char *const MQTT_STATE_STOPPED = "state_stopped";
|
||||
constexpr const char *const MQTT_STATE_LOCKED = "state_locked";
|
||||
constexpr const char *const MQTT_STATE_UNLOCKED = "state_unlocked";
|
||||
constexpr const char *const MQTT_STATE_TOPIC = "state_topic";
|
||||
constexpr const char *const MQTT_STATE_TEMPLATE = "state_template";
|
||||
constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "state_value_template";
|
||||
constexpr const char *const MQTT_STEP = "step";
|
||||
constexpr const char *const MQTT_SUBTYPE = "subtype";
|
||||
constexpr const char *const MQTT_SUPPORTED_FEATURES = "supported_features";
|
||||
constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "supported_color_modes";
|
||||
constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_command_template";
|
||||
constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_command_topic";
|
||||
constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_state_template";
|
||||
constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_state_topic";
|
||||
constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temperature_command_template";
|
||||
constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temperature_command_topic";
|
||||
constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temperature_high_command_template";
|
||||
constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temperature_high_command_topic";
|
||||
constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temperature_high_state_template";
|
||||
constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temperature_high_state_topic";
|
||||
constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temperature_low_command_template";
|
||||
constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temperature_low_command_topic";
|
||||
constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temperature_low_state_template";
|
||||
constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temperature_low_state_topic";
|
||||
constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temperature_state_template";
|
||||
constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temperature_state_topic";
|
||||
constexpr const char *const MQTT_TEMPERATURE_UNIT = "temperature_unit";
|
||||
constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_closed_value";
|
||||
constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_command_topic";
|
||||
constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_command_template";
|
||||
constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_invert_state";
|
||||
constexpr const char *const MQTT_TILT_MAX = "tilt_max";
|
||||
constexpr const char *const MQTT_TILT_MIN = "tilt_min";
|
||||
constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opened_value";
|
||||
constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_optimistic";
|
||||
constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_topic";
|
||||
constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_template";
|
||||
constexpr const char *const MQTT_TOPIC = "topic";
|
||||
constexpr const char *const MQTT_UNIQUE_ID = "unique_id";
|
||||
constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_measurement";
|
||||
constexpr const char *const MQTT_VALUE_TEMPLATE = "value_template";
|
||||
constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "white_command_topic";
|
||||
constexpr const char *const MQTT_WHITE_SCALE = "white_scale";
|
||||
constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "white_value_command_topic";
|
||||
constexpr const char *const MQTT_WHITE_VALUE_SCALE = "white_value_scale";
|
||||
constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "white_value_state_topic";
|
||||
constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "white_value_template";
|
||||
constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_command_topic";
|
||||
constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_state_topic";
|
||||
constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_value_template";
|
||||
|
||||
constexpr const char *const MQTT_DEVICE_CONNECTIONS = "connections";
|
||||
constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "identifiers";
|
||||
constexpr const char *const MQTT_DEVICE_NAME = "name";
|
||||
constexpr const char *const MQTT_DEVICE_MANUFACTURER = "manufacturer";
|
||||
constexpr const char *const MQTT_DEVICE_MODEL = "model";
|
||||
constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version";
|
||||
constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area";
|
||||
#endif
|
||||
|
||||
// Additional MQTT fields where no abbreviation is defined in HA source
|
||||
constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category";
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_MQTT
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "mqtt_cover.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_COVER
|
||||
|
||||
@@ -22,7 +24,7 @@ void MQTTCoverComponent::setup() {
|
||||
});
|
||||
if (traits.get_supports_position()) {
|
||||
this->subscribe(this->get_position_command_topic(), [this](const std::string &topic, const std::string &payload) {
|
||||
auto value = parse_float(payload);
|
||||
auto value = parse_number<float>(payload);
|
||||
if (!value.has_value()) {
|
||||
ESP_LOGW(TAG, "Invalid position value: '%s'", payload.c_str());
|
||||
return;
|
||||
@@ -34,7 +36,7 @@ void MQTTCoverComponent::setup() {
|
||||
}
|
||||
if (traits.get_supports_tilt()) {
|
||||
this->subscribe(this->get_tilt_command_topic(), [this](const std::string &topic, const std::string &payload) {
|
||||
auto value = parse_float(payload);
|
||||
auto value = parse_number<float>(payload);
|
||||
if (!value.has_value()) {
|
||||
ESP_LOGW(TAG, "Invalid tilt value: '%s'", payload.c_str());
|
||||
return;
|
||||
@@ -63,20 +65,20 @@ void MQTTCoverComponent::dump_config() {
|
||||
}
|
||||
void MQTTCoverComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
|
||||
if (!this->cover_->get_device_class().empty())
|
||||
root["device_class"] = this->cover_->get_device_class();
|
||||
root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class();
|
||||
|
||||
auto traits = this->cover_->get_traits();
|
||||
if (traits.get_is_assumed_state()) {
|
||||
root["optimistic"] = true;
|
||||
root[MQTT_OPTIMISTIC] = true;
|
||||
}
|
||||
if (traits.get_supports_position()) {
|
||||
config.state_topic = false;
|
||||
root["position_topic"] = this->get_position_state_topic();
|
||||
root["set_position_topic"] = this->get_position_command_topic();
|
||||
root[MQTT_POSITION_TOPIC] = this->get_position_state_topic();
|
||||
root[MQTT_SET_POSITION_TOPIC] = this->get_position_command_topic();
|
||||
}
|
||||
if (traits.get_supports_tilt()) {
|
||||
root["tilt_status_topic"] = this->get_tilt_state_topic();
|
||||
root["tilt_command_topic"] = this->get_tilt_command_topic();
|
||||
root[MQTT_TILT_STATUS_TOPIC] = this->get_tilt_state_topic();
|
||||
root[MQTT_TILT_COMMAND_TOPIC] = this->get_tilt_command_topic();
|
||||
}
|
||||
if (traits.get_supports_tilt() && !traits.get_supports_position()) {
|
||||
config.command_topic = false;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "mqtt_fan.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_FAN
|
||||
#include "esphome/components/fan/fan_helpers.h"
|
||||
@@ -69,7 +71,7 @@ void MQTTFanComponent::setup() {
|
||||
if (this->state_->get_traits().supports_speed()) {
|
||||
this->subscribe(this->get_speed_level_command_topic(),
|
||||
[this](const std::string &topic, const std::string &payload) {
|
||||
optional<int> speed_level_opt = parse_int(payload);
|
||||
optional<int> speed_level_opt = parse_number<int>(payload);
|
||||
if (speed_level_opt.has_value()) {
|
||||
const int speed_level = speed_level_opt.value();
|
||||
if (speed_level >= 0 && speed_level <= this->state_->get_traits().supported_speed_count()) {
|
||||
@@ -88,9 +90,12 @@ void MQTTFanComponent::setup() {
|
||||
|
||||
if (this->state_->get_traits().supports_speed()) {
|
||||
this->subscribe(this->get_speed_command_topic(), [this](const std::string &topic, const std::string &payload) {
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
this->state_->make_call()
|
||||
.set_speed(payload.c_str()) // NOLINT(clang-diagnostic-deprecated-declarations)
|
||||
.perform();
|
||||
#pragma GCC diagnostic pop
|
||||
});
|
||||
}
|
||||
|
||||
@@ -117,14 +122,14 @@ bool MQTTFanComponent::send_initial_state() { return this->publish_state(); }
|
||||
|
||||
void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
|
||||
if (this->state_->get_traits().supports_oscillation()) {
|
||||
root["oscillation_command_topic"] = this->get_oscillation_command_topic();
|
||||
root["oscillation_state_topic"] = this->get_oscillation_state_topic();
|
||||
root[MQTT_OSCILLATION_COMMAND_TOPIC] = this->get_oscillation_command_topic();
|
||||
root[MQTT_OSCILLATION_STATE_TOPIC] = this->get_oscillation_state_topic();
|
||||
}
|
||||
if (this->state_->get_traits().supports_speed()) {
|
||||
root["speed_level_command_topic"] = this->get_speed_level_command_topic();
|
||||
root["speed_level_state_topic"] = this->get_speed_level_state_topic();
|
||||
root["speed_command_topic"] = this->get_speed_command_topic();
|
||||
root["speed_state_topic"] = this->get_speed_state_topic();
|
||||
root[MQTT_SPEED_COMMAND_TOPIC] = this->get_speed_command_topic();
|
||||
root[MQTT_SPEED_STATE_TOPIC] = this->get_speed_state_topic();
|
||||
}
|
||||
}
|
||||
bool MQTTFanComponent::publish_state() {
|
||||
@@ -145,6 +150,8 @@ bool MQTTFanComponent::publish_state() {
|
||||
}
|
||||
if (traits.supports_speed()) {
|
||||
const char *payload;
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations)
|
||||
switch (fan::speed_level_to_enum(this->state_->speed, traits.supported_speed_count())) {
|
||||
case FAN_SPEED_LOW: { // NOLINT(clang-diagnostic-deprecated-declarations)
|
||||
@@ -161,6 +168,7 @@ bool MQTTFanComponent::publish_state() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
bool success = this->publish(this->get_speed_state_topic(), payload);
|
||||
failed = failed || !success;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "mqtt_light.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_LIGHT
|
||||
|
||||
@@ -38,7 +40,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover
|
||||
root["schema"] = "json";
|
||||
auto traits = this->state_->get_traits();
|
||||
|
||||
root["color_mode"] = true;
|
||||
root[MQTT_COLOR_MODE] = true;
|
||||
JsonArray &color_modes = root.createNestedArray("supported_color_modes");
|
||||
if (traits.supports_color_mode(ColorMode::ON_OFF))
|
||||
color_modes.add("onoff");
|
||||
@@ -64,7 +66,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover
|
||||
|
||||
if (this->state_->supports_effects()) {
|
||||
root["effect"] = true;
|
||||
JsonArray &effect_list = root.createNestedArray("effect_list");
|
||||
JsonArray &effect_list = root.createNestedArray(MQTT_EFFECT_LIST);
|
||||
for (auto *effect : this->state_->get_effects())
|
||||
effect_list.add(effect->get_name());
|
||||
effect_list.add("None");
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "mqtt_number.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_NUMBER
|
||||
|
||||
@@ -15,7 +17,7 @@ MQTTNumberComponent::MQTTNumberComponent(Number *number) : MQTTComponent(), numb
|
||||
|
||||
void MQTTNumberComponent::setup() {
|
||||
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) {
|
||||
auto val = parse_float(state);
|
||||
auto val = parse_number<float>(state);
|
||||
if (!val.has_value()) {
|
||||
ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str());
|
||||
return;
|
||||
@@ -38,9 +40,9 @@ const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_
|
||||
void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
|
||||
const auto &traits = number_->traits;
|
||||
// https://www.home-assistant.io/integrations/number.mqtt/
|
||||
root["min"] = traits.get_min_value();
|
||||
root["max"] = traits.get_max_value();
|
||||
root["step"] = traits.get_step();
|
||||
root[MQTT_MIN] = traits.get_min_value();
|
||||
root[MQTT_MAX] = traits.get_max_value();
|
||||
root[MQTT_STEP] = traits.get_step();
|
||||
|
||||
config.command_topic = true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "mqtt_select.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_SELECT
|
||||
|
||||
@@ -33,7 +35,7 @@ const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_
|
||||
void MQTTSelectComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
|
||||
const auto &traits = select_->traits;
|
||||
// https://www.home-assistant.io/integrations/select.mqtt/
|
||||
JsonArray &options = root.createNestedArray("options");
|
||||
JsonArray &options = root.createNestedArray(MQTT_OPTIONS);
|
||||
for (const auto &option : traits.get_options())
|
||||
options.add(option);
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "mqtt_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_SENSOR
|
||||
|
||||
@@ -42,19 +44,19 @@ void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; }
|
||||
|
||||
void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
|
||||
if (!this->sensor_->get_device_class().empty())
|
||||
root["device_class"] = this->sensor_->get_device_class();
|
||||
root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class();
|
||||
|
||||
if (!this->sensor_->get_unit_of_measurement().empty())
|
||||
root["unit_of_measurement"] = this->sensor_->get_unit_of_measurement();
|
||||
root[MQTT_UNIT_OF_MEASUREMENT] = this->sensor_->get_unit_of_measurement();
|
||||
|
||||
if (this->get_expire_after() > 0)
|
||||
root["expire_after"] = this->get_expire_after() / 1000;
|
||||
root[MQTT_EXPIRE_AFTER] = this->get_expire_after() / 1000;
|
||||
|
||||
if (this->sensor_->get_force_update())
|
||||
root["force_update"] = true;
|
||||
root[MQTT_FORCE_UPDATE] = true;
|
||||
|
||||
if (this->sensor_->get_state_class() != STATE_CLASS_NONE)
|
||||
root["state_class"] = state_class_to_string(this->sensor_->get_state_class());
|
||||
root[MQTT_STATE_CLASS] = state_class_to_string(this->sensor_->get_state_class());
|
||||
|
||||
config.command_topic = false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "mqtt_switch.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_SWITCH
|
||||
|
||||
@@ -44,7 +46,7 @@ std::string MQTTSwitchComponent::component_type() const { return "switch"; }
|
||||
const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; }
|
||||
void MQTTSwitchComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
|
||||
if (this->switch_->assumed_state())
|
||||
root["optimistic"] = true;
|
||||
root[MQTT_OPTIMISTIC] = true;
|
||||
}
|
||||
bool MQTTSwitchComponent::send_initial_state() { return this->publish_state(this->switch_->state); }
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ void MQTTSubscribeSensor::setup() {
|
||||
mqtt::global_mqtt_client->subscribe(
|
||||
this->topic_,
|
||||
[this](const std::string &topic, const std::string &payload) {
|
||||
auto val = parse_float(payload);
|
||||
auto val = parse_number<float>(payload);
|
||||
if (!val.has_value()) {
|
||||
ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
|
||||
this->publish_state(NAN);
|
||||
|
||||
@@ -75,30 +75,48 @@ void MS5611Component::read_pressure_(uint32_t raw_temperature) {
|
||||
const uint32_t raw_pressure = (uint32_t(bytes[0]) << 16) | (uint32_t(bytes[1]) << 8) | (uint32_t(bytes[2]));
|
||||
this->calculate_values_(raw_temperature, raw_pressure);
|
||||
}
|
||||
|
||||
// Calculations are taken from the datasheet which can be found here:
|
||||
// https://www.te.com/commerce/DocumentDelivery/DDEController?Action=showdoc&DocId=Data+Sheet%7FMS5611-01BA03%7FB3%7Fpdf%7FEnglish%7FENG_DS_MS5611-01BA03_B3.pdf%7FCAT-BLPS0036
|
||||
// Sections PRESSURE AND TEMPERATURE CALCULATION and SECOND ORDER TEMPERATURE COMPENSATION
|
||||
// Variable names below match variable names from the datasheet but lowercased
|
||||
void MS5611Component::calculate_values_(uint32_t raw_temperature, uint32_t raw_pressure) {
|
||||
const int32_t d_t = int32_t(raw_temperature) - (uint32_t(this->prom_[4]) << 8);
|
||||
float temperature = (2000 + (int64_t(d_t) * this->prom_[5]) / 8388608.0f) / 100.0f;
|
||||
const uint32_t c1 = uint32_t(this->prom_[0]);
|
||||
const uint32_t c2 = uint32_t(this->prom_[1]);
|
||||
const uint16_t c3 = uint16_t(this->prom_[2]);
|
||||
const uint16_t c4 = uint16_t(this->prom_[3]);
|
||||
const int32_t c5 = int32_t(this->prom_[4]);
|
||||
const uint16_t c6 = uint16_t(this->prom_[5]);
|
||||
const uint32_t d1 = raw_pressure;
|
||||
const int32_t d2 = raw_temperature;
|
||||
|
||||
float pressure_offset = (uint32_t(this->prom_[1]) << 16) + ((this->prom_[3] * d_t) >> 7);
|
||||
float pressure_sensitivity = (uint32_t(this->prom_[0]) << 15) + ((this->prom_[2] * d_t) >> 8);
|
||||
// Promote dt to 64 bit here to make the math below cleaner
|
||||
const int64_t dt = d2 - (c5 << 8);
|
||||
int32_t temp = (2000 + ((dt * c6) >> 23));
|
||||
|
||||
if (temperature < 20.0f) {
|
||||
const float t2 = (d_t * d_t) / 2147483648.0f;
|
||||
const float temp20 = (temperature - 20.0f) * 100.0f;
|
||||
float pressure_offset_2 = 2.5f * temp20 * temp20;
|
||||
float pressure_sensitivity_2 = 1.25f * temp20 * temp20;
|
||||
if (temp20 < -15.0f) {
|
||||
const float temp15 = (temperature + 15.0f) * 100.0f;
|
||||
pressure_offset_2 += 7.0f * temp15;
|
||||
pressure_sensitivity_2 += 5.5f * temp15;
|
||||
int64_t off = (c2 << 16) + ((dt * c4) >> 7);
|
||||
int64_t sens = (c1 << 15) + ((dt * c3) >> 8);
|
||||
|
||||
if (temp < 2000) {
|
||||
const int32_t t2 = (dt * dt) >> 31;
|
||||
int32_t off2 = ((5 * (temp - 2000) * (temp - 2000)) >> 1);
|
||||
int32_t sens2 = ((5 * (temp - 2000) * (temp - 2000)) >> 2);
|
||||
if (temp < -1500) {
|
||||
off2 = (off2 + 7 * (temp + 1500) * (temp + 1500));
|
||||
sens2 = sens2 + ((11 * (temp + 1500) * (temp + 1500)) >> 1);
|
||||
}
|
||||
temperature -= t2;
|
||||
pressure_offset -= pressure_offset_2;
|
||||
pressure_sensitivity -= pressure_sensitivity_2;
|
||||
temp = temp - t2;
|
||||
off = off - off2;
|
||||
sens = sens - sens2;
|
||||
}
|
||||
|
||||
const float pressure = ((raw_pressure * pressure_sensitivity) / 2097152.0f - pressure_offset) / 3276800.0f;
|
||||
// Here we multiply unsigned 32-bit by signed 64-bit using signed 64-bit math.
|
||||
// Possible ranges of D1 and SENS from the datasheet guarantee
|
||||
// that this multiplication does not overflow
|
||||
const int32_t p = ((((d1 * sens) >> 21) - off) >> 15);
|
||||
|
||||
const float temperature = temp / 100.0f;
|
||||
const float pressure = p / 100.0f;
|
||||
ESP_LOGD(TAG, "Got temperature=%0.02f°C pressure=%0.01fhPa", temperature, pressure);
|
||||
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
|
||||
418
esphome/components/neopixelbus/_methods.py
Normal file
418
esphome/components/neopixelbus/_methods.py
Normal file
@@ -0,0 +1,418 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, List
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_CHANNEL,
|
||||
CONF_CLOCK_PIN,
|
||||
CONF_DATA_PIN,
|
||||
CONF_METHOD,
|
||||
CONF_PIN,
|
||||
CONF_SPEED,
|
||||
)
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32C3,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from .const import (
|
||||
CONF_ASYNC,
|
||||
CONF_BUS,
|
||||
CHIP_400KBPS,
|
||||
CHIP_800KBPS,
|
||||
CHIP_APA106,
|
||||
CHIP_DOTSTAR,
|
||||
CHIP_LC8812,
|
||||
CHIP_LPD6803,
|
||||
CHIP_LPD8806,
|
||||
CHIP_P9813,
|
||||
CHIP_SK6812,
|
||||
CHIP_TM1814,
|
||||
CHIP_TM1829,
|
||||
CHIP_TM1914,
|
||||
CHIP_WS2801,
|
||||
CHIP_WS2811,
|
||||
CHIP_WS2812,
|
||||
CHIP_WS2812X,
|
||||
CHIP_WS2813,
|
||||
ONE_WIRE_CHIPS,
|
||||
TWO_WIRE_CHIPS,
|
||||
)
|
||||
|
||||
METHOD_BIT_BANG = "bit_bang"
|
||||
METHOD_ESP8266_UART = "esp8266_uart"
|
||||
METHOD_ESP8266_DMA = "esp8266_dma"
|
||||
METHOD_ESP32_RMT = "esp32_rmt"
|
||||
METHOD_ESP32_I2S = "esp32_i2s"
|
||||
METHOD_SPI = "spi"
|
||||
|
||||
CHANNEL_DYNAMIC = "dynamic"
|
||||
BUS_DYNAMIC = "dynamic"
|
||||
SPI_BUS_VSPI = "vspi"
|
||||
SPI_BUS_HSPI = "hspi"
|
||||
SPI_SPEEDS = [40e6, 20e6, 10e6, 5e6, 2e6, 1e6, 500e3]
|
||||
|
||||
|
||||
def _esp32_rmt_default_channel():
|
||||
return {
|
||||
VARIANT_ESP32S2: 1,
|
||||
VARIANT_ESP32C3: 1,
|
||||
}.get(get_esp32_variant(), 6)
|
||||
|
||||
|
||||
def _validate_esp32_rmt_channel(value):
|
||||
if isinstance(value, str) and value.lower() == CHANNEL_DYNAMIC:
|
||||
value = CHANNEL_DYNAMIC
|
||||
else:
|
||||
value = cv.int_(value)
|
||||
variant_channels = {
|
||||
VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7, CHANNEL_DYNAMIC],
|
||||
VARIANT_ESP32S2: [0, 1, 2, 3, CHANNEL_DYNAMIC],
|
||||
VARIANT_ESP32C3: [0, 1, CHANNEL_DYNAMIC],
|
||||
}
|
||||
variant = get_esp32_variant()
|
||||
if variant not in variant_channels:
|
||||
raise cv.Invalid(f"{variant} does not support the rmt method")
|
||||
if value not in variant_channels[variant]:
|
||||
raise cv.Invalid(f"{variant} does not support rmt channel {value}")
|
||||
return value
|
||||
|
||||
|
||||
def _esp32_i2s_default_bus():
|
||||
return {
|
||||
VARIANT_ESP32: 1,
|
||||
VARIANT_ESP32S2: 0,
|
||||
}.get(get_esp32_variant(), 0)
|
||||
|
||||
|
||||
def _validate_esp32_i2s_bus(value):
|
||||
if isinstance(value, str) and value.lower() == CHANNEL_DYNAMIC:
|
||||
value = CHANNEL_DYNAMIC
|
||||
else:
|
||||
value = cv.int_(value)
|
||||
variant_buses = {
|
||||
VARIANT_ESP32: [0, 1, BUS_DYNAMIC],
|
||||
VARIANT_ESP32S2: [0, BUS_DYNAMIC],
|
||||
}
|
||||
variant = get_esp32_variant()
|
||||
if variant not in variant_buses:
|
||||
raise cv.Invalid(f"{variant} does not support the i2s method")
|
||||
if value not in variant_buses[variant]:
|
||||
raise cv.Invalid(f"{variant} does not support i2s bus {value}")
|
||||
return value
|
||||
|
||||
|
||||
neo_ns = cg.global_ns
|
||||
|
||||
|
||||
def _bit_bang_to_code(config, chip: str, inverted: bool):
|
||||
# https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEspBitBangMethod.h
|
||||
# Some chips are only aliases
|
||||
chip = {
|
||||
CHIP_WS2813: CHIP_WS2812X,
|
||||
CHIP_LC8812: CHIP_SK6812,
|
||||
CHIP_TM1914: CHIP_TM1814,
|
||||
CHIP_WS2812: CHIP_800KBPS,
|
||||
}.get(chip, chip)
|
||||
|
||||
lookup = {
|
||||
CHIP_WS2811: (neo_ns.NeoEspBitBangSpeedWs2811, False),
|
||||
CHIP_WS2812X: (neo_ns.NeoEspBitBangSpeedWs2812x, False),
|
||||
CHIP_SK6812: (neo_ns.NeoEspBitBangSpeedSk6812, False),
|
||||
CHIP_TM1814: (neo_ns.NeoEspBitBangSpeedTm1814, True),
|
||||
CHIP_TM1829: (neo_ns.NeoEspBitBangSpeedTm1829, True),
|
||||
CHIP_800KBPS: (neo_ns.NeoEspBitBangSpeed800Kbps, False),
|
||||
CHIP_400KBPS: (neo_ns.NeoEspBitBangSpeed400Kbps, False),
|
||||
CHIP_APA106: (neo_ns.NeoEspBitBangSpeedApa106, False),
|
||||
}
|
||||
# For tm variants opposite of inverted is needed
|
||||
speed, pinset_inverted = lookup[chip]
|
||||
pinset = {
|
||||
False: neo_ns.NeoEspPinset,
|
||||
True: neo_ns.NeoEspPinsetInverted,
|
||||
}[inverted != pinset_inverted]
|
||||
return neo_ns.NeoEspBitBangMethodBase.template(speed, pinset)
|
||||
|
||||
|
||||
def _bit_bang_extra_validate(config):
|
||||
pin = config[CONF_PIN]
|
||||
if CORE.is_esp8266 and not (0 <= pin <= 15):
|
||||
# Due to use of w1ts
|
||||
raise cv.Invalid("Bit bang only supports pins GPIO0-GPIO15 on ESP8266")
|
||||
if CORE.is_esp32 and not (0 <= pin <= 31):
|
||||
raise cv.Invalid("Bit bang only supports pins GPIO0-GPIO31 on ESP32")
|
||||
|
||||
|
||||
def _esp8266_uart_to_code(config, chip: str, inverted: bool):
|
||||
# https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp8266UartMethod.h
|
||||
uart_context, uart_base = {
|
||||
False: (neo_ns.NeoEsp8266UartContext, neo_ns.NeoEsp8266Uart),
|
||||
True: (neo_ns.NeoEsp8266UartInterruptContext, neo_ns.NeoEsp8266AsyncUart),
|
||||
}[config[CONF_ASYNC]]
|
||||
uart_feature = {
|
||||
0: neo_ns.UartFeature0,
|
||||
1: neo_ns.UartFeature1,
|
||||
}[config[CONF_BUS]]
|
||||
# Some chips are only aliases
|
||||
chip = {
|
||||
CHIP_WS2811: CHIP_WS2812X,
|
||||
CHIP_WS2813: CHIP_WS2812X,
|
||||
CHIP_LC8812: CHIP_SK6812,
|
||||
CHIP_TM1914: CHIP_TM1814,
|
||||
CHIP_WS2812: CHIP_800KBPS,
|
||||
}.get(chip, chip)
|
||||
|
||||
lookup = {
|
||||
CHIP_WS2812X: (neo_ns.NeoEsp8266UartSpeedWs2812x, False),
|
||||
CHIP_SK6812: (neo_ns.NeoEsp8266UartSpeedSk6812, False),
|
||||
CHIP_TM1814: (neo_ns.NeoEsp8266UartSpeedTm1814, True),
|
||||
CHIP_TM1829: (neo_ns.NeoEsp8266UartSpeedTm1829, True),
|
||||
CHIP_800KBPS: (neo_ns.NeoEsp8266UartSpeed800Kbps, False),
|
||||
CHIP_400KBPS: (neo_ns.NeoEsp8266UartSpeed400Kbps, False),
|
||||
CHIP_APA106: (neo_ns.NeoEsp8266UartSpeedApa106, False),
|
||||
}
|
||||
speed, uart_inverted = lookup[chip]
|
||||
# For tm variants opposite of inverted is needed
|
||||
inv = {
|
||||
False: neo_ns.NeoEsp8266UartNotInverted,
|
||||
True: neo_ns.NeoEsp8266UartInverted,
|
||||
}[inverted != uart_inverted]
|
||||
return neo_ns.NeoEsp8266UartMethodBase.template(
|
||||
speed, uart_base.template(uart_feature, uart_context), inv
|
||||
)
|
||||
|
||||
|
||||
def _esp8266_uart_extra_validate(config):
|
||||
pin = config[CONF_PIN]
|
||||
bus = config[CONF_METHOD][CONF_BUS]
|
||||
right_pin = {
|
||||
0: 1, # U0TXD
|
||||
1: 2, # U1TXD
|
||||
}[bus]
|
||||
if pin != right_pin:
|
||||
raise cv.Invalid(f"ESP8266 uart bus {bus} only supports pin GPIO{right_pin}")
|
||||
|
||||
|
||||
def _esp8266_dma_to_code(config, chip: str, inverted: bool):
|
||||
# https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp8266DmaMethod.h
|
||||
# Some chips are only aliases
|
||||
chip = {
|
||||
CHIP_WS2811: CHIP_WS2812X,
|
||||
CHIP_WS2813: CHIP_WS2812X,
|
||||
CHIP_LC8812: CHIP_SK6812,
|
||||
CHIP_TM1914: CHIP_TM1814,
|
||||
CHIP_WS2812: CHIP_800KBPS,
|
||||
}.get(chip, chip)
|
||||
|
||||
lookup = {
|
||||
(CHIP_WS2812X, False): neo_ns.NeoEsp8266DmaSpeedWs2812x,
|
||||
(CHIP_SK6812, False): neo_ns.NeoEsp8266DmaSpeedSk6812,
|
||||
(CHIP_TM1814, True): neo_ns.NeoEsp8266DmaInvertedSpeedTm1814,
|
||||
(CHIP_TM1829, True): neo_ns.NeoEsp8266DmaInvertedSpeedTm1829,
|
||||
(CHIP_800KBPS, False): neo_ns.NeoEsp8266DmaSpeed800Kbps,
|
||||
(CHIP_400KBPS, False): neo_ns.NeoEsp8266DmaSpeed400Kbps,
|
||||
(CHIP_APA106, False): neo_ns.NeoEsp8266DmaSpeedApa106,
|
||||
(CHIP_WS2812X, True): neo_ns.NeoEsp8266DmaInvertedSpeedWs2812x,
|
||||
(CHIP_SK6812, True): neo_ns.NeoEsp8266DmaInvertedSpeedSk6812,
|
||||
(CHIP_TM1814, False): neo_ns.NeoEsp8266DmaSpeedTm1814,
|
||||
(CHIP_TM1829, False): neo_ns.NeoEsp8266DmaSpeedTm1829,
|
||||
(CHIP_800KBPS, True): neo_ns.NeoEsp8266DmaInvertedSpeed800Kbps,
|
||||
(CHIP_400KBPS, True): neo_ns.NeoEsp8266DmaInvertedSpeed400Kbps,
|
||||
(CHIP_APA106, True): neo_ns.NeoEsp8266DmaInvertedSpeedApa106,
|
||||
}
|
||||
speed = lookup[(chip, inverted)]
|
||||
return neo_ns.NeoEsp8266DmaMethodBase.template(speed)
|
||||
|
||||
|
||||
def _esp8266_dma_extra_validate(config):
|
||||
if config[CONF_PIN] != 3:
|
||||
raise cv.Invalid("ESP8266 dma method only supports pin GPIO3")
|
||||
|
||||
|
||||
def _esp32_rmt_to_code(config, chip: str, inverted: bool):
|
||||
# https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp32RmtMethod.h
|
||||
channel = {
|
||||
0: neo_ns.NeoEsp32RmtChannel0,
|
||||
1: neo_ns.NeoEsp32RmtChannel1,
|
||||
2: neo_ns.NeoEsp32RmtChannel2,
|
||||
3: neo_ns.NeoEsp32RmtChannel3,
|
||||
4: neo_ns.NeoEsp32RmtChannel4,
|
||||
5: neo_ns.NeoEsp32RmtChannel5,
|
||||
6: neo_ns.NeoEsp32RmtChannel6,
|
||||
7: neo_ns.NeoEsp32RmtChannel7,
|
||||
CHANNEL_DYNAMIC: neo_ns.NeoEsp32RmtChannelN,
|
||||
}[config[CONF_CHANNEL]]
|
||||
# Some chips are only aliases
|
||||
chip = {
|
||||
CHIP_WS2813: CHIP_WS2812X,
|
||||
CHIP_LC8812: CHIP_SK6812,
|
||||
CHIP_WS2812: CHIP_800KBPS,
|
||||
}.get(chip, chip)
|
||||
|
||||
lookup = {
|
||||
(CHIP_WS2811, False): neo_ns.NeoEsp32RmtSpeedWs2811,
|
||||
(CHIP_WS2812X, False): neo_ns.NeoEsp32RmtSpeedWs2812x,
|
||||
(CHIP_SK6812, False): neo_ns.NeoEsp32RmtSpeedSk6812,
|
||||
(CHIP_TM1814, False): neo_ns.NeoEsp32RmtSpeedTm1814,
|
||||
(CHIP_TM1829, False): neo_ns.NeoEsp32RmtSpeedTm1829,
|
||||
(CHIP_TM1914, False): neo_ns.NeoEsp32RmtSpeedTm1914,
|
||||
(CHIP_800KBPS, False): neo_ns.NeoEsp32RmtSpeed800Kbps,
|
||||
(CHIP_400KBPS, False): neo_ns.NeoEsp32RmtSpeed400Kbps,
|
||||
(CHIP_APA106, False): neo_ns.NeoEsp32RmtSpeedApa106,
|
||||
(CHIP_WS2811, True): neo_ns.NeoEsp32RmtInvertedSpeedWs2811,
|
||||
(CHIP_WS2812X, True): neo_ns.NeoEsp32RmtInvertedSpeedWs2812x,
|
||||
(CHIP_SK6812, True): neo_ns.NeoEsp32RmtInvertedSpeedSk6812,
|
||||
(CHIP_TM1814, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1814,
|
||||
(CHIP_TM1829, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1829,
|
||||
(CHIP_TM1914, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1914,
|
||||
(CHIP_800KBPS, True): neo_ns.NeoEsp32RmtInvertedSpeed800Kbps,
|
||||
(CHIP_400KBPS, True): neo_ns.NeoEsp32RmtInvertedSpeed400Kbps,
|
||||
(CHIP_APA106, True): neo_ns.NeoEsp32RmtInvertedSpeedApa106,
|
||||
}
|
||||
speed = lookup[(chip, inverted)]
|
||||
return neo_ns.NeoEsp32RmtMethodBase.template(speed, channel)
|
||||
|
||||
|
||||
def _esp32_i2s_to_code(config, chip: str, inverted: bool):
|
||||
# https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp32I2sMethod.h
|
||||
bus = {
|
||||
0: neo_ns.NeoEsp32I2sBusZero,
|
||||
1: neo_ns.NeoEsp32I2sBusOne,
|
||||
BUS_DYNAMIC: neo_ns.NeoEsp32I2sBusN,
|
||||
}[config[CONF_BUS]]
|
||||
# Some chips are only aliases
|
||||
chip = {
|
||||
CHIP_WS2811: CHIP_WS2812X,
|
||||
CHIP_WS2813: CHIP_WS2812X,
|
||||
CHIP_LC8812: CHIP_SK6812,
|
||||
CHIP_WS2812: CHIP_800KBPS,
|
||||
}.get(chip, chip)
|
||||
|
||||
lookup = {
|
||||
CHIP_WS2812X: (neo_ns.NeoEsp32I2sSpeedWs2812x, False),
|
||||
CHIP_SK6812: (neo_ns.NeoEsp32I2sSpeedSk6812, False),
|
||||
CHIP_TM1814: (neo_ns.NeoEsp32I2sSpeedTm1814, True),
|
||||
CHIP_TM1914: (neo_ns.NeoEsp32I2sSpeedTm1914, True),
|
||||
CHIP_TM1829: (neo_ns.NeoEsp32I2sSpeedTm1829, True),
|
||||
CHIP_800KBPS: (neo_ns.NeoEsp32I2sSpeed800Kbps, False),
|
||||
CHIP_400KBPS: (neo_ns.NeoEsp32I2sSpeed400Kbps, False),
|
||||
CHIP_APA106: (neo_ns.NeoEsp32I2sSpeedApa106, False),
|
||||
}
|
||||
speed, inv_inverted = lookup[chip]
|
||||
# For tm variants opposite of inverted is needed
|
||||
inv = {
|
||||
False: neo_ns.NeoEsp32I2sNotInverted,
|
||||
True: neo_ns.NeoEsp32I2sInverted,
|
||||
}[inverted != inv_inverted]
|
||||
return neo_ns.NeoEsp32I2sMethodBase.template(speed, bus, inv)
|
||||
|
||||
|
||||
def _spi_to_code(config, chip: str, inverted: bool):
|
||||
# https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/TwoWireSpiImple.h
|
||||
spi_imple = {
|
||||
None: neo_ns.TwoWireSpiImple,
|
||||
SPI_BUS_VSPI: neo_ns.TwoWireSpiImple,
|
||||
SPI_BUS_HSPI: neo_ns.TwoWireHspiImple,
|
||||
}[config.get(CONF_BUS)]
|
||||
spi_speed = {
|
||||
40e6: neo_ns.SpiSpeed40Mhz,
|
||||
20e6: neo_ns.SpiSpeed20Mhz,
|
||||
10e6: neo_ns.SpiSpeed10Mhz,
|
||||
5e6: neo_ns.SpiSpeed5Mhz,
|
||||
2e6: neo_ns.SpiSpeed2Mhz,
|
||||
1e6: neo_ns.SpiSpeed1Mhz,
|
||||
500e3: neo_ns.SpiSpeed500Khz,
|
||||
}[config[CONF_SPEED]]
|
||||
chip_method_base = {
|
||||
CHIP_DOTSTAR: neo_ns.DotStarMethodBase,
|
||||
CHIP_LPD6803: neo_ns.Lpd6803MethodBase,
|
||||
CHIP_LPD8806: neo_ns.Lpd8806MethodBase,
|
||||
CHIP_WS2801: neo_ns.Ws2801MethodBase,
|
||||
CHIP_P9813: neo_ns.P9813MethodBase,
|
||||
}[chip]
|
||||
return chip_method_base.template(spi_imple.template(spi_speed))
|
||||
|
||||
|
||||
def _spi_extra_validate(config):
|
||||
if CORE.is_esp32:
|
||||
return
|
||||
|
||||
if config[CONF_DATA_PIN] != 13 and config[CONF_CLOCK_PIN] != 14:
|
||||
raise cv.Invalid(
|
||||
"SPI only supports pins GPIO13 for data and GPIO14 for clock on ESP8266"
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MethodDescriptor:
|
||||
method_schema: Any
|
||||
to_code: Any
|
||||
supported_chips: List[str]
|
||||
extra_validate: Any = None
|
||||
|
||||
|
||||
METHODS = {
|
||||
METHOD_BIT_BANG: MethodDescriptor(
|
||||
method_schema={},
|
||||
to_code=_bit_bang_to_code,
|
||||
extra_validate=_bit_bang_extra_validate,
|
||||
supported_chips=ONE_WIRE_CHIPS,
|
||||
),
|
||||
METHOD_ESP8266_UART: MethodDescriptor(
|
||||
method_schema=cv.All(
|
||||
cv.only_on_esp8266,
|
||||
{
|
||||
cv.Optional(CONF_ASYNC, default=False): cv.boolean,
|
||||
cv.Optional(CONF_BUS, default=1): cv.int_range(min=0, max=1),
|
||||
},
|
||||
),
|
||||
extra_validate=_esp8266_uart_extra_validate,
|
||||
to_code=_esp8266_uart_to_code,
|
||||
supported_chips=ONE_WIRE_CHIPS,
|
||||
),
|
||||
METHOD_ESP8266_DMA: MethodDescriptor(
|
||||
method_schema=cv.All(cv.only_on_esp8266, {}),
|
||||
extra_validate=_esp8266_dma_extra_validate,
|
||||
to_code=_esp8266_dma_to_code,
|
||||
supported_chips=ONE_WIRE_CHIPS,
|
||||
),
|
||||
METHOD_ESP32_RMT: MethodDescriptor(
|
||||
method_schema=cv.All(
|
||||
cv.only_on_esp32,
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_CHANNEL, default=_esp32_rmt_default_channel
|
||||
): _validate_esp32_rmt_channel,
|
||||
},
|
||||
),
|
||||
to_code=_esp32_rmt_to_code,
|
||||
supported_chips=ONE_WIRE_CHIPS,
|
||||
),
|
||||
METHOD_ESP32_I2S: MethodDescriptor(
|
||||
method_schema=cv.All(
|
||||
cv.only_on_esp32,
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_BUS, default=_esp32_i2s_default_bus
|
||||
): _validate_esp32_i2s_bus,
|
||||
},
|
||||
),
|
||||
to_code=_esp32_i2s_to_code,
|
||||
supported_chips=ONE_WIRE_CHIPS,
|
||||
),
|
||||
METHOD_SPI: MethodDescriptor(
|
||||
method_schema={
|
||||
cv.Optional(CONF_BUS): cv.All(
|
||||
cv.only_on_esp32, cv.one_of(SPI_BUS_VSPI, SPI_BUS_HSPI, lower=True)
|
||||
),
|
||||
cv.Optional(CONF_SPEED, default="10MHz"): cv.All(
|
||||
cv.frequency, cv.one_of(*SPI_SPEEDS)
|
||||
),
|
||||
},
|
||||
to_code=_spi_to_code,
|
||||
extra_validate=_spi_extra_validate,
|
||||
supported_chips=TWO_WIRE_CHIPS,
|
||||
),
|
||||
}
|
||||
42
esphome/components/neopixelbus/const.py
Normal file
42
esphome/components/neopixelbus/const.py
Normal file
@@ -0,0 +1,42 @@
|
||||
CHIP_DOTSTAR = "dotstar"
|
||||
CHIP_WS2801 = "ws2801"
|
||||
CHIP_WS2811 = "ws2811"
|
||||
CHIP_WS2812 = "ws2812"
|
||||
CHIP_WS2812X = "ws2812x"
|
||||
CHIP_WS2813 = "ws2813"
|
||||
CHIP_SK6812 = "sk6812"
|
||||
CHIP_TM1814 = "tm1814"
|
||||
CHIP_TM1829 = "tm1829"
|
||||
CHIP_TM1914 = "tm1914"
|
||||
CHIP_800KBPS = "800kbps"
|
||||
CHIP_400KBPS = "400kbps"
|
||||
CHIP_APA106 = "apa106"
|
||||
CHIP_LC8812 = "lc8812"
|
||||
CHIP_LPD8806 = "lpd8806"
|
||||
CHIP_LPD6803 = "lpd6803"
|
||||
CHIP_P9813 = "p9813"
|
||||
|
||||
ONE_WIRE_CHIPS = [
|
||||
CHIP_WS2811,
|
||||
CHIP_WS2812,
|
||||
CHIP_WS2812X,
|
||||
CHIP_WS2813,
|
||||
CHIP_SK6812,
|
||||
CHIP_TM1814,
|
||||
CHIP_TM1829,
|
||||
CHIP_TM1914,
|
||||
CHIP_800KBPS,
|
||||
CHIP_400KBPS,
|
||||
CHIP_APA106,
|
||||
CHIP_LC8812,
|
||||
]
|
||||
TWO_WIRE_CHIPS = [
|
||||
CHIP_DOTSTAR,
|
||||
CHIP_WS2801,
|
||||
CHIP_LPD6803,
|
||||
CHIP_LPD8806,
|
||||
CHIP_P9813,
|
||||
]
|
||||
CHIP_TYPES = [*ONE_WIRE_CHIPS, *TWO_WIRE_CHIPS]
|
||||
CONF_ASYNC = "async"
|
||||
CONF_BUS = "bus"
|
||||
@@ -3,6 +3,7 @@ import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import light
|
||||
from esphome.const import (
|
||||
CONF_CHANNEL,
|
||||
CONF_CLOCK_PIN,
|
||||
CONF_DATA_PIN,
|
||||
CONF_METHOD,
|
||||
@@ -13,7 +14,26 @@ from esphome.const import (
|
||||
CONF_OUTPUT_ID,
|
||||
CONF_INVERT,
|
||||
)
|
||||
from esphome.components.esp32 import get_esp32_variant
|
||||
from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32C3,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from ._methods import (
|
||||
METHODS,
|
||||
METHOD_SPI,
|
||||
METHOD_ESP8266_UART,
|
||||
METHOD_BIT_BANG,
|
||||
METHOD_ESP32_I2S,
|
||||
METHOD_ESP32_RMT,
|
||||
METHOD_ESP8266_DMA,
|
||||
)
|
||||
from .const import (
|
||||
CHIP_TYPES,
|
||||
CONF_ASYNC,
|
||||
CONF_BUS,
|
||||
ONE_WIRE_CHIPS,
|
||||
)
|
||||
|
||||
neopixelbus_ns = cg.esphome_ns.namespace("neopixelbus")
|
||||
NeoPixelBusLightOutputBase = neopixelbus_ns.class_(
|
||||
@@ -46,127 +66,115 @@ def validate_type(value):
|
||||
return value
|
||||
|
||||
|
||||
def validate_variant(value):
|
||||
value = cv.string(value).upper()
|
||||
if value == "WS2813":
|
||||
value = "WS2812X"
|
||||
if value == "WS2812":
|
||||
value = "800KBPS"
|
||||
if value == "LC8812":
|
||||
value = "SK6812"
|
||||
return cv.one_of(*VARIANTS)(value)
|
||||
def _choose_default_method(config):
|
||||
if CONF_METHOD in config:
|
||||
return config
|
||||
config = config.copy()
|
||||
if CONF_PIN not in config:
|
||||
config[CONF_METHOD] = _validate_method(METHOD_SPI)
|
||||
return config
|
||||
|
||||
|
||||
def validate_method(value):
|
||||
if value is None:
|
||||
if CORE.is_esp32:
|
||||
return "ESP32_I2S_1"
|
||||
if CORE.is_esp8266:
|
||||
return "ESP8266_DMA"
|
||||
raise NotImplementedError
|
||||
|
||||
if CORE.is_esp32:
|
||||
return cv.one_of(*ESP32_METHODS, upper=True, space="_")(value)
|
||||
pin = config[CONF_PIN]
|
||||
if CORE.is_esp8266:
|
||||
return cv.one_of(*ESP8266_METHODS, upper=True, space="_")(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def validate_method_pin(value):
|
||||
method = value[CONF_METHOD]
|
||||
method_pins = {
|
||||
"ESP8266_DMA": [3],
|
||||
"ESP8266_UART0": [1],
|
||||
"ESP8266_ASYNC_UART0": [1],
|
||||
"ESP8266_UART1": [2],
|
||||
"ESP8266_ASYNC_UART1": [2],
|
||||
"ESP32_I2S_0": list(range(0, 32)),
|
||||
"ESP32_I2S_1": list(range(0, 32)),
|
||||
}
|
||||
if CORE.is_esp8266:
|
||||
method_pins["BIT_BANG"] = list(range(0, 16))
|
||||
elif CORE.is_esp32:
|
||||
method_pins["BIT_BANG"] = list(range(0, 32))
|
||||
pins_ = method_pins.get(method)
|
||||
if pins_ is None:
|
||||
# all pins allowed for this method
|
||||
return value
|
||||
|
||||
for opt in (CONF_PIN, CONF_CLOCK_PIN, CONF_DATA_PIN):
|
||||
if opt in value and value[opt] not in pins_:
|
||||
raise cv.Invalid(
|
||||
f"Method {method} only supports pin(s) {', '.join(f'GPIO{x}' for x in pins_)}",
|
||||
path=[CONF_METHOD],
|
||||
if pin == 3:
|
||||
config[CONF_METHOD] = _validate_method(METHOD_ESP8266_DMA)
|
||||
elif pin == 1:
|
||||
config[CONF_METHOD] = _validate_method(
|
||||
{
|
||||
CONF_TYPE: METHOD_ESP8266_UART,
|
||||
CONF_BUS: 0,
|
||||
}
|
||||
)
|
||||
elif pin == 2:
|
||||
config[CONF_METHOD] = _validate_method(
|
||||
{
|
||||
CONF_TYPE: METHOD_ESP8266_UART,
|
||||
CONF_BUS: 1,
|
||||
}
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
VARIANTS = {
|
||||
"WS2812X": "Ws2812x",
|
||||
"SK6812": "Sk6812",
|
||||
"800KBPS": "800Kbps",
|
||||
"400KBPS": "400Kbps",
|
||||
}
|
||||
|
||||
ESP8266_METHODS = {
|
||||
"ESP8266_DMA": "NeoEsp8266Dma{}Method",
|
||||
"ESP8266_UART0": "NeoEsp8266Uart0{}Method",
|
||||
"ESP8266_UART1": "NeoEsp8266Uart1{}Method",
|
||||
"ESP8266_ASYNC_UART0": "NeoEsp8266AsyncUart0{}Method",
|
||||
"ESP8266_ASYNC_UART1": "NeoEsp8266AsyncUart1{}Method",
|
||||
"BIT_BANG": "NeoEsp8266BitBang{}Method",
|
||||
}
|
||||
ESP32_METHODS = {
|
||||
"ESP32_I2S_0": "NeoEsp32I2s0{}Method",
|
||||
"ESP32_I2S_1": "NeoEsp32I2s1{}Method",
|
||||
"ESP32_RMT_0": "NeoEsp32Rmt0{}Method",
|
||||
"ESP32_RMT_1": "NeoEsp32Rmt1{}Method",
|
||||
"ESP32_RMT_2": "NeoEsp32Rmt2{}Method",
|
||||
"ESP32_RMT_3": "NeoEsp32Rmt3{}Method",
|
||||
"ESP32_RMT_4": "NeoEsp32Rmt4{}Method",
|
||||
"ESP32_RMT_5": "NeoEsp32Rmt5{}Method",
|
||||
"ESP32_RMT_6": "NeoEsp32Rmt6{}Method",
|
||||
"ESP32_RMT_7": "NeoEsp32Rmt7{}Method",
|
||||
"BIT_BANG": "NeoEsp32BitBang{}Method",
|
||||
}
|
||||
|
||||
|
||||
def format_method(config):
|
||||
variant = VARIANTS[config[CONF_VARIANT]]
|
||||
method = config[CONF_METHOD]
|
||||
|
||||
if config[CONF_INVERT]:
|
||||
if method == "ESP8266_DMA":
|
||||
variant = f"Inverted{variant}"
|
||||
else:
|
||||
variant += "Inverted"
|
||||
config[CONF_METHOD] = _validate_method(METHOD_BIT_BANG)
|
||||
|
||||
if CORE.is_esp8266:
|
||||
return ESP8266_METHODS[method].format(variant)
|
||||
if CORE.is_esp32:
|
||||
return ESP32_METHODS[method].format(variant)
|
||||
raise NotImplementedError
|
||||
if get_esp32_variant() == VARIANT_ESP32C3:
|
||||
config[CONF_METHOD] = _validate_method(METHOD_ESP32_RMT)
|
||||
else:
|
||||
config[CONF_METHOD] = _validate_method(METHOD_ESP32_I2S)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def _validate(config):
|
||||
if CONF_PIN in config:
|
||||
variant = config[CONF_VARIANT]
|
||||
if variant in ONE_WIRE_CHIPS:
|
||||
if CONF_PIN not in config:
|
||||
raise cv.Invalid(
|
||||
f"Chip {variant} is a 1-wire chip and needs the [pin] option."
|
||||
)
|
||||
if CONF_CLOCK_PIN in config or CONF_DATA_PIN in config:
|
||||
raise cv.Invalid("Cannot specify both 'pin' and 'clock_pin'+'data_pin'")
|
||||
return config
|
||||
if CONF_CLOCK_PIN in config:
|
||||
if CONF_DATA_PIN not in config:
|
||||
raise cv.Invalid("If you give clock_pin, you must also specify data_pin")
|
||||
return config
|
||||
raise cv.Invalid("Must specify at least one of 'pin' or 'clock_pin'+'data_pin'")
|
||||
raise cv.Invalid(
|
||||
f"Chip {variant} is a 1-wire chip, you need to set [pin] instead of ."
|
||||
)
|
||||
else:
|
||||
if CONF_PIN in config:
|
||||
raise cv.Invalid(
|
||||
f"Chip {variant} is a 2-wire chip and needs the [data_pin]+[clock_pin] option instead of [pin]."
|
||||
)
|
||||
if CONF_CLOCK_PIN not in config or CONF_DATA_PIN not in config:
|
||||
raise cv.Invalid(
|
||||
f"Chip {variant} is a 2-wire chip, you need to set [data_pin]+[clock_pin]."
|
||||
)
|
||||
|
||||
method_type = config[CONF_METHOD][CONF_TYPE]
|
||||
method_desc = METHODS[method_type]
|
||||
if variant not in method_desc.supported_chips:
|
||||
raise cv.Invalid(f"Method {method_type} does not support {variant}")
|
||||
if method_desc.extra_validate is not None:
|
||||
method_desc.extra_validate(config)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def _validate_method(value):
|
||||
if value is None:
|
||||
# default method is determined afterwards because it depends on the chip type chosen
|
||||
return None
|
||||
|
||||
compat_methods = {}
|
||||
for bus in [0, 1]:
|
||||
for is_async in [False, True]:
|
||||
compat_methods[f"ESP8266{'_ASYNC' if is_async else ''}_UART{bus}"] = {
|
||||
CONF_TYPE: METHOD_ESP8266_UART,
|
||||
CONF_BUS: bus,
|
||||
CONF_ASYNC: is_async,
|
||||
}
|
||||
compat_methods[f"ESP32_I2S_{bus}"] = {
|
||||
CONF_TYPE: METHOD_ESP32_I2S,
|
||||
CONF_BUS: bus,
|
||||
}
|
||||
for channel in range(8):
|
||||
compat_methods[f"ESP32_RMT_{channel}"] = {
|
||||
CONF_TYPE: METHOD_ESP32_RMT,
|
||||
CONF_CHANNEL: channel,
|
||||
}
|
||||
|
||||
if isinstance(value, str):
|
||||
if value.upper() in compat_methods:
|
||||
return _validate_method(compat_methods[value.upper()])
|
||||
return _validate_method({CONF_TYPE: value})
|
||||
return cv.typed_schema(
|
||||
{k: v.method_schema for k, v in METHODS.items()}, lower=True
|
||||
)(value)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.only_with_arduino,
|
||||
light.ADDRESSABLE_LIGHT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(NeoPixelBusLightOutputBase),
|
||||
cv.Optional(CONF_TYPE, default="GRB"): validate_type,
|
||||
cv.Optional(CONF_VARIANT, default="800KBPS"): validate_variant,
|
||||
cv.Optional(CONF_METHOD, default=None): validate_method,
|
||||
cv.Required(CONF_VARIANT): cv.one_of(*CHIP_TYPES, lower=True),
|
||||
cv.Optional(CONF_METHOD): _validate_method,
|
||||
cv.Optional(CONF_INVERT, default="no"): cv.boolean,
|
||||
cv.Optional(CONF_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_CLOCK_PIN): pins.internal_gpio_output_pin_number,
|
||||
@@ -174,19 +182,23 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
_choose_default_method,
|
||||
_validate,
|
||||
validate_method_pin,
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
has_white = "W" in config[CONF_TYPE]
|
||||
template = cg.TemplateArguments(getattr(cg.global_ns, format_method(config)))
|
||||
method = config[CONF_METHOD]
|
||||
|
||||
method_template = METHODS[method[CONF_TYPE]].to_code(
|
||||
method, config[CONF_VARIANT], config[CONF_INVERT]
|
||||
)
|
||||
|
||||
if has_white:
|
||||
out_type = NeoPixelRGBWLightOutput.template(template)
|
||||
out_type = NeoPixelRGBWLightOutput.template(method_template)
|
||||
else:
|
||||
out_type = NeoPixelRGBLightOutput.template(template)
|
||||
out_type = NeoPixelRGBLightOutput.template(method_template)
|
||||
rhs = out_type.new()
|
||||
var = cg.Pvariable(config[CONF_OUTPUT_ID], rhs, out_type)
|
||||
await light.register_light(var, config)
|
||||
@@ -204,4 +216,4 @@ async def to_code(config):
|
||||
cg.add(var.set_pixel_order(getattr(ESPNeoPixelOrder, config[CONF_TYPE])))
|
||||
|
||||
# https://github.com/Makuna/NeoPixelBus/blob/master/library.json
|
||||
cg.add_library("makuna/NeoPixelBus", "2.6.7")
|
||||
cg.add_library("makuna/NeoPixelBus", "2.6.9")
|
||||
|
||||
@@ -33,7 +33,7 @@ void NextionBinarySensor::update() {
|
||||
if (this->variable_name_.empty()) // This is a touch component
|
||||
return;
|
||||
|
||||
this->nextion_->add_to_get_queue(shared_from_this());
|
||||
this->nextion_->add_to_get_queue(this);
|
||||
}
|
||||
|
||||
void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nextion) {
|
||||
@@ -48,7 +48,7 @@ void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nexti
|
||||
this->needs_to_send_update_ = true;
|
||||
} else {
|
||||
this->needs_to_send_update_ = false;
|
||||
this->nextion_->add_no_result_to_queue_with_set(shared_from_this(), (int) state);
|
||||
this->nextion_->add_no_result_to_queue_with_set(this, (int) state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,7 @@ class NextionBinarySensor;
|
||||
|
||||
class NextionBinarySensor : public NextionComponent,
|
||||
public binary_sensor::BinarySensorInitiallyOff,
|
||||
public PollingComponent,
|
||||
public std::enable_shared_from_this<NextionBinarySensor> {
|
||||
public PollingComponent {
|
||||
public:
|
||||
NextionBinarySensor(NextionBase *nextion) { this->nextion_ = nextion; }
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from esphome.const import (
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
|
||||
from esphome.core import CORE
|
||||
from . import Nextion, nextion_ns, nextion_ref
|
||||
from .base_component import (
|
||||
CONF_ON_SLEEP,
|
||||
@@ -76,6 +76,9 @@ async def to_code(config):
|
||||
if CONF_TFT_URL in config:
|
||||
cg.add_define("USE_NEXTION_TFT_UPLOAD")
|
||||
cg.add(var.set_tft_url(config[CONF_TFT_URL]))
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("WiFiClientSecure", None)
|
||||
cg.add_library("HTTPClient", None)
|
||||
|
||||
if CONF_TOUCH_SLEEP_TIMEOUT in config:
|
||||
cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT]))
|
||||
|
||||
@@ -196,7 +196,7 @@ void Nextion::print_queue_members_() {
|
||||
ESP_LOGN(TAG, "print_queue_members_ (top 10) size %zu", this->nextion_queue_.size());
|
||||
ESP_LOGN(TAG, "*******************************************");
|
||||
int count = 0;
|
||||
for (auto &i : this->nextion_queue_) {
|
||||
for (auto *i : this->nextion_queue_) {
|
||||
if (count++ == 10)
|
||||
break;
|
||||
|
||||
@@ -257,9 +257,8 @@ bool Nextion::remove_from_q_(bool report_empty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto nb = std::move(this->nextion_queue_.front());
|
||||
this->nextion_queue_.pop_front();
|
||||
auto &component = nb->component;
|
||||
NextionQueue *nb = this->nextion_queue_.front();
|
||||
NextionComponentBase *component = nb->component;
|
||||
|
||||
ESP_LOGN(TAG, "Removing %s from the queue", component->get_variable_name().c_str());
|
||||
|
||||
@@ -267,8 +266,10 @@ bool Nextion::remove_from_q_(bool report_empty) {
|
||||
if (component->get_variable_name() == "sleep_wake") {
|
||||
this->is_sleeping_ = false;
|
||||
}
|
||||
delete component; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->nextion_queue_.pop_front();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -357,7 +358,7 @@ void Nextion::process_nextion_commands_() {
|
||||
int index = 0;
|
||||
int found = -1;
|
||||
for (auto &nb : this->nextion_queue_) {
|
||||
auto &component = nb->component;
|
||||
NextionComponentBase *component = nb->component;
|
||||
|
||||
if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) {
|
||||
ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!",
|
||||
@@ -368,6 +369,9 @@ void Nextion::process_nextion_commands_() {
|
||||
|
||||
found = index;
|
||||
|
||||
delete component; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
@@ -464,9 +468,8 @@ void Nextion::process_nextion_commands_() {
|
||||
break;
|
||||
}
|
||||
|
||||
auto nb = std::move(this->nextion_queue_.front());
|
||||
this->nextion_queue_.pop_front();
|
||||
auto &component = nb->component;
|
||||
NextionQueue *nb = this->nextion_queue_.front();
|
||||
NextionComponentBase *component = nb->component;
|
||||
|
||||
if (component->get_queue_type() != NextionQueueType::TEXT_SENSOR) {
|
||||
ESP_LOGE(TAG, "ERROR: Received string return but next in queue \"%s\" is not a text sensor",
|
||||
@@ -477,6 +480,9 @@ void Nextion::process_nextion_commands_() {
|
||||
component->set_state_from_string(to_process, true, false);
|
||||
}
|
||||
|
||||
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->nextion_queue_.pop_front();
|
||||
|
||||
break;
|
||||
}
|
||||
// 0x71 0x01 0x02 0x03 0x04 0xFF 0xFF 0xFF
|
||||
@@ -505,9 +511,8 @@ void Nextion::process_nextion_commands_() {
|
||||
++dataindex;
|
||||
}
|
||||
|
||||
auto nb = std::move(this->nextion_queue_.front());
|
||||
this->nextion_queue_.pop_front();
|
||||
auto &component = nb->component;
|
||||
NextionQueue *nb = this->nextion_queue_.front();
|
||||
NextionComponentBase *component = nb->component;
|
||||
|
||||
if (component->get_queue_type() != NextionQueueType::SENSOR &&
|
||||
component->get_queue_type() != NextionQueueType::BINARY_SENSOR &&
|
||||
@@ -521,6 +526,9 @@ void Nextion::process_nextion_commands_() {
|
||||
component->set_state_from_int(value, true, false);
|
||||
}
|
||||
|
||||
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->nextion_queue_.pop_front();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -682,7 +690,7 @@ void Nextion::process_nextion_commands_() {
|
||||
int index = 0;
|
||||
int found = -1;
|
||||
for (auto &nb : this->nextion_queue_) {
|
||||
auto &component = nb->component;
|
||||
auto component = nb->component;
|
||||
if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) {
|
||||
size_t buffer_to_send = component->get_wave_buffer().size() < 255 ? component->get_wave_buffer().size()
|
||||
: 255; // ADDT command can only send 255
|
||||
@@ -699,6 +707,8 @@ void Nextion::process_nextion_commands_() {
|
||||
component->get_wave_buffer().begin() + buffer_to_send);
|
||||
}
|
||||
found = index;
|
||||
delete component; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
@@ -727,7 +737,7 @@ void Nextion::process_nextion_commands_() {
|
||||
|
||||
if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) {
|
||||
for (int i = 0; i < this->nextion_queue_.size(); i++) {
|
||||
auto &component = this->nextion_queue_[i]->component;
|
||||
NextionComponentBase *component = this->nextion_queue_[i]->component;
|
||||
if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) {
|
||||
if (this->nextion_queue_[i]->queue_time == 0)
|
||||
ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\" queue_time 0",
|
||||
@@ -744,8 +754,11 @@ void Nextion::process_nextion_commands_() {
|
||||
if (component->get_variable_name() == "sleep_wake") {
|
||||
this->is_sleeping_ = false;
|
||||
}
|
||||
delete component; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
delete this->nextion_queue_[i]; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
|
||||
this->nextion_queue_.erase(this->nextion_queue_.begin() + i);
|
||||
i--;
|
||||
|
||||
@@ -899,16 +912,18 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool
|
||||
* @param variable_name Name for the queue
|
||||
*/
|
||||
void Nextion::add_no_result_to_queue_(const std::string &variable_name) {
|
||||
auto nextion_queue = make_unique<nextion::NextionQueue>();
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
|
||||
nextion::NextionQueue *nextion_queue = new nextion::NextionQueue;
|
||||
|
||||
nextion_queue->component = make_unique<nextion::NextionComponentBase>();
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
|
||||
nextion_queue->component = new nextion::NextionComponentBase;
|
||||
nextion_queue->component->set_variable_name(variable_name);
|
||||
|
||||
nextion_queue->queue_time = millis();
|
||||
|
||||
ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str());
|
||||
this->nextion_queue_.push_back(nextion_queue);
|
||||
|
||||
this->nextion_queue_.push_back(std::move(nextion_queue));
|
||||
ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -979,7 +994,7 @@ bool Nextion::add_no_result_to_queue_with_printf_(const std::string &variable_na
|
||||
* @param is_sleep_safe The command is safe to send when the Nextion is sleeping
|
||||
*/
|
||||
|
||||
void Nextion::add_no_result_to_queue_with_set(std::shared_ptr<NextionComponentBase> component, int state_value) {
|
||||
void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) {
|
||||
this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(),
|
||||
state_value);
|
||||
}
|
||||
@@ -1007,8 +1022,7 @@ void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &varia
|
||||
* @param state_value String value to set
|
||||
* @param is_sleep_safe The command is safe to send when the Nextion is sleeping
|
||||
*/
|
||||
void Nextion::add_no_result_to_queue_with_set(std::shared_ptr<NextionComponentBase> component,
|
||||
const std::string &state_value) {
|
||||
void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) {
|
||||
this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(),
|
||||
state_value);
|
||||
}
|
||||
@@ -1028,11 +1042,12 @@ void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &varia
|
||||
state_value.c_str());
|
||||
}
|
||||
|
||||
void Nextion::add_to_get_queue(std::shared_ptr<NextionComponentBase> component) {
|
||||
void Nextion::add_to_get_queue(NextionComponentBase *component) {
|
||||
if ((!this->is_setup() && !this->ignore_is_setup_))
|
||||
return;
|
||||
|
||||
auto nextion_queue = make_unique<nextion::NextionQueue>();
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
|
||||
nextion::NextionQueue *nextion_queue = new nextion::NextionQueue;
|
||||
|
||||
nextion_queue->component = component;
|
||||
nextion_queue->queue_time = millis();
|
||||
@@ -1043,7 +1058,7 @@ void Nextion::add_to_get_queue(std::shared_ptr<NextionComponentBase> component)
|
||||
std::string command = "get " + component->get_variable_name_to_send();
|
||||
|
||||
if (this->send_command_(command)) {
|
||||
this->nextion_queue_.push_back(std::move(nextion_queue));
|
||||
this->nextion_queue_.push_back(nextion_queue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1055,13 +1070,15 @@ void Nextion::add_to_get_queue(std::shared_ptr<NextionComponentBase> component)
|
||||
* @param buffer_to_send The buffer size
|
||||
* @param buffer_size The buffer data
|
||||
*/
|
||||
void Nextion::add_addt_command_to_queue(std::shared_ptr<NextionComponentBase> component) {
|
||||
void Nextion::add_addt_command_to_queue(NextionComponentBase *component) {
|
||||
if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping())
|
||||
return;
|
||||
|
||||
auto nextion_queue = make_unique<nextion::NextionQueue>();
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
|
||||
nextion::NextionQueue *nextion_queue = new nextion::NextionQueue;
|
||||
|
||||
nextion_queue->component = std::make_shared<nextion::NextionComponentBase>();
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
|
||||
nextion_queue->component = new nextion::NextionComponentBase;
|
||||
nextion_queue->queue_time = millis();
|
||||
|
||||
size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size()
|
||||
@@ -1070,7 +1087,7 @@ void Nextion::add_addt_command_to_queue(std::shared_ptr<NextionComponentBase> co
|
||||
std::string command = "addt " + to_string(component->get_component_id()) + "," +
|
||||
to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send);
|
||||
if (this->send_command_(command)) {
|
||||
this->nextion_queue_.push_back(std::move(nextion_queue));
|
||||
this->nextion_queue_.push_back(nextion_queue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -707,18 +707,17 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
void set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state);
|
||||
void set_nextion_text_state(const std::string &name, const std::string &state);
|
||||
|
||||
void add_no_result_to_queue_with_set(std::shared_ptr<NextionComponentBase> component, int state_value) override;
|
||||
void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) override;
|
||||
void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send,
|
||||
int state_value) override;
|
||||
|
||||
void add_no_result_to_queue_with_set(std::shared_ptr<NextionComponentBase> component,
|
||||
const std::string &state_value) override;
|
||||
void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) override;
|
||||
void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send,
|
||||
const std::string &state_value) override;
|
||||
|
||||
void add_to_get_queue(std::shared_ptr<NextionComponentBase> component) override;
|
||||
void add_to_get_queue(NextionComponentBase *component) override;
|
||||
|
||||
void add_addt_command_to_queue(std::shared_ptr<NextionComponentBase> component) override;
|
||||
void add_addt_command_to_queue(NextionComponentBase *component) override;
|
||||
|
||||
void update_components_by_prefix(const std::string &prefix);
|
||||
|
||||
@@ -729,7 +728,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; }
|
||||
|
||||
protected:
|
||||
std::deque<std::unique_ptr<NextionQueue>> nextion_queue_;
|
||||
std::deque<NextionQueue *> nextion_queue_;
|
||||
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag);
|
||||
void all_components_send_state_(bool force_update = false);
|
||||
uint64_t comok_sent_ = 0;
|
||||
|
||||
@@ -24,19 +24,18 @@ class NextionBase;
|
||||
|
||||
class NextionBase {
|
||||
public:
|
||||
virtual void add_no_result_to_queue_with_set(std::shared_ptr<NextionComponentBase> component, int state_value) = 0;
|
||||
virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) = 0;
|
||||
virtual void add_no_result_to_queue_with_set(const std::string &variable_name,
|
||||
const std::string &variable_name_to_send, int state_value) = 0;
|
||||
|
||||
virtual void add_no_result_to_queue_with_set(std::shared_ptr<NextionComponentBase> component,
|
||||
const std::string &state_value) = 0;
|
||||
virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) = 0;
|
||||
virtual void add_no_result_to_queue_with_set(const std::string &variable_name,
|
||||
const std::string &variable_name_to_send,
|
||||
const std::string &state_value) = 0;
|
||||
|
||||
virtual void add_addt_command_to_queue(std::shared_ptr<NextionComponentBase> component) = 0;
|
||||
virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0;
|
||||
|
||||
virtual void add_to_get_queue(std::shared_ptr<NextionComponentBase> component) = 0;
|
||||
virtual void add_to_get_queue(NextionComponentBase *component) = 0;
|
||||
|
||||
virtual void set_component_background_color(const char *component, Color color) = 0;
|
||||
virtual void set_component_pressed_background_color(const char *component, Color color) = 0;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user