Merge branch 'dev' into ble-server-controller

This commit is contained in:
Jesse Hills 2022-01-19 08:43:22 +13:00
commit 7401fd42a8
No known key found for this signature in database
GPG key ID: BEAAE804EFD8E83A
641 changed files with 16700 additions and 4551 deletions

View file

@ -5,11 +5,8 @@ Checks: >-
-altera-*, -altera-*,
-android-*, -android-*,
-boost-*, -boost-*,
-bugprone-branch-clone,
-bugprone-easily-swappable-parameters,
-bugprone-narrowing-conversions, -bugprone-narrowing-conversions,
-bugprone-signed-char-misuse, -bugprone-signed-char-misuse,
-bugprone-too-small-loop-variable,
-cert-dcl50-cpp, -cert-dcl50-cpp,
-cert-err58-cpp, -cert-err58-cpp,
-cert-oop57-cpp, -cert-oop57-cpp,
@ -19,12 +16,10 @@ Checks: >-
-clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-shadow-field, -clang-diagnostic-shadow-field,
-clang-diagnostic-sign-compare,
-clang-diagnostic-unused-variable,
-clang-diagnostic-unused-const-variable, -clang-diagnostic-unused-const-variable,
-clang-diagnostic-unused-parameter,
-concurrency-*, -concurrency-*,
-cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-init-variables, -cppcoreguidelines-init-variables,
-cppcoreguidelines-macro-usage, -cppcoreguidelines-macro-usage,
@ -41,7 +36,6 @@ Checks: >-
-cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions, -cppcoreguidelines-special-member-functions,
-fuchsia-default-arguments,
-fuchsia-multiple-inheritance, -fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator, -fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects, -fuchsia-statically-constructed-objects,
@ -51,6 +45,7 @@ Checks: >-
-google-explicit-constructor, -google-explicit-constructor,
-google-readability-braces-around-statements, -google-readability-braces-around-statements,
-google-readability-casting, -google-readability-casting,
-google-readability-namespace-comments,
-google-readability-todo, -google-readability-todo,
-google-runtime-references, -google-runtime-references,
-hicpp-*, -hicpp-*,
@ -97,9 +92,11 @@ CheckOptions:
value: '1' value: '1'
- key: google-readability-function-size.StatementThreshold - key: google-readability-function-size.StatementThreshold
value: '800' value: '800'
- key: google-readability-namespace-comments.ShortNamespaceLines - key: google-runtime-int.TypeSuffix
value: '_t'
- key: llvm-namespace-comment.ShortNamespaceLines
value: '10' value: '10'
- key: google-readability-namespace-comments.SpacesBeforeComments - key: llvm-namespace-comment.SpacesBeforeComments
value: '2' value: '2'
- key: modernize-loop-convert.MaxCopySize - key: modernize-loop-convert.MaxCopySize
value: '16' value: '16'

View file

@ -16,6 +16,7 @@ Quick description and explanation of changes
## Test Environment ## Test Environment
- [ ] ESP32 - [ ] ESP32
- [ ] ESP32 IDF
- [ ] ESP8266 - [ ] ESP8266
## Example entry for `config.yaml`: ## Example entry for `config.yaml`:

View file

@ -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
View file

@ -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

View file

@ -17,6 +17,10 @@ on:
- 'requirements*.txt' - 'requirements*.txt'
- 'platformio.ini' - 'platformio.ini'
permissions:
contents: read
packages: read
jobs: jobs:
check-docker: check-docker:
name: Build docker containers name: Build docker containers

View file

@ -1,5 +1,3 @@
# THESE JOBS ARE COPIED IN release.yml and release-dev.yml
# PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE
name: CI name: CI
on: on:
@ -8,6 +6,13 @@ on:
pull_request: pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
ci: ci:
name: ${{ matrix.name }} name: ${{ matrix.name }}
@ -31,7 +36,7 @@ jobs:
- id: test - id: test
file: tests/test3.yaml file: tests/test3.yaml
name: Test tests/test3.yaml name: Test tests/test3.yaml
pio_cache_key: test1 pio_cache_key: test3
- id: test - id: test
file: tests/test4.yaml file: tests/test4.yaml
name: Test tests/test4.yaml name: Test tests/test4.yaml
@ -46,26 +51,26 @@ jobs:
name: Run script/clang-format name: Run script/clang-format
- id: clang-tidy - id: clang-tidy
name: Run script/clang-tidy for ESP8266 name: Run script/clang-tidy for ESP8266
options: --environment esp8266-tidy --grep USE_ESP8266 options: --environment esp8266-arduino-tidy --grep USE_ESP8266
pio_cache_key: tidyesp8266 pio_cache_key: tidyesp8266
- id: clang-tidy - id: clang-tidy
name: Run script/clang-tidy for ESP32 1/4 name: Run script/clang-tidy for ESP32 Arduino 1/4
options: --environment esp32-tidy --split-num 4 --split-at 1 options: --environment esp32-arduino-tidy --split-num 4 --split-at 1
pio_cache_key: tidyesp32 pio_cache_key: tidyesp32
- id: clang-tidy - id: clang-tidy
name: Run script/clang-tidy for ESP32 2/4 name: Run script/clang-tidy for ESP32 Arduino 2/4
options: --environment esp32-tidy --split-num 4 --split-at 2 options: --environment esp32-arduino-tidy --split-num 4 --split-at 2
pio_cache_key: tidyesp32 pio_cache_key: tidyesp32
- id: clang-tidy - id: clang-tidy
name: Run script/clang-tidy for ESP32 3/4 name: Run script/clang-tidy for ESP32 Arduino 3/4
options: --environment esp32-tidy --split-num 4 --split-at 3 options: --environment esp32-arduino-tidy --split-num 4 --split-at 3
pio_cache_key: tidyesp32 pio_cache_key: tidyesp32
- id: clang-tidy - id: clang-tidy
name: Run script/clang-tidy for ESP32 4/4 name: Run script/clang-tidy for ESP32 Arduino 4/4
options: --environment esp32-tidy --split-num 4 --split-at 4 options: --environment esp32-arduino-tidy --split-num 4 --split-at 4
pio_cache_key: tidyesp32 pio_cache_key: tidyesp32
- id: clang-tidy - id: clang-tidy
name: Run script/clang-tidy for ESP32 esp-idf name: Run script/clang-tidy for ESP32 IDF
options: --environment esp32-idf-tidy --grep USE_ESP_IDF options: --environment esp32-idf-tidy --grep USE_ESP_IDF
pio_cache_key: tidyesp32-idf pio_cache_key: tidyesp32-idf
@ -77,18 +82,23 @@ jobs:
with: with:
python-version: '3.7' python-version: '3.7'
- name: Cache pip modules - name: Cache virtualenv
uses: actions/cache@v2 uses: actions/cache@v2
with: with:
path: ~/.cache/pip path: .venv
key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }}
restore-keys: | restore-keys: |
pip-${{ steps.python.outputs.python-version }}- venv-${{ steps.python.outputs.python-version }}-
- name: Set up python environment - name: Set up virtualenv
run: | run: |
pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt python -m venv .venv
pip3 install -e . source .venv/bin/activate
pip install -U pip
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -e .
echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH
echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV
# Use per check platformio cache because checks use different parts # Use per check platformio cache because checks use different parts
- name: Cache platformio - name: Cache platformio

View file

@ -9,13 +9,19 @@ permissions:
issues: write issues: write
pull-requests: write pull-requests: write
concurrency:
group: lock
jobs: jobs:
lock: lock:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v2 - uses: dessant/lock-threads@v3
with: with:
github-token: ${{ github.token }} pr-inactive-days: "1"
pr-lock-inactive-days: "1"
pr-lock-reason: "" 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

View file

