This commit is contained in:
Alexander Pohl 2020-09-03 16:58:05 +02:00
commit 5ebf4223e2
420 changed files with 12835 additions and 1929 deletions

12
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,12 @@
blank_issues_enabled: false
contact_links:
- name: Issue Tracker
url: https://github.com/esphome/issues
about: Please create bug reports in the dedicated issue tracker.
- name: Feature Request Tracker
url: https://github.com/esphome/feature-requests
about: Please create feature requests in the dedicated feature request tracker.
- name: Frequently Asked Question
url: https://esphome.io/guides/faq.html
about: Please view the FAQ for common questions and what to include in a bug report.

View file

@ -1,8 +0,0 @@
# Set to false to create a new comment instead of updating the app's first one
updateComment: true
# Use a custom string, or set to false to disable
before: "✨ Good work on this PR so far! ✨ Unfortunately, the [ build]() is failing as of . Here's the output:"
# Use a custom string, or set to false to disable
after: "Thanks for contributing to this project!"

11
.github/config.yml vendored
View file

@ -1,11 +0,0 @@
# Configuration for sentiment-bot - https://github.com/behaviorbot/sentiment-bot
# *Required* toxicity threshold between 0 and .99 with the higher numbers being the most toxic
# Anything higher than this threshold will be marked as toxic and commented on
sentimentBotToxicityThreshold: .8
# *Required* Comment to reply with
sentimentBotReplyComment: >
Please be sure to review the code of conduct and be respectful of other users.
# Note: the bot will only work if your repository has a Code of Conduct

9
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,9 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "daily"
ignore:
# Hypotehsis is only used for testing and is updated quite often
- dependency-name: hypothesis

54
.github/workflows/ci-docker.yml vendored Normal file
View file

@ -0,0 +1,54 @@
name: CI for docker images
# Only run when docker paths change
on:
push:
branches: [dev, beta, master]
paths:
- 'docker/**'
- '.github/workflows/**'
pull_request:
paths:
- 'docker/**'
- '.github/workflows/**'
jobs:
check-docker:
name: Build docker containers
runs-on: ubuntu-latest
strategy:
matrix:
arch: [amd64, armv7, aarch64]
build_type: ["hassio", "docker"]
steps:
- uses: actions/checkout@v2
- name: Set up env variables
run: |
base_version="2.6.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 "::set-env name=BUILD_FROM::${build_from}"
echo "::set-env name=BUILD_TO::${build_to}"
echo "::set-env name=DOCKERFILE::${dockerfile}"
- 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.0.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}" \
.

176
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,176 @@
# THESE JOBS ARE COPIED IN release.yml and release-dev.yml
# PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE
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, master]
pull_request:
jobs:
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:latest
steps:
- uses: actions/checkout@v2
# Cache platformio intermediary files (like libraries etc)
# Note: platformio platform versions should be cached via the esphome-lint image
- name: Cache Platformio
uses: actions/cache@v1
with:
path: .pio
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
restore-keys: |
lint-cpp-pio-
# 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:latest
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
matrix:
split: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v2
# Cache platformio intermediary files (like libraries etc)
# Note: platformio platform versions should be cached via the esphome-lint image
- name: Cache Platformio
uses: actions/cache@v1
with:
path: .pio
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
restore-keys: |
lint-cpp-pio-
# 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:
matrix:
test:
- test1
- test2
- test3
- test4
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 tests/${{ matrix.test }}.yaml compile
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

View file

@ -0,0 +1,16 @@
{
"problemMatcher": [
{
"owner": "ci-custom",
"pattern": [
{
"regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4
}
]
}
]
}

View file

