mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 11:37:27 +01:00
commit
8a025a6617
4
.github/actions/build-image/action.yaml
vendored
4
.github/actions/build-image/action.yaml
vendored
@ -46,7 +46,7 @@ runs:
|
|||||||
|
|
||||||
- name: Build and push to ghcr by digest
|
- name: Build and push to ghcr by digest
|
||||||
id: build-ghcr
|
id: build-ghcr
|
||||||
uses: docker/build-push-action@v6.7.0
|
uses: docker/build-push-action@v6.9.0
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILD_SUMMARY: false
|
DOCKER_BUILD_SUMMARY: false
|
||||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||||
@ -72,7 +72,7 @@ runs:
|
|||||||
|
|
||||||
- name: Build and push to dockerhub by digest
|
- name: Build and push to dockerhub by digest
|
||||||
id: build-dockerhub
|
id: build-dockerhub
|
||||||
uses: docker/build-push-action@v6.7.0
|
uses: docker/build-push-action@v6.9.0
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILD_SUMMARY: false
|
DOCKER_BUILD_SUMMARY: false
|
||||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||||
|
2
.github/actions/restore-python/action.yml
vendored
2
.github/actions/restore-python/action.yml
vendored
@ -22,7 +22,7 @@ runs:
|
|||||||
python-version: ${{ inputs.python-version }}
|
python-version: ${{ inputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@v4.0.2
|
uses: actions/cache/restore@v4.1.1
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
|
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@ -46,7 +46,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: "3.9"
|
python-version: "3.9"
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.6.1
|
uses: docker/setup-buildx-action@v3.7.1
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.2.0
|
uses: docker/setup-qemu-action@v3.2.0
|
||||||
|
|
||||||
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -46,7 +46,7 @@ jobs:
|
|||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v4.0.2
|
uses: actions/cache@v4.1.1
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
@ -302,14 +302,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache@v4.0.2
|
uses: actions/cache@v4.1.1
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}
|
key: platformio-${{ matrix.pio_cache_key }}
|
||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref != 'refs/heads/dev'
|
if: github.ref != 'refs/heads/dev'
|
||||||
uses: actions/cache/restore@v4.0.2
|
uses: actions/cache/restore@v4.1.1
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}
|
key: platformio-${{ matrix.pio_cache_key }}
|
||||||
|
91
.github/workflows/codeql.yml
vendored
Normal file
91
.github/workflows/codeql.yml
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL Advanced"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 18 * * 4"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze (${{ matrix.language }})
|
||||||
|
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||||
|
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||||
|
# - https://gh.io/supported-runners-and-hardware-resources
|
||||||
|
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||||
|
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||||
|
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||||
|
permissions:
|
||||||
|
# required for all workflows
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
# required to fetch internal or private CodeQL packs
|
||||||
|
packages: read
|
||||||
|
|
||||||
|
# only required for workflows in private repositories
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
# - language: c-cpp
|
||||||
|
# build-mode: autobuild
|
||||||
|
- language: python
|
||||||
|
build-mode: none
|
||||||
|
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||||
|
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||||
|
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||||
|
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||||
|
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||||
|
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||||
|
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||||
|
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v3
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
build-mode: ${{ matrix.build-mode }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
|
||||||
|
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||||
|
# queries: security-extended,security-and-quality
|
||||||
|
|
||||||
|
# If the analyze step fails for one of the languages you are analyzing with
|
||||||
|
# "We were unable to automatically build your code", modify the matrix above
|
||||||
|
# to set the build mode to "manual" for that language. Then modify this step
|
||||||
|
# to build your code.
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
- if: matrix.build-mode == 'manual'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||||
|
'languages you are analyzing, replace this with the commands to build' \
|
||||||
|
'your code, for example:'
|
||||||
|
echo ' make bootstrap'
|
||||||
|
echo ' make release'
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v3
|
||||||
|
with:
|
||||||
|
category: "/language:${{matrix.language}}"
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@ -65,7 +65,7 @@ jobs:
|
|||||||
pip3 install build
|
pip3 install build
|
||||||
python3 -m build
|
python3 -m build
|
||||||
- name: Publish
|
- name: Publish
|
||||||
uses: pypa/gh-action-pypi-publish@v1.10.1
|
uses: pypa/gh-action-pypi-publish@v1.10.3
|
||||||
|
|
||||||
deploy-docker:
|
deploy-docker:
|
||||||
name: Build ESPHome ${{ matrix.platform }}
|
name: Build ESPHome ${{ matrix.platform }}
|
||||||
@ -90,7 +90,7 @@ jobs:
|
|||||||
python-version: "3.9"
|
python-version: "3.9"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.6.1
|
uses: docker/setup-buildx-action@v3.7.1
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
if: matrix.platform != 'linux/amd64'
|
if: matrix.platform != 'linux/amd64'
|
||||||
uses: docker/setup-qemu-action@v3.2.0
|
uses: docker/setup-qemu-action@v3.2.0
|
||||||
@ -141,7 +141,7 @@ jobs:
|
|||||||
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
|
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Upload digests
|
- name: Upload digests
|
||||||
uses: actions/upload-artifact@v4.4.0
|
uses: actions/upload-artifact@v4.4.2
|
||||||
with:
|
with:
|
||||||
name: digests-${{ steps.sanitize.outputs.name }}
|
name: digests-${{ steps.sanitize.outputs.name }}
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
@ -184,7 +184,7 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.6.1
|
uses: docker/setup-buildx-action@v3.7.1
|
||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
if: matrix.registry == 'dockerhub'
|
if: matrix.registry == 'dockerhub'
|
||||||
|
2
.github/workflows/sync-device-classes.yml
vendored
2
.github/workflows/sync-device-classes.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
|||||||
python ./script/sync-device_class.py
|
python ./script/sync-device_class.py
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
uses: peter-evans/create-pull-request@v7.0.0
|
uses: peter-evans/create-pull-request@v7.0.5
|
||||||
with:
|
with:
|
||||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||||
committer: esphomebot <esphome@nabucasa.com>
|
committer: esphomebot <esphome@nabucasa.com>
|
||||||
|
12
CODEOWNERS
12
CODEOWNERS
@ -24,6 +24,7 @@ esphome/components/ade7953_i2c/* @angelnu
|
|||||||
esphome/components/ade7953_spi/* @angelnu
|
esphome/components/ade7953_spi/* @angelnu
|
||||||
esphome/components/ads1118/* @solomondg1
|
esphome/components/ads1118/* @solomondg1
|
||||||
esphome/components/ags10/* @mak-42
|
esphome/components/ags10/* @mak-42
|
||||||
|
esphome/components/aic3204/* @kbx81
|
||||||
esphome/components/airthings_ble/* @jeromelaban
|
esphome/components/airthings_ble/* @jeromelaban
|
||||||
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
|
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
|
||||||
esphome/components/airthings_wave_mini/* @ncareau
|
esphome/components/airthings_wave_mini/* @ncareau
|
||||||
@ -47,6 +48,7 @@ esphome/components/at581x/* @X-Ryl669
|
|||||||
esphome/components/atc_mithermometer/* @ahpohl
|
esphome/components/atc_mithermometer/* @ahpohl
|
||||||
esphome/components/atm90e26/* @danieltwagner
|
esphome/components/atm90e26/* @danieltwagner
|
||||||
esphome/components/atm90e32/* @circuitsetup @descipher
|
esphome/components/atm90e32/* @circuitsetup @descipher
|
||||||
|
esphome/components/audio_dac/* @kbx81
|
||||||
esphome/components/b_parasite/* @rbaron
|
esphome/components/b_parasite/* @rbaron
|
||||||
esphome/components/ballu/* @bazuchan
|
esphome/components/ballu/* @bazuchan
|
||||||
esphome/components/bang_bang/* @OttoWinter
|
esphome/components/bang_bang/* @OttoWinter
|
||||||
@ -86,7 +88,7 @@ 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/cd74hc4067/* @asoehlke
|
||||||
esphome/components/ch422g/* @jesterret
|
esphome/components/ch422g/* @clydebarrow @jesterret
|
||||||
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
|
||||||
@ -152,6 +154,7 @@ esphome/components/ft63x6/* @gpambrozio
|
|||||||
esphome/components/gcja5/* @gcormier
|
esphome/components/gcja5/* @gcormier
|
||||||
esphome/components/gdk101/* @Szewcson
|
esphome/components/gdk101/* @Szewcson
|
||||||
esphome/components/globals/* @esphome/core
|
esphome/components/globals/* @esphome/core
|
||||||
|
esphome/components/gp2y1010au0f/* @zry98
|
||||||
esphome/components/gp8403/* @jesserockz
|
esphome/components/gp8403/* @jesserockz
|
||||||
esphome/components/gpio/* @esphome/core
|
esphome/components/gpio/* @esphome/core
|
||||||
esphome/components/gpio/one_wire/* @ssieb
|
esphome/components/gpio/one_wire/* @ssieb
|
||||||
@ -159,6 +162,7 @@ esphome/components/gps/* @coogle
|
|||||||
esphome/components/graph/* @synco
|
esphome/components/graph/* @synco
|
||||||
esphome/components/graphical_display_menu/* @MrMDavidson
|
esphome/components/graphical_display_menu/* @MrMDavidson
|
||||||
esphome/components/gree/* @orestismers
|
esphome/components/gree/* @orestismers
|
||||||
|
esphome/components/grove_gas_mc_v2/* @YorkshireIoT
|
||||||
esphome/components/grove_tb6612fng/* @max246
|
esphome/components/grove_tb6612fng/* @max246
|
||||||
esphome/components/growatt_solar/* @leeuwte
|
esphome/components/growatt_solar/* @leeuwte
|
||||||
esphome/components/gt911/* @clydebarrow @jesserockz
|
esphome/components/gt911/* @clydebarrow @jesserockz
|
||||||
@ -166,6 +170,7 @@ esphome/components/haier/* @paveldn
|
|||||||
esphome/components/haier/binary_sensor/* @paveldn
|
esphome/components/haier/binary_sensor/* @paveldn
|
||||||
esphome/components/haier/button/* @paveldn
|
esphome/components/haier/button/* @paveldn
|
||||||
esphome/components/haier/sensor/* @paveldn
|
esphome/components/haier/sensor/* @paveldn
|
||||||
|
esphome/components/haier/switch/* @paveldn
|
||||||
esphome/components/haier/text_sensor/* @paveldn
|
esphome/components/haier/text_sensor/* @paveldn
|
||||||
esphome/components/havells_solar/* @sourabhjaiswal
|
esphome/components/havells_solar/* @sourabhjaiswal
|
||||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||||
@ -278,6 +283,7 @@ esphome/components/mopeka_std_check/* @Fabian-Schmidt
|
|||||||
esphome/components/mpl3115a2/* @kbickar
|
esphome/components/mpl3115a2/* @kbickar
|
||||||
esphome/components/mpu6886/* @fabaff
|
esphome/components/mpu6886/* @fabaff
|
||||||
esphome/components/ms8607/* @e28eta
|
esphome/components/ms8607/* @e28eta
|
||||||
|
esphome/components/nau7802/* @cujomalainey
|
||||||
esphome/components/network/* @esphome/core
|
esphome/components/network/* @esphome/core
|
||||||
esphome/components/nextion/* @edwardtfn @senexcrenshaw
|
esphome/components/nextion/* @edwardtfn @senexcrenshaw
|
||||||
esphome/components/nextion/binary_sensor/* @senexcrenshaw
|
esphome/components/nextion/binary_sensor/* @senexcrenshaw
|
||||||
@ -286,9 +292,11 @@ esphome/components/nextion/switch/* @senexcrenshaw
|
|||||||
esphome/components/nextion/text_sensor/* @senexcrenshaw
|
esphome/components/nextion/text_sensor/* @senexcrenshaw
|
||||||
esphome/components/nfc/* @jesserockz @kbx81
|
esphome/components/nfc/* @jesserockz @kbx81
|
||||||
esphome/components/noblex/* @AGalfra
|
esphome/components/noblex/* @AGalfra
|
||||||
|
esphome/components/npi19/* @bakerkj
|
||||||
esphome/components/number/* @esphome/core
|
esphome/components/number/* @esphome/core
|
||||||
esphome/components/one_wire/* @ssieb
|
esphome/components/one_wire/* @ssieb
|
||||||
esphome/components/online_image/* @guillempages
|
esphome/components/online_image/* @guillempages
|
||||||
|
esphome/components/opentherm/* @olegtarasov
|
||||||
esphome/components/ota/* @esphome/core
|
esphome/components/ota/* @esphome/core
|
||||||
esphome/components/output/* @esphome/core
|
esphome/components/output/* @esphome/core
|
||||||
esphome/components/pca6416a/* @Mat931
|
esphome/components/pca6416a/* @Mat931
|
||||||
@ -396,9 +404,11 @@ esphome/components/sun_gtil2/* @Mat931
|
|||||||
esphome/components/switch/* @esphome/core
|
esphome/components/switch/* @esphome/core
|
||||||
esphome/components/t6615/* @tylermenezes
|
esphome/components/t6615/* @tylermenezes
|
||||||
esphome/components/tca9548a/* @andreashergert1984
|
esphome/components/tca9548a/* @andreashergert1984
|
||||||
|
esphome/components/tca9555/* @mobrembski
|
||||||
esphome/components/tcl112/* @glmnet
|
esphome/components/tcl112/* @glmnet
|
||||||
esphome/components/tee501/* @Stock-M
|
esphome/components/tee501/* @Stock-M
|
||||||
esphome/components/teleinfo/* @0hax
|
esphome/components/teleinfo/* @0hax
|
||||||
|
esphome/components/tem3200/* @bakerkj
|
||||||
esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar
|
esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar
|
||||||
esphome/components/template/datetime/* @rfdarter
|
esphome/components/template/datetime/* @rfdarter
|
||||||
esphome/components/template/event/* @nohat
|
esphome/components/template/event/* @nohat
|
||||||
|
@ -7,3 +7,5 @@
|
|||||||
For issues, please go to [the issue tracker](https://github.com/esphome/issues/issues).
|
For issues, please go to [the issue tracker](https://github.com/esphome/issues/issues).
|
||||||
|
|
||||||
For feature requests, please see [feature requests](https://github.com/esphome/feature-requests/issues).
|
For feature requests, please see [feature requests](https://github.com/esphome/feature-requests/issues).
|
||||||
|
|
||||||
|
[![ESPHome - A project from the Open Home Foundation](https://www.openhomefoundation.org/badges/esphome.png)](https://www.openhomefoundation.org/)
|
||||||
|
0
esphome/components/aic3204/__init__.py
Normal file
0
esphome/components/aic3204/__init__.py
Normal file
173
esphome/components/aic3204/aic3204.cpp
Normal file
173
esphome/components/aic3204/aic3204.cpp
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
#include "aic3204.h"
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace aic3204 {
|
||||||
|
|
||||||
|
static const char *const TAG = "aic3204";
|
||||||
|
|
||||||
|
#define ERROR_CHECK(err, msg) \
|
||||||
|
if (!(err)) { \
|
||||||
|
ESP_LOGE(TAG, msg); \
|
||||||
|
this->mark_failed(); \
|
||||||
|
return; \
|
||||||
|
}
|
||||||
|
|
||||||
|
void AIC3204::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up AIC3204...");
|
||||||
|
|
||||||
|
// Set register page to 0
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x00), "Set page 0 failed");
|
||||||
|
// Initiate SW reset (PLL is powered off as part of reset)
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_SW_RST, 0x01), "Software reset failed");
|
||||||
|
// *** Program clock settings ***
|
||||||
|
// Default is CODEC_CLKIN is from MCLK pin. Don't need to change this.
|
||||||
|
// MDAC*NDAC*FOSR*48Khz = mClk (24.576 MHz when the XMOS is expecting 48kHz audio)
|
||||||
|
// (See page 51 of https://www.ti.com/lit/ml/slaa557/slaa557.pdf)
|
||||||
|
// We do need MDAC*DOSR/32 >= the resource compute level for the processing block
|
||||||
|
// So here 2*128/32 = 8, which is equal to processing block 1 's resource compute
|
||||||
|
// See page 5 of https://www.ti.com/lit/an/slaa404c/slaa404c.pdf for the workflow
|
||||||
|
// for determining these settings.
|
||||||
|
|
||||||
|
// Power up NDAC and set to 2
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_NDAC, 0x82), "Set NDAC failed");
|
||||||
|
// Power up MDAC and set to 2
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_MDAC, 0x82), "Set MDAC failed");
|
||||||
|
// Program DOSR = 128
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_DOSR, 0x80), "Set DOSR failed");
|
||||||
|
// Set Audio Interface Config: I2S, 32 bits, DOUT always driving
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_CODEC_IF, 0x30), "Set CODEC_IF failed");
|
||||||
|
// For I2S Firmware only, set SCLK/MFP3 pin as Audio Data In
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_SCLK_MFP3, 0x02), "Set SCLK/MFP3 failed");
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_AUDIO_IF_4, 0x01), "Set AUDIO_IF_4 failed");
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_AUDIO_IF_5, 0x01), "Set AUDIO_IF_5 failed");
|
||||||
|
// Program the DAC processing block to be used - PRB_P1
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_DAC_SIG_PROC, 0x01), "Set DAC_SIG_PROC failed");
|
||||||
|
|
||||||
|
// *** Select Page 1 ***
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x01), "Set page 1 failed");
|
||||||
|
// Enable the internal AVDD_LDO:
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_LDO_CTRL, 0x09), "Set LDO_CTRL failed");
|
||||||
|
// *** Program Analog Blocks ***
|
||||||
|
// Disable Internal Crude AVdd in presence of external AVdd supply or before powering up internal AVdd LDO
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_PWR_CFG, 0x08), "Set PWR_CFG failed");
|
||||||
|
// Enable Master Analog Power Control
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_LDO_CTRL, 0x01), "Set LDO_CTRL failed");
|
||||||
|
// Page 125: Common mode control register, set d6 to 1 to make the full chip common mode = 0.75 v
|
||||||
|
// We are using the internal AVdd regulator with a nominal output of 1.72 V (see LDO_CTRL_REGISTER on page 123)
|
||||||
|
// Page 86 says to only set the common mode voltage to 0.9 v if AVdd >= 1.8... but it isn't on our hardware
|
||||||
|
// We also adjust the HPL and HPR gains to -2dB gian later in this config flow compensate (see page 47)
|
||||||
|
// (All pages refer to the TLV320AIC3204 Application Reference Guide)
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_CM_CTRL, 0x40), "Set CM_CTRL failed");
|
||||||
|
// *** Set PowerTune Modes ***
|
||||||
|
// Set the Left & Right DAC PowerTune mode to PTM_P3/4. Use Class-AB driver.
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_PLAY_CFG1, 0x00), "Set PLAY_CFG1 failed");
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_PLAY_CFG2, 0x00), "Set PLAY_CFG2 failed");
|
||||||
|
// Set the REF charging time to 40ms
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_REF_STARTUP, 0x01), "Set REF_STARTUP failed");
|
||||||
|
// HP soft stepping settings for optimal pop performance at power up
|
||||||
|
// Rpop used is 6k with N = 6 and soft step = 20usec. This should work with 47uF coupling
|
||||||
|
// capacitor. Can try N=5,6 or 7 time constants as well. Trade-off delay vs “pop” sound.
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_HP_START, 0x25), "Set HP_START failed");
|
||||||
|
// Route Left DAC to HPL
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_HPL_ROUTE, 0x08), "Set HPL_ROUTE failed");
|
||||||
|
// Route Right DAC to HPR
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_HPR_ROUTE, 0x08), "Set HPR_ROUTE failed");
|
||||||
|
// Route Left DAC to LOL
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_LOL_ROUTE, 0x08), "Set LOL_ROUTE failed");
|
||||||
|
// Route Right DAC to LOR
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_LOR_ROUTE, 0x08), "Set LOR_ROUTE failed");
|
||||||
|
|
||||||
|
// Unmute HPL and set gain to -2dB (see comment before configuring the AIC3204_CM_CTRL register)
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_HPL_GAIN, 0x3e), "Set HPL_GAIN failed");
|
||||||
|
// Unmute HPR and set gain to -2dB (see comment before configuring the AIC3204_CM_CTRL register)
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_HPR_GAIN, 0x3e), "Set HPR_GAIN failed");
|
||||||
|
// Unmute LOL and set gain to 0dB
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_LOL_DRV_GAIN, 0x00), "Set LOL_DRV_GAIN failed");
|
||||||
|
// Unmute LOR and set gain to 0dB
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_LOR_DRV_GAIN, 0x00), "Set LOR_DRV_GAIN failed");
|
||||||
|
|
||||||
|
// Power up HPL and HPR, LOL and LOR drivers
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_OP_PWR_CTRL, 0x3C), "Set OP_PWR_CTRL failed");
|
||||||
|
|
||||||
|
// Wait for 2.5 sec for soft stepping to take effect before attempting power-up
|
||||||
|
this->set_timeout(2500, [this]() {
|
||||||
|
// *** Power Up DAC ***
|
||||||
|
// Select Page 0
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x00), "Set PAGE_CTRL failed");
|
||||||
|
// Power up the Left and Right DAC Channels. Route Left data to Left DAC and Right data to Right DAC.
|
||||||
|
// DAC Vol control soft step 1 step per DAC word clock.
|
||||||
|
ERROR_CHECK(this->write_byte(AIC3204_DAC_CH_SET1, 0xd4), "Set DAC_CH_SET1 failed");
|
||||||
|
// Set left and right DAC digital volume control
|
||||||
|
ERROR_CHECK(this->write_volume_(), "Set volume failed");
|
||||||
|
// Unmute left and right channels
|
||||||
|
ERROR_CHECK(this->write_mute_(), "Set mute failed");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AIC3204::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "AIC3204:");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, "Communication with AIC3204 failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AIC3204::set_mute_off() {
|
||||||
|
this->is_muted_ = false;
|
||||||
|
return this->write_mute_();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AIC3204::set_mute_on() {
|
||||||
|
this->is_muted_ = true;
|
||||||
|
return this->write_mute_();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AIC3204::set_auto_mute_mode(uint8_t auto_mute_mode) {
|
||||||
|
this->auto_mute_mode_ = auto_mute_mode & 0x07;
|
||||||
|
ESP_LOGVV(TAG, "Setting auto_mute_mode to 0x%.2x", this->auto_mute_mode_);
|
||||||
|
return this->write_mute_();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AIC3204::set_volume(float volume) {
|
||||||
|
this->volume_ = clamp<float>(volume, 0.0, 1.0);
|
||||||
|
return this->write_volume_();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AIC3204::is_muted() { return this->is_muted_; }
|
||||||
|
|
||||||
|
float AIC3204::volume() { return this->volume_; }
|
||||||
|
|
||||||
|
bool AIC3204::write_mute_() {
|
||||||
|
uint8_t mute_mode_byte = this->auto_mute_mode_ << 4; // auto-mute control is bits 4-6
|
||||||
|
mute_mode_byte |= this->is_muted_ ? 0x0c : 0x00; // mute bits are 2-3
|
||||||
|
if (!this->write_byte(AIC3204_PAGE_CTRL, 0x00) || !this->write_byte(AIC3204_DAC_CH_SET2, mute_mode_byte)) {
|
||||||
|
ESP_LOGE(TAG, "Writing mute modes failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AIC3204::write_volume_() {
|
||||||
|
const int8_t dvc_min_byte = -127;
|
||||||
|
const int8_t dvc_max_byte = 48;
|
||||||
|
|
||||||
|
int8_t volume_byte = dvc_min_byte + (this->volume_ * (dvc_max_byte - dvc_min_byte));
|
||||||
|
volume_byte = clamp<int8_t>(volume_byte, dvc_min_byte, dvc_max_byte);
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "Setting volume to 0x%.2x", volume_byte & 0xFF);
|
||||||
|
|
||||||
|
if ((!this->write_byte(AIC3204_PAGE_CTRL, 0x00)) || (!this->write_byte(AIC3204_DACL_VOL_D, volume_byte)) ||
|
||||||
|
(!this->write_byte(AIC3204_DACR_VOL_D, volume_byte))) {
|
||||||
|
ESP_LOGE(TAG, "Writing volume failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace aic3204
|
||||||
|
} // namespace esphome
|
88
esphome/components/aic3204/aic3204.h
Normal file
88
esphome/components/aic3204/aic3204.h
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/audio_dac/audio_dac.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace aic3204 {
|
||||||
|
|
||||||
|
// TLV320AIC3204 Register Addresses
|
||||||
|
// Page 0
|
||||||
|
static const uint8_t AIC3204_PAGE_CTRL = 0x00; // Register 0 - Page Control
|
||||||
|
static const uint8_t AIC3204_SW_RST = 0x01; // Register 1 - Software Reset
|
||||||
|
static const uint8_t AIC3204_CLK_PLL1 = 0x04; // Register 4 - Clock Setting Register 1, Multiplexers
|
||||||
|
static const uint8_t AIC3204_CLK_PLL2 = 0x05; // Register 5 - Clock Setting Register 2, P and R values
|
||||||
|
static const uint8_t AIC3204_CLK_PLL3 = 0x06; // Register 6 - Clock Setting Register 3, J values
|
||||||
|
static const uint8_t AIC3204_NDAC = 0x0B; // Register 11 - NDAC Divider Value
|
||||||
|
static const uint8_t AIC3204_MDAC = 0x0C; // Register 12 - MDAC Divider Value
|
||||||
|
static const uint8_t AIC3204_DOSR = 0x0E; // Register 14 - DOSR Divider Value (LS Byte)
|
||||||
|
static const uint8_t AIC3204_NADC = 0x12; // Register 18 - NADC Divider Value
|
||||||
|
static const uint8_t AIC3204_MADC = 0x13; // Register 19 - MADC Divider Value
|
||||||
|
static const uint8_t AIC3204_AOSR = 0x14; // Register 20 - AOSR Divider Value
|
||||||
|
static const uint8_t AIC3204_CODEC_IF = 0x1B; // Register 27 - CODEC Interface Control
|
||||||
|
static const uint8_t AIC3204_AUDIO_IF_4 = 0x1F; // Register 31 - Audio Interface Setting Register 4
|
||||||
|
static const uint8_t AIC3204_AUDIO_IF_5 = 0x20; // Register 32 - Audio Interface Setting Register 5
|
||||||
|
static const uint8_t AIC3204_SCLK_MFP3 = 0x38; // Register 56 - SCLK/MFP3 Function Control
|
||||||
|
static const uint8_t AIC3204_DAC_SIG_PROC = 0x3C; // Register 60 - DAC Sig Processing Block Control
|
||||||
|
static const uint8_t AIC3204_ADC_SIG_PROC = 0x3D; // Register 61 - ADC Sig Processing Block Control
|
||||||
|
static const uint8_t AIC3204_DAC_CH_SET1 = 0x3F; // Register 63 - DAC Channel Setup 1
|
||||||
|
static const uint8_t AIC3204_DAC_CH_SET2 = 0x40; // Register 64 - DAC Channel Setup 2
|
||||||
|
static const uint8_t AIC3204_DACL_VOL_D = 0x41; // Register 65 - DAC Left Digital Vol Control
|
||||||
|
static const uint8_t AIC3204_DACR_VOL_D = 0x42; // Register 66 - DAC Right Digital Vol Control
|
||||||
|
static const uint8_t AIC3204_DRC_ENABLE = 0x44;
|
||||||
|
static const uint8_t AIC3204_ADC_CH_SET = 0x51; // Register 81 - ADC Channel Setup
|
||||||
|
static const uint8_t AIC3204_ADC_FGA_MUTE = 0x52; // Register 82 - ADC Fine Gain Adjust/Mute
|
||||||
|
|
||||||
|
// Page 1
|
||||||
|
static const uint8_t AIC3204_PWR_CFG = 0x01; // Register 1 - Power Config
|
||||||
|
static const uint8_t AIC3204_LDO_CTRL = 0x02; // Register 2 - LDO Control
|
||||||
|
static const uint8_t AIC3204_PLAY_CFG1 = 0x03; // Register 3 - Playback Config 1
|
||||||
|
static const uint8_t AIC3204_PLAY_CFG2 = 0x04; // Register 4 - Playback Config 2
|
||||||
|
static const uint8_t AIC3204_OP_PWR_CTRL = 0x09; // Register 9 - Output Driver Power Control
|
||||||
|
static const uint8_t AIC3204_CM_CTRL = 0x0A; // Register 10 - Common Mode Control
|
||||||
|
static const uint8_t AIC3204_HPL_ROUTE = 0x0C; // Register 12 - HPL Routing Select
|
||||||
|
static const uint8_t AIC3204_HPR_ROUTE = 0x0D; // Register 13 - HPR Routing Select
|
||||||
|
static const uint8_t AIC3204_LOL_ROUTE = 0x0E; // Register 14 - LOL Routing Selection
|
||||||
|
static const uint8_t AIC3204_LOR_ROUTE = 0x0F; // Register 15 - LOR Routing Selection
|
||||||
|
static const uint8_t AIC3204_HPL_GAIN = 0x10; // Register 16 - HPL Driver Gain
|
||||||
|
static const uint8_t AIC3204_HPR_GAIN = 0x11; // Register 17 - HPR Driver Gain
|
||||||
|
static const uint8_t AIC3204_LOL_DRV_GAIN = 0x12; // Register 18 - LOL Driver Gain Setting
|
||||||
|
static const uint8_t AIC3204_LOR_DRV_GAIN = 0x13; // Register 19 - LOR Driver Gain Setting
|
||||||
|
static const uint8_t AIC3204_HP_START = 0x14; // Register 20 - Headphone Driver Startup
|
||||||
|
static const uint8_t AIC3204_LPGA_P_ROUTE = 0x34; // Register 52 - Left PGA Positive Input Route
|
||||||
|
static const uint8_t AIC3204_LPGA_N_ROUTE = 0x36; // Register 54 - Left PGA Negative Input Route
|
||||||
|
static const uint8_t AIC3204_RPGA_P_ROUTE = 0x37; // Register 55 - Right PGA Positive Input Route
|
||||||
|
static const uint8_t AIC3204_RPGA_N_ROUTE = 0x39; // Register 57 - Right PGA Negative Input Route
|
||||||
|
static const uint8_t AIC3204_LPGA_VOL = 0x3B; // Register 59 - Left PGA Volume
|
||||||
|
static const uint8_t AIC3204_RPGA_VOL = 0x3C; // Register 60 - Right PGA Volume
|
||||||
|
static const uint8_t AIC3204_ADC_PTM = 0x3D; // Register 61 - ADC Power Tune Config
|
||||||
|
static const uint8_t AIC3204_AN_IN_CHRG = 0x47; // Register 71 - Analog Input Quick Charging Config
|
||||||
|
static const uint8_t AIC3204_REF_STARTUP = 0x7B; // Register 123 - Reference Power Up Config
|
||||||
|
|
||||||
|
class AIC3204 : public audio_dac::AudioDac, public Component, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
bool set_mute_off() override;
|
||||||
|
bool set_mute_on() override;
|
||||||
|
bool set_auto_mute_mode(uint8_t auto_mute_mode);
|
||||||
|
bool set_volume(float volume) override;
|
||||||
|
|
||||||
|
bool is_muted() override;
|
||||||
|
float volume() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool write_mute_();
|
||||||
|
bool write_volume_();
|
||||||
|
|
||||||
|
uint8_t auto_mute_mode_{0};
|
||||||
|
float volume_{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace aic3204
|
||||||
|
} // namespace esphome
|
52
esphome/components/aic3204/audio_dac.py
Normal file
52
esphome/components/aic3204/audio_dac.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from esphome import automation
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import i2c
|
||||||
|
from esphome.components.audio_dac import AudioDac
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_ID, CONF_MODE
|
||||||
|
|
||||||
|
CODEOWNERS = ["@kbx81"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
aic3204_ns = cg.esphome_ns.namespace("aic3204")
|
||||||
|
AIC3204 = aic3204_ns.class_("AIC3204", AudioDac, cg.Component, i2c.I2CDevice)
|
||||||
|
|
||||||
|
SetAutoMuteAction = aic3204_ns.class_("SetAutoMuteAction", automation.Action)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(AIC3204),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(i2c.i2c_device_schema(0x18))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SET_AUTO_MUTE_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.use_id(AIC3204),
|
||||||
|
cv.Required(CONF_MODE): cv.templatable(cv.int_range(max=7, min=0)),
|
||||||
|
},
|
||||||
|
key=CONF_MODE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"aic3204.set_auto_mute_mode", SetAutoMuteAction, SET_AUTO_MUTE_ACTION_SCHEMA
|
||||||
|
)
|
||||||
|
async def aic3204_set_volume_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
template_ = await cg.templatable(config.get(CONF_MODE), args, int)
|
||||||
|
cg.add(var.set_auto_mute_mode(template_))
|
||||||
|
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
23
esphome/components/aic3204/automation.h
Normal file
23
esphome/components/aic3204/automation.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "aic3204.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace aic3204 {
|
||||||
|
|
||||||
|
template<typename... Ts> class SetAutoMuteAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit SetAutoMuteAction(AIC3204 *aic3204) : aic3204_(aic3204) {}
|
||||||
|
|
||||||
|
TEMPLATABLE_VALUE(uint8_t, auto_mute_mode)
|
||||||
|
|
||||||
|
void play(Ts... x) override { this->aic3204_->set_auto_mute_mode(this->auto_mute_mode_.value(x...)); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AIC3204 *aic3204_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace aic3204
|
||||||
|
} // namespace esphome
|
@ -9,7 +9,7 @@ from esphome.const import (
|
|||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
CONF_ON_STATE,
|
CONF_ON_STATE,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
@ -195,9 +195,8 @@ async def setup_alarm_control_panel_core_(var, config):
|
|||||||
for conf in config.get(CONF_ON_READY, []):
|
for conf in config.get(CONF_ON_READY, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [], conf)
|
await automation.build_automation(trigger, [], conf)
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
if mqtt_id := config.get(CONF_MQTT_ID):
|
if mqtt_id := config.get(CONF_MQTT_ID):
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from esphome import automation, core
|
from esphome import automation, core
|
||||||
|
import esphome.codegen as cg
|
||||||
from esphome.components import font
|
from esphome.components import font
|
||||||
import esphome.components.image as espImage
|
import esphome.components.image as espImage
|
||||||
from esphome.components.image import (
|
from esphome.components.image import (
|
||||||
CONF_USE_TRANSPARENCY,
|
CONF_USE_TRANSPARENCY,
|
||||||
LOCAL_SCHEMA,
|
LOCAL_SCHEMA,
|
||||||
WEB_SCHEMA,
|
|
||||||
SOURCE_WEB,
|
|
||||||
SOURCE_LOCAL,
|
SOURCE_LOCAL,
|
||||||
|
SOURCE_WEB,
|
||||||
|
WEB_SCHEMA,
|
||||||
)
|
)
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_FILE,
|
CONF_FILE,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
|
CONF_PATH,
|
||||||
CONF_RAW_DATA_ID,
|
CONF_RAW_DATA_ID,
|
||||||
CONF_REPEAT,
|
CONF_REPEAT,
|
||||||
CONF_RESIZE,
|
CONF_RESIZE,
|
||||||
CONF_TYPE,
|
|
||||||
CONF_SOURCE,
|
CONF_SOURCE,
|
||||||
CONF_PATH,
|
CONF_TYPE,
|
||||||
CONF_URL,
|
CONF_URL,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, HexInt
|
from esphome.core import CORE, HexInt
|
||||||
@ -172,6 +172,9 @@ async def to_code(config):
|
|||||||
path = CORE.relative_config_path(conf_file[CONF_PATH])
|
path = CORE.relative_config_path(conf_file[CONF_PATH])
|
||||||
elif conf_file[CONF_SOURCE] == SOURCE_WEB:
|
elif conf_file[CONF_SOURCE] == SOURCE_WEB:
|
||||||
path = espImage.compute_local_image_path(conf_file).as_posix()
|
path = espImage.compute_local_image_path(conf_file).as_posix()
|
||||||
|
else:
|
||||||
|
raise core.EsphomeError(f"Unknown animation source: {conf_file[CONF_SOURCE]}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
image = Image.open(path)
|
image = Image.open(path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -183,13 +186,12 @@ async def to_code(config):
|
|||||||
new_width_max, new_height_max = config[CONF_RESIZE]
|
new_width_max, new_height_max = config[CONF_RESIZE]
|
||||||
ratio = min(new_width_max / width, new_height_max / height)
|
ratio = min(new_width_max / width, new_height_max / height)
|
||||||
width, height = int(width * ratio), int(height * ratio)
|
width, height = int(width * ratio), int(height * ratio)
|
||||||
else:
|
elif width > 500 or height > 500:
|
||||||
if width > 500 or height > 500:
|
_LOGGER.warning(
|
||||||
_LOGGER.warning(
|
'The image "%s" you requested is very big. Please consider'
|
||||||
'The image "%s" you requested is very big. Please consider'
|
" using the resize parameter.",
|
||||||
" using the resize parameter.",
|
path,
|
||||||
path,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
transparent = config[CONF_USE_TRANSPARENCY]
|
transparent = config[CONF_USE_TRANSPARENCY]
|
||||||
|
|
||||||
@ -306,6 +308,8 @@ async def to_code(config):
|
|||||||
if transparent:
|
if transparent:
|
||||||
alpha = image.split()[-1]
|
alpha = image.split()[-1]
|
||||||
has_alpha = alpha.getextrema()[0] < 0xFF
|
has_alpha = alpha.getextrema()[0] < 0xFF
|
||||||
|
else:
|
||||||
|
has_alpha = False
|
||||||
frame = image.convert("1", dither=Image.Dither.NONE)
|
frame = image.convert("1", dither=Image.Dither.NONE)
|
||||||
if CONF_RESIZE in config:
|
if CONF_RESIZE in config:
|
||||||
frame = frame.resize([width, height])
|
frame = frame.resize([width, height])
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "api_connection.h"
|
#include "api_connection.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@ -1568,3 +1569,4 @@ void APIConnection::on_fatal_error() {
|
|||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include "api_frame_helper.h"
|
#include "api_frame_helper.h"
|
||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
#include "api_pb2_service.h"
|
#include "api_pb2_service.h"
|
||||||
#include "api_server.h"
|
#include "api_server.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -268,3 +269,4 @@ class APIConnection : public APIServerConnection {
|
|||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#include "api_frame_helper.h"
|
#include "api_frame_helper.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
@ -1028,3 +1028,4 @@ APIError APIPlaintextFrameHelper::shutdown(int how) {
|
|||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_API
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
#include "noise/protocol.h"
|
#include "noise/protocol.h"
|
||||||
#endif
|
#endif
|
||||||
@ -190,3 +190,4 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
|||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "api_server.h"
|
#include "api_server.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include "api_connection.h"
|
#include "api_connection.h"
|
||||||
#include "esphome/components/network/util.h"
|
#include "esphome/components/network/util.h"
|
||||||
@ -403,3 +404,4 @@ void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP
|
|||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include "api_noise_context.h"
|
#include "api_noise_context.h"
|
||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
#include "api_pb2_service.h"
|
#include "api_pb2_service.h"
|
||||||
@ -7,7 +9,6 @@
|
|||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/controller.h"
|
#include "esphome/core/controller.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "list_entities.h"
|
#include "list_entities.h"
|
||||||
#include "subscribe_state.h"
|
#include "subscribe_state.h"
|
||||||
@ -153,3 +154,4 @@ template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
|
|||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include "user_services.h"
|
|
||||||
#include "api_server.h"
|
#include "api_server.h"
|
||||||
|
#ifdef USE_API
|
||||||
|
#include "user_services.h"
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
@ -216,3 +216,4 @@ class CustomAPIDevice {
|
|||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "api_server.h"
|
||||||
|
#ifdef USE_API
|
||||||
|
#include "api_pb2.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "api_pb2.h"
|
|
||||||
#include "api_server.h"
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@ -81,3 +81,4 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "list_entities.h"
|
#include "list_entities.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include "api_connection.h"
|
#include "api_connection.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
@ -104,3 +105,4 @@ bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this
|
|||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/component_iterator.h"
|
#include "esphome/core/component_iterator.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
@ -87,3 +87,4 @@ class ListEntitiesIterator : public ComponentIterator {
|
|||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "subscribe_state.h"
|
#include "subscribe_state.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include "api_connection.h"
|
#include "api_connection.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
@ -84,3 +85,4 @@ InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(clie
|
|||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_API
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/component_iterator.h"
|
#include "esphome/core/component_iterator.h"
|
||||||
#include "esphome/core/controller.h"
|
#include "esphome/core/controller.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
@ -82,3 +82,4 @@ class InitialStateIterator : public ComponentIterator {
|
|||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
57
esphome/components/audio_dac/__init__.py
Normal file
57
esphome/components/audio_dac/__init__.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
from esphome import automation
|
||||||
|
from esphome.automation import maybe_simple_id
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_ID, CONF_VOLUME
|
||||||
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
|
CODEOWNERS = ["@kbx81"]
|
||||||
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
audio_dac_ns = cg.esphome_ns.namespace("audio_dac")
|
||||||
|
AudioDac = audio_dac_ns.class_("AudioDac")
|
||||||
|
|
||||||
|
MuteOffAction = audio_dac_ns.class_("MuteOffAction", automation.Action)
|
||||||
|
MuteOnAction = audio_dac_ns.class_("MuteOnAction", automation.Action)
|
||||||
|
SetVolumeAction = audio_dac_ns.class_("SetVolumeAction", automation.Action)
|
||||||
|
|
||||||
|
|
||||||
|
MUTE_ACTION_SCHEMA = maybe_simple_id(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.use_id(AudioDac),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SET_VOLUME_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.use_id(AudioDac),
|
||||||
|
cv.Required(CONF_VOLUME): cv.templatable(cv.percentage),
|
||||||
|
},
|
||||||
|
key=CONF_VOLUME,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action("audio_dac.mute_off", MuteOffAction, MUTE_ACTION_SCHEMA)
|
||||||
|
@automation.register_action("audio_dac.mute_on", MuteOnAction, MUTE_ACTION_SCHEMA)
|
||||||
|
async def audio_dac_mute_action_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
|
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"audio_dac.set_volume", SetVolumeAction, SET_VOLUME_ACTION_SCHEMA
|
||||||
|
)
|
||||||
|
async def audio_dac_set_volume_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
|
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
template_ = await cg.templatable(config.get(CONF_VOLUME), args, float)
|
||||||
|
cg.add(var.set_volume(template_))
|
||||||
|
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(100.0)
|
||||||
|
async def to_code(config):
|
||||||
|
cg.add_define("USE_AUDIO_DAC")
|
||||||
|
cg.add_global(audio_dac_ns.using)
|
23
esphome/components/audio_dac/audio_dac.h
Normal file
23
esphome/components/audio_dac/audio_dac.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace audio_dac {
|
||||||
|
|
||||||
|
class AudioDac {
|
||||||
|
public:
|
||||||
|
virtual bool set_mute_off() = 0;
|
||||||
|
virtual bool set_mute_on() = 0;
|
||||||
|
virtual bool set_volume(float volume) = 0;
|
||||||
|
|
||||||
|
virtual bool is_muted() = 0;
|
||||||
|
virtual float volume() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool is_muted_{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio_dac
|
||||||
|
} // namespace esphome
|
43
esphome/components/audio_dac/automation.h
Normal file
43
esphome/components/audio_dac/automation.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "audio_dac.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace audio_dac {
|
||||||
|
|
||||||
|
template<typename... Ts> class MuteOffAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit MuteOffAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
||||||
|
|
||||||
|
void play(Ts... x) override { this->audio_dac_->set_mute_off(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AudioDac *audio_dac_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class MuteOnAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit MuteOnAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
||||||
|
|
||||||
|
void play(Ts... x) override { this->audio_dac_->set_mute_on(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AudioDac *audio_dac_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class SetVolumeAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
explicit SetVolumeAction(AudioDac *audio_dac) : audio_dac_(audio_dac) {}
|
||||||
|
|
||||||
|
TEMPLATABLE_VALUE(float, volume)
|
||||||
|
|
||||||
|
void play(Ts... x) override { this->audio_dac_->set_volume(this->volume_.value(x...)); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AudioDac *audio_dac_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio_dac
|
||||||
|
} // namespace esphome
|
@ -157,8 +157,11 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
|
|||||||
default:
|
default:
|
||||||
trig = nullptr;
|
trig = nullptr;
|
||||||
}
|
}
|
||||||
assert(trig != nullptr);
|
if (trig != nullptr) {
|
||||||
trig->trigger();
|
trig->trigger();
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "trig not set - unsupported action");
|
||||||
|
}
|
||||||
this->action = action;
|
this->action = action;
|
||||||
this->prev_trigger_ = trig;
|
this->prev_trigger_ = trig;
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
|
@ -13,8 +13,10 @@ float bedjet_temp_to_f(const uint8_t temp) {
|
|||||||
|
|
||||||
/** Cleans up the packet before sending. */
|
/** Cleans up the packet before sending. */
|
||||||
BedjetPacket *BedjetCodec::clean_packet_() {
|
BedjetPacket *BedjetCodec::clean_packet_() {
|
||||||
// So far no commands require more than 2 bytes of data.
|
// So far no commands require more than 2 bytes of data
|
||||||
assert(this->packet_.data_length <= 2);
|
if (this->packet_.data_length > 2) {
|
||||||
|
ESP_LOGW(TAG, "Packet may be malformed");
|
||||||
|
}
|
||||||
for (int i = this->packet_.data_length; i < 2; i++) {
|
for (int i = this->packet_.data_length; i < 2; i++) {
|
||||||
this->packet_.data[i] = '\0';
|
this->packet_.data[i] = '\0';
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ from esphome.const import (
|
|||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
CONF_TIMING,
|
CONF_TIMING,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
DEVICE_CLASS_BATTERY,
|
DEVICE_CLASS_BATTERY,
|
||||||
DEVICE_CLASS_BATTERY_CHARGING,
|
DEVICE_CLASS_BATTERY_CHARGING,
|
||||||
DEVICE_CLASS_CARBON_MONOXIDE,
|
DEVICE_CLASS_CARBON_MONOXIDE,
|
||||||
@ -543,9 +543,8 @@ async def setup_binary_sensor_core_(var, config):
|
|||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_binary_sensor(var, config):
|
async def register_binary_sensor(var, config):
|
||||||
|
@ -11,7 +11,7 @@ from esphome.const import (
|
|||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
CONF_ON_PRESS,
|
CONF_ON_PRESS,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
DEVICE_CLASS_EMPTY,
|
DEVICE_CLASS_EMPTY,
|
||||||
DEVICE_CLASS_IDENTIFY,
|
DEVICE_CLASS_IDENTIFY,
|
||||||
DEVICE_CLASS_RESTART,
|
DEVICE_CLASS_RESTART,
|
||||||
@ -97,9 +97,8 @@ async def setup_button_core_(var, config):
|
|||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_button(var, config):
|
async def register_button(var, config):
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
from esphome import pins
|
from esphome import pins
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import i2c
|
from esphome.components import i2c
|
||||||
|
from esphome.components.i2c import I2CBus
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
|
CONF_I2C_ID,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_INPUT,
|
CONF_INPUT,
|
||||||
CONF_INVERTED,
|
CONF_INVERTED,
|
||||||
CONF_MODE,
|
CONF_MODE,
|
||||||
CONF_NUMBER,
|
CONF_NUMBER,
|
||||||
|
CONF_OPEN_DRAIN,
|
||||||
CONF_OUTPUT,
|
CONF_OUTPUT,
|
||||||
CONF_RESTORE_VALUE,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
CODEOWNERS = ["@jesterret"]
|
CODEOWNERS = ["@jesterret", "@clydebarrow"]
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
ch422g_ns = cg.esphome_ns.namespace("ch422g")
|
ch422g_ns = cg.esphome_ns.namespace("ch422g")
|
||||||
@ -23,29 +25,36 @@ CH422GGPIOPin = ch422g_ns.class_(
|
|||||||
)
|
)
|
||||||
|
|
||||||
CONF_CH422G = "ch422g"
|
CONF_CH422G = "ch422g"
|
||||||
CONFIG_SCHEMA = (
|
|
||||||
cv.Schema(
|
# Note that no address is configurable - each register in the CH422G has a dedicated i2c address
|
||||||
{
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
cv.Required(CONF_ID): cv.declare_id(CH422GComponent),
|
{
|
||||||
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
|
cv.GenerateID(CONF_ID): cv.declare_id(CH422GComponent),
|
||||||
}
|
cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CBus),
|
||||||
)
|
}
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
.extend(i2c.i2c_device_schema(0x24))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
|
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await i2c.register_i2c_device(var, config)
|
# Can't use register_i2c_device because there is no CONF_ADDRESS
|
||||||
|
parent = await cg.get_variable(config[CONF_I2C_ID])
|
||||||
|
cg.add(var.set_i2c_bus(parent))
|
||||||
|
|
||||||
|
|
||||||
|
# This is used as a final validation step so that modes have been fully transformed.
|
||||||
|
def pin_mode_check(pin_config, _):
|
||||||
|
if pin_config[CONF_MODE][CONF_INPUT] and pin_config[CONF_NUMBER] >= 8:
|
||||||
|
raise cv.Invalid("CH422G only supports input on pins 0-7")
|
||||||
|
if pin_config[CONF_MODE][CONF_OPEN_DRAIN] and pin_config[CONF_NUMBER] < 8:
|
||||||
|
raise cv.Invalid("CH422G only supports open drain output on pins 8-11")
|
||||||
|
|
||||||
|
|
||||||
CH422G_PIN_SCHEMA = pins.gpio_base_schema(
|
CH422G_PIN_SCHEMA = pins.gpio_base_schema(
|
||||||
CH422GGPIOPin,
|
CH422GGPIOPin,
|
||||||
cv.int_range(min=0, max=7),
|
cv.int_range(min=0, max=11),
|
||||||
modes=[CONF_INPUT, CONF_OUTPUT],
|
modes=[CONF_INPUT, CONF_OUTPUT, CONF_OPEN_DRAIN],
|
||||||
).extend(
|
).extend(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_CH422G): cv.use_id(CH422GComponent),
|
cv.Required(CONF_CH422G): cv.use_id(CH422GComponent),
|
||||||
@ -53,7 +62,7 @@ CH422G_PIN_SCHEMA = pins.gpio_base_schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pins.PIN_SCHEMA_REGISTRY.register(CONF_CH422G, CH422G_PIN_SCHEMA)
|
@pins.PIN_SCHEMA_REGISTRY.register(CONF_CH422G, CH422G_PIN_SCHEMA, pin_mode_check)
|
||||||
async def ch422g_pin_to_code(config):
|
async def ch422g_pin_to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
parent = await cg.get_variable(config[CONF_CH422G])
|
parent = await cg.get_variable(config[CONF_CH422G])
|
||||||
|
@ -4,33 +4,33 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ch422g {
|
namespace ch422g {
|
||||||
|
|
||||||
const uint8_t CH422G_REG_IN = 0x26;
|
static const uint8_t CH422G_REG_MODE = 0x24;
|
||||||
const uint8_t CH422G_REG_OUT = 0x38;
|
static const uint8_t CH422G_MODE_OUTPUT = 0x01; // enables output mode on 0-7
|
||||||
const uint8_t OUT_REG_DEFAULT_VAL = 0xdf;
|
static const uint8_t CH422G_MODE_OPEN_DRAIN = 0x04; // enables open drain mode on 8-11
|
||||||
|
static const uint8_t CH422G_REG_IN = 0x26; // read reg for input bits
|
||||||
|
static const uint8_t CH422G_REG_OUT = 0x38; // write reg for output bits 0-7
|
||||||
|
static const uint8_t CH422G_REG_OUT_UPPER = 0x23; // write reg for output bits 8-11
|
||||||
|
|
||||||
static const char *const TAG = "ch422g";
|
static const char *const TAG = "ch422g";
|
||||||
|
|
||||||
void CH422GComponent::setup() {
|
void CH422GComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up CH422G...");
|
ESP_LOGCONFIG(TAG, "Setting up CH422G...");
|
||||||
// Test to see if device exists
|
// set outputs before mode
|
||||||
if (!this->read_inputs_()) {
|
this->write_outputs_();
|
||||||
|
// Set mode and check for errors
|
||||||
|
if (!this->set_mode_(this->mode_value_) || !this->read_inputs_()) {
|
||||||
ESP_LOGE(TAG, "CH422G not detected at 0x%02X", this->address_);
|
ESP_LOGE(TAG, "CH422G not detected at 0x%02X", this->address_);
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore defaults over whatever got saved on last boot
|
ESP_LOGCONFIG(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
|
||||||
if (!this->restore_value_) {
|
this->status_has_error());
|
||||||
this->write_output_(OUT_REG_DEFAULT_VAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
|
|
||||||
this->status_has_error());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CH422GComponent::loop() {
|
void CH422GComponent::loop() {
|
||||||
// Clear all the previously read flags.
|
// Clear all the previously read flags.
|
||||||
this->pin_read_cache_ = 0x00;
|
this->pin_read_flags_ = 0x00;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CH422GComponent::dump_config() {
|
void CH422GComponent::dump_config() {
|
||||||
@ -41,82 +41,99 @@ void CH422GComponent::dump_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ch422g doesn't have any flag support (needs docs?)
|
void CH422GComponent::pin_mode(uint8_t pin, gpio::Flags flags) {
|
||||||
void CH422GComponent::pin_mode(uint8_t pin, gpio::Flags flags) {}
|
if (pin < 8) {
|
||||||
|
if (flags & gpio::FLAG_OUTPUT) {
|
||||||
|
this->mode_value_ |= CH422G_MODE_OUTPUT;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (flags & gpio::FLAG_OPEN_DRAIN) {
|
||||||
|
this->mode_value_ |= CH422G_MODE_OPEN_DRAIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool CH422GComponent::digital_read(uint8_t pin) {
|
bool CH422GComponent::digital_read(uint8_t pin) {
|
||||||
if (this->pin_read_cache_ == 0 || this->pin_read_cache_ & (1 << pin)) {
|
if (this->pin_read_flags_ == 0 || this->pin_read_flags_ & (1 << pin)) {
|
||||||
// Read values on first access or in case it's being read again in the same loop
|
// Read values on first access or in case it's being read again in the same loop
|
||||||
this->read_inputs_();
|
this->read_inputs_();
|
||||||
}
|
}
|
||||||
|
|
||||||
this->pin_read_cache_ |= (1 << pin);
|
this->pin_read_flags_ |= (1 << pin);
|
||||||
return this->state_mask_ & (1 << pin);
|
return (this->input_bits_ & (1 << pin)) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CH422GComponent::digital_write(uint8_t pin, bool value) {
|
void CH422GComponent::digital_write(uint8_t pin, bool value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
this->write_output_(this->state_mask_ | (1 << pin));
|
this->output_bits_ |= (1 << pin);
|
||||||
} else {
|
} else {
|
||||||
this->write_output_(this->state_mask_ & ~(1 << pin));
|
this->output_bits_ &= ~(1 << pin);
|
||||||
}
|
}
|
||||||
|
this->write_outputs_();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CH422GComponent::read_inputs_() {
|
bool CH422GComponent::read_inputs_() {
|
||||||
if (this->is_failed()) {
|
if (this->is_failed()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
uint8_t result;
|
||||||
uint8_t temp = 0;
|
// reading inputs requires the chip to be in input mode, possibly temporarily.
|
||||||
if ((this->last_error_ = this->read(&temp, 1)) != esphome::i2c::ERROR_OK) {
|
if (this->mode_value_ & CH422G_MODE_OUTPUT) {
|
||||||
this->status_set_warning(str_sprintf("read_inputs_(): I2C I/O error: %d", (int) this->last_error_).c_str());
|
this->set_mode_(this->mode_value_ & ~CH422G_MODE_OUTPUT);
|
||||||
return false;
|
result = this->read_reg_(CH422G_REG_IN);
|
||||||
|
this->set_mode_(this->mode_value_);
|
||||||
|
} else {
|
||||||
|
result = this->read_reg_(CH422G_REG_IN);
|
||||||
}
|
}
|
||||||
|
this->input_bits_ = result;
|
||||||
uint8_t output = 0;
|
|
||||||
if ((this->last_error_ = this->bus_->read(CH422G_REG_IN, &output, 1)) != esphome::i2c::ERROR_OK) {
|
|
||||||
this->status_set_warning(str_sprintf("read_inputs_(): I2C I/O error: %d", (int) this->last_error_).c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->state_mask_ = output;
|
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CH422GComponent::write_output_(uint8_t value) {
|
// Write a register. Can't use the standard write_byte() method because there is no single pre-configured i2c address.
|
||||||
const uint8_t temp = 1;
|
bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
|
||||||
if ((this->last_error_ = this->write(&temp, 1, false)) != esphome::i2c::ERROR_OK) {
|
auto err = this->bus_->write(reg, &value, 1);
|
||||||
this->status_set_warning(str_sprintf("write_output_(): I2C I/O error: %d", (int) this->last_error_).c_str());
|
if (err != i2c::ERROR_OK) {
|
||||||
|
this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t write_mask = value;
|
|
||||||
if ((this->last_error_ = this->bus_->write(CH422G_REG_OUT, &write_mask, 1)) != esphome::i2c::ERROR_OK) {
|
|
||||||
this->status_set_warning(
|
|
||||||
str_sprintf("write_output_(): I2C I/O error: %d for write_mask: %d", (int) this->last_error_, (int) write_mask)
|
|
||||||
.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->state_mask_ = value;
|
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t CH422GComponent::read_reg_(uint8_t reg) {
|
||||||
|
uint8_t value;
|
||||||
|
auto err = this->bus_->read(reg, &value, 1);
|
||||||
|
if (err != i2c::ERROR_OK) {
|
||||||
|
this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
this->status_clear_warning();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CH422GComponent::set_mode_(uint8_t mode) { return this->write_reg_(CH422G_REG_MODE, mode); }
|
||||||
|
|
||||||
|
bool CH422GComponent::write_outputs_() {
|
||||||
|
return this->write_reg_(CH422G_REG_OUT, static_cast<uint8_t>(this->output_bits_)) &&
|
||||||
|
this->write_reg_(CH422G_REG_OUT_UPPER, static_cast<uint8_t>(this->output_bits_ >> 8));
|
||||||
|
}
|
||||||
|
|
||||||
float CH422GComponent::get_setup_priority() const { return setup_priority::IO; }
|
float CH422GComponent::get_setup_priority() const { return setup_priority::IO; }
|
||||||
|
|
||||||
// Run our loop() method very early in the loop, so that we cache read values
|
// Run our loop() method very early in the loop, so that we cache read values
|
||||||
// before other components call our digital_read() method.
|
// before other components call our digital_read() method.
|
||||||
float CH422GComponent::get_loop_priority() const { return 9.0f; } // Just after WIFI
|
float CH422GComponent::get_loop_priority() const { return 9.0f; } // Just after WIFI
|
||||||
|
|
||||||
void CH422GGPIOPin::setup() { pin_mode(flags_); }
|
|
||||||
void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
||||||
bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; }
|
||||||
|
|
||||||
void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
|
void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value ^ this->inverted_); }
|
||||||
std::string CH422GGPIOPin::dump_summary() const { return str_sprintf("EXIO%u via CH422G", pin_); }
|
std::string CH422GGPIOPin::dump_summary() const { return str_sprintf("EXIO%u via CH422G", pin_); }
|
||||||
|
void CH422GGPIOPin::set_flags(gpio::Flags flags) {
|
||||||
|
flags_ = flags;
|
||||||
|
this->parent_->pin_mode(this->pin_, flags);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ch422g
|
} // namespace ch422g
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@ -23,32 +23,30 @@ class CH422GComponent : public Component, public i2c::I2CDevice {
|
|||||||
void pin_mode(uint8_t pin, gpio::Flags flags);
|
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||||
|
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
float get_loop_priority() const override;
|
float get_loop_priority() const override;
|
||||||
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
bool write_reg_(uint8_t reg, uint8_t value);
|
||||||
|
uint8_t read_reg_(uint8_t reg);
|
||||||
|
bool set_mode_(uint8_t mode);
|
||||||
bool read_inputs_();
|
bool read_inputs_();
|
||||||
|
bool write_outputs_();
|
||||||
bool write_output_(uint8_t value);
|
|
||||||
|
|
||||||
/// The mask to write as output state - 1 means HIGH, 0 means LOW
|
/// The mask to write as output state - 1 means HIGH, 0 means LOW
|
||||||
uint8_t state_mask_{0x00};
|
uint16_t output_bits_{0x00};
|
||||||
/// Flags to check if read previously during this loop
|
/// Flags to check if read previously during this loop
|
||||||
uint8_t pin_read_cache_ = {0x00};
|
uint8_t pin_read_flags_ = {0x00};
|
||||||
/// Storage for last I2C error seen
|
/// Copy of last read values
|
||||||
esphome::i2c::ErrorCode last_error_;
|
uint8_t input_bits_ = {0x00};
|
||||||
/// Whether we want to override stored values on expander
|
/// Copy of the mode value
|
||||||
bool restore_value_{false};
|
uint8_t mode_value_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Helper class to expose a CH422G pin as an internal input GPIO pin.
|
/// Helper class to expose a CH422G pin as a GPIO pin.
|
||||||
class CH422GGPIOPin : public GPIOPin {
|
class CH422GGPIOPin : public GPIOPin {
|
||||||
public:
|
public:
|
||||||
void setup() override;
|
void setup() override{};
|
||||||
void pin_mode(gpio::Flags flags) override;
|
void pin_mode(gpio::Flags flags) override;
|
||||||
bool digital_read() override;
|
bool digital_read() override;
|
||||||
void digital_write(bool value) override;
|
void digital_write(bool value) override;
|
||||||
@ -57,13 +55,13 @@ class CH422GGPIOPin : public GPIOPin {
|
|||||||
void set_parent(CH422GComponent *parent) { parent_ = parent; }
|
void set_parent(CH422GComponent *parent) { parent_ = parent; }
|
||||||
void set_pin(uint8_t pin) { pin_ = pin; }
|
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||||
void set_inverted(bool inverted) { inverted_ = inverted; }
|
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||||
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
void set_flags(gpio::Flags flags);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
CH422GComponent *parent_;
|
CH422GComponent *parent_{};
|
||||||
uint8_t pin_;
|
uint8_t pin_{};
|
||||||
bool inverted_;
|
bool inverted_{};
|
||||||
gpio::Flags flags_;
|
gpio::Flags flags_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ch422g
|
} // namespace ch422g
|
||||||
|
@ -43,7 +43,7 @@ from esphome.const import (
|
|||||||
CONF_TEMPERATURE_STEP,
|
CONF_TEMPERATURE_STEP,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_VISUAL,
|
CONF_VISUAL,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
@ -408,9 +408,8 @@ async def setup_climate_core_(var, config):
|
|||||||
trigger, [(ClimateCall.operator("ref"), "x")], conf
|
trigger, [(ClimateCall.operator("ref"), "x")], conf
|
||||||
)
|
)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_climate(var, config):
|
async def register_climate(var, config):
|
||||||
|
@ -17,7 +17,7 @@ from esphome.const import (
|
|||||||
CONF_TILT_COMMAND_TOPIC,
|
CONF_TILT_COMMAND_TOPIC,
|
||||||
CONF_TILT_STATE_TOPIC,
|
CONF_TILT_STATE_TOPIC,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
DEVICE_CLASS_AWNING,
|
DEVICE_CLASS_AWNING,
|
||||||
DEVICE_CLASS_BLIND,
|
DEVICE_CLASS_BLIND,
|
||||||
DEVICE_CLASS_CURTAIN,
|
DEVICE_CLASS_CURTAIN,
|
||||||
@ -137,10 +137,6 @@ async def setup_cover_core_(var, config):
|
|||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [], conf)
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
@ -156,6 +152,9 @@ async def setup_cover_core_(var, config):
|
|||||||
if (tilt_command_topic := config.get(CONF_TILT_COMMAND_TOPIC)) is not None:
|
if (tilt_command_topic := config.get(CONF_TILT_COMMAND_TOPIC)) is not None:
|
||||||
cg.add(mqtt_.set_custom_tilt_command_topic(tilt_command_topic))
|
cg.add(mqtt_.set_custom_tilt_command_topic(tilt_command_topic))
|
||||||
|
|
||||||
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
|
|
||||||
|
|
||||||
async def register_cover(var, config):
|
async def register_cover(var, config):
|
||||||
if not CORE.has_id(config[CONF_ID]):
|
if not CORE.has_id(config[CONF_ID]):
|
||||||
|
@ -147,6 +147,7 @@ void CSE7766Component::parse_data_() {
|
|||||||
float power = 0.0f;
|
float power = 0.0f;
|
||||||
if (power_cycle_exceeds_range) {
|
if (power_cycle_exceeds_range) {
|
||||||
// Datasheet: power cycle exceeding range means active power is 0
|
// Datasheet: power cycle exceeding range means active power is 0
|
||||||
|
have_power = true;
|
||||||
if (this->power_sensor_ != nullptr) {
|
if (this->power_sensor_ != nullptr) {
|
||||||
this->power_sensor_->publish_state(0.0f);
|
this->power_sensor_->publish_state(0.0f);
|
||||||
}
|
}
|
||||||
@ -178,6 +179,15 @@ void CSE7766Component::parse_data_() {
|
|||||||
if (this->apparent_power_sensor_ != nullptr) {
|
if (this->apparent_power_sensor_ != nullptr) {
|
||||||
this->apparent_power_sensor_->publish_state(apparent_power);
|
this->apparent_power_sensor_->publish_state(apparent_power);
|
||||||
}
|
}
|
||||||
|
if (have_power && this->reactive_power_sensor_ != nullptr) {
|
||||||
|
const float reactive_power = apparent_power - power;
|
||||||
|
if (reactive_power < 0.0f) {
|
||||||
|
ESP_LOGD(TAG, "Impossible reactive power: %.4f is negative", reactive_power);
|
||||||
|
this->reactive_power_sensor_->publish_state(0.0f);
|
||||||
|
} else {
|
||||||
|
this->reactive_power_sensor_->publish_state(reactive_power);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this->power_factor_sensor_ != nullptr && (have_power || power_cycle_exceeds_range)) {
|
if (this->power_factor_sensor_ != nullptr && (have_power || power_cycle_exceeds_range)) {
|
||||||
float pf = NAN;
|
float pf = NAN;
|
||||||
if (apparent_power > 0) {
|
if (apparent_power > 0) {
|
||||||
@ -232,6 +242,7 @@ void CSE7766Component::dump_config() {
|
|||||||
LOG_SENSOR(" ", "Power", this->power_sensor_);
|
LOG_SENSOR(" ", "Power", this->power_sensor_);
|
||||||
LOG_SENSOR(" ", "Energy", this->energy_sensor_);
|
LOG_SENSOR(" ", "Energy", this->energy_sensor_);
|
||||||
LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_);
|
LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Reactive Power", this->reactive_power_sensor_);
|
||||||
LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_);
|
LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_);
|
||||||
this->check_uart_settings(4800);
|
this->check_uart_settings(4800);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@ class CSE7766Component : public Component, public uart::UARTDevice {
|
|||||||
void set_apparent_power_sensor(sensor::Sensor *apparent_power_sensor) {
|
void set_apparent_power_sensor(sensor::Sensor *apparent_power_sensor) {
|
||||||
apparent_power_sensor_ = apparent_power_sensor;
|
apparent_power_sensor_ = apparent_power_sensor;
|
||||||
}
|
}
|
||||||
|
void set_reactive_power_sensor(sensor::Sensor *reactive_power_sensor) {
|
||||||
|
reactive_power_sensor_ = reactive_power_sensor;
|
||||||
|
}
|
||||||
void set_power_factor_sensor(sensor::Sensor *power_factor_sensor) { power_factor_sensor_ = power_factor_sensor; }
|
void set_power_factor_sensor(sensor::Sensor *power_factor_sensor) { power_factor_sensor_ = power_factor_sensor; }
|
||||||
|
|
||||||
void loop() override;
|
void loop() override;
|
||||||
@ -35,6 +38,7 @@ class CSE7766Component : public Component, public uart::UARTDevice {
|
|||||||
sensor::Sensor *power_sensor_{nullptr};
|
sensor::Sensor *power_sensor_{nullptr};
|
||||||
sensor::Sensor *energy_sensor_{nullptr};
|
sensor::Sensor *energy_sensor_{nullptr};
|
||||||
sensor::Sensor *apparent_power_sensor_{nullptr};
|
sensor::Sensor *apparent_power_sensor_{nullptr};
|
||||||
|
sensor::Sensor *reactive_power_sensor_{nullptr};
|
||||||
sensor::Sensor *power_factor_sensor_{nullptr};
|
sensor::Sensor *power_factor_sensor_{nullptr};
|
||||||
uint32_t cf_pulses_total_{0};
|
uint32_t cf_pulses_total_{0};
|
||||||
uint16_t cf_pulses_last_{0};
|
uint16_t cf_pulses_last_{0};
|
||||||
|
@ -8,18 +8,21 @@ from esphome.const import (
|
|||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_POWER,
|
CONF_POWER,
|
||||||
CONF_POWER_FACTOR,
|
CONF_POWER_FACTOR,
|
||||||
|
CONF_REACTIVE_POWER,
|
||||||
CONF_VOLTAGE,
|
CONF_VOLTAGE,
|
||||||
DEVICE_CLASS_APPARENT_POWER,
|
DEVICE_CLASS_APPARENT_POWER,
|
||||||
DEVICE_CLASS_CURRENT,
|
DEVICE_CLASS_CURRENT,
|
||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
DEVICE_CLASS_POWER,
|
DEVICE_CLASS_POWER,
|
||||||
DEVICE_CLASS_POWER_FACTOR,
|
DEVICE_CLASS_POWER_FACTOR,
|
||||||
|
DEVICE_CLASS_REACTIVE_POWER,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
STATE_CLASS_TOTAL_INCREASING,
|
STATE_CLASS_TOTAL_INCREASING,
|
||||||
UNIT_AMPERE,
|
UNIT_AMPERE,
|
||||||
UNIT_VOLT,
|
UNIT_VOLT,
|
||||||
UNIT_VOLT_AMPS,
|
UNIT_VOLT_AMPS,
|
||||||
|
UNIT_VOLT_AMPS_REACTIVE,
|
||||||
UNIT_WATT,
|
UNIT_WATT,
|
||||||
UNIT_WATT_HOURS,
|
UNIT_WATT_HOURS,
|
||||||
)
|
)
|
||||||
@ -62,6 +65,12 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
device_class=DEVICE_CLASS_APPARENT_POWER,
|
device_class=DEVICE_CLASS_APPARENT_POWER,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_REACTIVE_POWER,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
||||||
accuracy_decimals=2,
|
accuracy_decimals=2,
|
||||||
device_class=DEVICE_CLASS_POWER_FACTOR,
|
device_class=DEVICE_CLASS_POWER_FACTOR,
|
||||||
@ -70,7 +79,7 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
}
|
}
|
||||||
).extend(uart.UART_DEVICE_SCHEMA)
|
).extend(uart.UART_DEVICE_SCHEMA)
|
||||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||||
"cse7766", baud_rate=4800, require_rx=True
|
"cse7766", baud_rate=4800, parity="EVEN", require_rx=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -94,6 +103,9 @@ async def to_code(config):
|
|||||||
if apparent_power_config := config.get(CONF_APPARENT_POWER):
|
if apparent_power_config := config.get(CONF_APPARENT_POWER):
|
||||||
sens = await sensor.new_sensor(apparent_power_config)
|
sens = await sensor.new_sensor(apparent_power_config)
|
||||||
cg.add(var.set_apparent_power_sensor(sens))
|
cg.add(var.set_apparent_power_sensor(sens))
|
||||||
|
if reactive_power_config := config.get(CONF_REACTIVE_POWER):
|
||||||
|
sens = await sensor.new_sensor(reactive_power_config)
|
||||||
|
cg.add(var.set_reactive_power_sensor(sens))
|
||||||
if power_factor_config := config.get(CONF_POWER_FACTOR):
|
if power_factor_config := config.get(CONF_POWER_FACTOR):
|
||||||
sens = await sensor.new_sensor(power_factor_config)
|
sens = await sensor.new_sensor(power_factor_config)
|
||||||
cg.add(var.set_power_factor_sensor(sens))
|
cg.add(var.set_power_factor_sensor(sens))
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import esphome.codegen as cg
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
|
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
|
import esphome.codegen as cg
|
||||||
from esphome.components import i2c, touchscreen
|
from esphome.components import i2c, touchscreen
|
||||||
from esphome.const import CONF_INTERRUPT_PIN, CONF_ID, CONF_RESET_PIN
|
import esphome.config_validation as cv
|
||||||
from .. import cst816_ns
|
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN
|
||||||
|
|
||||||
|
from .. import cst816_ns
|
||||||
|
|
||||||
CST816Touchscreen = cst816_ns.class_(
|
CST816Touchscreen = cst816_ns.class_(
|
||||||
"CST816Touchscreen",
|
"CST816Touchscreen",
|
||||||
@ -14,11 +13,14 @@ CST816Touchscreen = cst816_ns.class_(
|
|||||||
)
|
)
|
||||||
|
|
||||||
CST816ButtonListener = cst816_ns.class_("CST816ButtonListener")
|
CST816ButtonListener = cst816_ns.class_("CST816ButtonListener")
|
||||||
|
|
||||||
|
CONF_SKIP_PROBE = "skip_probe"
|
||||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(CST816Touchscreen),
|
cv.GenerateID(): cv.declare_id(CST816Touchscreen),
|
||||||
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
|
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
|
||||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||||
|
cv.Optional(CONF_SKIP_PROBE, default=False): cv.boolean,
|
||||||
}
|
}
|
||||||
).extend(i2c.i2c_device_schema(0x15))
|
).extend(i2c.i2c_device_schema(0x15))
|
||||||
|
|
||||||
@ -28,6 +30,7 @@ async def to_code(config):
|
|||||||
await touchscreen.register_touchscreen(var, config)
|
await touchscreen.register_touchscreen(var, config)
|
||||||
await i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_skip_probe(config[CONF_SKIP_PROBE]))
|
||||||
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
|
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
|
||||||
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
|
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
|
||||||
if reset_pin := config.get(CONF_RESET_PIN):
|
if reset_pin := config.get(CONF_RESET_PIN):
|
||||||
|
@ -8,32 +8,33 @@ void CST816Touchscreen::continue_setup_() {
|
|||||||
this->interrupt_pin_->setup();
|
this->interrupt_pin_->setup();
|
||||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||||
}
|
}
|
||||||
if (!this->read_byte(REG_CHIP_ID, &this->chip_id_)) {
|
if (this->read_byte(REG_CHIP_ID, &this->chip_id_)) {
|
||||||
|
switch (this->chip_id_) {
|
||||||
|
case CST820_CHIP_ID:
|
||||||
|
case CST826_CHIP_ID:
|
||||||
|
case CST716_CHIP_ID:
|
||||||
|
case CST816S_CHIP_ID:
|
||||||
|
case CST816D_CHIP_ID:
|
||||||
|
case CST816T_CHIP_ID:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this->mark_failed();
|
||||||
|
this->status_set_error(str_sprintf("Unknown chip ID 0x%02X", this->chip_id_).c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
|
||||||
|
} else if (!this->skip_probe_) {
|
||||||
|
this->status_set_error("Failed to read chip id");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
esph_log_e(TAG, "Failed to read chip id");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (this->chip_id_) {
|
|
||||||
case CST820_CHIP_ID:
|
|
||||||
case CST826_CHIP_ID:
|
|
||||||
case CST716_CHIP_ID:
|
|
||||||
case CST816S_CHIP_ID:
|
|
||||||
case CST816D_CHIP_ID:
|
|
||||||
case CST816T_CHIP_ID:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this->mark_failed();
|
|
||||||
esph_log_e(TAG, "Unknown chip ID 0x%02X", this->chip_id_);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
|
|
||||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||||
this->x_raw_max_ = this->display_->get_native_width();
|
this->x_raw_max_ = this->display_->get_native_width();
|
||||||
}
|
}
|
||||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||||
this->y_raw_max_ = this->display_->get_native_height();
|
this->y_raw_max_ = this->display_->get_native_height();
|
||||||
}
|
}
|
||||||
esph_log_config(TAG, "CST816 Touchscreen setup complete");
|
ESP_LOGCONFIG(TAG, "CST816 Touchscreen setup complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CST816Touchscreen::update_button_state_(bool state) {
|
void CST816Touchscreen::update_button_state_(bool state) {
|
||||||
@ -45,7 +46,7 @@ void CST816Touchscreen::update_button_state_(bool state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CST816Touchscreen::setup() {
|
void CST816Touchscreen::setup() {
|
||||||
esph_log_config(TAG, "Setting up CST816 Touchscreen...");
|
ESP_LOGCONFIG(TAG, "Setting up CST816 Touchscreen...");
|
||||||
if (this->reset_pin_ != nullptr) {
|
if (this->reset_pin_ != nullptr) {
|
||||||
this->reset_pin_->setup();
|
this->reset_pin_->setup();
|
||||||
this->reset_pin_->digital_write(true);
|
this->reset_pin_->digital_write(true);
|
||||||
@ -73,7 +74,7 @@ void CST816Touchscreen::update_touches() {
|
|||||||
|
|
||||||
uint16_t x = encode_uint16(data[REG_XPOS_HIGH] & 0xF, data[REG_XPOS_LOW]);
|
uint16_t x = encode_uint16(data[REG_XPOS_HIGH] & 0xF, data[REG_XPOS_LOW]);
|
||||||
uint16_t y = encode_uint16(data[REG_YPOS_HIGH] & 0xF, data[REG_YPOS_LOW]);
|
uint16_t y = encode_uint16(data[REG_YPOS_HIGH] & 0xF, data[REG_YPOS_LOW]);
|
||||||
esph_log_v(TAG, "Read touch %d/%d", x, y);
|
ESP_LOGV(TAG, "Read touch %d/%d", x, y);
|
||||||
if (x >= this->x_raw_max_) {
|
if (x >= this->x_raw_max_) {
|
||||||
this->update_button_state_(true);
|
this->update_button_state_(true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -45,6 +45,7 @@ class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
|
|||||||
|
|
||||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||||
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
|
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
|
||||||
|
void set_skip_probe(bool skip_probe) { this->skip_probe_ = skip_probe; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void continue_setup_();
|
void continue_setup_();
|
||||||
@ -53,6 +54,7 @@ class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
|
|||||||
InternalGPIOPin *interrupt_pin_{};
|
InternalGPIOPin *interrupt_pin_{};
|
||||||
GPIOPin *reset_pin_{};
|
GPIOPin *reset_pin_{};
|
||||||
uint8_t chip_id_{};
|
uint8_t chip_id_{};
|
||||||
|
bool skip_probe_{}; // if set, do not expect to be able to probe the controller on the i2c bus.
|
||||||
std::vector<CST816ButtonListener *> button_listeners_;
|
std::vector<CST816ButtonListener *> button_listeners_;
|
||||||
bool button_touched_{};
|
bool button_touched_{};
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,7 @@ from esphome.const import (
|
|||||||
CONF_TIME_ID,
|
CONF_TIME_ID,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
CONF_YEAR,
|
CONF_YEAR,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
@ -26,7 +26,6 @@ from esphome.cpp_generator import MockObjClass
|
|||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
|
|
||||||
CODEOWNERS = ["@rfdarter", "@jesserockz"]
|
CODEOWNERS = ["@rfdarter", "@jesserockz"]
|
||||||
DEPENDENCIES = ["time"]
|
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
@ -62,20 +61,28 @@ DATETIME_MODES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
_DATETIME_SCHEMA = (
|
def _validate_time_present(config):
|
||||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
config = config.copy()
|
||||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
if CONF_ON_TIME in config and CONF_TIME_ID not in config:
|
||||||
.extend(
|
time_id = cv.use_id(time.RealTimeClock)(None)
|
||||||
|
config[CONF_TIME_ID] = time_id
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
_DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||||
|
web_server.WEBSERVER_SORTING_SCHEMA,
|
||||||
|
cv.MQTT_COMMAND_COMPONENT_SCHEMA,
|
||||||
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
|
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||||
}
|
}
|
||||||
)
|
),
|
||||||
)
|
).add_extra(_validate_time_present)
|
||||||
|
|
||||||
|
|
||||||
def date_schema(class_: MockObjClass) -> cv.Schema:
|
def date_schema(class_: MockObjClass) -> cv.Schema:
|
||||||
@ -131,15 +138,15 @@ async def setup_datetime_core_(var, config):
|
|||||||
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
for conf in config.get(CONF_ON_VALUE, []):
|
for conf in config.get(CONF_ON_VALUE, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)
|
await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)
|
||||||
|
|
||||||
rtc = await cg.get_variable(config[CONF_TIME_ID])
|
if CONF_TIME_ID in config:
|
||||||
cg.add(var.set_rtc(rtc))
|
rtc = await cg.get_variable(config[CONF_TIME_ID])
|
||||||
|
cg.add(var.set_rtc(rtc))
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_TIME, []):
|
for conf in config.get(CONF_ON_TIME, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||||
|
@ -4,8 +4,9 @@
|
|||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/entity_base.h"
|
#include "esphome/core/entity_base.h"
|
||||||
#include "esphome/core/time.h"
|
#include "esphome/core/time.h"
|
||||||
|
#ifdef USE_TIME
|
||||||
#include "esphome/components/time/real_time_clock.h"
|
#include "esphome/components/time/real_time_clock.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace datetime {
|
namespace datetime {
|
||||||
@ -19,23 +20,29 @@ class DateTimeBase : public EntityBase {
|
|||||||
|
|
||||||
void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
|
void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
void set_rtc(time::RealTimeClock *rtc) { this->rtc_ = rtc; }
|
void set_rtc(time::RealTimeClock *rtc) { this->rtc_ = rtc; }
|
||||||
time::RealTimeClock *get_rtc() const { return this->rtc_; }
|
time::RealTimeClock *get_rtc() const { return this->rtc_; }
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
CallbackManager<void()> state_callback_;
|
CallbackManager<void()> state_callback_;
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
time::RealTimeClock *rtc_;
|
time::RealTimeClock *rtc_;
|
||||||
|
#endif
|
||||||
|
|
||||||
bool has_state_{false};
|
bool has_state_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
class DateTimeStateTrigger : public Trigger<ESPTime> {
|
class DateTimeStateTrigger : public Trigger<ESPTime> {
|
||||||
public:
|
public:
|
||||||
explicit DateTimeStateTrigger(DateTimeBase *parent) {
|
explicit DateTimeStateTrigger(DateTimeBase *parent) {
|
||||||
parent->add_on_state_callback([this, parent]() { this->trigger(parent->state_as_esptime()); });
|
parent->add_on_state_callback([this, parent]() { this->trigger(parent->state_as_esptime()); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace datetime
|
} // namespace datetime
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@ -192,6 +192,7 @@ void DateTimeEntityRestoreState::apply(DateTimeEntity *time) {
|
|||||||
time->publish_state();
|
time->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
|
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
|
||||||
// there has been a drastic time synchronization
|
// there has been a drastic time synchronization
|
||||||
|
|
||||||
@ -245,6 +246,7 @@ bool OnDateTimeTrigger::matches_(const ESPTime &time) const {
|
|||||||
time.day_of_month == this->parent_->day && time.hour == this->parent_->hour &&
|
time.day_of_month == this->parent_->day && time.hour == this->parent_->hour &&
|
||||||
time.minute == this->parent_->minute && time.second == this->parent_->second;
|
time.minute == this->parent_->minute && time.second == this->parent_->second;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace datetime
|
} // namespace datetime
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@ -134,6 +134,7 @@ template<typename... Ts> class DateTimeSetAction : public Action<Ts...>, public
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
class OnDateTimeTrigger : public Trigger<>, public Component, public Parented<DateTimeEntity> {
|
class OnDateTimeTrigger : public Trigger<>, public Component, public Parented<DateTimeEntity> {
|
||||||
public:
|
public:
|
||||||
void loop() override;
|
void loop() override;
|
||||||
@ -143,6 +144,7 @@ class OnDateTimeTrigger : public Trigger<>, public Component, public Parented<Da
|
|||||||
|
|
||||||
optional<ESPTime> last_check_;
|
optional<ESPTime> last_check_;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace datetime
|
} // namespace datetime
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@ -94,6 +94,7 @@ void TimeEntityRestoreState::apply(TimeEntity *time) {
|
|||||||
time->publish_state();
|
time->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
|
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
|
||||||
// there has been a drastic time synchronization
|
// there has been a drastic time synchronization
|
||||||
|
|
||||||
@ -145,6 +146,7 @@ bool OnTimeTrigger::matches_(const ESPTime &time) const {
|
|||||||
return time.is_valid() && time.hour == this->parent_->hour && time.minute == this->parent_->minute &&
|
return time.is_valid() && time.hour == this->parent_->hour && time.minute == this->parent_->minute &&
|
||||||
time.second == this->parent_->second;
|
time.second == this->parent_->second;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace datetime
|
} // namespace datetime
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@ -113,6 +113,7 @@ template<typename... Ts> class TimeSetAction : public Action<Ts...>, public Pare
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
class OnTimeTrigger : public Trigger<>, public Component, public Parented<TimeEntity> {
|
class OnTimeTrigger : public Trigger<>, public Component, public Parented<TimeEntity> {
|
||||||
public:
|
public:
|
||||||
void loop() override;
|
void loop() override;
|
||||||
@ -122,6 +123,7 @@ class OnTimeTrigger : public Trigger<>, public Component, public Parented<TimeEn
|
|||||||
|
|
||||||
optional<ESPTime> last_check_;
|
optional<ESPTime> last_check_;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace datetime
|
} // namespace datetime
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@ -13,6 +13,7 @@ from esphome.const import (
|
|||||||
CONF_COMPONENTS,
|
CONF_COMPONENTS,
|
||||||
CONF_ESPHOME,
|
CONF_ESPHOME,
|
||||||
CONF_FRAMEWORK,
|
CONF_FRAMEWORK,
|
||||||
|
CONF_IGNORE_EFUSE_CUSTOM_MAC,
|
||||||
CONF_IGNORE_EFUSE_MAC_CRC,
|
CONF_IGNORE_EFUSE_MAC_CRC,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_PATH,
|
CONF_PATH,
|
||||||
@ -52,6 +53,7 @@ from .const import ( # noqa
|
|||||||
KEY_SDKCONFIG_OPTIONS,
|
KEY_SDKCONFIG_OPTIONS,
|
||||||
KEY_SUBMODULES,
|
KEY_SUBMODULES,
|
||||||
KEY_VARIANT,
|
KEY_VARIANT,
|
||||||
|
VARIANT_ESP32,
|
||||||
VARIANT_FRIENDLY,
|
VARIANT_FRIENDLY,
|
||||||
VARIANTS,
|
VARIANTS,
|
||||||
)
|
)
|
||||||
@ -239,7 +241,7 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0)
|
|||||||
# The default/recommended esp-idf framework version
|
# The default/recommended esp-idf framework version
|
||||||
# - https://github.com/espressif/esp-idf/releases
|
# - https://github.com/espressif/esp-idf/releases
|
||||||
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
|
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
|
||||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 7)
|
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 8)
|
||||||
# The platformio/espressif32 version to use for esp-idf frameworks
|
# The platformio/espressif32 version to use for esp-idf frameworks
|
||||||
# - https://github.com/platformio/platform-espressif32/releases
|
# - https://github.com/platformio/platform-espressif32/releases
|
||||||
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
|
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
|
||||||
@ -375,6 +377,15 @@ def final_validate(config):
|
|||||||
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
|
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
config[CONF_VARIANT] != VARIANT_ESP32
|
||||||
|
and CONF_ADVANCED in (conf_fw := config[CONF_FRAMEWORK])
|
||||||
|
and CONF_IGNORE_EFUSE_MAC_CRC in conf_fw[CONF_ADVANCED]
|
||||||
|
):
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"{CONF_IGNORE_EFUSE_MAC_CRC} is not supported on {config[CONF_VARIANT]}"
|
||||||
|
)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
@ -401,7 +412,10 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
|||||||
},
|
},
|
||||||
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
|
cv.Optional(CONF_ADVANCED, default={}): cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean,
|
cv.Optional(
|
||||||
|
CONF_IGNORE_EFUSE_CUSTOM_MAC, default=False
|
||||||
|
): cv.boolean,
|
||||||
|
cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC): cv.boolean,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||||
@ -526,8 +540,10 @@ async def to_code(config):
|
|||||||
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
|
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
|
||||||
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
||||||
|
|
||||||
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_MAC_CRC]:
|
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]:
|
||||||
cg.add_define("USE_ESP32_IGNORE_EFUSE_MAC_CRC")
|
cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC")
|
||||||
|
if conf[CONF_ADVANCED].get(CONF_IGNORE_EFUSE_MAC_CRC):
|
||||||
|
add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True)
|
||||||
if (framework_ver.major, framework_ver.minor) >= (4, 4):
|
if (framework_ver.major, framework_ver.minor) >= (4, 4):
|
||||||
add_idf_sdkconfig_option(
|
add_idf_sdkconfig_option(
|
||||||
"CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
"CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import binary_sensor, esp32_ble_server, output
|
from esphome.components import binary_sensor, esp32_ble_server, output
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID
|
||||||
|
|
||||||
AUTO_LOAD = ["esp32_ble_server"]
|
AUTO_LOAD = ["esp32_ble_server"]
|
||||||
CODEOWNERS = ["@jesserockz"]
|
CODEOWNERS = ["@jesserockz"]
|
||||||
@ -11,13 +12,36 @@ CONF_AUTHORIZED_DURATION = "authorized_duration"
|
|||||||
CONF_AUTHORIZER = "authorizer"
|
CONF_AUTHORIZER = "authorizer"
|
||||||
CONF_BLE_SERVER_ID = "ble_server_id"
|
CONF_BLE_SERVER_ID = "ble_server_id"
|
||||||
CONF_IDENTIFY_DURATION = "identify_duration"
|
CONF_IDENTIFY_DURATION = "identify_duration"
|
||||||
|
CONF_ON_PROVISIONED = "on_provisioned"
|
||||||
|
CONF_ON_PROVISIONING = "on_provisioning"
|
||||||
|
CONF_ON_START = "on_start"
|
||||||
|
CONF_ON_STOP = "on_stop"
|
||||||
CONF_STATUS_INDICATOR = "status_indicator"
|
CONF_STATUS_INDICATOR = "status_indicator"
|
||||||
CONF_WIFI_TIMEOUT = "wifi_timeout"
|
CONF_WIFI_TIMEOUT = "wifi_timeout"
|
||||||
|
|
||||||
|
improv_ns = cg.esphome_ns.namespace("improv")
|
||||||
|
Error = improv_ns.enum("Error")
|
||||||
|
State = improv_ns.enum("State")
|
||||||
|
|
||||||
esp32_improv_ns = cg.esphome_ns.namespace("esp32_improv")
|
esp32_improv_ns = cg.esphome_ns.namespace("esp32_improv")
|
||||||
ESP32ImprovComponent = esp32_improv_ns.class_(
|
ESP32ImprovComponent = esp32_improv_ns.class_(
|
||||||
"ESP32ImprovComponent", cg.Component, esp32_ble_server.BLEServiceComponent
|
"ESP32ImprovComponent", cg.Component, esp32_ble_server.BLEServiceComponent
|
||||||
)
|
)
|
||||||
|
ESP32ImprovProvisionedTrigger = esp32_improv_ns.class_(
|
||||||
|
"ESP32ImprovProvisionedTrigger", automation.Trigger.template()
|
||||||
|
)
|
||||||
|
ESP32ImprovProvisioningTrigger = esp32_improv_ns.class_(
|
||||||
|
"ESP32ImprovProvisioningTrigger", automation.Trigger.template()
|
||||||
|
)
|
||||||
|
ESP32ImprovStartTrigger = esp32_improv_ns.class_(
|
||||||
|
"ESP32ImprovStartTrigger", automation.Trigger.template()
|
||||||
|
)
|
||||||
|
ESP32ImprovStateTrigger = esp32_improv_ns.class_(
|
||||||
|
"ESP32ImprovStateTrigger", automation.Trigger.template()
|
||||||
|
)
|
||||||
|
ESP32ImprovStoppedTrigger = esp32_improv_ns.class_(
|
||||||
|
"ESP32ImprovStoppedTrigger", automation.Trigger.template()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
@ -37,6 +61,37 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_WIFI_TIMEOUT, default="1min"
|
CONF_WIFI_TIMEOUT, default="1min"
|
||||||
): cv.positive_time_period_milliseconds,
|
): cv.positive_time_period_milliseconds,
|
||||||
|
cv.Optional(CONF_ON_PROVISIONED): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
|
ESP32ImprovProvisionedTrigger
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ON_PROVISIONING): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
|
ESP32ImprovProvisioningTrigger
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ON_START): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESP32ImprovStartTrigger),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESP32ImprovStateTrigger),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ON_STOP): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
|
ESP32ImprovStoppedTrigger
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
@ -63,3 +118,29 @@ async def to_code(config):
|
|||||||
if CONF_STATUS_INDICATOR in config:
|
if CONF_STATUS_INDICATOR in config:
|
||||||
status_indicator = await cg.get_variable(config[CONF_STATUS_INDICATOR])
|
status_indicator = await cg.get_variable(config[CONF_STATUS_INDICATOR])
|
||||||
cg.add(var.set_status_indicator(status_indicator))
|
cg.add(var.set_status_indicator(status_indicator))
|
||||||
|
|
||||||
|
use_state_callback = False
|
||||||
|
for conf in config.get(CONF_ON_PROVISIONED, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
use_state_callback = True
|
||||||
|
for conf in config.get(CONF_ON_PROVISIONING, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
use_state_callback = True
|
||||||
|
for conf in config.get(CONF_ON_START, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
use_state_callback = True
|
||||||
|
for conf in config.get(CONF_ON_STATE, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await automation.build_automation(
|
||||||
|
trigger, [(State, "state"), (Error, "error")], conf
|
||||||
|
)
|
||||||
|
use_state_callback = True
|
||||||
|
for conf in config.get(CONF_ON_STOP, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
use_state_callback = True
|
||||||
|
if use_state_callback:
|
||||||
|
cg.add_define("USE_ESP32_IMPROV_STATE_CALLBACK")
|
||||||
|
72
esphome/components/esp32_improv/automation.h
Normal file
72
esphome/components/esp32_improv/automation.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifdef USE_ESP32
|
||||||
|
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
|
||||||
|
#include "esp32_improv_component.h"
|
||||||
|
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
|
||||||
|
#include <improv.h>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace esp32_improv {
|
||||||
|
|
||||||
|
class ESP32ImprovProvisionedTrigger : public Trigger<> {
|
||||||
|
public:
|
||||||
|
explicit ESP32ImprovProvisionedTrigger(ESP32ImprovComponent *parent) {
|
||||||
|
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
|
||||||
|
if (state == improv::STATE_PROVISIONED && !parent->is_failed()) {
|
||||||
|
trigger();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ESP32ImprovProvisioningTrigger : public Trigger<> {
|
||||||
|
public:
|
||||||
|
explicit ESP32ImprovProvisioningTrigger(ESP32ImprovComponent *parent) {
|
||||||
|
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
|
||||||
|
if (state == improv::STATE_PROVISIONING && !parent->is_failed()) {
|
||||||
|
trigger();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ESP32ImprovStartTrigger : public Trigger<> {
|
||||||
|
public:
|
||||||
|
explicit ESP32ImprovStartTrigger(ESP32ImprovComponent *parent) {
|
||||||
|
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
|
||||||
|
if ((state == improv::STATE_AUTHORIZED || state == improv::STATE_AWAITING_AUTHORIZATION) &&
|
||||||
|
!parent->is_failed()) {
|
||||||
|
trigger();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ESP32ImprovStateTrigger : public Trigger<improv::State, improv::Error> {
|
||||||
|
public:
|
||||||
|
explicit ESP32ImprovStateTrigger(ESP32ImprovComponent *parent) {
|
||||||
|
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
|
||||||
|
if (!parent->is_failed()) {
|
||||||
|
trigger(state, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ESP32ImprovStoppedTrigger : public Trigger<> {
|
||||||
|
public:
|
||||||
|
explicit ESP32ImprovStoppedTrigger(ESP32ImprovComponent *parent) {
|
||||||
|
parent->add_on_state_callback([this, parent](improv::State state, improv::Error error) {
|
||||||
|
if (state == improv::STATE_STOPPED && !parent->is_failed()) {
|
||||||
|
trigger();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esp32_improv
|
||||||
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
#endif
|
@ -68,7 +68,12 @@ void ESP32ImprovComponent::setup_characteristics() {
|
|||||||
|
|
||||||
void ESP32ImprovComponent::loop() {
|
void ESP32ImprovComponent::loop() {
|
||||||
if (!global_ble_server->is_running()) {
|
if (!global_ble_server->is_running()) {
|
||||||
this->state_ = improv::STATE_STOPPED;
|
if (this->state_ != improv::STATE_STOPPED) {
|
||||||
|
this->state_ = improv::STATE_STOPPED;
|
||||||
|
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
|
||||||
|
this->state_callback_.call(this->state_, this->error_state_);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
this->incoming_data_.clear();
|
this->incoming_data_.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -217,6 +222,9 @@ void ESP32ImprovComponent::set_state_(improv::State state) {
|
|||||||
service_data[7] = 0x00; // Reserved
|
service_data[7] = 0x00; // Reserved
|
||||||
|
|
||||||
esp32_ble::global_ble->advertising_set_service_data(service_data);
|
esp32_ble::global_ble->advertising_set_service_data(service_data);
|
||||||
|
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
|
||||||
|
this->state_callback_.call(this->state_, this->error_state_);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESP32ImprovComponent::set_error_(improv::Error error) {
|
void ESP32ImprovComponent::set_error_(improv::Error error) {
|
||||||
@ -270,7 +278,7 @@ void ESP32ImprovComponent::dump_config() {
|
|||||||
void ESP32ImprovComponent::process_incoming_data_() {
|
void ESP32ImprovComponent::process_incoming_data_() {
|
||||||
uint8_t length = this->incoming_data_[1];
|
uint8_t length = this->incoming_data_[1];
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
|
ESP_LOGV(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
|
||||||
if (this->incoming_data_.size() - 3 == length) {
|
if (this->incoming_data_.size() - 3 == length) {
|
||||||
this->set_error_(improv::ERROR_NONE);
|
this->set_error_(improv::ERROR_NONE);
|
||||||
improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
|
improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
|
||||||
@ -295,7 +303,7 @@ void ESP32ImprovComponent::process_incoming_data_() {
|
|||||||
wifi::global_wifi_component->set_sta(sta);
|
wifi::global_wifi_component->set_sta(sta);
|
||||||
wifi::global_wifi_component->start_connecting(sta, false);
|
wifi::global_wifi_component->start_connecting(sta, false);
|
||||||
this->set_state_(improv::STATE_PROVISIONING);
|
this->set_state_(improv::STATE_PROVISIONING);
|
||||||
ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
ESP_LOGD(TAG, "Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||||
command.password.c_str());
|
command.password.c_str());
|
||||||
|
|
||||||
auto f = std::bind(&ESP32ImprovComponent::on_wifi_connect_timeout_, this);
|
auto f = std::bind(&ESP32ImprovComponent::on_wifi_connect_timeout_, this);
|
||||||
@ -313,7 +321,7 @@ void ESP32ImprovComponent::process_incoming_data_() {
|
|||||||
this->incoming_data_.clear();
|
this->incoming_data_.clear();
|
||||||
}
|
}
|
||||||
} else if (this->incoming_data_.size() - 2 > length) {
|
} else if (this->incoming_data_.size() - 2 > length) {
|
||||||
ESP_LOGV(TAG, "Too much data came in, or malformed resetting buffer...");
|
ESP_LOGV(TAG, "Too much data received or data malformed; resetting buffer...");
|
||||||
this->incoming_data_.clear();
|
this->incoming_data_.clear();
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Waiting for split data packets...");
|
ESP_LOGV(TAG, "Waiting for split data packets...");
|
||||||
@ -327,7 +335,7 @@ void ESP32ImprovComponent::on_wifi_connect_timeout_() {
|
|||||||
if (this->authorizer_ != nullptr)
|
if (this->authorizer_ != nullptr)
|
||||||
this->authorized_start_ = millis();
|
this->authorized_start_ = millis();
|
||||||
#endif
|
#endif
|
||||||
ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
|
ESP_LOGW(TAG, "Timed out while connecting to Wi-Fi network");
|
||||||
wifi::global_wifi_component->clear_sta();
|
wifi::global_wifi_component->clear_sta();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,10 @@
|
|||||||
#include "esphome/components/esp32_ble_server/ble_server.h"
|
#include "esphome/components/esp32_ble_server/ble_server.h"
|
||||||
#include "esphome/components/wifi/wifi_component.h"
|
#include "esphome/components/wifi/wifi_component.h"
|
||||||
|
|
||||||
|
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
#endif
|
#endif
|
||||||
@ -42,6 +46,11 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent {
|
|||||||
void stop() override;
|
void stop() override;
|
||||||
bool is_active() const { return this->state_ != improv::STATE_STOPPED; }
|
bool is_active() const { return this->state_ != improv::STATE_STOPPED; }
|
||||||
|
|
||||||
|
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
|
||||||
|
void add_on_state_callback(std::function<void(improv::State, improv::Error)> &&callback) {
|
||||||
|
this->state_callback_.add(std::move(callback));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
void set_authorizer(binary_sensor::BinarySensor *authorizer) { this->authorizer_ = authorizer; }
|
void set_authorizer(binary_sensor::BinarySensor *authorizer) { this->authorizer_ = authorizer; }
|
||||||
#endif
|
#endif
|
||||||
@ -54,6 +63,9 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent {
|
|||||||
void set_wifi_timeout(uint32_t wifi_timeout) { this->wifi_timeout_ = wifi_timeout; }
|
void set_wifi_timeout(uint32_t wifi_timeout) { this->wifi_timeout_ = wifi_timeout; }
|
||||||
uint32_t get_wifi_timeout() const { return this->wifi_timeout_; }
|
uint32_t get_wifi_timeout() const { return this->wifi_timeout_; }
|
||||||
|
|
||||||
|
improv::State get_improv_state() const { return this->state_; }
|
||||||
|
improv::Error get_improv_error_state() const { return this->error_state_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool should_start_{false};
|
bool should_start_{false};
|
||||||
bool setup_complete_{false};
|
bool setup_complete_{false};
|
||||||
@ -84,6 +96,9 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent {
|
|||||||
|
|
||||||
improv::State state_{improv::STATE_STOPPED};
|
improv::State state_{improv::STATE_STOPPED};
|
||||||
improv::Error error_state_{improv::ERROR_NONE};
|
improv::Error error_state_{improv::ERROR_NONE};
|
||||||
|
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
|
||||||
|
CallbackManager<void(improv::State, improv::Error)> state_callback_{};
|
||||||
|
#endif
|
||||||
|
|
||||||
bool status_indicator_state_{false};
|
bool status_indicator_state_{false};
|
||||||
void set_status_indicator_state_(bool state);
|
void set_status_indicator_state_(bool state);
|
||||||
|
@ -59,6 +59,7 @@ ETHERNET_TYPES = {
|
|||||||
"KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081,
|
"KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081,
|
||||||
"KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA,
|
"KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA,
|
||||||
"W5500": EthernetType.ETHERNET_TYPE_W5500,
|
"W5500": EthernetType.ETHERNET_TYPE_W5500,
|
||||||
|
"OPENETH": EthernetType.ETHERNET_TYPE_OPENETH,
|
||||||
}
|
}
|
||||||
|
|
||||||
SPI_ETHERNET_TYPES = ["W5500"]
|
SPI_ETHERNET_TYPES = ["W5500"]
|
||||||
@ -171,6 +172,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
"KSZ8081": RMII_SCHEMA,
|
"KSZ8081": RMII_SCHEMA,
|
||||||
"KSZ8081RNA": RMII_SCHEMA,
|
"KSZ8081RNA": RMII_SCHEMA,
|
||||||
"W5500": SPI_SCHEMA,
|
"W5500": SPI_SCHEMA,
|
||||||
|
"OPENETH": BASE_SCHEMA,
|
||||||
},
|
},
|
||||||
upper=True,
|
upper=True,
|
||||||
),
|
),
|
||||||
@ -240,6 +242,9 @@ async def to_code(config):
|
|||||||
if CORE.using_esp_idf:
|
if CORE.using_esp_idf:
|
||||||
add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True)
|
add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True)
|
||||||
add_idf_sdkconfig_option("CONFIG_ETH_SPI_ETHERNET_W5500", True)
|
add_idf_sdkconfig_option("CONFIG_ETH_SPI_ETHERNET_W5500", True)
|
||||||
|
elif config[CONF_TYPE] == "OPENETH":
|
||||||
|
cg.add_define("USE_ETHERNET_OPENETH")
|
||||||
|
add_idf_sdkconfig_option("CONFIG_ETH_USE_OPENETH", True)
|
||||||
else:
|
else:
|
||||||
cg.add(var.set_phy_addr(config[CONF_PHY_ADDR]))
|
cg.add(var.set_phy_addr(config[CONF_PHY_ADDR]))
|
||||||
cg.add(var.set_mdc_pin(config[CONF_MDC_PIN]))
|
cg.add(var.set_mdc_pin(config[CONF_MDC_PIN]))
|
||||||
|
@ -120,6 +120,8 @@ void EthernetComponent::setup() {
|
|||||||
phy_config.reset_gpio_num = this->reset_pin_;
|
phy_config.reset_gpio_num = this->reset_pin_;
|
||||||
|
|
||||||
esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
|
esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
|
||||||
|
#elif defined(USE_ETHERNET_OPENETH)
|
||||||
|
esp_eth_mac_t *mac = esp_eth_mac_new_openeth(&mac_config);
|
||||||
#else
|
#else
|
||||||
phy_config.phy_addr = this->phy_addr_;
|
phy_config.phy_addr = this->phy_addr_;
|
||||||
phy_config.reset_gpio_num = this->power_pin_;
|
phy_config.reset_gpio_num = this->power_pin_;
|
||||||
@ -143,6 +145,13 @@ void EthernetComponent::setup() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
switch (this->type_) {
|
switch (this->type_) {
|
||||||
|
#ifdef USE_ETHERNET_OPENETH
|
||||||
|
case ETHERNET_TYPE_OPENETH: {
|
||||||
|
phy_config.autonego_timeout_ms = 1000;
|
||||||
|
this->phy_ = esp_eth_phy_new_dp83848(&phy_config);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#if CONFIG_ETH_USE_ESP32_EMAC
|
#if CONFIG_ETH_USE_ESP32_EMAC
|
||||||
case ETHERNET_TYPE_LAN8720: {
|
case ETHERNET_TYPE_LAN8720: {
|
||||||
this->phy_ = esp_eth_phy_new_lan87xx(&phy_config);
|
this->phy_ = esp_eth_phy_new_lan87xx(&phy_config);
|
||||||
@ -302,6 +311,10 @@ void EthernetComponent::dump_config() {
|
|||||||
eth_type = "W5500";
|
eth_type = "W5500";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ETHERNET_TYPE_OPENETH:
|
||||||
|
eth_type = "OPENETH";
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
eth_type = "Unknown";
|
eth_type = "Unknown";
|
||||||
break;
|
break;
|
||||||
|
@ -25,6 +25,7 @@ enum EthernetType {
|
|||||||
ETHERNET_TYPE_KSZ8081,
|
ETHERNET_TYPE_KSZ8081,
|
||||||
ETHERNET_TYPE_KSZ8081RNA,
|
ETHERNET_TYPE_KSZ8081RNA,
|
||||||
ETHERNET_TYPE_W5500,
|
ETHERNET_TYPE_W5500,
|
||||||
|
ETHERNET_TYPE_OPENETH,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ManualIP {
|
struct ManualIP {
|
||||||
|
@ -25,7 +25,7 @@ from esphome.const import (
|
|||||||
CONF_SPEED_LEVEL_STATE_TOPIC,
|
CONF_SPEED_LEVEL_STATE_TOPIC,
|
||||||
CONF_SPEED_STATE_TOPIC,
|
CONF_SPEED_STATE_TOPIC,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
@ -218,9 +218,8 @@ async def setup_fan_core_(var, config):
|
|||||||
if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None:
|
if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None:
|
||||||
cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic))
|
cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic))
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_STATE, []):
|
for conf in config.get(CONF_ON_STATE, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
0
esphome/components/gp2y1010au0f/__init__.py
Normal file
0
esphome/components/gp2y1010au0f/__init__.py
Normal file
67
esphome/components/gp2y1010au0f/gp2y1010au0f.cpp
Normal file
67
esphome/components/gp2y1010au0f/gp2y1010au0f.cpp
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#include "gp2y1010au0f.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace gp2y1010au0f {
|
||||||
|
|
||||||
|
static const char *const TAG = "gp2y1010au0f";
|
||||||
|
static const float MIN_VOLTAGE = 0.0f;
|
||||||
|
static const float MAX_VOLTAGE = 4.0f;
|
||||||
|
|
||||||
|
void GP2Y1010AU0FSensor::dump_config() {
|
||||||
|
LOG_SENSOR("", "Sharp GP2Y1010AU0F PM2.5 Sensor", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Sampling duration: %" PRId32 " ms", this->sample_duration_);
|
||||||
|
ESP_LOGCONFIG(TAG, " ADC voltage multiplier: %.3f", this->voltage_multiplier_);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GP2Y1010AU0FSensor::update() {
|
||||||
|
is_sampling_ = true;
|
||||||
|
|
||||||
|
this->set_timeout("read", this->sample_duration_, [this]() {
|
||||||
|
this->is_sampling_ = false;
|
||||||
|
if (this->num_samples_ == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float mean = this->sample_sum_ / float(this->num_samples_);
|
||||||
|
ESP_LOGD(TAG, "ADC read voltage: %.3f V (mean from %" PRId32 " samples)", mean, this->num_samples_);
|
||||||
|
|
||||||
|
// PM2.5 calculation
|
||||||
|
// ref: https://www.howmuchsnow.com/arduino/airquality/
|
||||||
|
int16_t pm_2_5_value = 170 * mean;
|
||||||
|
this->publish_state(pm_2_5_value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// reset readings
|
||||||
|
this->num_samples_ = 0;
|
||||||
|
this->sample_sum_ = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GP2Y1010AU0FSensor::loop() {
|
||||||
|
if (!this->is_sampling_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// enable the internal IR LED
|
||||||
|
this->led_output_->turn_on();
|
||||||
|
// wait for the sensor to stabilize
|
||||||
|
delayMicroseconds(this->sample_wait_before_);
|
||||||
|
// perform a single sample
|
||||||
|
float read_voltage = this->source_->sample();
|
||||||
|
// disable the internal IR LED
|
||||||
|
this->led_output_->turn_off();
|
||||||
|
|
||||||
|
if (std::isnan(read_voltage))
|
||||||
|
return;
|
||||||
|
read_voltage = read_voltage * this->voltage_multiplier_ - this->voltage_offset_;
|
||||||
|
if (read_voltage < MIN_VOLTAGE || read_voltage > MAX_VOLTAGE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->num_samples_++;
|
||||||
|
this->sample_sum_ += read_voltage;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gp2y1010au0f
|
||||||
|
} // namespace esphome
|
52
esphome/components/gp2y1010au0f/gp2y1010au0f.h
Normal file
52
esphome/components/gp2y1010au0f/gp2y1010au0f.h
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||||
|
#include "esphome/components/output/binary_output.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace gp2y1010au0f {
|
||||||
|
|
||||||
|
class GP2Y1010AU0FSensor : public sensor::Sensor, public PollingComponent {
|
||||||
|
public:
|
||||||
|
void update() override;
|
||||||
|
void loop() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override {
|
||||||
|
// after the base sensor has been initialized
|
||||||
|
return setup_priority::DATA - 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_adc_source(voltage_sampler::VoltageSampler *source) { source_ = source; }
|
||||||
|
void set_voltage_refs(float offset, float multiplier) {
|
||||||
|
this->voltage_offset_ = offset;
|
||||||
|
this->voltage_multiplier_ = multiplier;
|
||||||
|
}
|
||||||
|
void set_led_output(output::BinaryOutput *output) { led_output_ = output; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// duration in ms of the sampling phase
|
||||||
|
uint32_t sample_duration_ = 100;
|
||||||
|
// duration in us of the wait before sampling
|
||||||
|
// ref: https://global.sharp/products/device/lineup/data/pdf/datasheet/gp2y1010au_appl_e.pdf
|
||||||
|
uint32_t sample_wait_before_ = 280;
|
||||||
|
// duration in us of the wait after sampling
|
||||||
|
// it seems no need to delay on purpose since one ADC sampling takes longer than that (300-400 us on ESP8266)
|
||||||
|
// uint32_t sample_wait_after_ = 40;
|
||||||
|
// the sampling source to read voltage from
|
||||||
|
voltage_sampler::VoltageSampler *source_;
|
||||||
|
// ADC voltage reading offset
|
||||||
|
float voltage_offset_ = 0.0f;
|
||||||
|
// ADC voltage reading multiplier
|
||||||
|
float voltage_multiplier_ = 1.0f;
|
||||||
|
// the binary output to control the sampling LED
|
||||||
|
output::BinaryOutput *led_output_;
|
||||||
|
|
||||||
|
float sample_sum_ = 0.0f;
|
||||||
|
uint32_t num_samples_ = 0;
|
||||||
|
bool is_sampling_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gp2y1010au0f
|
||||||
|
} // namespace esphome
|
61
esphome/components/gp2y1010au0f/sensor.py
Normal file
61
esphome/components/gp2y1010au0f/sensor.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor, voltage_sampler, output
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_SENSOR,
|
||||||
|
CONF_OUTPUT,
|
||||||
|
DEVICE_CLASS_PM25,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
ICON_CHEMICAL_WEAPON,
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPENDENCIES = ["output"]
|
||||||
|
AUTO_LOAD = ["voltage_sampler"]
|
||||||
|
CODEOWNERS = ["@zry98"]
|
||||||
|
|
||||||
|
CONF_ADC_VOLTAGE_OFFSET = "adc_voltage_offset"
|
||||||
|
CONF_ADC_VOLTAGE_MULTIPLIER = "adc_voltage_multiplier"
|
||||||
|
|
||||||
|
gp2y1010au0f_ns = cg.esphome_ns.namespace("gp2y1010au0f")
|
||||||
|
GP2Y1010AU0FSensor = gp2y1010au0f_ns.class_(
|
||||||
|
"GP2Y1010AU0FSensor", sensor.Sensor, cg.PollingComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
sensor.sensor_schema(
|
||||||
|
GP2Y1010AU0FSensor,
|
||||||
|
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_PM25,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
icon=ICON_CHEMICAL_WEAPON,
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler),
|
||||||
|
cv.Optional(CONF_ADC_VOLTAGE_OFFSET, default=0.0): cv.float_,
|
||||||
|
cv.Optional(CONF_ADC_VOLTAGE_MULTIPLIER, default=1.0): cv.float_,
|
||||||
|
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = await sensor.new_sensor(config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
# the ADC sensor to read voltage from
|
||||||
|
adc_sensor = await cg.get_variable(config[CONF_SENSOR])
|
||||||
|
cg.add(var.set_adc_source(adc_sensor))
|
||||||
|
cg.add(
|
||||||
|
var.set_voltage_refs(
|
||||||
|
config[CONF_ADC_VOLTAGE_OFFSET], config[CONF_ADC_VOLTAGE_MULTIPLIER]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# the binary output to control the module's internal IR LED
|
||||||
|
led_output = await cg.get_variable(config[CONF_OUTPUT])
|
||||||
|
cg.add(var.set_led_output(led_output))
|
0
esphome/components/gpio_expander/__init__.py
Normal file
0
esphome/components/gpio_expander/__init__.py
Normal file
38
esphome/components/gpio_expander/cached_gpio.h
Normal file
38
esphome/components/gpio_expander/cached_gpio.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace gpio_expander {
|
||||||
|
|
||||||
|
/// @brief A class to cache the read state of a GPIO expander.
|
||||||
|
template<typename T, T N> class CachedGpioExpander {
|
||||||
|
public:
|
||||||
|
bool digital_read(T pin) {
|
||||||
|
if (!this->read_cache_invalidated_[pin]) {
|
||||||
|
this->read_cache_invalidated_[pin] = true;
|
||||||
|
return this->digital_read_cache(pin);
|
||||||
|
}
|
||||||
|
return this->digital_read_hw(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
void digital_write(T pin, bool value) { this->digital_write_hw(pin, value); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool digital_read_hw(T pin) = 0;
|
||||||
|
virtual bool digital_read_cache(T pin) = 0;
|
||||||
|
virtual void digital_write_hw(T pin, bool value) = 0;
|
||||||
|
|
||||||
|
void reset_pin_cache_() {
|
||||||
|
for (T i = 0; i < N; i++) {
|
||||||
|
this->read_cache_invalidated_[i] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<bool, N> read_cache_invalidated_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gpio_expander
|
||||||
|
} // namespace esphome
|
0
esphome/components/grove_gas_mc_v2/__init__.py
Normal file
0
esphome/components/grove_gas_mc_v2/__init__.py
Normal file
88
esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp
Normal file
88
esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#include "grove_gas_mc_v2.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace grove_gas_mc_v2 {
|
||||||
|
|
||||||
|
static const char *const TAG = "grove_gas_mc_v2";
|
||||||
|
|
||||||
|
// I2C Commands for Grove Gas Multichannel V2 Sensor
|
||||||
|
// Taken from:
|
||||||
|
// https://github.com/Seeed-Studio/Seeed_Arduino_MultiGas/blob/master/src/Multichannel_Gas_GroveGasMultichannelV2.h
|
||||||
|
static const uint8_t GROVE_GAS_MC_V2_HEAT_ON = 0xFE;
|
||||||
|
static const uint8_t GROVE_GAS_MC_V2_HEAT_OFF = 0xFF;
|
||||||
|
static const uint8_t GROVE_GAS_MC_V2_READ_GM102B = 0x01;
|
||||||
|
static const uint8_t GROVE_GAS_MC_V2_READ_GM302B = 0x03;
|
||||||
|
static const uint8_t GROVE_GAS_MC_V2_READ_GM502B = 0x05;
|
||||||
|
static const uint8_t GROVE_GAS_MC_V2_READ_GM702B = 0x07;
|
||||||
|
|
||||||
|
bool GroveGasMultichannelV2Component::read_sensor_(uint8_t address, sensor::Sensor *sensor) {
|
||||||
|
if (sensor == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
uint32_t value = 0;
|
||||||
|
if (!this->read_bytes(address, (uint8_t *) &value, 4)) {
|
||||||
|
ESP_LOGW(TAG, "Reading Grove Gas Sensor data failed!");
|
||||||
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
|
this->status_set_warning();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sensor->publish_state(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroveGasMultichannelV2Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up Grove Multichannel Gas Sensor V2...");
|
||||||
|
|
||||||
|
// Before reading sensor values, must preheat sensor
|
||||||
|
if (!(this->write_bytes(GROVE_GAS_MC_V2_HEAT_ON, {}))) {
|
||||||
|
this->mark_failed();
|
||||||
|
this->error_code_ = APP_START_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroveGasMultichannelV2Component::update() {
|
||||||
|
// Read from each of the gas sensors
|
||||||
|
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM102B, this->nitrogen_dioxide_sensor_))
|
||||||
|
return;
|
||||||
|
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM302B, this->ethanol_sensor_))
|
||||||
|
return;
|
||||||
|
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM502B, this->tvoc_sensor_))
|
||||||
|
return;
|
||||||
|
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM702B, this->carbon_monoxide_sensor_))
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroveGasMultichannelV2Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Grove Multichannel Gas Sensor V2");
|
||||||
|
LOG_I2C_DEVICE(this)
|
||||||
|
LOG_UPDATE_INTERVAL(this)
|
||||||
|
LOG_SENSOR(" ", "Nitrogen Dioxide", this->nitrogen_dioxide_sensor_)
|
||||||
|
LOG_SENSOR(" ", "Ethanol", this->ethanol_sensor_)
|
||||||
|
LOG_SENSOR(" ", "Carbon Monoxide", this->carbon_monoxide_sensor_)
|
||||||
|
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_)
|
||||||
|
|
||||||
|
if (this->is_failed()) {
|
||||||
|
switch (this->error_code_) {
|
||||||
|
case COMMUNICATION_FAILED:
|
||||||
|
ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
|
||||||
|
break;
|
||||||
|
case APP_INVALID:
|
||||||
|
ESP_LOGW(TAG, "Sensor reported invalid APP installed.");
|
||||||
|
break;
|
||||||
|
case APP_START_FAILED:
|
||||||
|
ESP_LOGW(TAG, "Sensor reported APP start failed.");
|
||||||
|
break;
|
||||||
|
case UNKNOWN:
|
||||||
|
default:
|
||||||
|
ESP_LOGW(TAG, "Unknown setup error!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace grove_gas_mc_v2
|
||||||
|
} // namespace esphome
|
39
esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.h
Normal file
39
esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace grove_gas_mc_v2 {
|
||||||
|
|
||||||
|
class GroveGasMultichannelV2Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
|
SUB_SENSOR(tvoc)
|
||||||
|
SUB_SENSOR(carbon_monoxide)
|
||||||
|
SUB_SENSOR(nitrogen_dioxide)
|
||||||
|
SUB_SENSOR(ethanol)
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Setup the sensor and test for a connection.
|
||||||
|
void setup() override;
|
||||||
|
/// Schedule temperature+pressure readings.
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
enum ErrorCode {
|
||||||
|
UNKNOWN,
|
||||||
|
COMMUNICATION_FAILED,
|
||||||
|
APP_INVALID,
|
||||||
|
APP_START_FAILED,
|
||||||
|
} error_code_{UNKNOWN};
|
||||||
|
|
||||||
|
bool read_sensor_(uint8_t address, sensor::Sensor *sensor);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace grove_gas_mc_v2
|
||||||
|
} // namespace esphome
|
77
esphome/components/grove_gas_mc_v2/sensor.py
Normal file
77
esphome/components/grove_gas_mc_v2/sensor.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_CARBON_MONOXIDE,
|
||||||
|
CONF_ETHANOL,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_NITROGEN_DIOXIDE,
|
||||||
|
CONF_TVOC,
|
||||||
|
DEVICE_CLASS_CARBON_MONOXIDE,
|
||||||
|
DEVICE_CLASS_NITROGEN_DIOXIDE,
|
||||||
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||||
|
ICON_AIR_FILTER,
|
||||||
|
ICON_FLASK_ROUND_BOTTOM,
|
||||||
|
ICON_GAS_CYLINDER,
|
||||||
|
ICON_MOLECULE_CO,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
UNIT_PARTS_PER_MILLION,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@YorkshireIoT"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
grove_gas_mc_v2_ns = cg.esphome_ns.namespace("grove_gas_mc_v2")
|
||||||
|
|
||||||
|
GroveGasMultichannelV2Component = grove_gas_mc_v2_ns.class_(
|
||||||
|
"GroveGasMultichannelV2Component", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(GroveGasMultichannelV2Component),
|
||||||
|
cv.Optional(CONF_TVOC): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
icon=ICON_AIR_FILTER,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CARBON_MONOXIDE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||||
|
icon=ICON_MOLECULE_CO,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_CARBON_MONOXIDE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_NITROGEN_DIOXIDE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
icon=ICON_GAS_CYLINDER,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_NITROGEN_DIOXIDE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ETHANOL): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||||
|
icon=ICON_FLASK_ROUND_BOTTOM,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x08))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
for key in [CONF_TVOC, CONF_CARBON_MONOXIDE, CONF_NITROGEN_DIOXIDE, CONF_ETHANOL]:
|
||||||
|
if sensor_config := config.get(key):
|
||||||
|
sensor_ = await sensor.new_sensor(sensor_config)
|
||||||
|
cg.add(getattr(var, f"set_{key}_sensor")(sensor_))
|
@ -114,7 +114,6 @@ SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS = {
|
|||||||
SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = {
|
SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = {
|
||||||
"AWAY": ClimatePreset.CLIMATE_PRESET_AWAY,
|
"AWAY": ClimatePreset.CLIMATE_PRESET_AWAY,
|
||||||
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
|
"BOOST": ClimatePreset.CLIMATE_PRESET_BOOST,
|
||||||
"ECO": ClimatePreset.CLIMATE_PRESET_ECO,
|
|
||||||
"SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP,
|
"SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,7 +239,9 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
): cv.ensure_list(
|
): cv.ensure_list(
|
||||||
cv.enum(SUPPORTED_HON_CONTROL_METHODS, upper=True)
|
cv.enum(SUPPORTED_HON_CONTROL_METHODS, upper=True)
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_BEEPER, default=True): cv.boolean,
|
cv.Optional(CONF_BEEPER): cv.invalid(
|
||||||
|
f"The {CONF_BEEPER} option is deprecated, use beeper_on/beeper_off actions or beeper switch for a haier platform instead"
|
||||||
|
),
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE
|
CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE
|
||||||
): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50),
|
): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50),
|
||||||
@ -254,7 +255,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
): cv.int_range(min=PROTOCOL_STATUS_MESSAGE_HEADER_SIZE),
|
): cv.int_range(min=PROTOCOL_STATUS_MESSAGE_HEADER_SIZE),
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_SUPPORTED_PRESETS,
|
CONF_SUPPORTED_PRESETS,
|
||||||
default=["BOOST", "ECO", "SLEEP"], # No AWAY by default
|
default=["BOOST", "SLEEP"], # No AWAY by default
|
||||||
): cv.ensure_list(
|
): cv.ensure_list(
|
||||||
cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True)
|
cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True)
|
||||||
),
|
),
|
||||||
|
@ -52,8 +52,6 @@ bool check_timeout(std::chrono::steady_clock::time_point now, std::chrono::stead
|
|||||||
HaierClimateBase::HaierClimateBase()
|
HaierClimateBase::HaierClimateBase()
|
||||||
: haier_protocol_(*this),
|
: haier_protocol_(*this),
|
||||||
protocol_phase_(ProtocolPhases::SENDING_INIT_1),
|
protocol_phase_(ProtocolPhases::SENDING_INIT_1),
|
||||||
display_status_(true),
|
|
||||||
health_mode_(false),
|
|
||||||
force_send_control_(false),
|
force_send_control_(false),
|
||||||
forced_request_status_(false),
|
forced_request_status_(false),
|
||||||
reset_protocol_request_(false),
|
reset_protocol_request_(false),
|
||||||
@ -127,21 +125,34 @@ haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool HaierClimateBase::get_display_state() const { return this->display_status_; }
|
void HaierClimateBase::save_settings() {
|
||||||
|
HaierBaseSettings settings{this->get_health_mode(), this->get_display_state()};
|
||||||
void HaierClimateBase::set_display_state(bool state) {
|
if (!this->base_rtc_.save(&settings)) {
|
||||||
if (this->display_status_ != state) {
|
ESP_LOGW(TAG, "Failed to save settings");
|
||||||
this->display_status_ = state;
|
|
||||||
this->force_send_control_ = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HaierClimateBase::get_health_mode() const { return this->health_mode_; }
|
bool HaierClimateBase::get_display_state() const {
|
||||||
|
return (this->display_status_ == SwitchState::ON) || (this->display_status_ == SwitchState::PENDING_ON);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HaierClimateBase::set_display_state(bool state) {
|
||||||
|
if (state != this->get_display_state()) {
|
||||||
|
this->display_status_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
|
||||||
|
this->force_send_control_ = true;
|
||||||
|
this->save_settings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HaierClimateBase::get_health_mode() const {
|
||||||
|
return (this->health_mode_ == SwitchState::ON) || (this->health_mode_ == SwitchState::PENDING_ON);
|
||||||
|
}
|
||||||
|
|
||||||
void HaierClimateBase::set_health_mode(bool state) {
|
void HaierClimateBase::set_health_mode(bool state) {
|
||||||
if (this->health_mode_ != state) {
|
if (state != this->get_health_mode()) {
|
||||||
this->health_mode_ = state;
|
this->health_mode_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
|
||||||
this->force_send_control_ = true;
|
this->force_send_control_ = true;
|
||||||
|
this->save_settings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,6 +298,14 @@ void HaierClimateBase::loop() {
|
|||||||
}
|
}
|
||||||
this->process_phase(now);
|
this->process_phase(now);
|
||||||
this->haier_protocol_.loop();
|
this->haier_protocol_.loop();
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
if ((this->display_switch_ != nullptr) && (this->display_switch_->state != this->get_display_state())) {
|
||||||
|
this->display_switch_->publish_state(this->get_display_state());
|
||||||
|
}
|
||||||
|
if ((this->health_mode_switch_ != nullptr) && (this->health_mode_switch_->state != this->get_health_mode())) {
|
||||||
|
this->health_mode_switch_->publish_state(this->get_health_mode());
|
||||||
|
}
|
||||||
|
#endif // USE_SWITCH
|
||||||
}
|
}
|
||||||
|
|
||||||
void HaierClimateBase::process_protocol_reset() {
|
void HaierClimateBase::process_protocol_reset() {
|
||||||
@ -329,6 +348,26 @@ bool HaierClimateBase::prepare_pending_action() {
|
|||||||
|
|
||||||
ClimateTraits HaierClimateBase::traits() { return traits_; }
|
ClimateTraits HaierClimateBase::traits() { return traits_; }
|
||||||
|
|
||||||
|
void HaierClimateBase::initialization() {
|
||||||
|
constexpr uint32_t restore_settings_version = 0xA77D21EF;
|
||||||
|
this->base_rtc_ =
|
||||||
|
global_preferences->make_preference<HaierBaseSettings>(this->get_object_id_hash() ^ restore_settings_version);
|
||||||
|
HaierBaseSettings recovered;
|
||||||
|
if (!this->base_rtc_.load(&recovered)) {
|
||||||
|
recovered = {false, true};
|
||||||
|
}
|
||||||
|
this->display_status_ = recovered.display_state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
|
||||||
|
this->health_mode_ = recovered.health_mode ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
if (this->display_switch_ != nullptr) {
|
||||||
|
this->display_switch_->publish_state(this->get_display_state());
|
||||||
|
}
|
||||||
|
if (this->health_mode_switch_ != nullptr) {
|
||||||
|
this->health_mode_switch_->publish_state(this->get_health_mode());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void HaierClimateBase::control(const ClimateCall &call) {
|
void HaierClimateBase::control(const ClimateCall &call) {
|
||||||
ESP_LOGD("Control", "Control call");
|
ESP_LOGD("Control", "Control call");
|
||||||
if (!this->valid_connection()) {
|
if (!this->valid_connection()) {
|
||||||
@ -353,6 +392,22 @@ void HaierClimateBase::control(const ClimateCall &call) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
void HaierClimateBase::set_display_switch(switch_::Switch *sw) {
|
||||||
|
this->display_switch_ = sw;
|
||||||
|
if ((this->display_switch_ != nullptr) && (this->valid_connection())) {
|
||||||
|
this->display_switch_->publish_state(this->get_display_state());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HaierClimateBase::set_health_mode_switch(switch_::Switch *sw) {
|
||||||
|
this->health_mode_switch_ = sw;
|
||||||
|
if ((this->health_mode_switch_ != nullptr) && (this->valid_connection())) {
|
||||||
|
this->health_mode_switch_->publish_state(this->get_health_mode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void HaierClimateBase::HvacSettings::reset() {
|
void HaierClimateBase::HvacSettings::reset() {
|
||||||
this->valid = false;
|
this->valid = false;
|
||||||
this->mode.reset();
|
this->mode.reset();
|
||||||
|
@ -8,6 +8,10 @@
|
|||||||
// HaierProtocol
|
// HaierProtocol
|
||||||
#include <protocol/haier_protocol.h>
|
#include <protocol/haier_protocol.h>
|
||||||
|
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
#include "esphome/components/switch/switch.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace haier {
|
namespace haier {
|
||||||
|
|
||||||
@ -20,10 +24,24 @@ enum class ActionRequest : uint8_t {
|
|||||||
START_STERI_CLEAN = 5, // only hOn
|
START_STERI_CLEAN = 5, // only hOn
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct HaierBaseSettings {
|
||||||
|
bool health_mode;
|
||||||
|
bool display_state;
|
||||||
|
};
|
||||||
|
|
||||||
class HaierClimateBase : public esphome::Component,
|
class HaierClimateBase : public esphome::Component,
|
||||||
public esphome::climate::Climate,
|
public esphome::climate::Climate,
|
||||||
public esphome::uart::UARTDevice,
|
public esphome::uart::UARTDevice,
|
||||||
public haier_protocol::ProtocolStream {
|
public haier_protocol::ProtocolStream {
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
public:
|
||||||
|
void set_display_switch(switch_::Switch *sw);
|
||||||
|
void set_health_mode_switch(switch_::Switch *sw);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
switch_::Switch *display_switch_{nullptr};
|
||||||
|
switch_::Switch *health_mode_switch_{nullptr};
|
||||||
|
#endif
|
||||||
public:
|
public:
|
||||||
HaierClimateBase();
|
HaierClimateBase();
|
||||||
HaierClimateBase(const HaierClimateBase &) = delete;
|
HaierClimateBase(const HaierClimateBase &) = delete;
|
||||||
@ -82,7 +100,8 @@ class HaierClimateBase : public esphome::Component,
|
|||||||
virtual void process_phase(std::chrono::steady_clock::time_point now) = 0;
|
virtual void process_phase(std::chrono::steady_clock::time_point now) = 0;
|
||||||
virtual haier_protocol::HaierMessage get_control_message() = 0; // NOLINT(readability-identifier-naming)
|
virtual haier_protocol::HaierMessage get_control_message() = 0; // NOLINT(readability-identifier-naming)
|
||||||
virtual haier_protocol::HaierMessage get_power_message(bool state) = 0; // NOLINT(readability-identifier-naming)
|
virtual haier_protocol::HaierMessage get_power_message(bool state) = 0; // NOLINT(readability-identifier-naming)
|
||||||
virtual void initialization(){};
|
virtual void save_settings();
|
||||||
|
virtual void initialization();
|
||||||
virtual bool prepare_pending_action();
|
virtual bool prepare_pending_action();
|
||||||
virtual void process_protocol_reset();
|
virtual void process_protocol_reset();
|
||||||
esphome::climate::ClimateTraits traits() override;
|
esphome::climate::ClimateTraits traits() override;
|
||||||
@ -127,13 +146,19 @@ class HaierClimateBase : public esphome::Component,
|
|||||||
ActionRequest action;
|
ActionRequest action;
|
||||||
esphome::optional<haier_protocol::HaierMessage> message;
|
esphome::optional<haier_protocol::HaierMessage> message;
|
||||||
};
|
};
|
||||||
|
enum class SwitchState {
|
||||||
|
OFF = 0b00,
|
||||||
|
ON = 0b01,
|
||||||
|
PENDING_OFF = 0b10,
|
||||||
|
PENDING_ON = 0b11,
|
||||||
|
};
|
||||||
haier_protocol::ProtocolHandler haier_protocol_;
|
haier_protocol::ProtocolHandler haier_protocol_;
|
||||||
ProtocolPhases protocol_phase_;
|
ProtocolPhases protocol_phase_;
|
||||||
esphome::optional<PendingAction> action_request_;
|
esphome::optional<PendingAction> action_request_;
|
||||||
uint8_t fan_mode_speed_;
|
uint8_t fan_mode_speed_;
|
||||||
uint8_t other_modes_fan_speed_;
|
uint8_t other_modes_fan_speed_;
|
||||||
bool display_status_;
|
SwitchState display_status_{SwitchState::ON};
|
||||||
bool health_mode_;
|
SwitchState health_mode_{SwitchState::OFF};
|
||||||
bool force_send_control_;
|
bool force_send_control_;
|
||||||
bool forced_request_status_;
|
bool forced_request_status_;
|
||||||
bool reset_protocol_request_;
|
bool reset_protocol_request_;
|
||||||
@ -148,6 +173,7 @@ class HaierClimateBase : public esphome::Component,
|
|||||||
std::chrono::steady_clock::time_point last_status_request_; // To request AC status
|
std::chrono::steady_clock::time_point last_status_request_; // To request AC status
|
||||||
std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level
|
std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level
|
||||||
CallbackManager<void(const char *, size_t)> status_message_callback_{};
|
CallbackManager<void(const char *, size_t)> status_message_callback_{};
|
||||||
|
ESPPreferenceObject base_rtc_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class StatusMessageTrigger : public Trigger<const char *, size_t> {
|
class StatusMessageTrigger : public Trigger<const char *, size_t> {
|
||||||
|
@ -31,9 +31,32 @@ HonClimate::HonClimate()
|
|||||||
|
|
||||||
HonClimate::~HonClimate() {}
|
HonClimate::~HonClimate() {}
|
||||||
|
|
||||||
void HonClimate::set_beeper_state(bool state) { this->beeper_status_ = state; }
|
void HonClimate::set_beeper_state(bool state) {
|
||||||
|
if (state != this->settings_.beeper_state) {
|
||||||
|
this->settings_.beeper_state = state;
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
this->beeper_switch_->publish_state(state);
|
||||||
|
#endif
|
||||||
|
this->hon_rtc_.save(&this->settings_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool HonClimate::get_beeper_state() const { return this->beeper_status_; }
|
bool HonClimate::get_beeper_state() const { return this->settings_.beeper_state; }
|
||||||
|
|
||||||
|
void HonClimate::set_quiet_mode_state(bool state) {
|
||||||
|
if (state != this->get_quiet_mode_state()) {
|
||||||
|
this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
|
||||||
|
this->settings_.quiet_mode_state = state;
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
this->quiet_mode_switch_->publish_state(state);
|
||||||
|
#endif
|
||||||
|
this->hon_rtc_.save(&this->settings_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HonClimate::get_quiet_mode_state() const {
|
||||||
|
return (this->quiet_mode_state_ == SwitchState::ON) || (this->quiet_mode_state_ == SwitchState::PENDING_ON);
|
||||||
|
}
|
||||||
|
|
||||||
esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const {
|
esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const {
|
||||||
return this->current_vertical_swing_;
|
return this->current_vertical_swing_;
|
||||||
@ -474,16 +497,19 @@ haier_protocol::HaierMessage HonClimate::get_power_message(bool state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HonClimate::initialization() {
|
void HonClimate::initialization() {
|
||||||
constexpr uint32_t restore_settings_version = 0xE834D8DCUL;
|
HaierClimateBase::initialization();
|
||||||
this->rtc_ = global_preferences->make_preference<HonSettings>(this->get_object_id_hash() ^ restore_settings_version);
|
constexpr uint32_t restore_settings_version = 0x57EB59DDUL;
|
||||||
|
this->hon_rtc_ =
|
||||||
|
global_preferences->make_preference<HonSettings>(this->get_object_id_hash() ^ restore_settings_version);
|
||||||
HonSettings recovered;
|
HonSettings recovered;
|
||||||
if (this->rtc_.load(&recovered)) {
|
if (this->hon_rtc_.load(&recovered)) {
|
||||||
this->settings_ = recovered;
|
this->settings_ = recovered;
|
||||||
} else {
|
} else {
|
||||||
this->settings_ = {hon_protocol::VerticalSwingMode::CENTER, hon_protocol::HorizontalSwingMode::CENTER};
|
this->settings_ = {hon_protocol::VerticalSwingMode::CENTER, hon_protocol::HorizontalSwingMode::CENTER, true, false};
|
||||||
}
|
}
|
||||||
this->current_vertical_swing_ = this->settings_.last_vertiacal_swing;
|
this->current_vertical_swing_ = this->settings_.last_vertiacal_swing;
|
||||||
this->current_horizontal_swing_ = this->settings_.last_horizontal_swing;
|
this->current_horizontal_swing_ = this->settings_.last_horizontal_swing;
|
||||||
|
this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
haier_protocol::HaierMessage HonClimate::get_control_message() {
|
haier_protocol::HaierMessage HonClimate::get_control_message() {
|
||||||
@ -519,8 +545,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
|
|||||||
out_data->ac_power = 1;
|
out_data->ac_power = 1;
|
||||||
out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::FAN;
|
out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::FAN;
|
||||||
out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode
|
out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode
|
||||||
// Disabling boost and eco mode for Fan only
|
// Disabling boost for Fan only
|
||||||
out_data->quiet_mode = 0;
|
|
||||||
out_data->fast_mode = 0;
|
out_data->fast_mode = 0;
|
||||||
break;
|
break;
|
||||||
case CLIMATE_MODE_COOL:
|
case CLIMATE_MODE_COOL:
|
||||||
@ -582,47 +607,34 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
|
|||||||
}
|
}
|
||||||
if (out_data->ac_power == 0) {
|
if (out_data->ac_power == 0) {
|
||||||
// If AC is off - no presets allowed
|
// If AC is off - no presets allowed
|
||||||
out_data->quiet_mode = 0;
|
|
||||||
out_data->fast_mode = 0;
|
out_data->fast_mode = 0;
|
||||||
out_data->sleep_mode = 0;
|
out_data->sleep_mode = 0;
|
||||||
} else if (climate_control.preset.has_value()) {
|
} else if (climate_control.preset.has_value()) {
|
||||||
switch (climate_control.preset.value()) {
|
switch (climate_control.preset.value()) {
|
||||||
case CLIMATE_PRESET_NONE:
|
case CLIMATE_PRESET_NONE:
|
||||||
out_data->quiet_mode = 0;
|
|
||||||
out_data->fast_mode = 0;
|
|
||||||
out_data->sleep_mode = 0;
|
|
||||||
out_data->ten_degree = 0;
|
|
||||||
break;
|
|
||||||
case CLIMATE_PRESET_ECO:
|
|
||||||
// Eco is not supported in Fan only mode
|
|
||||||
out_data->quiet_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0;
|
|
||||||
out_data->fast_mode = 0;
|
out_data->fast_mode = 0;
|
||||||
out_data->sleep_mode = 0;
|
out_data->sleep_mode = 0;
|
||||||
out_data->ten_degree = 0;
|
out_data->ten_degree = 0;
|
||||||
break;
|
break;
|
||||||
case CLIMATE_PRESET_BOOST:
|
case CLIMATE_PRESET_BOOST:
|
||||||
out_data->quiet_mode = 0;
|
|
||||||
// Boost is not supported in Fan only mode
|
// Boost is not supported in Fan only mode
|
||||||
out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0;
|
out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0;
|
||||||
out_data->sleep_mode = 0;
|
out_data->sleep_mode = 0;
|
||||||
out_data->ten_degree = 0;
|
out_data->ten_degree = 0;
|
||||||
break;
|
break;
|
||||||
case CLIMATE_PRESET_AWAY:
|
case CLIMATE_PRESET_AWAY:
|
||||||
out_data->quiet_mode = 0;
|
|
||||||
out_data->fast_mode = 0;
|
out_data->fast_mode = 0;
|
||||||
out_data->sleep_mode = 0;
|
out_data->sleep_mode = 0;
|
||||||
// 10 degrees allowed only in heat mode
|
// 10 degrees allowed only in heat mode
|
||||||
out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0;
|
out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0;
|
||||||
break;
|
break;
|
||||||
case CLIMATE_PRESET_SLEEP:
|
case CLIMATE_PRESET_SLEEP:
|
||||||
out_data->quiet_mode = 0;
|
|
||||||
out_data->fast_mode = 0;
|
out_data->fast_mode = 0;
|
||||||
out_data->sleep_mode = 1;
|
out_data->sleep_mode = 1;
|
||||||
out_data->ten_degree = 0;
|
out_data->ten_degree = 0;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ESP_LOGE("Control", "Unsupported preset");
|
ESP_LOGE("Control", "Unsupported preset");
|
||||||
out_data->quiet_mode = 0;
|
|
||||||
out_data->fast_mode = 0;
|
out_data->fast_mode = 0;
|
||||||
out_data->sleep_mode = 0;
|
out_data->sleep_mode = 0;
|
||||||
out_data->ten_degree = 0;
|
out_data->ten_degree = 0;
|
||||||
@ -638,10 +650,23 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
|
|||||||
out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value();
|
out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value();
|
||||||
this->pending_horizontal_direction_.reset();
|
this->pending_horizontal_direction_.reset();
|
||||||
}
|
}
|
||||||
out_data->beeper_status = ((!this->beeper_status_) || (!has_hvac_settings)) ? 1 : 0;
|
{
|
||||||
|
// Quiet mode
|
||||||
|
if ((out_data->ac_power == 0) || (out_data->ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN)) {
|
||||||
|
// If AC is off or in fan only mode - no quiet mode allowed
|
||||||
|
out_data->quiet_mode = 0;
|
||||||
|
} else {
|
||||||
|
out_data->quiet_mode = this->get_quiet_mode_state() ? 1 : 0;
|
||||||
|
}
|
||||||
|
// Clean quiet mode state pending flag
|
||||||
|
this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
|
||||||
|
}
|
||||||
|
out_data->beeper_status = ((!this->get_beeper_state()) || (!has_hvac_settings)) ? 1 : 0;
|
||||||
control_out_buffer[4] = 0; // This byte should be cleared before setting values
|
control_out_buffer[4] = 0; // This byte should be cleared before setting values
|
||||||
out_data->display_status = this->display_status_ ? 1 : 0;
|
out_data->display_status = this->get_display_state() ? 1 : 0;
|
||||||
out_data->health_mode = this->health_mode_ ? 1 : 0;
|
this->display_status_ = (SwitchState) ((uint8_t) this->display_status_ & 0b01);
|
||||||
|
out_data->health_mode = this->get_health_mode() ? 1 : 0;
|
||||||
|
this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
|
||||||
return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||||
(uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
|
(uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
|
||||||
control_out_buffer, this->real_control_packet_size_);
|
control_out_buffer, this->real_control_packet_size_);
|
||||||
@ -765,6 +790,22 @@ void HonClimate::update_sub_text_sensor_(SubTextSensorType type, const std::stri
|
|||||||
}
|
}
|
||||||
#endif // USE_TEXT_SENSOR
|
#endif // USE_TEXT_SENSOR
|
||||||
|
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
void HonClimate::set_beeper_switch(switch_::Switch *sw) {
|
||||||
|
this->beeper_switch_ = sw;
|
||||||
|
if (this->beeper_switch_ != nullptr) {
|
||||||
|
this->beeper_switch_->publish_state(this->get_beeper_state());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HonClimate::set_quiet_mode_switch(switch_::Switch *sw) {
|
||||||
|
this->quiet_mode_switch_ = sw;
|
||||||
|
if (this->quiet_mode_switch_ != nullptr) {
|
||||||
|
this->quiet_mode_switch_->publish_state(this->settings_.quiet_mode_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // USE_SWITCH
|
||||||
|
|
||||||
haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
|
haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
|
||||||
size_t expected_size =
|
size_t expected_size =
|
||||||
2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_;
|
2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_;
|
||||||
@ -827,9 +868,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
|||||||
{
|
{
|
||||||
// Extra modes/presets
|
// Extra modes/presets
|
||||||
optional<ClimatePreset> old_preset = this->preset;
|
optional<ClimatePreset> old_preset = this->preset;
|
||||||
if (packet.control.quiet_mode != 0) {
|
if (packet.control.fast_mode != 0) {
|
||||||
this->preset = CLIMATE_PRESET_ECO;
|
|
||||||
} else if (packet.control.fast_mode != 0) {
|
|
||||||
this->preset = CLIMATE_PRESET_BOOST;
|
this->preset = CLIMATE_PRESET_BOOST;
|
||||||
} else if (packet.control.sleep_mode != 0) {
|
} else if (packet.control.sleep_mode != 0) {
|
||||||
this->preset = CLIMATE_PRESET_SLEEP;
|
this->preset = CLIMATE_PRESET_SLEEP;
|
||||||
@ -883,28 +922,26 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
|||||||
}
|
}
|
||||||
should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value());
|
should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value());
|
||||||
}
|
}
|
||||||
{
|
// Display status
|
||||||
// Display status
|
// should be before "Climate mode" because it is changing this->mode
|
||||||
// should be before "Climate mode" because it is changing this->mode
|
if (packet.control.ac_power != 0) {
|
||||||
if (packet.control.ac_power != 0) {
|
// if AC is off display status always ON so process it only when AC is on
|
||||||
// if AC is off display status always ON so process it only when AC is on
|
bool disp_status = packet.control.display_status != 0;
|
||||||
bool disp_status = packet.control.display_status != 0;
|
if (disp_status != this->get_display_state()) {
|
||||||
if (disp_status != this->display_status_) {
|
// Do something only if display status changed
|
||||||
// Do something only if display status changed
|
if (this->mode == CLIMATE_MODE_OFF) {
|
||||||
if (this->mode == CLIMATE_MODE_OFF) {
|
// AC just turned on from remote need to turn off display
|
||||||
// AC just turned on from remote need to turn off display
|
this->force_send_control_ = true;
|
||||||
this->force_send_control_ = true;
|
} else if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
|
||||||
} else {
|
this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
|
||||||
this->display_status_ = disp_status;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
// Health mode
|
||||||
// Health mode
|
if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
|
||||||
bool old_health_mode = this->health_mode_;
|
bool old_health_mode = this->get_health_mode();
|
||||||
this->health_mode_ = packet.control.health_mode == 1;
|
this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
|
||||||
should_publish = should_publish || (old_health_mode != this->health_mode_);
|
should_publish = should_publish || (old_health_mode != this->get_health_mode());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
CleaningState new_cleaning;
|
CleaningState new_cleaning;
|
||||||
@ -958,17 +995,36 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
|||||||
}
|
}
|
||||||
should_publish = should_publish || (old_mode != this->mode);
|
should_publish = should_publish || (old_mode != this->mode);
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
// Quiet mode, should be after climate mode
|
||||||
|
if ((this->mode != CLIMATE_MODE_FAN_ONLY) && (this->mode != CLIMATE_MODE_OFF) &&
|
||||||
|
((((uint8_t) this->quiet_mode_state_) & 0b10) == 0)) {
|
||||||
|
// In proper mode and not in pending state
|
||||||
|
bool new_quiet_mode = packet.control.quiet_mode != 0;
|
||||||
|
if (new_quiet_mode != this->get_quiet_mode_state()) {
|
||||||
|
this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF;
|
||||||
|
this->settings_.quiet_mode_state = new_quiet_mode;
|
||||||
|
this->hon_rtc_.save(&this->settings_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
{
|
{
|
||||||
// Swing mode
|
// Swing mode
|
||||||
ClimateSwingMode old_swing_mode = this->swing_mode;
|
ClimateSwingMode old_swing_mode = this->swing_mode;
|
||||||
if (packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO) {
|
const std::set<ClimateSwingMode> &swing_modes = traits_.get_supported_swing_modes();
|
||||||
if (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO) {
|
bool vertical_swing_supported = swing_modes.find(CLIMATE_SWING_VERTICAL) != swing_modes.end();
|
||||||
|
bool horizontal_swing_supported = swing_modes.find(CLIMATE_SWING_HORIZONTAL) != swing_modes.end();
|
||||||
|
if (horizontal_swing_supported &&
|
||||||
|
(packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)) {
|
||||||
|
if (vertical_swing_supported &&
|
||||||
|
(packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) {
|
||||||
this->swing_mode = CLIMATE_SWING_BOTH;
|
this->swing_mode = CLIMATE_SWING_BOTH;
|
||||||
} else {
|
} else {
|
||||||
this->swing_mode = CLIMATE_SWING_HORIZONTAL;
|
this->swing_mode = CLIMATE_SWING_HORIZONTAL;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO) {
|
if (vertical_swing_supported &&
|
||||||
|
(packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) {
|
||||||
this->swing_mode = CLIMATE_SWING_VERTICAL;
|
this->swing_mode = CLIMATE_SWING_VERTICAL;
|
||||||
} else {
|
} else {
|
||||||
this->swing_mode = CLIMATE_SWING_OFF;
|
this->swing_mode = CLIMATE_SWING_OFF;
|
||||||
@ -985,7 +1041,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
|||||||
if (save_settings) {
|
if (save_settings) {
|
||||||
this->settings_.last_vertiacal_swing = this->current_vertical_swing_.value();
|
this->settings_.last_vertiacal_swing = this->current_vertical_swing_.value();
|
||||||
this->settings_.last_horizontal_swing = this->current_horizontal_swing_.value();
|
this->settings_.last_horizontal_swing = this->current_horizontal_swing_.value();
|
||||||
this->rtc_.save(&this->settings_);
|
this->hon_rtc_.save(&this->settings_);
|
||||||
}
|
}
|
||||||
should_publish = should_publish || (old_swing_mode != this->swing_mode);
|
should_publish = should_publish || (old_swing_mode != this->swing_mode);
|
||||||
}
|
}
|
||||||
@ -1017,7 +1073,7 @@ void HonClimate::fill_control_messages_queue_() {
|
|||||||
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||||
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
||||||
(uint8_t) hon_protocol::DataParameters::BEEPER_STATUS,
|
(uint8_t) hon_protocol::DataParameters::BEEPER_STATUS,
|
||||||
this->beeper_status_ ? ZERO_BUF : ONE_BUF, 2));
|
this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2));
|
||||||
}
|
}
|
||||||
// Health mode
|
// Health mode
|
||||||
{
|
{
|
||||||
@ -1025,13 +1081,16 @@ void HonClimate::fill_control_messages_queue_() {
|
|||||||
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||||
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
||||||
(uint8_t) hon_protocol::DataParameters::HEALTH_MODE,
|
(uint8_t) hon_protocol::DataParameters::HEALTH_MODE,
|
||||||
this->health_mode_ ? ONE_BUF : ZERO_BUF, 2));
|
this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2));
|
||||||
|
this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
|
||||||
}
|
}
|
||||||
// Climate mode
|
// Climate mode
|
||||||
|
ClimateMode climate_mode = this->mode;
|
||||||
bool new_power = this->mode != CLIMATE_MODE_OFF;
|
bool new_power = this->mode != CLIMATE_MODE_OFF;
|
||||||
uint8_t fan_mode_buf[] = {0x00, 0xFF};
|
uint8_t fan_mode_buf[] = {0x00, 0xFF};
|
||||||
uint8_t quiet_mode_buf[] = {0x00, 0xFF};
|
uint8_t quiet_mode_buf[] = {0x00, 0xFF};
|
||||||
if (climate_control.mode.has_value()) {
|
if (climate_control.mode.has_value()) {
|
||||||
|
climate_mode = climate_control.mode.value();
|
||||||
uint8_t buffer[2] = {0x00, 0x00};
|
uint8_t buffer[2] = {0x00, 0x00};
|
||||||
switch (climate_control.mode.value()) {
|
switch (climate_control.mode.value()) {
|
||||||
case CLIMATE_MODE_OFF:
|
case CLIMATE_MODE_OFF:
|
||||||
@ -1076,8 +1135,6 @@ void HonClimate::fill_control_messages_queue_() {
|
|||||||
(uint8_t) hon_protocol::DataParameters::AC_MODE,
|
(uint8_t) hon_protocol::DataParameters::AC_MODE,
|
||||||
buffer, 2));
|
buffer, 2));
|
||||||
fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode
|
fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode
|
||||||
// Disabling eco mode for Fan only
|
|
||||||
quiet_mode_buf[1] = 0;
|
|
||||||
break;
|
break;
|
||||||
case CLIMATE_MODE_COOL:
|
case CLIMATE_MODE_COOL:
|
||||||
new_power = true;
|
new_power = true;
|
||||||
@ -1108,30 +1165,20 @@ void HonClimate::fill_control_messages_queue_() {
|
|||||||
uint8_t away_mode_buf[] = {0x00, 0xFF};
|
uint8_t away_mode_buf[] = {0x00, 0xFF};
|
||||||
if (!new_power) {
|
if (!new_power) {
|
||||||
// If AC is off - no presets allowed
|
// If AC is off - no presets allowed
|
||||||
quiet_mode_buf[1] = 0x00;
|
|
||||||
fast_mode_buf[1] = 0x00;
|
fast_mode_buf[1] = 0x00;
|
||||||
away_mode_buf[1] = 0x00;
|
away_mode_buf[1] = 0x00;
|
||||||
} else if (climate_control.preset.has_value()) {
|
} else if (climate_control.preset.has_value()) {
|
||||||
switch (climate_control.preset.value()) {
|
switch (climate_control.preset.value()) {
|
||||||
case CLIMATE_PRESET_NONE:
|
case CLIMATE_PRESET_NONE:
|
||||||
quiet_mode_buf[1] = 0x00;
|
|
||||||
fast_mode_buf[1] = 0x00;
|
|
||||||
away_mode_buf[1] = 0x00;
|
|
||||||
break;
|
|
||||||
case CLIMATE_PRESET_ECO:
|
|
||||||
// Eco is not supported in Fan only mode
|
|
||||||
quiet_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
|
|
||||||
fast_mode_buf[1] = 0x00;
|
fast_mode_buf[1] = 0x00;
|
||||||
away_mode_buf[1] = 0x00;
|
away_mode_buf[1] = 0x00;
|
||||||
break;
|
break;
|
||||||
case CLIMATE_PRESET_BOOST:
|
case CLIMATE_PRESET_BOOST:
|
||||||
quiet_mode_buf[1] = 0x00;
|
|
||||||
// Boost is not supported in Fan only mode
|
// Boost is not supported in Fan only mode
|
||||||
fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
|
fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
|
||||||
away_mode_buf[1] = 0x00;
|
away_mode_buf[1] = 0x00;
|
||||||
break;
|
break;
|
||||||
case CLIMATE_PRESET_AWAY:
|
case CLIMATE_PRESET_AWAY:
|
||||||
quiet_mode_buf[1] = 0x00;
|
|
||||||
fast_mode_buf[1] = 0x00;
|
fast_mode_buf[1] = 0x00;
|
||||||
away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00;
|
away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00;
|
||||||
break;
|
break;
|
||||||
@ -1140,8 +1187,18 @@ void HonClimate::fill_control_messages_queue_() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
// Quiet mode
|
||||||
|
if (new_power && (climate_mode != CLIMATE_MODE_FAN_ONLY) && this->get_quiet_mode_state()) {
|
||||||
|
quiet_mode_buf[1] = 0x01;
|
||||||
|
} else {
|
||||||
|
quiet_mode_buf[1] = 0x00;
|
||||||
|
}
|
||||||
|
// Clean quiet mode state pending flag
|
||||||
|
this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
|
||||||
|
}
|
||||||
auto presets = this->traits_.get_supported_presets();
|
auto presets = this->traits_.get_supported_presets();
|
||||||
if ((quiet_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_ECO) != presets.end()))) {
|
if (quiet_mode_buf[1] != 0xFF) {
|
||||||
this->control_messages_queue_.push(
|
this->control_messages_queue_.push(
|
||||||
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||||
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
||||||
|
@ -10,6 +10,9 @@
|
|||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
#include "esphome/components/text_sensor/text_sensor.h"
|
#include "esphome/components/text_sensor/text_sensor.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
#include "esphome/components/switch/switch.h"
|
||||||
|
#endif
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "haier_base.h"
|
#include "haier_base.h"
|
||||||
#include "hon_packet.h"
|
#include "hon_packet.h"
|
||||||
@ -28,6 +31,8 @@ enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE
|
|||||||
struct HonSettings {
|
struct HonSettings {
|
||||||
hon_protocol::VerticalSwingMode last_vertiacal_swing;
|
hon_protocol::VerticalSwingMode last_vertiacal_swing;
|
||||||
hon_protocol::HorizontalSwingMode last_horizontal_swing;
|
hon_protocol::HorizontalSwingMode last_horizontal_swing;
|
||||||
|
bool beeper_state;
|
||||||
|
bool quiet_mode_state;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HonClimate : public HaierClimateBase {
|
class HonClimate : public HaierClimateBase {
|
||||||
@ -86,6 +91,15 @@ class HonClimate : public HaierClimateBase {
|
|||||||
protected:
|
protected:
|
||||||
void update_sub_text_sensor_(SubTextSensorType type, const std::string &value);
|
void update_sub_text_sensor_(SubTextSensorType type, const std::string &value);
|
||||||
text_sensor::TextSensor *sub_text_sensors_[(size_t) SubTextSensorType::SUB_TEXT_SENSOR_TYPE_COUNT]{nullptr};
|
text_sensor::TextSensor *sub_text_sensors_[(size_t) SubTextSensorType::SUB_TEXT_SENSOR_TYPE_COUNT]{nullptr};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SWITCH
|
||||||
|
public:
|
||||||
|
void set_beeper_switch(switch_::Switch *sw);
|
||||||
|
void set_quiet_mode_switch(switch_::Switch *sw);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
switch_::Switch *beeper_switch_{nullptr};
|
||||||
|
switch_::Switch *quiet_mode_switch_{nullptr};
|
||||||
#endif
|
#endif
|
||||||
public:
|
public:
|
||||||
HonClimate();
|
HonClimate();
|
||||||
@ -95,6 +109,8 @@ class HonClimate : public HaierClimateBase {
|
|||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void set_beeper_state(bool state);
|
void set_beeper_state(bool state);
|
||||||
bool get_beeper_state() const;
|
bool get_beeper_state() const;
|
||||||
|
void set_quiet_mode_state(bool state);
|
||||||
|
bool get_quiet_mode_state() const;
|
||||||
esphome::optional<hon_protocol::VerticalSwingMode> get_vertical_airflow() const;
|
esphome::optional<hon_protocol::VerticalSwingMode> get_vertical_airflow() const;
|
||||||
void set_vertical_airflow(hon_protocol::VerticalSwingMode direction);
|
void set_vertical_airflow(hon_protocol::VerticalSwingMode direction);
|
||||||
esphome::optional<hon_protocol::HorizontalSwingMode> get_horizontal_airflow() const;
|
esphome::optional<hon_protocol::HorizontalSwingMode> get_horizontal_airflow() const;
|
||||||
@ -153,7 +169,6 @@ class HonClimate : public HaierClimateBase {
|
|||||||
bool functions_[5];
|
bool functions_[5];
|
||||||
};
|
};
|
||||||
|
|
||||||
bool beeper_status_;
|
|
||||||
CleaningState cleaning_status_;
|
CleaningState cleaning_status_;
|
||||||
bool got_valid_outdoor_temp_;
|
bool got_valid_outdoor_temp_;
|
||||||
esphome::optional<hon_protocol::VerticalSwingMode> pending_vertical_direction_{};
|
esphome::optional<hon_protocol::VerticalSwingMode> pending_vertical_direction_{};
|
||||||
@ -175,7 +190,8 @@ class HonClimate : public HaierClimateBase {
|
|||||||
esphome::optional<hon_protocol::VerticalSwingMode> current_vertical_swing_{};
|
esphome::optional<hon_protocol::VerticalSwingMode> current_vertical_swing_{};
|
||||||
esphome::optional<hon_protocol::HorizontalSwingMode> current_horizontal_swing_{};
|
esphome::optional<hon_protocol::HorizontalSwingMode> current_horizontal_swing_{};
|
||||||
HonSettings settings_;
|
HonSettings settings_;
|
||||||
ESPPreferenceObject rtc_;
|
ESPPreferenceObject hon_rtc_;
|
||||||
|
SwitchState quiet_mode_state_{SwitchState::OFF};
|
||||||
};
|
};
|
||||||
|
|
||||||
class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> {
|
class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> {
|
||||||
|
@ -376,8 +376,10 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out_data->display_status = this->display_status_ ? 0 : 1;
|
out_data->display_status = this->get_display_state() ? 0 : 1;
|
||||||
out_data->health_mode = this->health_mode_ ? 1 : 0;
|
this->display_status_ = (SwitchState) ((uint8_t) this->display_status_ & 0b01);
|
||||||
|
out_data->health_mode = this->get_health_mode() ? 1 : 0;
|
||||||
|
this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
|
||||||
return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, 0x4D5F, control_out_buffer,
|
return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, 0x4D5F, control_out_buffer,
|
||||||
sizeof(smartair2_protocol::HaierPacketControl));
|
sizeof(smartair2_protocol::HaierPacketControl));
|
||||||
}
|
}
|
||||||
@ -446,28 +448,26 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin
|
|||||||
}
|
}
|
||||||
should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value());
|
should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value());
|
||||||
}
|
}
|
||||||
{
|
// Display status
|
||||||
// Display status
|
// should be before "Climate mode" because it is changing this->mode
|
||||||
// should be before "Climate mode" because it is changing this->mode
|
if (packet.control.ac_power != 0) {
|
||||||
if (packet.control.ac_power != 0) {
|
// if AC is off display status always ON so process it only when AC is on
|
||||||
// if AC is off display status always ON so process it only when AC is on
|
bool disp_status = packet.control.display_status == 0;
|
||||||
bool disp_status = packet.control.display_status == 0;
|
if (disp_status != this->get_display_state()) {
|
||||||
if (disp_status != this->display_status_) {
|
// Do something only if display status changed
|
||||||
// Do something only if display status changed
|
if (this->mode == CLIMATE_MODE_OFF) {
|
||||||
if (this->mode == CLIMATE_MODE_OFF) {
|
// AC just turned on from remote need to turn off display
|
||||||
// AC just turned on from remote need to turn off display
|
this->force_send_control_ = true;
|
||||||
this->force_send_control_ = true;
|
} else if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
|
||||||
} else {
|
this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
|
||||||
this->display_status_ = disp_status;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
// Health mode
|
||||||
// Health mode
|
if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
|
||||||
bool old_health_mode = this->health_mode_;
|
bool old_health_mode = this->get_health_mode();
|
||||||
this->health_mode_ = packet.control.health_mode == 1;
|
this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
|
||||||
should_publish = should_publish || (old_health_mode != this->health_mode_);
|
should_publish = should_publish || (old_health_mode != this->get_health_mode());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Climate mode
|
// Climate mode
|
||||||
|
91
esphome/components/haier/switch/__init__.py
Normal file
91
esphome/components/haier/switch/__init__.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
import esphome.final_validate as fv
|
||||||
|
from esphome.components import switch
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_BEEPER,
|
||||||
|
CONF_DISPLAY,
|
||||||
|
ENTITY_CATEGORY_CONFIG,
|
||||||
|
)
|
||||||
|
from ..climate import (
|
||||||
|
CONF_HAIER_ID,
|
||||||
|
CONF_PROTOCOL,
|
||||||
|
HaierClimateBase,
|
||||||
|
haier_ns,
|
||||||
|
PROTOCOL_HON,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@paveldn"]
|
||||||
|
BeeperSwitch = haier_ns.class_("BeeperSwitch", switch.Switch)
|
||||||
|
HealthModeSwitch = haier_ns.class_("HealthModeSwitch", switch.Switch)
|
||||||
|
DisplaySwitch = haier_ns.class_("DisplaySwitch", switch.Switch)
|
||||||
|
QuietModeSwitch = haier_ns.class_("QuietModeSwitch", switch.Switch)
|
||||||
|
|
||||||
|
# Haier switches
|
||||||
|
CONF_HEALTH_MODE = "health_mode"
|
||||||
|
CONF_QUIET_MODE = "quiet_mode"
|
||||||
|
|
||||||
|
# Additional icons
|
||||||
|
ICON_LEAF = "mdi:leaf"
|
||||||
|
ICON_LED_ON = "mdi:led-on"
|
||||||
|
ICON_VOLUME_HIGH = "mdi:volume-high"
|
||||||
|
ICON_VOLUME_OFF = "mdi:volume-off"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_HAIER_ID): cv.use_id(HaierClimateBase),
|
||||||
|
cv.Optional(CONF_DISPLAY): switch.switch_schema(
|
||||||
|
DisplaySwitch,
|
||||||
|
icon=ICON_LED_ON,
|
||||||
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
|
default_restore_mode="DISABLED",
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_HEALTH_MODE): switch.switch_schema(
|
||||||
|
HealthModeSwitch,
|
||||||
|
icon=ICON_LEAF,
|
||||||
|
default_restore_mode="DISABLED",
|
||||||
|
),
|
||||||
|
# Beeper switch is only supported for HonClimate
|
||||||
|
cv.Optional(CONF_BEEPER): switch.switch_schema(
|
||||||
|
BeeperSwitch,
|
||||||
|
icon=ICON_VOLUME_HIGH,
|
||||||
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
|
default_restore_mode="DISABLED",
|
||||||
|
),
|
||||||
|
# Quiet mode is only supported for HonClimate
|
||||||
|
cv.Optional(CONF_QUIET_MODE): switch.switch_schema(
|
||||||
|
QuietModeSwitch,
|
||||||
|
icon=ICON_VOLUME_OFF,
|
||||||
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
|
default_restore_mode="DISABLED",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _final_validate(config):
|
||||||
|
full_config = fv.full_config.get()
|
||||||
|
for switch_type in [CONF_BEEPER, CONF_QUIET_MODE]:
|
||||||
|
# Check switches that are only supported for HonClimate
|
||||||
|
if config.get(switch_type):
|
||||||
|
climate_path = full_config.get_path_for_id(config[CONF_HAIER_ID])[:-1]
|
||||||
|
climate_conf = full_config.get_config_for_path(climate_path)
|
||||||
|
protocol_type = climate_conf.get(CONF_PROTOCOL)
|
||||||
|
if protocol_type.casefold() != PROTOCOL_HON.casefold():
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"{switch_type} switch is only supported for hon climate"
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
parent = await cg.get_variable(config[CONF_HAIER_ID])
|
||||||
|
|
||||||
|
for switch_type in [CONF_DISPLAY, CONF_HEALTH_MODE, CONF_BEEPER, CONF_QUIET_MODE]:
|
||||||
|
if conf := config.get(switch_type):
|
||||||
|
sw_var = await switch.new_switch(conf)
|
||||||
|
await cg.register_parented(sw_var, parent)
|
||||||
|
cg.add(getattr(parent, f"set_{switch_type}_switch")(sw_var))
|
14
esphome/components/haier/switch/beeper.cpp
Normal file
14
esphome/components/haier/switch/beeper.cpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#include "beeper.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace haier {
|
||||||
|
|
||||||
|
void BeeperSwitch::write_state(bool state) {
|
||||||
|
if (this->parent_->get_beeper_state() != state) {
|
||||||
|
this->parent_->set_beeper_state(state);
|
||||||
|
}
|
||||||
|
this->publish_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace haier
|
||||||
|
} // namespace esphome
|
18
esphome/components/haier/switch/beeper.h
Normal file
18
esphome/components/haier/switch/beeper.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/switch/switch.h"
|
||||||
|
#include "../hon_climate.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace haier {
|
||||||
|
|
||||||
|
class BeeperSwitch : public switch_::Switch, public Parented<HonClimate> {
|
||||||
|
public:
|
||||||
|
BeeperSwitch() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void write_state(bool state) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace haier
|
||||||
|
} // namespace esphome
|
14
esphome/components/haier/switch/display.cpp
Normal file
14
esphome/components/haier/switch/display.cpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#include "display.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace haier {
|
||||||
|
|
||||||
|
void DisplaySwitch::write_state(bool state) {
|
||||||
|
if (this->parent_->get_display_state() != state) {
|
||||||
|
this->parent_->set_display_state(state);
|
||||||
|
}
|
||||||
|
this->publish_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace haier
|
||||||
|
} // namespace esphome
|
18
esphome/components/haier/switch/display.h
Normal file
18
esphome/components/haier/switch/display.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/switch/switch.h"
|
||||||
|
#include "../haier_base.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace haier {
|
||||||
|
|
||||||
|
class DisplaySwitch : public switch_::Switch, public Parented<HaierClimateBase> {
|
||||||
|
public:
|
||||||
|
DisplaySwitch() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void write_state(bool state) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace haier
|
||||||
|
} // namespace esphome
|
14
esphome/components/haier/switch/health_mode.cpp
Normal file
14
esphome/components/haier/switch/health_mode.cpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#include "health_mode.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace haier {
|
||||||
|
|
||||||
|
void HealthModeSwitch::write_state(bool state) {
|
||||||
|
if (this->parent_->get_health_mode() != state) {
|
||||||
|
this->parent_->set_health_mode(state);
|
||||||
|
}
|
||||||
|
this->publish_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace haier
|
||||||
|
} // namespace esphome
|
18
esphome/components/haier/switch/health_mode.h
Normal file
18
esphome/components/haier/switch/health_mode.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/switch/switch.h"
|
||||||
|
#include "../haier_base.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace haier {
|
||||||
|
|
||||||
|
class HealthModeSwitch : public switch_::Switch, public Parented<HaierClimateBase> {
|
||||||
|
public:
|
||||||
|
HealthModeSwitch() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void write_state(bool state) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace haier
|
||||||
|
} // namespace esphome
|
14
esphome/components/haier/switch/quiet_mode.cpp
Normal file
14
esphome/components/haier/switch/quiet_mode.cpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#include "quiet_mode.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace haier {
|
||||||
|
|
||||||
|
void QuietModeSwitch::write_state(bool state) {
|
||||||
|
if (this->parent_->get_quiet_mode_state() != state) {
|
||||||
|
this->parent_->set_quiet_mode_state(state);
|
||||||
|
}
|
||||||
|
this->publish_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace haier
|
||||||
|
} // namespace esphome
|
18
esphome/components/haier/switch/quiet_mode.h
Normal file
18
esphome/components/haier/switch/quiet_mode.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/switch/switch.h"
|
||||||
|
#include "../hon_climate.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace haier {
|
||||||
|
|
||||||
|
class QuietModeSwitch : public switch_::Switch, public Parented<HonClimate> {
|
||||||
|
public:
|
||||||
|
QuietModeSwitch() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void write_state(bool state) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace haier
|
||||||
|
} // namespace esphome
|
@ -1,6 +1,7 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "hmac_md5.h"
|
#include "hmac_md5.h"
|
||||||
|
#ifdef USE_MD5
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@ -54,3 +55,4 @@ bool HmacMD5::equals_hex(const char *expected) { return this->ohash_.equals_hex(
|
|||||||
|
|
||||||
} // namespace hmac_md5
|
} // namespace hmac_md5
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_MD5
|
||||||
#include "esphome/components/md5/md5.h"
|
#include "esphome/components/md5/md5.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@ -46,3 +46,4 @@ class HmacMD5 {
|
|||||||
|
|
||||||
} // namespace hmac_md5
|
} // namespace hmac_md5
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
#endif
|
||||||
|
@ -25,6 +25,7 @@ I2SAudioSpeaker = i2s_audio_ns.class_(
|
|||||||
|
|
||||||
|
|
||||||
CONF_DAC_TYPE = "dac_type"
|
CONF_DAC_TYPE = "dac_type"
|
||||||
|
CONF_I2S_COMM_FMT = "i2s_comm_fmt"
|
||||||
|
|
||||||
i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t")
|
i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t")
|
||||||
INTERNAL_DAC_OPTIONS = {
|
INTERNAL_DAC_OPTIONS = {
|
||||||
@ -33,6 +34,20 @@ INTERNAL_DAC_OPTIONS = {
|
|||||||
CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
|
CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i2s_comm_format_t = cg.global_ns.enum("i2s_comm_format_t")
|
||||||
|
I2C_COMM_FMT_OPTIONS = {
|
||||||
|
"stand_i2s": i2s_comm_format_t.I2S_COMM_FORMAT_STAND_I2S,
|
||||||
|
"stand_msb": i2s_comm_format_t.I2S_COMM_FORMAT_STAND_MSB,
|
||||||
|
"stand_pcm_short": i2s_comm_format_t.I2S_COMM_FORMAT_STAND_PCM_SHORT,
|
||||||
|
"stand_pcm_long": i2s_comm_format_t.I2S_COMM_FORMAT_STAND_PCM_LONG,
|
||||||
|
"stand_max": i2s_comm_format_t.I2S_COMM_FORMAT_STAND_MAX,
|
||||||
|
"i2s_msb": i2s_comm_format_t.I2S_COMM_FORMAT_I2S_MSB,
|
||||||
|
"i2s_lsb": i2s_comm_format_t.I2S_COMM_FORMAT_I2S_LSB,
|
||||||
|
"pcm": i2s_comm_format_t.I2S_COMM_FORMAT_PCM,
|
||||||
|
"pcm_short": i2s_comm_format_t.I2S_COMM_FORMAT_PCM_SHORT,
|
||||||
|
"pcm_long": i2s_comm_format_t.I2S_COMM_FORMAT_PCM_LONG,
|
||||||
|
}
|
||||||
|
|
||||||
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
|
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
|
||||||
|
|
||||||
|
|
||||||
@ -77,6 +92,9 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Required(
|
cv.Required(
|
||||||
CONF_I2S_DOUT_PIN
|
CONF_I2S_DOUT_PIN
|
||||||
): pins.internal_gpio_output_pin_number,
|
): pins.internal_gpio_output_pin_number,
|
||||||
|
cv.Optional(CONF_I2S_COMM_FMT, default="stand_i2s"): cv.enum(
|
||||||
|
I2C_COMM_FMT_OPTIONS, lower=True
|
||||||
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -96,4 +114,5 @@ async def to_code(config):
|
|||||||
cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL]))
|
cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL]))
|
||||||
else:
|
else:
|
||||||
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
|
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
|
||||||
|
cg.add(var.set_i2s_comm_fmt(config[CONF_I2S_COMM_FMT]))
|
||||||
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
|
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
|
||||||
|
@ -83,7 +83,7 @@ void I2SAudioSpeaker::player_task(void *params) {
|
|||||||
.sample_rate = this_speaker->sample_rate_,
|
.sample_rate = this_speaker->sample_rate_,
|
||||||
.bits_per_sample = this_speaker->bits_per_sample_,
|
.bits_per_sample = this_speaker->bits_per_sample_,
|
||||||
.channel_format = this_speaker->channel_,
|
.channel_format = this_speaker->channel_,
|
||||||
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
|
.communication_format = this_speaker->i2s_comm_fmt_,
|
||||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||||
.dma_buf_count = 8,
|
.dma_buf_count = 8,
|
||||||
.dma_buf_len = 256,
|
.dma_buf_len = 256,
|
||||||
|
@ -49,6 +49,7 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
|
|||||||
#if SOC_I2S_SUPPORTS_DAC
|
#if SOC_I2S_SUPPORTS_DAC
|
||||||
void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; }
|
void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; }
|
||||||
#endif
|
#endif
|
||||||
|
void set_i2s_comm_fmt(i2s_comm_format_t mode) { this->i2s_comm_fmt_ = mode; }
|
||||||
|
|
||||||
void start() override;
|
void start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
@ -76,6 +77,7 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
|
|||||||
#if SOC_I2S_SUPPORTS_DAC
|
#if SOC_I2S_SUPPORTS_DAC
|
||||||
i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
|
i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
|
||||||
#endif
|
#endif
|
||||||
|
i2s_comm_format_t i2s_comm_fmt_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace i2s_audio
|
} // namespace i2s_audio
|
||||||
|
@ -89,6 +89,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
|||||||
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
void on_shutdown() override { this->command(ILI9XXX_SLPIN); }
|
||||||
|
|
||||||
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
|
||||||
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import io
|
import io
|
||||||
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
from magic import Magic
|
|
||||||
|
|
||||||
from esphome import core
|
import puremagic
|
||||||
from esphome.components import font
|
|
||||||
from esphome import external_files
|
from esphome import core, external_files
|
||||||
import esphome.config_validation as cv
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import font
|
||||||
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_DITHER,
|
CONF_DITHER,
|
||||||
CONF_FILE,
|
CONF_FILE,
|
||||||
@ -238,13 +237,12 @@ CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA)
|
|||||||
|
|
||||||
|
|
||||||
def load_svg_image(file: bytes, resize: tuple[int, int]):
|
def load_svg_image(file: bytes, resize: tuple[int, int]):
|
||||||
# Local import only to allow "validate_pillow_installed" to run *before* importing it
|
# Local imports only to allow "validate_pillow_installed" to run *before* importing it
|
||||||
from PIL import Image
|
# cairosvg is only needed in case of SVG images; adding it
|
||||||
|
|
||||||
# This import is only needed in case of SVG images; adding it
|
|
||||||
# to the top would force configurations not using SVG to also have it
|
# to the top would force configurations not using SVG to also have it
|
||||||
# installed for no reason.
|
# installed for no reason.
|
||||||
from cairosvg import svg2png
|
from cairosvg import svg2png
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
if resize:
|
if resize:
|
||||||
req_width, req_height = resize
|
req_width, req_height = resize
|
||||||
@ -274,14 +272,16 @@ async def to_code(config):
|
|||||||
elif conf_file[CONF_SOURCE] == SOURCE_WEB:
|
elif conf_file[CONF_SOURCE] == SOURCE_WEB:
|
||||||
path = compute_local_image_path(conf_file).as_posix()
|
path = compute_local_image_path(conf_file).as_posix()
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise core.EsphomeError(f"Unknown image source: {conf_file[CONF_SOURCE]}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
file_contents = f.read()
|
file_contents = f.read()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise core.EsphomeError(f"Could not load image file {path}: {e}")
|
raise core.EsphomeError(f"Could not load image file {path}: {e}")
|
||||||
|
|
||||||
mime = Magic(mime=True)
|
file_type = puremagic.from_string(file_contents, mime=True)
|
||||||
file_type = mime.from_buffer(file_contents)
|
|
||||||
|
|
||||||
resize = config.get(CONF_RESIZE)
|
resize = config.get(CONF_RESIZE)
|
||||||
if "svg" in file_type:
|
if "svg" in file_type:
|
||||||
|
@ -79,6 +79,50 @@ Color Image::get_pixel(int x, int y, Color color_on, Color color_off) const {
|
|||||||
return color_off;
|
return color_off;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifdef USE_LVGL
|
||||||
|
lv_img_dsc_t *Image::get_lv_img_dsc() {
|
||||||
|
// lazily construct lvgl image_dsc.
|
||||||
|
if (this->dsc_.data != this->data_start_) {
|
||||||
|
this->dsc_.data = this->data_start_;
|
||||||
|
this->dsc_.header.always_zero = 0;
|
||||||
|
this->dsc_.header.reserved = 0;
|
||||||
|
this->dsc_.header.w = this->width_;
|
||||||
|
this->dsc_.header.h = this->height_;
|
||||||
|
this->dsc_.data_size = image_type_to_width_stride(this->dsc_.header.w * this->dsc_.header.h, this->get_type());
|
||||||
|
switch (this->get_type()) {
|
||||||
|
case IMAGE_TYPE_BINARY:
|
||||||
|
this->dsc_.header.cf = LV_IMG_CF_ALPHA_1BIT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IMAGE_TYPE_GRAYSCALE:
|
||||||
|
this->dsc_.header.cf = LV_IMG_CF_ALPHA_8BIT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IMAGE_TYPE_RGB24:
|
||||||
|
this->dsc_.header.cf = LV_IMG_CF_RGB888;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IMAGE_TYPE_RGB565:
|
||||||
|
#if LV_COLOR_DEPTH == 16
|
||||||
|
this->dsc_.header.cf = this->has_transparency() ? LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED : LV_IMG_CF_TRUE_COLOR;
|
||||||
|
#else
|
||||||
|
this->dsc_.header.cf = LV_IMG_CF_RGB565;
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
|
||||||
|
case image::IMAGE_TYPE_RGBA:
|
||||||
|
#if LV_COLOR_DEPTH == 32
|
||||||
|
this->dsc_.header.cf = LV_IMG_CF_TRUE_COLOR;
|
||||||
|
#else
|
||||||
|
this->dsc_.header.cf = LV_IMG_CF_RGBA8888;
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &this->dsc_;
|
||||||
|
}
|
||||||
|
#endif // USE_LVGL
|
||||||
|
|
||||||
bool Image::get_binary_pixel_(int x, int y) const {
|
bool Image::get_binary_pixel_(int x, int y) const {
|
||||||
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
|
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
|
||||||
const uint32_t pos = x + y * width_8;
|
const uint32_t pos = x + y * width_8;
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "esphome/core/color.h"
|
#include "esphome/core/color.h"
|
||||||
#include "esphome/components/display/display_buffer.h"
|
#include "esphome/components/display/display.h"
|
||||||
|
|
||||||
|
#ifdef USE_LVGL
|
||||||
|
// required for clang-tidy
|
||||||
|
#ifndef LV_CONF_H
|
||||||
|
#define LV_CONF_SKIP 1 // NOLINT
|
||||||
|
#endif // LV_CONF_H
|
||||||
|
|
||||||
|
#include <lvgl.h>
|
||||||
|
#endif // USE_LVGL
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace image {
|
namespace image {
|
||||||
@ -37,7 +46,7 @@ class Image : public display::BaseImage {
|
|||||||
Color get_pixel(int x, int y, Color color_on = display::COLOR_ON, Color color_off = display::COLOR_OFF) const;
|
Color get_pixel(int x, int y, Color color_on = display::COLOR_ON, Color color_off = display::COLOR_OFF) const;
|
||||||
int get_width() const override;
|
int get_width() const override;
|
||||||
int get_height() const override;
|
int get_height() const override;
|
||||||
const uint8_t *get_data_start() { return this->data_start_; }
|
const uint8_t *get_data_start() const { return this->data_start_; }
|
||||||
ImageType get_type() const;
|
ImageType get_type() const;
|
||||||
|
|
||||||
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override;
|
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override;
|
||||||
@ -45,6 +54,9 @@ class Image : public display::BaseImage {
|
|||||||
void set_transparency(bool transparent) { transparent_ = transparent; }
|
void set_transparency(bool transparent) { transparent_ = transparent; }
|
||||||
bool has_transparency() const { return transparent_; }
|
bool has_transparency() const { return transparent_; }
|
||||||
|
|
||||||
|
#ifdef USE_LVGL
|
||||||
|
lv_img_dsc_t *get_lv_img_dsc();
|
||||||
|
#endif
|
||||||
protected:
|
protected:
|
||||||
bool get_binary_pixel_(int x, int y) const;
|
bool get_binary_pixel_(int x, int y) const;
|
||||||
Color get_rgb24_pixel_(int x, int y) const;
|
Color get_rgb24_pixel_(int x, int y) const;
|
||||||
@ -57,6 +69,9 @@ class Image : public display::BaseImage {
|
|||||||
ImageType type_;
|
ImageType type_;
|
||||||
const uint8_t *data_start_;
|
const uint8_t *data_start_;
|
||||||
bool transparent_;
|
bool transparent_;
|
||||||
|
#ifdef USE_LVGL
|
||||||
|
lv_img_dsc_t dsc_{};
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace image
|
} // namespace image
|
||||||
|
@ -53,6 +53,8 @@ MODELS = {
|
|||||||
"inkplate_10": InkplateModel.INKPLATE_10,
|
"inkplate_10": InkplateModel.INKPLATE_10,
|
||||||
"inkplate_6_plus": InkplateModel.INKPLATE_6_PLUS,
|
"inkplate_6_plus": InkplateModel.INKPLATE_6_PLUS,
|
||||||
"inkplate_6_v2": InkplateModel.INKPLATE_6_V2,
|
"inkplate_6_v2": InkplateModel.INKPLATE_6_V2,
|
||||||
|
"inkplate_5": InkplateModel.INKPLATE_5,
|
||||||
|
"inkplate_5_v2": InkplateModel.INKPLATE_5_V2,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
@ -15,6 +15,8 @@ enum InkplateModel : uint8_t {
|
|||||||
INKPLATE_10 = 1,
|
INKPLATE_10 = 1,
|
||||||
INKPLATE_6_PLUS = 2,
|
INKPLATE_6_PLUS = 2,
|
||||||
INKPLATE_6_V2 = 3,
|
INKPLATE_6_V2 = 3,
|
||||||
|
INKPLATE_5 = 4,
|
||||||
|
INKPLATE_5_V2 = 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Inkplate6 : public display::DisplayBuffer, public i2c::I2CDevice {
|
class Inkplate6 : public display::DisplayBuffer, public i2c::I2CDevice {
|
||||||
@ -29,7 +31,7 @@ class Inkplate6 : public display::DisplayBuffer, public i2c::I2CDevice {
|
|||||||
const uint8_t pixelMaskLUT[8] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
|
const uint8_t pixelMaskLUT[8] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
|
||||||
const uint8_t pixelMaskGLUT[2] = {0x0F, 0xF0};
|
const uint8_t pixelMaskGLUT[2] = {0x0F, 0xF0};
|
||||||
|
|
||||||
const uint8_t waveform3BitAll[4][8][9] = {// INKPLATE_6
|
const uint8_t waveform3BitAll[6][8][9] = {// INKPLATE_6
|
||||||
{{0, 1, 1, 0, 0, 1, 1, 0, 0},
|
{{0, 1, 1, 0, 0, 1, 1, 0, 0},
|
||||||
{0, 1, 2, 1, 1, 2, 1, 0, 0},
|
{0, 1, 2, 1, 1, 2, 1, 0, 0},
|
||||||
{1, 1, 1, 2, 2, 1, 0, 0, 0},
|
{1, 1, 1, 2, 2, 1, 0, 0, 0},
|
||||||
@ -64,7 +66,25 @@ class Inkplate6 : public display::DisplayBuffer, public i2c::I2CDevice {
|
|||||||
{1, 1, 1, 1, 2, 2, 1, 0, 0},
|
{1, 1, 1, 1, 2, 2, 1, 0, 0},
|
||||||
{0, 1, 1, 1, 2, 2, 1, 0, 0},
|
{0, 1, 1, 1, 2, 2, 1, 0, 0},
|
||||||
{0, 0, 0, 0, 1, 1, 2, 0, 0},
|
{0, 0, 0, 0, 1, 1, 2, 0, 0},
|
||||||
{0, 0, 0, 0, 0, 1, 2, 0, 0}}};
|
{0, 0, 0, 0, 0, 1, 2, 0, 0}},
|
||||||
|
// INKPLATE_5
|
||||||
|
{{0, 0, 1, 1, 0, 1, 1, 1, 0},
|
||||||
|
{0, 1, 1, 1, 1, 2, 0, 1, 0},
|
||||||
|
{1, 2, 2, 0, 2, 1, 1, 1, 0},
|
||||||
|
{1, 1, 1, 2, 0, 1, 1, 2, 0},
|
||||||
|
{0, 1, 1, 1, 2, 0, 1, 2, 0},
|
||||||
|
{0, 0, 0, 1, 1, 2, 1, 2, 0},
|
||||||
|
{1, 1, 1, 2, 0, 2, 1, 2, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0}},
|
||||||
|
// INKPLATE_5_V2
|
||||||
|
{{0, 0, 1, 1, 2, 1, 1, 1, 0},
|
||||||
|
{1, 1, 2, 2, 1, 2, 1, 1, 0},
|
||||||
|
{0, 1, 2, 2, 1, 1, 2, 1, 0},
|
||||||
|
{0, 0, 1, 1, 1, 1, 1, 2, 0},
|
||||||
|
{1, 2, 1, 2, 1, 1, 1, 2, 0},
|
||||||
|
{0, 1, 1, 1, 2, 0, 1, 2, 0},
|
||||||
|
{1, 1, 1, 2, 2, 2, 1, 2, 0},
|
||||||
|
{0, 0, 0, 0, 0, 0, 0, 0, 0}}};
|
||||||
|
|
||||||
void set_greyscale(bool greyscale) {
|
void set_greyscale(bool greyscale) {
|
||||||
this->greyscale_ = greyscale;
|
this->greyscale_ = greyscale;
|
||||||
@ -146,6 +166,10 @@ class Inkplate6 : public display::DisplayBuffer, public i2c::I2CDevice {
|
|||||||
return 800;
|
return 800;
|
||||||
} else if (this->model_ == INKPLATE_10) {
|
} else if (this->model_ == INKPLATE_10) {
|
||||||
return 1200;
|
return 1200;
|
||||||
|
} else if (this->model_ == INKPLATE_5) {
|
||||||
|
return 960;
|
||||||
|
} else if (this->model_ == INKPLATE_5_V2) {
|
||||||
|
return 1280;
|
||||||
} else if (this->model_ == INKPLATE_6_PLUS) {
|
} else if (this->model_ == INKPLATE_6_PLUS) {
|
||||||
return 1024;
|
return 1024;
|
||||||
}
|
}
|
||||||
@ -155,6 +179,10 @@ class Inkplate6 : public display::DisplayBuffer, public i2c::I2CDevice {
|
|||||||
int get_height_internal() override {
|
int get_height_internal() override {
|
||||||
if (this->model_ == INKPLATE_6 || this->model_ == INKPLATE_6_V2) {
|
if (this->model_ == INKPLATE_6 || this->model_ == INKPLATE_6_V2) {
|
||||||
return 600;
|
return 600;
|
||||||
|
} else if (this->model_ == INKPLATE_5) {
|
||||||
|
return 540;
|
||||||
|
} else if (this->model_ == INKPLATE_5_V2) {
|
||||||
|
return 720;
|
||||||
} else if (this->model_ == INKPLATE_10) {
|
} else if (this->model_ == INKPLATE_10) {
|
||||||
return 825;
|
return 825;
|
||||||
} else if (this->model_ == INKPLATE_6_PLUS) {
|
} else if (this->model_ == INKPLATE_6_PLUS) {
|
||||||
|
@ -18,7 +18,7 @@ from esphome.const import (
|
|||||||
CONF_RESTORE_MODE,
|
CONF_RESTORE_MODE,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WARM_WHITE_COLOR_TEMPERATURE,
|
CONF_WARM_WHITE_COLOR_TEMPERATURE,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import coroutine_with_priority
|
from esphome.core import coroutine_with_priority
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
@ -181,9 +181,8 @@ async def setup_light_core_(light_var, output_var, config):
|
|||||||
mqtt_ = cg.new_Pvariable(mqtt_id, light_var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, light_var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(light_var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, light_var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_light(output_var, config):
|
async def register_light(output_var, config):
|
||||||
|
@ -9,7 +9,7 @@ from esphome.const import (
|
|||||||
CONF_ON_LOCK,
|
CONF_ON_LOCK,
|
||||||
CONF_ON_UNLOCK,
|
CONF_ON_UNLOCK,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_WEB_SERVER_ID,
|
CONF_WEB_SERVER,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.cpp_helpers import setup_entity
|
from esphome.cpp_helpers import setup_entity
|
||||||
@ -66,9 +66,8 @@ async def setup_lock_core_(var, config):
|
|||||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
web_server_ = await cg.get_variable(webserver_id)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
|
||||||
|
|
||||||
|
|
||||||
async def register_lock(var, config):
|
async def register_lock(var, config):
|
||||||
|
@ -22,9 +22,10 @@ from esphome.helpers import write_file_if_changed
|
|||||||
|
|
||||||
from . import defines as df, helpers, lv_validation as lvalid
|
from . import defines as df, helpers, lv_validation as lvalid
|
||||||
from .automation import disp_update, focused_widgets, update_to_code
|
from .automation import disp_update, focused_widgets, update_to_code
|
||||||
from .defines import add_define
|
from .defines import CONF_WIDGETS, add_define
|
||||||
from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code
|
from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code
|
||||||
from .gradient import GRADIENT_SCHEMA, gradients_to_code
|
from .gradient import GRADIENT_SCHEMA, gradients_to_code
|
||||||
|
from .hello_world import get_hello_world
|
||||||
from .lv_validation import lv_bool, lv_images_used
|
from .lv_validation import lv_bool, lv_images_used
|
||||||
from .lvcode import LvContext, LvglComponent
|
from .lvcode import LvContext, LvglComponent
|
||||||
from .schemas import (
|
from .schemas import (
|
||||||
@ -32,7 +33,7 @@ from .schemas import (
|
|||||||
FLEX_OBJ_SCHEMA,
|
FLEX_OBJ_SCHEMA,
|
||||||
GRID_CELL_SCHEMA,
|
GRID_CELL_SCHEMA,
|
||||||
LAYOUT_SCHEMAS,
|
LAYOUT_SCHEMAS,
|
||||||
STYLE_SCHEMA,
|
STATE_SCHEMA,
|
||||||
WIDGET_TYPES,
|
WIDGET_TYPES,
|
||||||
any_widget_schema,
|
any_widget_schema,
|
||||||
container_schema,
|
container_schema,
|
||||||
@ -52,7 +53,7 @@ from .types import (
|
|||||||
lv_style_t,
|
lv_style_t,
|
||||||
lvgl_ns,
|
lvgl_ns,
|
||||||
)
|
)
|
||||||
from .widgets import Widget, add_widgets, lv_scr_act, set_obj_properties
|
from .widgets import Widget, add_widgets, lv_scr_act, set_obj_properties, styles_used
|
||||||
from .widgets.animimg import animimg_spec
|
from .widgets.animimg import animimg_spec
|
||||||
from .widgets.arc import arc_spec
|
from .widgets.arc import arc_spec
|
||||||
from .widgets.button import button_spec
|
from .widgets.button import button_spec
|
||||||
@ -279,6 +280,8 @@ async def to_code(config):
|
|||||||
|
|
||||||
for comp in helpers.lvgl_components_required:
|
for comp in helpers.lvgl_components_required:
|
||||||
CORE.add_define(f"USE_LVGL_{comp.upper()}")
|
CORE.add_define(f"USE_LVGL_{comp.upper()}")
|
||||||
|
if "transform_angle" in styles_used:
|
||||||
|
add_define("LV_COLOR_SCREEN_TRANSP", "1")
|
||||||
for use in helpers.lv_uses:
|
for use in helpers.lv_uses:
|
||||||
add_define(f"LV_USE_{use.upper()}")
|
add_define(f"LV_USE_{use.upper()}")
|
||||||
lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME)
|
lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME)
|
||||||
@ -292,6 +295,13 @@ def display_schema(config):
|
|||||||
return value or [cv.use_id(Display)(config)]
|
return value or [cv.use_id(Display)(config)]
|
||||||
|
|
||||||
|
|
||||||
|
def add_hello_world(config):
|
||||||
|
if CONF_WIDGETS not in config and CONF_PAGES not in config:
|
||||||
|
LOGGER.info("No pages or widgets configured, creating default hello_world page")
|
||||||
|
config[CONF_WIDGETS] = cv.ensure_list(WIDGET_SCHEMA)(get_hello_world())
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
FINAL_VALIDATE_SCHEMA = final_validation
|
FINAL_VALIDATE_SCHEMA = final_validation
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
@ -313,7 +323,7 @@ CONFIG_SCHEMA = (
|
|||||||
),
|
),
|
||||||
cv.Optional(df.CONF_STYLE_DEFINITIONS): cv.ensure_list(
|
cv.Optional(df.CONF_STYLE_DEFINITIONS): cv.ensure_list(
|
||||||
cv.Schema({cv.Required(CONF_ID): cv.declare_id(lv_style_t)})
|
cv.Schema({cv.Required(CONF_ID): cv.declare_id(lv_style_t)})
|
||||||
.extend(STYLE_SCHEMA)
|
.extend(STATE_SCHEMA)
|
||||||
.extend(
|
.extend(
|
||||||
{
|
{
|
||||||
cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments,
|
cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments,
|
||||||
@ -349,4 +359,5 @@ CONFIG_SCHEMA = (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(DISP_BG_SCHEMA)
|
.extend(DISP_BG_SCHEMA)
|
||||||
).add_extra(cv.has_at_least_one_key(CONF_PAGES, df.CONF_WIDGETS))
|
.add_extra(add_hello_world)
|
||||||
|
)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user