@ -4,7 +4,7 @@
"owner": "ci-custom", "owner": "ci-custom",
"pattern": [ "pattern": [
{ {
"regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$", "regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$",
"file": 1, "file": 1,
"line": 2, "line": 2,
"column": 3, "column": 3,

View file

@ -5,7 +5,7 @@
"severity": "error", "severity": "error",
"pattern": [ "pattern": [
{ {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", "regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
"file": 1, "file": 1,
"line": 2, "line": 2,
"column": 3, "column": 3,

View file

@ -1,11 +1,22 @@
{ {
"problemMatcher": [ "problemMatcher": [
{
"owner": "black",
"severity": "error",
"pattern": [
{
"regexp": "^(.*): (Please format this file with the black formatter)",
"file": 1,
"message": 2
}
]
},
{ {
"owner": "flake8", "owner": "flake8",
"severity": "error", "severity": "error",
"pattern": [ "pattern": [
{ {
"regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$", "regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$",
"file": 1, "file": 1,
"line": 2, "line": 2,
"message": 3 "message": 3
@ -17,7 +28,7 @@
"severity": "error", "severity": "error",
"pattern": [ "pattern": [
{ {
"regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$", "regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
"file": 1, "file": 1,
"line": 2, "line": 2,
"message": 3 "message": 3

View file

@ -7,6 +7,9 @@ on:
schedule: schedule:
- cron: "0 2 * * *" - cron: "0 2 * * *"
permissions:
contents: read
jobs: jobs:
init: init:
name: Initialize build name: Initialize build
@ -52,6 +55,9 @@ jobs:
deploy-docker: deploy-docker:
name: Build and publish docker containers name: Build and publish docker containers
if: github.repository == 'esphome/esphome' if: github.repository == 'esphome/esphome'
permissions:
contents: read
packages: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [init] needs: [init]
strategy: strategy:
@ -93,6 +99,9 @@ jobs:
deploy-docker-manifest: deploy-docker-manifest:
if: github.repository == 'esphome/esphome' if: github.repository == 'esphome/esphome'
permissions:
contents: read
packages: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [init, deploy-docker] needs: [init, deploy-docker]
strategy: strategy:

View file

@ -9,13 +9,15 @@ permissions:
issues: write issues: write
pull-requests: write pull-requests: write
concurrency:
group: lock
jobs: jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v4 - uses: actions/stale@v4
with: with:
repo-token: ${{ github.token }}
days-before-pr-stale: 90 days-before-pr-stale: 90
days-before-pr-close: 7 days-before-pr-close: 7
days-before-issue-stale: -1 days-before-issue-stale: -1
@ -28,3 +30,19 @@ jobs:
pull request has been automatically marked as stale because of that pull request has been automatically marked as stale because of that
and will be closed if no further activity occurs within 7 days. and will be closed if no further activity occurs within 7 days.
Thank you for your contributions. 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

View file

@ -3,4 +3,4 @@ ports:
onOpen: open-preview onOpen: open-preview
tasks: tasks:
- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup - before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup
command: python -m esphome config dashboard command: python -m esphome dashboard config

View file

@ -28,17 +28,23 @@ esphome/components/b_parasite/* @rbaron
esphome/components/ballu/* @bazuchan esphome/components/ballu/* @bazuchan
esphome/components/bang_bang/* @OttoWinter esphome/components/bang_bang/* @OttoWinter
esphome/components/binary_sensor/* @esphome/core esphome/components/binary_sensor/* @esphome/core
esphome/components/bl0940/* @tobias-
esphome/components/ble_client/* @buxtronix esphome/components/ble_client/* @buxtronix
esphome/components/bme680_bsec/* @trvrnrth esphome/components/bme680_bsec/* @trvrnrth
esphome/components/bmp3xx/* @martgras
esphome/components/button/* @esphome/core
esphome/components/canbus/* @danielschramm @mvturnho esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @MrEditor97
esphome/components/captive_portal/* @OttoWinter esphome/components/captive_portal/* @OttoWinter
esphome/components/ccs811/* @habbie esphome/components/ccs811/* @habbie
esphome/components/cd74hc4067/* @asoehlke
esphome/components/climate/* @esphome/core esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet esphome/components/climate_ir/* @glmnet
esphome/components/color_temperature/* @jesserockz esphome/components/color_temperature/* @jesserockz
esphome/components/coolix/* @glmnet esphome/components/coolix/* @glmnet
esphome/components/cover/* @esphome/core esphome/components/cover/* @esphome/core
esphome/components/cs5460a/* @balrog-kun esphome/components/cs5460a/* @balrog-kun
esphome/components/cse7761/* @berfenger
esphome/components/ct_clamp/* @jesserockz esphome/components/ct_clamp/* @jesserockz
esphome/components/current_based/* @djwmarcx esphome/components/current_based/* @djwmarcx
esphome/components/daly_bms/* @s1lvi0 esphome/components/daly_bms/* @s1lvi0
@ -52,6 +58,8 @@ esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble/* @jesserockz
esphome/components/esp32_ble_controller/* @jesserockz esphome/components/esp32_ble_controller/* @jesserockz
esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz
esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron
esphome/components/esp32_improv/* @jesserockz esphome/components/esp32_improv/* @jesserockz
esphome/components/esp8266/* @esphome/core esphome/components/esp8266/* @esphome/core
esphome/components/exposure_notifications/* @OttoWinter esphome/components/exposure_notifications/* @OttoWinter
@ -62,6 +70,7 @@ esphome/components/globals/* @esphome/core
esphome/components/gpio/* @esphome/core esphome/components/gpio/* @esphome/core
esphome/components/gps/* @coogle esphome/components/gps/* @coogle
esphome/components/graph/* @synco esphome/components/graph/* @synco
esphome/components/growatt_solar/* @leeuwte
esphome/components/havells_solar/* @sourabhjaiswal esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann esphome/components/hbridge/light/* @DotNetDann
@ -70,12 +79,14 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/homeassistant/* @OttoWinter esphome/components/homeassistant/* @OttoWinter
esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/i2c/* @esphome/core esphome/components/i2c/* @esphome/core
esphome/components/improv/* @jesserockz esphome/components/improv_serial/* @esphome/core
esphome/components/ina260/* @MrEditor97
esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz esphome/components/inkplate6/* @jesserockz
esphome/components/integration/* @OttoWinter esphome/components/integration/* @OttoWinter
esphome/components/interval/* @esphome/core esphome/components/interval/* @esphome/core
esphome/components/json/* @OttoWinter esphome/components/json/* @OttoWinter
esphome/components/kalman_combinator/* @Cat-Ion
esphome/components/ledc/* @OttoWinter esphome/components/ledc/* @OttoWinter
esphome/components/light/* @esphome/core esphome/components/light/* @esphome/core
esphome/components/logger/* @esphome/core esphome/components/logger/* @esphome/core
@ -89,9 +100,13 @@ esphome/components/mcp23x08_base/* @jesserockz
esphome/components/mcp23x17_base/* @jesserockz esphome/components/mcp23x17_base/* @jesserockz
esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz
esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp2515/* @danielschramm @mvturnho
esphome/components/mcp3204/* @rsumner
esphome/components/mcp47a1/* @jesserockz
esphome/components/mcp9808/* @k7hpn esphome/components/mcp9808/* @k7hpn
esphome/components/md5/* @esphome/core
esphome/components/mdns/* @esphome/core esphome/components/mdns/* @esphome/core
esphome/components/midea/* @dudanov esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov
esphome/components/mitsubishi/* @RubyBailey esphome/components/mitsubishi/* @RubyBailey
esphome/components/modbus_controller/* @martgras esphome/components/modbus_controller/* @martgras
esphome/components/modbus_controller/binary_sensor/* @martgras esphome/components/modbus_controller/binary_sensor/* @martgras
@ -119,6 +134,7 @@ esphome/components/pn532_i2c/* @OttoWinter @jesserockz
esphome/components/pn532_spi/* @OttoWinter @jesserockz esphome/components/pn532_spi/* @OttoWinter @jesserockz
esphome/components/power_supply/* @esphome/core esphome/components/power_supply/* @esphome/core
esphome/components/preferences/* @esphome/core esphome/components/preferences/* @esphome/core
esphome/components/psram/* @esphome/core
esphome/components/pulse_meter/* @stevebaxter esphome/components/pulse_meter/* @stevebaxter
esphome/components/pvvx_mithermometer/* @pasiz esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/rc522/* @glmnet esphome/components/rc522/* @glmnet
@ -128,7 +144,7 @@ esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz esphome/components/rgbct/* @jesserockz
esphome/components/rtttl/* @glmnet esphome/components/rtttl/* @glmnet
esphome/components/safe_mode/* @paulmonigatti esphome/components/safe_mode/* @jsuanet @paulmonigatti
esphome/components/scd4x/* @sjtrny esphome/components/scd4x/* @sjtrny
esphome/components/script/* @esphome/core esphome/components/script/* @esphome/core
esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdm_meter/* @jesserockz @polyfaces
@ -138,7 +154,7 @@ esphome/components/select/* @esphome/core
esphome/components/sensor/* @esphome/core esphome/components/sensor/* @esphome/core
esphome/components/sgp40/* @SenexCrenshaw esphome/components/sgp40/* @SenexCrenshaw
esphome/components/sht4x/* @sjtrny esphome/components/sht4x/* @sjtrny
esphome/components/shutdown/* @esphome/core esphome/components/shutdown/* @esphome/core @jsuanet
esphome/components/sim800l/* @glmnet esphome/components/sim800l/* @glmnet
esphome/components/sm2135/* @BoukeHaarsma23 esphome/components/sm2135/* @BoukeHaarsma23
esphome/components/socket/* @esphome/core esphome/components/socket/* @esphome/core
@ -175,8 +191,10 @@ esphome/components/toshiba/* @kbx81
esphome/components/tsl2591/* @wjcarpenter esphome/components/tsl2591/* @wjcarpenter
esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/number/* @frankiboy1
esphome/components/tuya/sensor/* @jesserockz esphome/components/tuya/sensor/* @jesserockz
esphome/components/tuya/switch/* @jesserockz esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra
esphome/components/uart/* @esphome/core esphome/components/uart/* @esphome/core
esphome/components/ultrasonic/* @OttoWinter esphome/components/ultrasonic/* @OttoWinter
esphome/components/version/* @esphome/core esphome/components/version/* @esphome/core

View file

@ -5,12 +5,12 @@
# One of "docker", "hassio" # One of "docker", "hassio"
ARG BASEIMGTYPE=docker 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/amd64:5.2.3 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/aarch64:5.2.3 AS base-hassio-arm64
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7 FROM ghcr.io/hassio-addons/debian-base/armv7:5.2.3 AS base-hassio-armv7
FROM debian:bullseye-20210902-slim AS base-docker-amd64 FROM debian:bullseye-20211220-slim AS base-docker-amd64
FROM debian:bullseye-20210902-slim AS base-docker-arm64 FROM debian:bullseye-20211220-slim AS base-docker-arm64
FROM debian:bullseye-20210902-slim AS base-docker-armv7 FROM debian:bullseye-20211220-slim AS base-docker-armv7
# Use TARGETARCH/TARGETVARIANT defined by docker # Use TARGETARCH/TARGETVARIANT defined by docker
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope # https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
@ -27,7 +27,7 @@ RUN \
python3-cryptography=3.3.2-1 \ python3-cryptography=3.3.2-1 \
iputils-ping=3:20210202-1 \ iputils-ping=3:20210202-1 \
git=1:2.30.2-1 \ git=1:2.30.2-1 \
curl=7.74.0-1.3+b1 \ curl=7.74.0-1.3+deb11u1 \
&& rm -rf \ && rm -rf \
/tmp/* \ /tmp/* \
/var/{cache,log}/* \ /var/{cache,log}/* \
@ -42,8 +42,8 @@ ENV \
RUN \ RUN \
# Ubuntu python3-pip is missing wheel # Ubuntu python3-pip is missing wheel
pip3 install --no-cache-dir \ pip3 install --no-cache-dir \
wheel==0.36.2 \ wheel==0.37.1 \
platformio==5.2.0 \ platformio==5.2.4 \
# Change some platformio settings # Change some platformio settings
&& platformio settings set enable_telemetry No \ && platformio settings set enable_telemetry No \
&& platformio settings set check_libraries_interval 1000000 \ && platformio settings set check_libraries_interval 1000000 \
@ -64,7 +64,7 @@ RUN \
# Copy esphome and install # Copy esphome and install
COPY . /esphome COPY . /esphome
RUN pip3 install --no-cache-dir -e /esphome RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome
# Settings for dashboard # Settings for dashboard
ENV USERNAME="" PASSWORD="" ENV USERNAME="" PASSWORD=""
@ -112,7 +112,7 @@ RUN \
# Copy esphome and install # Copy esphome and install
COPY . /esphome COPY . /esphome
RUN pip3 install --no-cache-dir -e /esphome RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome
# Labels # Labels
LABEL \ LABEL \
@ -147,9 +147,9 @@ RUN \
/var/{cache,log}/* \ /var/{cache,log}/* \
/var/lib/apt/lists/* /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 \ 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 && /platformio_install_deps.py /platformio.ini
VOLUME ["/esphome"] VOLUME ["/esphome"]

View file

@ -32,6 +32,7 @@ parser.add_argument("--dry-run", action="store_true", help="Don't run any comman
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True) subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
build_parser = subparsers.add_parser("build", help="Build the image") build_parser = subparsers.add_parser("build", help="Build the image")
build_parser.add_argument("--push", help="Also push the images", action="store_true") build_parser.add_argument("--push", help="Also push the images", action="store_true")
build_parser.add_argument("--load", help="Load the docker image locally", action="store_true")
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
@ -132,6 +133,8 @@ def main():
cmd += ["--tag", img] cmd += ["--tag", img]
if args.push: if args.push:
cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"] cmd += ["--push", "--cache-to", f"type=registry,ref={cache_img},mode=max"]
if args.load:
cmd += ["--load"]
run_command(*cmd, ".") run_command(*cmd, ".")
elif args.command == "manifest": elif args.command == "manifest":

View file

@ -8,6 +8,23 @@ import sys
config = configparser.ConfigParser(inline_comment_prefixes=(';', )) config = configparser.ConfigParser(inline_comment_prefixes=(';', ))
config.read(sys.argv[1]) 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]) subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])

View file

@ -18,6 +18,7 @@ from esphome.const import (
CONF_PORT, CONF_PORT,
CONF_ESPHOME, CONF_ESPHOME,
CONF_PLATFORMIO_OPTIONS, CONF_PLATFORMIO_OPTIONS,
SECRETS_FILES,
) )
from esphome.core import CORE, EsphomeError, coroutine from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent from esphome.helpers import indent
@ -144,6 +145,8 @@ def wrap_to_code(name, comp):
if comp.config_schema is not None: if comp.config_schema is not None:
conf_str = yaml_util.dump(conf) conf_str = yaml_util.dump(conf)
conf_str = conf_str.replace("//", "") conf_str = conf_str.replace("//", "")
# remove tailing \ to avoid multi-line comment warning
conf_str = conf_str.replace("\\\n", "\n")
cg.add(cg.LineComment(indent(conf_str))) cg.add(cg.LineComment(indent(conf_str)))
await coro(conf) await coro(conf)
@ -180,7 +183,11 @@ def compile_program(args, config):
from esphome import platformio_api from esphome import platformio_api
_LOGGER.info("Compiling app...") _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): def upload_using_esptool(config, port):
@ -196,8 +203,7 @@ def upload_using_esptool(config, port):
firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
flash_images = [ flash_images = [
platformio_api.FlashImage( platformio_api.FlashImage(
path=idedata.firmware_bin_path, path=idedata.firmware_bin_path, offset=firmware_offset
offset=firmware_offset,
), ),
*idedata.extra_flash_images, *idedata.extra_flash_images,
] ]
@ -222,6 +228,8 @@ def upload_using_esptool(config, port):
mcu, mcu,
"write_flash", "write_flash",
"-z", "-z",
"--flash_size",
"detect",
] ]
for img in flash_images: for img in flash_images:
cmd += [img.offset, img.path] cmd += [img.offset, img.path]
@ -458,6 +466,21 @@ def command_update_all(args):
return failed 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 = { PRE_CONFIG_ACTIONS = {
"wizard": command_wizard, "wizard": command_wizard,
"version": command_version, "version": command_version,
@ -475,6 +498,7 @@ POST_CONFIG_ACTIONS = {
"clean-mqtt": command_clean_mqtt, "clean-mqtt": command_clean_mqtt,
"mqtt-fingerprint": command_mqtt_fingerprint, "mqtt-fingerprint": command_mqtt_fingerprint,
"clean": command_clean, "clean": command_clean,
"idedata": command_idedata,
} }
@ -585,10 +609,7 @@ def parse_args(argv):
"wizard", "wizard",
help="A helpful setup wizard that will guide you through setting up ESPHome.", help="A helpful setup wizard that will guide you through setting up ESPHome.",
) )
parser_wizard.add_argument( parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
"configuration",
help="Your YAML configuration file.",
)
parser_fingerprint = subparsers.add_parser( parser_fingerprint = subparsers.add_parser(
"mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker." "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
@ -610,8 +631,7 @@ def parse_args(argv):
"dashboard", help="Create a simple web server for a dashboard." "dashboard", help="Create a simple web server for a dashboard."
) )
parser_dashboard.add_argument( parser_dashboard.add_argument(
"configuration", "configuration", help="Your YAML configuration file directory."
help="Your YAML configuration file directory.",
) )
parser_dashboard.add_argument( parser_dashboard.add_argument(
"--port", "--port",
@ -619,6 +639,12 @@ def parse_args(argv):
type=int, type=int,
default=6052, default=6052,
) )
parser_dashboard.add_argument(
"--address",
help="The address to bind to.",
type=str,
default="0.0.0.0",
)
parser_dashboard.add_argument( parser_dashboard.add_argument(
"--username", "--username",
help="The optional username to require for authentication.", help="The optional username to require for authentication.",
@ -650,6 +676,11 @@ def parse_args(argv):
"configuration", help="Your YAML configuration file directories.", nargs="+" "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 # Keep backward compatibility with the old command line format of
# esphome <config> <command>. # esphome <config> <command>.
# #
@ -733,7 +764,12 @@ def run_esphome(argv):
args = parse_args(argv) args = parse_args(argv)
CORE.dashboard = args.dashboard 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": if args.deprecated_argv_suggestion is not None and args.command != "vscode":
_LOGGER.warning( _LOGGER.warning(
"Calling ESPHome with the configuration before the command is deprecated " "Calling ESPHome with the configuration before the command is deprecated "
@ -757,12 +793,16 @@ def run_esphome(argv):
return 1 return 1
for conf_path in args.configuration: 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.config_path = conf_path
CORE.dashboard = args.dashboard CORE.dashboard = args.dashboard
config = read_config(dict(args.substitution) if args.substitution else {}) config = read_config(dict(args.substitution) if args.substitution else {})
if config is None: if config is None:
return 1 return 2
CORE.config = config CORE.config = config
if args.command not in POST_CONFIG_ACTIONS: if args.command not in POST_CONFIG_ACTIONS:

View file

@ -3,6 +3,7 @@ import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_AUTOMATION_ID, CONF_AUTOMATION_ID,
CONF_CONDITION, CONF_CONDITION,
CONF_COUNT,
CONF_ELSE, CONF_ELSE,
CONF_ID, CONF_ID,
CONF_THEN, CONF_THEN,
@ -66,6 +67,7 @@ DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
IfAction = cg.esphome_ns.class_("IfAction", Action) IfAction = cg.esphome_ns.class_("IfAction", Action)
WhileAction = cg.esphome_ns.class_("WhileAction", Action) WhileAction = cg.esphome_ns.class_("WhileAction", Action)
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component) WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action) UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
Automation = cg.esphome_ns.class_("Automation") Automation = cg.esphome_ns.class_("Automation")
@ -241,6 +243,25 @@ async def while_action_to_code(config, action_id, template_arg, args):
return var 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): def validate_wait_until(value):
schema = cv.Schema( schema = cv.Schema(
{ {

View file

@ -75,10 +75,10 @@ from esphome.cpp_types import ( # noqa
optional, optional,
arduino_json_ns, arduino_json_ns,
JsonObject, JsonObject,
JsonObjectRef, JsonObjectConst,
JsonObjectConstRef,
Controller, Controller,
GPIOPin, GPIOPin,
InternalGPIOPin, InternalGPIOPin,
gpio_Flags, gpio_Flags,
EntityCategory,
) )

View file

@ -25,7 +25,7 @@ void AdalightLightEffect::stop() {
AddressableLightEffect::stop(); AddressableLightEffect::stop();
} }
int AdalightLightEffect::get_frame_size_(int led_count) const { unsigned int AdalightLightEffect::get_frame_size_(int led_count) const {
// 3 bytes: Ada // 3 bytes: Ada
// 2 bytes: LED count // 2 bytes: LED count
// 1 byte: checksum // 1 byte: checksum

View file

@ -25,7 +25,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U
CONSUMED, CONSUMED,
}; };
int get_frame_size_(int led_count) const; unsigned int get_frame_size_(int led_count) const;
void reset_frame_(light::AddressableLight &it); void reset_frame_(light::AddressableLight &it);
void blank_all_leds_(light::AddressableLight &it); void blank_all_leds_(light::AddressableLight &it);
Frame parse_frame_(light::AddressableLight &it); Frame parse_frame_(light::AddressableLight &it);

View file

@ -1,5 +1,6 @@
#include "adc_sensor.h" #include "adc_sensor.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP8266 #ifdef USE_ESP8266
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
@ -15,50 +16,6 @@ namespace adc {
static const char *const TAG = "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() { void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC #ifndef USE_ADC_SENSOR_VCC
@ -66,13 +23,36 @@ void ADCSensor::setup() {
#endif #endif
#ifdef USE_ESP32 #ifdef USE_ESP32
adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_);
adc1_config_width(ADC_WIDTH_BIT_12); adc1_config_width(ADC_WIDTH_BIT_12);
#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 if (!autorange_) {
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_->get_pin())); adc1_config_channel_atten(channel_, attenuation_);
#endif }
// 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
#endif // USE_ESP32
} }
void ADCSensor::dump_config() { void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this); LOG_SENSOR("", "ADC Sensor", this);
#ifdef USE_ESP8266 #ifdef USE_ESP8266
@ -81,84 +61,107 @@ void ADCSensor::dump_config() {
#else #else
LOG_PIN(" Pin: ", pin_); LOG_PIN(" Pin: ", pin_);
#endif #endif
#endif #endif // USE_ESP8266
#ifdef USE_ESP32 #ifdef USE_ESP32
LOG_PIN(" Pin: ", pin_); LOG_PIN(" Pin: ", pin_);
switch (this->attenuation_) { if (autorange_)
case ADC_ATTEN_DB_0: ESP_LOGCONFIG(TAG, " Attenuation: auto");
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); else
break; switch (this->attenuation_) {
case ADC_ATTEN_DB_2_5: case ADC_ATTEN_DB_0:
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
break; break;
case ADC_ATTEN_DB_6: case ADC_ATTEN_DB_2_5:
ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
break; break;
case ADC_ATTEN_DB_11: case ADC_ATTEN_DB_6:
ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
break; break;
default: // This is to satisfy the unused ADC_ATTEN_MAX case ADC_ATTEN_DB_11:
break; ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
} break;
#endif default: // This is to satisfy the unused ADC_ATTEN_MAX
break;
}
#endif // USE_ESP32
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
} }
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; } float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
void ADCSensor::update() { void ADCSensor::update() {
float value_v = this->sample(); float value_v = this->sample();
ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v); ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
this->publish_state(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 #ifdef USE_ESP8266
float ADCSensor::sample() {
#ifdef USE_ADC_SENSOR_VCC #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 #else
return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT int raw = analogRead(this->pin_->get_pin()); // NOLINT
#endif
#endif #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 #ifdef USE_ESP8266
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; } std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
#endif #endif

View file

@ -8,6 +8,7 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "driver/adc.h" #include "driver/adc.h"
#include <esp_adc_cal.h>
#endif #endif
namespace esphome { namespace esphome {
@ -17,7 +18,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
public: public:
#ifdef USE_ESP32 #ifdef USE_ESP32
/// Set the attenuation for this pin. Only available on the 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 #endif
/// Update adc values. /// Update adc values.
@ -28,6 +31,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
/// `HARDWARE_LATE` setup priority. /// `HARDWARE_LATE` setup priority.
float get_setup_priority() const override; float get_setup_priority() const override;
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
void set_output_raw(bool output_raw) { output_raw_ = output_raw; }
float sample() override; float sample() override;
#ifdef USE_ESP8266 #ifdef USE_ESP8266
@ -36,9 +40,13 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
protected: protected:
InternalGPIOPin *pin_; InternalGPIOPin *pin_;
bool output_raw_{false};
#ifdef USE_ESP32 #ifdef USE_ESP32
adc_atten_t attenuation_{ADC_ATTEN_DB_0}; adc_atten_t attenuation_{ADC_ATTEN_DB_0};
adc1_channel_t channel_{};
bool autorange_{false};
esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {};
#endif #endif
}; };

View file

@ -4,14 +4,24 @@ from esphome import pins
from esphome.components import sensor, voltage_sampler from esphome.components import sensor, voltage_sampler
from esphome.const import ( from esphome.const import (
CONF_ATTENUATION, CONF_ATTENUATION,
CONF_RAW,
CONF_ID, CONF_ID,
CONF_INPUT, CONF_INPUT,
CONF_NUMBER,
CONF_PIN, CONF_PIN,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_VOLT, UNIT_VOLT,
) )
from esphome.core import CORE 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"] AUTO_LOAD = ["voltage_sampler"]
@ -21,6 +31,62 @@ ATTENUATION_MODES = {
"2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
"6db": cg.global_ns.ADC_ATTEN_DB_6, "6db": cg.global_ns.ADC_ATTEN_DB_6,
"11db": cg.global_ns.ADC_ATTEN_DB_11, "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") return cv.only_on_esp8266("VCC")
if CORE.is_esp32: if CORE.is_esp32:
from esphome.components.esp32 import is_esp32c3
value = pins.internal_gpio_input_pin_number(value) value = pins.internal_gpio_input_pin_number(value)
if is_esp32c3(): variant = get_esp32_variant()
if not (0 <= value <= 4): # ADC1 if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.") raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
if not (32 <= value <= 39): # ADC1
raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.") if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
elif CORE.is_esp8266: 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 from esphome.components.esp8266.gpio import CONF_ANALOG
value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})( 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( return pins.gpio_pin_schema(
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
)(value) )(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") adc_ns = cg.esphome_ns.namespace("adc")
@ -60,7 +131,7 @@ ADCSensor = adc_ns.class_(
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
) )
CONFIG_SCHEMA = ( CONFIG_SCHEMA = cv.All(
sensor.sensor_schema( sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT, unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2, accuracy_decimals=2,
@ -71,12 +142,14 @@ CONFIG_SCHEMA = (
{ {
cv.GenerateID(): cv.declare_id(ADCSensor), cv.GenerateID(): cv.declare_id(ADCSensor),
cv.Required(CONF_PIN): validate_adc_pin, cv.Required(CONF_PIN): validate_adc_pin,
cv.Optional(CONF_RAW, default=False): cv.boolean,
cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All( cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True) 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]) pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(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: 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))

View file

@ -76,9 +76,9 @@ class ADE7953 : public i2c::I2CDevice, public PollingComponent {
return err; return err;
*value = 0; *value = 0;
*value |= ((uint32_t) recv[0]) << 24; *value |= ((uint32_t) recv[0]) << 24;
*value |= ((uint32_t) recv[1]) << 24; *value |= ((uint32_t) recv[1]) << 16;
*value |= ((uint32_t) recv[2]) << 24; *value |= ((uint32_t) recv[2]) << 8;
*value |= ((uint32_t) recv[3]) << 24; *value |= ((uint32_t) recv[3]);
return i2c::ERROR_OK; return i2c::ERROR_OK;
} }

View file

@ -73,13 +73,6 @@ void AHT10Component::update() {
bool success = false; bool success = false;
for (int i = 0; i < AHT10_ATTEMPTS; ++i) { for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
delay_microseconds_accurate(4);
uint8_t reg = 0;
if (this->write(&reg, 1) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
continue;
}
delay(delay_ms); delay(delay_ms);
if (this->read(data, 6) != i2c::ERROR_OK) { if (this->read(data, 6) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
@ -117,12 +110,12 @@ void AHT10Component::update() {
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0; float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
float humidity; float humidity;
if (raw_humidity == 0) { // unrealistic value if (raw_humidity == 0) { // unrealistic value
humidity = NAN; humidity = NAN;
} else { } else {
humidity = (float) raw_humidity * 100.0 / 1048576.0; humidity = (float) raw_humidity * 100.0f / 1048576.0f;
} }
if (this->temperature_sensor_ != nullptr) { if (this->temperature_sensor_ != nullptr) {

View file

@ -38,9 +38,9 @@ void AM2320Component::update() {
return; return;
} }
float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0; float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0f;
temperature = (data[4] & 0x80) ? -temperature : temperature; temperature = (data[4] & 0x80) ? -temperature : temperature;
float humidity = ((data[2] << 8) + data[3]) / 10.0; float humidity = ((data[2] << 8) + data[3]) / 10.0f;
ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity); ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity);
if (this->temperature_sensor_ != nullptr) if (this->temperature_sensor_ != nullptr)

View file

@ -4,7 +4,8 @@ from esphome.components import sensor, ble_client
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
CONF_BATTERY_LEVEL, CONF_BATTERY_LEVEL,
ICON_BATTERY, DEVICE_CLASS_BATTERY,
ENTITY_CATEGORY_DIAGNOSTIC,
CONF_ILLUMINANCE, CONF_ILLUMINANCE,
ICON_BRIGHTNESS_5, ICON_BRIGHTNESS_5,
UNIT_PERCENT, UNIT_PERCENT,
@ -20,10 +21,15 @@ CONFIG_SCHEMA = (
{ {
cv.GenerateID(): cv.declare_id(Am43), cv.GenerateID(): cv.declare_id(Am43),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( 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( 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,
), ),
} }
) )

View file

@ -44,8 +44,9 @@ async def to_code(config):
width, height = image.size width, height = image.size
frames = image.n_frames frames = image.n_frames
if CONF_RESIZE in config: if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE]) new_width_max, new_height_max = config[CONF_RESIZE]
width, height = image.size ratio = min(new_width_max / width, new_height_max / height)
width, height = int(width * ratio), int(height * ratio)
else: else:
if width > 500 or height > 500: if width > 500 or height > 500:
_LOGGER.warning( _LOGGER.warning(
@ -59,7 +60,13 @@ async def to_code(config):
for frameIndex in range(frames): for frameIndex in range(frames):
image.seek(frameIndex) image.seek(frameIndex)
frame = image.convert("L", dither=Image.NONE) frame = image.convert("L", dither=Image.NONE)
if CONF_RESIZE in config:
frame = frame.resize([width, height])
pixels = list(frame.getdata()) 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: for pix in pixels:
data[pos] = pix data[pos] = pix
pos += 1 pos += 1
@ -70,7 +77,13 @@ async def to_code(config):
for frameIndex in range(frames): for frameIndex in range(frames):
image.seek(frameIndex) image.seek(frameIndex)
frame = image.convert("RGB") frame = image.convert("RGB")
if CONF_RESIZE in config:
frame = frame.resize([width, height])
pixels = list(frame.getdata()) 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: for pix in pixels:
data[pos] = pix[0] data[pos] = pix[0]
pos += 1 pos += 1
@ -85,6 +98,8 @@ async def to_code(config):
for frameIndex in range(frames): for frameIndex in range(frames):
image.seek(frameIndex) image.seek(frameIndex)
frame = image.convert("1", dither=Image.NONE) frame = image.convert("1", dither=Image.NONE)
if CONF_RESIZE in config:
frame = frame.resize([width, height])
for y in range(height): for y in range(height):
for x in range(width): for x in range(width):
if frame.getpixel((x, y)): if frame.getpixel((x, y)):

View file

@ -30,7 +30,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
climate::ClimateTraits traits() override { climate::ClimateTraits traits() override {
auto traits = climate::ClimateTraits(); auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true); traits.set_supports_current_temperature(true);
traits.set_supports_heat_mode(true); traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::ClimateMode::CLIMATE_MODE_HEAT});
traits.set_visual_min_temperature(25.0); traits.set_visual_min_temperature(25.0);
traits.set_visual_max_temperature(100.0); traits.set_visual_max_temperature(100.0);
traits.set_visual_temperature_step(0.1); traits.set_visual_temperature_step(0.1);

View file

@ -73,51 +73,46 @@ AnovaPacket *AnovaCodec::get_stop_request() {
} }
void AnovaCodec::decode(const uint8_t *data, uint16_t length) { void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
memset(this->buf_, 0, 32); char buf[32];
strncpy(this->buf_, (char *) data, length); memset(buf, 0, sizeof(buf));
strncpy(buf, (char *) data, std::min<uint16_t>(length, sizeof(buf) - 1));
this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false; this->has_target_temp_ = this->has_current_temp_ = this->has_unit_ = this->has_running_ = false;
switch (this->current_query_) { switch (this->current_query_) {
case READ_DEVICE_STATUS: { case READ_DEVICE_STATUS: {
if (!strncmp(this->buf_, "stopped", 7)) { if (!strncmp(buf, "stopped", 7)) {
this->has_running_ = true; this->has_running_ = true;
this->running_ = false; this->running_ = false;
} }
if (!strncmp(this->buf_, "running", 7)) { if (!strncmp(buf, "running", 7)) {
this->has_running_ = true; this->has_running_ = true;
this->running_ = true; this->running_ = true;
} }
break; break;
} }
case START: { case START: {
if (!strncmp(this->buf_, "start", 5)) { if (!strncmp(buf, "start", 5)) {
this->has_running_ = true; this->has_running_ = true;
this->running_ = true; this->running_ = true;
} }
break; break;
} }
case STOP: { case STOP: {
if (!strncmp(this->buf_, "stop", 4)) { if (!strncmp(buf, "stop", 4)) {
this->has_running_ = true; this->has_running_ = true;
this->running_ = false; this->running_ = false;
} }
break; break;
} }
case READ_TARGET_TEMPERATURE: { case READ_TARGET_TEMPERATURE:
this->target_temp_ = strtof(this->buf_, nullptr);
if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true;
break;
}
case SET_TARGET_TEMPERATURE: { case SET_TARGET_TEMPERATURE: {
this->target_temp_ = strtof(this->buf_, nullptr); this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f);
if (this->fahrenheit_) if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_); this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true; this->has_target_temp_ = true;
break; break;
} }
case READ_CURRENT_TEMPERATURE: { case READ_CURRENT_TEMPERATURE: {
this->current_temp_ = strtof(this->buf_, nullptr); this->current_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f);
if (this->fahrenheit_) if (this->fahrenheit_)
this->current_temp_ = ftoc(this->current_temp_); this->current_temp_ = ftoc(this->current_temp_);
this->has_current_temp_ = true; this->has_current_temp_ = true;
@ -125,8 +120,8 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
} }
case SET_UNIT: case SET_UNIT:
case READ_UNIT: { case READ_UNIT: {
this->unit_ = this->buf_[0]; this->unit_ = buf[0];
this->fahrenheit_ = this->buf_[0] == 'f'; this->fahrenheit_ = buf[0] == 'f';
this->has_unit_ = true; this->has_unit_ = true;
break; break;
} }

View file

@ -70,7 +70,6 @@ class AnovaCodec {
bool has_current_temp_; bool has_current_temp_;
bool has_unit_; bool has_unit_;
bool has_running_; bool has_running_;
char buf_[32];
bool fahrenheit_; bool fahrenheit_;
CurrentQuery current_query_; CurrentQuery current_query_;

View file

@ -121,7 +121,7 @@ async def to_code(config):
decoded = base64.b64decode(conf[CONF_KEY]) decoded = base64.b64decode(conf[CONF_KEY])
cg.add(var.set_noise_psk(list(decoded))) cg.add(var.set_noise_psk(list(decoded)))
cg.add_define("USE_API_NOISE") 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: else:
cg.add_define("USE_API_PLAINTEXT") cg.add_define("USE_API_PLAINTEXT")

View file

@ -40,6 +40,7 @@ service APIConnection {
rpc climate_command (ClimateCommandRequest) returns (void) {} rpc climate_command (ClimateCommandRequest) returns (void) {}
rpc number_command (NumberCommandRequest) returns (void) {} rpc number_command (NumberCommandRequest) returns (void) {}
rpc select_command (SelectCommandRequest) returns (void) {} rpc select_command (SelectCommandRequest) returns (void) {}
rpc button_command (ButtonCommandRequest) returns (void) {}
} }
@ -182,6 +183,8 @@ message DeviceInfoResponse {
// The esphome project details if set // The esphome project details if set
string project_name = 8; string project_name = 8;
string project_version = 9; string project_version = 9;
uint32 webserver_port = 10;
} }
message ListEntitiesRequest { message ListEntitiesRequest {
@ -201,6 +204,14 @@ message SubscribeStatesRequest {
// Empty // Empty
} }
// ==================== COMMON =====================
enum EntityCategory {
ENTITY_CATEGORY_NONE = 0;
ENTITY_CATEGORY_CONFIG = 1;
ENTITY_CATEGORY_DIAGNOSTIC = 2;
}
// ==================== BINARY SENSOR ==================== // ==================== BINARY SENSOR ====================
message ListEntitiesBinarySensorResponse { message ListEntitiesBinarySensorResponse {
option (id) = 12; option (id) = 12;
@ -216,6 +227,7 @@ message ListEntitiesBinarySensorResponse {
bool is_status_binary_sensor = 6; bool is_status_binary_sensor = 6;
bool disabled_by_default = 7; bool disabled_by_default = 7;
string icon = 8; string icon = 8;
EntityCategory entity_category = 9;
} }
message BinarySensorStateResponse { message BinarySensorStateResponse {
option (id) = 21; option (id) = 21;
@ -247,6 +259,7 @@ message ListEntitiesCoverResponse {
string device_class = 8; string device_class = 8;
bool disabled_by_default = 9; bool disabled_by_default = 9;
string icon = 10; string icon = 10;
EntityCategory entity_category = 11;
} }
enum LegacyCoverState { enum LegacyCoverState {
@ -316,6 +329,7 @@ message ListEntitiesFanResponse {
int32 supported_speed_count = 8; int32 supported_speed_count = 8;
bool disabled_by_default = 9; bool disabled_by_default = 9;
string icon = 10; string icon = 10;
EntityCategory entity_category = 11;
} }
enum FanSpeed { enum FanSpeed {
FAN_SPEED_LOW = 0; FAN_SPEED_LOW = 0;
@ -392,6 +406,7 @@ message ListEntitiesLightResponse {
repeated string effects = 11; repeated string effects = 11;
bool disabled_by_default = 13; bool disabled_by_default = 13;
string icon = 14; string icon = 14;
EntityCategory entity_category = 15;
} }
message LightStateResponse { message LightStateResponse {
option (id) = 24; option (id) = 24;
@ -480,6 +495,7 @@ message ListEntitiesSensorResponse {
// Last reset type removed in 2021.9.0 // Last reset type removed in 2021.9.0
SensorLastResetType legacy_last_reset_type = 11; SensorLastResetType legacy_last_reset_type = 11;
bool disabled_by_default = 12; bool disabled_by_default = 12;
EntityCategory entity_category = 13;
} }
message SensorStateResponse { message SensorStateResponse {
option (id) = 25; option (id) = 25;
@ -508,6 +524,7 @@ message ListEntitiesSwitchResponse {
string icon = 5; string icon = 5;
bool assumed_state = 6; bool assumed_state = 6;
bool disabled_by_default = 7; bool disabled_by_default = 7;
EntityCategory entity_category = 8;
} }
message SwitchStateResponse { message SwitchStateResponse {
option (id) = 26; option (id) = 26;
@ -541,6 +558,7 @@ message ListEntitiesTextSensorResponse {
string icon = 5; string icon = 5;
bool disabled_by_default = 6; bool disabled_by_default = 6;
EntityCategory entity_category = 7;
} }
message TextSensorStateResponse { message TextSensorStateResponse {
option (id) = 27; option (id) = 27;
@ -701,6 +719,8 @@ message ListEntitiesCameraResponse {
string name = 3; string name = 3;
string unique_id = 4; string unique_id = 4;
bool disabled_by_default = 5; bool disabled_by_default = 5;
string icon = 6;
EntityCategory entity_category = 7;
} }
message CameraImageResponse { message CameraImageResponse {
@ -795,6 +815,7 @@ message ListEntitiesClimateResponse {
repeated string supported_custom_presets = 17; repeated string supported_custom_presets = 17;
bool disabled_by_default = 18; bool disabled_by_default = 18;
string icon = 19; string icon = 19;
EntityCategory entity_category = 20;
} }
message ClimateStateResponse { message ClimateStateResponse {
option (id) = 47; option (id) = 47;
@ -848,6 +869,11 @@ message ClimateCommandRequest {
} }
// ==================== NUMBER ==================== // ==================== NUMBER ====================
enum NumberMode {
NUMBER_MODE_AUTO = 0;
NUMBER_MODE_BOX = 1;
NUMBER_MODE_SLIDER = 2;
}
message ListEntitiesNumberResponse { message ListEntitiesNumberResponse {
option (id) = 49; option (id) = 49;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
@ -863,6 +889,9 @@ message ListEntitiesNumberResponse {
float max_value = 7; float max_value = 7;
float step = 8; float step = 8;
bool disabled_by_default = 9; bool disabled_by_default = 9;
EntityCategory entity_category = 10;
string unit_of_measurement = 11;
NumberMode mode = 12;
} }
message NumberStateResponse { message NumberStateResponse {
option (id) = 50; option (id) = 50;
@ -900,6 +929,7 @@ message ListEntitiesSelectResponse {
string icon = 5; string icon = 5;
repeated string options = 6; repeated string options = 6;
bool disabled_by_default = 7; bool disabled_by_default = 7;
EntityCategory entity_category = 8;
} }
message SelectStateResponse { message SelectStateResponse {
option (id) = 53; option (id) = 53;
@ -922,3 +952,28 @@ message SelectCommandRequest {
fixed32 key = 1; fixed32 key = 1;
string state = 2; string state = 2;
} }
// ==================== BUTTON ====================
message ListEntitiesButtonResponse {
option (id) = 61;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BUTTON";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
string device_class = 8;
}
message ButtonCommandRequest {
option (id) = 62;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BUTTON";
option (no_delay) = true;
fixed32 key = 1;
}

View file

@ -20,6 +20,7 @@ namespace esphome {
namespace api { namespace api {
static const char *const TAG = "api.connection"; static const char *const TAG = "api.connection";
static const int ESP32_CAMERA_STOP_STREAM = 5000;
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
: parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { : parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
@ -78,6 +79,8 @@ void APIConnection::loop() {
on_fatal_error(); on_fatal_error();
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str()); 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 { } else {
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
} }
@ -130,7 +133,7 @@ void APIConnection::loop() {
if (state_subs_at_ != -1) { if (state_subs_at_ != -1) {
const auto &subs = this->parent_->get_state_subs(); const auto &subs = this->parent_->get_state_subs();
if (state_subs_at_ >= subs.size()) { if (state_subs_at_ >= (int) subs.size()) {
state_subs_at_ = -1; state_subs_at_ = -1;
} else { } else {
auto &it = subs[state_subs_at_]; auto &it = subs[state_subs_at_];
@ -182,6 +185,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
msg.disabled_by_default = binary_sensor->is_disabled_by_default(); msg.disabled_by_default = binary_sensor->is_disabled_by_default();
msg.icon = binary_sensor->get_icon(); msg.icon = binary_sensor->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(binary_sensor->get_entity_category());
return this->send_list_entities_binary_sensor_response(msg); return this->send_list_entities_binary_sensor_response(msg);
} }
#endif #endif
@ -215,6 +219,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
msg.device_class = cover->get_device_class(); msg.device_class = cover->get_device_class();
msg.disabled_by_default = cover->is_disabled_by_default(); msg.disabled_by_default = cover->is_disabled_by_default();
msg.icon = cover->get_icon(); msg.icon = cover->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(cover->get_entity_category());
return this->send_list_entities_cover_response(msg); return this->send_list_entities_cover_response(msg);
} }
void APIConnection::cover_command(const CoverCommandRequest &msg) { void APIConnection::cover_command(const CoverCommandRequest &msg) {
@ -281,6 +286,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
msg.supported_speed_count = traits.supported_speed_count(); msg.supported_speed_count = traits.supported_speed_count();
msg.disabled_by_default = fan->is_disabled_by_default(); msg.disabled_by_default = fan->is_disabled_by_default();
msg.icon = fan->get_icon(); msg.icon = fan->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(fan->get_entity_category());
return this->send_list_entities_fan_response(msg); return this->send_list_entities_fan_response(msg);
} }
void APIConnection::fan_command(const FanCommandRequest &msg) { void APIConnection::fan_command(const FanCommandRequest &msg) {
@ -344,6 +350,7 @@ bool APIConnection::send_light_info(light::LightState *light) {
msg.disabled_by_default = light->is_disabled_by_default(); msg.disabled_by_default = light->is_disabled_by_default();
msg.icon = light->get_icon(); msg.icon = light->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(light->get_entity_category());
for (auto mode : traits.get_supported_color_modes()) for (auto mode : traits.get_supported_color_modes())
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode)); msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
@ -430,7 +437,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
msg.device_class = sensor->get_device_class(); msg.device_class = sensor->get_device_class();
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class()); msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
msg.disabled_by_default = sensor->is_disabled_by_default(); msg.disabled_by_default = sensor->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(sensor->get_entity_category());
return this->send_list_entities_sensor_response(msg); return this->send_list_entities_sensor_response(msg);
} }
#endif #endif
@ -454,6 +461,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
msg.icon = a_switch->get_icon(); msg.icon = a_switch->get_icon();
msg.assumed_state = a_switch->assumed_state(); msg.assumed_state = a_switch->assumed_state();
msg.disabled_by_default = a_switch->is_disabled_by_default(); 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); return this->send_list_entities_switch_response(msg);
} }
void APIConnection::switch_command(const SwitchCommandRequest &msg) { void APIConnection::switch_command(const SwitchCommandRequest &msg) {
@ -489,6 +497,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
msg.unique_id = get_default_unique_id("text_sensor", text_sensor); msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
msg.icon = text_sensor->get_icon(); msg.icon = text_sensor->get_icon();
msg.disabled_by_default = text_sensor->is_disabled_by_default(); 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); return this->send_list_entities_text_sensor_response(msg);
} }
#endif #endif
@ -535,6 +544,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
msg.disabled_by_default = climate->is_disabled_by_default(); msg.disabled_by_default = climate->is_disabled_by_default();
msg.icon = climate->get_icon(); 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_current_temperature = traits.get_supports_current_temperature();
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
@ -609,6 +619,9 @@ bool APIConnection::send_number_info(number::Number *number) {
msg.unique_id = get_default_unique_id("number", number); msg.unique_id = get_default_unique_id("number", number);
msg.icon = number->get_icon(); msg.icon = number->get_icon();
msg.disabled_by_default = number->is_disabled_by_default(); msg.disabled_by_default = number->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category());
msg.unit_of_measurement = number->traits.get_unit_of_measurement();
msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
msg.min_value = number->traits.get_min_value(); msg.min_value = number->traits.get_min_value();
msg.max_value = number->traits.get_max_value(); msg.max_value = number->traits.get_max_value();
@ -646,6 +659,7 @@ bool APIConnection::send_select_info(select::Select *select) {
msg.unique_id = get_default_unique_id("select", select); msg.unique_id = get_default_unique_id("select", select);
msg.icon = select->get_icon(); msg.icon = select->get_icon();
msg.disabled_by_default = select->is_disabled_by_default(); 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()) for (const auto &option : select->traits.get_options())
msg.options.push_back(option); msg.options.push_back(option);
@ -663,13 +677,37 @@ void APIConnection::select_command(const SelectCommandRequest &msg) {
} }
#endif #endif
#ifdef USE_BUTTON
bool APIConnection::send_button_info(button::Button *button) {
ListEntitiesButtonResponse msg;
msg.key = button->get_object_id_hash();
msg.object_id = button->get_object_id();
msg.name = button->get_name();
msg.unique_id = get_default_unique_id("button", button);
msg.icon = button->get_icon();
msg.disabled_by_default = button->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(button->get_entity_category());
msg.device_class = button->get_device_class();
return this->send_list_entities_button_response(msg);
}
void APIConnection::button_command(const ButtonCommandRequest &msg) {
button::Button *button = App.get_button_by_key(msg.key);
if (button == nullptr)
return;
button->press();
}
#endif
#ifdef USE_ESP32_CAMERA #ifdef USE_ESP32_CAMERA
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
if (!this->state_subscription_) if (!this->state_subscription_)
return; return;
if (this->image_reader_.available()) if (this->image_reader_.available())
return; return;
this->image_reader_.set_image(std::move(image)); if (image->was_requested_by(esphome::esp32_camera::API_REQUESTER) ||
image->was_requested_by(esphome::esp32_camera::IDLE))
this->image_reader_.set_image(std::move(image));
} }
bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) { bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
ListEntitiesCameraResponse msg; ListEntitiesCameraResponse msg;
@ -678,6 +716,8 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
msg.name = camera->get_name(); msg.name = camera->get_name();
msg.unique_id = get_default_unique_id("camera", camera); msg.unique_id = get_default_unique_id("camera", camera);
msg.disabled_by_default = camera->is_disabled_by_default(); msg.disabled_by_default = camera->is_disabled_by_default();
msg.icon = camera->get_icon();
msg.entity_category = static_cast<enums::EntityCategory>(camera->get_entity_category());
return this->send_list_entities_camera_response(msg); return this->send_list_entities_camera_response(msg);
} }
void APIConnection::camera_image(const CameraImageRequest &msg) { void APIConnection::camera_image(const CameraImageRequest &msg) {
@ -685,9 +725,14 @@ void APIConnection::camera_image(const CameraImageRequest &msg) {
return; return;
if (msg.single) if (msg.single)
esp32_camera::global_esp32_camera->request_image(); esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::API_REQUESTER);
if (msg.stream) if (msg.stream) {
esp32_camera::global_esp32_camera->request_stream(); esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::API_REQUESTER);
App.scheduler.set_timeout(this->parent_, "api_esp32_camera_stop_stream", ESP32_CAMERA_STOP_STREAM, []() {
esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::API_REQUESTER);
});
}
} }
#endif #endif
@ -756,6 +801,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
#ifdef ESPHOME_PROJECT_NAME #ifdef ESPHOME_PROJECT_NAME
resp.project_name = ESPHOME_PROJECT_NAME; resp.project_name = ESPHOME_PROJECT_NAME;
resp.project_version = ESPHOME_PROJECT_VERSION; resp.project_version = ESPHOME_PROJECT_VERSION;
#endif
#ifdef USE_WEBSERVER
resp.webserver_port = WEBSERVER_PORT;
#endif #endif
return resp; return resp;
} }

View file

@ -73,6 +73,10 @@ class APIConnection : public APIServerConnection {
bool send_select_state(select::Select *select, std::string state); bool send_select_state(select::Select *select, std::string state);
bool send_select_info(select::Select *select); bool send_select_info(select::Select *select);
void select_command(const SelectCommandRequest &msg) override; void select_command(const SelectCommandRequest &msg) override;
#endif
#ifdef USE_BUTTON
bool send_button_info(button::Button *button);
void button_command(const ButtonCommandRequest &msg) override;
#endif #endif
bool send_log_message(int level, const char *tag, const char *line); bool send_log_message(int level, const char *tag, const char *line);
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {

View file

@ -1,6 +1,7 @@
#include "api_frame_helper.h" #include "api_frame_helper.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "proto.h" #include "proto.h"
#include <cstring> #include <cstring>
@ -10,7 +11,7 @@ namespace api {
static const char *const TAG = "api.socket"; 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) { bool is_would_block(ssize_t ret) {
if (ret == -1) { if (ret == -1) {
return errno == EWOULDBLOCK || errno == EAGAIN; return errno == EWOULDBLOCK || errno == EAGAIN;
@ -64,6 +65,8 @@ const char *api_error_to_str(APIError err) {
return "HANDSHAKESTATE_SPLIT_FAILED"; return "HANDSHAKESTATE_SPLIT_FAILED";
} else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) { } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
return "BAD_HANDSHAKE_ERROR_BYTE"; return "BAD_HANDSHAKE_ERROR_BYTE";
} else if (err == APIError::CONNECTION_CLOSED) {
return "CONNECTION_CLOSED";
} }
return "UNKNOWN"; return "UNKNOWN";
} }
@ -172,9 +175,6 @@ APIError APINoiseFrameHelper::loop() {
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
*/ */
APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
int err;
APIError aerr;
if (frame == nullptr) { if (frame == nullptr) {
HELPER_LOG("Bad argument for try_read_frame_"); HELPER_LOG("Bad argument for try_read_frame_");
return APIError::BAD_ARG; return APIError::BAD_ARG;
@ -185,15 +185,20 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// no header information yet // no header information yet
size_t to_read = 3 - rx_header_buf_len_; size_t to_read = 3 - rx_header_buf_len_;
ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read); ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
if (is_would_block(received)) { if (received == -1) {
return APIError::WOULD_BLOCK; if (errno == EWOULDBLOCK || errno == EAGAIN) {
} else if (received == -1) { return APIError::WOULD_BLOCK;
}
state_ = State::FAILED; state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno); HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED; 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; rx_header_buf_len_ += received;
if (received != to_read) { if ((size_t) received != to_read) {
// not a full read // not a full read
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
@ -227,15 +232,20 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// more data to read // more data to read
size_t to_read = msg_size - rx_buf_len_; size_t to_read = msg_size - rx_buf_len_;
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (is_would_block(received)) { if (received == -1) {
return APIError::WOULD_BLOCK; if (errno == EWOULDBLOCK || errno == EAGAIN) {
} else if (received == -1) { return APIError::WOULD_BLOCK;
}
state_ = State::FAILED; state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno); HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED; return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
} }
rx_buf_len_ += received; rx_buf_len_ += received;
if (received != to_read) { if ((size_t) received != to_read) {
// not all read // not all read
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
@ -243,7 +253,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// uncomment for even more debugging // uncomment for even more debugging
#ifdef HELPER_LOG_PACKETS #ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
#endif #endif
frame->msg = std::move(rx_buf_); frame->msg = std::move(rx_buf_);
// consume msg // consume msg
@ -532,13 +542,13 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() {
APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
if (iovcnt == 0) if (iovcnt == 0)
return APIError::OK; return APIError::OK;
int err;
APIError aerr; APIError aerr;
size_t total_write_len = 0; size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) { for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS #ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str()); ESP_LOGVV(TAG, "Sending raw: %s",
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif #endif
total_write_len += iov[i].iov_len; total_write_len += iov[i].iov_len;
} }
@ -572,7 +582,7 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
state_ = State::FAILED; state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno); HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED; return APIError::SOCKET_WRITE_FAILED;
} else if (sent != total_write_len) { } else if ((size_t) sent != total_write_len) {
// partially sent, add end to tx_buf // partially sent, add end to tx_buf
size_t to_consume = sent; size_t to_consume = sent;
for (int i = 0; i < iovcnt; i++) { for (int i = 0; i < iovcnt; i++) {
@ -712,7 +722,12 @@ APIError APINoiseFrameHelper::shutdown(int how) {
} }
extern "C" { extern "C" {
// declare how noise generates random bytes (here with a good HWRNG based on the RF system) // declare how noise generates random bytes (here with a good HWRNG based on the RF system)
void noise_rand_bytes(void *output, size_t len) { esphome::fill_random(reinterpret_cast<uint8_t *>(output), len); } void noise_rand_bytes(void *output, size_t len) {
if (!esphome::random_bytes(reinterpret_cast<uint8_t *>(output), len)) {
ESP_LOGE(TAG, "Failed to acquire random bytes, rebooting!");
arch_restart();
}
}
} }
#endif // USE_API_NOISE #endif // USE_API_NOISE
@ -766,9 +781,6 @@ APIError APIPlaintextFrameHelper::loop() {
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
*/ */
APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
int err;
APIError aerr;
if (frame == nullptr) { if (frame == nullptr) {
HELPER_LOG("Bad argument for try_read_frame_"); HELPER_LOG("Bad argument for try_read_frame_");
return APIError::BAD_ARG; return APIError::BAD_ARG;
@ -778,12 +790,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
while (!rx_header_parsed_) { while (!rx_header_parsed_) {
uint8_t data; uint8_t data;
ssize_t received = socket_->read(&data, 1); ssize_t received = socket_->read(&data, 1);
if (is_would_block(received)) { if (received == -1) {
return APIError::WOULD_BLOCK; if (errno == EWOULDBLOCK || errno == EAGAIN) {
} else if (received == -1) { return APIError::WOULD_BLOCK;
}
state_ = State::FAILED; state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno); HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED; 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); rx_header_buf_.push_back(data);
@ -824,15 +841,20 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
// more data to read // more data to read
size_t to_read = rx_header_parsed_len_ - rx_buf_len_; size_t to_read = rx_header_parsed_len_ - rx_buf_len_;
ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read); ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
if (is_would_block(received)) { if (received == -1) {
return APIError::WOULD_BLOCK; if (errno == EWOULDBLOCK || errno == EAGAIN) {
} else if (received == -1) { return APIError::WOULD_BLOCK;
}
state_ = State::FAILED; state_ = State::FAILED;
HELPER_LOG("Socket read failed with errno %d", errno); HELPER_LOG("Socket read failed with errno %d", errno);
return APIError::SOCKET_READ_FAILED; return APIError::SOCKET_READ_FAILED;
} else if (received == 0) {
state_ = State::FAILED;
HELPER_LOG("Connection closed");
return APIError::CONNECTION_CLOSED;
} }
rx_buf_len_ += received; rx_buf_len_ += received;
if (received != to_read) { if ((size_t) received != to_read) {
// not all read // not all read
return APIError::WOULD_BLOCK; return APIError::WOULD_BLOCK;
} }
@ -840,7 +862,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
// uncomment for even more debugging // uncomment for even more debugging
#ifdef HELPER_LOG_PACKETS #ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
#endif #endif
frame->msg = std::move(rx_buf_); frame->msg = std::move(rx_buf_);
// consume msg // consume msg
@ -852,7 +874,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
} }
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
int err;
APIError aerr; APIError aerr;
if (state_ != State::DATA) { if (state_ != State::DATA) {
@ -872,9 +893,6 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
} }
bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) { APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
int err;
APIError aerr;
if (state_ != State::DATA) { if (state_ != State::DATA) {
return APIError::BAD_STATE; return APIError::BAD_STATE;
} }
@ -918,13 +936,13 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
if (iovcnt == 0) if (iovcnt == 0)
return APIError::OK; return APIError::OK;
int err;
APIError aerr; APIError aerr;
size_t total_write_len = 0; size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) { for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS #ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str()); ESP_LOGVV(TAG, "Sending raw: %s",
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif #endif
total_write_len += iov[i].iov_len; total_write_len += iov[i].iov_len;
} }
@ -958,7 +976,7 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt
state_ = State::FAILED; state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno); HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED; return APIError::SOCKET_WRITE_FAILED;
} else if (sent != total_write_len) { } else if ((size_t) sent != total_write_len) {
// partially sent, add end to tx_buf // partially sent, add end to tx_buf
size_t to_consume = sent; size_t to_consume = sent;
for (int i = 0; i < iovcnt; i++) { for (int i = 0; i < iovcnt; i++) {

View file

@ -53,6 +53,7 @@ enum class APIError : int {
HANDSHAKESTATE_SETUP_FAILED = 1019, HANDSHAKESTATE_SETUP_FAILED = 1019,
HANDSHAKESTATE_SPLIT_FAILED = 1020, HANDSHAKESTATE_SPLIT_FAILED = 1020,
BAD_HANDSHAKE_ERROR_BYTE = 1021, BAD_HANDSHAKE_ERROR_BYTE = 1021,
CONNECTION_CLOSED = 1022,
}; };
const char *api_error_to_str(APIError err); const char *api_error_to_str(APIError err);

View file

@ -6,6 +6,18 @@
namespace esphome { namespace esphome {
namespace api { 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) { template<> const char *proto_enum_to_string<enums::LegacyCoverState>(enums::LegacyCoverState value) {
switch (value) { switch (value) {
case enums::LEGACY_COVER_STATE_OPEN: case enums::LEGACY_COVER_STATE_OPEN:
@ -254,6 +266,18 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate
return "UNKNOWN"; return "UNKNOWN";
} }
} }
template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) {
switch (value) {
case enums::NUMBER_MODE_AUTO:
return "NUMBER_MODE_AUTO";
case enums::NUMBER_MODE_BOX:
return "NUMBER_MODE_BOX";
case enums::NUMBER_MODE_SLIDER:
return "NUMBER_MODE_SLIDER";
default:
return "UNKNOWN";
}
}
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: { case 1: {
@ -267,7 +291,7 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value)
void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); } void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void HelloRequest::dump_to(std::string &out) const { void HelloRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("HelloRequest {\n"); out.append("HelloRequest {\n");
out.append(" client_info: "); out.append(" client_info: ");
out.append("'").append(this->client_info).append("'"); out.append("'").append(this->client_info).append("'");
@ -306,7 +330,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void HelloResponse::dump_to(std::string &out) const { void HelloResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("HelloResponse {\n"); out.append("HelloResponse {\n");
out.append(" api_version_major: "); out.append(" api_version_major: ");
sprintf(buffer, "%u", this->api_version_major); sprintf(buffer, "%u", this->api_version_major);
@ -337,7 +361,7 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value
void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); } void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ConnectRequest::dump_to(std::string &out) const { void ConnectRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ConnectRequest {\n"); out.append("ConnectRequest {\n");
out.append(" password: "); out.append(" password: ");
out.append("'").append(this->password).append("'"); out.append("'").append(this->password).append("'");
@ -358,7 +382,7 @@ bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ConnectResponse::dump_to(std::string &out) const { void ConnectResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ConnectResponse {\n"); out.append("ConnectResponse {\n");
out.append(" invalid_password: "); out.append(" invalid_password: ");
out.append(YESNO(this->invalid_password)); out.append(YESNO(this->invalid_password));
@ -396,6 +420,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->has_deep_sleep = value.as_bool(); this->has_deep_sleep = value.as_bool();
return true; return true;
} }
case 10: {
this->webserver_port = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -444,10 +472,11 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(7, this->has_deep_sleep); buffer.encode_bool(7, this->has_deep_sleep);
buffer.encode_string(8, this->project_name); buffer.encode_string(8, this->project_name);
buffer.encode_string(9, this->project_version); buffer.encode_string(9, this->project_version);
buffer.encode_uint32(10, this->webserver_port);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const { void DeviceInfoResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("DeviceInfoResponse {\n"); out.append("DeviceInfoResponse {\n");
out.append(" uses_password: "); out.append(" uses_password: ");
out.append(YESNO(this->uses_password)); out.append(YESNO(this->uses_password));
@ -484,6 +513,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append(" project_version: "); out.append(" project_version: ");
out.append("'").append(this->project_version).append("'"); out.append("'").append(this->project_version).append("'");
out.append("\n"); out.append("\n");
out.append(" webserver_port: ");
sprintf(buffer, "%u", this->webserver_port);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -509,6 +543,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 9: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -558,10 +596,11 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->is_status_binary_sensor); buffer.encode_bool(6, this->is_status_binary_sensor);
buffer.encode_bool(7, this->disabled_by_default); buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_string(8, this->icon); buffer.encode_string(8, this->icon);
buffer.encode_enum<enums::EntityCategory>(9, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesBinarySensorResponse {\n"); out.append("ListEntitiesBinarySensorResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -595,6 +634,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
out.append(" icon: "); out.append(" icon: ");
out.append("'").append(this->icon).append("'"); out.append("'").append(this->icon).append("'");
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -629,7 +672,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void BinarySensorStateResponse::dump_to(std::string &out) const { void BinarySensorStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("BinarySensorStateResponse {\n"); out.append("BinarySensorStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -664,6 +707,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 11: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -715,10 +762,11 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(8, this->device_class); buffer.encode_string(8, this->device_class);
buffer.encode_bool(9, this->disabled_by_default); buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_string(10, this->icon); buffer.encode_string(10, this->icon);
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCoverResponse::dump_to(std::string &out) const { void ListEntitiesCoverResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesCoverResponse {\n"); out.append("ListEntitiesCoverResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -760,6 +808,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
out.append(" icon: "); out.append(" icon: ");
out.append("'").append(this->icon).append("'"); out.append("'").append(this->icon).append("'");
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -804,7 +856,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void CoverStateResponse::dump_to(std::string &out) const { void CoverStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("CoverStateResponse {\n"); out.append("CoverStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -887,7 +939,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void CoverCommandRequest::dump_to(std::string &out) const { void CoverCommandRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("CoverCommandRequest {\n"); out.append("CoverCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -948,6 +1000,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 11: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -995,10 +1051,11 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_int32(8, this->supported_speed_count); buffer.encode_int32(8, this->supported_speed_count);
buffer.encode_bool(9, this->disabled_by_default); buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_string(10, this->icon); buffer.encode_string(10, this->icon);
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesFanResponse::dump_to(std::string &out) const { void ListEntitiesFanResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesFanResponse {\n"); out.append("ListEntitiesFanResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -1041,6 +1098,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
out.append(" icon: "); out.append(" icon: ");
out.append("'").append(this->icon).append("'"); out.append("'").append(this->icon).append("'");
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -1090,7 +1151,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void FanStateResponse::dump_to(std::string &out) const { void FanStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("FanStateResponse {\n"); out.append("FanStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -1191,7 +1252,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void FanCommandRequest::dump_to(std::string &out) const { void FanCommandRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("FanCommandRequest {\n"); out.append("FanCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -1267,6 +1328,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 15: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -1334,10 +1399,11 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
} }
buffer.encode_bool(13, this->disabled_by_default); buffer.encode_bool(13, this->disabled_by_default);
buffer.encode_string(14, this->icon); buffer.encode_string(14, this->icon);
buffer.encode_enum<enums::EntityCategory>(15, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesLightResponse::dump_to(std::string &out) const { void ListEntitiesLightResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesLightResponse {\n"); out.append("ListEntitiesLightResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -1401,6 +1467,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
out.append(" icon: "); out.append(" icon: ");
out.append("'").append(this->icon).append("'"); out.append("'").append(this->icon).append("'");
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -1491,7 +1561,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void LightStateResponse::dump_to(std::string &out) const { void LightStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("LightStateResponse {\n"); out.append("LightStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -1714,7 +1784,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void LightCommandRequest::dump_to(std::string &out) const { void LightCommandRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("LightCommandRequest {\n"); out.append("LightCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -1860,6 +1930,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 13: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -1917,10 +1991,11 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::SensorStateClass>(10, this->state_class); buffer.encode_enum<enums::SensorStateClass>(10, this->state_class);
buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type); buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type);
buffer.encode_bool(12, this->disabled_by_default); buffer.encode_bool(12, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(13, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSensorResponse::dump_to(std::string &out) const { void ListEntitiesSensorResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesSensorResponse {\n"); out.append("ListEntitiesSensorResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -1971,6 +2046,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -2005,7 +2084,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SensorStateResponse::dump_to(std::string &out) const { void SensorStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SensorStateResponse {\n"); out.append("SensorStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -2033,6 +2112,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 8: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -2077,10 +2160,11 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon); buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->assumed_state); buffer.encode_bool(6, this->assumed_state);
buffer.encode_bool(7, this->disabled_by_default); buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSwitchResponse::dump_to(std::string &out) const { void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesSwitchResponse {\n"); out.append("ListEntitiesSwitchResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -2110,6 +2194,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -2139,7 +2227,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SwitchStateResponse::dump_to(std::string &out) const { void SwitchStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SwitchStateResponse {\n"); out.append("SwitchStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -2178,7 +2266,7 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SwitchCommandRequest::dump_to(std::string &out) const { void SwitchCommandRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SwitchCommandRequest {\n"); out.append("SwitchCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -2197,6 +2285,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -2240,10 +2332,11 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(4, this->unique_id); buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon); buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default); buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesTextSensorResponse {\n"); out.append("ListEntitiesTextSensorResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -2269,6 +2362,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -2309,7 +2406,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void TextSensorStateResponse::dump_to(std::string &out) const { void TextSensorStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("TextSensorStateResponse {\n"); out.append("TextSensorStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -2346,7 +2443,7 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeLogsRequest::dump_to(std::string &out) const { void SubscribeLogsRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SubscribeLogsRequest {\n"); out.append("SubscribeLogsRequest {\n");
out.append(" level: "); out.append(" level: ");
out.append(proto_enum_to_string<enums::LogLevel>(this->level)); out.append(proto_enum_to_string<enums::LogLevel>(this->level));
@ -2389,7 +2486,7 @@ void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeLogsResponse::dump_to(std::string &out) const { void SubscribeLogsResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SubscribeLogsResponse {\n"); out.append("SubscribeLogsResponse {\n");
out.append(" level: "); out.append(" level: ");
out.append(proto_enum_to_string<enums::LogLevel>(this->level)); out.append(proto_enum_to_string<enums::LogLevel>(this->level));
@ -2431,7 +2528,7 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void HomeassistantServiceMap::dump_to(std::string &out) const { void HomeassistantServiceMap::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("HomeassistantServiceMap {\n"); out.append("HomeassistantServiceMap {\n");
out.append(" key: "); out.append(" key: ");
out.append("'").append(this->key).append("'"); out.append("'").append(this->key).append("'");
@ -2490,7 +2587,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void HomeassistantServiceResponse::dump_to(std::string &out) const { void HomeassistantServiceResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("HomeassistantServiceResponse {\n"); out.append("HomeassistantServiceResponse {\n");
out.append(" service: "); out.append(" service: ");
out.append("'").append(this->service).append("'"); out.append("'").append(this->service).append("'");
@ -2546,7 +2643,7 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SubscribeHomeAssistantStateResponse {\n"); out.append("SubscribeHomeAssistantStateResponse {\n");
out.append(" entity_id: "); out.append(" entity_id: ");
out.append("'").append(this->entity_id).append("'"); out.append("'").append(this->entity_id).append("'");
@ -2583,7 +2680,7 @@ void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void HomeAssistantStateResponse::dump_to(std::string &out) const { void HomeAssistantStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("HomeAssistantStateResponse {\n"); out.append("HomeAssistantStateResponse {\n");
out.append(" entity_id: "); out.append(" entity_id: ");
out.append("'").append(this->entity_id).append("'"); out.append("'").append(this->entity_id).append("'");
@ -2616,7 +2713,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); } void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void GetTimeResponse::dump_to(std::string &out) const { void GetTimeResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("GetTimeResponse {\n"); out.append("GetTimeResponse {\n");
out.append(" epoch_seconds: "); out.append(" epoch_seconds: ");
sprintf(buffer, "%u", this->epoch_seconds); sprintf(buffer, "%u", this->epoch_seconds);
@ -2651,7 +2748,7 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesServicesArgument::dump_to(std::string &out) const { void ListEntitiesServicesArgument::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesServicesArgument {\n"); out.append("ListEntitiesServicesArgument {\n");
out.append(" name: "); out.append(" name: ");
out.append("'").append(this->name).append("'"); out.append("'").append(this->name).append("'");
@ -2696,7 +2793,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesServicesResponse::dump_to(std::string &out) const { void ListEntitiesServicesResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesServicesResponse {\n"); out.append("ListEntitiesServicesResponse {\n");
out.append(" name: "); out.append(" name: ");
out.append("'").append(this->name).append("'"); out.append("'").append(this->name).append("'");
@ -2790,7 +2887,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ExecuteServiceArgument::dump_to(std::string &out) const { void ExecuteServiceArgument::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ExecuteServiceArgument {\n"); out.append("ExecuteServiceArgument {\n");
out.append(" bool_: "); out.append(" bool_: ");
out.append(YESNO(this->bool_)); out.append(YESNO(this->bool_));
@ -2871,7 +2968,7 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ExecuteServiceRequest::dump_to(std::string &out) const { void ExecuteServiceRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ExecuteServiceRequest {\n"); out.append("ExecuteServiceRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -2892,6 +2989,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -2910,6 +3011,10 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->unique_id = value.as_string(); this->unique_id = value.as_string();
return true; return true;
} }
case 6: {
this->icon = value.as_string();
return true;
}
default: default:
return false; return false;
} }
@ -2930,10 +3035,12 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(3, this->name); buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id); buffer.encode_string(4, this->unique_id);
buffer.encode_bool(5, this->disabled_by_default); 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 #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCameraResponse::dump_to(std::string &out) const { void ListEntitiesCameraResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesCameraResponse {\n"); out.append("ListEntitiesCameraResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -2955,6 +3062,14 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); 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("}"); out.append("}");
} }
#endif #endif
@ -2995,7 +3110,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void CameraImageResponse::dump_to(std::string &out) const { void CameraImageResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("CameraImageResponse {\n"); out.append("CameraImageResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -3032,7 +3147,7 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void CameraImageRequest::dump_to(std::string &out) const { void CameraImageRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("CameraImageRequest {\n"); out.append("CameraImageRequest {\n");
out.append(" single: "); out.append(" single: ");
out.append(YESNO(this->single)); out.append(YESNO(this->single));
@ -3082,6 +3197,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 20: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -3170,10 +3289,11 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
} }
buffer.encode_bool(18, this->disabled_by_default); buffer.encode_bool(18, this->disabled_by_default);
buffer.encode_string(19, this->icon); buffer.encode_string(19, this->icon);
buffer.encode_enum<enums::EntityCategory>(20, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesClimateResponse::dump_to(std::string &out) const { void ListEntitiesClimateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesClimateResponse {\n"); out.append("ListEntitiesClimateResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -3266,6 +3386,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append(" icon: "); out.append(" icon: ");
out.append("'").append(this->icon).append("'"); out.append("'").append(this->icon).append("'");
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -3356,7 +3480,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ClimateStateResponse::dump_to(std::string &out) const { void ClimateStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ClimateStateResponse {\n"); out.append("ClimateStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -3544,7 +3668,7 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ClimateCommandRequest::dump_to(std::string &out) const { void ClimateCommandRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ClimateCommandRequest {\n"); out.append("ClimateCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -3642,6 +3766,14 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 10: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 12: {
this->mode = value.as_enum<enums::NumberMode>();
return true;
}
default: default:
return false; return false;
} }
@ -3664,6 +3796,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->icon = value.as_string(); this->icon = value.as_string();
return true; return true;
} }
case 11: {
this->unit_of_measurement = value.as_string();
return true;
}
default: default:
return false; return false;
} }
@ -3700,10 +3836,13 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(7, this->max_value); buffer.encode_float(7, this->max_value);
buffer.encode_float(8, this->step); buffer.encode_float(8, this->step);
buffer.encode_bool(9, this->disabled_by_default); buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
buffer.encode_string(11, this->unit_of_measurement);
buffer.encode_enum<enums::NumberMode>(12, this->mode);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesNumberResponse::dump_to(std::string &out) const { void ListEntitiesNumberResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesNumberResponse {\n"); out.append("ListEntitiesNumberResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -3744,6 +3883,18 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" unit_of_measurement: ");
out.append("'").append(this->unit_of_measurement).append("'");
out.append("\n");
out.append(" mode: ");
out.append(proto_enum_to_string<enums::NumberMode>(this->mode));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -3778,7 +3929,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void NumberStateResponse::dump_to(std::string &out) const { void NumberStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("NumberStateResponse {\n"); out.append("NumberStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -3816,7 +3967,7 @@ void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void NumberCommandRequest::dump_to(std::string &out) const { void NumberCommandRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("NumberCommandRequest {\n"); out.append("NumberCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -3836,6 +3987,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool(); this->disabled_by_default = value.as_bool();
return true; return true;
} }
case 8: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default: default:
return false; return false;
} }
@ -3886,10 +4041,11 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(6, it, true); buffer.encode_string(6, it, true);
} }
buffer.encode_bool(7, this->disabled_by_default); buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSelectResponse::dump_to(std::string &out) const { void ListEntitiesSelectResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesSelectResponse {\n"); out.append("ListEntitiesSelectResponse {\n");
out.append(" object_id: "); out.append(" object_id: ");
out.append("'").append(this->object_id).append("'"); out.append("'").append(this->object_id).append("'");
@ -3921,6 +4077,10 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: "); out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default)); out.append(YESNO(this->disabled_by_default));
out.append("\n"); out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -3961,7 +4121,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SelectStateResponse::dump_to(std::string &out) const { void SelectStateResponse::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SelectStateResponse {\n"); out.append("SelectStateResponse {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -4004,7 +4164,7 @@ void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const {
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SelectCommandRequest::dump_to(std::string &out) const { void SelectCommandRequest::dump_to(std::string &out) const {
char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("SelectCommandRequest {\n"); out.append("SelectCommandRequest {\n");
out.append(" key: "); out.append(" key: ");
sprintf(buffer, "%u", this->key); sprintf(buffer, "%u", this->key);
@ -4017,6 +4177,127 @@ void SelectCommandRequest::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
}
bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
case 8: {
this->device_class = value.as_string();
return true;
}
default:
return false;
}
}
bool ListEntitiesButtonResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
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);
buffer.encode_string(8, this->device_class);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
char buffer[64];
out.append("ListEntitiesButtonResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
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(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
out.append("}");
}
#endif
bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void ButtonCommandRequest::dump_to(std::string &out) const {
char buffer[64];
out.append("ButtonCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%u", this->key);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -9,6 +9,11 @@ namespace api {
namespace enums { namespace enums {
enum EntityCategory : uint32_t {
ENTITY_CATEGORY_NONE = 0,
ENTITY_CATEGORY_CONFIG = 1,
ENTITY_CATEGORY_DIAGNOSTIC = 2,
};
enum LegacyCoverState : uint32_t { enum LegacyCoverState : uint32_t {
LEGACY_COVER_STATE_OPEN = 0, LEGACY_COVER_STATE_OPEN = 0,
LEGACY_COVER_STATE_CLOSED = 1, LEGACY_COVER_STATE_CLOSED = 1,
@ -118,6 +123,11 @@ enum ClimatePreset : uint32_t {
CLIMATE_PRESET_SLEEP = 6, CLIMATE_PRESET_SLEEP = 6,
CLIMATE_PRESET_ACTIVITY = 7, CLIMATE_PRESET_ACTIVITY = 7,
}; };
enum NumberMode : uint32_t {
NUMBER_MODE_AUTO = 0,
NUMBER_MODE_BOX = 1,
NUMBER_MODE_SLIDER = 2,
};
} // namespace enums } // namespace enums
@ -224,6 +234,7 @@ class DeviceInfoResponse : public ProtoMessage {
bool has_deep_sleep{false}; bool has_deep_sleep{false};
std::string project_name{}; std::string project_name{};
std::string project_version{}; std::string project_version{};
uint32_t webserver_port{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -270,6 +281,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
bool is_status_binary_sensor{false}; bool is_status_binary_sensor{false};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{}; std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -306,6 +318,7 @@ class ListEntitiesCoverResponse : public ProtoMessage {
std::string device_class{}; std::string device_class{};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{}; std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -363,6 +376,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
int32_t supported_speed_count{0}; int32_t supported_speed_count{0};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{}; std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -428,6 +442,7 @@ class ListEntitiesLightResponse : public ProtoMessage {
std::vector<std::string> effects{}; std::vector<std::string> effects{};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{}; std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -516,6 +531,7 @@ class ListEntitiesSensorResponse : public ProtoMessage {
enums::SensorStateClass state_class{}; enums::SensorStateClass state_class{};
enums::SensorLastResetType legacy_last_reset_type{}; enums::SensorLastResetType legacy_last_reset_type{};
bool disabled_by_default{false}; bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -549,6 +565,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
std::string icon{}; std::string icon{};
bool assumed_state{false}; bool assumed_state{false};
bool disabled_by_default{false}; bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -593,6 +610,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
std::string unique_id{}; std::string unique_id{};
std::string icon{}; std::string icon{};
bool disabled_by_default{false}; bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -803,6 +821,8 @@ class ListEntitiesCameraResponse : public ProtoMessage {
std::string name{}; std::string name{};
std::string unique_id{}; std::string unique_id{};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -861,6 +881,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
std::vector<std::string> supported_custom_presets{}; std::vector<std::string> supported_custom_presets{};
bool disabled_by_default{false}; bool disabled_by_default{false};
std::string icon{}; std::string icon{};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -940,6 +961,9 @@ class ListEntitiesNumberResponse : public ProtoMessage {
float max_value{0.0f}; float max_value{0.0f};
float step{0.0f}; float step{0.0f};
bool disabled_by_default{false}; bool disabled_by_default{false};
enums::EntityCategory entity_category{};
std::string unit_of_measurement{};
enums::NumberMode mode{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -985,6 +1009,7 @@ class ListEntitiesSelectResponse : public ProtoMessage {
std::string icon{}; std::string icon{};
std::vector<std::string> options{}; std::vector<std::string> options{};
bool disabled_by_default{false}; bool disabled_by_default{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -1023,6 +1048,37 @@ class SelectCommandRequest : public ProtoMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
}; };
class ListEntitiesButtonResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
std::string device_class{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ButtonCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
};
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -282,6 +282,16 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
#endif #endif
#ifdef USE_BUTTON
bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_button_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesButtonResponse>(msg, 61);
}
#endif
#ifdef USE_BUTTON
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) { switch (msg_type) {
case 1: { case 1: {
@ -513,6 +523,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
#endif #endif
this->on_select_command_request(msg); this->on_select_command_request(msg);
#endif
break;
}
case 62: {
#ifdef USE_BUTTON
ButtonCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str());
#endif
this->on_button_command_request(msg);
#endif #endif
break; break;
} }
@ -737,6 +758,19 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest &
this->select_command(msg); this->select_command(msg);
} }
#endif #endif
#ifdef USE_BUTTON
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->button_command(msg);
}
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -129,6 +129,12 @@ class APIServerConnectionBase : public ProtoService {
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
virtual void on_select_command_request(const SelectCommandRequest &value){}; virtual void on_select_command_request(const SelectCommandRequest &value){};
#endif
#ifdef USE_BUTTON
bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg);
#endif
#ifdef USE_BUTTON
virtual void on_button_command_request(const ButtonCommandRequest &value){};
#endif #endif
protected: protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
@ -171,6 +177,9 @@ class APIServerConnection : public APIServerConnectionBase {
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
virtual void select_command(const SelectCommandRequest &msg) = 0; virtual void select_command(const SelectCommandRequest &msg) = 0;
#endif
#ifdef USE_BUTTON
virtual void button_command(const ButtonCommandRequest &msg) = 0;
#endif #endif
protected: protected:
void on_hello_request(const HelloRequest &msg) override; void on_hello_request(const HelloRequest &msg) override;
@ -209,6 +218,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_SELECT #ifdef USE_SELECT
void on_select_command_request(const SelectCommandRequest &msg) override; void on_select_command_request(const SelectCommandRequest &msg) override;
#endif #endif
#ifdef USE_BUTTON
void on_button_command_request(const ButtonCommandRequest &msg) override;
#endif
}; };
} // namespace api } // namespace api

View file

@ -77,7 +77,7 @@ void APIServer::setup() {
this->last_connected_ = millis(); this->last_connected_ = millis();
#ifdef USE_ESP32_CAMERA #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( esp32_camera::global_esp32_camera->add_image_callback(
[this](const std::shared_ptr<esp32_camera::CameraImage> &image) { [this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
for (auto &c : this->clients_) for (auto &c : this->clients_)

View file

@ -23,7 +23,6 @@ async def async_run_logs(config, address):
_LOGGER.info("Starting log output from %s using esphome API", address) _LOGGER.info("Starting log output from %s using esphome API", address)
zc = zeroconf.Zeroconf() zc = zeroconf.Zeroconf()
cli = APIClient( cli = APIClient(
asyncio.get_event_loop(),
address, address,
port, port,
password, password,

View file

@ -8,6 +8,18 @@
namespace esphome { namespace esphome {
namespace api { 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_invocable<F, X...>::value, int> = 0>
TemplatableStringValue(F value) : TemplatableValue<std::string, X...>(value) {}
template<typename F, enable_if_t<is_invocable<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 { template<typename... Ts> class TemplatableKeyValuePair {
public: public:
template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {} 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: public:
explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {} 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) { template<typename T> void add_data(std::string key, T value) {
this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value)); this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
} }
@ -58,6 +71,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
protected: protected:
APIServer *parent_; APIServer *parent_;
bool is_event_; bool is_event_;
TemplatableStringValue<Ts...> service_{};
std::vector<TemplatableKeyValuePair<Ts...>> data_; std::vector<TemplatableKeyValuePair<Ts...>> data_;
std::vector<TemplatableKeyValuePair<Ts...>> data_template_; std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
std::vector<TemplatableKeyValuePair<Ts...>> variables_; std::vector<TemplatableKeyValuePair<Ts...>> variables_;

View file

@ -27,6 +27,9 @@ bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->clie
#ifdef USE_SWITCH #ifdef USE_SWITCH
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); }
#endif #endif
#ifdef USE_BUTTON
bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); }
#endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
return this->client_->send_text_sensor_info(text_sensor); return this->client_->send_text_sensor_info(text_sensor);

View file

@ -30,6 +30,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_SWITCH #ifdef USE_SWITCH
bool on_switch(switch_::Switch *a_switch) override; bool on_switch(switch_::Switch *a_switch) override;
#endif #endif
#ifdef USE_BUTTON
bool on_button(button::Button *button) override;
#endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
#endif #endif

View file

@ -31,6 +31,9 @@ class InitialStateIterator : public ComponentIterator {
#ifdef USE_SWITCH #ifdef USE_SWITCH
bool on_switch(switch_::Switch *a_switch) override; bool on_switch(switch_::Switch *a_switch) override;
#endif #endif
#ifdef USE_BUTTON
bool on_button(button::Button *button) override { return true; };
#endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
#endif #endif

View file

@ -116,6 +116,21 @@ void ComponentIterator::advance() {
} }
break; break;
#endif #endif
#ifdef USE_BUTTON
case IteratorState::BUTTON:
if (this->at_ >= App.get_buttons().size()) {
advance_platform = true;
} else {
auto *button = App.get_buttons()[this->at_];
if (button->is_internal()) {
success = true;
break;
} else {
success = this->on_button(button);
}
}
break;
#endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
case IteratorState::TEXT_SENSOR: case IteratorState::TEXT_SENSOR:
if (this->at_ >= App.get_text_sensors().size()) { if (this->at_ >= App.get_text_sensors().size()) {

View file

@ -38,6 +38,9 @@ class ComponentIterator {
#ifdef USE_SWITCH #ifdef USE_SWITCH
virtual bool on_switch(switch_::Switch *a_switch) = 0; virtual bool on_switch(switch_::Switch *a_switch) = 0;
#endif #endif
#ifdef USE_BUTTON
virtual bool on_button(button::Button *button) = 0;
#endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0;
#endif #endif
@ -78,6 +81,9 @@ class ComponentIterator {
#ifdef USE_SWITCH #ifdef USE_SWITCH
SWITCH, SWITCH,
#endif #endif
#ifdef USE_BUTTON
BUTTON,
#endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
TEXT_SENSOR, TEXT_SENSOR,
#endif #endif

View file

@ -45,6 +45,8 @@ bool ATCMiThermometer::parse_device(const esp32_ble_tracker::ESPBTDevice &device
this->battery_voltage_->publish_state(*res->battery_voltage); this->battery_voltage_->publish_state(*res->battery_voltage);
success = true; success = true;
} }
if (this->signal_strength_ != nullptr)
this->signal_strength_->publish_state(device.get_rssi());
return success; return success;
} }

View file

@ -28,6 +28,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; }
void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; } void set_battery_voltage(sensor::Sensor *battery_voltage) { battery_voltage_ = battery_voltage; }
void set_signal_strength(sensor::Sensor *signal_strength) { signal_strength_ = signal_strength; }
protected: protected:
uint64_t address_; uint64_t address_;
@ -35,6 +36,7 @@ class ATCMiThermometer : public Component, public esp32_ble_tracker::ESPBTDevice
sensor::Sensor *humidity_{nullptr}; sensor::Sensor *humidity_{nullptr};
sensor::Sensor *battery_level_{nullptr}; sensor::Sensor *battery_level_{nullptr};
sensor::Sensor *battery_voltage_{nullptr}; sensor::Sensor *battery_voltage_{nullptr};
sensor::Sensor *signal_strength_{nullptr};
optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data); optional<ParseResult> parse_header_(const esp32_ble_tracker::ServiceData &service_data);
bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result); bool parse_message_(const std::vector<uint8_t> &message, ParseResult &result);

View file

@ -6,14 +6,18 @@ from esphome.const import (
CONF_BATTERY_VOLTAGE, CONF_BATTERY_VOLTAGE,
CONF_MAC_ADDRESS, CONF_MAC_ADDRESS,
CONF_HUMIDITY, CONF_HUMIDITY,
CONF_SIGNAL_STRENGTH,
CONF_TEMPERATURE, CONF_TEMPERATURE,
CONF_ID, CONF_ID,
DEVICE_CLASS_BATTERY, DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS, UNIT_CELSIUS,
UNIT_DECIBEL_MILLIWATT,
UNIT_PERCENT, UNIT_PERCENT,
UNIT_VOLT, UNIT_VOLT,
) )
@ -49,12 +53,21 @@ CONFIG_SCHEMA = (
accuracy_decimals=0, accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT, unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3, accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE, device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_SIGNAL_STRENGTH): sensor.sensor_schema(
unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
} }
) )
@ -82,3 +95,6 @@ async def to_code(config):
if CONF_BATTERY_VOLTAGE in config: if CONF_BATTERY_VOLTAGE in config:
sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE]) sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE])
cg.add(var.set_battery_voltage(sens)) cg.add(var.set_battery_voltage(sens))
if CONF_SIGNAL_STRENGTH in config:
sens = await sensor.new_sensor(config[CONF_SIGNAL_STRENGTH])
cg.add(var.set_signal_strength(sens))

View file

@ -17,6 +17,7 @@ from esphome.const import (
DEVICE_CLASS_POWER_FACTOR, DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_LIGHTBULB, ICON_LIGHTBULB,
ICON_CURRENT_AC, ICON_CURRENT_AC,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
@ -125,6 +126,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=1, accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum( cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(

View file

@ -13,6 +13,7 @@ from esphome.const import (
DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS, UNIT_CELSIUS,
UNIT_LUX, UNIT_LUX,
@ -51,6 +52,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=3, accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE, device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
cv.Optional(CONF_MOISTURE): sensor.sensor_schema( cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT, unit_of_measurement=UNIT_PERCENT,

View file

@ -80,21 +80,23 @@ void BangBangClimate::compute_state_() {
climate::ClimateAction target_action; climate::ClimateAction target_action;
if (too_cold) { if (too_cold) {
// too cold -> enable heating if possible, else idle // too cold -> enable heating if possible and enabled, else idle
if (this->supports_heat_) if (this->supports_heat_ &&
(this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_HEAT))
target_action = climate::CLIMATE_ACTION_HEATING; target_action = climate::CLIMATE_ACTION_HEATING;
else else
target_action = climate::CLIMATE_ACTION_IDLE; target_action = climate::CLIMATE_ACTION_IDLE;
} else if (too_hot) { } else if (too_hot) {
// too hot -> enable cooling if possible, else idle // too hot -> enable cooling if possible and enabled, else idle
if (this->supports_cool_) if (this->supports_cool_ &&
(this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_COOL))
target_action = climate::CLIMATE_ACTION_COOLING; target_action = climate::CLIMATE_ACTION_COOLING;
else else
target_action = climate::CLIMATE_ACTION_IDLE; target_action = climate::CLIMATE_ACTION_IDLE;
} else { } else {
// neither too hot nor too cold -> in range // neither too hot nor too cold -> in range
if (this->supports_cool_ && this->supports_heat_) { if (this->supports_cool_ && this->supports_heat_ && this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
// if supports both ends, go to idle action // if supports both ends and both cooling and heating enabled, go to idle action
target_action = climate::CLIMATE_ACTION_IDLE; target_action = climate::CLIMATE_ACTION_IDLE;
} else { } else {
// else use current mode and don't change (hysteresis) // else use current mode and don't change (hysteresis)

View file

@ -79,6 +79,9 @@ void BH1750Sensor::read_data_() {
float lx = float(raw_value) / 1.2f; float lx = float(raw_value) / 1.2f;
lx *= 69.0f / this->measurement_duration_; 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); ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
this->publish_state(lx); this->publish_state(lx);
this->status_clear_warning(); this->status_clear_warning();

View file

@ -44,9 +44,11 @@ from esphome.const import (
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESENCE, DEVICE_CLASS_PRESENCE,
DEVICE_CLASS_PROBLEM, DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_RUNNING,
DEVICE_CLASS_SAFETY, DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE, DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND, DEVICE_CLASS_SOUND,
DEVICE_CLASS_TAMPER,
DEVICE_CLASS_UPDATE, DEVICE_CLASS_UPDATE,
DEVICE_CLASS_VIBRATION, DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW, DEVICE_CLASS_WINDOW,
@ -76,9 +78,11 @@ DEVICE_CLASSES = [
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESENCE, DEVICE_CLASS_PRESENCE,
DEVICE_CLASS_PROBLEM, DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_RUNNING,
DEVICE_CLASS_SAFETY, DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE, DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND, DEVICE_CLASS_SOUND,
DEVICE_CLASS_TAMPER,
DEVICE_CLASS_UPDATE, DEVICE_CLASS_UPDATE,
DEVICE_CLASS_VIBRATION, DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW, DEVICE_CLASS_WINDOW,
@ -313,6 +317,7 @@ def validate_multi_click_timing(value):
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{ {
cv.GenerateID(): cv.declare_id(BinarySensor), cv.GenerateID(): cv.declare_id(BinarySensor),

View file

@ -48,7 +48,10 @@ void BinarySensor::set_device_class(const std::string &device_class) { this->dev
std::string BinarySensor::get_device_class() { std::string BinarySensor::get_device_class() {
if (this->device_class_.has_value()) if (this->device_class_.has_value())
return *this->device_class_; return *this->device_class_;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
return this->device_class(); return this->device_class();
#pragma GCC diagnostic pop
} }
void BinarySensor::add_filter(Filter *filter) { void BinarySensor::add_filter(Filter *filter) {
filter->parent_ = this; filter->parent_ = this;

View file

@ -74,7 +74,10 @@ class BinarySensor : public EntityBase {
// ========== OVERRIDE METHODS ========== // ========== OVERRIDE METHODS ==========
// (You'll only need this when creating your own custom binary sensor) // (You'll only need this when creating your own custom binary sensor)
/// Get the default device class for this sensor, or empty string for no default. /** Override this to set the default device class.
*
* @deprecated This method is deprecated, set the property during config validation instead. (2022.1)
*/
virtual std::string device_class(); virtual std::string device_class();
protected: protected:

View file

@ -0,0 +1 @@
CODEOWNERS = ["@tobias-"]

View file

@ -0,0 +1,137 @@
#include "bl0940.h"
#include "esphome/core/log.h"
namespace esphome {
namespace bl0940 {
static const char *const TAG = "bl0940";
static const uint8_t BL0940_READ_COMMAND = 0x50; // 0x58 according to documentation
static const uint8_t BL0940_FULL_PACKET = 0xAA;
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to documentation
static const uint8_t BL0940_WRITE_COMMAND = 0xA0; // 0xA8 according to documentation
static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10;
static const uint8_t BL0940_REG_MODE = 0x18;
static const uint8_t BL0940_REG_SOFT_RESET = 0x19;
static const uint8_t BL0940_REG_USR_WRPROT = 0x1A;
static const uint8_t BL0940_REG_TPS_CTRL = 0x1B;
const uint8_t BL0940_INIT[5][6] = {
// Reset to default
{BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
// Enable User Operation Write
{BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
{BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
{BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
// 0x181C = Half cycle, Fast RMS threshold 6172
{BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
void BL0940::loop() {
DataPacket buffer;
if (!this->available()) {
return;
}
if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
if (validate_checksum(&buffer)) {
received_package_(&buffer);
}
} else {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
while (read() >= 0)
;
}
}
bool BL0940::validate_checksum(const DataPacket *data) {
uint8_t checksum = BL0940_READ_COMMAND;
// Whole package but checksum
for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) {
checksum += data->raw[i];
}
checksum ^= 0xFF;
if (checksum != data->checksum) {
ESP_LOGW(TAG, "BL0940 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
}
return checksum == data->checksum;
}
void BL0940::update() {
this->flush();
this->write_byte(BL0940_READ_COMMAND);
this->write_byte(BL0940_FULL_PACKET);
}
void BL0940::setup() {
for (auto i : BL0940_INIT) {
this->write_array(i, 6);
delay(1);
}
this->flush();
}
float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
auto tb = (float) (temperature.h << 8 | temperature.l);
float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
if (sensor != nullptr) {
if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
ESP_LOGD("bl0940", "Invalid temperature change. Sensor: '%s', Old temperature: %f, New temperature: %f",
sensor->get_name().c_str(), sensor->get_state(), converted_temp);
return 0.0f;
}
sensor->publish_state(converted_temp);
}
return converted_temp;
}
void BL0940::received_package_(const DataPacket *data) const {
// Bad header
if (data->frame_header != BL0940_PACKET_HEADER) {
ESP_LOGI("bl0940", "Invalid data. Header mismatch: %d", data->frame_header);
return;
}
float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_;
float i_rms = (float) to_uint32_t(data->i_rms) / current_reference_;
float watt = (float) to_int32_t(data->watt) / power_reference_;
uint32_t cf_cnt = to_uint32_t(data->cf_cnt);
float total_energy_consumption = (float) cf_cnt / energy_reference_;
float tps1 = update_temp_(internal_temperature_sensor_, data->tps1);
float tps2 = update_temp_(external_temperature_sensor_, data->tps2);
if (voltage_sensor_ != nullptr) {
voltage_sensor_->publish_state(v_rms);
}
if (current_sensor_ != nullptr) {
current_sensor_->publish_state(i_rms);
}
if (power_sensor_ != nullptr) {
power_sensor_->publish_state(watt);
}
if (energy_sensor_ != nullptr) {
energy_sensor_->publish_state(total_energy_consumption);
}
ESP_LOGV("bl0940", "BL0940: U %fV, I %fA, P %fW, Cnt %d, ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
total_energy_consumption, tps1, tps2);
}
void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexity)
ESP_LOGCONFIG(TAG, "BL0940:");
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
LOG_SENSOR("", "Current", this->current_sensor_);
LOG_SENSOR("", "Power", this->power_sensor_);
LOG_SENSOR("", "Energy", this->energy_sensor_);
LOG_SENSOR("", "Internal temperature", this->internal_temperature_sensor_);
LOG_SENSOR("", "External temperature", this->external_temperature_sensor_);
}
uint32_t BL0940::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
int32_t BL0940::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
} // namespace bl0940
} // namespace esphome

View file

@ -0,0 +1,109 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace bl0940 {
static const float BL0940_PREF = 1430;
static const float BL0940_UREF = 33000;
static const float BL0940_IREF = 275000; // 2750 from tasmota. Seems to generate values 100 times too high
// Measured to 297J per click according to power consumption of 5 minutes
// Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
static const float BL0940_EREF = 3.6e6 / 297;
struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t m;
uint8_t h;
} __attribute__((packed));
struct ube16_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t h;
} __attribute__((packed));
struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l;
uint8_t m;
int8_t h;
} __attribute__((packed));
// Caveat: All these values are big endian (low - middle - high)
union DataPacket { // NOLINT(altera-struct-pack-align)
uint8_t raw[35];
struct {
uint8_t frame_header; // value of 0x58 according to docs. 0x55 according to Tasmota real world tests. Reality wins.
ube24_t i_fast_rms; // 0x00
ube24_t i_rms; // 0x04
ube24_t RESERVED0; // reserved
ube24_t v_rms; // 0x06
ube24_t RESERVED1; // reserved
sbe24_t watt; // 0x08
ube24_t RESERVED2; // reserved
ube24_t cf_cnt; // 0x0A
ube24_t RESERVED3; // reserved
ube16_t tps1; // 0x0c
uint8_t RESERVED4; // value of 0x00
ube16_t tps2; // 0x0c
uint8_t RESERVED5; // value of 0x00
uint8_t checksum; // checksum
};
} __attribute__((packed));
class BL0940 : public PollingComponent, public uart::UARTDevice {
public:
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) {
internal_temperature_sensor_ = internal_temperature_sensor;
}
void set_external_temperature_sensor(sensor::Sensor *external_temperature_sensor) {
external_temperature_sensor_ = external_temperature_sensor;
}
void loop() override;
void update() override;
void setup() override;
void dump_config() override;
protected:
sensor::Sensor *voltage_sensor_;
sensor::Sensor *current_sensor_;
// NB This may be negative as the circuits is seemingly able to measure
// power in both directions
sensor::Sensor *power_sensor_;
sensor::Sensor *energy_sensor_;
sensor::Sensor *internal_temperature_sensor_;
sensor::Sensor *external_temperature_sensor_;
// Max difference between two measurements of the temperature. Used to avoid noise.
float max_temperature_diff_{0};
// Divide by this to turn into Watt
float power_reference_ = BL0940_PREF;
// Divide by this to turn into Volt
float voltage_reference_ = BL0940_UREF;
// Divide by this to turn into Ampere
float current_reference_ = BL0940_IREF;
// Divide by this to turn into kWh
float energy_reference_ = BL0940_EREF;
float update_temp_(sensor::Sensor *sensor, ube16_t packed_temperature) const;
static uint32_t to_uint32_t(ube24_t input);
static int32_t to_int32_t(sbe24_t input);
static bool validate_checksum(const DataPacket *data);
void received_package_(const DataPacket *data) const;
};
} // namespace bl0940
} // namespace esphome

View file

@ -0,0 +1,106 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, uart
from esphome.const import (
CONF_CURRENT,
CONF_ENERGY,
CONF_ID,
CONF_POWER,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_TEMPERATURE,
ICON_EMPTY,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_NONE,
UNIT_AMPERE,
UNIT_CELSIUS,
UNIT_KILOWATT_HOURS,
UNIT_VOLT,
UNIT_WATT,
)
DEPENDENCIES = ["uart"]
CONF_INTERNAL_TEMPERATURE = "internal_temperature"
CONF_EXTERNAL_TEMPERATURE = "external_temperature"
bl0940_ns = cg.esphome_ns.namespace("bl0940")
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BL0940),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
UNIT_AMPERE,
ICON_EMPTY,
2,
DEVICE_CLASS_CURRENT,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER): sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
),
cv.Optional(CONF_ENERGY): sensor.sensor_schema(
UNIT_KILOWATT_HOURS,
ICON_EMPTY,
0,
DEVICE_CLASS_ENERGY,
STATE_CLASS_NONE,
),
cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS,
ICON_EMPTY,
0,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_NONE,
),
cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema(
UNIT_CELSIUS,
ICON_EMPTY,
0,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_NONE,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
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)
if CONF_VOLTAGE in config:
conf = config[CONF_VOLTAGE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config:
conf = config[CONF_CURRENT]
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
conf = config[CONF_POWER]
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens))
if CONF_ENERGY in config:
conf = config[CONF_ENERGY]
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor(sens))
if CONF_INTERNAL_TEMPERATURE in config:
conf = config[CONF_INTERNAL_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_internal_temperature_sensor(sens))
if CONF_EXTERNAL_TEMPERATURE in config:
conf = config[CONF_EXTERNAL_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_external_temperature_sensor(sens))

View file

@ -11,6 +11,8 @@ namespace ble_client {
static const char *const TAG = "ble_client"; static const char *const TAG = "ble_client";
float BLEClient::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
void BLEClient::setup() { void BLEClient::setup() {
auto ret = esp_ble_gattc_app_register(this->app_id); auto ret = esp_ble_gattc_app_register(this->app_id);
if (ret) { if (ret) {
@ -386,6 +388,15 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(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 ble_client
} // namespace esphome } // namespace esphome

View file

@ -59,7 +59,7 @@ class BLECharacteristic {
void parse_descriptors(); void parse_descriptors();
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
BLEDescriptor *get_descriptor(uint16_t uuid); BLEDescriptor *get_descriptor(uint16_t uuid);
void write_value(uint8_t *new_val, int16_t new_val_size);
BLEService *service; BLEService *service;
}; };
@ -81,6 +81,7 @@ class BLEClient : public espbt::ESPBTClient, public Component {
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
void loop() 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, void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override; esp_ble_gattc_cb_param_t *param) override;

View 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)

View 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

View 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

View file

@ -67,7 +67,7 @@ async def to_code(config):
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) 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): 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)) cg.add(var.set_service_uuid128(uuid128))
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): 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( elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format 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)) cg.add(var.set_char_uuid128(uuid128))
if CONF_DESCRIPTOR_UUID in config: if CONF_DESCRIPTOR_UUID in config:
@ -108,7 +110,9 @@ async def to_code(config):
elif len(config[CONF_DESCRIPTOR_UUID]) == len( elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format 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)) cg.add(var.set_descr_uuid128(uuid128))
if CONF_LAMBDA in config: if CONF_LAMBDA in config:

View file

@ -33,6 +33,7 @@ static const uint8_t BME280_REGISTER_CONTROLHUMID = 0xF2;
static const uint8_t BME280_REGISTER_STATUS = 0xF3; static const uint8_t BME280_REGISTER_STATUS = 0xF3;
static const uint8_t BME280_REGISTER_CONTROL = 0xF4; static const uint8_t BME280_REGISTER_CONTROL = 0xF4;
static const uint8_t BME280_REGISTER_CONFIG = 0xF5; static const uint8_t BME280_REGISTER_CONFIG = 0xF5;
static const uint8_t BME280_REGISTER_MEASUREMENTS = 0xF7;
static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7; static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7;
static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA; static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA;
static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD; static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD;
@ -178,23 +179,29 @@ void BME280Component::update() {
return; return;
} }
float meas_time = 1.5; float meas_time = 1.5f;
meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_); meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_);
meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f; meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f;
meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f; meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f;
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
uint8_t data[8];
if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
ESP_LOGW(TAG, "Error reading registers.");
this->status_set_warning();
return;
}
int32_t t_fine = 0; int32_t t_fine = 0;
float temperature = this->read_temperature_(&t_fine); float temperature = this->read_temperature_(data, &t_fine);
if (std::isnan(temperature)) { if (std::isnan(temperature)) {
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values."); ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
this->status_set_warning(); this->status_set_warning();
return; return;
} }
float pressure = this->read_pressure_(t_fine); float pressure = this->read_pressure_(data, t_fine);
float humidity = this->read_humidity_(t_fine); float humidity = this->read_humidity_(data, t_fine);
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
if (this->temperature_sensor_ != nullptr) if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature); this->temperature_sensor_->publish_state(temperature);
if (this->pressure_sensor_ != nullptr) if (this->pressure_sensor_ != nullptr)
@ -204,11 +211,8 @@ void BME280Component::update() {
this->status_clear_warning(); this->status_clear_warning();
}); });
} }
float BME280Component::read_temperature_(int32_t *t_fine) { float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
uint8_t data[3]; int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF);
if (!this->read_bytes(BME280_REGISTER_TEMPDATA, data, 3))
return NAN;
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
adc >>= 4; adc >>= 4;
if (adc == 0x80000) if (adc == 0x80000)
// temperature was disabled // temperature was disabled
@ -226,10 +230,7 @@ float BME280Component::read_temperature_(int32_t *t_fine) {
return temperature / 100.0f; return temperature / 100.0f;
} }
float BME280Component::read_pressure_(int32_t t_fine) { float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
uint8_t data[3];
if (!this->read_bytes(BME280_REGISTER_PRESSUREDATA, data, 3))
return NAN;
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
adc >>= 4; adc >>= 4;
if (adc == 0x80000) if (adc == 0x80000)
@ -265,9 +266,9 @@ float BME280Component::read_pressure_(int32_t t_fine) {
return (p / 256.0f) / 100.0f; return (p / 256.0f) / 100.0f;
} }
float BME280Component::read_humidity_(int32_t t_fine) { float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) {
uint16_t raw_adc; uint16_t raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF);
if (!this->read_byte_16(BME280_REGISTER_HUMIDDATA, &raw_adc) || raw_adc == 0x8000) if (raw_adc == 0x8000)
return NAN; return NAN;
int32_t adc = raw_adc; int32_t adc = raw_adc;

View file

@ -82,11 +82,11 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice {
protected: protected:
/// Read the temperature value and store the calculated ambient temperature in t_fine. /// Read the temperature value and store the calculated ambient temperature in t_fine.
float read_temperature_(int32_t *t_fine); float read_temperature_(const uint8_t *data, int32_t *t_fine);
/// Read the pressure value in hPa using the provided t_fine value. /// Read the pressure value in hPa using the provided t_fine value.
float read_pressure_(int32_t t_fine); float read_pressure_(const uint8_t *data, int32_t t_fine);
/// Read the humidity value in % using the provided t_fine value. /// Read the humidity value in % using the provided t_fine value.
float read_humidity_(int32_t t_fine); float read_humidity_(const uint8_t *data, int32_t t_fine);
uint8_t read_u8_(uint8_t a_register); uint8_t read_u8_(uint8_t a_register);
uint16_t read_u16_le_(uint8_t a_register); uint16_t read_u16_le_(uint8_t a_register);
int16_t read_s16_le_(uint8_t a_register); int16_t read_s16_le_(uint8_t a_register);

View file

@ -44,7 +44,8 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional( cv.Optional(
CONF_STATE_SAVE_INTERVAL, default="6hours" CONF_STATE_SAVE_INTERVAL, default="6hours"
): cv.positive_time_period_minutes, ): cv.positive_time_period_minutes,
} },
cv.only_with_arduino,
).extend(i2c.i2c_device_schema(0x76)) ).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) 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_define("USE_BSEC")
cg.add_library("BSEC Software Library", "1.6.1480") cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480")

View file

View file

@ -0,0 +1,388 @@
/*
based on BMP388_DEV by Martin Lindupp
under MIT License (MIT)
Copyright (C) Martin Lindupp 2020
http://github.com/MartinL1/BMP388_DEV
*/
#include "bmp3xx.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace bmp3xx {
static const char *const TAG = "bmp3xx.sensor";
static const LogString *chip_type_to_str(uint8_t chip_type) {
switch (chip_type) {
case BMP388_ID:
return LOG_STR("BMP 388");
case BMP390_ID:
return LOG_STR("BMP 390");
default:
return LOG_STR("Unknown Chip Type");
}
}
static const LogString *oversampling_to_str(Oversampling oversampling) {
switch (oversampling) {
case Oversampling::OVERSAMPLING_NONE:
return LOG_STR("None");
case Oversampling::OVERSAMPLING_X2:
return LOG_STR("2x");
case Oversampling::OVERSAMPLING_X4:
return LOG_STR("4x");
case Oversampling::OVERSAMPLING_X8:
return LOG_STR("8x");
case Oversampling::OVERSAMPLING_X16:
return LOG_STR("16x");
case Oversampling::OVERSAMPLING_X32:
return LOG_STR("32x");
default:
return LOG_STR("");
}
}
static const LogString *iir_filter_to_str(IIRFilter filter) {
switch (filter) {
case IIRFilter::IIR_FILTER_OFF:
return LOG_STR("OFF");
case IIRFilter::IIR_FILTER_2:
return LOG_STR("2x");
case IIRFilter::IIR_FILTER_4:
return LOG_STR("4x");
case IIRFilter::IIR_FILTER_8:
return LOG_STR("8x");
case IIRFilter::IIR_FILTER_16:
return LOG_STR("16x");
case IIRFilter::IIR_FILTER_32:
return LOG_STR("32x");
case IIRFilter::IIR_FILTER_64:
return LOG_STR("64x");
case IIRFilter::IIR_FILTER_128:
return LOG_STR("128x");
default:
return LOG_STR("");
}
}
void BMP3XXComponent::setup() {
this->error_code_ = NONE;
ESP_LOGCONFIG(TAG, "Setting up BMP3XX...");
// Call the Device base class "initialise" function
if (!reset()) {
ESP_LOGE(TAG, "Failed to reset BMP3XX...");
this->error_code_ = ERROR_SENSOR_RESET;
this->mark_failed();
}
if (!read_byte(BMP388_CHIP_ID, &this->chip_id_.reg)) {
ESP_LOGE(TAG, "Can't read chip id");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
ESP_LOGCONFIG(TAG, "Chip %s Id 0x%X", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg);
if (chip_id_.reg != BMP388_ID && chip_id_.reg != BMP390_ID) {
ESP_LOGE(TAG, "Unknown chip id - is this really a BMP388 or BMP390?");
this->error_code_ = ERROR_WRONG_CHIP_ID;
this->mark_failed();
return;
}
// set sensor in sleep mode
stop_conversion();
// Read the calibration parameters into the params structure
if (!read_bytes(BMP388_TRIM_PARAMS, (uint8_t *) &compensation_params_, sizeof(compensation_params_))) {
ESP_LOGE(TAG, "Can't read calibration data");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
compensation_float_params_.param_T1 =
(float) compensation_params_.param_T1 / powf(2.0f, -8.0f); // Calculate the floating point trim parameters
compensation_float_params_.param_T2 = (float) compensation_params_.param_T2 / powf(2.0f, 30.0f);
compensation_float_params_.param_T3 = (float) compensation_params_.param_T3 / powf(2.0f, 48.0f);
compensation_float_params_.param_P1 = ((float) compensation_params_.param_P1 - powf(2.0f, 14.0f)) / powf(2.0f, 20.0f);
compensation_float_params_.param_P2 = ((float) compensation_params_.param_P2 - powf(2.0f, 14.0f)) / powf(2.0f, 29.0f);
compensation_float_params_.param_P3 = (float) compensation_params_.param_P3 / powf(2.0f, 32.0f);
compensation_float_params_.param_P4 = (float) compensation_params_.param_P4 / powf(2.0f, 37.0f);
compensation_float_params_.param_P5 = (float) compensation_params_.param_P5 / powf(2.0f, -3.0f);
compensation_float_params_.param_P6 = (float) compensation_params_.param_P6 / powf(2.0f, 6.0f);
compensation_float_params_.param_P7 = (float) compensation_params_.param_P7 / powf(2.0f, 8.0f);
compensation_float_params_.param_P8 = (float) compensation_params_.param_P8 / powf(2.0f, 15.0f);
compensation_float_params_.param_P9 = (float) compensation_params_.param_P9 / powf(2.0f, 48.0f);
compensation_float_params_.param_P10 = (float) compensation_params_.param_P10 / powf(2.0f, 48.0f);
compensation_float_params_.param_P11 = (float) compensation_params_.param_P11 / powf(2.0f, 65.0f);
// Initialise the BMP388 IIR filter register
if (!set_iir_filter(this->iir_filter_)) {
ESP_LOGE(TAG, "Failed to set IIR filter");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
// Set power control registers
pwr_ctrl_.bit.press_en = 1;
pwr_ctrl_.bit.temp_en = 1;
// Disable pressure if no sensor defined
// keep temperature enabled since it's needed for compensation
if (this->pressure_sensor_ == nullptr) {
pwr_ctrl_.bit.press_en = 0;
this->pressure_oversampling_ = OVERSAMPLING_NONE;
}
// just disable oeversampling for temp if not used
if (this->temperature_sensor_ == nullptr) {
this->temperature_oversampling_ = OVERSAMPLING_NONE;
}
// Initialise the BMP388 oversampling register
if (!set_oversampling_register(this->pressure_oversampling_, this->temperature_oversampling_)) {
ESP_LOGE(TAG, "Failed to set oversampling register");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
}
void BMP3XXComponent::dump_config() {
ESP_LOGCONFIG(TAG, "BMP3XX:");
ESP_LOGCONFIG(TAG, " Type: %s (0x%X)", LOG_STR_ARG(chip_type_to_str(this->chip_id_.reg)), this->chip_id_.reg);
LOG_I2C_DEVICE(this);
switch (this->error_code_) {
case NONE:
break;
case ERROR_COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Communication with BMP3XX failed!");
break;
case ERROR_WRONG_CHIP_ID:
ESP_LOGE(
TAG,
"BMP3XX has wrong chip ID (reported id: 0x%X) - please check if you are really using a BMP 388 or BMP 390",
this->chip_id_.reg);
break;
case ERROR_SENSOR_RESET:
ESP_LOGE(TAG, "BMP3XX failed to reset");
break;
default:
ESP_LOGE(TAG, "BMP3XX error code %d", (int) this->error_code_);
break;
}
ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_filter_)));
LOG_UPDATE_INTERVAL(this);
if (this->temperature_sensor_) {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_)));
}
if (this->pressure_sensor_) {
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_)));
}
}
float BMP3XXComponent::get_setup_priority() const { return setup_priority::DATA; }
inline uint8_t oversampling_to_time(Oversampling over_sampling) { return (1 << uint8_t(over_sampling)); }
void BMP3XXComponent::update() {
// Enable sensor
ESP_LOGV(TAG, "Sending conversion request...");
float meas_time = 1.0f;
// Ref: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp390-ds002.pdf 3.9.2
meas_time += 2.02f * oversampling_to_time(this->temperature_oversampling_) + 0.163f;
meas_time += 2.02f * oversampling_to_time(this->pressure_oversampling_) + 0.392f;
meas_time += 0.234f;
if (!set_mode(FORCED_MODE)) {
ESP_LOGE(TAG, "Failed start forced mode");
this->mark_failed();
return;
}
ESP_LOGVV(TAG, "measurement time %d", uint32_t(ceilf(meas_time)));
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
float temperature = 0.0f;
float pressure = 0.0f;
if (this->pressure_sensor_ != nullptr) {
if (!get_measurements(temperature, pressure)) {
ESP_LOGW(TAG, "Failed to read pressure and temperature - skipping update");
this->status_set_warning();
return;
}
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa", temperature, pressure);
} else {
if (!get_temperature(temperature)) {
ESP_LOGW(TAG, "Failed to read temperature - skipping update");
this->status_set_warning();
return;
}
ESP_LOGD(TAG, "Got temperature=%.1f°C", temperature);
}
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->pressure_sensor_ != nullptr)
this->pressure_sensor_->publish_state(pressure);
this->status_clear_warning();
set_mode(SLEEP_MODE);
});
}
// Reset the BMP3XX
uint8_t BMP3XXComponent::reset() {
write_byte(BMP388_CMD, RESET_CODE); // Write the reset code to the command register
// Wait for 10ms
delay(10);
this->read_byte(BMP388_EVENT, &event_.reg); // Read the BMP388's event register
return event_.bit.por_detected; // Return if device reset is complete
}
// Start a one shot measurement in FORCED_MODE
bool BMP3XXComponent::start_forced_conversion() {
// Only set FORCED_MODE if we're already in SLEEP_MODE
if (pwr_ctrl_.bit.mode == SLEEP_MODE) {
return set_mode(FORCED_MODE);
}
return true;
}
// Stop the conversion and return to SLEEP_MODE
bool BMP3XXComponent::stop_conversion() { return set_mode(SLEEP_MODE); }
// Set the pressure oversampling rate
bool BMP3XXComponent::set_pressure_oversampling(Oversampling oversampling) {
osr_.bit.osr_p = oversampling;
return this->write_byte(BMP388_OSR, osr_.reg);
}
// Set the temperature oversampling rate
bool BMP3XXComponent::set_temperature_oversampling(Oversampling oversampling) {
osr_.bit.osr_t = oversampling;
return this->write_byte(BMP388_OSR, osr_.reg);
}
// Set the IIR filter setting
bool BMP3XXComponent::set_iir_filter(IIRFilter iir_filter) {
config_.bit.iir_filter = iir_filter;
return this->write_byte(BMP388_CONFIG, config_.reg);
}
// Get temperature
bool BMP3XXComponent::get_temperature(float &temperature) {
// Check if a measurement is ready
if (!data_ready()) {
return false;
}
uint8_t data[3];
// Read the temperature
if (!this->read_bytes(BMP388_DATA_3, &data[0], 3)) {
ESP_LOGE(TAG, "Failed to read temperature");
return false;
}
// Copy the temperature data into the adc variables
int32_t adc_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
// Temperature compensation (function from BMP388 datasheet)
temperature = bmp388_compensate_temperature_((float) adc_temp);
return true;
}
// Get the pressure
bool BMP3XXComponent::get_pressure(float &pressure) {
float temperature;
return get_measurements(temperature, pressure);
}
// Get temperature and pressure
bool BMP3XXComponent::get_measurements(float &temperature, float &pressure) {
// Check if a measurement is ready
if (!data_ready()) {
ESP_LOGD(TAG, "BMP3XX Get measurement - data not ready skipping update");
return false;
}
uint8_t data[6];
// Read the temperature and pressure data
if (!this->read_bytes(BMP388_DATA_0, &data[0], 6)) {
ESP_LOGE(TAG, "Failed to read measurements");
return false;
}
// Copy the temperature and pressure data into the adc variables
int32_t adc_pres = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
int32_t adc_temp = (int32_t) data[5] << 16 | (int32_t) data[4] << 8 | (int32_t) data[3];
// Temperature compensation (function from BMP388 datasheet)
temperature = bmp388_compensate_temperature_((float) adc_temp);
// Pressure compensation (function from BMP388 datasheet)
pressure = bmp388_compensate_pressure_((float) adc_pres, temperature);
// Calculate the pressure in millibar/hPa
pressure /= 100.0f;
return true;
}
// Set the BMP388's mode in the power control register
bool BMP3XXComponent::set_mode(OperationMode mode) {
pwr_ctrl_.bit.mode = mode;
return this->write_byte(BMP388_PWR_CTRL, pwr_ctrl_.reg);
}
// Set the BMP388 oversampling register
bool BMP3XXComponent::set_oversampling_register(Oversampling pressure_oversampling,
Oversampling temperature_oversampling) {
osr_.reg = temperature_oversampling << 3 | pressure_oversampling;
return this->write_byte(BMP388_OSR, osr_.reg);
}
// Check if measurement data is ready
bool BMP3XXComponent::data_ready() {
// If we're in SLEEP_MODE return immediately
if (pwr_ctrl_.bit.mode == SLEEP_MODE) {
ESP_LOGD(TAG, "Not ready - sensor is in sleep mode");
return false;
}
// Read the interrupt status register
uint8_t status;
if (!this->read_byte(BMP388_INT_STATUS, &status)) {
ESP_LOGE(TAG, "Failed to read status register");
return false;
}
int_status_.reg = status;
ESP_LOGVV(TAG, "data ready status %d", status);
// If we're in FORCED_MODE switch back to SLEEP_MODE
if (int_status_.bit.drdy) {
if (pwr_ctrl_.bit.mode == FORCED_MODE) {
pwr_ctrl_.bit.mode = SLEEP_MODE;
}
return true; // The measurement is ready
}
return false; // The measurement is still pending
}
////////////////////////////////////////////////////////////////////////////////
// Bosch BMP3XXComponent (Private) Member Functions
////////////////////////////////////////////////////////////////////////////////
float BMP3XXComponent::bmp388_compensate_temperature_(float uncomp_temp) {
float partial_data1 = uncomp_temp - compensation_float_params_.param_T1;
float partial_data2 = partial_data1 * compensation_float_params_.param_T2;
return partial_data2 + partial_data1 * partial_data1 * compensation_float_params_.param_T3;
}
float BMP3XXComponent::bmp388_compensate_pressure_(float uncomp_press, float t_lin) {
float partial_data1 = compensation_float_params_.param_P6 * t_lin;
float partial_data2 = compensation_float_params_.param_P7 * t_lin * t_lin;
float partial_data3 = compensation_float_params_.param_P8 * t_lin * t_lin * t_lin;
float partial_out1 = compensation_float_params_.param_P5 + partial_data1 + partial_data2 + partial_data3;
partial_data1 = compensation_float_params_.param_P2 * t_lin;
partial_data2 = compensation_float_params_.param_P3 * t_lin * t_lin;
partial_data3 = compensation_float_params_.param_P4 * t_lin * t_lin * t_lin;
float partial_out2 =
uncomp_press * (compensation_float_params_.param_P1 + partial_data1 + partial_data2 + partial_data3);
partial_data1 = uncomp_press * uncomp_press;
partial_data2 = compensation_float_params_.param_P9 + compensation_float_params_.param_P10 * t_lin;
partial_data3 = partial_data1 * partial_data2;
float partial_data4 =
partial_data3 + uncomp_press * uncomp_press * uncomp_press * compensation_float_params_.param_P11;
return partial_out1 + partial_out2 + partial_data4;
}
} // namespace bmp3xx
} // namespace esphome

View file

@ -0,0 +1,237 @@
/*
based on BMP388_DEV by Martin Lindupp
under MIT License (MIT)
Copyright (C) Martin Lindupp 2020
http://github.com/MartinL1/BMP388_DEV
*/
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace bmp3xx {
static const uint8_t BMP388_ID = 0x50; // The BMP388 device ID
static const uint8_t BMP390_ID = 0x60; // The BMP390 device ID
static const uint8_t RESET_CODE = 0xB6; // The BMP388 reset code
/// BMP388_DEV Registers
enum {
BMP388_CHIP_ID = 0x00, // Chip ID register sub-address
BMP388_ERR_REG = 0x02, // Error register sub-address
BMP388_STATUS = 0x03, // Status register sub-address
BMP388_DATA_0 = 0x04, // Pressure eXtended Least Significant Byte (XLSB) register sub-address
BMP388_DATA_1 = 0x05, // Pressure Least Significant Byte (LSB) register sub-address
BMP388_DATA_2 = 0x06, // Pressure Most Significant Byte (MSB) register sub-address
BMP388_DATA_3 = 0x07, // Temperature eXtended Least Significant Byte (XLSB) register sub-address
BMP388_DATA_4 = 0x08, // Temperature Least Significant Byte (LSB) register sub-address
BMP388_DATA_5 = 0x09, // Temperature Most Significant Byte (MSB) register sub-address
BMP388_SENSORTIME_0 = 0x0C, // Sensor time register 0 sub-address
BMP388_SENSORTIME_1 = 0x0D, // Sensor time register 1 sub-address
BMP388_SENSORTIME_2 = 0x0E, // Sensor time register 2 sub-address
BMP388_EVENT = 0x10, // Event register sub-address
BMP388_INT_STATUS = 0x11, // Interrupt Status register sub-address
BMP388_INT_CTRL = 0x19, // Interrupt Control register sub-address
BMP388_IF_CONFIG = 0x1A, // Interface Configuration register sub-address
BMP388_PWR_CTRL = 0x1B, // Power Control register sub-address
BMP388_OSR = 0x1C, // Oversampling register sub-address
BMP388_ODR = 0x1D, // Output Data Rate register sub-address
BMP388_CONFIG = 0x1F, // Configuration register sub-address
BMP388_TRIM_PARAMS = 0x31, // Trim parameter registers' base sub-address
BMP388_CMD = 0x7E // Command register sub-address
};
/// Device mode bitfield in the control and measurement register
enum OperationMode { SLEEP_MODE = 0x00, FORCED_MODE = 0x01, NORMAL_MODE = 0x03 };
/// Oversampling bit fields in the control and measurement register
enum Oversampling {
OVERSAMPLING_NONE = 0x00,
OVERSAMPLING_X2 = 0x01,
OVERSAMPLING_X4 = 0x02,
OVERSAMPLING_X8 = 0x03,
OVERSAMPLING_X16 = 0x04,
OVERSAMPLING_X32 = 0x05
};
/// Infinite Impulse Response (IIR) filter bit field in the configuration register
enum IIRFilter {
IIR_FILTER_OFF = 0x00,
IIR_FILTER_2 = 0x01,
IIR_FILTER_4 = 0x02,
IIR_FILTER_8 = 0x03,
IIR_FILTER_16 = 0x04,
IIR_FILTER_32 = 0x05,
IIR_FILTER_64 = 0x06,
IIR_FILTER_128 = 0x07
};
/// This class implements support for the BMP3XX Temperature+Pressure i2c sensor.
class BMP3XXComponent : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
/// Set the oversampling value for the temperature sensor. Default is 16x.
void set_temperature_oversampling_config(Oversampling temperature_oversampling) {
this->temperature_oversampling_ = temperature_oversampling;
}
/// Set the oversampling value for the pressure sensor. Default is 16x.
void set_pressure_oversampling_config(Oversampling pressure_oversampling) {
this->pressure_oversampling_ = pressure_oversampling;
}
/// Set the IIR Filter used to increase accuracy, defaults to no IIR Filter.
void set_iir_filter_config(IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
/// Soft reset the sensor
uint8_t reset();
/// Start continuous measurement in NORMAL_MODE
bool start_normal_conversion();
/// Start a one shot measurement in FORCED_MODE
bool start_forced_conversion();
/// Stop the conversion and return to SLEEP_MODE
bool stop_conversion();
/// Set the pressure oversampling: OFF, X1, X2, X4, X8, X16, X32
bool set_pressure_oversampling(Oversampling pressure_oversampling);
/// Set the temperature oversampling: OFF, X1, X2, X4, X8, X16, X32
bool set_temperature_oversampling(Oversampling temperature_oversampling);
/// Set the IIR filter setting: OFF, 2, 3, 8, 16, 32
bool set_iir_filter(IIRFilter iir_filter);
/// Get a temperature measurement
bool get_temperature(float &temperature);
/// Get a pressure measurement
bool get_pressure(float &pressure);
/// Get a temperature and pressure measurement
bool get_measurements(float &temperature, float &pressure);
/// Get a temperature and pressure measurement
bool get_measurement();
/// Set the barometer mode
bool set_mode(OperationMode mode);
/// Set the BMP388 oversampling register
bool set_oversampling_register(Oversampling pressure_oversampling, Oversampling temperature_oversampling);
/// Checks if a measurement is ready
bool data_ready();
protected:
Oversampling temperature_oversampling_{OVERSAMPLING_X16};
Oversampling pressure_oversampling_{OVERSAMPLING_X16};
IIRFilter iir_filter_{IIR_FILTER_OFF};
OperationMode operation_mode_{FORCED_MODE};
sensor::Sensor *temperature_sensor_;
sensor::Sensor *pressure_sensor_;
enum ErrorCode {
NONE = 0,
ERROR_COMMUNICATION_FAILED,
ERROR_WRONG_CHIP_ID,
ERROR_SENSOR_STATUS,
ERROR_SENSOR_RESET,
} error_code_{NONE};
struct { // The BMP388 compensation trim parameters (coefficients)
uint16_t param_T1;
uint16_t param_T2;
int8_t param_T3;
int16_t param_P1;
int16_t param_P2;
int8_t param_P3;
int8_t param_P4;
uint16_t param_P5;
uint16_t param_P6;
int8_t param_P7;
int8_t param_P8;
int16_t param_P9;
int8_t param_P10;
int8_t param_P11;
} __attribute__((packed)) compensation_params_;
struct FloatParams { // The BMP388 float point compensation trim parameters
float param_T1;
float param_T2;
float param_T3;
float param_P1;
float param_P2;
float param_P3;
float param_P4;
float param_P5;
float param_P6;
float param_P7;
float param_P8;
float param_P9;
float param_P10;
float param_P11;
} compensation_float_params_;
union { // Copy of the BMP388's chip id register
struct {
uint8_t chip_id_nvm : 4;
uint8_t chip_id_fixed : 4;
} bit;
uint8_t reg;
} chip_id_ = {.reg = 0};
union { // Copy of the BMP388's event register
struct {
uint8_t por_detected : 1;
} bit;
uint8_t reg;
} event_ = {.reg = 0};
union { // Copy of the BMP388's interrupt status register
struct {
uint8_t fwm_int : 1;
uint8_t ffull_int : 1;
uint8_t : 1;
uint8_t drdy : 1;
} bit;
uint8_t reg;
} int_status_ = {.reg = 0};
union { // Copy of the BMP388's power control register
struct {
uint8_t press_en : 1;
uint8_t temp_en : 1;
uint8_t : 2;
uint8_t mode : 2;
} bit;
uint8_t reg;
} pwr_ctrl_ = {.reg = 0};
union { // Copy of the BMP388's oversampling register
struct {
uint8_t osr_p : 3;
uint8_t osr_t : 3;
} bit;
uint8_t reg;
} osr_ = {.reg = 0};
union { // Copy of the BMP388's output data rate register
struct {
uint8_t odr_sel : 5;
} bit;
uint8_t reg;
} odr_ = {.reg = 0};
union { // Copy of the BMP388's configuration register
struct {
uint8_t : 1;
uint8_t iir_filter : 3;
} bit;
uint8_t reg;
} config_ = {.reg = 0};
// Bosch temperature compensation function
float bmp388_compensate_temperature_(float uncomp_temp);
// Bosch pressure compensation function
float bmp388_compensate_pressure_(float uncomp_press, float t_lin);
};
} // namespace bmp3xx
} // namespace esphome

View file

@ -0,0 +1,100 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_IIR_FILTER,
CONF_OVERSAMPLING,
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
)
CODEOWNERS = ["@martgras"]
DEPENDENCIES = ["i2c"]
bmp3xx_ns = cg.esphome_ns.namespace("bmp3xx")
Oversampling = bmp3xx_ns.enum("Oversampling")
OVERSAMPLING_OPTIONS = {
"NONE": Oversampling.OVERSAMPLING_NONE,
"2X": Oversampling.OVERSAMPLING_X2,
"4X": Oversampling.OVERSAMPLING_X4,
"8X": Oversampling.OVERSAMPLING_X8,
"16X": Oversampling.OVERSAMPLING_X16,
"32x": Oversampling.OVERSAMPLING_X32,
}
IIRFilter = bmp3xx_ns.enum("IIRFilter")
IIR_FILTER_OPTIONS = {
"OFF": IIRFilter.IIR_FILTER_OFF,
"2X": IIRFilter.IIR_FILTER_2,
"4X": IIRFilter.IIR_FILTER_4,
"8X": IIRFilter.IIR_FILTER_8,
"16X": IIRFilter.IIR_FILTER_16,
"32X": IIRFilter.IIR_FILTER_32,
"64X": IIRFilter.IIR_FILTER_64,
"128X": IIRFilter.IIR_FILTER_128,
}
BMP3XXComponent = bmp3xx_ns.class_(
"BMP3XXComponent", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BMP3XXComponent),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="2X"): cv.enum(
OVERSAMPLING_OPTIONS, upper=True
),
}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
OVERSAMPLING_OPTIONS, upper=True
),
}
),
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
IIR_FILTER_OPTIONS, upper=True
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x77))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_iir_filter_config(config[CONF_IIR_FILTER]))
if CONF_TEMPERATURE in config:
conf = config[CONF_TEMPERATURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling_config(conf[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config:
conf = config[CONF_PRESSURE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling_config(conf[CONF_OVERSAMPLING]))

View file

@ -0,0 +1,127 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.components import mqtt
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_ON_PRESS,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True
DEVICE_CLASSES = [
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
]
button_ns = cg.esphome_ns.namespace("button")
Button = button_ns.class_("Button", cg.EntityBase)
ButtonPtr = Button.operator("ptr")
PressAction = button_ns.class_("PressAction", automation.Action)
ButtonPressTrigger = button_ns.class_(
"ButtonPressTrigger", automation.Trigger.template()
)
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
}
),
}
)
_UNDEF = object()
def button_schema(
icon: str = _UNDEF,
entity_category: str = _UNDEF,
device_class: str = _UNDEF,
) -> cv.Schema:
schema = BUTTON_SCHEMA
if icon is not _UNDEF:
schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon})
if entity_category is not _UNDEF:
schema = schema.extend(
{
cv.Optional(
CONF_ENTITY_CATEGORY, default=entity_category
): cv.entity_category
}
)
if device_class is not _UNDEF:
schema = schema.extend(
{
cv.Optional(
CONF_DEVICE_CLASS, default=device_class
): validate_device_class
}
)
return schema
async def setup_button_core_(var, config):
await setup_entity(var, config)
for conf in config.get(CONF_ON_PRESS, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if CONF_DEVICE_CLASS in config:
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
await mqtt.register_mqtt_component(mqtt_, config)
async def register_button(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_button(var))
await setup_button_core_(var, config)
async def new_button(config):
var = cg.new_Pvariable(config[CONF_ID])
await register_button(var, config)
return var
BUTTON_PRESS_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(Button),
}
)
@automation.register_action("button.press", PressAction, BUTTON_PRESS_SCHEMA)
async def button_press_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(button_ns.using)
cg.add_define("USE_BUTTON")

View file

@ -0,0 +1,28 @@
#pragma once
#include "esphome/components/button/button.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
namespace esphome {
namespace button {
template<typename... Ts> class PressAction : public Action<Ts...> {
public:
explicit PressAction(Button *button) : button_(button) {}
void play(Ts... x) override { this->button_->press(); }
protected:
Button *button_;
};
class ButtonPressTrigger : public Trigger<> {
public:
ButtonPressTrigger(Button *button) {
button->add_on_press_callback([this]() { this->trigger(); });
}
};
} // namespace button
} // namespace esphome

View file

@ -0,0 +1,28 @@
#include "button.h"
#include "esphome/core/log.h"
namespace esphome {
namespace button {
static const char *const TAG = "button";
Button::Button(const std::string &name) : EntityBase(name) {}
Button::Button() : Button("") {}
void Button::press() {
ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
this->press_action();
this->press_callback_.call();
}
void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); }
uint32_t Button::hash_base() { return 1495763804UL; }
void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
std::string Button::get_device_class() {
if (this->device_class_.has_value())
return *this->device_class_;
return "";
}
} // namespace button
} // namespace esphome

View file

@ -0,0 +1,57 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace button {
#define LOG_BUTTON(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
}
/** Base class for all buttons.
*
* A button is just a momentary switch that does not have a state, only a trigger.
*/
class Button : public EntityBase {
public:
explicit Button();
explicit Button(const std::string &name);
/** Press this button. This is called by the front-end.
*
* For implementing buttons, please override press_action.
*/
void press();
/** Set callback for state changes.
*
* @param callback The void() callback.
*/
void add_on_press_callback(std::function<void()> &&callback);
/// Set the Home Assistant device class (see button::device_class).
void set_device_class(const std::string &device_class);
/// Get the device class for this button.
std::string get_device_class();
protected:
/** You should implement this virtual method if you want to create your own button.
*/
virtual void press_action(){};
uint32_t hash_base() override;
CallbackManager<void()> press_callback_{};
optional<std::string> device_class_{};
};
} // namespace button
} // namespace esphome

View 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)

View 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))

View 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

View 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

View file

@ -36,3 +36,5 @@ async def to_code(config):
if CORE.is_esp32: if CORE.is_esp32:
cg.add_library("DNSServer", None) cg.add_library("DNSServer", None)
cg.add_library("WiFi", None) cg.add_library("WiFi", None)
if CORE.is_esp8266:
cg.add_library("DNSServer", None)

View file

@ -67,6 +67,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str()); ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.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->save_wifi_sta(ssid, psk);
wifi::global_wifi_component->start_scanning();
request->redirect("/?save=true"); request->redirect("/?save=true");
} }
@ -84,14 +85,7 @@ void CaptivePortal::start() {
this->dns_server_->start(53, "*", (uint32_t) ip); this->dns_server_->start(53, "*", (uint32_t) ip);
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
bool not_found = false; if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
if (!this->active_) {
not_found = true;
} else if (req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) {
not_found = true;
}
if (not_found) {
req->send(404, "text/html", "File not found"); req->send(404, "text/html", "File not found");
return; return;
} }

View file

@ -52,7 +52,7 @@ void CCS811Component::setup() {
if (this->baseline_.has_value()) { if (this->baseline_.has_value()) {
// baseline available, write to sensor // 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); auto hardware_version_data = this->read_bytes<1>(0x21);

View file

@ -0,0 +1,53 @@
import esphome.codegen as cg
from esphome import pins
import esphome.config_validation as cv
from esphome.const import (
CONF_DELAY,
CONF_ID,
)
CODEOWNERS = ["@asoehlke"]
AUTO_LOAD = ["sensor", "voltage_sampler"]
cd74hc4067_ns = cg.esphome_ns.namespace("cd74hc4067")
CD74HC4067Component = cd74hc4067_ns.class_(
"CD74HC4067Component", cg.Component, cg.PollingComponent
)
CONF_PIN_S0 = "pin_s0"
CONF_PIN_S1 = "pin_s1"
CONF_PIN_S2 = "pin_s2"
CONF_PIN_S3 = "pin_s3"
DEFAULT_DELAY = "2ms"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(CD74HC4067Component),
cv.Required(CONF_PIN_S0): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_PIN_S1): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_PIN_S2): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_PIN_S3): pins.internal_gpio_output_pin_schema,
cv.Optional(
CONF_DELAY, default=DEFAULT_DELAY
): cv.positive_time_period_milliseconds,
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
pin_s0 = await cg.gpio_pin_expression(config[CONF_PIN_S0])
cg.add(var.set_pin_s0(pin_s0))
pin_s1 = await cg.gpio_pin_expression(config[CONF_PIN_S1])
cg.add(var.set_pin_s1(pin_s1))
pin_s2 = await cg.gpio_pin_expression(config[CONF_PIN_S2])
cg.add(var.set_pin_s2(pin_s2))
pin_s3 = await cg.gpio_pin_expression(config[CONF_PIN_S3])
cg.add(var.set_pin_s3(pin_s3))
cg.add(var.set_switch_delay(config[CONF_DELAY]))

View file

@ -0,0 +1,86 @@
#include "cd74hc4067.h"
#include "esphome/core/log.h"
namespace esphome {
namespace cd74hc4067 {
static const char *const TAG = "cd74hc4067";
float CD74HC4067Component::get_setup_priority() const { return setup_priority::DATA; }
void CD74HC4067Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up CD74HC4067...");
this->pin_s0_->setup();
this->pin_s1_->setup();
this->pin_s2_->setup();
this->pin_s3_->setup();
// set other pin, so that activate_pin will really switch
this->active_pin_ = 1;
this->activate_pin(0);
}
void CD74HC4067Component::dump_config() {
ESP_LOGCONFIG(TAG, "CD74HC4067 Multiplexer:");
LOG_PIN(" S0 Pin: ", this->pin_s0_);
LOG_PIN(" S1 Pin: ", this->pin_s1_);
LOG_PIN(" S2 Pin: ", this->pin_s2_);
LOG_PIN(" S3 Pin: ", this->pin_s3_);
ESP_LOGCONFIG(TAG, "switch delay: %d", this->switch_delay_);
}
void CD74HC4067Component::activate_pin(uint8_t pin) {
if (this->active_pin_ != pin) {
ESP_LOGD(TAG, "switch to input %d", pin);
static int mux_channel[16][4] = {
{0, 0, 0, 0}, // channel 0
{1, 0, 0, 0}, // channel 1
{0, 1, 0, 0}, // channel 2
{1, 1, 0, 0}, // channel 3
{0, 0, 1, 0}, // channel 4
{1, 0, 1, 0}, // channel 5
{0, 1, 1, 0}, // channel 6
{1, 1, 1, 0}, // channel 7
{0, 0, 0, 1}, // channel 8
{1, 0, 0, 1}, // channel 9
{0, 1, 0, 1}, // channel 10
{1, 1, 0, 1}, // channel 11
{0, 0, 1, 1}, // channel 12
{1, 0, 1, 1}, // channel 13
{0, 1, 1, 1}, // channel 14
{1, 1, 1, 1} // channel 15
};
this->pin_s0_->digital_write(mux_channel[pin][0]);
this->pin_s1_->digital_write(mux_channel[pin][1]);
this->pin_s2_->digital_write(mux_channel[pin][2]);
this->pin_s3_->digital_write(mux_channel[pin][3]);
// small delay is needed to let the multiplexer switch
delay(this->switch_delay_);
this->active_pin_ = pin;
}
}
CD74HC4067Sensor::CD74HC4067Sensor(CD74HC4067Component *parent) : parent_(parent) {}
void CD74HC4067Sensor::update() {
float value_v = this->sample();
this->publish_state(value_v);
}
float CD74HC4067Sensor::get_setup_priority() const { return this->parent_->get_setup_priority() - 1.0f; }
float CD74HC4067Sensor::sample() {
this->parent_->activate_pin(this->pin_);
return this->source_->sample();
}
void CD74HC4067Sensor::dump_config() {
LOG_SENSOR(TAG, "CD74HC4067 Sensor", this);
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
LOG_UPDATE_INTERVAL(this);
}
} // namespace cd74hc4067
} // namespace esphome

View file

@ -0,0 +1,65 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
namespace esphome {
namespace cd74hc4067 {
class CD74HC4067Component : public Component {
public:
/// Set up the internal sensor array.
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
/// setting pin active by setting the right combination of the four multiplexer input pins
void activate_pin(uint8_t pin);
/// set the pin connected to multiplexer control pin 0
void set_pin_s0(InternalGPIOPin *pin) { this->pin_s0_ = pin; }
/// set the pin connected to multiplexer control pin 1
void set_pin_s1(InternalGPIOPin *pin) { this->pin_s1_ = pin; }
/// set the pin connected to multiplexer control pin 2
void set_pin_s2(InternalGPIOPin *pin) { this->pin_s2_ = pin; }
/// set the pin connected to multiplexer control pin 3
void set_pin_s3(InternalGPIOPin *pin) { this->pin_s3_ = pin; }
/// set the delay needed after an input switch
void set_switch_delay(uint32_t switch_delay) { this->switch_delay_ = switch_delay; }
private:
InternalGPIOPin *pin_s0_;
InternalGPIOPin *pin_s1_;
InternalGPIOPin *pin_s2_;
InternalGPIOPin *pin_s3_;
/// the currently active pin
uint8_t active_pin_;
uint32_t switch_delay_;
};
class CD74HC4067Sensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
public:
CD74HC4067Sensor(CD74HC4067Component *parent);
void update() override;
void dump_config() override;
/// `HARDWARE_LATE` setup priority.
float get_setup_priority() const override;
void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_source(voltage_sampler::VoltageSampler *source) { this->source_ = source; }
float sample() override;
protected:
CD74HC4067Component *parent_;
/// The sampling source to read values from.
voltage_sampler::VoltageSampler *source_;
uint8_t pin_;
};
} // namespace cd74hc4067
} // namespace esphome

View file

@ -0,0 +1,55 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, voltage_sampler
from esphome.const import (
CONF_ID,
CONF_SENSOR,
CONF_NUMBER,
ICON_FLASH,
UNIT_VOLT,
STATE_CLASS_MEASUREMENT,
DEVICE_CLASS_VOLTAGE,
)
from . import cd74hc4067_ns, CD74HC4067Component
DEPENDENCIES = ["cd74hc4067"]
CD74HC4067Sensor = cd74hc4067_ns.class_(
"CD74HC4067Sensor",
sensor.Sensor,
cg.PollingComponent,
voltage_sampler.VoltageSampler,
)
CONF_CD74HC4067_ID = "cd74hc4067_id"
CONFIG_SCHEMA = (
sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
icon=ICON_FLASH,
)
.extend(
{
cv.GenerateID(): cv.declare_id(CD74HC4067Sensor),
cv.GenerateID(CONF_CD74HC4067_ID): cv.use_id(CD74HC4067Component),
cv.Required(CONF_NUMBER): cv.int_range(0, 15),
cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler),
}
)
.extend(cv.polling_component_schema("60s"))
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_CD74HC4067_ID])
var = cg.new_Pvariable(config[CONF_ID], parent)
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
cg.add(var.set_pin(config[CONF_NUMBER]))
sens = await cg.get_variable(config[CONF_SENSOR])
cg.add(var.set_source(sens))

View file

@ -20,6 +20,7 @@ from esphome.const import (
CONF_MODE, CONF_MODE,
CONF_MODE_COMMAND_TOPIC, CONF_MODE_COMMAND_TOPIC,
CONF_MODE_STATE_TOPIC, CONF_MODE_STATE_TOPIC,
CONF_ON_STATE,
CONF_PRESET, CONF_PRESET,
CONF_SWING_MODE, CONF_SWING_MODE,
CONF_SWING_MODE_COMMAND_TOPIC, CONF_SWING_MODE_COMMAND_TOPIC,
@ -34,6 +35,7 @@ from esphome.const import (
CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC, CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC,
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC, CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC,
CONF_TEMPERATURE_STEP, CONF_TEMPERATURE_STEP,
CONF_TRIGGER_ID,
CONF_VISUAL, CONF_VISUAL,
CONF_MQTT_ID, CONF_MQTT_ID,
) )
@ -101,6 +103,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
# Actions # Actions
ControlAction = climate_ns.class_("ControlAction", automation.Action) 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( 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.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic cv.requires_component("mqtt"), cv.publish_topic
), ),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
} }
) )
@ -205,7 +213,7 @@ async def setup_climate_core_(var, config):
if CONF_MODE_COMMAND_TOPIC in config: if CONF_MODE_COMMAND_TOPIC in config:
cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC])) cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC]))
if CONF_MODE_STATE_TOPIC in config: if CONF_MODE_STATE_TOPIC in config:
cg.add(mqtt_.set_custom_state_topic(config[CONF_MODE_STATE_TOPIC])) cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC]))
if CONF_SWING_MODE_COMMAND_TOPIC in config: if CONF_SWING_MODE_COMMAND_TOPIC in config:
cg.add( cg.add(
@ -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): async def register_climate(var, config):
if not CORE.has_id(config[CONF_ID]): if not CORE.has_id(config[CONF_ID]):

View file

@ -42,5 +42,12 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
Climate *climate_; Climate *climate_;
}; };
class StateTrigger : public Trigger<> {
public:
StateTrigger(Climate *climate) {
climate->add_on_state_callback([this]() { this->trigger(); });
}
};
} // namespace climate } // namespace climate
} // namespace esphome } // namespace esphome

View file

@ -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) { void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) {
this->visual_temperature_step_override_ = 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) {} Climate::Climate(const std::string &name) : EntityBase(name) {}
#pragma GCC diagnostic pop
Climate::Climate() : Climate("") {} Climate::Climate() : Climate("") {}
ClimateCall Climate::make_call() { return ClimateCall(this); } ClimateCall Climate::make_call() { return ClimateCall(this); }

View file

@ -10,21 +10,22 @@ climate::ClimateTraits ClimateIR::traits() {
auto traits = climate::ClimateTraits(); auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(this->sensor_ != nullptr); traits.set_supports_current_temperature(this->sensor_ != nullptr);
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
if (supports_cool_) if (this->supports_cool_)
traits.add_supported_mode(climate::CLIMATE_MODE_COOL); traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
if (supports_heat_) if (this->supports_heat_)
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
if (supports_dry_) if (this->supports_dry_)
traits.add_supported_mode(climate::CLIMATE_MODE_DRY); traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
if (supports_fan_only_) if (this->supports_fan_only_)
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
traits.set_supports_two_point_target_temperature(false); traits.set_supports_two_point_target_temperature(false);
traits.set_visual_min_temperature(this->minimum_temperature_); traits.set_visual_min_temperature(this->minimum_temperature_);
traits.set_visual_max_temperature(this->maximum_temperature_); traits.set_visual_max_temperature(this->maximum_temperature_);
traits.set_visual_temperature_step(this->temperature_step_); traits.set_visual_temperature_step(this->temperature_step_);
traits.set_supported_fan_modes(fan_modes_); traits.set_supported_fan_modes(this->fan_modes_);
traits.set_supported_swing_modes(swing_modes_); traits.set_supported_swing_modes(this->swing_modes_);
traits.set_supported_presets(this->presets_);
return traits; return traits;
} }
@ -50,6 +51,7 @@ void ClimateIR::setup() {
roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_)); roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_));
this->fan_mode = climate::CLIMATE_FAN_AUTO; this->fan_mode = climate::CLIMATE_FAN_AUTO;
this->swing_mode = climate::CLIMATE_SWING_OFF; this->swing_mode = climate::CLIMATE_SWING_OFF;
this->preset = climate::CLIMATE_PRESET_NONE;
} }
// Never send nan to HA // Never send nan to HA
if (std::isnan(this->target_temperature)) if (std::isnan(this->target_temperature))
@ -65,6 +67,8 @@ void ClimateIR::control(const climate::ClimateCall &call) {
this->fan_mode = *call.get_fan_mode(); this->fan_mode = *call.get_fan_mode();
if (call.get_swing_mode().has_value()) if (call.get_swing_mode().has_value())
this->swing_mode = *call.get_swing_mode(); this->swing_mode = *call.get_swing_mode();
if (call.get_preset().has_value())
this->preset = *call.get_preset();
this->transmit_state(); this->transmit_state();
this->publish_state(); this->publish_state();
} }