@ -0,0 +1,17 @@
{
"problemMatcher": [
{
"owner": "clang-tidy",
"pattern": [
{
"regexp": "^(.*):(\\d+):(\\d+):\\s+(error):\\s+(.*) \\[([a-z0-9,\\-]+)\\]\\s*$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
]
}
]
}

18
.github/workflows/matchers/gcc.json vendored Normal file
View file

@ -0,0 +1,18 @@
{
"problemMatcher": [
{
"owner": "gcc",
"severity": "error",
"pattern": [
{
"regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
]
}
]
}

View file

@ -0,0 +1,28 @@
{
"problemMatcher": [
{
"owner": "flake8",
"severity": "error",
"pattern": [
{
"regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$",
"file": 1,
"line": 2,
"message": 3
}
]
},
{
"owner": "pylint",
"severity": "error",
"pattern": [
{
"regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$",
"file": 1,
"line": 2,
"message": 3
}
]
}
]
}

18
.github/workflows/matchers/python.json vendored Normal file
View file

@ -0,0 +1,18 @@
{
"problemMatcher": [
{
"owner": "python",
"pattern": [
{
"regexp": "^\\s*File\\s\\\"(.*)\\\",\\sline\\s(\\d+),\\sin\\s(.*)$",
"file": 1,
"line": 2
},
{
"regexp": "^\\s*raise\\s(.*)\\(\\'(.*)\\'\\)$",
"message": 2
}
]
}
]
}

262
.github/workflows/release-dev.yml vendored Normal file
View file

@ -0,0 +1,262 @@
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:latest
steps:
- uses: actions/checkout@v2
# Cache platformio intermediary files (like libraries etc)
# Note: platformio platform versions should be cached via the esphome-lint image
- name: Cache Platformio
uses: actions/cache@v1
with:
path: .pio
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
restore-keys: |
lint-cpp-pio-
# 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:latest
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
matrix:
split: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v2
# Cache platformio intermediary files (like libraries etc)
# Note: platformio platform versions should be cached via the esphome-lint image
- name: Cache Platformio
uses: actions/cache@v1
with:
path: .pio
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
restore-keys: |
lint-cpp-pio-
# 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:
matrix:
test:
- test1
- test2
- test3
- test4
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 tests/${{ matrix.test }}.yaml compile
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 "::set-env name=TAG::${TAG}"
- name: Set up env variables
run: |
base_version="2.6.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 "::set-env name=BUILD_FROM::${build_from}"
echo "::set-env name=BUILD_TO::${build_to}"
echo "::set-env name=DOCKERFILE::${dockerfile}"
- 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.0.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 "::set-env name=TAG::${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}"
- 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

325
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,325 @@
name: Publish Release
on:
release:
types: [published]
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:latest
steps:
- uses: actions/checkout@v2
# Cache platformio intermediary files (like libraries etc)
# Note: platformio platform versions should be cached via the esphome-lint image
- name: Cache Platformio
uses: actions/cache@v1
with:
path: .pio
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
restore-keys: |
lint-cpp-pio-
# 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:latest
# Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files
strategy:
matrix:
split: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v2
# Cache platformio intermediary files (like libraries etc)
# Note: platformio platform versions should be cached via the esphome-lint image
- name: Cache Platformio
uses: actions/cache@v1
with:
path: .pio
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
restore-keys: |
lint-cpp-pio-
# 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:
matrix:
test:
- test1
- test2
- test3
- test4
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 tests/${{ matrix.test }}.yaml compile
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-pypi:
name: Build and publish to PyPi
if: github.repository == 'esphome/esphome'
needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: '3.x'
- name: Set up python environment
run: |
script/setup
pip install setuptools wheel twine
- name: Build
run: python setup.py sdist bdist_wheel
- name: Upload
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: twine upload dist/*
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]
build_type: ["hassio", "docker"]
steps:
- uses: actions/checkout@v2
- name: Set TAG
run: |
TAG="${GITHUB_REF#refs/tags/v}"
echo "::set-env name=TAG::${TAG}"
- name: Set up env variables
run: |
base_version="2.6.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
if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then
cache_tag="beta"
else
cache_tag="latest"
fi
# Set env variables so these values don't need to be calculated again
echo "::set-env name=BUILD_FROM::${build_from}"
echo "::set-env name=BUILD_TO::${build_to}"
echo "::set-env name=DOCKERFILE::${dockerfile}"
echo "::set-env name=CACHE_TAG::${cache_tag}"
- 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.0.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"
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_REF#refs/tags/v}"
echo "::set-env name=TAG::${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}"
- 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}
- name: Publish docker beta tag
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
deploy-hassio-repo:
if: github.repository == 'esphome/esphome'
runs-on: ubuntu-latest
needs: [deploy-docker]
steps:
- env:
TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }}
run: |
TAG="${GITHUB_REF#refs/tags/v}"
curl \
-u ":$TOKEN" \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \
-d "{\"ref\":\"master\",\"inputs\":{\"version\":\"$TAG\"}}"

1
.gitignore vendored
View file

@ -54,6 +54,7 @@ htmlcov/
.esphome .esphome
nosetests.xml nosetests.xml
coverage.xml coverage.xml
cov.xml
*.cover *.cover
.hypothesis/ .hypothesis/
.pytest_cache/ .pytest_cache/

View file

@ -1,342 +0,0 @@
---
# Based on https://gitlab.com/hassio-addons/addon-node-red/blob/master/.gitlab-ci.yml
variables:
DOCKER_DRIVER: overlay2
DOCKER_HOST: tcp://docker:2375/
BASE_VERSION: '2.0.1'
TZ: UTC
stages:
- lint
- test
- deploy
.lint: &lint
image: esphome/esphome-lint:latest
stage: lint
before_script:
- script/setup
tags:
- docker
.test: &test
image: esphome/esphome-lint:latest
stage: test
before_script:
- script/setup
tags:
- docker
.docker-base: &docker-base
image: esphome/esphome-base-builder
before_script:
- docker info
- docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD"
script:
- docker run --rm --privileged multiarch/qemu-user-static:4.1.0-1 --reset -p yes
- TAG="${CI_COMMIT_TAG#v}"
- TAG="${TAG:-${CI_COMMIT_SHA:0:7}}"
- echo "Tag ${TAG}"
- |
if [[ "${IS_HASSIO}" == "YES" ]]; then
BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:${BASE_VERSION}
BUILD_TO=esphome/esphome-hassio-${BUILD_ARCH}
DOCKERFILE=docker/Dockerfile.hassio
else
BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:${BASE_VERSION}
if [[ "${BUILD_ARCH}" == "amd64" ]]; then
BUILD_TO=esphome/esphome
else
BUILD_TO=esphome/esphome-${BUILD_ARCH}
fi
DOCKERFILE=docker/Dockerfile
fi
- |
docker build \
--build-arg "BUILD_FROM=${BUILD_FROM}" \
--build-arg "BUILD_VERSION=${TAG}" \
--tag "${BUILD_TO}:${TAG}" \
--file "${DOCKERFILE}" \
.
- |
if [[ "${RELEASE}" = "YES" ]]; then
echo "Pushing to ${BUILD_TO}:${TAG}"
docker push "${BUILD_TO}:${TAG}"
fi
- |
if [[ "${LATEST}" = "YES" ]]; then
echo "Pushing to :latest"
docker tag ${BUILD_TO}:${TAG} ${BUILD_TO}:latest
docker push ${BUILD_TO}:latest
fi
- |
if [[ "${BETA}" = "YES" ]]; then
echo "Pushing to :beta"
docker tag \
${BUILD_TO}:${TAG} \
${BUILD_TO}:beta
docker push ${BUILD_TO}:beta
fi
- |
if [[ "${DEV}" = "YES" ]]; then
echo "Pushing to :dev"
docker tag \
${BUILD_TO}:${TAG} \
${BUILD_TO}:dev
docker push ${BUILD_TO}:dev
fi
services:
- docker:dind
tags:
- docker
stage: deploy
lint-custom:
<<: *lint
script:
- script/ci-custom.py
lint-python:
<<: *lint
script:
- script/lint-python
lint-tidy:
<<: *lint
script:
- pio init --ide atom
- script/clang-tidy --all-headers --fix
- script/ci-suggest-changes
lint-format:
<<: *lint
script:
- script/clang-format -i
- script/ci-suggest-changes
test1:
<<: *test
script:
- esphome tests/test1.yaml compile
test2:
<<: *test
script:
- esphome tests/test2.yaml compile
test3:
<<: *test
script:
- esphome tests/test3.yaml compile
.deploy-pypi: &deploy-pypi
<<: *lint
stage: deploy
script:
- pip install twine wheel
- python setup.py sdist bdist_wheel
- twine upload dist/*
deploy-release:pypi:
<<: *deploy-pypi
only:
- /^v\d+\.\d+\.\d+$/
except:
- /^(?!master).+@/
deploy-beta:pypi:
<<: *deploy-pypi
only:
- /^v\d+\.\d+\.\d+b\d+$/
except:
- /^(?!rc).+@/
.latest: &latest
<<: *docker-base
only:
- /^v([0-9\.]+)$/
except:
- branches
.beta: &beta
<<: *docker-base
only:
- /^v([0-9\.]+b\d+)$/
except:
- branches
.dev: &dev
<<: *docker-base
only:
- dev
aarch64-beta-docker:
<<: *beta
variables:
BETA: "YES"
BUILD_ARCH: aarch64
IS_HASSIO: "NO"
RELEASE: "YES"
aarch64-beta-hassio:
<<: *beta
variables:
BETA: "YES"
BUILD_ARCH: aarch64
IS_HASSIO: "YES"
RELEASE: "YES"
aarch64-dev-docker:
<<: *dev
variables:
BUILD_ARCH: aarch64
DEV: "YES"
IS_HASSIO: "NO"
aarch64-dev-hassio:
<<: *dev
variables:
BUILD_ARCH: aarch64
DEV: "YES"
IS_HASSIO: "YES"
aarch64-latest-docker:
<<: *latest
variables:
BETA: "YES"
BUILD_ARCH: aarch64
IS_HASSIO: "NO"
LATEST: "YES"
RELEASE: "YES"
aarch64-latest-hassio:
<<: *latest
variables:
BETA: "YES"
BUILD_ARCH: aarch64
IS_HASSIO: "YES"
LATEST: "YES"
RELEASE: "YES"
amd64-beta-docker:
<<: *beta
variables:
BETA: "YES"
BUILD_ARCH: amd64
IS_HASSIO: "NO"
RELEASE: "YES"
amd64-beta-hassio:
<<: *beta
variables:
BETA: "YES"
BUILD_ARCH: amd64
IS_HASSIO: "YES"
RELEASE: "YES"
amd64-dev-docker:
<<: *dev
variables:
BUILD_ARCH: amd64
DEV: "YES"
IS_HASSIO: "NO"
amd64-dev-hassio:
<<: *dev
variables:
BUILD_ARCH: amd64
DEV: "YES"
IS_HASSIO: "YES"
amd64-latest-docker:
<<: *latest
variables:
BETA: "YES"
BUILD_ARCH: amd64
IS_HASSIO: "NO"
LATEST: "YES"
RELEASE: "YES"
amd64-latest-hassio:
<<: *latest
variables:
BETA: "YES"
BUILD_ARCH: amd64
IS_HASSIO: "YES"
LATEST: "YES"
RELEASE: "YES"
armv7-beta-docker:
<<: *beta
variables:
BETA: "YES"
BUILD_ARCH: armv7
IS_HASSIO: "NO"
RELEASE: "YES"
armv7-beta-hassio:
<<: *beta
variables:
BETA: "YES"
BUILD_ARCH: armv7
IS_HASSIO: "YES"
RELEASE: "YES"
armv7-dev-docker:
<<: *dev
variables:
BUILD_ARCH: armv7
DEV: "YES"
IS_HASSIO: "NO"
armv7-dev-hassio:
<<: *dev
variables:
BUILD_ARCH: armv7
DEV: "YES"
IS_HASSIO: "YES"
armv7-latest-docker:
<<: *latest
variables:
BETA: "YES"
BUILD_ARCH: armv7
IS_HASSIO: "NO"
LATEST: "YES"
RELEASE: "YES"
armv7-latest-hassio:
<<: *latest
variables:
BETA: "YES"
BUILD_ARCH: armv7
IS_HASSIO: "YES"
LATEST: "YES"
RELEASE: "YES"
i386-beta-docker:
<<: *beta
variables:
BETA: "YES"
BUILD_ARCH: i386
IS_HASSIO: "NO"
RELEASE: "YES"
i386-beta-hassio:
<<: *beta
variables:
BETA: "YES"
BUILD_ARCH: i386
IS_HASSIO: "YES"
RELEASE: "YES"
i386-dev-docker:
<<: *dev
variables:
BUILD_ARCH: i386
DEV: "YES"
IS_HASSIO: "NO"
i386-dev-hassio:
<<: *dev
variables:
BUILD_ARCH: i386
DEV: "YES"
IS_HASSIO: "YES"
i386-latest-docker:
<<: *latest
variables:
BETA: "YES"
BUILD_ARCH: i386
IS_HASSIO: "NO"
LATEST: "YES"
RELEASE: "YES"
i386-latest-hassio:
<<: *latest
variables:
BETA: "YES"
BUILD_ARCH: i386
IS_HASSIO: "YES"
LATEST: "YES"
RELEASE: "YES"

View file

@ -1,43 +0,0 @@
sudo: false
language: python
python: '3.6'
install: script/setup
cache:
directories:
- "~/.platformio"
matrix:
fast_finish: true
include:
- python: "3.7"
env: TARGET=Lint3.7
script:
- script/ci-custom.py
- flake8 esphome
- pylint esphome
- python: "3.6"
env: TARGET=Test3.6
script:
- esphome tests/test1.yaml compile
- esphome tests/test2.yaml compile
- esphome tests/test3.yaml compile
- env: TARGET=Cpp-Lint
dist: trusty
sudo: required
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty-7
packages:
- clang-tidy-7
- clang-format-7
before_script:
- pio init --ide atom
- clang-tidy-7 -version
- clang-format-7 -version
- clang-apply-replacements-7 -version
script:
- script/clang-tidy --all-headers -j 2 --fix
- script/clang-format -i -j 2
- script/ci-suggest-changes

69
CODEOWNERS Normal file
View file

@ -0,0 +1,69 @@
# This file is generated by script/build_codeowners.py
# People marked here will be automatically requested for a review
# when the code that they own is touched.
#
# Every time an issue is created with a label corresponding to an integration,
# the integration's code owner is automatically notified.
# Core Code
setup.py @esphome/core
esphome/*.py @esphome/core
esphome/core/* @esphome/core
# Integrations
esphome/components/ac_dimmer/* @glmnet
esphome/components/adc/* @esphome/core
esphome/components/api/* @OttoWinter
esphome/components/async_tcp/* @OttoWinter
esphome/components/bang_bang/* @OttoWinter
esphome/components/binary_sensor/* @esphome/core
esphome/components/captive_portal/* @OttoWinter
esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet
esphome/components/coolix/* @glmnet
esphome/components/cover/* @esphome/core
esphome/components/ct_clamp/* @jesserockz
esphome/components/debug/* @OttoWinter
esphome/components/dfplayer/* @glmnet
esphome/components/dht/* @OttoWinter
esphome/components/exposure_notifications/* @OttoWinter
esphome/components/fastled_base/* @OttoWinter
esphome/components/globals/* @esphome/core
esphome/components/gpio/* @esphome/core
esphome/components/homeassistant/* @OttoWinter
esphome/components/i2c/* @esphome/core
esphome/components/integration/* @OttoWinter
esphome/components/interval/* @esphome/core
esphome/components/json/* @OttoWinter
esphome/components/ledc/* @OttoWinter
esphome/components/light/* @esphome/core
esphome/components/logger/* @esphome/core
esphome/components/network/* @esphome/core
esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/pid/* @OttoWinter
esphome/components/pn532/* @OttoWinter
esphome/components/power_supply/* @esphome/core
esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz
esphome/components/rtttl/* @glmnet
esphome/components/script/* @esphome/core
esphome/components/sensor/* @esphome/core
esphome/components/shutdown/* @esphome/core
esphome/components/sim800l/* @glmnet
esphome/components/spi/* @esphome/core
esphome/components/substitutions/* @esphome/core
esphome/components/sun/* @OttoWinter
esphome/components/switch/* @esphome/core
esphome/components/tcl112/* @glmnet
esphome/components/time/* @OttoWinter
esphome/components/tm1637/* @glmnet
esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/sensor/* @jesserockz
esphome/components/tuya/switch/* @jesserockz
esphome/components/uart/* @esphome/core
esphome/components/ultrasonic/* @OttoWinter
esphome/components/version/* @esphome/core
esphome/components/web_server_base/* @OttoWinter
esphome/components/whirlpool/* @glmnet

View file

@ -1,5 +1,6 @@
include LICENSE include LICENSE
include README.md include README.md
include requirements.txt
include esphome/dashboard/templates/*.html include esphome/dashboard/templates/*.html
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
recursive-include esphome *.cpp *.h *.tcc recursive-include esphome *.cpp *.h *.tcc

View file

@ -1,12 +1,21 @@
ARG BUILD_FROM=esphome/esphome-base-amd64:2.0.1 ARG BUILD_FROM=esphome/esphome-base-amd64:2.6.0
FROM ${BUILD_FROM} FROM ${BUILD_FROM}
# First install requirements to leverage caching when requirements don't change
COPY requirements.txt /
RUN pip3 install --no-cache-dir -r /requirements.txt
# Then copy esphome and install
COPY . . COPY . .
RUN pip3 install --no-cache-dir -e . RUN pip3 install --no-cache-dir -e .
ENV USERNAME="" # Settings for dashboard
ENV PASSWORD="" ENV USERNAME="" PASSWORD=""
# The directory the user should mount their configuration files to
WORKDIR /config WORKDIR /config
# Set entrypoint to esphome so that the user doesn't have to type 'esphome'
# in every docker command twice
ENTRYPOINT ["esphome"] ENTRYPOINT ["esphome"]
# When no arguments given, start the dashboard in the workdir
CMD ["/config", "dashboard"] CMD ["/config", "dashboard"]

View file

@ -1,4 +1,4 @@
FROM esphome/esphome-base-amd64:2.0.1 FROM esphome/esphome-base-amd64:2.6.0
COPY . . COPY . .

View file

@ -1,11 +1,15 @@
ARG BUILD_FROM ARG BUILD_FROM
FROM ${BUILD_FROM} FROM ${BUILD_FROM}
# First install requirements to leverage caching when requirements don't change
COPY requirements.txt /
RUN pip3 install --no-cache-dir -r /requirements.txt
# Copy root filesystem # Copy root filesystem
COPY docker/rootfs/ / COPY docker/rootfs/ /
COPY setup.py setup.cfg MANIFEST.in /opt/esphome/
COPY esphome /opt/esphome/esphome
# Then copy esphome and install
COPY . /opt/esphome/
RUN pip3 install --no-cache-dir -e /opt/esphome RUN pip3 install --no-cache-dir -e /opt/esphome
# Build arguments # Build arguments

View file

@ -1,18 +1,7 @@
FROM esphome/esphome-base-amd64:2.0.1 FROM esphome/esphome-lint-base:2.6.0
RUN \ COPY requirements.txt requirements_test.txt /
apt-get update \ RUN pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt
&& apt-get install -y --no-install-recommends \
clang-format-7 \
clang-tidy-7 \
patch \
&& rm -rf \
/tmp/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*
COPY requirements_test.txt /requirements_test.txt
RUN pip3 install --no-cache-dir wheel && pip3 install --no-cache-dir -r /requirements_test.txt
VOLUME ["/esphome"] VOLUME ["/esphome"]
WORKDIR /esphome WORKDIR /esphome

0
docker/rootfs/etc/cont-init.d/30-esphome.sh Normal file → Executable file
View file

0
docker/rootfs/etc/cont-init.d/40-migrate.sh Normal file → Executable file
View file

0
docker/rootfs/etc/nginx/nginx.conf Executable file → Normal file
View file

View file

@ -12,29 +12,17 @@ from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
from esphome.helpers import color, indent from esphome.helpers import color, indent
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files, \
get_serial_ports
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def get_serial_ports():
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
from serial.tools.list_ports import comports
result = []
for port, desc, info in comports(include_links=True):
if not port:
continue
if "VID:PID" in info:
result.append((port, desc))
result.sort(key=lambda x: x[0])
return result
def choose_prompt(options): def choose_prompt(options):
if not options: if not options:
raise EsphomeError("Found no valid options for upload/logging, please make sure relevant " raise EsphomeError("Found no valid options for upload/logging, please make sure relevant "
"sections (ota, mqtt, ...) are in your configuration and/or the device " "sections (ota, api, mqtt, ...) are in your configuration and/or the "
"is plugged in.") "device is plugged in.")
if len(options) == 1: if len(options) == 1:
return options[0][1] return options[0][1]
@ -60,8 +48,8 @@ def choose_prompt(options):
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api): def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
options = [] options = []
for res, desc in get_serial_ports(): for port in get_serial_ports():
options.append((f"{res} ({desc})", res)) options.append((f"{port.path} ({port.description})", port.path))
if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config): if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config):
options.append((f"Over The Air ({CORE.address})", CORE.address)) options.append((f"Over The Air ({CORE.address})", CORE.address))
if default == 'OTA': if default == 'OTA':
@ -131,6 +119,11 @@ def wrap_to_code(name, comp):
def write_cpp(config): def write_cpp(config):
generate_cpp_contents(config)
return write_cpp_file()
def generate_cpp_contents(config):
_LOGGER.info("Generating C++ source...") _LOGGER.info("Generating C++ source...")
for name, component, conf in iter_components(CORE.config): for name, component, conf in iter_components(CORE.config):
@ -140,6 +133,8 @@ def write_cpp(config):
CORE.flush_tasks() CORE.flush_tasks()
def write_cpp_file():
writer.write_platformio_project() writer.write_platformio_project()
code_s = indent(CORE.cpp_main_section) code_s = indent(CORE.cpp_main_section)
@ -428,6 +423,8 @@ def parse_args(argv):
parser.add_argument('-q', '--quiet', help="Disable all esphome logs.", parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
action='store_true') action='store_true')
parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true') parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
parser.add_argument('-s', '--substitution', nargs=2, action='append',
help='Add a substitution', metavar=('key', 'value'))
parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*') parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*')
subparsers = parser.add_subparsers(help='Commands', dest='command') subparsers = parser.add_subparsers(help='Commands', dest='command')
@ -532,7 +529,7 @@ def run_esphome(argv):
CORE.config_path = conf_path CORE.config_path = conf_path
CORE.dashboard = args.dashboard CORE.dashboard = args.dashboard
config = read_config() config = read_config(dict(args.substitution) if args.substitution else {})
if config is None: if config is None:
return 1 return 1
CORE.config = config CORE.config = config

View file

@ -4,6 +4,8 @@ from esphome import pins
from esphome.components import output from esphome.components import output
from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD
CODEOWNERS = ['@glmnet']
ac_dimmer_ns = cg.esphome_ns.namespace('ac_dimmer') ac_dimmer_ns = cg.esphome_ns.namespace('ac_dimmer')
AcDimmer = ac_dimmer_ns.class_('AcDimmer', output.FloatOutput, cg.Component) AcDimmer = ac_dimmer_ns.class_('AcDimmer', output.FloatOutput, cg.Component)

View file

@ -0,0 +1,24 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart
from esphome.components.light.types import AddressableLightEffect
from esphome.components.light.effects import register_addressable_effect
from esphome.const import CONF_NAME, CONF_UART_ID
DEPENDENCIES = ['uart']
adalight_ns = cg.esphome_ns.namespace('adalight')
AdalightLightEffect = adalight_ns.class_(
'AdalightLightEffect', uart.UARTDevice, AddressableLightEffect)
CONFIG_SCHEMA = cv.Schema({})
@register_addressable_effect('adalight', AdalightLightEffect, "Adalight", {
cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)
})
def adalight_light_effect_to_code(config, effect_id):
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
yield uart.register_uart_device(effect, config)
yield effect

View file

@ -0,0 +1,140 @@
#include "adalight_light_effect.h"
#include "esphome/core/log.h"
namespace esphome {
namespace adalight {
static const char *TAG = "adalight_light_effect";
static const uint32_t ADALIGHT_ACK_INTERVAL = 1000;
static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000;
AdalightLightEffect::AdalightLightEffect(const std::string &name) : AddressableLightEffect(name) {}
void AdalightLightEffect::start() {
AddressableLightEffect::start();
last_ack_ = 0;
last_byte_ = 0;
last_reset_ = 0;
}
void AdalightLightEffect::stop() {
frame_.resize(0);
AddressableLightEffect::stop();
}
int AdalightLightEffect::get_frame_size_(int led_count) const {
// 3 bytes: Ada
// 2 bytes: LED count
// 1 byte: checksum
// 3 bytes per LED
return 3 + 2 + 1 + led_count * 3;
}
void AdalightLightEffect::reset_frame_(light::AddressableLight &it) {
int buffer_capacity = get_frame_size_(it.size());
frame_.clear();
frame_.reserve(buffer_capacity);
}
void AdalightLightEffect::blank_all_leds_(light::AddressableLight &it) {
for (int led = it.size(); led-- > 0;) {
it[led].set(light::ESPColor::BLACK);
}
}
void AdalightLightEffect::apply(light::AddressableLight &it, const light::ESPColor &current_color) {
const uint32_t now = millis();
if (now - this->last_ack_ >= ADALIGHT_ACK_INTERVAL) {
ESP_LOGV(TAG, "Sending ACK");
this->write_str("Ada\n");
this->last_ack_ = now;
}
if (!this->last_reset_) {
ESP_LOGW(TAG, "Frame: Reset.");
reset_frame_(it);
blank_all_leds_(it);
this->last_reset_ = now;
}
if (!this->frame_.empty() && now - this->last_byte_ >= ADALIGHT_RECEIVE_TIMEOUT) {
ESP_LOGW(TAG, "Frame: Receive timeout (size=%zu).", this->frame_.size());
reset_frame_(it);
blank_all_leds_(it);
}
if (this->available() > 0) {
ESP_LOGV(TAG, "Frame: Available (size=%d).", this->available());
}
while (this->available() != 0) {
uint8_t data;
if (!this->read_byte(&data))
break;
this->frame_.push_back(data);
this->last_byte_ = now;
switch (this->parse_frame_(it)) {
case INVALID:
ESP_LOGD(TAG, "Frame: Invalid (size=%zu, first=%d).", this->frame_.size(), this->frame_[0]);
reset_frame_(it);
break;
case PARTIAL:
break;
case CONSUMED:
ESP_LOGV(TAG, "Frame: Consumed (size=%zu).", this->frame_.size());
reset_frame_(it);
break;
}
}
}
AdalightLightEffect::Frame AdalightLightEffect::parse_frame_(light::AddressableLight &it) {
if (frame_.empty())
return INVALID;
// Check header: `Ada`
if (frame_[0] != 'A')
return INVALID;
if (frame_.size() > 1 && frame_[1] != 'd')
return INVALID;
if (frame_.size() > 2 && frame_[2] != 'a')
return INVALID;
// 3 bytes: Count Hi, Count Lo, Checksum
if (frame_.size() < 6)
return PARTIAL;
// Check checksum
uint16_t checksum = frame_[3] ^ frame_[4] ^ 0x55;
if (checksum != frame_[5])
return INVALID;
// Check if we received the full frame
uint16_t led_count = (frame_[3] << 8) + frame_[4] + 1;
auto buffer_size = get_frame_size_(led_count);
if (frame_.size() < buffer_size)
return PARTIAL;
// Apply lights
auto accepted_led_count = std::min<int>(led_count, it.size());
uint8_t *led_data = &frame_[6];
for (int led = 0; led < accepted_led_count; led++, led_data += 3) {
auto white = std::min(std::min(led_data[0], led_data[1]), led_data[2]);
it[led].set(light::ESPColor(led_data[0], led_data[1], led_data[2], white));
}
return CONSUMED;
}
} // namespace adalight
} // namespace esphome

View file

@ -0,0 +1,41 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/light/addressable_light_effect.h"
#include "esphome/components/uart/uart.h"
#include <vector>
namespace esphome {
namespace adalight {
class AdalightLightEffect : public light::AddressableLightEffect, public uart::UARTDevice {
public:
AdalightLightEffect(const std::string &name);
public:
void start() override;
void stop() override;
void apply(light::AddressableLight &it, const light::ESPColor &current_color) override;
protected:
enum Frame {
INVALID,
PARTIAL,
CONSUMED,
};
int get_frame_size_(int led_count) const;
void reset_frame_(light::AddressableLight &it);
void blank_all_leds_(light::AddressableLight &it);
Frame parse_frame_(light::AddressableLight &it);
protected:
uint32_t last_ack_{0};
uint32_t last_byte_{0};
uint32_t last_reset_{0};
std::vector<uint8_t> frame_;
};
} // namespace adalight
} // namespace esphome

View file

@ -0,0 +1 @@
CODEOWNERS = ['@esphome/core']

View file

@ -58,7 +58,7 @@ void ADCSensor::update() {
} }
float ADCSensor::sample() { float ADCSensor::sample() {
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
float value_v = analogRead(this->pin_) / 4095.0f; float value_v = analogRead(this->pin_) / 4095.0f; // NOLINT
switch (this->attenuation_) { switch (this->attenuation_) {
case ADC_0db: case ADC_0db:
value_v *= 1.1; value_v *= 1.1;
@ -80,7 +80,7 @@ float ADCSensor::sample() {
#ifdef USE_ADC_SENSOR_VCC #ifdef USE_ADC_SENSOR_VCC
return ESP.getVcc() / 1024.0f; return ESP.getVcc() / 1024.0f;
#else #else
return analogRead(this->pin_) / 1024.0f; return analogRead(this->pin_) / 1024.0f; // NOLINT
#endif #endif
#endif #endif
} }

View file

@ -18,7 +18,7 @@ void ADE7953::dump_config() {
} }
#define ADE_PUBLISH_(name, factor) \ #define ADE_PUBLISH_(name, factor) \
if (name) { \ if (name && this->name##_sensor_) { \
float value = *name / factor; \ float value = *name / factor; \
this->name##_sensor_->publish_state(value); \ this->name##_sensor_->publish_state(value); \
} }

View file

@ -8,6 +8,7 @@ from esphome.core import coroutine_with_priority
DEPENDENCIES = ['network'] DEPENDENCIES = ['network']
AUTO_LOAD = ['async_tcp'] AUTO_LOAD = ['async_tcp']
CODEOWNERS = ['@OttoWinter']
api_ns = cg.esphome_ns.namespace('api') api_ns = cg.esphome_ns.namespace('api')
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller) APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)

View file

@ -301,12 +301,17 @@ message ListEntitiesFanResponse {
bool supports_oscillation = 5; bool supports_oscillation = 5;
bool supports_speed = 6; bool supports_speed = 6;
bool supports_direction = 7;
} }
enum FanSpeed { enum FanSpeed {
FAN_SPEED_LOW = 0; FAN_SPEED_LOW = 0;
FAN_SPEED_MEDIUM = 1; FAN_SPEED_MEDIUM = 1;
FAN_SPEED_HIGH = 2; FAN_SPEED_HIGH = 2;
} }
enum FanDirection {
FAN_DIRECTION_FORWARD = 0;
FAN_DIRECTION_REVERSE = 1;
}
message FanStateResponse { message FanStateResponse {
option (id) = 23; option (id) = 23;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
@ -317,6 +322,7 @@ message FanStateResponse {
bool state = 2; bool state = 2;
bool oscillating = 3; bool oscillating = 3;
FanSpeed speed = 4; FanSpeed speed = 4;
FanDirection direction = 5;
} }
message FanCommandRequest { message FanCommandRequest {
option (id) = 31; option (id) = 31;
@ -331,6 +337,8 @@ message FanCommandRequest {
FanSpeed speed = 5; FanSpeed speed = 5;
bool has_oscillating = 6; bool has_oscillating = 6;
bool oscillating = 7; bool oscillating = 7;
bool has_direction = 8;
FanDirection direction = 9;
} }
// ==================== LIGHT ==================== // ==================== LIGHT ====================

View file

@ -248,6 +248,8 @@ bool APIConnection::send_fan_state(fan::FanState *fan) {
resp.oscillating = fan->oscillating; resp.oscillating = fan->oscillating;
if (traits.supports_speed()) if (traits.supports_speed())
resp.speed = static_cast<enums::FanSpeed>(fan->speed); resp.speed = static_cast<enums::FanSpeed>(fan->speed);
if (traits.supports_direction())
resp.direction = static_cast<enums::FanDirection>(fan->direction);
return this->send_fan_state_response(resp); return this->send_fan_state_response(resp);
} }
bool APIConnection::send_fan_info(fan::FanState *fan) { bool APIConnection::send_fan_info(fan::FanState *fan) {
@ -259,6 +261,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
msg.unique_id = get_default_unique_id("fan", fan); msg.unique_id = get_default_unique_id("fan", fan);
msg.supports_oscillation = traits.supports_oscillation(); msg.supports_oscillation = traits.supports_oscillation();
msg.supports_speed = traits.supports_speed(); msg.supports_speed = traits.supports_speed();
msg.supports_direction = traits.supports_direction();
return this->send_list_entities_fan_response(msg); return this->send_list_entities_fan_response(msg);
} }
void APIConnection::fan_command(const FanCommandRequest &msg) { void APIConnection::fan_command(const FanCommandRequest &msg) {
@ -273,6 +276,8 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
call.set_oscillating(msg.oscillating); call.set_oscillating(msg.oscillating);
if (msg.has_speed) if (msg.has_speed)
call.set_speed(static_cast<fan::FanSpeed>(msg.speed)); call.set_speed(static_cast<fan::FanSpeed>(msg.speed));
if (msg.has_direction)
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
call.perform(); call.perform();
} }
#endif #endif

View file

@ -52,6 +52,16 @@ template<> const char *proto_enum_to_string<enums::FanSpeed>(enums::FanSpeed val
return "UNKNOWN"; return "UNKNOWN";
} }
} }
template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirection value) {
switch (value) {
case enums::FAN_DIRECTION_FORWARD:
return "FAN_DIRECTION_FORWARD";
case enums::FAN_DIRECTION_REVERSE:
return "FAN_DIRECTION_REVERSE";
default:
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) { template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
switch (value) { switch (value) {
case enums::LOG_LEVEL_NONE: case enums::LOG_LEVEL_NONE:
@ -760,6 +770,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
this->supports_speed = value.as_bool(); this->supports_speed = value.as_bool();
return true; return true;
} }
case 7: {
this->supports_direction = value.as_bool();
return true;
}
default: default:
return false; return false;
} }
@ -799,6 +813,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(4, this->unique_id); buffer.encode_string(4, this->unique_id);
buffer.encode_bool(5, this->supports_oscillation); buffer.encode_bool(5, this->supports_oscillation);
buffer.encode_bool(6, this->supports_speed); buffer.encode_bool(6, this->supports_speed);
buffer.encode_bool(7, this->supports_direction);
} }
void ListEntitiesFanResponse::dump_to(std::string &out) const { void ListEntitiesFanResponse::dump_to(std::string &out) const {
char buffer[64]; char buffer[64];
@ -827,6 +842,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
out.append(" supports_speed: "); out.append(" supports_speed: ");
out.append(YESNO(this->supports_speed)); out.append(YESNO(this->supports_speed));
out.append("\n"); out.append("\n");
out.append(" supports_direction: ");
out.append(YESNO(this->supports_direction));
out.append("\n");
out.append("}"); out.append("}");
} }
bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
@ -843,6 +862,10 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->speed = value.as_enum<enums::FanSpeed>(); this->speed = value.as_enum<enums::FanSpeed>();
return true; return true;
} }
case 5: {
this->direction = value.as_enum<enums::FanDirection>();
return true;
}
default: default:
return false; return false;
} }
@ -862,6 +885,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(2, this->state); buffer.encode_bool(2, this->state);
buffer.encode_bool(3, this->oscillating); buffer.encode_bool(3, this->oscillating);
buffer.encode_enum<enums::FanSpeed>(4, this->speed); buffer.encode_enum<enums::FanSpeed>(4, this->speed);
buffer.encode_enum<enums::FanDirection>(5, this->direction);
} }
void FanStateResponse::dump_to(std::string &out) const { void FanStateResponse::dump_to(std::string &out) const {
char buffer[64]; char buffer[64];
@ -882,6 +906,10 @@ void FanStateResponse::dump_to(std::string &out) const {
out.append(" speed: "); out.append(" speed: ");
out.append(proto_enum_to_string<enums::FanSpeed>(this->speed)); out.append(proto_enum_to_string<enums::FanSpeed>(this->speed));
out.append("\n"); out.append("\n");
out.append(" direction: ");
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
out.append("\n");
out.append("}"); out.append("}");
} }
bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
@ -910,6 +938,14 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->oscillating = value.as_bool(); this->oscillating = value.as_bool();
return true; return true;
} }
case 8: {
this->has_direction = value.as_bool();
return true;
}
case 9: {
this->direction = value.as_enum<enums::FanDirection>();
return true;
}
default: default:
return false; return false;
} }
@ -932,6 +968,8 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::FanSpeed>(5, this->speed); buffer.encode_enum<enums::FanSpeed>(5, this->speed);
buffer.encode_bool(6, this->has_oscillating); buffer.encode_bool(6, this->has_oscillating);
buffer.encode_bool(7, this->oscillating); buffer.encode_bool(7, this->oscillating);
buffer.encode_bool(8, this->has_direction);
buffer.encode_enum<enums::FanDirection>(9, this->direction);
} }
void FanCommandRequest::dump_to(std::string &out) const { void FanCommandRequest::dump_to(std::string &out) const {
char buffer[64]; char buffer[64];
@ -964,6 +1002,14 @@ void FanCommandRequest::dump_to(std::string &out) const {
out.append(" oscillating: "); out.append(" oscillating: ");
out.append(YESNO(this->oscillating)); out.append(YESNO(this->oscillating));
out.append("\n"); out.append("\n");
out.append(" has_direction: ");
out.append(YESNO(this->has_direction));
out.append("\n");
out.append(" direction: ");
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
out.append("\n");
out.append("}"); out.append("}");
} }
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {

View file

@ -28,6 +28,10 @@ enum FanSpeed : uint32_t {
FAN_SPEED_MEDIUM = 1, FAN_SPEED_MEDIUM = 1,
FAN_SPEED_HIGH = 2, FAN_SPEED_HIGH = 2,
}; };
enum FanDirection : uint32_t {
FAN_DIRECTION_FORWARD = 0,
FAN_DIRECTION_REVERSE = 1,
};
enum LogLevel : uint32_t { enum LogLevel : uint32_t {
LOG_LEVEL_NONE = 0, LOG_LEVEL_NONE = 0,
LOG_LEVEL_ERROR = 1, LOG_LEVEL_ERROR = 1,
@ -279,6 +283,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
std::string unique_id{}; // NOLINT std::string unique_id{}; // NOLINT
bool supports_oscillation{false}; // NOLINT bool supports_oscillation{false}; // NOLINT
bool supports_speed{false}; // NOLINT bool supports_speed{false}; // NOLINT
bool supports_direction{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -289,10 +294,11 @@ class ListEntitiesFanResponse : public ProtoMessage {
}; };
class FanStateResponse : public ProtoMessage { class FanStateResponse : public ProtoMessage {
public: public:
uint32_t key{0}; // NOLINT uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT bool state{false}; // NOLINT
bool oscillating{false}; // NOLINT bool oscillating{false}; // NOLINT
enums::FanSpeed speed{}; // NOLINT enums::FanSpeed speed{}; // NOLINT
enums::FanDirection direction{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -302,13 +308,15 @@ class FanStateResponse : public ProtoMessage {
}; };
class FanCommandRequest : public ProtoMessage { class FanCommandRequest : public ProtoMessage {
public: public:
uint32_t key{0}; // NOLINT uint32_t key{0}; // NOLINT
bool has_state{false}; // NOLINT bool has_state{false}; // NOLINT
bool state{false}; // NOLINT bool state{false}; // NOLINT
bool has_speed{false}; // NOLINT bool has_speed{false}; // NOLINT
enums::FanSpeed speed{}; // NOLINT enums::FanSpeed speed{}; // NOLINT
bool has_oscillating{false}; // NOLINT bool has_oscillating{false}; // NOLINT
bool oscillating{false}; // NOLINT bool oscillating{false}; // NOLINT
bool has_direction{false}; // NOLINT
enums::FanDirection direction{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;

View file

@ -29,6 +29,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
template<typename T> void add_variable(std::string key, T value) { template<typename T> void add_variable(std::string key, T value) {
this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value)); this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
} }
void play(Ts... x) override { void play(Ts... x) override {
HomeassistantServiceResponse resp; HomeassistantServiceResponse resp;
resp.service = this->service_.value(x...); resp.service = this->service_.value(x...);

View file

@ -25,7 +25,7 @@ AS3935_SCHEMA = cv.Schema({
cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11), cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11),
cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True), cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True),
cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean, cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean,
cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 22, 64, 128, int=True), cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True),
cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15), cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15),
}) })

View file

@ -26,6 +26,9 @@ void AS3935Component::setup() {
void AS3935Component::dump_config() { void AS3935Component::dump_config() {
ESP_LOGCONFIG(TAG, "AS3935:"); ESP_LOGCONFIG(TAG, "AS3935:");
LOG_PIN(" Interrupt Pin: ", this->irq_pin_); LOG_PIN(" Interrupt Pin: ", this->irq_pin_);
LOG_BINARY_SENSOR(" ", "Thunder alert", this->thunder_alert_binary_sensor_);
LOG_SENSOR(" ", "Distance", this->distance_sensor_);
LOG_SENSOR(" ", "Lightning energy", this->energy_sensor_);
} }
float AS3935Component::get_setup_priority() const { return setup_priority::DATA; } float AS3935Component::get_setup_priority() const { return setup_priority::DATA; }

View file

@ -27,4 +27,4 @@ def to_code(config):
if CONF_LIGHTNING_ENERGY in config: if CONF_LIGHTNING_ENERGY in config:
conf = config[CONF_LIGHTNING_ENERGY] conf = config[CONF_LIGHTNING_ENERGY]
lightning_energy_sensor = yield sensor.new_sensor(conf) lightning_energy_sensor = yield sensor.new_sensor(conf)
cg.add(hub.set_distance_sensor(lightning_energy_sensor)) cg.add(hub.set_energy_sensor(lightning_energy_sensor))

View file

@ -10,8 +10,8 @@ as3935_spi_ns = cg.esphome_ns.namespace('as3935_spi')
SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice) SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice)
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({ CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(SPIAS3935) cv.GenerateID(): cv.declare_id(SPIAS3935),
}).extend(cv.COMPONENT_SCHEMA).extend(spi.SPI_DEVICE_SCHEMA)) }).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=True)))
def to_code(config): def to_code(config):

View file

@ -2,6 +2,8 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
CODEOWNERS = ['@OttoWinter']
@coroutine_with_priority(200.0) @coroutine_with_priority(200.0)
def to_code(config): def to_code(config):
@ -10,4 +12,4 @@ def to_code(config):
cg.add_library('AsyncTCP-esphome', '1.1.1') cg.add_library('AsyncTCP-esphome', '1.1.1')
elif CORE.is_esp8266: elif CORE.is_esp8266:
# https://github.com/OttoWinter/ESPAsyncTCP # https://github.com/OttoWinter/ESPAsyncTCP
cg.add_library('ESPAsyncTCP-esphome', '1.2.2') cg.add_library('ESPAsyncTCP-esphome', '1.2.3')

View file

@ -55,7 +55,7 @@ CONFIG_SCHEMA = cv.Schema({
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
cv.Optional(CONF_CURRENT_PHASES, default='3'): cv.enum(CURRENT_PHASES, upper=True), cv.Optional(CONF_CURRENT_PHASES, default='3'): cv.enum(CURRENT_PHASES, upper=True),
cv.Optional(CONF_GAIN_PGA, default='2X'): cv.enum(PGA_GAINS, upper=True), cv.Optional(CONF_GAIN_PGA, default='2X'): cv.enum(PGA_GAINS, upper=True),
}).extend(cv.polling_component_schema('60s')).extend(spi.SPI_DEVICE_SCHEMA) }).extend(cv.polling_component_schema('60s')).extend(spi.spi_device_schema())
def to_code(config): def to_code(config):

View file

@ -0,0 +1 @@
CODEOWNERS = ['@OttoWinter']

View file

@ -108,7 +108,7 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
} }
if (this->prev_trigger_ != nullptr) { if (this->prev_trigger_ != nullptr) {
this->prev_trigger_->stop(); this->prev_trigger_->stop_action();
this->prev_trigger_ = nullptr; this->prev_trigger_ = nullptr;
} }
Trigger<> *trig; Trigger<> *trig;

View file

@ -7,6 +7,8 @@ namespace bh1750 {
static const char *TAG = "bh1750.sensor"; static const char *TAG = "bh1750.sensor";
static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001; static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001;
static const uint8_t BH1750_COMMAND_MT_REG_HI = 0b01000000; // last 3 bits
static const uint8_t BH1750_COMMAND_MT_REG_LO = 0b01100000; // last 5 bits
void BH1750Sensor::setup() { void BH1750Sensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up BH1750 '%s'...", this->name_.c_str()); ESP_LOGCONFIG(TAG, "Setting up BH1750 '%s'...", this->name_.c_str());
@ -14,7 +16,13 @@ void BH1750Sensor::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
uint8_t mtreg_hi = (this->measurement_time_ >> 5) & 0b111;
uint8_t mtreg_lo = (this->measurement_time_ >> 0) & 0b11111;
this->write_bytes(BH1750_COMMAND_MT_REG_HI | mtreg_hi, nullptr, 0);
this->write_bytes(BH1750_COMMAND_MT_REG_LO | mtreg_lo, nullptr, 0);
} }
void BH1750Sensor::dump_config() { void BH1750Sensor::dump_config() {
LOG_SENSOR("", "BH1750", this); LOG_SENSOR("", "BH1750", this);
LOG_I2C_DEVICE(this); LOG_I2C_DEVICE(this);
@ -59,6 +67,7 @@ void BH1750Sensor::update() {
this->set_timeout("illuminance", wait, [this]() { this->read_data_(); }); this->set_timeout("illuminance", wait, [this]() { this->read_data_(); });
} }
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; } float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
void BH1750Sensor::read_data_() { void BH1750Sensor::read_data_() {
uint16_t raw_value; uint16_t raw_value;
@ -68,10 +77,12 @@ void BH1750Sensor::read_data_() {
} }
float lx = float(raw_value) / 1.2f; float lx = float(raw_value) / 1.2f;
lx *= 69.0f / this->measurement_time_;
ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx); ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
this->publish_state(lx); this->publish_state(lx);
this->status_clear_warning(); this->status_clear_warning();
} }
void BH1750Sensor::set_resolution(BH1750Resolution resolution) { this->resolution_ = resolution; } void BH1750Sensor::set_resolution(BH1750Resolution resolution) { this->resolution_ = resolution; }
} // namespace bh1750 } // namespace bh1750

View file

@ -28,6 +28,7 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
* @param resolution The new resolution of the sensor. * @param resolution The new resolution of the sensor.
*/ */
void set_resolution(BH1750Resolution resolution); void set_resolution(BH1750Resolution resolution);
void set_measurement_time(uint8_t measurement_time) { measurement_time_ = measurement_time; }
// ========== INTERNAL METHODS ========== // ========== INTERNAL METHODS ==========
// (In most use cases you won't need these) // (In most use cases you won't need these)
@ -40,6 +41,7 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
void read_data_(); void read_data_();
BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX}; BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX};
uint8_t measurement_time_;
}; };
} // namespace bh1750 } // namespace bh1750

