Compare commits

...

10 Commits

Author SHA1 Message Date
Jesse Hills
6a09d7c49b Merge pull request #2034 from esphome/bump-1.20.0b3
1.20.0b3
2021-07-19 08:44:04 +12:00
Jesse Hills
46e50ba53f Bump version to v1.20.0b3 2021-07-19 08:28:58 +12:00
Otto Winter
f1e3ff2ed2 Improve external components error messages (#2026) 2021-07-19 08:28:58 +12:00
Otto Winter
7787fa8f29 Dashboard disable assets caching (#2025) 2021-07-19 08:28:58 +12:00
Otto Winter
70902029f8 GH Actions CI use GHCR (#2027) 2021-07-19 08:28:58 +12:00
Otto Winter
4f9a56c884 Refactor docker build system and workflows (#2023) 2021-07-19 08:28:53 +12:00
Sean Vig
3715ba030b Fix ethernet component hostname handling (#2010)
Co-authored-by: Otto Winter <otto@otto-winter.com>
2021-07-19 08:25:08 +12:00
Jesse Hills
0c93be97a9 Merge pull request #2017 from esphome/bump-1.20.0b2
1.20.0b2
2021-07-15 14:59:08 +12:00
Jesse Hills
54eb6070fb Bump version to v1.20.0b2 2021-07-15 13:55:58 +12:00
SenexCrenshaw
4dbf1c521e Nextion upload and sensors (#1464)
Co-authored-by: Senex Crenshaw <senexcrenshaw@gmail.com>
2021-07-15 13:55:58 +12:00
48 changed files with 3932 additions and 1118 deletions

View File

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

View File

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

View File

@@ -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
View 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
}
]
}
]
}

View File

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

View File

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

@@ -13,6 +13,9 @@ __pycache__/
# Intellij Idea
.idea
# Vim
*.swp
# Hide some OS X stuff
.DS_Store
.AppleDouble

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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):
try:
ret = subprocess.run(cmd, 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,59 @@ 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.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"])
# Fetch remote ref
cmd = ["git", "fetch", "--", "origin"]
if CONF_REF in config:
cmd.append(config[CONF_REF])
_run_git_command(cmd)
# Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch)
_run_git_command(["git", "reset", "--hard", "FETCH_HEAD"])
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:

View File

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

View 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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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

View 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

View 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

View 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

View 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

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

View 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

View 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

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

View 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

View 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

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

View File

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

View 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

View File

@@ -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();

View File

@@ -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.0b3"
ESP_PLATFORM_ESP32 = "ESP32"
ESP_PLATFORM_ESP8266 = "ESP8266"

View File

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

View File

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

View File

@@ -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}"',
)

View File

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

View File

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

View File

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