View file

@ -22,7 +22,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
public: public:
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f, ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {}, bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {},
std::set<climate::ClimateSwingMode> swing_modes = {}) { std::set<climate::ClimateSwingMode> swing_modes = {}, std::set<climate::ClimatePreset> presets = {}) {
this->minimum_temperature_ = minimum_temperature; this->minimum_temperature_ = minimum_temperature;
this->maximum_temperature_ = maximum_temperature; this->maximum_temperature_ = maximum_temperature;
this->temperature_step_ = temperature_step; this->temperature_step_ = temperature_step;
@ -30,6 +30,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
this->supports_fan_only_ = supports_fan_only; this->supports_fan_only_ = supports_fan_only;
this->fan_modes_ = std::move(fan_modes); this->fan_modes_ = std::move(fan_modes);
this->swing_modes_ = std::move(swing_modes); this->swing_modes_ = std::move(swing_modes);
this->presets_ = std::move(presets);
} }
void setup() override; void setup() override;
@ -61,6 +62,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
bool supports_fan_only_{false}; bool supports_fan_only_{false};
std::set<climate::ClimateFanMode> fan_modes_ = {}; std::set<climate::ClimateFanMode> fan_modes_ = {};
std::set<climate::ClimateSwingMode> swing_modes_ = {}; std::set<climate::ClimateSwingMode> swing_modes_ = {};
std::set<climate::ClimatePreset> presets_ = {};
remote_transmitter::RemoteTransmitterComponent *transmitter_; remote_transmitter::RemoteTransmitterComponent *transmitter_;
sensor::Sensor *sensor_{nullptr}; sensor::Sensor *sensor_{nullptr};