View file

@ -15,9 +15,11 @@ BH1750_RESOLUTIONS = {
BH1750Sensor = bh1750_ns.class_('BH1750Sensor', sensor.Sensor, cg.PollingComponent, i2c.I2CDevice) BH1750Sensor = bh1750_ns.class_('BH1750Sensor', sensor.Sensor, cg.PollingComponent, i2c.I2CDevice)
CONF_MEASUREMENT_TIME = 'measurement_time'
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({ CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({
cv.GenerateID(): cv.declare_id(BH1750Sensor), cv.GenerateID(): cv.declare_id(BH1750Sensor),
cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(BH1750_RESOLUTIONS, float=True), cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(BH1750_RESOLUTIONS, float=True),
cv.Optional(CONF_MEASUREMENT_TIME, default=69): cv.int_range(min=31, max=254),
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x23)) }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x23))
@ -28,3 +30,4 @@ def to_code(config):
yield i2c.register_i2c_device(var, config) yield i2c.register_i2c_device(var, config)
cg.add(var.set_resolution(config[CONF_RESOLUTION])) cg.add(var.set_resolution(config[CONF_RESOLUTION]))
cg.add(var.set_measurement_time(config[CONF_MEASUREMENT_TIME]))

View file

@ -1,7 +1,8 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import fan, output from esphome.components import fan, output
from esphome.const import CONF_OSCILLATION_OUTPUT, CONF_OUTPUT, CONF_OUTPUT_ID from esphome.const import CONF_DIRECTION_OUTPUT, CONF_OSCILLATION_OUTPUT, \
CONF_OUTPUT, CONF_OUTPUT_ID
from .. import binary_ns from .. import binary_ns
BinaryFan = binary_ns.class_('BinaryFan', cg.Component) BinaryFan = binary_ns.class_('BinaryFan', cg.Component)
@ -9,6 +10,7 @@ BinaryFan = binary_ns.class_('BinaryFan', cg.Component)
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan), cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan),
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
}).extend(cv.COMPONENT_SCHEMA) }).extend(cv.COMPONENT_SCHEMA)
@ -25,3 +27,7 @@ def to_code(config):
if CONF_OSCILLATION_OUTPUT in config: if CONF_OSCILLATION_OUTPUT in config:
oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT])
cg.add(var.set_oscillating(oscillation_output)) cg.add(var.set_oscillating(oscillation_output))
if CONF_DIRECTION_OUTPUT in config:
direction_output = yield cg.get_variable(config[CONF_DIRECTION_OUTPUT])
cg.add(var.set_direction(direction_output))

