mirror of
https://github.com/esphome/esphome.git
synced 2025-11-01 23:51:47 +00:00
Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f27757039 | ||
|
|
532907219b | ||
|
|
eeaba74553 | ||
|
|
dd637582a4 | ||
|
|
b0d12aeea1 | ||
|
|
bdbd813455 | ||
|
|
a6fac2b175 | ||
|
|
5ce923ea90 | ||
|
|
29f0508dc2 | ||
|
|
3ffa59f0cd | ||
|
|
790d6ef94c | ||
|
|
7828f48b9a | ||
|
|
9fbb3659a6 | ||
|
|
fee446c28a | ||
|
|
1d56f0b035 | ||
|
|
341fddb9aa | ||
|
|
456824669f | ||
|
|
62f3039d82 | ||
|
|
be4c718859 | ||
|
|
c2f9ed7c59 | ||
|
|
bfac6607d1 | ||
|
|
e43dcded62 | ||
|
|
887081fd71 | ||
|
|
71ded24fce | ||
|
|
1e2a9e8348 | ||
|
|
64a3aa7092 | ||
|
|
fda8dd4ce3 | ||
|
|
1efabd27d8 | ||
|
|
caa651e55b | ||
|
|
10a6e9b4ee | ||
|
|
4b8ec44262 | ||
|
|
bd74ed4bc0 | ||
|
|
d01f296420 | ||
|
|
27112e2ace | ||
|
|
837930234f | ||
|
|
e19aa3bbe0 | ||
|
|
35b5c1ed56 | ||
|
|
c9d93ff685 | ||
|
|
fa72990a63 | ||
|
|
e5afb1c4ea | ||
|
|
73ead5f328 | ||
|
|
5c57b51378 | ||
|
|
e25935ef21 | ||
|
|
c7a52c3894 | ||
|
|
53a4689ed1 | ||
|
|
0a82e6e792 | ||
|
|
98855e4123 | ||
|
|
6a09d7c49b | ||
|
|
46e50ba53f | ||
|
|
f1e3ff2ed2 | ||
|
|
7787fa8f29 | ||
|
|
70902029f8 | ||
|
|
4f9a56c884 | ||
|
|
3715ba030b | ||
|
|
0c93be97a9 | ||
|
|
54eb6070fb | ||
|
|
4dbf1c521e | ||
|
|
a91e6a6bdf | ||
|
|
8600620305 | ||
|
|
96721f305f | ||
|
|
2bf70d7d00 | ||
|
|
1d8c170f48 | ||
|
|
6009c7edb4 | ||
|
|
e3f36c033e | ||
|
|
d4eb0f1655 | ||
|
|
e20ec00071 | ||
|
|
150114d774 | ||
|
|
89dfa5ea82 | ||
|
|
97aa930ad2 | ||
|
|
2a5def10e7 | ||
|
|
969834e037 | ||
|
|
d73a44c504 | ||
|
|
8aec092ab6 | ||
|
|
4fa959ba45 | ||
|
|
b43712d78d | ||
|
|
01904a0f10 | ||
|
|
dd875e7529 | ||
|
|
f1dcf0f0b8 | ||
|
|
a045d001bf | ||
|
|
066c1022d0 | ||
|
|
59c192becc | ||
|
|
a800816750 | ||
|
|
970563e07b |
47
.github/workflows/ci-docker.yml
vendored
47
.github/workflows/ci-docker.yml
vendored
@@ -18,38 +18,23 @@ jobs:
|
||||
name: Build docker containers
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["hassio", "docker"]
|
||||
build_type: ["ha-addon", "docker", "lint"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="3.4.0"
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Set TAG
|
||||
run: |
|
||||
echo "TAG=check" >> $GITHUB_ENV
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile.hassio"
|
||||
else
|
||||
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile"
|
||||
fi
|
||||
|
||||
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
|
||||
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
|
||||
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
|
||||
- name: Pull for cache
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:dev" || true
|
||||
- name: Register QEMU binfmt
|
||||
run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
|
||||
- run: |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
--build-arg "BUILD_VERSION=ci" \
|
||||
--cache-from "${BUILD_TO}:dev" \
|
||||
--file "${DOCKERFILE}" \
|
||||
.
|
||||
- name: Run build
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${TAG}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
build
|
||||
|
||||
178
.github/workflows/ci.yml
vendored
178
.github/workflows/ci.yml
vendored
@@ -4,40 +4,36 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
# On dev branch release-dev already performs CI checks
|
||||
# On other branches the `pull_request` trigger will be used
|
||||
branches: [beta, release]
|
||||
branches: [dev, beta, release]
|
||||
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint-clang-format:
|
||||
ci-with-container:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:1.1
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
- name: Run clang-format
|
||||
run: script/clang-format -i
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-clang-tidy:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:1.1
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
split: [1, 2, 3, 4]
|
||||
include:
|
||||
- id: clang-format
|
||||
name: Run script/clang-format
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy 1/4
|
||||
split: 1
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy 2/4
|
||||
split: 2
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy 3/4
|
||||
split: 3
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy 4/4
|
||||
split: 4
|
||||
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: ghcr.io/esphome/esphome-lint:1.1
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
@@ -45,26 +41,57 @@ jobs:
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
|
||||
- name: Run clang-format
|
||||
run: script/clang-format -i
|
||||
if: ${{ matrix.id == 'clang-format' }}
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
|
||||
if: ${{ matrix.id == 'clang-tidy' }}
|
||||
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-python:
|
||||
ci:
|
||||
# Don't use the esphome-lint docker image because it may contain outdated requirements.
|
||||
# This way, all dependencies are cached via the cache action.
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- id: ci-custom
|
||||
name: Run script/ci-custom
|
||||
- id: lint-python
|
||||
name: Run script/lint-python
|
||||
- id: test
|
||||
file: tests/test1.yaml
|
||||
name: Test tests/test1.yaml
|
||||
- id: test
|
||||
file: tests/test2.yaml
|
||||
name: Test tests/test2.yaml
|
||||
- id: test
|
||||
file: tests/test3.yaml
|
||||
name: Test tests/test3.yaml
|
||||
- id: test
|
||||
file: tests/test4.yaml
|
||||
name: Test tests/test4.yaml
|
||||
- id: pytest
|
||||
name: Run pytest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
@@ -72,6 +99,17 @@ jobs:
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
|
||||
# Use per test platformio cache because tests have different platform versions
|
||||
- name: Cache ~/.platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.file }}-${{ hashFiles('esphome/core/config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.file }}-
|
||||
if: ${{ matrix.id == 'test' }}
|
||||
|
||||
- name: Set up python environment
|
||||
run: script/setup
|
||||
|
||||
@@ -80,82 +118,22 @@ jobs:
|
||||
echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/pytest.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
|
||||
- name: Lint Custom
|
||||
run: script/ci-custom.py
|
||||
run: |
|
||||
script/ci-custom.py
|
||||
script/build_codeowners.py --check
|
||||
if: ${{ matrix.id == 'ci-custom' }}
|
||||
- name: Lint Python
|
||||
run: script/lint-python
|
||||
- name: Lint CODEOWNERS
|
||||
run: script/build_codeowners.py --check
|
||||
if: ${{ matrix.id == 'lint-python' }}
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test:
|
||||
- test1
|
||||
- test2
|
||||
- test3
|
||||
- test4
|
||||
- test5
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
# Use per test platformio cache because tests have different platform versions
|
||||
- name: Cache ~/.platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
- run: esphome compile ${{ matrix.file }}
|
||||
if: ${{ matrix.id == 'test' }}
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- run: esphome compile tests/${{ matrix.test }}.yaml
|
||||
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
- name: Install Github Actions annotator
|
||||
run: pip install pytest-github-actions-annotate-failures
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Run pytest
|
||||
run: |
|
||||
pytest \
|
||||
-qq \
|
||||
--durations=10 \
|
||||
-o console_output_style=count \
|
||||
tests
|
||||
pytest -vv --tb=native tests
|
||||
if: ${{ matrix.id == 'pytest' }}
|
||||
|
||||
108
.github/workflows/docker-lint-build.yml
vendored
108
.github/workflows/docker-lint-build.yml
vendored
@@ -13,30 +13,88 @@ on:
|
||||
- '.github/workflows/docker-lint-build.yml'
|
||||
|
||||
jobs:
|
||||
publish-docker-lint-iage:
|
||||
name: Build docker containers
|
||||
deploy-docker:
|
||||
name: Build and publish docker containers
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["lint"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set TAG
|
||||
run: |
|
||||
echo "TAG=1.1" >> $GITHUB_ENV
|
||||
- name: Pull for cache
|
||||
run: |
|
||||
docker pull "esphome/esphome-lint:latest" || true
|
||||
- name: Build
|
||||
run: |
|
||||
docker build \
|
||||
--cache-from "esphome/esphome-lint:latest" \
|
||||
--file "docker/Dockerfile.lint" \
|
||||
--tag "esphome/esphome-lint:latest" \
|
||||
--tag "esphome/esphome-lint:${TAG}" \
|
||||
.
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
|
||||
- run: |
|
||||
docker push "esphome/esphome-lint:${TAG}"
|
||||
docker push "esphome/esphome-lint:latest"
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Set TAG
|
||||
run: |
|
||||
echo "TAG=1.1" >> $GITHUB_ENV
|
||||
|
||||
- name: Run build
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${TAG}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
build
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run push
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${TAG}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
push
|
||||
|
||||
deploy-docker-manifest:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: ["lint"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Set TAG
|
||||
run: |
|
||||
echo "TAG=1.1" >> $GITHUB_ENV
|
||||
- name: Enable experimental manifest support
|
||||
run: |
|
||||
mkdir -p ~/.docker
|
||||
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run manifest
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${TAG}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
manifest
|
||||
|
||||
19
.github/workflows/matchers/pytest.json
vendored
Normal file
19
.github/workflows/matchers/pytest.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "pytest",
|
||||
"fileLocation": "absolute",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^\\s+File \"(.*)\", line (\\d+), in (.*)$",
|
||||
"file": 1,
|
||||
"line": 2
|
||||
},
|
||||
{
|
||||
"regexp": "^\\s+(.*)$",
|
||||
"message": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
247
.github/workflows/release-dev.yml
vendored
247
.github/workflows/release-dev.yml
vendored
@@ -1,247 +0,0 @@
|
||||
name: Publish dev releases to docker hub
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
# THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
|
||||
|
||||
lint-clang-format:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:1.1
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
- name: Run clang-format
|
||||
run: script/clang-format -i
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-clang-tidy:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:1.1
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
split: [1, 2, 3, 4]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
- name: Run clang-tidy
|
||||
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-python:
|
||||
# Don't use the esphome-lint docker image because it may contain outdated requirements.
|
||||
# This way, all dependencies are cached via the cache action.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up python environment
|
||||
run: script/setup
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Lint Custom
|
||||
run: script/ci-custom.py
|
||||
- name: Lint Python
|
||||
run: script/lint-python
|
||||
- name: Lint CODEOWNERS
|
||||
run: script/build_codeowners.py --check
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test:
|
||||
- test1
|
||||
- test2
|
||||
- test3
|
||||
- test4
|
||||
- test5
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
# Use per test platformio cache because tests have different platform versions
|
||||
- name: Cache ~/.platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- run: esphome compile tests/${{ matrix.test }}.yaml
|
||||
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
- name: Install Github Actions annotator
|
||||
run: pip install pytest-github-actions-annotate-failures
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Run pytest
|
||||
run: |
|
||||
pytest \
|
||||
-qq \
|
||||
--durations=10 \
|
||||
-o console_output_style=count \
|
||||
tests
|
||||
|
||||
deploy-docker:
|
||||
name: Build and publish docker containers
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
# Hassio dev image doesn't use esphome/esphome-hassio-$arch and uses base directly
|
||||
build_type: ["docker"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_SHA:0:7}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="3.4.0"
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile.hassio"
|
||||
else
|
||||
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile"
|
||||
fi
|
||||
|
||||
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
|
||||
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
|
||||
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
|
||||
- name: Pull for cache
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:dev" || true
|
||||
- name: Register QEMU binfmt
|
||||
run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
|
||||
- run: |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
--build-arg "BUILD_VERSION=${TAG}" \
|
||||
--tag "${BUILD_TO}:${TAG}" \
|
||||
--tag "${BUILD_TO}:dev" \
|
||||
--cache-from "${BUILD_TO}:dev" \
|
||||
--file "${DOCKERFILE}" \
|
||||
.
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
|
||||
- run: |
|
||||
docker push "${BUILD_TO}:${TAG}"
|
||||
docker push "${BUILD_TO}:dev"
|
||||
|
||||
|
||||
deploy-docker-manifest:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
steps:
|
||||
- name: Enable experimental manifest support
|
||||
run: |
|
||||
mkdir -p ~/.docker
|
||||
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
|
||||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_SHA:0:7}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
|
||||
- name: "Create the manifest"
|
||||
run: |
|
||||
docker manifest create esphome/esphome:${TAG} \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:${TAG}
|
||||
|
||||
docker manifest create esphome/esphome:dev \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:dev
|
||||
315
.github/workflows/release.yml
vendored
315
.github/workflows/release.yml
vendored
@@ -1,164 +1,35 @@
|
||||
name: Publish Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
schedule:
|
||||
- cron: "0 2 * * *"
|
||||
|
||||
jobs:
|
||||
# THE LINT/TEST JOBS ARE COPIED FROM ci.yaml
|
||||
|
||||
lint-clang-format:
|
||||
init:
|
||||
name: Initialize build
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:1.1
|
||||
outputs:
|
||||
tag: ${{ steps.tag.outputs.tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
- name: Run clang-format
|
||||
run: script/clang-format -i
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-clang-tidy:
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:1.1
|
||||
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
split: [1, 2, 3, 4]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
|
||||
|
||||
- name: Register problem matchers
|
||||
- name: Get tag
|
||||
id: tag
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
- name: Run clang-tidy
|
||||
run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }}
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
|
||||
lint-python:
|
||||
# Don't use the esphome-lint docker image because it may contain outdated requirements.
|
||||
# This way, all dependencies are cached via the cache action.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up python environment
|
||||
run: script/setup
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Lint Custom
|
||||
run: script/ci-custom.py
|
||||
- name: Lint Python
|
||||
run: script/lint-python
|
||||
- name: Lint CODEOWNERS
|
||||
run: script/build_codeowners.py --check
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test:
|
||||
- test1
|
||||
- test2
|
||||
- test3
|
||||
- test4
|
||||
- test5
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
# Use per test platformio cache because tests have different platform versions
|
||||
- name: Cache ~/.platformio
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }}
|
||||
restore-keys: |
|
||||
test-home-platformio-${{ matrix.test }}-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- run: esphome compile tests/${{ matrix.test }}.yaml
|
||||
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.7-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.7-
|
||||
- name: Set up environment
|
||||
run: script/setup
|
||||
- name: Install Github Actions annotator
|
||||
run: pip install pytest-github-actions-annotate-failures
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Run pytest
|
||||
run: |
|
||||
pytest \
|
||||
-qq \
|
||||
--durations=10 \
|
||||
-o console_output_style=count \
|
||||
tests
|
||||
if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then
|
||||
TAG="${GITHUB_REF#refs/tags/v}"
|
||||
else
|
||||
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
|
||||
today="$(date --utc '+%Y%m%d')"
|
||||
TAG="${TAG}${today}"
|
||||
fi
|
||||
echo "::set-output name=tag::${TAG}"
|
||||
|
||||
deploy-pypi:
|
||||
name: Build and publish to PyPi
|
||||
if: github.repository == 'esphome/esphome'
|
||||
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
|
||||
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -182,119 +53,85 @@ jobs:
|
||||
name: Build and publish docker containers
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
|
||||
needs: [init]
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["hassio", "docker"]
|
||||
build_type: ["ha-addon", "docker"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/v}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
- name: Set up env variables
|
||||
run: |
|
||||
base_version="3.4.0"
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
if [[ "${{ matrix.build_type }}" == "hassio" ]]; then
|
||||
build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-hassio-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile.hassio"
|
||||
else
|
||||
build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}"
|
||||
build_to="esphome/esphome-${{ matrix.arch }}"
|
||||
dockerfile="docker/Dockerfile"
|
||||
fi
|
||||
- name: Run build
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${{ needs.init.outputs.tag }}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
build
|
||||
|
||||
if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then
|
||||
cache_tag="beta"
|
||||
else
|
||||
cache_tag="latest"
|
||||
fi
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Set env variables so these values don't need to be calculated again
|
||||
echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV
|
||||
echo "BUILD_TO=${build_to}" >> $GITHUB_ENV
|
||||
echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV
|
||||
echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV
|
||||
- name: Pull for cache
|
||||
run: |
|
||||
docker pull "${BUILD_TO}:${CACHE_TAG}" || true
|
||||
- name: Register QEMU binfmt
|
||||
run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes
|
||||
- run: |
|
||||
docker build \
|
||||
--build-arg "BUILD_FROM=${BUILD_FROM}" \
|
||||
--build-arg "BUILD_VERSION=${TAG}" \
|
||||
--tag "${BUILD_TO}:${TAG}" \
|
||||
--cache-from "${BUILD_TO}:${CACHE_TAG}" \
|
||||
--file "${DOCKERFILE}" \
|
||||
.
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
|
||||
- run: docker push "${BUILD_TO}:${TAG}"
|
||||
|
||||
# Always publish to beta tag (also full releases)
|
||||
- name: Publish docker beta tag
|
||||
run: |
|
||||
docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:beta"
|
||||
docker push "${BUILD_TO}:beta"
|
||||
|
||||
- if: ${{ !github.event.release.prerelease }}
|
||||
name: Publish docker latest tag
|
||||
run: |
|
||||
docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:latest"
|
||||
docker push "${BUILD_TO}:latest"
|
||||
- name: Run push
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${{ needs.init.outputs.tag }}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
push
|
||||
|
||||
deploy-docker-manifest:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
needs: [init, deploy-docker]
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: ["ha-addon", "docker"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Enable experimental manifest support
|
||||
run: |
|
||||
mkdir -p ~/.docker
|
||||
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
|
||||
- name: Set TAG
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/v}"
|
||||
echo "TAG=${TAG}" >> $GITHUB_ENV
|
||||
|
||||
- name: Log in to docker hub
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
|
||||
- name: "Create the manifest"
|
||||
run: |
|
||||
docker manifest create esphome/esphome:${TAG} \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:${TAG}
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish docker beta tag
|
||||
- name: Run manifest
|
||||
run: |
|
||||
docker manifest create esphome/esphome:beta \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:beta
|
||||
|
||||
- name: Publish docker latest tag
|
||||
if: ${{ !github.event.release.prerelease }}
|
||||
run: |
|
||||
docker manifest create esphome/esphome:latest \
|
||||
esphome/esphome-aarch64:${TAG} \
|
||||
esphome/esphome-amd64:${TAG} \
|
||||
esphome/esphome-armv7:${TAG}
|
||||
docker manifest push esphome/esphome:latest
|
||||
docker/build.py \
|
||||
--tag "${{ needs.init.outputs.tag }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
manifest
|
||||
|
||||
deploy-hassio-repo:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
steps:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -13,6 +13,9 @@ __pycache__/
|
||||
# Intellij Idea
|
||||
.idea
|
||||
|
||||
# Vim
|
||||
*.swp
|
||||
|
||||
# Hide some OS X stuff
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
|
||||
@@ -73,6 +73,11 @@ esphome/components/midea_ac/* @dudanov
|
||||
esphome/components/midea_dongle/* @dudanov
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/network/* @esphome/core
|
||||
esphome/components/nextion/* @senexcrenshaw
|
||||
esphome/components/nextion/binary_sensor/* @senexcrenshaw
|
||||
esphome/components/nextion/sensor/* @senexcrenshaw
|
||||
esphome/components/nextion/switch/* @senexcrenshaw
|
||||
esphome/components/nextion/text_sensor/* @senexcrenshaw
|
||||
esphome/components/nfc/* @jesserockz
|
||||
esphome/components/number/* @esphome/core
|
||||
esphome/components/ota/* @esphome/core
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG BUILD_FROM=esphome/esphome-base-amd64:3.4.0
|
||||
ARG BUILD_FROM=esphome/esphome-base:latest
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG BUILD_FROM
|
||||
ARG BUILD_FROM=esphome/esphome-hassio-base:latest
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
FROM esphome/esphome-lint-base:3.4.0
|
||||
ARG BUILD_FROM=esphome/esphome-lint-base:latest
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
|
||||
177
docker/build.py
Executable file
177
docker/build.py
Executable file
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env python3
|
||||
from dataclasses import dataclass
|
||||
import subprocess
|
||||
import argparse
|
||||
import platform
|
||||
import shlex
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
CHANNEL_DEV = 'dev'
|
||||
CHANNEL_BETA = 'beta'
|
||||
CHANNEL_RELEASE = 'release'
|
||||
CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE]
|
||||
|
||||
ARCH_AMD64 = 'amd64'
|
||||
ARCH_ARMV7 = 'armv7'
|
||||
ARCH_AARCH64 = 'aarch64'
|
||||
ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64]
|
||||
|
||||
TYPE_DOCKER = 'docker'
|
||||
TYPE_HA_ADDON = 'ha-addon'
|
||||
TYPE_LINT = 'lint'
|
||||
TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
|
||||
|
||||
|
||||
BASE_VERSION = "3.6.0"
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag")
|
||||
parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for")
|
||||
parser.add_argument("--build-type", choices=TYPES, required=True, help="The type of build to run")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them")
|
||||
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
|
||||
build_parser = subparsers.add_parser("build", help="Build the image")
|
||||
push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub")
|
||||
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
|
||||
|
||||
|
||||
|
||||
# only lists some possibilities, doesn't have to be perfect
|
||||
# https://stackoverflow.com/a/45125525
|
||||
UNAME_TO_ARCH = {
|
||||
"x86_64": ARCH_AMD64,
|
||||
"aarch64": ARCH_AARCH64,
|
||||
"aarch64_be": ARCH_AARCH64,
|
||||
"arm": ARCH_ARMV7,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DockerParams:
|
||||
build_from: str
|
||||
build_to: str
|
||||
manifest_to: str
|
||||
dockerfile: str
|
||||
|
||||
@classmethod
|
||||
def for_type_arch(cls, build_type, arch):
|
||||
prefix = {
|
||||
TYPE_DOCKER: "esphome/esphome",
|
||||
TYPE_HA_ADDON: "esphome/esphome-hassio",
|
||||
TYPE_LINT: "esphome/esphome-lint"
|
||||
}[build_type]
|
||||
build_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}"
|
||||
build_to = f"{prefix}-{arch}"
|
||||
dockerfile = {
|
||||
TYPE_DOCKER: "docker/Dockerfile",
|
||||
TYPE_HA_ADDON: "docker/Dockerfile.hassio",
|
||||
TYPE_LINT: "docker/Dockerfile.lint",
|
||||
}[build_type]
|
||||
return cls(
|
||||
build_from=build_from,
|
||||
build_to=build_to,
|
||||
manifest_to=prefix,
|
||||
dockerfile=dockerfile
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
def run_command(*cmd, ignore_error: bool = False):
|
||||
print(f"$ {shlex.join(list(cmd))}")
|
||||
if not args.dry_run:
|
||||
rc = subprocess.call(list(cmd))
|
||||
if rc != 0 and not ignore_error:
|
||||
print("Command failed")
|
||||
sys.exit(1)
|
||||
|
||||
# detect channel from tag
|
||||
match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag)
|
||||
if match is None:
|
||||
channel = CHANNEL_DEV
|
||||
elif match.group(1) is None:
|
||||
channel = CHANNEL_RELEASE
|
||||
else:
|
||||
channel = CHANNEL_BETA
|
||||
|
||||
tags_to_push = [args.tag]
|
||||
if channel == CHANNEL_DEV:
|
||||
tags_to_push.append("dev")
|
||||
elif channel == CHANNEL_BETA:
|
||||
tags_to_push.append("beta")
|
||||
elif channel == CHANNEL_RELEASE:
|
||||
# Additionally push to beta
|
||||
tags_to_push.append("beta")
|
||||
tags_to_push.append("latest")
|
||||
|
||||
if args.command == "build":
|
||||
# 1. pull cache image
|
||||
params = DockerParams.for_type_arch(args.build_type, args.arch)
|
||||
cache_tag = {
|
||||
CHANNEL_DEV: "dev",
|
||||
CHANNEL_BETA: "beta",
|
||||
CHANNEL_RELEASE: "latest",
|
||||
}[channel]
|
||||
cache_img = f"ghcr.io/{params.build_to}:{cache_tag}"
|
||||
run_command("docker", "pull", cache_img, ignore_error=True)
|
||||
|
||||
# 2. register QEMU binfmt (if not host arch)
|
||||
is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch
|
||||
if not is_native:
|
||||
run_command(
|
||||
"docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2",
|
||||
"--reset", "-p", "yes"
|
||||
)
|
||||
|
||||
# 3. build
|
||||
run_command(
|
||||
"docker", "build",
|
||||
"--build-arg", f"BUILD_FROM={params.build_from}",
|
||||
"--build-arg", f"BUILD_VERSION={args.tag}",
|
||||
"--tag", f"{params.build_to}:{args.tag}",
|
||||
"--cache-from", cache_img,
|
||||
"--file", params.dockerfile,
|
||||
"."
|
||||
)
|
||||
elif args.command == "push":
|
||||
params = DockerParams.for_type_arch(args.build_type, args.arch)
|
||||
imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push]
|
||||
imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push]
|
||||
src = imgs[0]
|
||||
# 1. tag images
|
||||
for img in imgs[1:]:
|
||||
run_command(
|
||||
"docker", "tag", src, img
|
||||
)
|
||||
# 2. push images
|
||||
for img in imgs:
|
||||
run_command(
|
||||
"docker", "push", img
|
||||
)
|
||||
elif args.command == "manifest":
|
||||
manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to
|
||||
|
||||
targets = [f"{manifest}:{tag}" for tag in tags_to_push]
|
||||
targets += [f"ghcr.io/{manifest}:{tag}" for tag in tags_to_push]
|
||||
# 1. Create manifests
|
||||
for target in targets:
|
||||
cmd = ["docker", "manifest", "create", target]
|
||||
for arch in ARCHS:
|
||||
src = f"{DockerParams.for_type_arch(args.build_type, arch).build_to}:{args.tag}"
|
||||
if target.startswith("ghcr.io"):
|
||||
src = f"ghcr.io/{src}"
|
||||
cmd.append(src)
|
||||
run_command(*cmd)
|
||||
# 2. Push manifests
|
||||
for target in targets:
|
||||
run_command(
|
||||
"docker", "manifest", "push", target
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -11,7 +11,30 @@ namespace adc {
|
||||
static const char *const TAG = "adc";
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; }
|
||||
void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; }
|
||||
|
||||
inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
|
||||
switch (pin) {
|
||||
case 36:
|
||||
return ADC1_CHANNEL_0;
|
||||
case 37:
|
||||
return ADC1_CHANNEL_1;
|
||||
case 38:
|
||||
return ADC1_CHANNEL_2;
|
||||
case 39:
|
||||
return ADC1_CHANNEL_3;
|
||||
case 32:
|
||||
return ADC1_CHANNEL_4;
|
||||
case 33:
|
||||
return ADC1_CHANNEL_5;
|
||||
case 34:
|
||||
return ADC1_CHANNEL_6;
|
||||
case 35:
|
||||
return ADC1_CHANNEL_7;
|
||||
default:
|
||||
return ADC1_CHANNEL_MAX;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ADCSensor::setup() {
|
||||
@@ -21,7 +44,9 @@ void ADCSensor::setup() {
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
analogSetPinAttenuation(this->pin_, this->attenuation_);
|
||||
adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_);
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_));
|
||||
#endif
|
||||
}
|
||||
void ADCSensor::dump_config() {
|
||||
@@ -36,18 +61,20 @@ void ADCSensor::dump_config() {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
|
||||
switch (this->attenuation_) {
|
||||
case ADC_0db:
|
||||
case ADC_ATTEN_DB_0:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
|
||||
break;
|
||||
case ADC_2_5db:
|
||||
case ADC_ATTEN_DB_2_5:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
|
||||
break;
|
||||
case ADC_6db:
|
||||
case ADC_ATTEN_DB_6:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
|
||||
break;
|
||||
case ADC_11db:
|
||||
case ADC_ATTEN_DB_11:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
|
||||
break;
|
||||
default: // This is to satisfy the unused ADC_ATTEN_MAX
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
@@ -60,20 +87,23 @@ void ADCSensor::update() {
|
||||
}
|
||||
float ADCSensor::sample() {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
float value_v = analogRead(this->pin_) / 4095.0f; // NOLINT
|
||||
int raw = adc1_get_raw(gpio_to_adc1(pin_));
|
||||
float value_v = raw / 4095.0f;
|
||||
switch (this->attenuation_) {
|
||||
case ADC_0db:
|
||||
case ADC_ATTEN_DB_0:
|
||||
value_v *= 1.1;
|
||||
break;
|
||||
case ADC_2_5db:
|
||||
case ADC_ATTEN_DB_2_5:
|
||||
value_v *= 1.5;
|
||||
break;
|
||||
case ADC_6db:
|
||||
case ADC_ATTEN_DB_6:
|
||||
value_v *= 2.2;
|
||||
break;
|
||||
case ADC_11db:
|
||||
case ADC_ATTEN_DB_11:
|
||||
value_v *= 3.9;
|
||||
break;
|
||||
default: // This is to satisfy the unused ADC_ATTEN_MAX
|
||||
break;
|
||||
}
|
||||
return value_v;
|
||||
#endif
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "driver/adc.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace adc {
|
||||
|
||||
@@ -13,7 +17,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
||||
public:
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
/// Set the attenuation for this pin. Only available on the ESP32.
|
||||
void set_attenuation(adc_attenuation_t attenuation);
|
||||
void set_attenuation(adc_atten_t attenuation);
|
||||
#endif
|
||||
|
||||
/// Update adc values.
|
||||
@@ -34,7 +38,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
||||
uint8_t pin_;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
adc_attenuation_t attenuation_{ADC_0db};
|
||||
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@ from esphome.const import (
|
||||
AUTO_LOAD = ["voltage_sampler"]
|
||||
|
||||
ATTENUATION_MODES = {
|
||||
"0db": cg.global_ns.ADC_0db,
|
||||
"2.5db": cg.global_ns.ADC_2_5db,
|
||||
"6db": cg.global_ns.ADC_6db,
|
||||
"11db": cg.global_ns.ADC_11db,
|
||||
"0db": cg.global_ns.ADC_ATTEN_DB_0,
|
||||
"2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
|
||||
"6db": cg.global_ns.ADC_ATTEN_DB_6,
|
||||
"11db": cg.global_ns.ADC_ATTEN_DB_11,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -64,11 +64,6 @@ void ADS1115Component::setup() {
|
||||
return;
|
||||
}
|
||||
this->prev_config_ = config;
|
||||
|
||||
for (auto *sensor : this->sensors_) {
|
||||
this->set_interval(sensor->get_name(), sensor->update_interval(),
|
||||
[this, sensor] { this->request_measurement(sensor); });
|
||||
}
|
||||
}
|
||||
void ADS1115Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
|
||||
@@ -107,17 +102,22 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
||||
}
|
||||
this->prev_config_ = config;
|
||||
|
||||
// about 1.6 ms with 860 samples per second
|
||||
// about 1.2 ms with 860 samples per second
|
||||
delay(2);
|
||||
|
||||
uint32_t start = millis();
|
||||
while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
|
||||
if (millis() - start > 100) {
|
||||
ESP_LOGW(TAG, "Reading ADS1115 timed out");
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
// in continuous mode, conversion will always be running, rely on the delay
|
||||
// to ensure conversion is taking place with the correct settings
|
||||
// can we use the rdy pin to trigger when a conversion is done?
|
||||
if (!this->continuous_mode_) {
|
||||
uint32_t start = millis();
|
||||
while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
|
||||
if (millis() - start > 100) {
|
||||
ESP_LOGW(TAG, "Reading ADS1115 timed out");
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
yield();
|
||||
}
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
||||
auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str());
|
||||
ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str());
|
||||
break;
|
||||
}
|
||||
this->char_handle_ = chr->handle;
|
||||
|
||||
@@ -422,6 +422,12 @@ enum SensorStateClass {
|
||||
STATE_CLASS_MEASUREMENT = 1;
|
||||
}
|
||||
|
||||
enum SensorLastResetType {
|
||||
LAST_RESET_NONE = 0;
|
||||
LAST_RESET_NEVER = 1;
|
||||
LAST_RESET_AUTO = 2;
|
||||
}
|
||||
|
||||
message ListEntitiesSensorResponse {
|
||||
option (id) = 16;
|
||||
option (source) = SOURCE_SERVER;
|
||||
@@ -438,6 +444,7 @@ message ListEntitiesSensorResponse {
|
||||
bool force_update = 8;
|
||||
string device_class = 9;
|
||||
SensorStateClass state_class = 10;
|
||||
SensorLastResetType last_reset_type = 11;
|
||||
}
|
||||
message SensorStateResponse {
|
||||
option (id) = 25;
|
||||
|
||||
@@ -399,6 +399,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
||||
msg.force_update = sensor->get_force_update();
|
||||
msg.device_class = sensor->get_device_class();
|
||||
msg.state_class = static_cast<enums::SensorStateClass>(sensor->state_class);
|
||||
msg.last_reset_type = static_cast<enums::SensorLastResetType>(sensor->last_reset_type);
|
||||
|
||||
return this->send_list_entities_sensor_response(msg);
|
||||
}
|
||||
@@ -570,11 +571,11 @@ bool APIConnection::send_number_info(number::Number *number) {
|
||||
msg.object_id = number->get_object_id();
|
||||
msg.name = number->get_name();
|
||||
msg.unique_id = get_default_unique_id("number", number);
|
||||
msg.icon = number->get_icon();
|
||||
msg.icon = number->traits.get_icon();
|
||||
|
||||
msg.min_value = number->get_min_value();
|
||||
msg.max_value = number->get_max_value();
|
||||
msg.step = number->get_step();
|
||||
msg.min_value = number->traits.get_min_value();
|
||||
msg.max_value = number->traits.get_max_value();
|
||||
msg.step = number->traits.get_step();
|
||||
|
||||
return this->send_list_entities_number_response(msg);
|
||||
}
|
||||
|
||||
@@ -72,6 +72,18 @@ template<> const char *proto_enum_to_string<enums::SensorStateClass>(enums::Sens
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::SensorLastResetType>(enums::SensorLastResetType value) {
|
||||
switch (value) {
|
||||
case enums::LAST_RESET_NONE:
|
||||
return "LAST_RESET_NONE";
|
||||
case enums::LAST_RESET_NEVER:
|
||||
return "LAST_RESET_NEVER";
|
||||
case enums::LAST_RESET_AUTO:
|
||||
return "LAST_RESET_AUTO";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
|
||||
switch (value) {
|
||||
case enums::LOG_LEVEL_NONE:
|
||||
@@ -1592,6 +1604,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->state_class = value.as_enum<enums::SensorStateClass>();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->last_reset_type = value.as_enum<enums::SensorLastResetType>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1647,6 +1663,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(8, this->force_update);
|
||||
buffer.encode_string(9, this->device_class);
|
||||
buffer.encode_enum<enums::SensorStateClass>(10, this->state_class);
|
||||
buffer.encode_enum<enums::SensorLastResetType>(11, this->last_reset_type);
|
||||
}
|
||||
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
@@ -1692,6 +1709,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
out.append(" state_class: ");
|
||||
out.append(proto_enum_to_string<enums::SensorStateClass>(this->state_class));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" last_reset_type: ");
|
||||
out.append(proto_enum_to_string<enums::SensorLastResetType>(this->last_reset_type));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
|
||||
@@ -36,6 +36,11 @@ enum SensorStateClass : uint32_t {
|
||||
STATE_CLASS_NONE = 0,
|
||||
STATE_CLASS_MEASUREMENT = 1,
|
||||
};
|
||||
enum SensorLastResetType : uint32_t {
|
||||
LAST_RESET_NONE = 0,
|
||||
LAST_RESET_NEVER = 1,
|
||||
LAST_RESET_AUTO = 2,
|
||||
};
|
||||
enum LogLevel : uint32_t {
|
||||
LOG_LEVEL_NONE = 0,
|
||||
LOG_LEVEL_ERROR = 1,
|
||||
@@ -429,6 +434,7 @@ class ListEntitiesSensorResponse : public ProtoMessage {
|
||||
bool force_update{false};
|
||||
std::string device_class{};
|
||||
enums::SensorStateClass state_class{};
|
||||
enums::SensorLastResetType last_reset_type{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ from esphome.const import (
|
||||
ICON_EMPTY,
|
||||
ICON_LIGHTBULB,
|
||||
ICON_CURRENT_AC,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_HERTZ,
|
||||
UNIT_VOLT,
|
||||
@@ -91,10 +92,20 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT
|
||||
UNIT_WATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
),
|
||||
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT
|
||||
UNIT_WATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
),
|
||||
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
|
||||
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
|
||||
|
||||
@@ -312,8 +312,12 @@ void Climate::add_on_state_callback(std::function<void()> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
// Random 32bit value; If this changes existing restore preferences are invalidated
|
||||
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
|
||||
|
||||
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
|
||||
this->rtc_ = global_preferences.make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash());
|
||||
this->rtc_ =
|
||||
global_preferences.make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash() ^ RESTORE_STATE_VERSION);
|
||||
ClimateDeviceRestoreState recovered{};
|
||||
if (!this->rtc_.load(&recovered))
|
||||
return {};
|
||||
|
||||
@@ -120,6 +120,7 @@ class ClimateCall {
|
||||
};
|
||||
|
||||
/// Struct used to save the state of the climate device in restore memory.
|
||||
/// Make sure to update RESTORE_STATE_VERSION when changing the struct entries.
|
||||
struct ClimateDeviceRestoreState {
|
||||
ClimateMode mode;
|
||||
bool uses_custom_fan_mode{false};
|
||||
|
||||
@@ -31,7 +31,9 @@ DisplayPageShowPrevAction = display_ns.class_(
|
||||
DisplayIsDisplayingPageCondition = display_ns.class_(
|
||||
"DisplayIsDisplayingPageCondition", automation.Condition
|
||||
)
|
||||
DisplayOnPageChangeTrigger = display_ns.class_("DisplayOnPageChangeTrigger")
|
||||
DisplayOnPageChangeTrigger = display_ns.class_(
|
||||
"DisplayOnPageChangeTrigger", automation.Trigger
|
||||
)
|
||||
|
||||
CONF_ON_PAGE_CHANGE = "on_page_change"
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ void DutyCycleSensor::setup() {
|
||||
this->store_.pin = this->pin_->to_isr();
|
||||
this->store_.last_level = this->pin_->digital_read();
|
||||
this->last_update_ = micros();
|
||||
this->store_.last_interrupt = micros();
|
||||
|
||||
this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, CHANGE);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent {
|
||||
protected:
|
||||
GPIOPin *pin_;
|
||||
|
||||
DutyCycleSensorStore store_;
|
||||
DutyCycleSensorStore store_{};
|
||||
uint32_t last_update_;
|
||||
};
|
||||
|
||||
|
||||
@@ -25,6 +25,13 @@ static const char *const TAG = "ethernet";
|
||||
|
||||
EthernetComponent *global_eth_component;
|
||||
|
||||
#define ESPHL_ERROR_CHECK(err, message) \
|
||||
if (err != ESP_OK) { \
|
||||
ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \
|
||||
this->mark_failed(); \
|
||||
return; \
|
||||
}
|
||||
|
||||
EthernetComponent::EthernetComponent() { global_eth_component = this; }
|
||||
void EthernetComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up Ethernet...");
|
||||
@@ -36,103 +43,6 @@ void EthernetComponent::setup() {
|
||||
this->power_pin_->setup();
|
||||
}
|
||||
|
||||
this->start_connect_();
|
||||
|
||||
#ifdef USE_MDNS
|
||||
network_setup_mdns();
|
||||
#endif
|
||||
}
|
||||
void EthernetComponent::loop() {
|
||||
const uint32_t now = millis();
|
||||
if (!this->connected_ && !this->last_connected_ && now - this->connect_begin_ > 15000) {
|
||||
ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting...");
|
||||
this->start_connect_();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->connected_ == this->last_connected_)
|
||||
// nothing changed
|
||||
return;
|
||||
|
||||
if (this->connected_) {
|
||||
// connection established
|
||||
ESP_LOGI(TAG, "Connected via Ethernet!");
|
||||
this->dump_connect_params_();
|
||||
this->status_clear_warning();
|
||||
} else {
|
||||
// connection lost
|
||||
ESP_LOGW(TAG, "Connection via Ethernet lost! Re-connecting...");
|
||||
this->start_connect_();
|
||||
}
|
||||
|
||||
this->last_connected_ = this->connected_;
|
||||
|
||||
network_tick_mdns();
|
||||
}
|
||||
void EthernetComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Ethernet:");
|
||||
this->dump_connect_params_();
|
||||
LOG_PIN(" Power Pin: ", this->power_pin_);
|
||||
ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_);
|
||||
ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Type: %s", this->type_ == ETHERNET_TYPE_LAN8720 ? "LAN8720" : "TLK110");
|
||||
}
|
||||
float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; }
|
||||
bool EthernetComponent::can_proceed() { return this->is_connected(); }
|
||||
IPAddress EthernetComponent::get_ip_address() {
|
||||
tcpip_adapter_ip_info_t ip;
|
||||
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip);
|
||||
return IPAddress(ip.ip.addr);
|
||||
}
|
||||
|
||||
void EthernetComponent::on_wifi_event_(system_event_id_t event, system_event_info_t info) {
|
||||
const char *event_name;
|
||||
|
||||
switch (event) {
|
||||
case SYSTEM_EVENT_ETH_START:
|
||||
event_name = "ETH started";
|
||||
break;
|
||||
case SYSTEM_EVENT_ETH_STOP:
|
||||
event_name = "ETH stopped";
|
||||
this->connected_ = false;
|
||||
break;
|
||||
case SYSTEM_EVENT_ETH_CONNECTED:
|
||||
event_name = "ETH connected";
|
||||
break;
|
||||
case SYSTEM_EVENT_ETH_DISCONNECTED:
|
||||
event_name = "ETH disconnected";
|
||||
this->connected_ = false;
|
||||
break;
|
||||
case SYSTEM_EVENT_ETH_GOT_IP:
|
||||
event_name = "ETH Got IP";
|
||||
this->connected_ = true;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event);
|
||||
}
|
||||
|
||||
#define ESPHL_ERROR_CHECK(err, message) \
|
||||
if (err != ESP_OK) { \
|
||||
ESP_LOGE(TAG, message ": %d", err); \
|
||||
this->mark_failed(); \
|
||||
return; \
|
||||
}
|
||||
|
||||
void EthernetComponent::start_connect_() {
|
||||
this->connect_begin_ = millis();
|
||||
this->status_set_warning();
|
||||
|
||||
esp_err_t err;
|
||||
if (this->initialized_) {
|
||||
// already initialized
|
||||
err = esp_eth_enable();
|
||||
ESPHL_ERROR_CHECK(err, "ETH enable error");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this->type_) {
|
||||
case ETHERNET_TYPE_LAN8720: {
|
||||
memcpy(&this->eth_config, &phy_lan8720_default_ethernet_config, sizeof(eth_config_t));
|
||||
@@ -160,16 +70,111 @@ void EthernetComponent::start_connect_() {
|
||||
|
||||
tcpipInit();
|
||||
|
||||
esp_err_t err;
|
||||
err = esp_eth_init(&this->eth_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "ETH init error: %d", err);
|
||||
this->mark_failed();
|
||||
return;
|
||||
ESPHL_ERROR_CHECK(err, "ETH init error");
|
||||
err = esp_eth_enable();
|
||||
ESPHL_ERROR_CHECK(err, "ETH enable error");
|
||||
|
||||
#ifdef USE_MDNS
|
||||
network_setup_mdns();
|
||||
#endif
|
||||
}
|
||||
void EthernetComponent::loop() {
|
||||
const uint32_t now = millis();
|
||||
|
||||
switch (this->state_) {
|
||||
case EthernetComponentState::STOPPED:
|
||||
if (this->started_) {
|
||||
ESP_LOGI(TAG, "Starting ethernet connection");
|
||||
this->state_ = EthernetComponentState::CONNECTING;
|
||||
this->start_connect_();
|
||||
}
|
||||
break;
|
||||
case EthernetComponentState::CONNECTING:
|
||||
if (!this->started_) {
|
||||
ESP_LOGI(TAG, "Stopped ethernet connection");
|
||||
this->state_ = EthernetComponentState::STOPPED;
|
||||
} else if (this->connected_) {
|
||||
// connection established
|
||||
ESP_LOGI(TAG, "Connected via Ethernet!");
|
||||
this->state_ = EthernetComponentState::CONNECTED;
|
||||
|
||||
this->dump_connect_params_();
|
||||
this->status_clear_warning();
|
||||
|
||||
network_tick_mdns();
|
||||
} else if (now - this->connect_begin_ > 15000) {
|
||||
ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting...");
|
||||
this->start_connect_();
|
||||
}
|
||||
break;
|
||||
case EthernetComponentState::CONNECTED:
|
||||
if (!this->started_) {
|
||||
ESP_LOGI(TAG, "Stopped ethernet connection");
|
||||
this->state_ = EthernetComponentState::STOPPED;
|
||||
} else if (!this->connected_) {
|
||||
ESP_LOGW(TAG, "Connection via Ethernet lost! Re-connecting...");
|
||||
this->state_ = EthernetComponentState::CONNECTING;
|
||||
this->start_connect_();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
void EthernetComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Ethernet:");
|
||||
this->dump_connect_params_();
|
||||
LOG_PIN(" Power Pin: ", this->power_pin_);
|
||||
ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_);
|
||||
ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Type: %s", this->type_ == ETHERNET_TYPE_LAN8720 ? "LAN8720" : "TLK110");
|
||||
}
|
||||
float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; }
|
||||
bool EthernetComponent::can_proceed() { return this->is_connected(); }
|
||||
IPAddress EthernetComponent::get_ip_address() {
|
||||
tcpip_adapter_ip_info_t ip;
|
||||
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip);
|
||||
return IPAddress(ip.ip.addr);
|
||||
}
|
||||
|
||||
void EthernetComponent::on_wifi_event_(system_event_id_t event, system_event_info_t info) {
|
||||
const char *event_name;
|
||||
|
||||
switch (event) {
|
||||
case SYSTEM_EVENT_ETH_START:
|
||||
event_name = "ETH started";
|
||||
this->started_ = true;
|
||||
break;
|
||||
case SYSTEM_EVENT_ETH_STOP:
|
||||
event_name = "ETH stopped";
|
||||
this->started_ = false;
|
||||
this->connected_ = false;
|
||||
break;
|
||||
case SYSTEM_EVENT_ETH_CONNECTED:
|
||||
event_name = "ETH connected";
|
||||
break;
|
||||
case SYSTEM_EVENT_ETH_DISCONNECTED:
|
||||
event_name = "ETH disconnected";
|
||||
this->connected_ = false;
|
||||
break;
|
||||
case SYSTEM_EVENT_ETH_GOT_IP:
|
||||
event_name = "ETH Got IP";
|
||||
this->connected_ = true;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
this->initialized_ = true;
|
||||
ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event);
|
||||
}
|
||||
|
||||
tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str());
|
||||
void EthernetComponent::start_connect_() {
|
||||
this->connect_begin_ = millis();
|
||||
this->status_set_warning();
|
||||
|
||||
esp_err_t err;
|
||||
err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str());
|
||||
ESPHL_ERROR_CHECK(err, "ETH set hostname error");
|
||||
|
||||
tcpip_adapter_ip_info_t info;
|
||||
if (this->manual_ip_.has_value()) {
|
||||
@@ -220,7 +225,7 @@ void EthernetComponent::eth_phy_power_enable_(bool enable) {
|
||||
delay(1);
|
||||
global_eth_component->orig_power_enable_fun_(enable);
|
||||
}
|
||||
bool EthernetComponent::is_connected() { return this->connected_ && this->last_connected_; }
|
||||
bool EthernetComponent::is_connected() { return this->state_ == EthernetComponentState::CONNECTED; }
|
||||
void EthernetComponent::dump_connect_params_() {
|
||||
tcpip_adapter_ip_info_t ip;
|
||||
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip);
|
||||
|
||||
@@ -26,6 +26,12 @@ struct ManualIP {
|
||||
IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default.
|
||||
};
|
||||
|
||||
enum class EthernetComponentState {
|
||||
STOPPED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
};
|
||||
|
||||
class EthernetComponent : public Component {
|
||||
public:
|
||||
EthernetComponent();
|
||||
@@ -65,9 +71,9 @@ class EthernetComponent : public Component {
|
||||
eth_clock_mode_t clk_mode_{ETH_CLOCK_GPIO0_IN};
|
||||
optional<ManualIP> manual_ip_{};
|
||||
|
||||
bool initialized_{false};
|
||||
bool started_{false};
|
||||
bool connected_{false};
|
||||
bool last_connected_{false};
|
||||
EthernetComponentState state_{EthernetComponentState::STOPPED};
|
||||
uint32_t connect_begin_;
|
||||
eth_config_t eth_config;
|
||||
eth_phy_power_enable_func orig_power_enable_fun_;
|
||||
|
||||
@@ -109,7 +109,15 @@ def _compute_destination_path(key: str) -> Path:
|
||||
return base_dir / h.hexdigest()[:8]
|
||||
|
||||
|
||||
def _handle_git_response(ret):
|
||||
def _run_git_command(cmd, cwd=None):
|
||||
try:
|
||||
ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False)
|
||||
except FileNotFoundError as err:
|
||||
raise cv.Invalid(
|
||||
"git is not installed but required for external_components.\n"
|
||||
"Please see https://git-scm.com/book/en/v2/Getting-Started-Installing-Git for installing git"
|
||||
) from err
|
||||
|
||||
if ret.returncode != 0 and ret.stderr:
|
||||
err_str = ret.stderr.decode("utf-8")
|
||||
lines = [x.strip() for x in err_str.splitlines()]
|
||||
@@ -118,46 +126,61 @@ def _handle_git_response(ret):
|
||||
raise cv.Invalid(err_str)
|
||||
|
||||
|
||||
def _process_git_config(config: dict, refresh) -> str:
|
||||
key = f"{config[CONF_URL]}@{config.get(CONF_REF)}"
|
||||
repo_dir = _compute_destination_path(key)
|
||||
if not repo_dir.is_dir():
|
||||
_LOGGER.info("Cloning %s", key)
|
||||
_LOGGER.debug("Location: %s", repo_dir)
|
||||
cmd = ["git", "clone", "--depth=1"]
|
||||
if CONF_REF in config:
|
||||
cmd += ["--branch", config[CONF_REF]]
|
||||
cmd += ["--", config[CONF_URL], str(repo_dir)]
|
||||
_run_git_command(cmd)
|
||||
|
||||
else:
|
||||
# Check refresh needed
|
||||
file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD")
|
||||
# On first clone, FETCH_HEAD does not exists
|
||||
if not file_timestamp.exists():
|
||||
file_timestamp = Path(repo_dir / ".git" / "HEAD")
|
||||
age = datetime.datetime.now() - datetime.datetime.fromtimestamp(
|
||||
file_timestamp.stat().st_mtime
|
||||
)
|
||||
if age.total_seconds() > refresh.total_seconds:
|
||||
_LOGGER.info("Updating %s", key)
|
||||
_LOGGER.debug("Location: %s", repo_dir)
|
||||
# Stash local changes (if any)
|
||||
_run_git_command(
|
||||
["git", "stash", "push", "--include-untracked"], str(repo_dir)
|
||||
)
|
||||
# Fetch remote ref
|
||||
cmd = ["git", "fetch", "--", "origin"]
|
||||
if CONF_REF in config:
|
||||
cmd.append(config[CONF_REF])
|
||||
_run_git_command(cmd, str(repo_dir))
|
||||
# Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch)
|
||||
_run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir))
|
||||
|
||||
if (repo_dir / "esphome" / "components").is_dir():
|
||||
components_dir = repo_dir / "esphome" / "components"
|
||||
elif (repo_dir / "components").is_dir():
|
||||
components_dir = repo_dir / "components"
|
||||
else:
|
||||
raise cv.Invalid(
|
||||
"Could not find components folder for source. Please check the source contains a 'components' or 'esphome/components' folder"
|
||||
)
|
||||
|
||||
return components_dir
|
||||
|
||||
|
||||
def _process_single_config(config: dict):
|
||||
conf = config[CONF_SOURCE]
|
||||
if conf[CONF_TYPE] == TYPE_GIT:
|
||||
key = f"{conf[CONF_URL]}@{conf.get(CONF_REF)}"
|
||||
repo_dir = _compute_destination_path(key)
|
||||
if not repo_dir.is_dir():
|
||||
cmd = ["git", "clone", "--depth=1"]
|
||||
if CONF_REF in conf:
|
||||
cmd += ["--branch", conf[CONF_REF]]
|
||||
cmd += [conf[CONF_URL], str(repo_dir)]
|
||||
ret = subprocess.run(cmd, capture_output=True, check=False)
|
||||
_handle_git_response(ret)
|
||||
|
||||
else:
|
||||
# Check refresh needed
|
||||
file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD")
|
||||
# On first clone, FETCH_HEAD does not exists
|
||||
if not file_timestamp.exists():
|
||||
file_timestamp = Path(repo_dir / ".git" / "HEAD")
|
||||
age = datetime.datetime.now() - datetime.datetime.fromtimestamp(
|
||||
file_timestamp.stat().st_mtime
|
||||
with cv.prepend_path([CONF_SOURCE]):
|
||||
components_dir = _process_git_config(
|
||||
config[CONF_SOURCE], config[CONF_REFRESH]
|
||||
)
|
||||
if age.seconds > config[CONF_REFRESH].total_seconds:
|
||||
_LOGGER.info("Executing git pull %s", key)
|
||||
cmd = ["git", "pull"]
|
||||
ret = subprocess.run(
|
||||
cmd, cwd=repo_dir, capture_output=True, check=False
|
||||
)
|
||||
_handle_git_response(ret)
|
||||
|
||||
if (repo_dir / "esphome" / "components").is_dir():
|
||||
components_dir = repo_dir / "esphome" / "components"
|
||||
elif (repo_dir / "components").is_dir():
|
||||
components_dir = repo_dir / "components"
|
||||
else:
|
||||
raise cv.Invalid(
|
||||
"Could not find components folder for source. Please check the source contains a 'components' or 'esphome/components' folder",
|
||||
[CONF_SOURCE],
|
||||
)
|
||||
|
||||
elif conf[CONF_TYPE] == TYPE_LOCAL:
|
||||
components_dir = Path(CORE.relative_config_path(conf[CONF_PATH]))
|
||||
else:
|
||||
|
||||
@@ -15,6 +15,7 @@ from esphome.const import (
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_CURRENT_AC,
|
||||
ICON_EMPTY,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_NONE,
|
||||
UNIT_AMPERE,
|
||||
@@ -121,14 +122,16 @@ CONFIG_SCHEMA = (
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_NONE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
),
|
||||
cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema(
|
||||
UNIT_KILOWATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
0,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_NONE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
),
|
||||
cv.Optional(CONF_TOTAL_GENERATION_TIME): sensor.sensor_schema(
|
||||
UNIT_HOURS,
|
||||
|
||||
@@ -45,6 +45,7 @@ void HLW8012Component::dump_config() {
|
||||
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_)
|
||||
LOG_SENSOR(" ", "Current", this->current_sensor_)
|
||||
LOG_SENSOR(" ", "Power", this->power_sensor_)
|
||||
LOG_SENSOR(" ", "Energy", this->energy_sensor_)
|
||||
}
|
||||
float HLW8012Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void HLW8012Component::update() {
|
||||
|
||||
@@ -19,8 +19,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_NONE,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_WATT,
|
||||
@@ -67,7 +67,12 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||
),
|
||||
cv.Optional(CONF_ENERGY): sensor.sensor_schema(
|
||||
UNIT_WATT_HOURS, ICON_EMPTY, 1, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE
|
||||
UNIT_WATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
1,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance,
|
||||
cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float,
|
||||
|
||||
@@ -68,10 +68,7 @@ void AddressableLight::write_state(LightState *state) {
|
||||
|
||||
// our transition will handle brightness, disable brightness in correction.
|
||||
this->correction_.set_local_brightness(255);
|
||||
uint8_t orig_w = target_color.w;
|
||||
target_color *= static_cast<uint8_t>(roundf(end_values.get_brightness() * end_values.get_state() * 255.0f));
|
||||
// w is not scaled by brightness
|
||||
target_color.w = orig_w;
|
||||
|
||||
float denom = (1.0f - new_smoothed);
|
||||
float alpha = denom == 0.0f ? 0.0f : (new_smoothed - prev_smoothed) / denom;
|
||||
|
||||
@@ -100,7 +100,7 @@ bool MideaAC::allow_preset(climate::ClimatePreset preset) const {
|
||||
ESP_LOGD(TAG, "BOOST preset is only available in HEAT or COOL mode");
|
||||
}
|
||||
break;
|
||||
case climate::CLIMATE_PRESET_HOME:
|
||||
case climate::CLIMATE_PRESET_NONE:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
@@ -191,7 +191,7 @@ climate::ClimateTraits MideaAC::traits() {
|
||||
if (traits_swing_both_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH);
|
||||
traits.set_supported_presets({
|
||||
climate::CLIMATE_PRESET_HOME,
|
||||
climate::CLIMATE_PRESET_NONE,
|
||||
});
|
||||
if (traits_preset_eco_)
|
||||
traits.add_supported_preset(climate::CLIMATE_PRESET_ECO);
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/midea_dongle/midea_dongle.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/midea_dongle/midea_dongle.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "midea_frame.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -86,18 +86,17 @@ void PropertiesFrame::set_mode(climate::ClimateMode mode) {
|
||||
}
|
||||
|
||||
optional<climate::ClimatePreset> PropertiesFrame::get_preset() const {
|
||||
if (this->get_eco_mode()) {
|
||||
if (this->get_eco_mode())
|
||||
return climate::CLIMATE_PRESET_ECO;
|
||||
} else if (this->get_sleep_mode()) {
|
||||
if (this->get_sleep_mode())
|
||||
return climate::CLIMATE_PRESET_SLEEP;
|
||||
} else if (this->get_turbo_mode()) {
|
||||
if (this->get_turbo_mode())
|
||||
return climate::CLIMATE_PRESET_BOOST;
|
||||
} else {
|
||||
return climate::CLIMATE_PRESET_HOME;
|
||||
}
|
||||
return climate::CLIMATE_PRESET_NONE;
|
||||
}
|
||||
|
||||
void PropertiesFrame::set_preset(climate::ClimatePreset preset) {
|
||||
this->clear_presets();
|
||||
switch (preset) {
|
||||
case climate::CLIMATE_PRESET_ECO:
|
||||
this->set_eco_mode(true);
|
||||
@@ -113,14 +112,21 @@ void PropertiesFrame::set_preset(climate::ClimatePreset preset) {
|
||||
}
|
||||
}
|
||||
|
||||
void PropertiesFrame::clear_presets() {
|
||||
this->set_eco_mode(false);
|
||||
this->set_sleep_mode(false);
|
||||
this->set_turbo_mode(false);
|
||||
this->set_freeze_protection_mode(false);
|
||||
}
|
||||
|
||||
bool PropertiesFrame::is_custom_preset() const { return this->get_freeze_protection_mode(); }
|
||||
|
||||
const std::string &PropertiesFrame::get_custom_preset() const { return midea_ac::MIDEA_FREEZE_PROTECTION_PRESET; };
|
||||
|
||||
void PropertiesFrame::set_custom_preset(const std::string &preset) {
|
||||
if (preset == MIDEA_FREEZE_PROTECTION_PRESET) {
|
||||
this->clear_presets();
|
||||
if (preset == MIDEA_FREEZE_PROTECTION_PRESET)
|
||||
this->set_freeze_protection_mode(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool PropertiesFrame::is_custom_fan_mode() const {
|
||||
|
||||
@@ -102,8 +102,11 @@ class PropertiesFrame : public midea_dongle::BaseFrame {
|
||||
void set_sleep_mode(bool state) { this->set_bytemask_(20, 0x01, state); }
|
||||
|
||||
/* TURBO MODE */
|
||||
bool get_turbo_mode() const { return this->pbuf_[18] & 0x20; }
|
||||
void set_turbo_mode(bool state) { this->set_bytemask_(18, 0x20, state); }
|
||||
bool get_turbo_mode() const { return this->pbuf_[18] & 0x20 || this->pbuf_[20] & 0x02; }
|
||||
void set_turbo_mode(bool state) {
|
||||
this->set_bytemask_(18, 0x20, state);
|
||||
this->set_bytemask_(20, 0x02, state);
|
||||
}
|
||||
|
||||
/* FREEZE PROTECTION */
|
||||
bool get_freeze_protection_mode() const { return this->pbuf_[31] & 0x80; }
|
||||
@@ -112,6 +115,7 @@ class PropertiesFrame : public midea_dongle::BaseFrame {
|
||||
/* PRESET */
|
||||
optional<climate::ClimatePreset> get_preset() const;
|
||||
void set_preset(climate::ClimatePreset preset);
|
||||
void clear_presets();
|
||||
|
||||
bool is_custom_preset() const;
|
||||
const std::string &get_custom_preset() const;
|
||||
|
||||
@@ -72,7 +72,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
|
||||
root["act_t"] = this->get_action_state_topic();
|
||||
}
|
||||
|
||||
if (traits.get_supports_fan_modes()) {
|
||||
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
|
||||
// fan_mode_command_topic
|
||||
root["fan_mode_cmd_t"] = this->get_fan_mode_command_topic();
|
||||
// fan_mode_state_topic
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
|
||||
#ifdef USE_DEEP_SLEEP
|
||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
@@ -20,7 +16,7 @@ void MQTTNumberComponent::setup() {
|
||||
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) {
|
||||
auto val = parse_float(state);
|
||||
if (!val.has_value()) {
|
||||
ESP_LOGE(TAG, "Can't convert '%s' to number!", state.c_str());
|
||||
ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str());
|
||||
return;
|
||||
}
|
||||
auto call = this->number_->make_call();
|
||||
@@ -39,8 +35,15 @@ std::string MQTTNumberComponent::component_type() const { return "number"; }
|
||||
|
||||
std::string MQTTNumberComponent::friendly_name() const { return this->number_->get_name(); }
|
||||
void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
|
||||
if (!this->number_->get_icon().empty())
|
||||
root["icon"] = this->number_->get_icon();
|
||||
const auto &traits = number_->traits;
|
||||
// https://www.home-assistant.io/integrations/number.mqtt/
|
||||
if (!traits.get_icon().empty())
|
||||
root["icon"] = traits.get_icon();
|
||||
root["min"] = traits.get_min_value();
|
||||
root["max"] = traits.get_max_value();
|
||||
root["step"] = traits.get_step();
|
||||
|
||||
config.command_topic = true;
|
||||
}
|
||||
bool MQTTNumberComponent::send_initial_state() {
|
||||
if (this->number_->has_state()) {
|
||||
@@ -51,8 +54,9 @@ bool MQTTNumberComponent::send_initial_state() {
|
||||
}
|
||||
bool MQTTNumberComponent::is_internal() { return this->number_->is_internal(); }
|
||||
bool MQTTNumberComponent::publish_state(float value) {
|
||||
int8_t accuracy = this->number_->get_accuracy_decimals();
|
||||
return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy));
|
||||
char buffer[64];
|
||||
snprintf(buffer, sizeof(buffer), "%f", value);
|
||||
return this->publish(this->get_state_topic_(), buffer);
|
||||
}
|
||||
|
||||
} // namespace mqtt
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import uart
|
||||
|
||||
nextion_ns = cg.esphome_ns.namespace("nextion")
|
||||
Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice)
|
||||
nextion_ref = Nextion.operator("ref")
|
||||
|
||||
CONF_NEXTION_ID = "nextion_id"
|
||||
|
||||
30
esphome/components/nextion/automation.h
Normal file
30
esphome/components/nextion/automation.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include "esphome/core/automation.h"
|
||||
#include "nextion.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
|
||||
class SetupTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit SetupTrigger(Nextion *nextion) {
|
||||
nextion->add_setup_state_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
class SleepTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit SleepTrigger(Nextion *nextion) {
|
||||
nextion->add_sleep_state_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
class WakeTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit WakeTrigger(Nextion *nextion) {
|
||||
nextion->add_wake_state_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
126
esphome/components/nextion/base_component.py
Normal file
126
esphome/components/nextion/base_component.py
Normal file
@@ -0,0 +1,126 @@
|
||||
from string import ascii_letters, digits
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import color
|
||||
|
||||
from . import CONF_NEXTION_ID
|
||||
from . import Nextion
|
||||
|
||||
CONF_VARIABLE_NAME = "variable_name"
|
||||
CONF_COMPONENT_NAME = "component_name"
|
||||
CONF_WAVE_CHANNEL_ID = "wave_channel_id"
|
||||
CONF_WAVE_MAX_VALUE = "wave_max_value"
|
||||
CONF_PRECISION = "precision"
|
||||
CONF_WAVEFORM_SEND_LAST_VALUE = "waveform_send_last_value"
|
||||
CONF_TFT_URL = "tft_url"
|
||||
CONF_ON_SLEEP = "on_sleep"
|
||||
CONF_ON_WAKE = "on_wake"
|
||||
CONF_ON_SETUP = "on_setup"
|
||||
CONF_TOUCH_SLEEP_TIMEOUT = "touch_sleep_timeout"
|
||||
CONF_WAKE_UP_PAGE = "wake_up_page"
|
||||
CONF_AUTO_WAKE_ON_TOUCH = "auto_wake_on_touch"
|
||||
CONF_WAVE_MAX_LENGTH = "wave_max_length"
|
||||
CONF_BACKGROUND_COLOR = "background_color"
|
||||
CONF_BACKGROUND_PRESSED_COLOR = "background_pressed_color"
|
||||
CONF_FOREGROUND_COLOR = "foreground_color"
|
||||
CONF_FOREGROUND_PRESSED_COLOR = "foreground_pressed_color"
|
||||
CONF_FONT_ID = "font_id"
|
||||
CONF_VISIBLE = "visible"
|
||||
|
||||
|
||||
def NextionName(value):
|
||||
valid_chars = ascii_letters + digits + "."
|
||||
if not isinstance(value, str) or len(value) > 29:
|
||||
raise cv.Invalid("Must be a string less than 29 characters")
|
||||
|
||||
for char in value:
|
||||
if char not in valid_chars:
|
||||
raise cv.Invalid(
|
||||
"Must only consist of upper/lowercase characters, numbers and the period '.'. The character '{}' cannot be used.".format(
|
||||
char
|
||||
)
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_BASE_COMPONENT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion),
|
||||
cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color),
|
||||
cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color),
|
||||
cv.Optional(CONF_VISIBLE, default=True): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
CONFIG_TEXT_COMPONENT_SCHEMA = CONFIG_BASE_COMPONENT_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_COMPONENT_NAME): NextionName,
|
||||
cv.Optional(CONF_FONT_ID): cv.int_range(min=0, max=255),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
CONFIG_BINARY_SENSOR_SCHEMA = CONFIG_BASE_COMPONENT_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_COMPONENT_NAME): NextionName,
|
||||
cv.Optional(CONF_VARIABLE_NAME): NextionName,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
CONFIG_SENSOR_COMPONENT_SCHEMA = CONFIG_BINARY_SENSOR_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_FONT_ID): cv.int_range(min=0, max=255),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SWITCH_COMPONENT_SCHEMA = CONFIG_SENSOR_COMPONENT_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_FOREGROUND_PRESSED_COLOR): cv.use_id(color),
|
||||
cv.Optional(CONF_BACKGROUND_PRESSED_COLOR): cv.use_id(color),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def setup_component_core_(var, config, arg):
|
||||
|
||||
if CONF_VARIABLE_NAME in config:
|
||||
cg.add(var.set_variable_name(config[CONF_VARIABLE_NAME]))
|
||||
elif CONF_COMPONENT_NAME in config:
|
||||
cg.add(
|
||||
var.set_variable_name(
|
||||
config[CONF_COMPONENT_NAME],
|
||||
config[CONF_COMPONENT_NAME] + arg,
|
||||
)
|
||||
)
|
||||
|
||||
if CONF_BACKGROUND_COLOR in config:
|
||||
color_component = await cg.get_variable(config[CONF_BACKGROUND_COLOR])
|
||||
cg.add(var.set_background_color(color_component))
|
||||
|
||||
if CONF_BACKGROUND_PRESSED_COLOR in config:
|
||||
color_component = await cg.get_variable(config[CONF_BACKGROUND_PRESSED_COLOR])
|
||||
cg.add(var.set_background_pressed_color(color_component))
|
||||
|
||||
if CONF_FOREGROUND_COLOR in config:
|
||||
color_component = await cg.get_variable(config[CONF_FOREGROUND_COLOR])
|
||||
cg.add(var.set_foreground_color(color_component))
|
||||
|
||||
if CONF_FOREGROUND_PRESSED_COLOR in config:
|
||||
color_component = await cg.get_variable(config[CONF_FOREGROUND_PRESSED_COLOR])
|
||||
cg.add(var.set_foreground_pressed_color(color_component))
|
||||
|
||||
if CONF_FONT_ID in config:
|
||||
cg.add(var.set_font_id(config[CONF_FONT_ID]))
|
||||
|
||||
if CONF_VISIBLE in config:
|
||||
cg.add(var.set_visible(config[CONF_VISIBLE]))
|
||||
@@ -1,34 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID
|
||||
from . import nextion_ns
|
||||
from .display import Nextion
|
||||
|
||||
DEPENDENCIES = ["display"]
|
||||
|
||||
CONF_NEXTION_ID = "nextion_id"
|
||||
|
||||
NextionTouchComponent = nextion_ns.class_(
|
||||
"NextionTouchComponent", binary_sensor.BinarySensor
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(NextionTouchComponent),
|
||||
cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion),
|
||||
cv.Required(CONF_PAGE_ID): cv.uint8_t,
|
||||
cv.Required(CONF_COMPONENT_ID): cv.uint8_t,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await binary_sensor.register_binary_sensor(var, config)
|
||||
|
||||
hub = await cg.get_variable(config[CONF_NEXTION_ID])
|
||||
cg.add(hub.register_touch_component(var))
|
||||
|
||||
cg.add(var.set_component_id(config[CONF_COMPONENT_ID]))
|
||||
cg.add(var.set_page_id(config[CONF_PAGE_ID]))
|
||||
54
esphome/components/nextion/binary_sensor/__init__.py
Normal file
54
esphome/components/nextion/binary_sensor/__init__.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
|
||||
from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID
|
||||
from .. import nextion_ns, CONF_NEXTION_ID
|
||||
|
||||
|
||||
from ..base_component import (
|
||||
setup_component_core_,
|
||||
CONFIG_BINARY_SENSOR_SCHEMA,
|
||||
CONF_VARIABLE_NAME,
|
||||
CONF_COMPONENT_NAME,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@senexcrenshaw"]
|
||||
|
||||
NextionBinarySensor = nextion_ns.class_(
|
||||
"NextionBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(NextionBinarySensor),
|
||||
cv.Optional(CONF_PAGE_ID): cv.uint8_t,
|
||||
cv.Optional(CONF_COMPONENT_ID): cv.uint8_t,
|
||||
}
|
||||
)
|
||||
.extend(CONFIG_BINARY_SENSOR_SCHEMA)
|
||||
.extend(cv.polling_component_schema("never")),
|
||||
cv.has_at_least_one_key(
|
||||
CONF_PAGE_ID,
|
||||
CONF_COMPONENT_ID,
|
||||
CONF_COMPONENT_NAME,
|
||||
CONF_VARIABLE_NAME,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_NEXTION_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], hub)
|
||||
await binary_sensor.register_binary_sensor(var, config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if config.keys() >= {CONF_PAGE_ID, CONF_COMPONENT_ID}:
|
||||
cg.add(hub.register_touch_component(var))
|
||||
cg.add(var.set_component_id(config[CONF_COMPONENT_ID]))
|
||||
cg.add(var.set_page_id(config[CONF_PAGE_ID]))
|
||||
|
||||
if CONF_COMPONENT_NAME in config or CONF_VARIABLE_NAME in config:
|
||||
await setup_component_core_(var, config, ".val")
|
||||
cg.add(hub.register_binarysensor_component(var))
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "nextion_binarysensor.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
|
||||
static const char *const TAG = "nextion_binarysensor";
|
||||
|
||||
void NextionBinarySensor::process_bool(const std::string &variable_name, bool state) {
|
||||
if (!this->nextion_->is_setup())
|
||||
return;
|
||||
|
||||
if (this->variable_name_.empty()) // This is a touch component
|
||||
return;
|
||||
|
||||
if (this->variable_name_ == variable_name) {
|
||||
this->publish_state(state);
|
||||
ESP_LOGD(TAG, "Processed binarysensor \"%s\" state %s", variable_name.c_str(), state ? "ON" : "OFF");
|
||||
}
|
||||
}
|
||||
|
||||
void NextionBinarySensor::process_touch(uint8_t page_id, uint8_t component_id, bool state) {
|
||||
if (this->page_id_ == page_id && this->component_id_ == component_id) {
|
||||
this->publish_state(state);
|
||||
}
|
||||
}
|
||||
|
||||
void NextionBinarySensor::update() {
|
||||
if (!this->nextion_->is_setup())
|
||||
return;
|
||||
|
||||
if (this->variable_name_.empty()) // This is a touch component
|
||||
return;
|
||||
|
||||
this->nextion_->add_to_get_queue(this);
|
||||
}
|
||||
|
||||
void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nextion) {
|
||||
if (!this->nextion_->is_setup())
|
||||
return;
|
||||
|
||||
if (this->component_id_ == 0) // This is a legacy touch component
|
||||
return;
|
||||
|
||||
if (send_to_nextion) {
|
||||
if (this->nextion_->is_sleeping() || !this->visible_) {
|
||||
this->needs_to_send_update_ = true;
|
||||
} else {
|
||||
this->needs_to_send_update_ = false;
|
||||
this->nextion_->add_no_result_to_queue_with_set(this, (int) state);
|
||||
}
|
||||
}
|
||||
|
||||
if (publish) {
|
||||
this->publish_state(state);
|
||||
} else {
|
||||
this->state = state;
|
||||
this->has_state_ = true;
|
||||
}
|
||||
|
||||
this->update_component_settings();
|
||||
|
||||
ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %s", this->variable_name_.c_str(),
|
||||
ONOFF(this->variable_name_.c_str()));
|
||||
}
|
||||
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "../nextion_component.h"
|
||||
#include "../nextion_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
class NextionBinarySensor;
|
||||
|
||||
class NextionBinarySensor : public NextionComponent,
|
||||
public binary_sensor::BinarySensorInitiallyOff,
|
||||
public PollingComponent {
|
||||
public:
|
||||
NextionBinarySensor(NextionBase *nextion) { this->nextion_ = nextion; }
|
||||
|
||||
void update_component() override { this->update(); }
|
||||
void update() override;
|
||||
void send_state_to_nextion() override { this->set_state(this->state, false); };
|
||||
void process_bool(const std::string &variable_name, bool state) override;
|
||||
void process_touch(uint8_t page_id, uint8_t component_id, bool state) override;
|
||||
|
||||
// Set the components page id for Nextion Touch Component
|
||||
void set_page_id(uint8_t page_id) { page_id_ = page_id; }
|
||||
// Set the components component id for Nextion Touch Component
|
||||
void set_component_id(uint8_t component_id) { component_id_ = component_id; }
|
||||
|
||||
void set_state(bool state) override { this->set_state(state, true, true); }
|
||||
void set_state(bool state, bool publish) override { this->set_state(state, publish, true); }
|
||||
void set_state(bool state, bool publish, bool send_to_nextion) override;
|
||||
|
||||
NextionQueueType get_queue_type() override { return NextionQueueType::BINARY_SENSOR; }
|
||||
void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {}
|
||||
void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {
|
||||
this->set_state(state_value != 0, publish, send_to_nextion);
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t page_id_;
|
||||
};
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
@@ -1,20 +1,58 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import display, uart
|
||||
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_BRIGHTNESS
|
||||
from . import nextion_ns
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
|
||||
from . import Nextion, nextion_ns, nextion_ref
|
||||
from .base_component import (
|
||||
CONF_ON_SLEEP,
|
||||
CONF_ON_WAKE,
|
||||
CONF_ON_SETUP,
|
||||
CONF_TFT_URL,
|
||||
CONF_TOUCH_SLEEP_TIMEOUT,
|
||||
CONF_WAKE_UP_PAGE,
|
||||
CONF_AUTO_WAKE_ON_TOUCH,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@senexcrenshaw"]
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
AUTO_LOAD = ["binary_sensor"]
|
||||
AUTO_LOAD = ["binary_sensor", "switch", "sensor", "text_sensor"]
|
||||
|
||||
Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice)
|
||||
NextionRef = Nextion.operator("ref")
|
||||
SetupTrigger = nextion_ns.class_("SetupTrigger", automation.Trigger.template())
|
||||
SleepTrigger = nextion_ns.class_("SleepTrigger", automation.Trigger.template())
|
||||
WakeTrigger = nextion_ns.class_("WakeTrigger", automation.Trigger.template())
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
display.BASIC_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Nextion),
|
||||
cv.Optional(CONF_TFT_URL): cv.string,
|
||||
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
|
||||
cv.Optional(CONF_ON_SETUP): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SetupTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_SLEEP): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SleepTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_WAKE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WakeTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_TOUCH_SLEEP_TIMEOUT): cv.int_range(min=3, max=65535),
|
||||
cv.Optional(CONF_WAKE_UP_PAGE): cv.positive_int,
|
||||
cv.Optional(CONF_AUTO_WAKE_ON_TOUCH, default=True): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("5s"))
|
||||
@@ -31,8 +69,33 @@ async def to_code(config):
|
||||
cg.add(var.set_brightness(config[CONF_BRIGHTNESS]))
|
||||
if CONF_LAMBDA in config:
|
||||
lambda_ = await cg.process_lambda(
|
||||
config[CONF_LAMBDA], [(NextionRef, "it")], return_type=cg.void
|
||||
config[CONF_LAMBDA], [(nextion_ref, "it")], return_type=cg.void
|
||||
)
|
||||
cg.add(var.set_writer(lambda_))
|
||||
|
||||
if CONF_TFT_URL in config:
|
||||
cg.add_define("USE_TFT_UPLOAD")
|
||||
cg.add(var.set_tft_url(config[CONF_TFT_URL]))
|
||||
|
||||
if CONF_TOUCH_SLEEP_TIMEOUT in config:
|
||||
cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT]))
|
||||
|
||||
if CONF_WAKE_UP_PAGE in config:
|
||||
cg.add(var.set_wake_up_page_internal(config[CONF_WAKE_UP_PAGE]))
|
||||
|
||||
if CONF_AUTO_WAKE_ON_TOUCH in config:
|
||||
cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH]))
|
||||
|
||||
await display.register_display(var, config)
|
||||
|
||||
for conf in config.get(CONF_ON_SETUP, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_SLEEP, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_WAKE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include <deque>
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "nextion_base.h"
|
||||
#include "nextion_component.h"
|
||||
#include "esphome/components/display/display_color_utils.h"
|
||||
|
||||
#if defined(USE_ETHERNET) || defined(USE_WIFI)
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <HTTPClient.h>
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef USE_TIME
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
@@ -12,12 +24,14 @@
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
|
||||
class NextionTouchComponent;
|
||||
class Nextion;
|
||||
class NextionComponentBase;
|
||||
|
||||
using nextion_writer_t = std::function<void(Nextion &)>;
|
||||
|
||||
class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
static const std::string COMMAND_DELIMITER{static_cast<char>(255), static_cast<char>(255), static_cast<char>(255)};
|
||||
|
||||
class Nextion : public NextionBase, public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
/**
|
||||
* Set the text of a component to a static string.
|
||||
@@ -73,9 +87,20 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
*
|
||||
* This will change the image of the component `pic` to the image with ID `4`.
|
||||
*/
|
||||
void set_component_picture(const char *component, const char *picture) {
|
||||
this->send_command_printf("%s.val=%s", component, picture);
|
||||
}
|
||||
void set_component_picture(const char *component, const char *picture);
|
||||
/**
|
||||
* Set the background color of a component.
|
||||
* @param component The component name.
|
||||
* @param color The color (as a uint32_t).
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_background_color("button", 0xFF0000);
|
||||
* ```
|
||||
*
|
||||
* This will change the background color of the component `button` to red.
|
||||
*/
|
||||
void set_component_background_color(const char *component, uint32_t color);
|
||||
/**
|
||||
* Set the background color of a component.
|
||||
* @param component The component name.
|
||||
@@ -83,7 +108,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_background_color("button", "17013");
|
||||
* it.set_component_background_color("button", "RED");
|
||||
* ```
|
||||
*
|
||||
* This will change the background color of the component `button` to blue.
|
||||
@@ -91,6 +116,33 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
* Nextion HMI colors.
|
||||
*/
|
||||
void set_component_background_color(const char *component, const char *color);
|
||||
/**
|
||||
* Set the background color of a component.
|
||||
* @param component The component name.
|
||||
* @param color The color (as Color).
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_background_color("button", color);
|
||||
* ```
|
||||
*
|
||||
* This will change the background color of the component `button` to what color contains.
|
||||
*/
|
||||
void set_component_background_color(const char *component, Color color) override;
|
||||
/**
|
||||
* Set the pressed background color of a component.
|
||||
* @param component The component name.
|
||||
* @param color The color (as a int).
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_pressed_background_color("button", 0xFF0000 );
|
||||
* ```
|
||||
*
|
||||
* This will change the pressed background color of the component `button` to red. This is the background color that
|
||||
* is shown when the component is pressed.
|
||||
*/
|
||||
void set_component_pressed_background_color(const char *component, uint32_t color);
|
||||
/**
|
||||
* Set the pressed background color of a component.
|
||||
* @param component The component name.
|
||||
@@ -98,7 +150,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_pressed_background_color("button", "17013");
|
||||
* it.set_component_pressed_background_color("button", "RED");
|
||||
* ```
|
||||
*
|
||||
* This will change the pressed background color of the component `button` to blue. This is the background color that
|
||||
@@ -107,6 +159,63 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
* colors.
|
||||
*/
|
||||
void set_component_pressed_background_color(const char *component, const char *color);
|
||||
/**
|
||||
* Set the pressed background color of a component.
|
||||
* @param component The component name.
|
||||
* @param color The color (as Color).
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_pressed_background_color("button", color);
|
||||
* ```
|
||||
*
|
||||
* This will change the pressed background color of the component `button` to blue. This is the background color that
|
||||
* is shown when the component is pressed. Use this [color
|
||||
* picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI
|
||||
* colors.
|
||||
*/
|
||||
void set_component_pressed_background_color(const char *component, Color color) override;
|
||||
|
||||
/**
|
||||
* Set the picture id of a component.
|
||||
* @param component The component name.
|
||||
* @param pic_id The picture ID.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_pic("textview", 1);
|
||||
* ```
|
||||
*
|
||||
* This will change the picture id of the component `textview`.
|
||||
*/
|
||||
void set_component_pic(const char *component, uint8_t pic_id);
|
||||
/**
|
||||
* Set the background picture id of component.
|
||||
* @param component The component name.
|
||||
* @param pic_id The picture ID.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_picc("textview", 1);
|
||||
* ```
|
||||
*
|
||||
* This will change the background picture id of the component `textview`.
|
||||
*/
|
||||
void set_component_picc(const char *component, uint8_t pic_id);
|
||||
|
||||
/**
|
||||
* Set the font color of a component.
|
||||
* @param component The component name.
|
||||
* @param color The color (as a uint32_t ).
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_font_color("textview", 0xFF0000);
|
||||
* ```
|
||||
*
|
||||
* This will change the font color of the component `textview` to a red color.
|
||||
*/
|
||||
void set_component_font_color(const char *component, uint32_t color);
|
||||
/**
|
||||
* Set the font color of a component.
|
||||
* @param component The component name.
|
||||
@@ -114,7 +223,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_font_color("textview", "17013");
|
||||
* it.set_component_font_color("textview", "RED");
|
||||
* ```
|
||||
*
|
||||
* This will change the font color of the component `textview` to a blue color.
|
||||
@@ -122,6 +231,34 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
* Nextion HMI colors.
|
||||
*/
|
||||
void set_component_font_color(const char *component, const char *color);
|
||||
/**
|
||||
* Set the font color of a component.
|
||||
* @param component The component name.
|
||||
* @param color The color (as Color).
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_font_color("textview", color);
|
||||
* ```
|
||||
*
|
||||
* This will change the font color of the component `textview` to a blue color.
|
||||
* Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to
|
||||
* Nextion HMI colors.
|
||||
*/
|
||||
void set_component_font_color(const char *component, Color color) override;
|
||||
/**
|
||||
* Set the pressed font color of a component.
|
||||
* @param component The component name.
|
||||
* @param color The color (as a uint32_t).
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_pressed_font_color("button", 0xFF0000);
|
||||
* ```
|
||||
*
|
||||
* This will change the pressed font color of the component `button` to a red.
|
||||
*/
|
||||
void set_component_pressed_font_color(const char *component, uint32_t color);
|
||||
/**
|
||||
* Set the pressed font color of a component.
|
||||
* @param component The component name.
|
||||
@@ -129,7 +266,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_pressed_font_color("button", "17013");
|
||||
* it.set_component_pressed_font_color("button", "RED");
|
||||
* ```
|
||||
*
|
||||
* This will change the pressed font color of the component `button` to a blue color.
|
||||
@@ -137,6 +274,21 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
* Nextion HMI colors.
|
||||
*/
|
||||
void set_component_pressed_font_color(const char *component, const char *color);
|
||||
/**
|
||||
* Set the pressed font color of a component.
|
||||
* @param component The component name.
|
||||
* @param color The color (as Color).
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_component_pressed_font_color("button", color);
|
||||
* ```
|
||||
*
|
||||
* This will change the pressed font color of the component `button` to a blue color.
|
||||
* Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to
|
||||
* Nextion HMI colors.
|
||||
*/
|
||||
void set_component_pressed_font_color(const char *component, Color color) override;
|
||||
/**
|
||||
* Set the coordinates of a component on screen.
|
||||
* @param component The component name.
|
||||
@@ -163,7 +315,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
*
|
||||
* Changes the font of the component named `textveiw`. Font IDs are set in the Nextion Editor.
|
||||
*/
|
||||
void set_component_font(const char *component, uint8_t font_id);
|
||||
void set_component_font(const char *component, uint8_t font_id) override;
|
||||
#ifdef USE_TIME
|
||||
/**
|
||||
* Send the current time to the nextion display.
|
||||
@@ -195,7 +347,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
*
|
||||
* Hides the component named `button`.
|
||||
*/
|
||||
void hide_component(const char *component);
|
||||
void hide_component(const char *component) override;
|
||||
/**
|
||||
* Show a component.
|
||||
* @param component The component name.
|
||||
@@ -207,7 +359,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
*
|
||||
* Shows the component named `button`.
|
||||
*/
|
||||
void show_component(const char *component);
|
||||
void show_component(const char *component) override;
|
||||
/**
|
||||
* Enable touch for a component.
|
||||
* @param component The component name.
|
||||
@@ -239,6 +391,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
* @param value The value to write.
|
||||
*/
|
||||
void add_waveform_data(int component_id, uint8_t channel_number, uint8_t value);
|
||||
void open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value);
|
||||
/**
|
||||
* Display a picture at coordinates.
|
||||
* @param picture_id The picture id.
|
||||
@@ -263,7 +416,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* fill_area(50, 50, 100, 100, "17013");
|
||||
* fill_area(50, 50, 100, 100, "RED");
|
||||
* ```
|
||||
*
|
||||
* Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with
|
||||
@@ -271,6 +424,24 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
* convert color codes to Nextion HMI colors
|
||||
*/
|
||||
void fill_area(int x1, int y1, int width, int height, const char *color);
|
||||
/**
|
||||
* Fill a rectangle with a color.
|
||||
* @param x1 The starting x coordinate.
|
||||
* @param y1 The starting y coordinate.
|
||||
* @param width The width to draw.
|
||||
* @param height The height to draw.
|
||||
* @param color The color to draw with (as Color).
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* fill_area(50, 50, 100, 100, color);
|
||||
* ```
|
||||
*
|
||||
* Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with
|
||||
* the color of blue. Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to
|
||||
* convert color codes to Nextion HMI colors
|
||||
*/
|
||||
void fill_area(int x1, int y1, int width, int height, Color color);
|
||||
/**
|
||||
* Draw a line on the screen.
|
||||
* @param x1 The starting x coordinate.
|
||||
@@ -290,6 +461,25 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
* colors.
|
||||
*/
|
||||
void line(int x1, int y1, int x2, int y2, const char *color);
|
||||
/**
|
||||
* Draw a line on the screen.
|
||||
* @param x1 The starting x coordinate.
|
||||
* @param y1 The starting y coordinate.
|
||||
* @param x2 The ending x coordinate.
|
||||
* @param y2 The ending y coordinate.
|
||||
* @param color The color to draw with (as Color).
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.line(50, 50, 75, 75, "17013");
|
||||
* ```
|
||||
*
|
||||
* Makes a line that starts at x coordinate `50` and y coordinate `50` and ends at x coordinate `75` and y coordinate
|
||||
* `75` with the color of blue. Use this [color
|
||||
* picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI
|
||||
* colors.
|
||||
*/
|
||||
void line(int x1, int y1, int x2, int y2, Color color);
|
||||
/**
|
||||
* Draw a rectangle outline.
|
||||
* @param x1 The starting x coordinate.
|
||||
@@ -309,6 +499,25 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
* colors.
|
||||
*/
|
||||
void rectangle(int x1, int y1, int width, int height, const char *color);
|
||||
/**
|
||||
* Draw a rectangle outline.
|
||||
* @param x1 The starting x coordinate.
|
||||
* @param y1 The starting y coordinate.
|
||||
* @param width The width of the rectangle.
|
||||
* @param height The height of the rectangle.
|
||||
* @param color The color to draw with (as Color).
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.rectangle(25, 35, 40, 50, "17013");
|
||||
* ```
|
||||
*
|
||||
* Makes a outline of a rectangle that starts at x coordinate `25` and y coordinate `35` and has a width of `40` and a
|
||||
* length of `50` with color of blue. Use this [color
|
||||
* picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI
|
||||
* colors.
|
||||
*/
|
||||
void rectangle(int x1, int y1, int width, int height, Color color);
|
||||
/**
|
||||
* Draw a circle outline
|
||||
* @param center_x The center x coordinate.
|
||||
@@ -317,6 +526,14 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
* @param color The color to draw with (as a string).
|
||||
*/
|
||||
void circle(int center_x, int center_y, int radius, const char *color);
|
||||
/**
|
||||
* Draw a circle outline
|
||||
* @param center_x The center x coordinate.
|
||||
* @param center_y The center y coordinate.
|
||||
* @param radius The circle radius.
|
||||
* @param color The color to draw with (as Color).
|
||||
*/
|
||||
void circle(int center_x, int center_y, int radius, Color color);
|
||||
/**
|
||||
* Draw a filled circled.
|
||||
* @param center_x The center x coordinate.
|
||||
@@ -334,19 +551,36 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
* Nextion HMI colors.
|
||||
*/
|
||||
void filled_circle(int center_x, int center_y, int radius, const char *color);
|
||||
|
||||
/** Set the brightness of the backlight.
|
||||
*
|
||||
* @param brightness The brightness, from 0 to 100.
|
||||
/**
|
||||
* Draw a filled circled.
|
||||
* @param center_x The center x coordinate.
|
||||
* @param center_y The center y coordinate.
|
||||
* @param radius The circle radius.
|
||||
* @param color The color to draw with (as Color).
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_backlight_brightness(30);
|
||||
* it.filled_cricle(25, 25, 10, color);
|
||||
* ```
|
||||
*
|
||||
* Makes a filled circle at the x cordinates `25` and y coordinate `25` with a radius of `10` with a color of blue.
|
||||
* Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to
|
||||
* Nextion HMI colors.
|
||||
*/
|
||||
void filled_circle(int center_x, int center_y, int radius, Color color);
|
||||
|
||||
/** Set the brightness of the backlight.
|
||||
*
|
||||
* @param brightness The brightness percentage from 0 to 1.0.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_backlight_brightness(.3);
|
||||
* ```
|
||||
*
|
||||
* Changes the brightness of the display to 30%.
|
||||
*/
|
||||
void set_backlight_brightness(uint8_t brightness);
|
||||
void set_backlight_brightness(float brightness);
|
||||
/**
|
||||
* Set the touch sleep timeout of the display.
|
||||
* @param timeout Timeout in seconds.
|
||||
@@ -360,10 +594,46 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
* `thup`.
|
||||
*/
|
||||
void set_touch_sleep_timeout(uint16_t timeout);
|
||||
/**
|
||||
* Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode.
|
||||
* @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
|
||||
* wakes up to current page.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_wake_up_page(2);
|
||||
* ```
|
||||
*
|
||||
* The display will wake up to page 2.
|
||||
*/
|
||||
void set_wake_up_page(uint8_t page_id = 255);
|
||||
/**
|
||||
* Sets if Nextion should auto-wake from sleep when touch press occurs.
|
||||
* @param auto_wake True or false. When auto_wake is true and Nextion is in sleep mode,
|
||||
* the first touch will only trigger the auto wake mode and not trigger a Touch Event.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_auto_wake_on_touch(true);
|
||||
* ```
|
||||
*
|
||||
* The display will wake up by touch.
|
||||
*/
|
||||
void set_auto_wake_on_touch(bool auto_wake);
|
||||
/**
|
||||
* Sets Nextion mode between sleep and awake
|
||||
* @param True or false. Sleep=true to enter sleep mode or sleep=false to exit sleep mode.
|
||||
*/
|
||||
void sleep(bool sleep);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void register_touch_component(NextionTouchComponent *obj) { this->touch_.push_back(obj); }
|
||||
void register_touch_component(NextionComponentBase *obj) { this->touch_.push_back(obj); }
|
||||
void register_switch_component(NextionComponentBase *obj) { this->switchtype_.push_back(obj); }
|
||||
void register_binarysensor_component(NextionComponentBase *obj) { this->binarysensortype_.push_back(obj); }
|
||||
void register_sensor_component(NextionComponentBase *obj) { this->sensortype_.push_back(obj); }
|
||||
void register_textsensor_component(NextionComponentBase *obj) { this->textsensortype_.push_back(obj); }
|
||||
|
||||
void setup() override;
|
||||
void set_brightness(float brightness) { this->brightness_ = brightness; }
|
||||
float get_setup_priority() const override;
|
||||
@@ -371,11 +641,9 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
void loop() override;
|
||||
void set_writer(const nextion_writer_t &writer);
|
||||
|
||||
/**
|
||||
* Manually send a raw command to the display and don't wait for an acknowledgement packet.
|
||||
* @param command The command to write, for example "vis b0,0".
|
||||
*/
|
||||
void send_command_no_ack(const char *command);
|
||||
// This function has been deprecated
|
||||
void set_wait_for_ack(bool wait_for_ack);
|
||||
|
||||
/**
|
||||
* Manually send a raw formatted command to the display.
|
||||
* @param format The printf-style command format, like "vis %s,0"
|
||||
@@ -384,28 +652,199 @@ class Nextion : public PollingComponent, public uart::UARTDevice {
|
||||
*/
|
||||
bool send_command_printf(const char *format, ...) __attribute__((format(printf, 2, 3)));
|
||||
|
||||
void set_wait_for_ack(bool wait_for_ack);
|
||||
#ifdef USE_TFT_UPLOAD
|
||||
/**
|
||||
* Set the tft file URL. https seems problamtic with arduino..
|
||||
*/
|
||||
void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; }
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Upload the tft file and softreset the Nextion
|
||||
*/
|
||||
void upload_tft();
|
||||
void dump_config() override;
|
||||
|
||||
/**
|
||||
* Softreset the Nextion
|
||||
*/
|
||||
void soft_reset();
|
||||
|
||||
/** Add a callback to be notified of sleep state changes.
|
||||
*
|
||||
* @param callback The void() callback.
|
||||
*/
|
||||
void add_sleep_state_callback(std::function<void()> &&callback);
|
||||
|
||||
/** Add a callback to be notified of wake state changes.
|
||||
*
|
||||
* @param callback The void() callback.
|
||||
*/
|
||||
void add_wake_state_callback(std::function<void()> &&callback);
|
||||
|
||||
/** Add a callback to be notified when the nextion completes its initialize setup.
|
||||
*
|
||||
* @param callback The void() callback.
|
||||
*/
|
||||
void add_setup_state_callback(std::function<void()> &&callback);
|
||||
|
||||
void update_all_components();
|
||||
|
||||
/**
|
||||
* @brief Set the nextion sensor state object.
|
||||
*
|
||||
* @param[in] queue_type
|
||||
* Index of NextionQueueType.
|
||||
*
|
||||
* @param[in] name
|
||||
* Component/variable name.
|
||||
*
|
||||
* @param[in] state
|
||||
* State to set.
|
||||
*/
|
||||
void set_nextion_sensor_state(int queue_type, const std::string &name, float state);
|
||||
void set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state);
|
||||
void set_nextion_text_state(const std::string &name, const std::string &state);
|
||||
|
||||
void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) override;
|
||||
void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send,
|
||||
int state_value) override;
|
||||
|
||||
void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) override;
|
||||
void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send,
|
||||
const std::string &state_value) override;
|
||||
|
||||
void add_to_get_queue(NextionComponentBase *component) override;
|
||||
|
||||
void add_addt_command_to_queue(NextionComponentBase *component) override;
|
||||
|
||||
void update_components_by_prefix(const std::string &prefix);
|
||||
|
||||
void set_touch_sleep_timeout_internal(uint32_t touch_sleep_timeout) {
|
||||
this->touch_sleep_timeout_ = touch_sleep_timeout;
|
||||
}
|
||||
void set_wake_up_page_internal(uint8_t wake_up_page) { this->wake_up_page_ = wake_up_page; }
|
||||
void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; }
|
||||
|
||||
protected:
|
||||
bool ack_();
|
||||
bool read_until_ack_();
|
||||
std::deque<NextionQueue *> nextion_queue_;
|
||||
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag);
|
||||
void all_components_send_state_(bool force_update = false);
|
||||
uint64_t comok_sent_ = 0;
|
||||
bool remove_from_q_(bool report_empty = true);
|
||||
/**
|
||||
* @brief
|
||||
* Sends commands ignoring of the Nextion has been setup.
|
||||
*/
|
||||
bool ignore_is_setup_ = false;
|
||||
bool nextion_reports_is_setup_ = false;
|
||||
uint8_t nextion_event_;
|
||||
|
||||
void process_nextion_commands_();
|
||||
void process_serial_();
|
||||
bool is_updating_ = false;
|
||||
uint32_t touch_sleep_timeout_ = 0;
|
||||
int wake_up_page_ = -1;
|
||||
bool auto_wake_on_touch_ = true;
|
||||
|
||||
/**
|
||||
* Manually send a raw command to the display and don't wait for an acknowledgement packet.
|
||||
* @param command The command to write, for example "vis b0,0".
|
||||
*/
|
||||
bool send_command_(const std::string &command);
|
||||
void add_no_result_to_queue_(const std::string &variable_name);
|
||||
bool add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format, ...)
|
||||
__attribute__((format(printf, 3, 4)));
|
||||
void add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command);
|
||||
|
||||
bool add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...)
|
||||
__attribute__((format(printf, 3, 4)));
|
||||
|
||||
void add_no_result_to_queue_with_set_internal_(const std::string &variable_name,
|
||||
const std::string &variable_name_to_send, int state_value,
|
||||
bool is_sleep_safe = false);
|
||||
|
||||
void add_no_result_to_queue_with_set_internal_(const std::string &variable_name,
|
||||
const std::string &variable_name_to_send,
|
||||
const std::string &state_value, bool is_sleep_safe = false);
|
||||
|
||||
#ifdef USE_TFT_UPLOAD
|
||||
#if defined(USE_ETHERNET) || defined(USE_WIFI)
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
WiFiClient *wifi_client_{nullptr};
|
||||
BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr};
|
||||
WiFiClient *get_wifi_client_();
|
||||
#endif
|
||||
|
||||
/**
|
||||
* will request chunk_size chunks from the web server
|
||||
* and send each to the nextion
|
||||
* @param int contentLength Total size of the file
|
||||
* @param uint32_t chunk_size
|
||||
* @return true if success, false for failure.
|
||||
*/
|
||||
int content_length_ = 0;
|
||||
int tft_size_ = 0;
|
||||
int upload_by_chunks_(HTTPClient *http, int range_start);
|
||||
|
||||
bool upload_with_range_(uint32_t range_start, uint32_t range_end);
|
||||
|
||||
/**
|
||||
* start update tft file to nextion.
|
||||
*
|
||||
* @param const uint8_t *file_buf
|
||||
* @param size_t buf_size
|
||||
* @return true if success, false for failure.
|
||||
*/
|
||||
bool upload_from_buffer_(const uint8_t *file_buf, size_t buf_size);
|
||||
void upload_end_();
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
bool get_is_connected_() { return this->is_connected_; }
|
||||
|
||||
bool check_connect_();
|
||||
|
||||
std::vector<NextionComponentBase *> touch_;
|
||||
std::vector<NextionComponentBase *> switchtype_;
|
||||
std::vector<NextionComponentBase *> sensortype_;
|
||||
std::vector<NextionComponentBase *> textsensortype_;
|
||||
std::vector<NextionComponentBase *> binarysensortype_;
|
||||
CallbackManager<void()> setup_callback_{};
|
||||
CallbackManager<void()> sleep_callback_{};
|
||||
CallbackManager<void()> wake_callback_{};
|
||||
|
||||
std::vector<NextionTouchComponent *> touch_;
|
||||
optional<nextion_writer_t> writer_;
|
||||
bool wait_for_ack_{true};
|
||||
float brightness_{1.0};
|
||||
|
||||
std::string device_model_;
|
||||
std::string firmware_version_;
|
||||
std::string serial_number_;
|
||||
std::string flash_size_;
|
||||
|
||||
void remove_front_no_sensors_();
|
||||
|
||||
#ifdef USE_TFT_UPLOAD
|
||||
std::string tft_url_;
|
||||
uint8_t *transfer_buffer_{nullptr};
|
||||
size_t transfer_buffer_size_;
|
||||
bool upload_first_chunk_sent_ = false;
|
||||
#endif
|
||||
|
||||
#ifdef NEXTION_PROTOCOL_LOG
|
||||
void print_queue_members_();
|
||||
#endif
|
||||
void reset_(bool reset_nextion = true);
|
||||
|
||||
std::string command_data_;
|
||||
bool is_connected_ = false;
|
||||
uint32_t startup_override_ms_ = 8000;
|
||||
uint32_t max_q_age_ms_ = 8000;
|
||||
uint32_t started_ms_ = 0;
|
||||
bool sent_setup_commands_ = false;
|
||||
};
|
||||
|
||||
class NextionTouchComponent : public binary_sensor::BinarySensorInitiallyOff {
|
||||
public:
|
||||
void set_page_id(uint8_t page_id) { page_id_ = page_id; }
|
||||
void set_component_id(uint8_t component_id) { component_id_ = component_id; }
|
||||
void process(uint8_t page_id, uint8_t component_id, bool on);
|
||||
|
||||
protected:
|
||||
uint8_t page_id_;
|
||||
uint8_t component_id_;
|
||||
};
|
||||
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
|
||||
58
esphome/components/nextion/nextion_base.h
Normal file
58
esphome/components/nextion/nextion_base.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/color.h"
|
||||
#include "nextion_component_base.h"
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
|
||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||
#define NEXTION_PROTOCOL_LOG
|
||||
#endif
|
||||
|
||||
#ifdef NEXTION_PROTOCOL_LOG
|
||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||
#define ESP_LOGN(tag, ...) ESP_LOGVV(tag, __VA_ARGS__)
|
||||
#else
|
||||
#define ESP_LOGN(tag, ...) ESP_LOGD(tag, __VA_ARGS__)
|
||||
#endif
|
||||
#else
|
||||
#define ESP_LOGN(tag, ...) \
|
||||
{}
|
||||
#endif
|
||||
|
||||
class NextionBase;
|
||||
|
||||
class NextionBase {
|
||||
public:
|
||||
virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) = 0;
|
||||
virtual void add_no_result_to_queue_with_set(const std::string &variable_name,
|
||||
const std::string &variable_name_to_send, int state_value) = 0;
|
||||
|
||||
virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) = 0;
|
||||
virtual void add_no_result_to_queue_with_set(const std::string &variable_name,
|
||||
const std::string &variable_name_to_send,
|
||||
const std::string &state_value) = 0;
|
||||
|
||||
virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0;
|
||||
|
||||
virtual void add_to_get_queue(NextionComponentBase *component) = 0;
|
||||
|
||||
virtual void set_component_background_color(const char *component, Color color) = 0;
|
||||
virtual void set_component_pressed_background_color(const char *component, Color color) = 0;
|
||||
virtual void set_component_font_color(const char *component, Color color) = 0;
|
||||
virtual void set_component_pressed_font_color(const char *component, Color color) = 0;
|
||||
virtual void set_component_font(const char *component, uint8_t font_id) = 0;
|
||||
|
||||
virtual void show_component(const char *component) = 0;
|
||||
virtual void hide_component(const char *component) = 0;
|
||||
|
||||
bool is_sleeping() { return this->is_sleeping_; }
|
||||
bool is_setup() { return this->is_setup_; }
|
||||
|
||||
protected:
|
||||
bool is_setup_ = false;
|
||||
bool is_sleeping_ = false;
|
||||
};
|
||||
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
234
esphome/components/nextion/nextion_commands.cpp
Normal file
234
esphome/components/nextion/nextion_commands.cpp
Normal file
@@ -0,0 +1,234 @@
|
||||
#include "nextion.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
static const char *const TAG = "nextion";
|
||||
|
||||
// Sleep safe commands
|
||||
void Nextion::soft_reset() { this->send_command_("rest"); }
|
||||
|
||||
void Nextion::set_wake_up_page(uint8_t page_id) {
|
||||
if (page_id > 255) {
|
||||
ESP_LOGD(TAG, "Wake up page of bounds, range 0-255");
|
||||
return;
|
||||
}
|
||||
this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true);
|
||||
}
|
||||
|
||||
void Nextion::set_touch_sleep_timeout(uint16_t timeout) {
|
||||
if (timeout < 3 || timeout > 65535) {
|
||||
ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535");
|
||||
return;
|
||||
}
|
||||
|
||||
this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", timeout, true);
|
||||
}
|
||||
|
||||
void Nextion::sleep(bool sleep) {
|
||||
if (sleep) { // Set sleep
|
||||
this->is_sleeping_ = true;
|
||||
this->add_no_result_to_queue_with_set_internal_("sleep", "sleep", 1, true);
|
||||
} else { // Turn off sleep. Wait for a sleep_wake return before setting sleep off
|
||||
this->add_no_result_to_queue_with_set_internal_("sleep_wake", "sleep", 0, true);
|
||||
}
|
||||
}
|
||||
// End sleep safe commands
|
||||
|
||||
// Set Colors
|
||||
void Nextion::set_component_background_color(const char *component, uint32_t color) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component, color);
|
||||
}
|
||||
|
||||
void Nextion::set_component_background_color(const char *component, const char *color) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%s", component, color);
|
||||
}
|
||||
|
||||
void Nextion::set_component_background_color(const char *component, Color color) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component,
|
||||
display::ColorUtil::color_to_565(color));
|
||||
}
|
||||
|
||||
void Nextion::set_component_pressed_background_color(const char *component, uint32_t color) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component, color);
|
||||
}
|
||||
|
||||
void Nextion::set_component_pressed_background_color(const char *component, const char *color) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%s", component, color);
|
||||
}
|
||||
|
||||
void Nextion::set_component_pressed_background_color(const char *component, Color color) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component,
|
||||
display::ColorUtil::color_to_565(color));
|
||||
}
|
||||
|
||||
void Nextion::set_component_pic(const char *component, uint8_t pic_id) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.pic=%d", component, pic_id);
|
||||
}
|
||||
|
||||
void Nextion::set_component_picc(const char *component, uint8_t pic_id) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.picc=%d", component, pic_id);
|
||||
}
|
||||
|
||||
void Nextion::set_component_font_color(const char *component, uint32_t color) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component, color);
|
||||
}
|
||||
|
||||
void Nextion::set_component_font_color(const char *component, const char *color) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%s", component, color);
|
||||
}
|
||||
|
||||
void Nextion::set_component_font_color(const char *component, Color color) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component,
|
||||
display::ColorUtil::color_to_565(color));
|
||||
}
|
||||
|
||||
void Nextion::set_component_pressed_font_color(const char *component, uint32_t color) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component, color);
|
||||
}
|
||||
|
||||
void Nextion::set_component_pressed_font_color(const char *component, const char *color) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", " %s.pco2=%s", component, color);
|
||||
}
|
||||
|
||||
void Nextion::set_component_pressed_font_color(const char *component, Color color) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component,
|
||||
display::ColorUtil::color_to_565(color));
|
||||
}
|
||||
|
||||
void Nextion::set_component_text_printf(const char *component, const char *format, ...) {
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
char buffer[256];
|
||||
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
|
||||
va_end(arg);
|
||||
if (ret > 0)
|
||||
this->set_component_text(component, buffer);
|
||||
}
|
||||
|
||||
// General Nextion
|
||||
void Nextion::goto_page(const char *page) { this->add_no_result_to_queue_with_printf_("goto_page", "page %s", page); }
|
||||
|
||||
void Nextion::set_backlight_brightness(float brightness) {
|
||||
if (brightness < 0 || brightness > 1.0) {
|
||||
ESP_LOGD(TAG, "Brightness out of bounds, percentage range 0-1.0");
|
||||
return;
|
||||
}
|
||||
this->add_no_result_to_queue_with_set("backlight_brightness", "dim", static_cast<int>(brightness * 100));
|
||||
}
|
||||
|
||||
void Nextion::set_auto_wake_on_touch(bool auto_wake) {
|
||||
this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake ? 1 : 0);
|
||||
}
|
||||
|
||||
// General Component
|
||||
void Nextion::set_component_font(const char *component, uint8_t font_id) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_font", "%s.font=%d", component, font_id);
|
||||
}
|
||||
|
||||
void Nextion::hide_component(const char *component) {
|
||||
this->add_no_result_to_queue_with_printf_("hide_component", "vis %s,0", component);
|
||||
}
|
||||
|
||||
void Nextion::show_component(const char *component) {
|
||||
this->add_no_result_to_queue_with_printf_("show_component", "vis %s,1", component);
|
||||
}
|
||||
|
||||
void Nextion::enable_component_touch(const char *component) {
|
||||
this->add_no_result_to_queue_with_printf_("enable_component_touch", "tsw %s,1", component);
|
||||
}
|
||||
|
||||
void Nextion::disable_component_touch(const char *component) {
|
||||
this->add_no_result_to_queue_with_printf_("disable_component_touch", "tsw %s,0", component);
|
||||
}
|
||||
|
||||
void Nextion::set_component_picture(const char *component, const char *picture) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.val=%s", component, picture);
|
||||
}
|
||||
|
||||
void Nextion::set_component_text(const char *component, const char *text) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_text", "%s.txt=\"%s\"", component, text);
|
||||
}
|
||||
|
||||
void Nextion::set_component_value(const char *component, int value) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_value", "%s.val=%d", component, value);
|
||||
}
|
||||
|
||||
void Nextion::add_waveform_data(int component_id, uint8_t channel_number, uint8_t value) {
|
||||
this->add_no_result_to_queue_with_printf_("add_waveform_data", "add %d,%u,%u", component_id, channel_number, value);
|
||||
}
|
||||
|
||||
void Nextion::open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value) {
|
||||
this->add_no_result_to_queue_with_printf_("open_waveform_channel", "addt %d,%u,%u", component_id, channel_number,
|
||||
value);
|
||||
}
|
||||
|
||||
void Nextion::set_component_coordinates(const char *component, int x, int y) {
|
||||
this->add_no_result_to_queue_with_printf_("set_component_coordinates command 1", "%s.xcen=%d", component, x);
|
||||
this->add_no_result_to_queue_with_printf_("set_component_coordinates command 2", "%s.ycen=%d", component, y);
|
||||
}
|
||||
|
||||
// Drawing
|
||||
void Nextion::display_picture(int picture_id, int x_start, int y_start) {
|
||||
this->add_no_result_to_queue_with_printf_("display_picture", "pic %d %d %d", x_start, y_start, picture_id);
|
||||
}
|
||||
|
||||
void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) {
|
||||
this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%s", x1, y1, width, height, color);
|
||||
}
|
||||
|
||||
void Nextion::fill_area(int x1, int y1, int width, int height, Color color) {
|
||||
this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%d", x1, y1, width, height,
|
||||
display::ColorUtil::color_to_565(color));
|
||||
}
|
||||
|
||||
void Nextion::line(int x1, int y1, int x2, int y2, const char *color) {
|
||||
this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%s", x1, y1, x2, y2, color);
|
||||
}
|
||||
|
||||
void Nextion::line(int x1, int y1, int x2, int y2, Color color) {
|
||||
this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%d", x1, y1, x2, y2,
|
||||
display::ColorUtil::color_to_565(color));
|
||||
}
|
||||
|
||||
void Nextion::rectangle(int x1, int y1, int width, int height, const char *color) {
|
||||
this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%s", x1, y1, x1 + width, y1 + height, color);
|
||||
}
|
||||
|
||||
void Nextion::rectangle(int x1, int y1, int width, int height, Color color) {
|
||||
this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%d", x1, y1, x1 + width, y1 + height,
|
||||
display::ColorUtil::color_to_565(color));
|
||||
}
|
||||
|
||||
void Nextion::circle(int center_x, int center_y, int radius, const char *color) {
|
||||
this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%s", center_x, center_y, radius, color);
|
||||
}
|
||||
|
||||
void Nextion::circle(int center_x, int center_y, int radius, Color color) {
|
||||
this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%d", center_x, center_y, radius,
|
||||
display::ColorUtil::color_to_565(color));
|
||||
}
|
||||
|
||||
void Nextion::filled_circle(int center_x, int center_y, int radius, const char *color) {
|
||||
this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%s", center_x, center_y, radius, color);
|
||||
}
|
||||
|
||||
void Nextion::filled_circle(int center_x, int center_y, int radius, Color color) {
|
||||
this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%d", center_x, center_y, radius,
|
||||
display::ColorUtil::color_to_565(color));
|
||||
}
|
||||
|
||||
#ifdef USE_TIME
|
||||
void Nextion::set_nextion_rtc_time(time::ESPTime time) {
|
||||
this->add_no_result_to_queue_with_printf_("rtc0", "rtc0=%u", time.year);
|
||||
this->add_no_result_to_queue_with_printf_("rtc1", "rtc1=%u", time.month);
|
||||
this->add_no_result_to_queue_with_printf_("rtc2", "rtc2=%u", time.day_of_month);
|
||||
this->add_no_result_to_queue_with_printf_("rtc3", "rtc3=%u", time.hour);
|
||||
this->add_no_result_to_queue_with_printf_("rtc4", "rtc4=%u", time.minute);
|
||||
this->add_no_result_to_queue_with_printf_("rtc5", "rtc5=%u", time.second);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
116
esphome/components/nextion/nextion_component.cpp
Normal file
116
esphome/components/nextion/nextion_component.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
#include "nextion_component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
|
||||
void NextionComponent::set_background_color(Color bco) {
|
||||
if (this->variable_name_ == this->variable_name_to_send_) {
|
||||
return; // This is a variable. no need to set color
|
||||
}
|
||||
this->bco_ = bco;
|
||||
this->bco_needs_update_ = true;
|
||||
this->bco_is_set_ = true;
|
||||
this->update_component_settings();
|
||||
}
|
||||
|
||||
void NextionComponent::set_background_pressed_color(Color bco2) {
|
||||
if (this->variable_name_ == this->variable_name_to_send_) {
|
||||
return; // This is a variable. no need to set color
|
||||
}
|
||||
|
||||
this->bco2_ = bco2;
|
||||
this->bco2_needs_update_ = true;
|
||||
this->bco2_is_set_ = true;
|
||||
this->update_component_settings();
|
||||
}
|
||||
|
||||
void NextionComponent::set_foreground_color(Color pco) {
|
||||
if (this->variable_name_ == this->variable_name_to_send_) {
|
||||
return; // This is a variable. no need to set color
|
||||
}
|
||||
this->pco_ = pco;
|
||||
this->pco_needs_update_ = true;
|
||||
this->pco_is_set_ = true;
|
||||
this->update_component_settings();
|
||||
}
|
||||
|
||||
void NextionComponent::set_foreground_pressed_color(Color pco2) {
|
||||
if (this->variable_name_ == this->variable_name_to_send_) {
|
||||
return; // This is a variable. no need to set color
|
||||
}
|
||||
this->pco2_ = pco2;
|
||||
this->pco2_needs_update_ = true;
|
||||
this->pco2_is_set_ = true;
|
||||
this->update_component_settings();
|
||||
}
|
||||
|
||||
void NextionComponent::set_font_id(uint8_t font_id) {
|
||||
if (this->variable_name_ == this->variable_name_to_send_) {
|
||||
return; // This is a variable. no need to set color
|
||||
}
|
||||
this->font_id_ = font_id;
|
||||
this->font_id_needs_update_ = true;
|
||||
this->font_id_is_set_ = true;
|
||||
this->update_component_settings();
|
||||
}
|
||||
|
||||
void NextionComponent::set_visible(bool visible) {
|
||||
if (this->variable_name_ == this->variable_name_to_send_) {
|
||||
return; // This is a variable. no need to set color
|
||||
}
|
||||
this->visible_ = visible;
|
||||
this->visible_needs_update_ = true;
|
||||
this->visible_is_set_ = true;
|
||||
this->update_component_settings();
|
||||
}
|
||||
|
||||
void NextionComponent::update_component_settings(bool force_update) {
|
||||
if (this->nextion_->is_sleeping() || !this->nextion_->is_setup() || !this->visible_is_set_ ||
|
||||
(!this->visible_needs_update_ && !this->visible_)) {
|
||||
this->needs_to_send_update_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->visible_needs_update_ || (force_update && this->visible_is_set_)) {
|
||||
std::string name_to_send = this->variable_name_;
|
||||
|
||||
size_t pos = name_to_send.find_last_of('.');
|
||||
if (pos != std::string::npos) {
|
||||
name_to_send = name_to_send.substr(pos + 1);
|
||||
}
|
||||
|
||||
this->visible_needs_update_ = false;
|
||||
|
||||
if (this->visible_) {
|
||||
this->nextion_->show_component(name_to_send.c_str());
|
||||
this->send_state_to_nextion();
|
||||
} else {
|
||||
this->nextion_->hide_component(name_to_send.c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->bco_needs_update_ || (force_update && this->bco2_is_set_)) {
|
||||
this->nextion_->set_component_background_color(this->variable_name_.c_str(), this->bco_);
|
||||
this->bco_needs_update_ = false;
|
||||
}
|
||||
if (this->bco2_needs_update_ || (force_update && this->bco2_is_set_)) {
|
||||
this->nextion_->set_component_pressed_background_color(this->variable_name_.c_str(), this->bco2_);
|
||||
this->bco2_needs_update_ = false;
|
||||
}
|
||||
if (this->pco_needs_update_ || (force_update && this->pco_is_set_)) {
|
||||
this->nextion_->set_component_font_color(this->variable_name_.c_str(), this->pco_);
|
||||
this->pco_needs_update_ = false;
|
||||
}
|
||||
if (this->pco2_needs_update_ || (force_update && this->pco2_is_set_)) {
|
||||
this->nextion_->set_component_pressed_font_color(this->variable_name_.c_str(), this->pco2_);
|
||||
this->pco2_needs_update_ = false;
|
||||
}
|
||||
|
||||
if (this->font_id_needs_update_ || (force_update && this->font_id_is_set_)) {
|
||||
this->nextion_->set_component_font(this->variable_name_.c_str(), this->font_id_);
|
||||
this->font_id_needs_update_ = false;
|
||||
}
|
||||
}
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
49
esphome/components/nextion/nextion_component.h
Normal file
49
esphome/components/nextion/nextion_component.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/color.h"
|
||||
#include "nextion_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
class NextionComponent;
|
||||
|
||||
class NextionComponent : public NextionComponentBase {
|
||||
public:
|
||||
void update_component_settings() override { this->update_component_settings(false); };
|
||||
|
||||
void update_component_settings(bool force_update) override;
|
||||
|
||||
void set_background_color(Color bco);
|
||||
void set_background_pressed_color(Color bco2);
|
||||
void set_foreground_color(Color pco);
|
||||
void set_foreground_pressed_color(Color pco2);
|
||||
void set_font_id(uint8_t font_id);
|
||||
void set_visible(bool visible);
|
||||
|
||||
protected:
|
||||
NextionBase *nextion_;
|
||||
|
||||
bool bco_needs_update_ = false;
|
||||
bool bco_is_set_ = false;
|
||||
Color bco_;
|
||||
bool bco2_needs_update_ = false;
|
||||
bool bco2_is_set_ = false;
|
||||
Color bco2_;
|
||||
bool pco_needs_update_ = false;
|
||||
bool pco_is_set_ = false;
|
||||
Color pco_;
|
||||
bool pco2_needs_update_ = false;
|
||||
bool pco2_is_set_ = false;
|
||||
Color pco2_;
|
||||
uint8_t font_id_ = 0;
|
||||
bool font_id_needs_update_ = false;
|
||||
bool font_id_is_set_ = false;
|
||||
|
||||
bool visible_ = true;
|
||||
bool visible_needs_update_ = false;
|
||||
bool visible_is_set_ = false;
|
||||
|
||||
// void send_state_to_nextion() = 0;
|
||||
};
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
95
esphome/components/nextion/nextion_component_base.h
Normal file
95
esphome/components/nextion/nextion_component_base.h
Normal file
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
#include <utility>
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
|
||||
enum NextionQueueType {
|
||||
NO_RESULT = 0,
|
||||
SENSOR = 1,
|
||||
BINARY_SENSOR = 2,
|
||||
SWITCH = 3,
|
||||
TEXT_SENSOR = 4,
|
||||
WAVEFORM_SENSOR = 5,
|
||||
};
|
||||
|
||||
static const char *const NEXTION_QUEUE_TYPE_STRINGS[] = {"NO_RESULT", "SENSOR", "BINARY_SENSOR",
|
||||
"SWITCH", "TEXT_SENSOR", "WAVEFORM_SENSOR"};
|
||||
|
||||
class NextionComponentBase;
|
||||
|
||||
class NextionQueue {
|
||||
public:
|
||||
virtual ~NextionQueue() = default;
|
||||
NextionComponentBase *component;
|
||||
uint32_t queue_time = 0;
|
||||
};
|
||||
|
||||
class NextionComponentBase {
|
||||
public:
|
||||
virtual ~NextionComponentBase() = default;
|
||||
|
||||
void set_variable_name(const std::string &variable_name, const std::string &variable_name_to_send = "") {
|
||||
variable_name_ = variable_name;
|
||||
if (variable_name_to_send.empty()) {
|
||||
variable_name_to_send_ = variable_name;
|
||||
} else {
|
||||
variable_name_to_send_ = variable_name_to_send;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void update_component_settings(){};
|
||||
virtual void update_component_settings(bool force_update){};
|
||||
|
||||
virtual void update_component(){};
|
||||
virtual void process_sensor(const std::string &variable_name, int state){};
|
||||
virtual void process_touch(uint8_t page_id, uint8_t component_id, bool on){};
|
||||
virtual void process_text(const std::string &variable_name, const std::string &text_value){};
|
||||
virtual void process_bool(const std::string &variable_name, bool on){};
|
||||
|
||||
virtual void set_state(float state){};
|
||||
virtual void set_state(float state, bool publish){};
|
||||
virtual void set_state(float state, bool publish, bool send_to_nextion){};
|
||||
|
||||
virtual void set_state(bool state){};
|
||||
virtual void set_state(bool state, bool publish){};
|
||||
virtual void set_state(bool state, bool publish, bool send_to_nextion){};
|
||||
|
||||
virtual void set_state(const std::string &state) {}
|
||||
virtual void set_state(const std::string &state, bool publish) {}
|
||||
virtual void set_state(const std::string &state, bool publish, bool send_to_nextion){};
|
||||
|
||||
uint8_t get_component_id() { return this->component_id_; }
|
||||
void set_component_id(uint8_t component_id) { component_id_ = component_id; }
|
||||
|
||||
uint8_t get_wave_channel_id() { return this->wave_chan_id_; }
|
||||
void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; }
|
||||
|
||||
std::vector<uint8_t> get_wave_buffer() { return this->wave_buffer_; }
|
||||
size_t get_wave_buffer_size() { return this->wave_buffer_.size(); }
|
||||
|
||||
std::string get_variable_name() { return this->variable_name_; }
|
||||
std::string get_variable_name_to_send() { return this->variable_name_to_send_; }
|
||||
virtual NextionQueueType get_queue_type() { return NextionQueueType::NO_RESULT; }
|
||||
virtual std::string get_queue_type_string() { return NEXTION_QUEUE_TYPE_STRINGS[this->get_queue_type()]; }
|
||||
virtual void set_state_from_int(int state_value, bool publish, bool send_to_nextion){};
|
||||
virtual void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion){};
|
||||
virtual void send_state_to_nextion(){};
|
||||
bool get_needs_to_send_update() { return this->needs_to_send_update_; }
|
||||
uint8_t get_wave_chan_id() { return this->wave_chan_id_; }
|
||||
void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; }
|
||||
|
||||
protected:
|
||||
std::string variable_name_;
|
||||
std::string variable_name_to_send_;
|
||||
|
||||
uint8_t component_id_ = 0;
|
||||
uint8_t wave_chan_id_ = UINT8_MAX;
|
||||
std::vector<uint8_t> wave_buffer_;
|
||||
int wave_max_length_ = 255;
|
||||
|
||||
bool needs_to_send_update_;
|
||||
};
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
343
esphome/components/nextion/nextion_upload.cpp
Normal file
343
esphome/components/nextion/nextion_upload.cpp
Normal file
@@ -0,0 +1,343 @@
|
||||
|
||||
#include "nextion.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
static const char *const TAG = "nextion_upload";
|
||||
|
||||
#if defined(USE_TFT_UPLOAD) && (defined(USE_ETHERNET) || defined(USE_WIFI))
|
||||
|
||||
// Followed guide
|
||||
// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
|
||||
|
||||
int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) {
|
||||
int range_end = 0;
|
||||
|
||||
if (range_start == 0 && this->transfer_buffer_size_ > 16384) { // Start small at the first run in case of a big skip
|
||||
range_end = 16384 - 1;
|
||||
} else {
|
||||
range_end = range_start + this->transfer_buffer_size_ - 1;
|
||||
}
|
||||
|
||||
if (range_end > this->tft_size_)
|
||||
range_end = this->tft_size_;
|
||||
|
||||
bool begin_status = false;
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
begin_status = http->begin(this->tft_url_.c_str());
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifndef CLANG_TIDY
|
||||
http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
http->setRedirectLimit(3);
|
||||
begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str());
|
||||
#endif
|
||||
#endif
|
||||
|
||||
char range_header[64];
|
||||
sprintf(range_header, "bytes=%d-%d", range_start, range_end);
|
||||
|
||||
ESP_LOGD(TAG, "Requesting range: %s", range_header);
|
||||
|
||||
int tries = 1;
|
||||
int code = 0;
|
||||
while (tries <= 5) {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
begin_status = http->begin(this->tft_url_.c_str());
|
||||
#endif
|
||||
#ifndef CLANG_TIDY
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str());
|
||||
#endif
|
||||
#endif
|
||||
|
||||
++tries;
|
||||
if (!begin_status) {
|
||||
ESP_LOGD(TAG, "upload_by_chunks_: connection failed");
|
||||
continue;
|
||||
}
|
||||
|
||||
http->addHeader("Range", range_header);
|
||||
|
||||
code = http->GET();
|
||||
if (code == 200 || code == 206) {
|
||||
break;
|
||||
}
|
||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retries(%d/5)", this->tft_url_.c_str(),
|
||||
HTTPClient::errorToString(code).c_str(), tries);
|
||||
http->end();
|
||||
App.feed_wdt();
|
||||
delay(500); // NOLINT
|
||||
}
|
||||
|
||||
if (tries > 5) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string recv_string;
|
||||
size_t size = 0;
|
||||
int sent = 0;
|
||||
int range = range_end - range_start;
|
||||
|
||||
while (sent < range) {
|
||||
size = http->getStreamPtr()->available();
|
||||
if (!size) {
|
||||
App.feed_wdt();
|
||||
delay(0);
|
||||
continue;
|
||||
}
|
||||
int c = http->getStreamPtr()->readBytes(
|
||||
&this->transfer_buffer_[sent], ((size > this->transfer_buffer_size_) ? this->transfer_buffer_size_ : size));
|
||||
sent += c;
|
||||
}
|
||||
http->end();
|
||||
ESP_LOGN(TAG, "this->content_length_ %d sent %d", this->content_length_, sent);
|
||||
for (uint32_t i = 0; i < range; i += 4096) {
|
||||
this->write_array(&this->transfer_buffer_[i], 4096);
|
||||
this->content_length_ -= 4096;
|
||||
ESP_LOGN(TAG, "this->content_length_ %d range %d range_end %d range_start %d", this->content_length_, range,
|
||||
range_end, range_start);
|
||||
|
||||
if (!this->upload_first_chunk_sent_) {
|
||||
this->upload_first_chunk_sent_ = true;
|
||||
delay(500); // NOLINT
|
||||
App.feed_wdt();
|
||||
}
|
||||
|
||||
this->recv_ret_string_(recv_string, 2048, true);
|
||||
if (recv_string[0] == 0x08) {
|
||||
uint32_t result = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
result += static_cast<uint8_t>(recv_string[i + 1]) << (8 * i);
|
||||
}
|
||||
if (result > 0) {
|
||||
ESP_LOGD(TAG, "Nextion reported new range %d", result);
|
||||
this->content_length_ = this->tft_size_ - result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
recv_string.clear();
|
||||
}
|
||||
return range_end + 1;
|
||||
}
|
||||
|
||||
void Nextion::upload_tft() {
|
||||
if (this->is_updating_) {
|
||||
ESP_LOGD(TAG, "Currently updating");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!network_is_connected()) {
|
||||
ESP_LOGD(TAG, "network is not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
this->is_updating_ = true;
|
||||
|
||||
HTTPClient http;
|
||||
http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along
|
||||
bool begin_status = false;
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
begin_status = http.begin(this->tft_url_.c_str());
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#ifndef CLANG_TIDY
|
||||
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
http.setRedirectLimit(3);
|
||||
begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str());
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (!begin_status) {
|
||||
this->is_updating_ = false;
|
||||
ESP_LOGD(TAG, "connection failed");
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (psramFound())
|
||||
free(this->transfer_buffer_);
|
||||
else
|
||||
#endif
|
||||
delete this->transfer_buffer_;
|
||||
return;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Connected");
|
||||
}
|
||||
|
||||
http.addHeader("Range", "bytes=0-255");
|
||||
const char *header_names[] = {"Content-Range"};
|
||||
http.collectHeaders(header_names, 1);
|
||||
ESP_LOGD(TAG, "Requesting URL: %s", this->tft_url_.c_str());
|
||||
|
||||
http.setReuse(true);
|
||||
// try up to 5 times. DNS sometimes needs a second try or so
|
||||
int tries = 1;
|
||||
int code = http.GET();
|
||||
delay(100); // NOLINT
|
||||
|
||||
App.feed_wdt();
|
||||
while (code != 200 && code != 206 && tries <= 5) {
|
||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retrying (%d/5)", this->tft_url_.c_str(),
|
||||
HTTPClient::errorToString(code).c_str(), tries);
|
||||
|
||||
delay(250); // NOLINT
|
||||
App.feed_wdt();
|
||||
code = http.GET();
|
||||
++tries;
|
||||
}
|
||||
|
||||
if ((code != 200 && code != 206) || tries > 5) {
|
||||
this->upload_end_();
|
||||
}
|
||||
|
||||
String content_range_string = http.header("Content-Range");
|
||||
content_range_string.remove(0, 12);
|
||||
this->content_length_ = content_range_string.toInt();
|
||||
this->tft_size_ = content_length_;
|
||||
http.end();
|
||||
|
||||
if (this->content_length_ < 4096) {
|
||||
ESP_LOGE(TAG, "Failed to get file size");
|
||||
this->upload_end_();
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Updating Nextion %s...", this->device_model_.c_str());
|
||||
// The Nextion will ignore the update command if it is sleeping
|
||||
|
||||
this->send_command_("sleep=0");
|
||||
this->set_backlight_brightness(1.0);
|
||||
delay(250); // NOLINT
|
||||
|
||||
App.feed_wdt();
|
||||
|
||||
char command[128];
|
||||
// Tells the Nextion the content length of the tft file and baud rate it will be sent at
|
||||
// Once the Nextion accepts the command it will wait until the file is successfully uploaded
|
||||
// If it fails for any reason a power cycle of the display will be needed
|
||||
sprintf(command, "whmi-wris %d,%d,1", this->content_length_, this->parent_->get_baud_rate());
|
||||
|
||||
// Clear serial receive buffer
|
||||
uint8_t d;
|
||||
while (this->available()) {
|
||||
this->read_byte(&d);
|
||||
};
|
||||
|
||||
this->send_command_(command);
|
||||
|
||||
App.feed_wdt();
|
||||
|
||||
std::string response;
|
||||
ESP_LOGD(TAG, "Waiting for upgrade response");
|
||||
this->recv_ret_string_(response, 2000, true); // This can take some time to return
|
||||
|
||||
// The Nextion display will, if it's ready to accept data, send a 0x05 byte.
|
||||
ESP_LOGD(TAG, "Upgrade response is %s %zu", response.c_str(), response.length());
|
||||
|
||||
for (int i = 0; i < response.length(); i++) {
|
||||
ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]);
|
||||
}
|
||||
|
||||
if (response.find(0x05) != std::string::npos) {
|
||||
ESP_LOGD(TAG, "preparation for tft update done");
|
||||
} else {
|
||||
ESP_LOGD(TAG, "preparation for tft update failed %d \"%s\"", response[0], response.c_str());
|
||||
this->upload_end_();
|
||||
}
|
||||
|
||||
// Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
uint32_t chunk_size = 8192;
|
||||
if (psramFound()) {
|
||||
chunk_size = this->content_length_;
|
||||
} else {
|
||||
if (ESP.getFreeHeap() > 40960) { // 32K to keep on hand
|
||||
int chunk = int((ESP.getFreeHeap() - 32768) / 4096);
|
||||
chunk_size = chunk * 4096;
|
||||
chunk_size = chunk_size > 65536 ? 65536 : chunk_size;
|
||||
} else if (ESP.getFreeHeap() < 10240) {
|
||||
chunk_size = 4096;
|
||||
}
|
||||
}
|
||||
#else
|
||||
uint32_t chunk_size = ESP.getFreeHeap() < 10240 ? 4096 : 8192;
|
||||
#endif
|
||||
|
||||
if (this->transfer_buffer_ == nullptr) {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (psramFound()) {
|
||||
ESP_LOGD(TAG, "Allocating PSRAM buffer size %d, Free PSRAM size is %u", chunk_size, ESP.getFreePsram());
|
||||
this->transfer_buffer_ = (uint8_t *) ps_malloc(chunk_size);
|
||||
if (this->transfer_buffer_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate buffer size %d!", chunk_size);
|
||||
this->upload_end_();
|
||||
}
|
||||
} else {
|
||||
#endif
|
||||
ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap());
|
||||
this->transfer_buffer_ = new uint8_t[chunk_size];
|
||||
if (!this->transfer_buffer_) { // Try a smaller size
|
||||
ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size);
|
||||
chunk_size = 4096;
|
||||
ESP_LOGD(TAG, "Allocating %d buffer", chunk_size);
|
||||
this->transfer_buffer_ = new uint8_t[chunk_size];
|
||||
|
||||
if (!this->transfer_buffer_)
|
||||
this->upload_end_();
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
this->transfer_buffer_size_ = chunk_size;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d",
|
||||
this->tft_url_.c_str(), this->content_length_, this->transfer_buffer_size_, ESP.getFreeHeap());
|
||||
|
||||
int result = 0;
|
||||
while (this->content_length_ > 0) {
|
||||
result = this->upload_by_chunks_(&http, result);
|
||||
if (result < 0) {
|
||||
ESP_LOGD(TAG, "Error updating Nextion!");
|
||||
this->upload_end_();
|
||||
}
|
||||
App.feed_wdt();
|
||||
ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), this->content_length_);
|
||||
}
|
||||
ESP_LOGD(TAG, "Succesfully updated Nextion!");
|
||||
|
||||
this->upload_end_();
|
||||
}
|
||||
|
||||
void Nextion::upload_end_() {
|
||||
ESP_LOGD(TAG, "Restarting Nextion");
|
||||
this->soft_reset();
|
||||
delay(1500); // NOLINT
|
||||
ESP_LOGD(TAG, "Restarting esphome");
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
WiFiClient *Nextion::get_wifi_client_() {
|
||||
if (this->tft_url_.compare(0, 6, "https:") == 0) {
|
||||
if (this->wifi_client_secure_ == nullptr) {
|
||||
this->wifi_client_secure_ = new BearSSL::WiFiClientSecure();
|
||||
this->wifi_client_secure_->setInsecure();
|
||||
this->wifi_client_secure_->setBufferSizes(512, 512);
|
||||
}
|
||||
return this->wifi_client_secure_;
|
||||
}
|
||||
|
||||
if (this->wifi_client_ == nullptr) {
|
||||
this->wifi_client_ = new WiFiClient();
|
||||
}
|
||||
return this->wifi_client_;
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
void Nextion::upload_tft() { ESP_LOGW(TAG, "tft_url, WIFI or Ethernet components are needed. Cannot upload."); }
|
||||
#endif
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
99
esphome/components/nextion/sensor/__init__.py
Normal file
99
esphome/components/nextion/sensor/__init__.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
UNIT_EMPTY,
|
||||
ICON_EMPTY,
|
||||
CONF_COMPONENT_ID,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
)
|
||||
from .. import nextion_ns, CONF_NEXTION_ID
|
||||
|
||||
from ..base_component import (
|
||||
setup_component_core_,
|
||||
CONFIG_SENSOR_COMPONENT_SCHEMA,
|
||||
CONF_VARIABLE_NAME,
|
||||
CONF_COMPONENT_NAME,
|
||||
CONF_PRECISION,
|
||||
CONF_WAVE_CHANNEL_ID,
|
||||
CONF_WAVE_MAX_VALUE,
|
||||
CONF_WAVEFORM_SEND_LAST_VALUE,
|
||||
CONF_WAVE_MAX_LENGTH,
|
||||
)
|
||||
|
||||
|
||||
CODEOWNERS = ["@senexcrenshaw"]
|
||||
|
||||
NextionSensor = nextion_ns.class_("NextionSensor", sensor.Sensor, cg.PollingComponent)
|
||||
|
||||
|
||||
def CheckWaveID(value):
|
||||
value = cv.int_(value)
|
||||
if value < 0 or value > 3:
|
||||
raise cv.Invalid(f"Valid range for {CONF_WAVE_CHANNEL_ID} is 0-3")
|
||||
return value
|
||||
|
||||
|
||||
def _validate(config):
|
||||
if CONF_WAVE_CHANNEL_ID in config and CONF_COMPONENT_ID not in config:
|
||||
raise cv.Invalid(
|
||||
f"{CONF_COMPONENT_ID} is required when {CONF_WAVE_CHANNEL_ID} is set"
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_EMPTY)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(NextionSensor),
|
||||
cv.Optional(CONF_PRECISION, default=0): cv.int_range(min=0, max=8),
|
||||
cv.Optional(CONF_WAVE_CHANNEL_ID): CheckWaveID,
|
||||
cv.Optional(CONF_COMPONENT_ID): cv.uint8_t,
|
||||
cv.Optional(CONF_WAVE_MAX_LENGTH, default=255): cv.int_range(
|
||||
min=1, max=1024
|
||||
),
|
||||
cv.Optional(CONF_WAVE_MAX_VALUE, default=100): cv.int_range(
|
||||
min=1, max=1024
|
||||
),
|
||||
cv.Optional(CONF_WAVEFORM_SEND_LAST_VALUE, default=True): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(CONFIG_SENSOR_COMPONENT_SCHEMA)
|
||||
.extend(cv.polling_component_schema("never")),
|
||||
cv.has_exactly_one_key(CONF_COMPONENT_ID, CONF_COMPONENT_NAME, CONF_VARIABLE_NAME),
|
||||
_validate,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
hub = await cg.get_variable(config[CONF_NEXTION_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], hub)
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
|
||||
cg.add(hub.register_sensor_component(var))
|
||||
|
||||
await setup_component_core_(var, config, ".val")
|
||||
|
||||
if CONF_PRECISION in config:
|
||||
cg.add(var.set_precision(config[CONF_PRECISION]))
|
||||
|
||||
if CONF_COMPONENT_ID in config:
|
||||
cg.add(var.set_component_id(config[CONF_COMPONENT_ID]))
|
||||
|
||||
if CONF_WAVE_CHANNEL_ID in config:
|
||||
cg.add(var.set_wave_channel_id(config[CONF_WAVE_CHANNEL_ID]))
|
||||
|
||||
if CONF_WAVEFORM_SEND_LAST_VALUE in config:
|
||||
cg.add(var.set_waveform_send_last_value(config[CONF_WAVEFORM_SEND_LAST_VALUE]))
|
||||
|
||||
if CONF_WAVE_MAX_VALUE in config:
|
||||
cg.add(var.set_wave_max_value(config[CONF_WAVE_MAX_VALUE]))
|
||||
|
||||
if CONF_WAVE_MAX_LENGTH in config:
|
||||
cg.add(var.set_wave_max_length(config[CONF_WAVE_MAX_LENGTH]))
|
||||
110
esphome/components/nextion/sensor/nextion_sensor.cpp
Normal file
110
esphome/components/nextion/sensor/nextion_sensor.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "nextion_sensor.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
|
||||
static const char *const TAG = "nextion_sensor";
|
||||
|
||||
void NextionSensor::process_sensor(const std::string &variable_name, int state) {
|
||||
if (!this->nextion_->is_setup())
|
||||
return;
|
||||
|
||||
if (this->wave_chan_id_ == UINT8_MAX && this->variable_name_ == variable_name) {
|
||||
this->publish_state(state);
|
||||
ESP_LOGD(TAG, "Processed sensor \"%s\" state %d", variable_name.c_str(), state);
|
||||
}
|
||||
}
|
||||
|
||||
void NextionSensor::add_to_wave_buffer(float state) {
|
||||
this->needs_to_send_update_ = true;
|
||||
|
||||
int wave_state = (int) ((state / (float) this->wave_maxvalue_) * 100);
|
||||
|
||||
wave_buffer_.push_back(wave_state);
|
||||
|
||||
if (this->wave_buffer_.size() > this->wave_max_length_) {
|
||||
this->wave_buffer_.erase(this->wave_buffer_.begin());
|
||||
}
|
||||
}
|
||||
|
||||
void NextionSensor::update() {
|
||||
if (!this->nextion_->is_setup())
|
||||
return;
|
||||
|
||||
if (this->wave_chan_id_ == UINT8_MAX) {
|
||||
this->nextion_->add_to_get_queue(this);
|
||||
} else {
|
||||
if (this->send_last_value_) {
|
||||
this->add_to_wave_buffer(this->last_value_);
|
||||
}
|
||||
|
||||
this->wave_update_();
|
||||
}
|
||||
}
|
||||
|
||||
void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) {
|
||||
if (!this->nextion_->is_setup())
|
||||
return;
|
||||
|
||||
if (isnan(state))
|
||||
return;
|
||||
|
||||
if (this->wave_chan_id_ == UINT8_MAX) {
|
||||
if (send_to_nextion) {
|
||||
if (this->nextion_->is_sleeping() || !this->visible_) {
|
||||
this->needs_to_send_update_ = true;
|
||||
} else {
|
||||
this->needs_to_send_update_ = false;
|
||||
|
||||
if (this->precision_ > 0) {
|
||||
double to_multiply = pow(10, this->precision_);
|
||||
int state_value = (int) (state * to_multiply);
|
||||
|
||||
this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value);
|
||||
} else {
|
||||
this->nextion_->add_no_result_to_queue_with_set(this, (int) state);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this->send_last_value_) {
|
||||
this->last_value_ = state; // Update will handle setting the buffer
|
||||
} else {
|
||||
this->add_to_wave_buffer(state);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->wave_chan_id_ == UINT8_MAX) {
|
||||
if (publish) {
|
||||
this->publish_state(state);
|
||||
} else {
|
||||
this->raw_state = state;
|
||||
this->state = state;
|
||||
this->has_state_ = true;
|
||||
}
|
||||
}
|
||||
this->update_component_settings();
|
||||
|
||||
ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %lf", this->variable_name_.c_str(), state);
|
||||
}
|
||||
|
||||
void NextionSensor::wave_update_() {
|
||||
if (this->nextion_->is_sleeping() || this->wave_buffer_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef NEXTION_PROTOCOL_LOG
|
||||
size_t buffer_to_send =
|
||||
this->wave_buffer_.size() < 255 ? this->wave_buffer_.size() : 255; // ADDT command can only send 255
|
||||
|
||||
ESP_LOGN(TAG, "wave_update send %zu of %zu value(s) to wave nextion component id %d and wave channel id %d",
|
||||
buffer_to_send, this->wave_buffer_.size(), this->component_id_, this->wave_chan_id_);
|
||||
#endif
|
||||
|
||||
this->nextion_->add_addt_command_to_queue(this);
|
||||
}
|
||||
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
49
esphome/components/nextion/sensor/nextion_sensor.h
Normal file
49
esphome/components/nextion/sensor/nextion_sensor.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "../nextion_component.h"
|
||||
#include "../nextion_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
class NextionSensor;
|
||||
|
||||
class NextionSensor : public NextionComponent, public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
NextionSensor(NextionBase *nextion) { this->nextion_ = nextion; }
|
||||
void send_state_to_nextion() override { this->set_state(this->state, false, true); };
|
||||
|
||||
void update_component() override { this->update(); }
|
||||
void update() override;
|
||||
void add_to_wave_buffer(float state);
|
||||
void set_precision(uint8_t precision) { this->precision_ = precision; }
|
||||
void set_component_id(uint8_t component_id) { component_id_ = component_id; }
|
||||
void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; }
|
||||
void set_wave_max_value(uint32_t wave_maxvalue) { this->wave_maxvalue_ = wave_maxvalue; }
|
||||
void process_sensor(const std::string &variable_name, int state) override;
|
||||
|
||||
void set_state(float state) override { this->set_state(state, true, true); }
|
||||
void set_state(float state, bool publish) override { this->set_state(state, publish, true); }
|
||||
void set_state(float state, bool publish, bool send_to_nextion) override;
|
||||
|
||||
void set_waveform_send_last_value(bool send_last_value) { this->send_last_value_ = send_last_value; }
|
||||
uint8_t get_wave_chan_id() { return this->wave_chan_id_; }
|
||||
void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; }
|
||||
NextionQueueType get_queue_type() override {
|
||||
return this->wave_chan_id_ == UINT8_MAX ? NextionQueueType::SENSOR : NextionQueueType::WAVEFORM_SENSOR;
|
||||
}
|
||||
void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {}
|
||||
void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {
|
||||
this->set_state(state_value, publish, send_to_nextion);
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t precision_ = 0;
|
||||
uint32_t wave_maxvalue_ = 255;
|
||||
|
||||
float last_value_ = 0;
|
||||
bool send_last_value_ = true;
|
||||
void wave_update_();
|
||||
};
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
39
esphome/components/nextion/switch/__init__.py
Normal file
39
esphome/components/nextion/switch/__init__.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import switch
|
||||
|
||||
from esphome.const import CONF_ID
|
||||
from .. import nextion_ns, CONF_NEXTION_ID
|
||||
|
||||
from ..base_component import (
|
||||
setup_component_core_,
|
||||
CONF_COMPONENT_NAME,
|
||||
CONF_VARIABLE_NAME,
|
||||
CONFIG_SWITCH_COMPONENT_SCHEMA,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@senexcrenshaw"]
|
||||
|
||||
NextionSwitch = nextion_ns.class_("NextionSwitch", switch.Switch, cg.PollingComponent)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
switch.SWITCH_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(NextionSwitch),
|
||||
}
|
||||
)
|
||||
.extend(CONFIG_SWITCH_COMPONENT_SCHEMA)
|
||||
.extend(cv.polling_component_schema("never")),
|
||||
cv.has_exactly_one_key(CONF_COMPONENT_NAME, CONF_VARIABLE_NAME),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_NEXTION_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], hub)
|
||||
await cg.register_component(var, config)
|
||||
await switch.register_switch(var, config)
|
||||
|
||||
cg.add(hub.register_switch_component(var))
|
||||
|
||||
await setup_component_core_(var, config, ".val")
|
||||
52
esphome/components/nextion/switch/nextion_switch.cpp
Normal file
52
esphome/components/nextion/switch/nextion_switch.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "nextion_switch.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
|
||||
static const char *const TAG = "nextion_switch";
|
||||
|
||||
void NextionSwitch::process_bool(const std::string &variable_name, bool on) {
|
||||
if (!this->nextion_->is_setup())
|
||||
return;
|
||||
if (this->variable_name_ == variable_name) {
|
||||
this->publish_state(on);
|
||||
|
||||
ESP_LOGD(TAG, "Processed switch \"%s\" state %s", variable_name.c_str(), state ? "ON" : "OFF");
|
||||
}
|
||||
}
|
||||
|
||||
void NextionSwitch::update() {
|
||||
if (!this->nextion_->is_setup())
|
||||
return;
|
||||
this->nextion_->add_to_get_queue(this);
|
||||
}
|
||||
|
||||
void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) {
|
||||
if (!this->nextion_->is_setup())
|
||||
return;
|
||||
|
||||
if (send_to_nextion) {
|
||||
if (this->nextion_->is_sleeping() || !this->visible_) {
|
||||
this->needs_to_send_update_ = true;
|
||||
} else {
|
||||
this->needs_to_send_update_ = false;
|
||||
this->nextion_->add_no_result_to_queue_with_set(this, (int) state);
|
||||
}
|
||||
}
|
||||
if (publish) {
|
||||
this->publish_state(state);
|
||||
} else {
|
||||
this->state = state;
|
||||
}
|
||||
|
||||
this->update_component_settings();
|
||||
|
||||
ESP_LOGN(TAG, "Updated switch \"%s\" state %s", this->variable_name_.c_str(), ONOFF(state));
|
||||
}
|
||||
|
||||
void NextionSwitch::write_state(bool state) { this->set_state(state); }
|
||||
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
34
esphome/components/nextion/switch/nextion_switch.h
Normal file
34
esphome/components/nextion/switch/nextion_switch.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#include "../nextion_component.h"
|
||||
#include "../nextion_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
class NextionSwitch;
|
||||
|
||||
class NextionSwitch : public NextionComponent, public switch_::Switch, public PollingComponent {
|
||||
public:
|
||||
NextionSwitch(NextionBase *nextion) { this->nextion_ = nextion; }
|
||||
|
||||
void update() override;
|
||||
void update_component() override { this->update(); }
|
||||
void process_bool(const std::string &variable_name, bool on) override;
|
||||
|
||||
void set_state(bool state) override { this->set_state(state, true, true); }
|
||||
void set_state(bool state, bool publish) override { this->set_state(state, publish, true); }
|
||||
void set_state(bool state, bool publish, bool send_to_nextion) override;
|
||||
|
||||
void send_state_to_nextion() override { this->set_state(this->state, false, true); };
|
||||
NextionQueueType get_queue_type() override { return NextionQueueType::SWITCH; }
|
||||
void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {}
|
||||
void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {
|
||||
this->set_state(state_value != 0, publish, send_to_nextion);
|
||||
}
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
38
esphome/components/nextion/text_sensor/__init__.py
Normal file
38
esphome/components/nextion/text_sensor/__init__.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
from .. import nextion_ns, CONF_NEXTION_ID
|
||||
|
||||
from ..base_component import (
|
||||
setup_component_core_,
|
||||
CONFIG_TEXT_COMPONENT_SCHEMA,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@senexcrenshaw"]
|
||||
|
||||
NextionTextSensor = nextion_ns.class_(
|
||||
"NextionTextSensor", text_sensor.TextSensor, cg.PollingComponent
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(NextionTextSensor),
|
||||
}
|
||||
)
|
||||
.extend(CONFIG_TEXT_COMPONENT_SCHEMA)
|
||||
.extend(cv.polling_component_schema("never"))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_NEXTION_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], hub)
|
||||
await cg.register_component(var, config)
|
||||
await text_sensor.register_text_sensor(var, config)
|
||||
|
||||
cg.add(hub.register_textsensor_component(var))
|
||||
|
||||
await setup_component_core_(var, config, ".txt")
|
||||
@@ -0,0 +1,49 @@
|
||||
#include "nextion_textsensor.h"
|
||||
#include "esphome/core/util.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
static const char *const TAG = "nextion_textsensor";
|
||||
|
||||
void NextionTextSensor::process_text(const std::string &variable_name, const std::string &text_value) {
|
||||
if (!this->nextion_->is_setup())
|
||||
return;
|
||||
if (this->variable_name_ == variable_name) {
|
||||
this->publish_state(text_value);
|
||||
ESP_LOGD(TAG, "Processed text_sensor \"%s\" state \"%s\"", variable_name.c_str(), text_value.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void NextionTextSensor::update() {
|
||||
if (!this->nextion_->is_setup())
|
||||
return;
|
||||
this->nextion_->add_to_get_queue(this);
|
||||
}
|
||||
|
||||
void NextionTextSensor::set_state(const std::string &state, bool publish, bool send_to_nextion) {
|
||||
if (!this->nextion_->is_setup())
|
||||
return;
|
||||
|
||||
if (send_to_nextion) {
|
||||
if (this->nextion_->is_sleeping() || !this->visible_) {
|
||||
this->needs_to_send_update_ = true;
|
||||
} else {
|
||||
this->nextion_->add_no_result_to_queue_with_set(this, state);
|
||||
}
|
||||
}
|
||||
|
||||
if (publish) {
|
||||
this->publish_state(state);
|
||||
} else {
|
||||
this->state = state;
|
||||
this->has_state_ = true;
|
||||
}
|
||||
|
||||
this->update_component_settings();
|
||||
|
||||
ESP_LOGN(TAG, "Wrote state for text_sensor \"%s\" state \"%s\"", this->variable_name_.c_str(), state.c_str());
|
||||
}
|
||||
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
32
esphome/components/nextion/text_sensor/nextion_textsensor.h
Normal file
32
esphome/components/nextion/text_sensor/nextion_textsensor.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "../nextion_component.h"
|
||||
#include "../nextion_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nextion {
|
||||
class NextionTextSensor;
|
||||
|
||||
class NextionTextSensor : public NextionComponent, public text_sensor::TextSensor, public PollingComponent {
|
||||
public:
|
||||
NextionTextSensor(NextionBase *nextion) { this->nextion_ = nextion; }
|
||||
void update() override;
|
||||
void update_component() override { this->update(); }
|
||||
void on_state_changed(const std::string &state);
|
||||
|
||||
void process_text(const std::string &variable_name, const std::string &text_value) override;
|
||||
|
||||
void set_state(const std::string &state, bool publish) override { this->set_state(state, publish, true); }
|
||||
void set_state(const std::string &state) override { this->set_state(state, true, true); }
|
||||
void set_state(const std::string &state, bool publish, bool send_to_nextion) override;
|
||||
|
||||
void send_state_to_nextion() override { this->set_state(this->state, false, true); };
|
||||
NextionQueueType get_queue_type() override { return NextionQueueType::TEXT_SENSOR; }
|
||||
void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {}
|
||||
void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {
|
||||
this->set_state(state_value, publish, send_to_nextion);
|
||||
}
|
||||
};
|
||||
} // namespace nextion
|
||||
} // namespace esphome
|
||||
@@ -1,3 +1,4 @@
|
||||
from typing import Optional
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
@@ -66,12 +67,18 @@ NUMBER_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
|
||||
)
|
||||
|
||||
|
||||
async def setup_number_core_(var, config):
|
||||
async def setup_number_core_(
|
||||
var, config, *, min_value: float, max_value: float, step: Optional[float]
|
||||
):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
if CONF_INTERNAL in config:
|
||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||
|
||||
cg.add(var.set_icon(config[CONF_ICON]))
|
||||
cg.add(var.traits.set_icon(config[CONF_ICON]))
|
||||
cg.add(var.traits.set_min_value(min_value))
|
||||
cg.add(var.traits.set_max_value(max_value))
|
||||
if step is not None:
|
||||
cg.add(var.traits.set_step(step))
|
||||
|
||||
for conf in config.get(CONF_ON_VALUE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
@@ -92,16 +99,24 @@ async def setup_number_core_(var, config):
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
|
||||
async def register_number(var, config):
|
||||
async def register_number(
|
||||
var, config, *, min_value: float, max_value: float, step: Optional[float] = None
|
||||
):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_number(var))
|
||||
await setup_number_core_(var, config)
|
||||
await setup_number_core_(
|
||||
var, config, min_value=min_value, max_value=max_value, step=step
|
||||
)
|
||||
|
||||
|
||||
async def new_number(config):
|
||||
async def new_number(
|
||||
config, *, min_value: float, max_value: float, step: Optional[float] = None
|
||||
):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await register_number(var, config)
|
||||
await register_number(
|
||||
var, config, min_value=min_value, max_value=max_value, step=step
|
||||
)
|
||||
return var
|
||||
|
||||
|
||||
|
||||
@@ -8,67 +8,38 @@ static const char *const TAG = "number";
|
||||
|
||||
void NumberCall::perform() {
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
if (this->value_.has_value()) {
|
||||
auto value = *this->value_;
|
||||
uint8_t accuracy = this->parent_->get_accuracy_decimals();
|
||||
float min_value = this->parent_->get_min_value();
|
||||
if (value < min_value) {
|
||||
ESP_LOGW(TAG, " Value %s must not be less than minimum %s", value_accuracy_to_string(value, accuracy).c_str(),
|
||||
value_accuracy_to_string(min_value, accuracy).c_str());
|
||||
this->value_.reset();
|
||||
return;
|
||||
}
|
||||
float max_value = this->parent_->get_max_value();
|
||||
if (value > max_value) {
|
||||
ESP_LOGW(TAG, " Value %s must not be larger than maximum %s", value_accuracy_to_string(value, accuracy).c_str(),
|
||||
value_accuracy_to_string(max_value, accuracy).c_str());
|
||||
this->value_.reset();
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, " Value: %s", value_accuracy_to_string(*this->value_, accuracy).c_str());
|
||||
this->parent_->set(*this->value_);
|
||||
if (!this->value_.has_value() || isnan(*this->value_)) {
|
||||
ESP_LOGW(TAG, "No value set for NumberCall");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &traits = this->parent_->traits;
|
||||
auto value = *this->value_;
|
||||
|
||||
float min_value = traits.get_min_value();
|
||||
if (value < min_value) {
|
||||
ESP_LOGW(TAG, " Value %f must not be less than minimum %f", value, min_value);
|
||||
return;
|
||||
}
|
||||
float max_value = traits.get_max_value();
|
||||
if (value > max_value) {
|
||||
ESP_LOGW(TAG, " Value %f must not be greater than maximum %f", value, max_value);
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, " Value: %f", *this->value_);
|
||||
this->parent_->control(*this->value_);
|
||||
}
|
||||
|
||||
NumberCall &NumberCall::set_value(float value) {
|
||||
this->value_ = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const optional<float> &NumberCall::get_value() const { return this->value_; }
|
||||
|
||||
NumberCall Number::make_call() { return NumberCall(this); }
|
||||
|
||||
void Number::publish_state(float state) {
|
||||
this->has_state_ = true;
|
||||
this->state = state;
|
||||
ESP_LOGD(TAG, "'%s': Sending state %.5f", this->get_name().c_str(), state);
|
||||
ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state);
|
||||
this->state_callback_.call(state);
|
||||
}
|
||||
|
||||
uint32_t Number::update_interval() { return 0; }
|
||||
Number::Number(const std::string &name) : Nameable(name), state(NAN) {}
|
||||
Number::Number() : Number("") {}
|
||||
|
||||
void Number::add_on_state_callback(std::function<void(float)> &&callback) {
|
||||
this->state_callback_.add(std::move(callback));
|
||||
}
|
||||
void Number::set_icon(const std::string &icon) { this->icon_ = icon; }
|
||||
std::string Number::get_icon() { return *this->icon_; }
|
||||
int8_t Number::get_accuracy_decimals() {
|
||||
// use printf %g to find number of digits based on step
|
||||
char buf[32];
|
||||
sprintf(buf, "%.5g", this->step_);
|
||||
std::string str{buf};
|
||||
size_t dot_pos = str.find('.');
|
||||
if (dot_pos == std::string::npos)
|
||||
return 0;
|
||||
|
||||
return str.length() - dot_pos - 1;
|
||||
}
|
||||
float Number::get_state() const { return this->state; }
|
||||
|
||||
bool Number::has_state() const { return this->has_state_; }
|
||||
|
||||
uint32_t Number::hash_base() { return 2282307003UL; }
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ namespace number {
|
||||
#define LOG_NUMBER(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||
if (!(obj)->traits.get_icon().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \
|
||||
} \
|
||||
}
|
||||
|
||||
@@ -19,72 +19,56 @@ class Number;
|
||||
class NumberCall {
|
||||
public:
|
||||
explicit NumberCall(Number *parent) : parent_(parent) {}
|
||||
NumberCall &set_value(float value);
|
||||
void perform();
|
||||
|
||||
const optional<float> &get_value() const;
|
||||
NumberCall &set_value(float value) {
|
||||
value_ = value;
|
||||
return *this;
|
||||
}
|
||||
const optional<float> &get_value() const { return value_; }
|
||||
|
||||
protected:
|
||||
Number *const parent_;
|
||||
optional<float> value_;
|
||||
};
|
||||
|
||||
class NumberTraits {
|
||||
public:
|
||||
void set_min_value(float min_value) { min_value_ = min_value; }
|
||||
float get_min_value() const { return min_value_; }
|
||||
void set_max_value(float max_value) { max_value_ = max_value; }
|
||||
float get_max_value() const { return max_value_; }
|
||||
void set_step(float step) { step_ = step; }
|
||||
float get_step() const { return step_; }
|
||||
void set_icon(std::string icon) { icon_ = std::move(icon); }
|
||||
const std::string &get_icon() const { return icon_; }
|
||||
|
||||
protected:
|
||||
float min_value_ = NAN;
|
||||
float max_value_ = NAN;
|
||||
float step_ = NAN;
|
||||
std::string icon_;
|
||||
};
|
||||
|
||||
/** Base-class for all numbers.
|
||||
*
|
||||
* A number can use publish_state to send out a new value.
|
||||
*/
|
||||
class Number : public Nameable {
|
||||
public:
|
||||
explicit Number();
|
||||
explicit Number(const std::string &name);
|
||||
|
||||
/** Manually set the icon of this number. By default the number's default defined by icon() is used.
|
||||
*
|
||||
* @param icon The icon, for example "mdi:flash". "" to disable.
|
||||
*/
|
||||
void set_icon(const std::string &icon);
|
||||
/// Get the Home Assistant Icon. Uses the manual override if specified or the default value instead.
|
||||
std::string get_icon();
|
||||
|
||||
/// Getter-syntax for .state.
|
||||
float get_state() const;
|
||||
|
||||
/// Get the accuracy in decimals. Based on the step value.
|
||||
int8_t get_accuracy_decimals();
|
||||
|
||||
/** Publish the current state to the front-end.
|
||||
*/
|
||||
void publish_state(float state);
|
||||
|
||||
NumberCall make_call();
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
/// Add a callback that will be called every time the state changes.
|
||||
void add_on_state_callback(std::function<void(float)> &&callback);
|
||||
|
||||
/** This member variable stores the last state.
|
||||
*
|
||||
* On startup, when no state is available yet, this is NAN (not-a-number) and the validity
|
||||
* can be checked using has_state().
|
||||
*
|
||||
* This is exposed through a member variable for ease of use in esphome lambdas.
|
||||
*/
|
||||
float state;
|
||||
|
||||
void publish_state(float state);
|
||||
|
||||
NumberCall make_call() { return NumberCall(this); }
|
||||
void set(float value) { make_call().set_value(value).perform(); }
|
||||
|
||||
void add_on_state_callback(std::function<void(float)> &&callback);
|
||||
|
||||
NumberTraits traits;
|
||||
|
||||
/// Return whether this number has gotten a full state yet.
|
||||
bool has_state() const;
|
||||
|
||||
/// Return with which interval the number is polled. Return 0 for non-polling mode.
|
||||
virtual uint32_t update_interval();
|
||||
|
||||
void set_min_value(float min_value) { this->min_value_ = min_value; }
|
||||
void set_max_value(float max_value) { this->max_value_ = max_value; }
|
||||
void set_step(float step) { this->step_ = step; }
|
||||
|
||||
float get_min_value() const { return this->min_value_; }
|
||||
float get_max_value() const { return this->max_value_; }
|
||||
float get_step() const { return this->step_; }
|
||||
bool has_state() const { return has_state_; }
|
||||
|
||||
protected:
|
||||
friend class NumberCall;
|
||||
@@ -95,17 +79,12 @@ class Number : public Nameable {
|
||||
*
|
||||
* @param value The value as validated by the NumberCall.
|
||||
*/
|
||||
virtual void set(float value) = 0;
|
||||
virtual void control(float value) = 0;
|
||||
|
||||
uint32_t hash_base() override;
|
||||
|
||||
CallbackManager<void(float)> state_callback_;
|
||||
/// Override the icon advertised to Home Assistant, otherwise number's icon will be used.
|
||||
optional<std::string> icon_;
|
||||
bool has_state_{false};
|
||||
float step_{1.0};
|
||||
float min_value_{0};
|
||||
float max_value_{100};
|
||||
};
|
||||
|
||||
} // namespace number
|
||||
|
||||
@@ -35,9 +35,9 @@ void PIDClimate::control(const climate::ClimateCall &call) {
|
||||
if (call.get_target_temperature().has_value())
|
||||
this->target_temperature = *call.get_target_temperature();
|
||||
|
||||
// If switching to non-auto mode, set output immediately
|
||||
if (this->mode != climate::CLIMATE_MODE_HEAT_COOL)
|
||||
this->handle_non_auto_mode_();
|
||||
// If switching to off mode, set output immediately
|
||||
if (this->mode == climate::CLIMATE_MODE_OFF)
|
||||
this->write_output_(0.0f);
|
||||
|
||||
this->publish_state();
|
||||
}
|
||||
@@ -98,15 +98,6 @@ void PIDClimate::write_output_(float value) {
|
||||
}
|
||||
this->pid_computed_callback_.call();
|
||||
}
|
||||
void PIDClimate::handle_non_auto_mode_() {
|
||||
// in non-auto mode, switch directly to appropriate action
|
||||
// - OFF mode -> Output at 0%
|
||||
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
||||
this->write_output_(0.0);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
void PIDClimate::update_pid_() {
|
||||
float value;
|
||||
if (isnan(this->current_temperature) || isnan(this->target_temperature)) {
|
||||
@@ -135,7 +126,7 @@ void PIDClimate::update_pid_() {
|
||||
}
|
||||
|
||||
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
||||
this->handle_non_auto_mode_();
|
||||
this->write_output_(0.0);
|
||||
} else {
|
||||
this->write_output_(value);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ class PIDClimate : public climate::Climate, public Component {
|
||||
bool supports_heat_() const { return this->heat_output_ != nullptr; }
|
||||
|
||||
void write_output_(float value);
|
||||
void handle_non_auto_mode_();
|
||||
|
||||
/// The sensor used for getting the current temperature
|
||||
sensor::Sensor *sensor_;
|
||||
|
||||
@@ -11,8 +11,8 @@ from esphome.const import (
|
||||
CONF_TOTAL,
|
||||
CONF_VALUE,
|
||||
ICON_PULSE,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_NONE,
|
||||
UNIT_PULSES,
|
||||
UNIT_PULSES_PER_MINUTE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
@@ -59,7 +59,12 @@ CONFIG_SCHEMA = sensor.sensor_schema(
|
||||
cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter,
|
||||
cv.Optional(CONF_TIMEOUT, default="5min"): validate_timeout,
|
||||
cv.Optional(CONF_TOTAL): sensor.sensor_schema(
|
||||
UNIT_PULSES, ICON_PULSE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
|
||||
UNIT_PULSES,
|
||||
ICON_PULSE,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -12,8 +12,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_EMPTY,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_NONE,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_WATT,
|
||||
@@ -47,7 +47,8 @@ CONFIG_SCHEMA = (
|
||||
ICON_EMPTY,
|
||||
0,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_NONE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -17,8 +17,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_ENERGY,
|
||||
ICON_EMPTY,
|
||||
ICON_CURRENT_AC,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_NONE,
|
||||
UNIT_HERTZ,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
@@ -54,7 +54,8 @@ CONFIG_SCHEMA = (
|
||||
ICON_EMPTY,
|
||||
0,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_NONE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
),
|
||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||
UNIT_HERTZ,
|
||||
|
||||
@@ -41,6 +41,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
|
||||
bool initialized_{false};
|
||||
std::vector<rmt_item32_t> rmt_temp_;
|
||||
esp_err_t error_code_{ESP_OK};
|
||||
bool inverted_{false};
|
||||
#endif
|
||||
uint8_t carrier_duty_percent_{50};
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace remote_transmitter {
|
||||
|
||||
static const char *const TAG = "remote_transmitter";
|
||||
|
||||
void RemoteTransmitterComponent::setup() {}
|
||||
void RemoteTransmitterComponent::setup() { this->configure_rmt(); }
|
||||
|
||||
void RemoteTransmitterComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Remote Transmitter...");
|
||||
@@ -50,6 +50,7 @@ void RemoteTransmitterComponent::configure_rmt() {
|
||||
} else {
|
||||
c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
|
||||
c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH;
|
||||
this->inverted_ = true;
|
||||
}
|
||||
|
||||
esp_err_t error = rmt_config(&c);
|
||||
@@ -95,10 +96,10 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
|
||||
val -= item;
|
||||
|
||||
if (rmt_i % 2 == 0) {
|
||||
rmt_item.level0 = static_cast<uint32_t>(level);
|
||||
rmt_item.level0 = static_cast<uint32_t>(level ^ this->inverted_);
|
||||
rmt_item.duration0 = static_cast<uint32_t>(item);
|
||||
} else {
|
||||
rmt_item.level1 = static_cast<uint32_t>(level);
|
||||
rmt_item.level1 = static_cast<uint32_t>(level ^ this->inverted_);
|
||||
rmt_item.duration1 = static_cast<uint32_t>(item);
|
||||
this->rmt_temp_.push_back(rmt_item);
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ from esphome.const import (
|
||||
ICON_CURRENT_AC,
|
||||
ICON_EMPTY,
|
||||
ICON_FLASH,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_NONE,
|
||||
UNIT_AMPERE,
|
||||
UNIT_DEGREES,
|
||||
UNIT_EMPTY,
|
||||
@@ -88,24 +88,36 @@ CONFIG_SCHEMA = (
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_IMPORT_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE
|
||||
UNIT_WATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
),
|
||||
cv.Optional(CONF_EXPORT_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE
|
||||
UNIT_WATT_HOURS,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
),
|
||||
cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_VOLT_AMPS_REACTIVE_HOURS,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_NONE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
),
|
||||
cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_VOLT_AMPS_REACTIVE_HOURS,
|
||||
ICON_EMPTY,
|
||||
2,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_NONE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@ from esphome.const import (
|
||||
CONF_ICON,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_LAST_RESET_TYPE,
|
||||
CONF_ON_RAW_VALUE,
|
||||
CONF_ON_VALUE,
|
||||
CONF_ON_VALUE_RANGE,
|
||||
@@ -30,6 +31,9 @@ from esphome.const import (
|
||||
CONF_NAME,
|
||||
CONF_MQTT_ID,
|
||||
CONF_FORCE_UPDATE,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
LAST_RESET_TYPE_NEVER,
|
||||
LAST_RESET_TYPE_NONE,
|
||||
UNIT_EMPTY,
|
||||
ICON_EMPTY,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
@@ -79,6 +83,15 @@ STATE_CLASSES = {
|
||||
}
|
||||
validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_")
|
||||
|
||||
LastResetTypes = sensor_ns.enum("LastResetType")
|
||||
LAST_RESET_TYPES = {
|
||||
LAST_RESET_TYPE_NONE: LastResetTypes.LAST_RESET_TYPE_NONE,
|
||||
LAST_RESET_TYPE_NEVER: LastResetTypes.LAST_RESET_TYPE_NEVER,
|
||||
LAST_RESET_TYPE_AUTO: LastResetTypes.LAST_RESET_TYPE_AUTO,
|
||||
}
|
||||
validate_last_reset_type = cv.enum(LAST_RESET_TYPES, lower=True, space="_")
|
||||
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
|
||||
@@ -168,6 +181,7 @@ SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
|
||||
cv.Optional(CONF_ACCURACY_DECIMALS): accuracy_decimals,
|
||||
cv.Optional(CONF_DEVICE_CLASS): device_class,
|
||||
cv.Optional(CONF_STATE_CLASS): validate_state_class,
|
||||
cv.Optional(CONF_LAST_RESET_TYPE): validate_last_reset_type,
|
||||
cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_EXPIRE_AFTER): cv.All(
|
||||
cv.requires_component("mqtt"),
|
||||
@@ -202,6 +216,7 @@ def sensor_schema(
|
||||
accuracy_decimals_: int,
|
||||
device_class_: Optional[str] = DEVICE_CLASS_EMPTY,
|
||||
state_class_: Optional[str] = STATE_CLASS_NONE,
|
||||
last_reset_type_: Optional[str] = LAST_RESET_TYPE_NONE,
|
||||
) -> cv.Schema:
|
||||
schema = SENSOR_SCHEMA
|
||||
if unit_of_measurement_ != UNIT_EMPTY:
|
||||
@@ -230,6 +245,14 @@ def sensor_schema(
|
||||
schema = schema.extend(
|
||||
{cv.Optional(CONF_STATE_CLASS, default=state_class_): validate_state_class}
|
||||
)
|
||||
if last_reset_type_ != LAST_RESET_TYPE_NONE:
|
||||
schema = schema.extend(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_LAST_RESET_TYPE, default=last_reset_type_
|
||||
): validate_last_reset_type
|
||||
}
|
||||
)
|
||||
return schema
|
||||
|
||||
|
||||
@@ -479,6 +502,8 @@ async def setup_sensor_core_(var, config):
|
||||
cg.add(var.set_icon(config[CONF_ICON]))
|
||||
if CONF_ACCURACY_DECIMALS in config:
|
||||
cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS]))
|
||||
if CONF_LAST_RESET_TYPE in config:
|
||||
cg.add(var.set_last_reset_type(config[CONF_LAST_RESET_TYPE]))
|
||||
cg.add(var.set_force_update(config[CONF_FORCE_UPDATE]))
|
||||
if config.get(CONF_FILTERS): # must exist and not be empty
|
||||
filters = await build_filters(config[CONF_FILTERS])
|
||||
|
||||
@@ -16,6 +16,18 @@ const char *state_class_to_string(StateClass state_class) {
|
||||
}
|
||||
}
|
||||
|
||||
const char *last_reset_type_to_string(LastResetType last_reset_type) {
|
||||
switch (last_reset_type) {
|
||||
case LAST_RESET_TYPE_NEVER:
|
||||
return "never";
|
||||
case LAST_RESET_TYPE_AUTO:
|
||||
return "auto";
|
||||
case LAST_RESET_TYPE_NONE:
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void Sensor::publish_state(float state) {
|
||||
this->raw_state = state;
|
||||
this->raw_callback_.call(state);
|
||||
@@ -64,6 +76,7 @@ void Sensor::set_state_class(const std::string &state_class) {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str());
|
||||
}
|
||||
}
|
||||
void Sensor::set_last_reset_type(LastResetType last_reset_type) { this->last_reset_type = last_reset_type; }
|
||||
std::string Sensor::get_unit_of_measurement() {
|
||||
if (this->unit_of_measurement_.has_value())
|
||||
return *this->unit_of_measurement_;
|
||||
|
||||
@@ -14,6 +14,10 @@ namespace sensor {
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||
} \
|
||||
ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \
|
||||
if ((obj)->state_class == sensor::STATE_CLASS_MEASUREMENT && \
|
||||
(obj)->last_reset_type != sensor::LAST_RESET_TYPE_NONE) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Last Reset Type: '%s'", prefix, last_reset_type_to_string((obj)->last_reset_type)); \
|
||||
} \
|
||||
ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \
|
||||
ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
@@ -37,6 +41,20 @@ enum StateClass : uint8_t {
|
||||
|
||||
const char *state_class_to_string(StateClass state_class);
|
||||
|
||||
/**
|
||||
* Sensor last reset types
|
||||
*/
|
||||
enum LastResetType : uint8_t {
|
||||
/// This sensor does not support resetting. ie, it is not accumulative
|
||||
LAST_RESET_TYPE_NONE = 0,
|
||||
/// This sensor is expected to never reset its value
|
||||
LAST_RESET_TYPE_NEVER = 1,
|
||||
/// This sensor may reset and Home Assistant will watch for this
|
||||
LAST_RESET_TYPE_AUTO = 2,
|
||||
};
|
||||
|
||||
const char *last_reset_type_to_string(LastResetType last_reset_type);
|
||||
|
||||
/** Base-class for all sensors.
|
||||
*
|
||||
* A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy.
|
||||
@@ -155,6 +173,12 @@ class Sensor : public Nameable {
|
||||
*/
|
||||
virtual std::string device_class();
|
||||
|
||||
// The Last reset type of this sensor
|
||||
LastResetType last_reset_type{LAST_RESET_TYPE_NONE};
|
||||
|
||||
/// Manually set the Home Assistant last reset type for this sensor.
|
||||
void set_last_reset_type(LastResetType last_reset_type);
|
||||
|
||||
/** A unique ID for this sensor, empty for no unique id. See unique ID requirements:
|
||||
* https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements
|
||||
*
|
||||
|
||||
@@ -4,10 +4,12 @@ import esphome.config_validation as cv
|
||||
from esphome.components import number
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_INITIAL_VALUE,
|
||||
CONF_LAMBDA,
|
||||
CONF_MAX_VALUE,
|
||||
CONF_MIN_VALUE,
|
||||
CONF_OPTIMISTIC,
|
||||
CONF_RESTORE_VALUE,
|
||||
CONF_STEP,
|
||||
)
|
||||
from .. import template_ns
|
||||
@@ -25,6 +27,17 @@ def validate_min_max(config):
|
||||
return config
|
||||
|
||||
|
||||
def validate(config):
|
||||
if CONF_LAMBDA in config:
|
||||
if CONF_OPTIMISTIC in config:
|
||||
raise cv.Invalid("optimistic cannot be used with lambda")
|
||||
if CONF_INITIAL_VALUE in config:
|
||||
raise cv.Invalid("initial_value cannot be used with lambda")
|
||||
if CONF_RESTORE_VALUE in config:
|
||||
raise cv.Invalid("restore_value cannot be used with lambda")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
number.NUMBER_SCHEMA.extend(
|
||||
{
|
||||
@@ -33,31 +46,43 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_MIN_VALUE): cv.float_,
|
||||
cv.Required(CONF_STEP): cv.positive_float,
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OPTIMISTIC): cv.boolean,
|
||||
cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_INITIAL_VALUE): cv.float_,
|
||||
cv.Optional(CONF_RESTORE_VALUE): cv.boolean,
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s")),
|
||||
validate_min_max,
|
||||
validate,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await number.register_number(var, config)
|
||||
await number.register_number(
|
||||
var,
|
||||
config,
|
||||
min_value=config[CONF_MIN_VALUE],
|
||||
max_value=config[CONF_MAX_VALUE],
|
||||
step=config[CONF_STEP],
|
||||
)
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(float)
|
||||
)
|
||||
cg.add(var.set_template(template_))
|
||||
|
||||
else:
|
||||
if CONF_OPTIMISTIC in config:
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
if CONF_INITIAL_VALUE in config:
|
||||
cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE]))
|
||||
if CONF_RESTORE_VALUE in config:
|
||||
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
|
||||
|
||||
if CONF_SET_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION]
|
||||
)
|
||||
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
|
||||
cg.add(var.set_min_value(config[CONF_MIN_VALUE]))
|
||||
cg.add(var.set_max_value(config[CONF_MAX_VALUE]))
|
||||
cg.add(var.set_step(config[CONF_STEP]))
|
||||
|
||||
@@ -6,34 +6,50 @@ namespace template_ {
|
||||
|
||||
static const char *const TAG = "template.number";
|
||||
|
||||
TemplateNumber::TemplateNumber() : set_trigger_(new Trigger<float>()) {}
|
||||
void TemplateNumber::setup() {
|
||||
if (this->f_.has_value())
|
||||
return;
|
||||
|
||||
float value;
|
||||
if (!this->restore_value_) {
|
||||
value = this->initial_value_;
|
||||
} else {
|
||||
this->pref_ = global_preferences.make_preference<float>(this->get_object_id_hash());
|
||||
if (!this->pref_.load(&value)) {
|
||||
if (!isnan(this->initial_value_))
|
||||
value = this->initial_value_;
|
||||
else
|
||||
value = this->traits.get_min_value();
|
||||
}
|
||||
}
|
||||
this->publish_state(value);
|
||||
}
|
||||
|
||||
void TemplateNumber::update() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
|
||||
auto val = (*this->f_)();
|
||||
if (val.has_value()) {
|
||||
this->publish_state(*val);
|
||||
}
|
||||
if (!val.has_value())
|
||||
return;
|
||||
|
||||
this->publish_state(*val);
|
||||
}
|
||||
|
||||
void TemplateNumber::set(float value) {
|
||||
void TemplateNumber::control(float value) {
|
||||
this->set_trigger_->trigger(value);
|
||||
|
||||
if (this->optimistic_)
|
||||
this->publish_state(value);
|
||||
|
||||
if (this->restore_value_)
|
||||
this->pref_.save(&value);
|
||||
}
|
||||
float TemplateNumber::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
void TemplateNumber::set_template(std::function<optional<float>()> &&f) { this->f_ = f; }
|
||||
void TemplateNumber::dump_config() {
|
||||
LOG_NUMBER("", "Template Number", this);
|
||||
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void TemplateNumber::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
Trigger<float> *TemplateNumber::get_set_trigger() const { return this->set_trigger_; };
|
||||
|
||||
} // namespace template_
|
||||
} // namespace esphome
|
||||
|
||||
@@ -3,27 +3,34 @@
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
|
||||
class TemplateNumber : public number::Number, public PollingComponent {
|
||||
public:
|
||||
TemplateNumber();
|
||||
void set_template(std::function<optional<float>()> &&f);
|
||||
void set_template(std::function<optional<float>()> &&f) { this->f_ = f; }
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
Trigger<float> *get_set_trigger() const;
|
||||
void set_optimistic(bool optimistic);
|
||||
Trigger<float> *get_set_trigger() const { return set_trigger_; }
|
||||
void set_optimistic(bool optimistic) { optimistic_ = optimistic; }
|
||||
void set_initial_value(float initial_value) { initial_value_ = initial_value; }
|
||||
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
|
||||
|
||||
protected:
|
||||
void set(float value) override;
|
||||
void control(float value) override;
|
||||
bool optimistic_{false};
|
||||
Trigger<float> *set_trigger_;
|
||||
float initial_value_{NAN};
|
||||
bool restore_value_{false};
|
||||
Trigger<float> *set_trigger_ = new Trigger<float>();
|
||||
optional<std::function<optional<float>()>> f_;
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
} // namespace template_
|
||||
|
||||
@@ -22,7 +22,10 @@ void CronTrigger::loop() {
|
||||
return;
|
||||
|
||||
if (this->last_check_.has_value()) {
|
||||
if (*this->last_check_ >= time) {
|
||||
if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > 900) {
|
||||
// We went back in time (a lot), probably caused by time synchronization
|
||||
ESP_LOGW(TAG, "Time has jumped back!");
|
||||
} else if (*this->last_check_ >= time) {
|
||||
// already handled this one
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, time
|
||||
from esphome.const import CONF_ID, CONF_TIME_ID
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_TIME_ID,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
ICON_EMPTY,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_EMPTY,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["time"]
|
||||
|
||||
@@ -11,13 +19,24 @@ TotalDailyEnergy = total_daily_energy_ns.class_(
|
||||
"TotalDailyEnergy", sensor.Sensor, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TotalDailyEnergy),
|
||||
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||
cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
UNIT_EMPTY,
|
||||
ICON_EMPTY,
|
||||
0,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
LAST_RESET_TYPE_AUTO,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TotalDailyEnergy),
|
||||
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||
cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -7,10 +7,11 @@ namespace esphome {
|
||||
namespace tuya {
|
||||
|
||||
static const char *const TAG = "tuya";
|
||||
static const int COMMAND_DELAY = 50;
|
||||
static const int COMMAND_DELAY = 10;
|
||||
static const int RECEIVE_TIMEOUT = 300;
|
||||
|
||||
void Tuya::setup() {
|
||||
this->set_interval("heartbeat", 10000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
|
||||
this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
|
||||
}
|
||||
|
||||
void Tuya::loop() {
|
||||
@@ -113,11 +114,19 @@ void Tuya::handle_char_(uint8_t c) {
|
||||
this->rx_message_.push_back(c);
|
||||
if (!this->validate_message_()) {
|
||||
this->rx_message_.clear();
|
||||
} else {
|
||||
this->last_rx_char_timestamp_ = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) {
|
||||
switch ((TuyaCommandType) command) {
|
||||
TuyaCommandType command_type = (TuyaCommandType) command;
|
||||
|
||||
if (this->expected_response_.has_value() && this->expected_response_ == command_type) {
|
||||
this->expected_response_.reset();
|
||||
}
|
||||
|
||||
switch (command_type) {
|
||||
case TuyaCommandType::HEARTBEAT:
|
||||
ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]);
|
||||
this->protocol_version_ = version;
|
||||
@@ -316,6 +325,25 @@ void Tuya::send_raw_command_(TuyaCommand command) {
|
||||
uint8_t version = 0;
|
||||
|
||||
this->last_command_timestamp_ = millis();
|
||||
switch (command.cmd) {
|
||||
case TuyaCommandType::HEARTBEAT:
|
||||
this->expected_response_ = TuyaCommandType::HEARTBEAT;
|
||||
break;
|
||||
case TuyaCommandType::PRODUCT_QUERY:
|
||||
this->expected_response_ = TuyaCommandType::PRODUCT_QUERY;
|
||||
break;
|
||||
case TuyaCommandType::CONF_QUERY:
|
||||
this->expected_response_ = TuyaCommandType::CONF_QUERY;
|
||||
break;
|
||||
case TuyaCommandType::DATAPOINT_DELIVER:
|
||||
this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT;
|
||||
break;
|
||||
case TuyaCommandType::DATAPOINT_QUERY:
|
||||
this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast<uint8_t>(command.cmd),
|
||||
version, hexencode(command.payload).c_str(), static_cast<uint8_t>(this->init_state_));
|
||||
@@ -331,9 +359,20 @@ void Tuya::send_raw_command_(TuyaCommand command) {
|
||||
}
|
||||
|
||||
void Tuya::process_command_queue_() {
|
||||
uint32_t delay = millis() - this->last_command_timestamp_;
|
||||
uint32_t now = millis();
|
||||
uint32_t delay = now - this->last_command_timestamp_;
|
||||
|
||||
if (now - this->last_rx_char_timestamp_ > RECEIVE_TIMEOUT) {
|
||||
this->rx_message_.clear();
|
||||
}
|
||||
|
||||
if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) {
|
||||
this->expected_response_.reset();
|
||||
}
|
||||
|
||||
// Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly
|
||||
if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty()) {
|
||||
if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty() &&
|
||||
!this->expected_response_.has_value()) {
|
||||
this->send_raw_command_(command_queue_.front());
|
||||
this->command_queue_.erase(command_queue_.begin());
|
||||
}
|
||||
@@ -345,7 +384,7 @@ void Tuya::send_command_(const TuyaCommand &command) {
|
||||
}
|
||||
|
||||
void Tuya::send_empty_command_(TuyaCommandType command) {
|
||||
send_command_(TuyaCommand{.cmd = command, .payload = std::vector<uint8_t>{0x04}});
|
||||
send_command_(TuyaCommand{.cmd = command, .payload = std::vector<uint8_t>{}});
|
||||
}
|
||||
|
||||
void Tuya::send_wifi_status_() {
|
||||
|
||||
@@ -107,12 +107,14 @@ class Tuya : public Component, public uart::UARTDevice {
|
||||
int gpio_status_ = -1;
|
||||
int gpio_reset_ = -1;
|
||||
uint32_t last_command_timestamp_ = 0;
|
||||
uint32_t last_rx_char_timestamp_ = 0;
|
||||
std::string product_ = "";
|
||||
std::vector<TuyaDatapointListener> listeners_;
|
||||
std::vector<TuyaDatapoint> datapoints_;
|
||||
std::vector<uint8_t> rx_message_;
|
||||
std::vector<uint8_t> ignore_mcu_update_on_datapoints_{};
|
||||
std::vector<TuyaCommand> command_queue_;
|
||||
optional<TuyaCommandType> expected_response_{};
|
||||
uint8_t wifi_status_ = -1;
|
||||
};
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ class ESP8266SoftwareSerial {
|
||||
class UARTComponent : public Component, public Stream {
|
||||
public:
|
||||
void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; }
|
||||
uint32_t get_baud_rate() const { return baud_rate_; }
|
||||
|
||||
uint32_t get_config();
|
||||
|
||||
|
||||
@@ -614,8 +614,9 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
|
||||
std::string WebServer::number_json(number::Number *obj, float value) {
|
||||
return json::build_json([obj, value](JsonObject &root) {
|
||||
root["id"] = "number-" + obj->get_object_id();
|
||||
std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals());
|
||||
root["state"] = state;
|
||||
char buffer[64];
|
||||
snprintf(buffer, sizeof(buffer), "%f", value);
|
||||
root["state"] = buffer;
|
||||
root["value"] = value;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
MAJOR_VERSION = 1
|
||||
MINOR_VERSION = 20
|
||||
PATCH_VERSION = "0b1"
|
||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||
__version__ = "1.20.4"
|
||||
|
||||
ESP_PLATFORM_ESP32 = "ESP32"
|
||||
ESP_PLATFORM_ESP8266 = "ESP8266"
|
||||
@@ -301,6 +297,7 @@ CONF_KEY = "key"
|
||||
CONF_LAMBDA = "lambda"
|
||||
CONF_LAST_CONFIDENCE = "last_confidence"
|
||||
CONF_LAST_FINGER_ID = "last_finger_id"
|
||||
CONF_LAST_RESET_TYPE = "last_reset_type"
|
||||
CONF_LATITUDE = "latitude"
|
||||
CONF_LENGTH = "length"
|
||||
CONF_LEVEL = "level"
|
||||
@@ -789,3 +786,10 @@ STATE_CLASS_NONE = ""
|
||||
|
||||
# The state represents a measurement in present time
|
||||
STATE_CLASS_MEASUREMENT = "measurement"
|
||||
|
||||
# This sensor does not support resetting. ie, it is not accumulative
|
||||
LAST_RESET_TYPE_NONE = ""
|
||||
# This sensor is expected to never reset its value
|
||||
LAST_RESET_TYPE_NEVER = "never"
|
||||
# This sensor may reset and Home Assistant will watch for this
|
||||
LAST_RESET_TYPE_AUTO = "auto"
|
||||
|
||||
@@ -782,10 +782,9 @@ def make_app(debug=get_bool_env(ENV_DEV)):
|
||||
|
||||
class StaticFileHandler(tornado.web.StaticFileHandler):
|
||||
def set_extra_headers(self, path):
|
||||
if debug:
|
||||
self.set_header(
|
||||
"Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"
|
||||
)
|
||||
self.set_header(
|
||||
"Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"
|
||||
)
|
||||
|
||||
app_settings = {
|
||||
"debug": debug,
|
||||
|
||||
@@ -1104,7 +1104,7 @@ def shorthand_input_pullup_pin(value):
|
||||
|
||||
def shorthand_analog_pin(value):
|
||||
value = analog_pin(value)
|
||||
return GPIO_FULL_INPUT_PIN_SCHEMA({CONF_NUMBER: value})
|
||||
return GPIO_FULL_ANALOG_PIN_SCHEMA({CONF_NUMBER: value})
|
||||
|
||||
|
||||
def validate_has_interrupt(value):
|
||||
|
||||
@@ -11,4 +11,4 @@ ifaddr==0.1.7
|
||||
platformio==5.1.1
|
||||
esptool==2.8
|
||||
click==7.1.2
|
||||
esphome-dashboard==20210623.0
|
||||
esphome-dashboard==20210728.0
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def sub(path, pattern, repl, expected_count=1):
|
||||
with open(path) as fh:
|
||||
content = fh.read()
|
||||
content, count = re.subn(pattern, repl, content, flags=re.MULTILINE)
|
||||
if expected_count is not None:
|
||||
assert count == expected_count, f"Pattern {pattern} replacement failed!"
|
||||
with open(path, "wt") as fh:
|
||||
fh.write(content)
|
||||
|
||||
|
||||
def write_version(version: str):
|
||||
for p in [
|
||||
".github/workflows/ci-docker.yml",
|
||||
".github/workflows/release-dev.yml",
|
||||
".github/workflows/release.yml",
|
||||
]:
|
||||
sub(p, r'base_version=".*"', f'base_version="{version}"')
|
||||
|
||||
sub(
|
||||
"docker/Dockerfile",
|
||||
r"ARG BUILD_FROM=esphome/esphome-base-amd64:.*",
|
||||
f"ARG BUILD_FROM=esphome/esphome-base-amd64:{version}",
|
||||
)
|
||||
sub(
|
||||
"docker/Dockerfile.lint",
|
||||
r"FROM esphome/esphome-lint-base:.*",
|
||||
f"FROM esphome/esphome-lint-base:{version}",
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("new_version", type=str)
|
||||
args = parser.parse_args()
|
||||
|
||||
version = args.new_version
|
||||
print(f"Bumping to {version}")
|
||||
write_version(version)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main() or 0)
|
||||
@@ -50,16 +50,10 @@ def sub(path, pattern, repl, expected_count=1):
|
||||
|
||||
|
||||
def write_version(version: Version):
|
||||
sub(
|
||||
"esphome/const.py", r"^MAJOR_VERSION = \d+$", f"MAJOR_VERSION = {version.major}"
|
||||
)
|
||||
sub(
|
||||
"esphome/const.py", r"^MINOR_VERSION = \d+$", f"MINOR_VERSION = {version.minor}"
|
||||
)
|
||||
sub(
|
||||
"esphome/const.py",
|
||||
r"^PATCH_VERSION = .*$",
|
||||
f'PATCH_VERSION = "{version.full_patch}"',
|
||||
r"^__version__ = .*$",
|
||||
f'__version__ = "{version}"',
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from helpers import git_ls_files, filter_changed
|
||||
import codecs
|
||||
import collections
|
||||
import fnmatch
|
||||
@@ -12,7 +13,6 @@ import functools
|
||||
import argparse
|
||||
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
from helpers import git_ls_files, filter_changed
|
||||
|
||||
|
||||
def find_all(a_str, sub):
|
||||
@@ -562,6 +562,7 @@ def lint_inclusive_language(fname, match):
|
||||
"esphome/components/number/number.h",
|
||||
"esphome/components/output/binary_output.h",
|
||||
"esphome/components/output/float_output.h",
|
||||
"esphome/components/nextion/nextion_base.h",
|
||||
"esphome/components/sensor/sensor.h",
|
||||
"esphome/components/stepper/stepper.h",
|
||||
"esphome/components/switch/switch.h",
|
||||
|
||||
@@ -1055,10 +1055,6 @@ binary_sensor:
|
||||
pin: GPIO27
|
||||
threshold: 1000
|
||||
id: btn_left
|
||||
- platform: nextion
|
||||
page_id: 0
|
||||
component_id: 2
|
||||
name: 'Nextion Component 2 Touch'
|
||||
- platform: template
|
||||
name: 'Garage Door Open'
|
||||
id: garage_door
|
||||
@@ -1882,11 +1878,6 @@ display:
|
||||
intensity: 3
|
||||
lambda: |-
|
||||
it.print("1234");
|
||||
- platform: nextion
|
||||
uart_id: uart0
|
||||
lambda: |-
|
||||
it.set_component_value("gauge", 50);
|
||||
it.set_component_text("textview", "Hello World!");
|
||||
- platform: pcd8544
|
||||
cs_pin: GPIO23
|
||||
dc_pin: GPIO23
|
||||
|
||||
@@ -269,6 +269,7 @@ wled:
|
||||
|
||||
adalight:
|
||||
|
||||
|
||||
sensor:
|
||||
- platform: apds9960
|
||||
type: proximity
|
||||
@@ -534,6 +535,15 @@ sensor:
|
||||
export_reactive_energy:
|
||||
name: 'Export Reactive Energy'
|
||||
|
||||
- platform: nextion
|
||||
id: testnumber
|
||||
name: 'testnumber'
|
||||
variable_name: testnumber
|
||||
- platform: nextion
|
||||
id: testwave
|
||||
name: 'testwave'
|
||||
component_id: 2
|
||||
wave_channel_id: 1
|
||||
time:
|
||||
- platform: homeassistant
|
||||
|
||||
@@ -605,7 +615,14 @@ binary_sensor:
|
||||
binary_sensors:
|
||||
- id: custom_binary_sensor
|
||||
name: Custom Binary Sensor
|
||||
|
||||
- platform: nextion
|
||||
page_id: 0
|
||||
component_id: 2
|
||||
name: 'Nextion Component 2 Touch'
|
||||
- platform: nextion
|
||||
id: r0_sensor
|
||||
name: 'R0 Sensor'
|
||||
component_name: page0.r0
|
||||
globals:
|
||||
- id: my_global_string
|
||||
type: std::string
|
||||
@@ -653,6 +670,11 @@ text_sensor:
|
||||
text_sensors:
|
||||
- id: custom_text_sensor
|
||||
name: Custom Text Sensor
|
||||
- platform: nextion
|
||||
name: text0
|
||||
id: text0
|
||||
update_interval: 4s
|
||||
component_name: text0
|
||||
|
||||
script:
|
||||
- id: my_script
|
||||
@@ -704,6 +726,10 @@ switch:
|
||||
switches:
|
||||
- id: custom_switch
|
||||
name: Custom Switch
|
||||
- platform: nextion
|
||||
id: r0
|
||||
name: 'R0 Switch'
|
||||
component_name: page0.r0
|
||||
|
||||
custom_component:
|
||||
lambda: |-
|
||||
@@ -1086,6 +1112,16 @@ display:
|
||||
id: my_matrix
|
||||
lambda: |-
|
||||
it.printdigit("hello");
|
||||
- platform: nextion
|
||||
uart_id: uart1
|
||||
tft_url: 'http://esphome.io/default35.tft'
|
||||
update_interval: 5s
|
||||
on_sleep:
|
||||
then:
|
||||
lambda: 'ESP_LOGD("display","Display went to sleep");'
|
||||
on_wake:
|
||||
then:
|
||||
lambda: 'ESP_LOGD("display","Display woke up");'
|
||||
|
||||
http_request:
|
||||
useragent: esphome/device
|
||||
|
||||
Reference in New Issue
Block a user