View file

@ -1,4 +1,5 @@
#include "coolix.h" #include "coolix.h"
#include "esphome/components/remote_base/coolix_protocol.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome { namespace esphome {
@ -6,29 +7,29 @@ namespace coolix {
static const char *const TAG = "coolix.climate"; static const char *const TAG = "coolix.climate";
const uint32_t COOLIX_OFF = 0xB27BE0; static const uint32_t COOLIX_OFF = 0xB27BE0;
const uint32_t COOLIX_SWING = 0xB26BE0; static const uint32_t COOLIX_SWING = 0xB26BE0;
const uint32_t COOLIX_LED = 0xB5F5A5; static const uint32_t COOLIX_LED = 0xB5F5A5;
const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6; static const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6;
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore. // On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
const uint8_t COOLIX_COOL = 0b0000; static const uint8_t COOLIX_COOL = 0b0000;
const uint8_t COOLIX_DRY_FAN = 0b0100; static const uint8_t COOLIX_DRY_FAN = 0b0100;
const uint8_t COOLIX_AUTO = 0b1000; static const uint8_t COOLIX_AUTO = 0b1000;
const uint8_t COOLIX_HEAT = 0b1100; static const uint8_t COOLIX_HEAT = 0b1100;
const uint32_t COOLIX_MODE_MASK = 0b1100; static const uint32_t COOLIX_MODE_MASK = 0b1100;
const uint32_t COOLIX_FAN_MASK = 0xF000; static const uint32_t COOLIX_FAN_MASK = 0xF000;
const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000; static const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000;
const uint32_t COOLIX_FAN_AUTO = 0xB000; static const uint32_t COOLIX_FAN_AUTO = 0xB000;
const uint32_t COOLIX_FAN_MIN = 0x9000; static const uint32_t COOLIX_FAN_MIN = 0x9000;
const uint32_t COOLIX_FAN_MED = 0x5000; static const uint32_t COOLIX_FAN_MED = 0x5000;
const uint32_t COOLIX_FAN_MAX = 0x3000; static const uint32_t COOLIX_FAN_MAX = 0x3000;
// Temperature // Temperature
const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1; static const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1;
const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000; // Part of Fan Mode. static const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000; // Part of Fan Mode.
const uint32_t COOLIX_TEMP_MASK = 0b11110000; static const uint32_t COOLIX_TEMP_MASK = 0b11110000;
const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = { static const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
0b00000000, // 17C 0b00000000, // 17C
0b00010000, // 18c 0b00010000, // 18c
0b00110000, // 19C 0b00110000, // 19C
@ -45,17 +46,6 @@ const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
0b10110000 // 30C 0b10110000 // 30C
}; };
// Constants
static const uint32_t BIT_MARK_US = 660;
static const uint32_t HEADER_MARK_US = 560 * 8;
static const uint32_t HEADER_SPACE_US = 560 * 8;
static const uint32_t BIT_ONE_SPACE_US = 1500;
static const uint32_t BIT_ZERO_SPACE_US = 450;
static const uint32_t FOOTER_MARK_US = BIT_MARK_US;
static const uint32_t FOOTER_SPACE_US = HEADER_SPACE_US;
const uint16_t COOLIX_BITS = 24;
void CoolixClimate::transmit_state() { void CoolixClimate::transmit_state() {
uint32_t remote_state = 0xB20F00; uint32_t remote_state = 0xB20F00;
@ -111,119 +101,60 @@ void CoolixClimate::transmit_state() {
} }
} }
} }
ESP_LOGV(TAG, "Sending coolix code: 0x%02X", remote_state); ESP_LOGV(TAG, "Sending coolix code: 0x%06X", remote_state);
auto transmit = this->transmitter_->transmit(); auto transmit = this->transmitter_->transmit();
auto data = transmit.get_data(); auto data = transmit.get_data();
remote_base::CoolixProtocol().encode(data, remote_state);
data->set_carrier_frequency(38000);
uint16_t repeat = 1;
for (uint16_t r = 0; r <= repeat; r++) {
// Header
data->mark(HEADER_MARK_US);
data->space(HEADER_SPACE_US);
// Data
// Break data into bytes, starting at the Most Significant
// Byte. Each byte then being sent normal, then followed inverted.
for (uint16_t i = 8; i <= COOLIX_BITS; i += 8) {
// Grab a bytes worth of data.
uint8_t byte = (remote_state >> (COOLIX_BITS - i)) & 0xFF;
// Normal
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
data->mark(BIT_MARK_US);
data->space((byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
}
// Inverted
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
data->mark(BIT_MARK_US);
data->space(!(byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
}
}
// Footer
data->mark(BIT_MARK_US);
data->space(FOOTER_SPACE_US); // Pause before repeating
}
transmit.perform(); transmit.perform();
} }
bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) { bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data) {
auto decoded = remote_base::CoolixProtocol().decode(data);
if (!decoded.has_value())
return false;
// Decoded remote state y 3 bytes long code. // Decoded remote state y 3 bytes long code.
uint32_t remote_state = 0; uint32_t remote_state = *decoded;
// The protocol sends the data twice, read here ESP_LOGV(TAG, "Decoded 0x%06X", remote_state);
uint32_t loop_read; if ((remote_state & 0xFF0000) != 0xB20000)
for (uint16_t loop = 1; loop <= 2; loop++) {
if (!data.expect_item(HEADER_MARK_US, HEADER_SPACE_US))
return false;
loop_read = 0;
for (uint8_t a_byte = 0; a_byte < 3; a_byte++) {
uint8_t byte = 0;
for (int8_t a_bit = 7; a_bit >= 0; a_bit--) {
if (data.expect_item(BIT_MARK_US, BIT_ONE_SPACE_US))
byte |= 1 << a_bit;
else if (!data.expect_item(BIT_MARK_US, BIT_ZERO_SPACE_US))
return false;
}
// Need to see this segment inverted
for (int8_t a_bit = 7; a_bit >= 0; a_bit--) {
bool bit = byte & (1 << a_bit);
if (!data.expect_item(BIT_MARK_US, bit ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US))
return false;
}
// Receiving MSB first: reorder bytes
loop_read |= byte << ((2 - a_byte) * 8);
}
// Footer Mark
if (!data.expect_mark(BIT_MARK_US))
return false;
if (loop == 1) {
// Back up state on first loop
remote_state = loop_read;
if (!data.expect_space(FOOTER_SPACE_US))
return false;
}
}
ESP_LOGV(TAG, "Decoded 0x%02X", remote_state);
if (remote_state != loop_read || (remote_state & 0xFF0000) != 0xB20000)
return false; return false;
if (remote_state == COOLIX_OFF) { if (remote_state == COOLIX_OFF) {
this->mode = climate::CLIMATE_MODE_OFF; parent->mode = climate::CLIMATE_MODE_OFF;
} else if (remote_state == COOLIX_SWING) { } else if (remote_state == COOLIX_SWING) {
this->swing_mode = parent->swing_mode =
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; parent->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
} else { } else {
if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT) if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT)
this->mode = climate::CLIMATE_MODE_HEAT; parent->mode = climate::CLIMATE_MODE_HEAT;
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO) else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO)
this->mode = climate::CLIMATE_MODE_HEAT_COOL; parent->mode = climate::CLIMATE_MODE_HEAT_COOL;
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) { else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY) if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY)
this->mode = climate::CLIMATE_MODE_DRY; parent->mode = climate::CLIMATE_MODE_DRY;
else else
this->mode = climate::CLIMATE_MODE_FAN_ONLY; parent->mode = climate::CLIMATE_MODE_FAN_ONLY;
} else } else
this->mode = climate::CLIMATE_MODE_COOL; parent->mode = climate::CLIMATE_MODE_COOL;
// Fan Speed // Fan Speed
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL || if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || parent->mode == climate::CLIMATE_MODE_HEAT_COOL ||
this->mode == climate::CLIMATE_MODE_DRY) parent->mode == climate::CLIMATE_MODE_DRY)
this->fan_mode = climate::CLIMATE_FAN_AUTO; parent->fan_mode = climate::CLIMATE_FAN_AUTO;
else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN) else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN)
this->fan_mode = climate::CLIMATE_FAN_LOW; parent->fan_mode = climate::CLIMATE_FAN_LOW;
else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED) else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED)
this->fan_mode = climate::CLIMATE_FAN_MEDIUM; parent->fan_mode = climate::CLIMATE_FAN_MEDIUM;
else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX) else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX)
this->fan_mode = climate::CLIMATE_FAN_HIGH; parent->fan_mode = climate::CLIMATE_FAN_HIGH;
// Temperature // Temperature
uint8_t temperature_code = remote_state & COOLIX_TEMP_MASK; uint8_t temperature_code = remote_state & COOLIX_TEMP_MASK;
for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++) for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++)
if (COOLIX_TEMP_MAP[i] == temperature_code) if (COOLIX_TEMP_MAP[i] == temperature_code)
this->target_temperature = i + COOLIX_TEMP_MIN; parent->target_temperature = i + COOLIX_TEMP_MIN;
} }
this->publish_state(); parent->publish_state();
return true; return true;
} }

View file

@ -26,11 +26,15 @@ class CoolixClimate : public climate_ir::ClimateIR {
climate_ir::ClimateIR::control(call); climate_ir::ClimateIR::control(call);
} }
/// This static method can be used in other climate components that accept the Coolix protocol. See midea_ir for
/// example.
static bool on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data);
protected: protected:
/// Transmit via IR the state of this climate controller. /// Transmit via IR the state of this climate controller.
void transmit_state() override; void transmit_state() override;
/// Handle received IR Buffer /// Handle received IR Buffer
bool on_receive(remote_base::RemoteReceiveData data) override; bool on_receive(remote_base::RemoteReceiveData data) override { return CoolixClimate::on_coolix(this, data); }
bool send_swing_cmd_{false}; bool send_swing_cmd_{false};
}; };

Some files were not shown because too many files have changed in this diff Show more