View file

@ -11,9 +11,12 @@ void binary::BinaryFan::dump_config() {
if (this->fan_->get_traits().supports_oscillation()) { if (this->fan_->get_traits().supports_oscillation()) {
ESP_LOGCONFIG(TAG, " Oscillation: YES"); ESP_LOGCONFIG(TAG, " Oscillation: YES");
} }
if (this->fan_->get_traits().supports_direction()) {
ESP_LOGCONFIG(TAG, " Direction: YES");
}
} }
void BinaryFan::setup() { void BinaryFan::setup() {
auto traits = fan::FanTraits(this->oscillating_ != nullptr, false); auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr);
this->fan_->set_traits(traits); this->fan_->set_traits(traits);
this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); this->fan_->add_on_state_callback([this]() { this->next_update_ = true; });
} }
@ -41,6 +44,16 @@ void BinaryFan::loop() {
} }
ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable)); ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable));
} }
if (this->direction_ != nullptr) {
bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE;
if (enable) {
this->direction_->turn_on();
} else {
this->direction_->turn_off();
}
ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable));
}
} }
float BinaryFan::get_setup_priority() const { return setup_priority::DATA; } float BinaryFan::get_setup_priority() const { return setup_priority::DATA; }

View file

@ -16,11 +16,13 @@ class BinaryFan : public Component {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; }
void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; }
protected: protected:
fan::FanState *fan_; fan::FanState *fan_;
output::BinaryOutput *output_; output::BinaryOutput *output_;
output::BinaryOutput *oscillating_{nullptr}; output::BinaryOutput *oscillating_{nullptr};
output::BinaryOutput *direction_{nullptr};
bool next_update_{true}; bool next_update_{true};
}; };

View file

@ -11,6 +11,7 @@ from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \
from esphome.core import CORE, coroutine, coroutine_with_priority from esphome.core import CORE, coroutine, coroutine_with_priority
from esphome.util import Registry from esphome.util import Registry
CODEOWNERS = ['@esphome/core']
DEVICE_CLASSES = [ DEVICE_CLASSES = [
'', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas', '', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas',
'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy', 'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy',
@ -224,7 +225,7 @@ BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
def setup_binary_sensor_core_(var, config): def setup_binary_sensor_core_(var, config):
cg.add(var.set_name(config[CONF_NAME])) cg.add(var.set_name(config[CONF_NAME]))
if CONF_INTERNAL in config: if CONF_INTERNAL in config:
cg.add(var.set_internal(CONF_INTERNAL)) cg.add(var.set_internal(config[CONF_INTERNAL]))
if CONF_DEVICE_CLASS in config: if CONF_DEVICE_CLASS in config:
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
if CONF_INVERTED in config: if CONF_INVERTED in config:

View file

@ -137,6 +137,7 @@ template<typename... Ts> class BinarySensorPublishAction : public Action<Ts...>
public: public:
explicit BinarySensorPublishAction(BinarySensor *sensor) : sensor_(sensor) {} explicit BinarySensorPublishAction(BinarySensor *sensor) : sensor_(sensor) {}
TEMPLATABLE_VALUE(bool, state) TEMPLATABLE_VALUE(bool, state)
void play(Ts... x) override { void play(Ts... x) override {
auto val = this->state_.value(x...); auto val = this->state_.value(x...);
this->sensor_->publish_state(val); this->sensor_->publish_state(val);

View file

@ -7,6 +7,7 @@ from esphome.core import coroutine_with_priority
AUTO_LOAD = ['web_server_base'] AUTO_LOAD = ['web_server_base']
DEPENDENCIES = ['wifi'] DEPENDENCIES = ['wifi']
CODEOWNERS = ['@OttoWinter']
captive_portal_ns = cg.esphome_ns.namespace('captive_portal') captive_portal_ns = cg.esphome_ns.namespace('captive_portal')
CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component) CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component)

View file

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor
from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \ from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \
UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_PERIODIC_TABLE_CO2 UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_MOLECULE_CO2
DEPENDENCIES = ['i2c'] DEPENDENCIES = ['i2c']
@ -15,7 +15,7 @@ CONF_BASELINE = 'baseline'
CONFIG_SCHEMA = cv.Schema({ CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(CCS811Component), cv.GenerateID(): cv.declare_id(CCS811Component),
cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_PERIODIC_TABLE_CO2, cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2,
0), 0),
cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0), cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0),

View file

@ -10,6 +10,7 @@ from esphome.core import CORE, coroutine, coroutine_with_priority
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
CODEOWNERS = ['@esphome/core']
climate_ns = cg.esphome_ns.namespace('climate') climate_ns = cg.esphome_ns.namespace('climate')
Climate = climate_ns.class_('Climate', cg.Nameable) Climate = climate_ns.class_('Climate', cg.Nameable)

View file

@ -6,6 +6,7 @@ from esphome.const import CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, CONF_SENSOR
from esphome.core import coroutine from esphome.core import coroutine
AUTO_LOAD = ['sensor', 'remote_base'] AUTO_LOAD = ['sensor', 'remote_base']
CODEOWNERS = ['@glmnet']
climate_ir_ns = cg.esphome_ns.namespace('climate_ir') climate_ir_ns = cg.esphome_ns.namespace('climate_ir')
ClimateIR = climate_ir_ns.class_('ClimateIR', climate.Climate, cg.Component, ClimateIR = climate_ir_ns.class_('ClimateIR', climate.Climate, cg.Component,

View file

@ -0,0 +1,18 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate_ir
from esphome.const import CONF_ID
AUTO_LOAD = ['climate_ir']
climate_ir_lg_ns = cg.esphome_ns.namespace('climate_ir_lg')
LgIrClimate = climate_ir_lg_ns.class_('LgIrClimate', climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(LgIrClimate),
})
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield climate_ir.register_climate_ir(var, config)

View file

@ -0,0 +1,204 @@
#include "climate_ir_lg.h"
#include "esphome/core/log.h"
namespace esphome {
namespace climate_ir_lg {
static const char *TAG = "climate.climate_ir_lg";
const uint32_t COMMAND_ON = 0x00000;
const uint32_t COMMAND_ON_AI = 0x03000;
const uint32_t COMMAND_COOL = 0x08000;
const uint32_t COMMAND_OFF = 0xC0000;
const uint32_t COMMAND_SWING = 0x10000;
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
const uint32_t COMMAND_AUTO = 0x0B000;
const uint32_t COMMAND_DRY_FAN = 0x09000;
const uint32_t COMMAND_MASK = 0xFF000;
const uint32_t FAN_MASK = 0xF0;
const uint32_t FAN_AUTO = 0x50;
const uint32_t FAN_MIN = 0x00;
const uint32_t FAN_MED = 0x20;
const uint32_t FAN_MAX = 0x40;
// Temperature
const uint8_t TEMP_RANGE = TEMP_MAX - TEMP_MIN + 1;
const uint32_t TEMP_MASK = 0XF00;
const uint32_t TEMP_SHIFT = 8;
// Constants
static const uint32_t HEADER_HIGH_US = 8000;
static const uint32_t HEADER_LOW_US = 4000;
static const uint32_t BIT_HIGH_US = 600;
static const uint32_t BIT_ONE_LOW_US = 1600;
static const uint32_t BIT_ZERO_LOW_US = 550;
const uint16_t BITS = 28;
void LgIrClimate::transmit_state() {
uint32_t remote_state = 0x8800000;
// ESP_LOGD(TAG, "climate_lg_ir mode_before_ code: 0x%02X", modeBefore_);
if (send_swing_cmd_) {
send_swing_cmd_ = false;
remote_state |= COMMAND_SWING;
} else {
if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_AUTO) {
remote_state |= COMMAND_ON_AI;
} else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) {
remote_state |= COMMAND_ON;
this->mode = climate::CLIMATE_MODE_COOL;
} else {
switch (this->mode) {
case climate::CLIMATE_MODE_COOL:
remote_state |= COMMAND_COOL;
break;
case climate::CLIMATE_MODE_AUTO:
remote_state |= COMMAND_AUTO;
break;
case climate::CLIMATE_MODE_DRY:
remote_state |= COMMAND_DRY_FAN;
break;
case climate::CLIMATE_MODE_OFF:
default:
remote_state |= COMMAND_OFF;
break;
}
}
mode_before_ = this->mode;
ESP_LOGD(TAG, "climate_lg_ir mode code: 0x%02X", this->mode);
if (this->mode == climate::CLIMATE_MODE_OFF) {
remote_state |= FAN_AUTO;
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY) {
switch (this->fan_mode) {
case climate::CLIMATE_FAN_HIGH:
remote_state |= FAN_MAX;
break;
case climate::CLIMATE_FAN_MEDIUM:
remote_state |= FAN_MED;
break;
case climate::CLIMATE_FAN_LOW:
remote_state |= FAN_MIN;
break;
case climate::CLIMATE_FAN_AUTO:
default:
remote_state |= FAN_AUTO;
break;
}
}
if (this->mode == climate::CLIMATE_MODE_AUTO) {
this->fan_mode = climate::CLIMATE_FAN_AUTO;
// remote_state |= FAN_MODE_AUTO_DRY;
}
if (this->mode == climate::CLIMATE_MODE_COOL) {
auto temp = (uint8_t) roundf(clamp(this->target_temperature, TEMP_MIN, TEMP_MAX));
remote_state |= ((temp - 15) << TEMP_SHIFT);
}
}
transmit_(remote_state);
this->publish_state();
}
bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
uint8_t nbits = 0;
uint32_t remote_state = 0;
if (!data.expect_item(HEADER_HIGH_US, HEADER_LOW_US))
return false;
for (nbits = 0; nbits < 32; nbits++) {
if (data.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
remote_state = (remote_state << 1) | 1;
} else if (data.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
remote_state = (remote_state << 1) | 0;
} else if (nbits == BITS) {
break;
} else {
return false;
}
}
ESP_LOGD(TAG, "Decoded 0x%02X", remote_state);
if ((remote_state & 0xFF00000) != 0x8800000)
return false;
if ((remote_state & COMMAND_MASK) == COMMAND_ON) {
this->mode = climate::CLIMATE_MODE_COOL;
} else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) {
this->mode = climate::CLIMATE_MODE_AUTO;
}
if ((remote_state & COMMAND_MASK) == COMMAND_OFF) {
this->mode = climate::CLIMATE_MODE_OFF;
} else if ((remote_state & COMMAND_MASK) == COMMAND_SWING) {
this->swing_mode =
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
} else {
if ((remote_state & COMMAND_MASK) == COMMAND_AUTO)
this->mode = climate::CLIMATE_MODE_AUTO;
else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) {
this->mode = climate::CLIMATE_MODE_DRY;
} else {
this->mode = climate::CLIMATE_MODE_COOL;
}
}
// Temperature
if (this->mode == climate::CLIMATE_MODE_COOL)
this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
// Fan Speed
if (this->mode == climate::CLIMATE_MODE_AUTO) {
this->fan_mode = climate::CLIMATE_FAN_AUTO;
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY) {
if ((remote_state & FAN_MASK) == FAN_AUTO)
this->fan_mode = climate::CLIMATE_FAN_AUTO;
else if ((remote_state & FAN_MASK) == FAN_MIN)
this->fan_mode = climate::CLIMATE_FAN_LOW;
else if ((remote_state & FAN_MASK) == FAN_MED)
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
else if ((remote_state & FAN_MASK) == FAN_MAX)
this->fan_mode = climate::CLIMATE_FAN_HIGH;
}
this->publish_state();
return true;
}
void LgIrClimate::transmit_(uint32_t value) {
calc_checksum_(value);
ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02X", value);
auto transmit = this->transmitter_->transmit();
auto data = transmit.get_data();
data->set_carrier_frequency(38000);
data->reserve(2 + BITS * 2u);
data->item(HEADER_HIGH_US, HEADER_LOW_US);
for (uint32_t mask = 1UL << (BITS - 1); mask != 0; mask >>= 1) {
if (value & mask)
data->item(BIT_HIGH_US, BIT_ONE_LOW_US);
else
data->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
}
data->mark(BIT_HIGH_US);
transmit.perform();
}
void LgIrClimate::calc_checksum_(uint32_t &value) {
uint32_t mask = 0xF;
uint32_t sum = 0;
for (uint8_t i = 1; i < 8; i++) {
sum += (value & (mask << (i * 4))) >> (i * 4);
}
value |= (sum & mask);
}
} // namespace climate_ir_lg
} // namespace esphome

View file

@ -0,0 +1,44 @@
#pragma once
#include "esphome/components/climate_ir/climate_ir.h"
namespace esphome {
namespace climate_ir_lg {
// Temperature
const uint8_t TEMP_MIN = 18; // Celsius
const uint8_t TEMP_MAX = 30; // Celsius
class LgIrClimate : public climate_ir::ClimateIR {
public:
LgIrClimate()
: climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, false,
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {}
/// Override control to change settings of the climate device.
void control(const climate::ClimateCall &call) override {
send_swing_cmd_ = call.get_swing_mode().has_value();
// swing resets after unit powered off
if (call.get_mode().has_value() && *call.get_mode() == climate::CLIMATE_MODE_OFF)
this->swing_mode = climate::CLIMATE_SWING_OFF;
climate_ir::ClimateIR::control(call);
}
protected:
/// Transmit via IR the state of this climate controller.
void transmit_state() override;
/// Handle received IR Buffer
bool on_receive(remote_base::RemoteReceiveData data) override;
bool send_swing_cmd_{false};
void calc_checksum_(uint32_t &value);
void transmit_(uint32_t value);
climate::ClimateMode mode_before_{climate::CLIMATE_MODE_OFF};
};
} // namespace climate_ir_lg
} // namespace esphome

View file

@ -0,0 +1,23 @@
from esphome import config_validation as cv
from esphome import codegen as cg
from esphome.const import CONF_BLUE, CONF_GREEN, CONF_ID, CONF_RED, CONF_WHITE
ColorStruct = cg.esphome_ns.struct('Color')
MULTI_CONF = True
CONFIG_SCHEMA = cv.Schema({
cv.Required(CONF_ID): cv.declare_id(ColorStruct),
cv.Optional(CONF_RED, default=0.0): cv.percentage,
cv.Optional(CONF_GREEN, default=0.0): cv.percentage,
cv.Optional(CONF_BLUE, default=0.0): cv.percentage,
cv.Optional(CONF_WHITE, default=0.0): cv.percentage,
}).extend(cv.COMPONENT_SCHEMA)
def to_code(config):
cg.variable(config[CONF_ID], cg.StructInitializer(
ColorStruct,
('r', config[CONF_RED]),
('g', config[CONF_GREEN]),
('b', config[CONF_BLUE]),
('w', config[CONF_WHITE])))

View file

@ -4,6 +4,7 @@ from esphome.components import climate_ir
from esphome.const import CONF_ID from esphome.const import CONF_ID
AUTO_LOAD = ['climate_ir'] AUTO_LOAD = ['climate_ir']
CODEOWNERS = ['@glmnet']
coolix_ns = cg.esphome_ns.namespace('coolix') coolix_ns = cg.esphome_ns.namespace('coolix')
CoolixClimate = coolix_ns.class_('CoolixClimate', climate_ir.ClimateIR) CoolixClimate = coolix_ns.class_('CoolixClimate', climate_ir.ClimateIR)

View file

@ -9,9 +9,10 @@ from esphome.core import CORE, coroutine, coroutine_with_priority
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
CODEOWNERS = ['@esphome/core']
DEVICE_CLASSES = [ DEVICE_CLASSES = [
'', 'awning', 'blind', 'curtain', 'damper', 'door', 'garage', '', 'awning', 'blind', 'curtain', 'damper', 'door', 'garage',
'shade', 'shutter', 'window' 'gate', 'shade', 'shutter', 'window'
] ]
cover_ns = cg.esphome_ns.namespace('cover') cover_ns = cg.esphome_ns.namespace('cover')

View file

@ -41,6 +41,10 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
public: public:
explicit ControlAction(Cover *cover) : cover_(cover) {} explicit ControlAction(Cover *cover) : cover_(cover) {}
TEMPLATABLE_VALUE(bool, stop)
TEMPLATABLE_VALUE(float, position)
TEMPLATABLE_VALUE(float, tilt)
void play(Ts... x) override { void play(Ts... x) override {
auto call = this->cover_->make_call(); auto call = this->cover_->make_call();
if (this->stop_.has_value()) if (this->stop_.has_value())
@ -52,10 +56,6 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
call.perform(); call.perform();
} }
TEMPLATABLE_VALUE(bool, stop)
TEMPLATABLE_VALUE(float, position)
TEMPLATABLE_VALUE(float, tilt)
protected: protected:
Cover *cover_; Cover *cover_;
}; };
@ -63,6 +63,10 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
template<typename... Ts> class CoverPublishAction : public Action<Ts...> { template<typename... Ts> class CoverPublishAction : public Action<Ts...> {
public: public:
CoverPublishAction(Cover *cover) : cover_(cover) {} CoverPublishAction(Cover *cover) : cover_(cover) {}
TEMPLATABLE_VALUE(float, position)
TEMPLATABLE_VALUE(float, tilt)
TEMPLATABLE_VALUE(CoverOperation, current_operation)
void play(Ts... x) override { void play(Ts... x) override {
if (this->position_.has_value()) if (this->position_.has_value())
this->cover_->position = this->position_.value(x...); this->cover_->position = this->position_.value(x...);
@ -73,10 +77,6 @@ template<typename... Ts> class CoverPublishAction : public Action<Ts...> {
this->cover_->publish_state(); this->cover_->publish_state();
} }
TEMPLATABLE_VALUE(float, position)
TEMPLATABLE_VALUE(float, tilt)
TEMPLATABLE_VALUE(CoverOperation, current_operation)
protected: protected:
Cover *cover_; Cover *cover_;
}; };

View file

@ -4,6 +4,7 @@ from esphome.components import sensor, voltage_sampler
from esphome.const import CONF_SENSOR, CONF_ID, ICON_FLASH, UNIT_AMPERE from esphome.const import CONF_SENSOR, CONF_ID, ICON_FLASH, UNIT_AMPERE
AUTO_LOAD = ['voltage_sampler'] AUTO_LOAD = ['voltage_sampler']
CODEOWNERS = ['@jesserockz']
CONF_SAMPLE_DURATION = 'sample_duration' CONF_SAMPLE_DURATION = 'sample_duration'

View file

@ -1,18 +1,12 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.const import CONF_ID, CONF_PIN, \ from esphome.const import CONF_ID, CONF_PIN
CONF_RESOLUTION, CONF_UNIT_OF_MEASUREMENT, UNIT_CELSIUS, \
CONF_ICON, ICON_THERMOMETER, CONF_ACCURACY_DECIMALS
MULTI_CONF = True MULTI_CONF = True
AUTO_LOAD = ['sensor'] AUTO_LOAD = ['sensor']
CONF_ONE_WIRE_ID = 'one_wire_id' CONF_ONE_WIRE_ID = 'one_wire_id'
CONF_AUTO_SETUP_SENSORS = 'auto_setup_sensors'
CONF_SENSOR_NAME_TEMPLATE = 'sensor_name_template'
SENSOR_NAME_TEMPLATE_DEFAULT = '%s.%s'
dallas_ns = cg.esphome_ns.namespace('dallas') dallas_ns = cg.esphome_ns.namespace('dallas')
DallasComponent = dallas_ns.class_('DallasComponent', cg.PollingComponent) DallasComponent = dallas_ns.class_('DallasComponent', cg.PollingComponent)
ESPOneWire = dallas_ns.class_('ESPOneWire') ESPOneWire = dallas_ns.class_('ESPOneWire')
@ -21,12 +15,6 @@ CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(DallasComponent), cv.GenerateID(): cv.declare_id(DallasComponent),
cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire), cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
cv.Required(CONF_PIN): pins.gpio_input_pin_schema, cv.Required(CONF_PIN): pins.gpio_input_pin_schema,
cv.Optional(CONF_AUTO_SETUP_SENSORS, default=False): cv.boolean,
cv.Optional(CONF_SENSOR_NAME_TEMPLATE, default=SENSOR_NAME_TEMPLATE_DEFAULT): cv.string_strict,
cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=UNIT_CELSIUS): cv.string_strict,
cv.Optional(CONF_ICON, default=ICON_THERMOMETER): cv.icon,
cv.Optional(CONF_ACCURACY_DECIMALS, default=1): cv.int_,
}).extend(cv.polling_component_schema('60s')) }).extend(cv.polling_component_schema('60s'))
@ -34,16 +22,4 @@ def to_code(config):
pin = yield cg.gpio_pin_expression(config[CONF_PIN]) pin = yield cg.gpio_pin_expression(config[CONF_PIN])
one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin) one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin)
var = cg.new_Pvariable(config[CONF_ID], one_wire) var = cg.new_Pvariable(config[CONF_ID], one_wire)
if CONF_AUTO_SETUP_SENSORS in config:
cg.add(var.set_auto_setup_sensors(config[CONF_AUTO_SETUP_SENSORS]))
if CONF_SENSOR_NAME_TEMPLATE in config:
cg.add(var.set_sensor_name_template(config[CONF_SENSOR_NAME_TEMPLATE]))
if CONF_RESOLUTION in config:
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
if CONF_UNIT_OF_MEASUREMENT in config:
cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT]))
if CONF_ICON in config:
cg.add(var.set_icon(config[CONF_ICON]))
if CONF_ACCURACY_DECIMALS in config:
cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS]))
yield cg.register_component(var, config) yield cg.register_component(var, config)

View file

@ -1,6 +1,5 @@
#include "dallas_component.h" #include "dallas_component.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome { namespace esphome {
namespace dallas { namespace dallas {
@ -53,29 +52,6 @@ void DallasComponent::setup() {
continue; continue;
} }
this->found_sensors_.push_back(address); this->found_sensors_.push_back(address);
if (this->auto_setup_sensors_) {
// avoid re-generating pre-configured sensors
bool skip = false;
for (auto sensor : this->sensors_) {
if (sensor->get_address() == address) {
skip = true;
break;
}
}
if (!skip) {
auto dallastemperaturesensor = this->get_sensor_by_address(address, this->resolution_);
char sensor_name[64];
snprintf(sensor_name, sizeof(sensor_name), this->sensor_name_template_.c_str(), App.get_name().c_str(),
s.c_str());
dallastemperaturesensor->set_name(sensor_name);
dallastemperaturesensor->set_unit_of_measurement(this->unit_of_measurement_);
dallastemperaturesensor->set_icon(this->icon_);
dallastemperaturesensor->set_accuracy_decimals(this->accuracy_decimals_);
dallastemperaturesensor->set_force_update(false);
App.register_sensor(dallastemperaturesensor);
}
}
} }
for (auto sensor : this->sensors_) { for (auto sensor : this->sensors_) {
@ -180,25 +156,12 @@ void DallasComponent::update() {
} }
} }
DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {} DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {}
void DallasComponent::set_auto_setup_sensors(bool auto_setup_sensors) {
this->auto_setup_sensors_ = auto_setup_sensors;
}
void DallasComponent::set_sensor_name_template(const std::string &sensor_name_template) {
this->sensor_name_template_ = sensor_name_template;
}
void DallasComponent::set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
void DallasComponent::set_unit_of_measurement(const std::string &unit_of_measurement) {
this->unit_of_measurement_ = unit_of_measurement;
}
void DallasComponent::set_icon(const std::string &icon) { this->icon_ = icon; }
void DallasComponent::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; }
DallasTemperatureSensor::DallasTemperatureSensor(uint64_t address, uint8_t resolution, DallasComponent *parent) DallasTemperatureSensor::DallasTemperatureSensor(uint64_t address, uint8_t resolution, DallasComponent *parent)
: parent_(parent) { : parent_(parent) {
this->set_address(address); this->set_address(address);
this->set_resolution(resolution); this->set_resolution(resolution);
} }
const uint64_t &DallasTemperatureSensor::get_address() const { return this->address_; }
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; } void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; } uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
void DallasTemperatureSensor::set_resolution(uint8_t resolution) { this->resolution_ = resolution; } void DallasTemperatureSensor::set_resolution(uint8_t resolution) { this->resolution_ = resolution; }

View file

@ -22,30 +22,12 @@ class DallasComponent : public PollingComponent {
void update() override; void update() override;
/// Automatic sensors instantiation
bool get_auto_setup_sensors() const;
void set_auto_setup_sensors(bool auto_setup_sensors);
/// Get/Set properties for automatically generated sensors.
void set_sensor_name_template(const std::string &sensor_name_template);
void set_resolution(uint8_t resolution);
void set_unit_of_measurement(const std::string &unit_of_measurement);
void set_icon(const std::string &icon);
void set_accuracy_decimals(int8_t accuracy_decimals);
protected: protected:
friend DallasTemperatureSensor; friend DallasTemperatureSensor;
ESPOneWire *one_wire_; ESPOneWire *one_wire_;
std::vector<DallasTemperatureSensor *> sensors_; std::vector<DallasTemperatureSensor *> sensors_;
std::vector<uint64_t> found_sensors_; std::vector<uint64_t> found_sensors_;
bool auto_setup_sensors_;
std::string sensor_name_template_;
uint8_t resolution_;
std::string unit_of_measurement_;
std::string icon_;
int8_t accuracy_decimals_;
}; };
/// Internal class that helps us create multiple sensors for one Dallas hub. /// Internal class that helps us create multiple sensors for one Dallas hub.
@ -58,8 +40,6 @@ class DallasTemperatureSensor : public sensor::Sensor {
/// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29". /// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
const std::string &get_address_name(); const std::string &get_address_name();
/// Get the 64-bit unsigned address of this sensor.
const uint64_t &get_address() const;
/// Set the 64-bit unsigned address for this sensor. /// Set the 64-bit unsigned address for this sensor.
void set_address(uint64_t address); void set_address(uint64_t address);
/// Get the index of this sensor. (0 if using address.) /// Get the index of this sensor. (0 if using address.)

View file

@ -30,7 +30,7 @@ bool HOT ICACHE_RAM_ATTR ESPOneWire::reset() {
// Switch into RX mode, letting the pin float // Switch into RX mode, letting the pin float
this->pin_->pin_mode(INPUT_PULLUP); this->pin_->pin_mode(INPUT_PULLUP);
// after 15µs-60µs wait time, slave pulls low for 60µs-240µs // after 15µs-60µs wait time, responder pulls low for 60µs-240µs
// let's have 70µs just in case // let's have 70µs just in case
delayMicroseconds(70); delayMicroseconds(70);

View file

@ -2,6 +2,7 @@ import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.const import CONF_ID from esphome.const import CONF_ID
CODEOWNERS = ['@OttoWinter']
DEPENDENCIES = ['logger'] DEPENDENCIES = ['logger']
debug_ns = cg.esphome_ns.namespace('debug') debug_ns = cg.esphome_ns.namespace('debug')

View file

@ -5,6 +5,7 @@ from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE
from esphome.components import uart from esphome.components import uart
DEPENDENCIES = ['uart'] DEPENDENCIES = ['uart']
CODEOWNERS = ['@glmnet']
dfplayer_ns = cg.esphome_ns.namespace('dfplayer') dfplayer_ns = cg.esphome_ns.namespace('dfplayer')
DFPlayer = dfplayer_ns.class_('DFPlayer', cg.Component) DFPlayer = dfplayer_ns.class_('DFPlayer', cg.Component)

View file

@ -104,7 +104,6 @@ class DFPlayer : public uart::UARTDevice, public Component {
#define DFPLAYER_SIMPLE_ACTION(ACTION_CLASS, ACTION_METHOD) \ #define DFPLAYER_SIMPLE_ACTION(ACTION_CLASS, ACTION_METHOD) \
template<typename... Ts> class ACTION_CLASS : public Action<Ts...>, public Parented<DFPlayer> { \ template<typename... Ts> class ACTION_CLASS : public Action<Ts...>, public Parented<DFPlayer> { \
public: \
void play(Ts... x) override { this->parent_->ACTION_METHOD(); } \ void play(Ts... x) override { this->parent_->ACTION_METHOD(); } \
}; };
@ -115,6 +114,7 @@ template<typename... Ts> class PlayFileAction : public Action<Ts...>, public Par
public: public:
TEMPLATABLE_VALUE(uint16_t, file) TEMPLATABLE_VALUE(uint16_t, file)
TEMPLATABLE_VALUE(boolean, loop) TEMPLATABLE_VALUE(boolean, loop)
void play(Ts... x) override { void play(Ts... x) override {
auto file = this->file_.value(x...); auto file = this->file_.value(x...);
auto loop = this->loop_.value(x...); auto loop = this->loop_.value(x...);
@ -131,6 +131,7 @@ template<typename... Ts> class PlayFolderAction : public Action<Ts...>, public P
TEMPLATABLE_VALUE(uint16_t, folder) TEMPLATABLE_VALUE(uint16_t, folder)
TEMPLATABLE_VALUE(uint16_t, file) TEMPLATABLE_VALUE(uint16_t, file)
TEMPLATABLE_VALUE(boolean, loop) TEMPLATABLE_VALUE(boolean, loop)
void play(Ts... x) override { void play(Ts... x) override {
auto folder = this->folder_.value(x...); auto folder = this->folder_.value(x...);
auto file = this->file_.value(x...); auto file = this->file_.value(x...);
@ -146,6 +147,7 @@ template<typename... Ts> class PlayFolderAction : public Action<Ts...>, public P
template<typename... Ts> class SetDeviceAction : public Action<Ts...>, public Parented<DFPlayer> { template<typename... Ts> class SetDeviceAction : public Action<Ts...>, public Parented<DFPlayer> {
public: public:
TEMPLATABLE_VALUE(Device, device) TEMPLATABLE_VALUE(Device, device)
void play(Ts... x) override { void play(Ts... x) override {
auto device = this->device_.value(x...); auto device = this->device_.value(x...);
this->parent_->set_device(device); this->parent_->set_device(device);
@ -155,6 +157,7 @@ template<typename... Ts> class SetDeviceAction : public Action<Ts...>, public Pa
template<typename... Ts> class SetVolumeAction : public Action<Ts...>, public Parented<DFPlayer> { template<typename... Ts> class SetVolumeAction : public Action<Ts...>, public Parented<DFPlayer> {
public: public:
TEMPLATABLE_VALUE(uint8_t, volume) TEMPLATABLE_VALUE(uint8_t, volume)
void play(Ts... x) override { void play(Ts... x) override {
auto volume = this->volume_.value(x...); auto volume = this->volume_.value(x...);
this->parent_->set_volume(volume); this->parent_->set_volume(volume);
@ -164,6 +167,7 @@ template<typename... Ts> class SetVolumeAction : public Action<Ts...>, public Pa
template<typename... Ts> class SetEqAction : public Action<Ts...>, public Parented<DFPlayer> { template<typename... Ts> class SetEqAction : public Action<Ts...>, public Parented<DFPlayer> {
public: public:
TEMPLATABLE_VALUE(EqPreset, eq) TEMPLATABLE_VALUE(EqPreset, eq)
void play(Ts... x) override { void play(Ts... x) override {
auto eq = this->eq_.value(x...); auto eq = this->eq_.value(x...);
this->parent_->set_eq(eq); this->parent_->set_eq(eq);

View file

@ -0,0 +1 @@
CODEOWNERS = ['@OttoWinter']

View file

@ -1,4 +1,5 @@
#include "display_buffer.h" #include "display_buffer.h"
#include "esphome/core/color.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
@ -7,8 +8,8 @@ namespace display {
static const char *TAG = "display"; static const char *TAG = "display";
const uint8_t COLOR_OFF = 0; const Color COLOR_OFF(0, 0, 0, 0);
const uint8_t COLOR_ON = 1; const Color COLOR_ON(1, 1, 1, 1);
void DisplayBuffer::init_internal_(uint32_t buffer_length) { void DisplayBuffer::init_internal_(uint32_t buffer_length) {
this->buffer_ = new uint8_t[buffer_length]; this->buffer_ = new uint8_t[buffer_length];
@ -18,7 +19,7 @@ void DisplayBuffer::init_internal_(uint32_t buffer_length) {
} }
this->clear(); this->clear();
} }
void DisplayBuffer::fill(int color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); } void DisplayBuffer::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
void DisplayBuffer::clear() { this->fill(COLOR_OFF); } void DisplayBuffer::clear() { this->fill(COLOR_OFF); }
int DisplayBuffer::get_width() { int DisplayBuffer::get_width() {
switch (this->rotation_) { switch (this->rotation_) {
@ -43,7 +44,7 @@ int DisplayBuffer::get_height() {
} }
} }
void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; } void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
void HOT DisplayBuffer::draw_pixel_at(int x, int y, int color) { void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
switch (this->rotation_) { switch (this->rotation_) {
case DISPLAY_ROTATION_0_DEGREES: case DISPLAY_ROTATION_0_DEGREES:
break; break;
@ -63,7 +64,7 @@ void HOT DisplayBuffer::draw_pixel_at(int x, int y, int color) {
this->draw_absolute_pixel_internal(x, y, color); this->draw_absolute_pixel_internal(x, y, color);
App.feed_wdt(); App.feed_wdt();
} }
void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, int color) { void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, Color color) {
const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1; const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1; const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
int32_t err = dx + dy; int32_t err = dx + dy;
@ -83,29 +84,29 @@ void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, int color) {
} }
} }
} }
void HOT DisplayBuffer::horizontal_line(int x, int y, int width, int color) { void HOT DisplayBuffer::horizontal_line(int x, int y, int width, Color color) {
// Future: Could be made more efficient by manipulating buffer directly in certain rotations. // Future: Could be made more efficient by manipulating buffer directly in certain rotations.
for (int i = x; i < x + width; i++) for (int i = x; i < x + width; i++)
this->draw_pixel_at(i, y, color); this->draw_pixel_at(i, y, color);
} }
void HOT DisplayBuffer::vertical_line(int x, int y, int height, int color) { void HOT DisplayBuffer::vertical_line(int x, int y, int height, Color color) {
// Future: Could be made more efficient by manipulating buffer directly in certain rotations. // Future: Could be made more efficient by manipulating buffer directly in certain rotations.
for (int i = y; i < y + height; i++) for (int i = y; i < y + height; i++)
this->draw_pixel_at(x, i, color); this->draw_pixel_at(x, i, color);
} }
void DisplayBuffer::rectangle(int x1, int y1, int width, int height, int color) { void DisplayBuffer::rectangle(int x1, int y1, int width, int height, Color color) {
this->horizontal_line(x1, y1, width, color); this->horizontal_line(x1, y1, width, color);
this->horizontal_line(x1, y1 + height - 1, width, color); this->horizontal_line(x1, y1 + height - 1, width, color);
this->vertical_line(x1, y1, height, color); this->vertical_line(x1, y1, height, color);
this->vertical_line(x1 + width - 1, y1, height, color); this->vertical_line(x1 + width - 1, y1, height, color);
} }
void DisplayBuffer::filled_rectangle(int x1, int y1, int width, int height, int color) { void DisplayBuffer::filled_rectangle(int x1, int y1, int width, int height, Color color) {
// Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses. // Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
for (int i = y1; i < y1 + height; i++) { for (int i = y1; i < y1 + height; i++) {
this->horizontal_line(x1, i, width, color); this->horizontal_line(x1, i, width, color);
} }
} }
void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, int color) { void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, Color color) {
int dx = -radius; int dx = -radius;
int dy = 0; int dy = 0;
int err = 2 - 2 * radius; int err = 2 - 2 * radius;
@ -128,7 +129,7 @@ void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, int colo
} }
} while (dx <= 0); } while (dx <= 0);
} }
void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, int color) { void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, Color color) {
int dx = -int32_t(radius); int dx = -int32_t(radius);
int dy = 0; int dy = 0;
int err = 2 - 2 * radius; int err = 2 - 2 * radius;
@ -155,7 +156,7 @@ void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, int co
} while (dx <= 0); } while (dx <= 0);
} }
void DisplayBuffer::print(int x, int y, Font *font, int color, TextAlign align, const char *text) { void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align, const char *text) {
int x_start, y_start; int x_start, y_start;
int width, height; int width, height;
this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height); this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
@ -197,19 +198,39 @@ void DisplayBuffer::print(int x, int y, Font *font, int color, TextAlign align,
i += match_length; i += match_length;
} }
} }
void DisplayBuffer::vprintf_(int x, int y, Font *font, int color, TextAlign align, const char *format, va_list arg) { void DisplayBuffer::vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg) {
char buffer[256]; char buffer[256];
int ret = vsnprintf(buffer, sizeof(buffer), format, arg); int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
if (ret > 0) if (ret > 0)
this->print(x, y, font, color, align, buffer); this->print(x, y, font, color, align, buffer);
} }
void DisplayBuffer::image(int x, int y, Image *image) {
for (int img_x = 0; img_x < image->get_width(); img_x++) { void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color color_off) {
for (int img_y = 0; img_y < image->get_height(); img_y++) { switch (image->get_type()) {
this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? COLOR_ON : COLOR_OFF); case IMAGE_TYPE_BINARY:
} for (int img_x = 0; img_x < image->get_width(); img_x++) {
for (int img_y = 0; img_y < image->get_height(); img_y++) {
this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? color_on : color_off);
}
}
break;
case IMAGE_TYPE_GRAYSCALE:
for (int img_x = 0; img_x < image->get_width(); img_x++) {
for (int img_y = 0; img_y < image->get_height(); img_y++) {
this->draw_pixel_at(x + img_x, y + img_y, image->get_grayscale_pixel(img_x, img_y));
}
}
break;
case IMAGE_TYPE_RGB24:
for (int img_x = 0; img_x < image->get_width(); img_x++) {
for (int img_y = 0; img_y < image->get_height(); img_y++) {
this->draw_pixel_at(x + img_x, y + img_y, image->get_color_pixel(img_x, img_y));
}
}
break;
} }
} }
void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1, void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1,
int *width, int *height) { int *width, int *height) {
int x_offset, baseline; int x_offset, baseline;
@ -248,7 +269,7 @@ void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font,
break; break;
} }
} }
void DisplayBuffer::print(int x, int y, Font *font, int color, const char *text) { void DisplayBuffer::print(int x, int y, Font *font, Color color, const char *text) {
this->print(x, y, font, color, TextAlign::TOP_LEFT, text); this->print(x, y, font, color, TextAlign::TOP_LEFT, text);
} }
void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char *text) { void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char *text) {
@ -257,13 +278,13 @@ void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char
void DisplayBuffer::print(int x, int y, Font *font, const char *text) { void DisplayBuffer::print(int x, int y, Font *font, const char *text) {
this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text); this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
} }
void DisplayBuffer::printf(int x, int y, Font *font, int color, TextAlign align, const char *format, ...) { void DisplayBuffer::printf(int x, int y, Font *font, Color color, TextAlign align, const char *format, ...) {
va_list arg; va_list arg;
va_start(arg, format); va_start(arg, format);
this->vprintf_(x, y, font, color, align, format, arg); this->vprintf_(x, y, font, color, align, format, arg);
va_end(arg); va_end(arg);
} }
void DisplayBuffer::printf(int x, int y, Font *font, int color, const char *format, ...) { void DisplayBuffer::printf(int x, int y, Font *font, Color color, const char *format, ...) {
va_list arg; va_list arg;
va_start(arg, format); va_start(arg, format);
this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg); this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
@ -306,14 +327,14 @@ void DisplayBuffer::do_update_() {
} }
} }
#ifdef USE_TIME #ifdef USE_TIME
void DisplayBuffer::strftime(int x, int y, Font *font, int color, TextAlign align, const char *format, void DisplayBuffer::strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format,
time::ESPTime time) { time::ESPTime time) {
char buffer[64]; char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format); size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0) if (ret > 0)
this->print(x, y, font, color, align, buffer); this->print(x, y, font, color, align, buffer);
} }
void DisplayBuffer::strftime(int x, int y, Font *font, int color, const char *format, time::ESPTime time) { void DisplayBuffer::strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time) {
this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time); this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
} }
void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) { void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) {
@ -431,10 +452,27 @@ bool Image::get_pixel(int x, int y) const {
const uint32_t pos = x + y * width_8; const uint32_t pos = x + y * width_8;
return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
} }
Color Image::get_color_pixel(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return 0;
const uint32_t pos = (x + y * this->width_) * 3;
const uint32_t color32 = (pgm_read_byte(this->data_start_ + pos + 2) << 0) |
(pgm_read_byte(this->data_start_ + pos + 1) << 8) |
(pgm_read_byte(this->data_start_ + pos + 0) << 16);
return Color(color32);
}
Color Image::get_grayscale_pixel(int x, int y) const {
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
return 0;
const uint32_t pos = (x + y * this->width_);
const uint8_t gray = pgm_read_byte(this->data_start_ + pos);
return Color(gray | gray << 8 | gray << 16 | gray << 24);
}
int Image::get_width() const { return this->width_; } int Image::get_width() const { return this->width_; }
int Image::get_height() const { return this->height_; } int Image::get_height() const { return this->height_; }
Image::Image(const uint8_t *data_start, int width, int height) ImageType Image::get_type() const { return this->type_; }
: width_(width), height_(height), data_start_(data_start) {} Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
: width_(width), height_(height), type_(type), data_start_(data_start) {}
DisplayPage::DisplayPage(const display_writer_t &writer) : writer_(writer) {} DisplayPage::DisplayPage(const display_writer_t &writer) : writer_(writer) {}
void DisplayPage::show() { this->parent_->show_page(this); } void DisplayPage::show() { this->parent_->show_page(this); }

View file

@ -3,6 +3,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/color.h"
#ifdef USE_TIME #ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h" #include "esphome/components/time/real_time_clock.h"
@ -63,9 +64,11 @@ enum class TextAlign {
}; };
/// Turn the pixel OFF. /// Turn the pixel OFF.
extern const uint8_t COLOR_OFF; extern const Color COLOR_OFF;
/// Turn the pixel ON. /// Turn the pixel ON.
extern const uint8_t COLOR_ON; extern const Color COLOR_ON;
enum ImageType { IMAGE_TYPE_BINARY = 0, IMAGE_TYPE_GRAYSCALE = 1, IMAGE_TYPE_RGB24 = 2 };
enum DisplayRotation { enum DisplayRotation {
DISPLAY_ROTATION_0_DEGREES = 0, DISPLAY_ROTATION_0_DEGREES = 0,
@ -91,7 +94,7 @@ using display_writer_t = std::function<void(DisplayBuffer &)>;
class DisplayBuffer { class DisplayBuffer {
public: public:
/// Fill the entire screen with the given color. /// Fill the entire screen with the given color.
virtual void fill(int color); virtual void fill(Color color);
/// Clear the entire screen by filling it with OFF pixels. /// Clear the entire screen by filling it with OFF pixels.
void clear(); void clear();
@ -100,29 +103,29 @@ class DisplayBuffer {
/// Get the height of the image in pixels with rotation applied. /// Get the height of the image in pixels with rotation applied.
int get_height(); int get_height();
/// Set a single pixel at the specified coordinates to the given color. /// Set a single pixel at the specified coordinates to the given color.
void draw_pixel_at(int x, int y, int color = COLOR_ON); void draw_pixel_at(int x, int y, Color color = COLOR_ON);
/// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color. /// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
void line(int x1, int y1, int x2, int y2, int color = COLOR_ON); void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON);
/// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color. /// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
void horizontal_line(int x, int y, int width, int color = COLOR_ON); void horizontal_line(int x, int y, int width, Color color = COLOR_ON);
/// Draw a vertical line from the point [x,y] to [x,y+width] with the given color. /// Draw a vertical line from the point [x,y] to [x,y+width] with the given color.
void vertical_line(int x, int y, int height, int color = COLOR_ON); void vertical_line(int x, int y, int height, Color color = COLOR_ON);
/// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at /// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at
/// [x1+width,y1+height]. /// [x1+width,y1+height].
void rectangle(int x1, int y1, int width, int height, int color = COLOR_ON); void rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
/// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height]. /// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height].
void filled_rectangle(int x1, int y1, int width, int height, int color = COLOR_ON); void filled_rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
/// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color. /// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color.
void circle(int center_x, int center_xy, int radius, int color = COLOR_ON); void circle(int center_x, int center_xy, int radius, Color color = COLOR_ON);
/// Fill a circle centered around [center_x,center_y] with the radius radius with the given color. /// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
void filled_circle(int center_x, int center_y, int radius, int color = COLOR_ON); void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON);
/** Print `text` with the anchor point at [x,y] with `font`. /** Print `text` with the anchor point at [x,y] with `font`.
* *
@ -133,7 +136,7 @@ class DisplayBuffer {
* @param align The alignment of the text. * @param align The alignment of the text.
* @param text The text to draw. * @param text The text to draw.
*/ */
void print(int x, int y, Font *font, int color, TextAlign align, const char *text); void print(int x, int y, Font *font, Color color, TextAlign align, const char *text);
/** Print `text` with the top left at [x,y] with `font`. /** Print `text` with the top left at [x,y] with `font`.
* *
@ -143,7 +146,7 @@ class DisplayBuffer {
* @param color The color to draw the text with. * @param color The color to draw the text with.
* @param text The text to draw. * @param text The text to draw.
*/ */
void print(int x, int y, Font *font, int color, const char *text); void print(int x, int y, Font *font, Color color, const char *text);
/** Print `text` with the anchor point at [x,y] with `font`. /** Print `text` with the anchor point at [x,y] with `font`.
* *
@ -174,7 +177,7 @@ class DisplayBuffer {
* @param format The format to use. * @param format The format to use.
* @param ... The arguments to use for the text formatting. * @param ... The arguments to use for the text formatting.
*/ */
void printf(int x, int y, Font *font, int color, TextAlign align, const char *format, ...) void printf(int x, int y, Font *font, Color color, TextAlign align, const char *format, ...)
__attribute__((format(printf, 7, 8))); __attribute__((format(printf, 7, 8)));
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`. /** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
@ -186,7 +189,7 @@ class DisplayBuffer {
* @param format The format to use. * @param format The format to use.
* @param ... The arguments to use for the text formatting. * @param ... The arguments to use for the text formatting.
*/ */
void printf(int x, int y, Font *font, int color, const char *format, ...) __attribute__((format(printf, 6, 7))); void printf(int x, int y, Font *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7)));
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`. /** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
* *
@ -220,7 +223,7 @@ class DisplayBuffer {
* @param format The strftime format to use. * @param format The strftime format to use.
* @param time The time to format. * @param time The time to format.
*/ */
void strftime(int x, int y, Font *font, int color, TextAlign align, const char *format, time::ESPTime time) void strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, time::ESPTime time)
__attribute__((format(strftime, 7, 0))); __attribute__((format(strftime, 7, 0)));
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`. /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
@ -232,7 +235,7 @@ class DisplayBuffer {
* @param format The strftime format to use. * @param format The strftime format to use.
* @param time The time to format. * @param time The time to format.
*/ */
void strftime(int x, int y, Font *font, int color, const char *format, time::ESPTime time) void strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time)
__attribute__((format(strftime, 6, 0))); __attribute__((format(strftime, 6, 0)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
@ -259,8 +262,15 @@ class DisplayBuffer {
__attribute__((format(strftime, 5, 0))); __attribute__((format(strftime, 5, 0)));
#endif #endif
/// Draw the `image` with the top-left corner at [x,y] to the screen. /** Draw the `image` with the top-left corner at [x,y] to the screen.
void image(int x, int y, Image *image); *
* @param x The x coordinate of the upper left corner.
* @param y The y coordinate of the upper left corner.
* @param image The image to draw
* @param color_on The color to replace in binary images for the on bits.
* @param color_off The color to replace in binary images for the off bits.
*/
void image(int x, int y, Image *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
/** Get the text bounds of the given string. /** Get the text bounds of the given string.
* *
@ -290,9 +300,9 @@ class DisplayBuffer {
void set_rotation(DisplayRotation rotation); void set_rotation(DisplayRotation rotation);
protected: protected:
void vprintf_(int x, int y, Font *font, int color, TextAlign align, const char *format, va_list arg); void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
virtual void draw_absolute_pixel_internal(int x, int y, int color) = 0; virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0;
virtual int get_height_internal() = 0; virtual int get_height_internal() = 0;
@ -377,20 +387,25 @@ class Font {
class Image { class Image {
public: public:
Image(const uint8_t *data_start, int width, int height); Image(const uint8_t *data_start, int width, int height, ImageType type);
bool get_pixel(int x, int y) const; bool get_pixel(int x, int y) const;
Color get_color_pixel(int x, int y) const;
Color get_grayscale_pixel(int x, int y) const;
int get_width() const; int get_width() const;
int get_height() const; int get_height() const;
ImageType get_type() const;
protected: protected:
int width_; int width_;
int height_; int height_;
ImageType type_;
const uint8_t *data_start_; const uint8_t *data_start_;
}; };
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> { template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
public: public:
TEMPLATABLE_VALUE(DisplayPage *, page) TEMPLATABLE_VALUE(DisplayPage *, page)
void play(Ts... x) override { void play(Ts... x) override {
auto *page = this->page_.value(x...); auto *page = this->page_.value(x...);
if (page != nullptr) { if (page != nullptr) {
@ -402,18 +417,18 @@ template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
template<typename... Ts> class DisplayPageShowNextAction : public Action<Ts...> { template<typename... Ts> class DisplayPageShowNextAction : public Action<Ts...> {
public: public:
DisplayPageShowNextAction(DisplayBuffer *buffer) : buffer_(buffer) {} DisplayPageShowNextAction(DisplayBuffer *buffer) : buffer_(buffer) {}
void play(Ts... x) override { this->buffer_->show_next_page(); } void play(Ts... x) override { this->buffer_->show_next_page(); }
protected:
DisplayBuffer *buffer_; DisplayBuffer *buffer_;
}; };
template<typename... Ts> class DisplayPageShowPrevAction : public Action<Ts...> { template<typename... Ts> class DisplayPageShowPrevAction : public Action<Ts...> {
public: public:
DisplayPageShowPrevAction(DisplayBuffer *buffer) : buffer_(buffer) {} DisplayPageShowPrevAction(DisplayBuffer *buffer) : buffer_(buffer) {}
void play(Ts... x) override { this->buffer_->show_prev_page(); } void play(Ts... x) override { this->buffer_->show_prev_page(); }
protected:
DisplayBuffer *buffer_; DisplayBuffer *buffer_;
}; };

View file

@ -0,0 +1,49 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components.light.types import AddressableLightEffect
from esphome.components.light.effects import register_addressable_effect
from esphome.const import CONF_ID, CONF_NAME, CONF_METHOD, CONF_CHANNELS
e131_ns = cg.esphome_ns.namespace('e131')
E131AddressableLightEffect = e131_ns.class_('E131AddressableLightEffect', AddressableLightEffect)
E131Component = e131_ns.class_('E131Component', cg.Component)
METHODS = {
'UNICAST': e131_ns.E131_UNICAST,
'MULTICAST': e131_ns.E131_MULTICAST
}
CHANNELS = {
'MONO': e131_ns.E131_MONO,
'RGB': e131_ns.E131_RGB,
'RGBW': e131_ns.E131_RGBW
}
CONF_UNIVERSE = 'universe'
CONF_E131_ID = 'e131_id'
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(E131Component),
cv.Optional(CONF_METHOD, default='MULTICAST'): cv.one_of(*METHODS, upper=True),
})
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
cg.add(var.set_method(METHODS[config[CONF_METHOD]]))
@register_addressable_effect('e131', E131AddressableLightEffect, "E1.31", {
cv.GenerateID(CONF_E131_ID): cv.use_id(E131Component),
cv.Required(CONF_UNIVERSE): cv.int_range(min=1, max=512),
cv.Optional(CONF_CHANNELS, default='RGB'): cv.one_of(*CHANNELS, upper=True)
})
def e131_light_effect_to_code(config, effect_id):
parent = yield cg.get_variable(config[CONF_E131_ID])
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
cg.add(effect.set_first_universe(config[CONF_UNIVERSE]))
cg.add(effect.set_channels(CHANNELS[config[CONF_CHANNELS]]))
cg.add(effect.set_e131(parent))
yield effect

View file

@ -0,0 +1,106 @@
#include "e131.h"
#include "e131_addressable_light_effect.h"
#include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP32
#include <WiFi.h>
#endif
#ifdef ARDUINO_ARCH_ESP8266
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#endif
namespace esphome {
namespace e131 {
static const char *TAG = "e131";
static const int PORT = 5568;
E131Component::E131Component() {}
E131Component::~E131Component() {
if (udp_) {
udp_->stop();
}
}
void E131Component::setup() {
udp_.reset(new WiFiUDP());
if (!udp_->begin(PORT)) {
ESP_LOGE(TAG, "Cannot bind E131 to %d.", PORT);
mark_failed();
return;
}
join_igmp_groups_();
}
void E131Component::loop() {
std::vector<uint8_t> payload;
E131Packet packet;
int universe = 0;
while (uint16_t packet_size = udp_->parsePacket()) {
payload.resize(packet_size);
if (!udp_->read(&payload[0], payload.size())) {
continue;
}
if (!packet_(payload, universe, packet)) {
ESP_LOGV(TAG, "Invalid packet recevied of size %zu.", payload.size());
continue;
}
if (!process_(universe, packet)) {
ESP_LOGV(TAG, "Ignored packet for %d universe of size %d.", universe, packet.count);
}
}
}
void E131Component::add_effect(E131AddressableLightEffect *light_effect) {
if (light_effects_.count(light_effect)) {
return;
}
ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name().c_str(),
light_effect->get_first_universe(), light_effect->get_last_universe());
light_effects_.insert(light_effect);
for (auto universe = light_effect->get_first_universe(); universe <= light_effect->get_last_universe(); ++universe) {
join_(universe);
}
}
void E131Component::remove_effect(E131AddressableLightEffect *light_effect) {
if (!light_effects_.count(light_effect)) {
return;
}
ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name().c_str(),
light_effect->get_first_universe(), light_effect->get_last_universe());
light_effects_.erase(light_effect);
for (auto universe = light_effect->get_first_universe(); universe <= light_effect->get_last_universe(); ++universe) {
leave_(universe);
}
}
bool E131Component::process_(int universe, const E131Packet &packet) {
bool handled = false;
ESP_LOGV(TAG, "Received E1.31 packet for %d universe, with %d bytes", universe, packet.count);
for (auto light_effect : light_effects_) {
handled = light_effect->process_(universe, packet) || handled;
}
return handled;
}
} // namespace e131
} // namespace esphome

View file

@ -0,0 +1,57 @@
#pragma once
#include "esphome/core/component.h"
#include <memory>
#include <set>
#include <map>
class UDP;
namespace esphome {
namespace e131 {
class E131AddressableLightEffect;
enum E131ListenMethod { E131_MULTICAST, E131_UNICAST };
const int E131_MAX_PROPERTY_VALUES_COUNT = 513;
struct E131Packet {
uint16_t count;
uint8_t values[E131_MAX_PROPERTY_VALUES_COUNT];
};
class E131Component : public esphome::Component {
public:
E131Component();
~E131Component();
void setup() override;
void loop() override;
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
public:
void add_effect(E131AddressableLightEffect *light_effect);
void remove_effect(E131AddressableLightEffect *light_effect);
public:
void set_method(E131ListenMethod listen_method) { this->listen_method_ = listen_method; }
protected:
bool packet_(const std::vector<uint8_t> &data, int &universe, E131Packet &packet);
bool process_(int universe, const E131Packet &packet);
bool join_igmp_groups_();
void join_(int universe);
void leave_(int universe);
protected:
E131ListenMethod listen_method_{E131_MULTICAST};
std::unique_ptr<UDP> udp_;
std::set<E131AddressableLightEffect *> light_effects_;
std::map<int, int> universe_consumers_;
std::map<int, E131Packet> universe_packets_;
};
} // namespace e131
} // namespace esphome

View file

@ -0,0 +1,90 @@
#include "e131.h"
#include "e131_addressable_light_effect.h"
#include "esphome/core/log.h"
namespace esphome {
namespace e131 {
static const char *TAG = "e131_addressable_light_effect";
static const int MAX_DATA_SIZE = (sizeof(E131Packet::values) - 1);
E131AddressableLightEffect::E131AddressableLightEffect(const std::string &name) : AddressableLightEffect(name) {}
int E131AddressableLightEffect::get_data_per_universe() const { return get_lights_per_universe() * channels_; }
int E131AddressableLightEffect::get_lights_per_universe() const { return MAX_DATA_SIZE / channels_; }
int E131AddressableLightEffect::get_first_universe() const { return first_universe_; }
int E131AddressableLightEffect::get_last_universe() const { return first_universe_ + get_universe_count() - 1; }
int E131AddressableLightEffect::get_universe_count() const {
// Round up to lights_per_universe
auto lights = get_lights_per_universe();
return (get_addressable_()->size() + lights - 1) / lights;
}
void E131AddressableLightEffect::start() {
AddressableLightEffect::start();
if (this->e131_) {
this->e131_->add_effect(this);
}
}
void E131AddressableLightEffect::stop() {
if (this->e131_) {
this->e131_->remove_effect(this);
}
AddressableLightEffect::stop();
}
void E131AddressableLightEffect::apply(light::AddressableLight &it, const light::ESPColor &current_color) {
// ignore, it is run by `E131Component::update()`
}
bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet) {
auto it = get_addressable_();
// check if this is our universe and data are valid
if (universe < first_universe_ || universe > get_last_universe())
return false;
int output_offset = (universe - first_universe_) * get_lights_per_universe();
// limit amount of lights per universe and received
int output_end = std::min(it->size(), std::min(output_offset + get_lights_per_universe(), packet.count - 1));
auto input_data = packet.values + 1;
ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %d-%d.", get_name().c_str(), universe, output_offset,
output_end);
switch (channels_) {
case E131_MONO:
for (; output_offset < output_end; output_offset++, input_data++) {
auto output = (*it)[output_offset];
output.set(light::ESPColor(input_data[0], input_data[0], input_data[0], input_data[0]));
}
break;
case E131_RGB:
for (; output_offset < output_end; output_offset++, input_data += 3) {
auto output = (*it)[output_offset];
output.set(light::ESPColor(input_data[0], input_data[1], input_data[2],
(input_data[0] + input_data[1] + input_data[2]) / 3));
}
break;
case E131_RGBW:
for (; output_offset < output_end; output_offset++, input_data += 4) {
auto output = (*it)[output_offset];
output.set(light::ESPColor(input_data[0], input_data[1], input_data[2], input_data[3]));
}
break;
}
return true;
}
} // namespace e131
} // namespace esphome

View file

@ -0,0 +1,48 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/light/addressable_light_effect.h"
namespace esphome {
namespace e131 {
class E131Component;
struct E131Packet;
enum E131LightChannels { E131_MONO = 1, E131_RGB = 3, E131_RGBW = 4 };
class E131AddressableLightEffect : public light::AddressableLightEffect {
public:
E131AddressableLightEffect(const std::string &name);
public:
void start() override;
void stop() override;
void apply(light::AddressableLight &it, const light::ESPColor &current_color) override;
public:
int get_data_per_universe() const;
int get_lights_per_universe() const;
int get_first_universe() const;
int get_last_universe() const;
int get_universe_count() const;
public:
void set_first_universe(int universe) { this->first_universe_ = universe; }
void set_channels(E131LightChannels channels) { this->channels_ = channels; }
void set_e131(E131Component *e131) { this->e131_ = e131; }
protected:
bool process_(int universe, const E131Packet &packet);
protected:
int first_universe_{0};
int last_universe_{0};
E131LightChannels channels_{E131_RGB};
E131Component *e131_{nullptr};
friend class E131Component;
};
} // namespace e131
} // namespace esphome

View file

@ -0,0 +1,136 @@
#include "e131.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
#include <lwip/ip_addr.h>
#include <lwip/igmp.h>
namespace esphome {
namespace e131 {
static const char *TAG = "e131";
static const uint8_t ACN_ID[12] = {0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00};
static const uint32_t VECTOR_ROOT = 4;
static const uint32_t VECTOR_FRAME = 2;
static const uint8_t VECTOR_DMP = 2;
// E1.31 Packet Structure
union E131RawPacket {
struct {
// Root Layer
uint16_t preamble_size;
uint16_t postamble_size;
uint8_t acn_id[12];
uint16_t root_flength;
uint32_t root_vector;
uint8_t cid[16];
// Frame Layer
uint16_t frame_flength;
uint32_t frame_vector;
uint8_t source_name[64];
uint8_t priority;
uint16_t reserved;
uint8_t sequence_number;
uint8_t options;
uint16_t universe;
// DMP Layer
uint16_t dmp_flength;
uint8_t dmp_vector;
uint8_t type;
uint16_t first_address;
uint16_t address_increment;
uint16_t property_value_count;
uint8_t property_values[E131_MAX_PROPERTY_VALUES_COUNT];
} __attribute__((packed));
uint8_t raw[638];
};
// We need to have at least one `1` value
// Get the offset of `property_values[1]`
const long E131_MIN_PACKET_SIZE = reinterpret_cast<long>(&((E131RawPacket *) nullptr)->property_values[1]);
bool E131Component::join_igmp_groups_() {
if (listen_method_ != E131_MULTICAST)
return false;
if (!udp_)
return false;
for (auto universe : universe_consumers_) {
if (!universe.second)
continue;
ip4_addr_t multicast_addr = {
static_cast<uint32_t>(IPAddress(239, 255, ((universe.first >> 8) & 0xff), ((universe.first >> 0) & 0xff)))};
auto err = igmp_joingroup(IP4_ADDR_ANY4, &multicast_addr);
if (err) {
ESP_LOGW(TAG, "IGMP join for %d universe of E1.31 failed. Multicast might not work.", universe.first);
}
}
return true;
}
void E131Component::join_(int universe) {
// store only latest received packet for the given universe
auto consumers = ++universe_consumers_[universe];
if (consumers > 1) {
return; // we already joined before
}
if (join_igmp_groups_()) {
ESP_LOGD(TAG, "Joined %d universe for E1.31.", universe);
}
}
void E131Component::leave_(int universe) {
auto consumers = --universe_consumers_[universe];
if (consumers > 0) {
return; // we have other consumers of the given universe
}
if (listen_method_ == E131_MULTICAST) {
ip4_addr_t multicast_addr = {
static_cast<uint32_t>(IPAddress(239, 255, ((universe >> 8) & 0xff), ((universe >> 0) & 0xff)))};
igmp_leavegroup(IP4_ADDR_ANY4, &multicast_addr);
}
ESP_LOGD(TAG, "Left %d universe for E1.31.", universe);
}
bool E131Component::packet_(const std::vector<uint8_t> &data, int &universe, E131Packet &packet) {
if (data.size() < E131_MIN_PACKET_SIZE)
return false;
auto sbuff = reinterpret_cast<const E131RawPacket *>(&data[0]);
if (memcmp(sbuff->acn_id, ACN_ID, sizeof(sbuff->acn_id)) != 0)
return false;
if (htonl(sbuff->root_vector) != VECTOR_ROOT)
return false;
if (htonl(sbuff->frame_vector) != VECTOR_FRAME)
return false;
if (sbuff->dmp_vector != VECTOR_DMP)
return false;
if (sbuff->property_values[0] != 0)
return false;
universe = htons(sbuff->universe);
packet.count = htons(sbuff->property_value_count);
if (packet.count > E131_MAX_PROPERTY_VALUES_COUNT)
return false;
memcpy(packet.values, sbuff->property_values, packet.count);
return true;
}
} // namespace e131
} // namespace esphome

View file

@ -94,7 +94,7 @@ void EndstopCover::dump_config() {
float EndstopCover::get_setup_priority() const { return setup_priority::DATA; } float EndstopCover::get_setup_priority() const { return setup_priority::DATA; }
void EndstopCover::stop_prev_trigger_() { void EndstopCover::stop_prev_trigger_() {
if (this->prev_command_trigger_ != nullptr) { if (this->prev_command_trigger_ != nullptr) {
this->prev_command_trigger_->stop(); this->prev_command_trigger_->stop_action();
this->prev_command_trigger_ = nullptr; this->prev_command_trigger_ = nullptr;
} }
} }

View file

@ -99,14 +99,14 @@ bool ESP32BLETracker::ble_setup() {
return false; return false;
} }
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
// Initialize the bluetooth controller with the default configuration // Initialize the bluetooth controller with the default configuration
if (!btStart()) { if (!btStart()) {
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
return false; return false;
} }
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
err = esp_bluedroid_init(); err = esp_bluedroid_init();
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err); ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err);

View file

@ -28,6 +28,7 @@ class ESPBTUUID {
bool contains(uint8_t data1, uint8_t data2) const; bool contains(uint8_t data1, uint8_t data2) const;
bool operator==(const ESPBTUUID &uuid) const; bool operator==(const ESPBTUUID &uuid) const;
bool operator!=(const ESPBTUUID &uuid) const { return !(*this == uuid); }
esp_bt_uuid_t get_uuid(); esp_bt_uuid_t get_uuid();
@ -74,6 +75,8 @@ class ESPBTDevice {
uint64_t address_uint64() const; uint64_t address_uint64() const;
const uint8_t *address() const { return address_; }
esp_ble_addr_type_t get_address_type() const { return this->address_type_; } esp_ble_addr_type_t get_address_type() const { return this->address_type_; }
int get_rssi() const { return rssi_; } int get_rssi() const { return rssi_; }
const std::string &get_name() const { return this->name_; } const std::string &get_name() const { return this->name_; }

View file

@ -13,7 +13,7 @@ class ESP8266PWM : public output::FloatOutput, public Component {
void set_pin(GPIOPin *pin) { pin_ = pin; } void set_pin(GPIOPin *pin) { pin_ = pin; }
void set_frequency(float frequency) { this->frequency_ = frequency; } void set_frequency(float frequency) { this->frequency_ = frequency; }
/// Dynamically update frequency /// Dynamically update frequency
void update_frequency(float frequency) { void update_frequency(float frequency) override {
this->set_frequency(frequency); this->set_frequency(frequency);
this->write_state(this->last_output_); this->write_state(this->last_output_);
} }
@ -43,7 +43,6 @@ template<typename... Ts> class SetFrequencyAction : public Action<Ts...> {
this->parent_->update_frequency(freq); this->parent_->update_frequency(freq);
} }
protected:
ESP8266PWM *parent_; ESP8266PWM *parent_;
}; };

View file

@ -37,7 +37,7 @@ void EthernetComponent::setup() {
} }
void EthernetComponent::loop() { void EthernetComponent::loop() {
const uint32_t now = millis(); const uint32_t now = millis();
if (!this->connected_ && !this->last_connected_ && now - this->last_connected_ > 15000) { if (!this->connected_ && !this->last_connected_ && now - this->connect_begin_ > 15000) {
ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting..."); ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting...");
this->start_connect_(); this->start_connect_();
return; return;

View file

@ -0,0 +1,29 @@
import esphome.codegen as cg
from esphome import automation
import esphome.config_validation as cv
from esphome.components import esp32_ble_tracker
from esphome.const import CONF_TRIGGER_ID
CODEOWNERS = ['@OttoWinter']
DEPENDENCIES = ['esp32_ble_tracker']
exposure_notifications_ns = cg.esphome_ns.namespace('exposure_notifications')
ExposureNotification = exposure_notifications_ns.struct('ExposureNotification')
ExposureNotificationTrigger = exposure_notifications_ns.class_(
'ExposureNotificationTrigger', esp32_ble_tracker.ESPBTDeviceListener,
automation.Trigger.template(ExposureNotification))
CONF_ON_EXPOSURE_NOTIFICATION = 'on_exposure_notification'
CONFIG_SCHEMA = cv.Schema({
cv.Required(CONF_ON_EXPOSURE_NOTIFICATION): automation.validate_automation(cv.Schema({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ExposureNotificationTrigger),
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)),
})
def to_code(config):
for conf in config.get(CONF_ON_EXPOSURE_NOTIFICATION, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
yield automation.build_automation(trigger, [(ExposureNotification, 'x')], conf)
yield esp32_ble_tracker.register_ble_device(trigger, conf)

View file

@ -0,0 +1,49 @@
#include "exposure_notifications.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace exposure_notifications {
using namespace esp32_ble_tracker;
static const char *TAG = "exposure_notifications";
bool ExposureNotificationTrigger::parse_device(const ESPBTDevice &device) {
// See also https://blog.google/documents/70/Exposure_Notification_-_Bluetooth_Specification_v1.2.2.pdf
if (device.get_service_uuids().size() != 1)
return false;
// Exposure notifications have Service UUID FD 6F
ESPBTUUID uuid = device.get_service_uuids()[0];
// constant service identifier
const ESPBTUUID expected_uuid = ESPBTUUID::from_uint16(0xFD6F);
if (uuid != expected_uuid)
return false;
if (device.get_service_datas().size() != 1)
return false;
// The service data should be 20 bytes
// First 16 bytes are the rolling proximity identifier (RPI)
// Then 4 bytes of encrypted metadata follow which can be used to get the transmit power level.
ServiceData service_data = device.get_service_datas()[0];
if (service_data.uuid != expected_uuid)
return false;
auto data = service_data.data;
if (data.size() != 20)
return false;
ExposureNotification notification{};
memcpy(&notification.address[0], device.address(), 6);
memcpy(&notification.rolling_proximity_identifier[0], &data[0], 16);
memcpy(&notification.associated_encrypted_metadata[0], &data[16], 4);
notification.rssi = device.get_rssi();
this->trigger(notification);
return true;
}
} // namespace exposure_notifications
} // namespace esphome
#endif

View file

@ -0,0 +1,29 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include <array>
#ifdef ARDUINO_ARCH_ESP32
namespace esphome {
namespace exposure_notifications {
struct ExposureNotification {
std::array<uint8_t, 6> address;
int rssi;
std::array<uint8_t, 16> rolling_proximity_identifier;
std::array<uint8_t, 4> associated_encrypted_metadata;
};
class ExposureNotificationTrigger : public Trigger<ExposureNotification>,
public esp32_ble_tracker::ESPBTDeviceListener {
public:
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
};
} // namespace exposure_notifications
} // namespace esphome
#endif

View file

@ -34,6 +34,10 @@ FAN_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
cv.publish_topic), cv.publish_topic),
cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(cv.requires_component('mqtt'), cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(cv.requires_component('mqtt'),
cv.subscribe_topic), cv.subscribe_topic),
cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All(cv.requires_component('mqtt'),
cv.publish_topic),
cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All(cv.requires_component('mqtt'),
cv.subscribe_topic),
}) })

View file

@ -25,7 +25,6 @@ template<typename... Ts> class TurnOnAction : public Action<Ts...> {
call.perform(); call.perform();
} }
protected:
FanState *state_; FanState *state_;
}; };
@ -35,7 +34,6 @@ template<typename... Ts> class TurnOffAction : public Action<Ts...> {
void play(Ts... x) override { this->state_->turn_off().perform(); } void play(Ts... x) override { this->state_->turn_off().perform(); }
protected:
FanState *state_; FanState *state_;
}; };
@ -45,7 +43,6 @@ template<typename... Ts> class ToggleAction : public Action<Ts...> {
void play(Ts... x) override { this->state_->toggle().perform(); } void play(Ts... x) override { this->state_->toggle().perform(); }
protected:
FanState *state_; FanState *state_;
}; };

View file

@ -22,6 +22,7 @@ struct FanStateRTCState {
bool state; bool state;
FanSpeed speed; FanSpeed speed;
bool oscillating; bool oscillating;
FanDirection direction;
}; };
void FanState::setup() { void FanState::setup() {
@ -34,6 +35,7 @@ void FanState::setup() {
call.set_state(recovered.state); call.set_state(recovered.state);
call.set_speed(recovered.speed); call.set_speed(recovered.speed);
call.set_oscillating(recovered.oscillating); call.set_oscillating(recovered.oscillating);
call.set_direction(recovered.direction);
call.perform(); call.perform();
} }
float FanState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } float FanState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
@ -46,6 +48,9 @@ void FanStateCall::perform() const {
if (this->oscillating_.has_value()) { if (this->oscillating_.has_value()) {
this->state_->oscillating = *this->oscillating_; this->state_->oscillating = *this->oscillating_;
} }
if (this->direction_.has_value()) {
this->state_->direction = *this->direction_;
}
if (this->speed_.has_value()) { if (this->speed_.has_value()) {
switch (*this->speed_) { switch (*this->speed_) {
case FAN_SPEED_LOW: case FAN_SPEED_LOW:
@ -63,6 +68,7 @@ void FanStateCall::perform() const {
saved.state = this->state_->state; saved.state = this->state_->state;
saved.speed = this->state_->speed; saved.speed = this->state_->speed;
saved.oscillating = this->state_->oscillating; saved.oscillating = this->state_->oscillating;
saved.direction = this->state_->direction;
this->state_->rtc_.save(&saved); this->state_->rtc_.save(&saved);
this->state_->state_callback_.call(); this->state_->state_callback_.call();

View file

@ -15,6 +15,9 @@ enum FanSpeed {
FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed. FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed.
}; };
/// Simple enum to represent the direction of a fan
enum FanDirection { FAN_DIRECTION_FORWARD = 0, FAN_DIRECTION_REVERSE = 1 };
class FanState; class FanState;
class FanStateCall { class FanStateCall {
@ -46,6 +49,14 @@ class FanStateCall {
return *this; return *this;
} }
FanStateCall &set_speed(const char *speed); FanStateCall &set_speed(const char *speed);
FanStateCall &set_direction(FanDirection direction) {
this->direction_ = direction;
return *this;
}
FanStateCall &set_direction(optional<FanDirection> direction) {
this->direction_ = direction;
return *this;
}
void perform() const; void perform() const;
@ -54,6 +65,7 @@ class FanStateCall {
optional<bool> binary_state_; optional<bool> binary_state_;
optional<bool> oscillating_{}; optional<bool> oscillating_{};
optional<FanSpeed> speed_{}; optional<FanSpeed> speed_{};
optional<FanDirection> direction_{};
}; };
class FanState : public Nameable, public Component { class FanState : public Nameable, public Component {
@ -76,6 +88,8 @@ class FanState : public Nameable, public Component {
bool oscillating{false}; bool oscillating{false};
/// The current fan speed. /// The current fan speed.
FanSpeed speed{FAN_SPEED_HIGH}; FanSpeed speed{FAN_SPEED_HIGH};
/// The current direction of the fan
FanDirection direction{FAN_DIRECTION_FORWARD};
FanStateCall turn_on(); FanStateCall turn_on();
FanStateCall turn_off(); FanStateCall turn_off();

View file

@ -6,7 +6,8 @@ namespace fan {
class FanTraits { class FanTraits {
public: public:
FanTraits() = default; FanTraits() = default;
FanTraits(bool oscillation, bool speed) : oscillation_(oscillation), speed_(speed) {} FanTraits(bool oscillation, bool speed, bool direction)
: oscillation_(oscillation), speed_(speed), direction_(direction) {}
/// Return if this fan supports oscillation. /// Return if this fan supports oscillation.
bool supports_oscillation() const { return this->oscillation_; } bool supports_oscillation() const { return this->oscillation_; }
@ -16,10 +17,15 @@ class FanTraits {
bool supports_speed() const { return this->speed_; } bool supports_speed() const { return this->speed_; }
/// Set whether this fan supports speed modes. /// Set whether this fan supports speed modes.
void set_speed(bool speed) { this->speed_ = speed; } void set_speed(bool speed) { this->speed_ = speed; }
/// Return if this fan supports changing direction
bool supports_direction() const { return this->direction_; }
/// Set whether this fan supports changing direction
void set_direction(bool direction) { this->direction_ = direction; }
protected: protected:
bool oscillation_{false}; bool oscillation_{false};
bool speed_{false}; bool speed_{false};
bool direction_{false};
}; };
} // namespace fan } // namespace fan

View file

@ -4,6 +4,7 @@ from esphome.components import light
from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_RGB_ORDER, CONF_MAX_REFRESH_RATE from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_RGB_ORDER, CONF_MAX_REFRESH_RATE
from esphome.core import coroutine from esphome.core import coroutine
CODEOWNERS = ['@OttoWinter']
fastled_base_ns = cg.esphome_ns.namespace('fastled_base') fastled_base_ns = cg.esphome_ns.namespace('fastled_base')
FastLEDLightOutput = fastled_base_ns.class_('FastLEDLightOutput', light.AddressableLight) FastLEDLightOutput = fastled_base_ns.class_('FastLEDLightOutput', light.AddressableLight)
@ -35,5 +36,7 @@ def new_fastled_light(config):
yield light.register_light(var, config) yield light.register_light(var, config)
# https://github.com/FastLED/FastLED/blob/master/library.json # https://github.com/FastLED/FastLED/blob/master/library.json
cg.add_library('FastLED', '3.3.3') # 3.3.3 has an issue on ESP32 with RMT and fastled_clockless:
# https://github.com/esphome/issues/issues/1375
cg.add_library('FastLED', '3.3.2')
yield var yield var

View file

@ -5,6 +5,7 @@ from esphome import codegen as cg
from esphome.const import CONF_ID, CONF_INITIAL_VALUE, CONF_RESTORE_VALUE, CONF_TYPE, CONF_VALUE from esphome.const import CONF_ID, CONF_INITIAL_VALUE, CONF_RESTORE_VALUE, CONF_TYPE, CONF_VALUE
from esphome.core import coroutine_with_priority from esphome.core import coroutine_with_priority
CODEOWNERS = ['@esphome/core']
globals_ns = cg.esphome_ns.namespace('globals') globals_ns = cg.esphome_ns.namespace('globals')
GlobalsComponent = globals_ns.class_('GlobalsComponent', cg.Component) GlobalsComponent = globals_ns.class_('GlobalsComponent', cg.Component)
GlobalVarSetAction = globals_ns.class_('GlobalVarSetAction', automation.Action) GlobalVarSetAction = globals_ns.class_('GlobalVarSetAction', automation.Action)

View file

@ -1,3 +1,4 @@
import esphome.codegen as cg import esphome.codegen as cg
CODEOWNERS = ['@esphome/core']
gpio_ns = cg.esphome_ns.namespace('gpio') gpio_ns = cg.esphome_ns.namespace('gpio')

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