mirror of
https://github.com/esphome/esphome.git
synced 2025-01-03 11:21:43 +01:00
Merge branch 'dev' of https://github.com/esphome/esphome into dev
This commit is contained in:
commit
5ebf4223e2
420 changed files with 12835 additions and 1929 deletions
12
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
12
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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.
|
||||
|
8
.github/ci-reporter.yml
vendored
8
.github/ci-reporter.yml
vendored
|
@ -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
11
.github/config.yml
vendored
|
@ -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
9
.github/dependabot.yml
vendored
Normal 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
54
.github/workflows/ci-docker.yml
vendored
Normal 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
176
.github/workflows/ci.yml
vendored
Normal 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
|
16
.github/workflows/matchers/ci-custom.json
vendored
Normal file
16
.github/workflows/matchers/ci-custom.json
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "ci-custom",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"message": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
17
.github/workflows/matchers/clang-tidy.json
vendored
Normal file
17
.github/workflows/matchers/clang-tidy.json
vendored
Normal 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
18
.github/workflows/matchers/gcc.json
vendored
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
28
.github/workflows/matchers/lint-python.json
vendored
Normal file
28
.github/workflows/matchers/lint-python.json
vendored
Normal 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
18
.github/workflows/matchers/python.json
vendored
Normal 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
262
.github/workflows/release-dev.yml
vendored
Normal 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
325
.github/workflows/release.yml
vendored
Normal 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
1
.gitignore
vendored
|
@ -54,6 +54,7 @@ htmlcov/
|
|||
.esphome
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
cov.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
|
342
.gitlab-ci.yml
342
.gitlab-ci.yml
|
@ -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"
|
43
.travis.yml
43
.travis.yml
|
@ -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
69
CODEOWNERS
Normal 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
|
|
@ -1,5 +1,6 @@
|
|||
include LICENSE
|
||||
include README.md
|
||||
include requirements.txt
|
||||
include esphome/dashboard/templates/*.html
|
||||
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
|
||||
recursive-include esphome *.cpp *.h *.tcc
|
||||
|
|
|
@ -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}
|
||||
|
||||
# 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 . .
|
||||
RUN pip3 install --no-cache-dir -e .
|
||||
|
||||
ENV USERNAME=""
|
||||
ENV PASSWORD=""
|
||||
# Settings for dashboard
|
||||
ENV USERNAME="" PASSWORD=""
|
||||
|
||||
# The directory the user should mount their configuration files to
|
||||
WORKDIR /config
|
||||
# Set entrypoint to esphome so that the user doesn't have to type 'esphome'
|
||||
# in every docker command twice
|
||||
ENTRYPOINT ["esphome"]
|
||||
# When no arguments given, start the dashboard in the workdir
|
||||
CMD ["/config", "dashboard"]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM esphome/esphome-base-amd64:2.0.1
|
||||
FROM esphome/esphome-base-amd64:2.6.0
|
||||
|
||||
COPY . .
|
||||
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
ARG 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 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
|
||||
|
||||
# Build arguments
|
||||
|
|
|
@ -1,18 +1,7 @@
|
|||
FROM esphome/esphome-base-amd64:2.0.1
|
||||
FROM esphome/esphome-lint-base:2.6.0
|
||||
|
||||
RUN \
|
||||
apt-get update \
|
||||
&& 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
|
||||
COPY requirements.txt requirements_test.txt /
|
||||
RUN pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt
|
||||
|
||||
VOLUME ["/esphome"]
|
||||
WORKDIR /esphome
|
||||
|
|
0
docker/rootfs/etc/cont-init.d/30-esphome.sh
Normal file → Executable file
0
docker/rootfs/etc/cont-init.d/30-esphome.sh
Normal file → Executable file
0
docker/rootfs/etc/cont-init.d/40-migrate.sh
Normal file → Executable file
0
docker/rootfs/etc/cont-init.d/40-migrate.sh
Normal file → Executable file
0
docker/rootfs/etc/nginx/nginx.conf
Executable file → Normal file
0
docker/rootfs/etc/nginx/nginx.conf
Executable file → Normal 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
|
||||
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
||||
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__)
|
||||
|
||||
|
||||
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):
|
||||
if not options:
|
||||
raise EsphomeError("Found no valid options for upload/logging, please make sure relevant "
|
||||
"sections (ota, mqtt, ...) are in your configuration and/or the device "
|
||||
"is plugged in.")
|
||||
"sections (ota, api, mqtt, ...) are in your configuration and/or the "
|
||||
"device is plugged in.")
|
||||
|
||||
if len(options) == 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):
|
||||
options = []
|
||||
for res, desc in get_serial_ports():
|
||||
options.append((f"{res} ({desc})", res))
|
||||
for port in get_serial_ports():
|
||||
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):
|
||||
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||
if default == 'OTA':
|
||||
|
@ -131,6 +119,11 @@ def wrap_to_code(name, comp):
|
|||
|
||||
|
||||
def write_cpp(config):
|
||||
generate_cpp_contents(config)
|
||||
return write_cpp_file()
|
||||
|
||||
|
||||
def generate_cpp_contents(config):
|
||||
_LOGGER.info("Generating C++ source...")
|
||||
|
||||
for name, component, conf in iter_components(CORE.config):
|
||||
|
@ -140,6 +133,8 @@ def write_cpp(config):
|
|||
|
||||
CORE.flush_tasks()
|
||||
|
||||
|
||||
def write_cpp_file():
|
||||
writer.write_platformio_project()
|
||||
|
||||
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.",
|
||||
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='*')
|
||||
|
||||
subparsers = parser.add_subparsers(help='Commands', dest='command')
|
||||
|
@ -532,7 +529,7 @@ def run_esphome(argv):
|
|||
CORE.config_path = conf_path
|
||||
CORE.dashboard = args.dashboard
|
||||
|
||||
config = read_config()
|
||||
config = read_config(dict(args.substitution) if args.substitution else {})
|
||||
if config is None:
|
||||
return 1
|
||||
CORE.config = config
|
||||
|
|
|
@ -4,6 +4,8 @@ from esphome import pins
|
|||
from esphome.components import output
|
||||
from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD
|
||||
|
||||
CODEOWNERS = ['@glmnet']
|
||||
|
||||
ac_dimmer_ns = cg.esphome_ns.namespace('ac_dimmer')
|
||||
AcDimmer = ac_dimmer_ns.class_('AcDimmer', output.FloatOutput, cg.Component)
|
||||
|
||||
|
|
24
esphome/components/adalight/__init__.py
Normal file
24
esphome/components/adalight/__init__.py
Normal 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
|
140
esphome/components/adalight/adalight_light_effect.cpp
Normal file
140
esphome/components/adalight/adalight_light_effect.cpp
Normal 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 ¤t_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
|
41
esphome/components/adalight/adalight_light_effect.h
Normal file
41
esphome/components/adalight/adalight_light_effect.h
Normal 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 ¤t_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
|
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ['@esphome/core']
|
|
@ -58,7 +58,7 @@ void ADCSensor::update() {
|
|||
}
|
||||
float ADCSensor::sample() {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
float value_v = analogRead(this->pin_) / 4095.0f;
|
||||
float value_v = analogRead(this->pin_) / 4095.0f; // NOLINT
|
||||
switch (this->attenuation_) {
|
||||
case ADC_0db:
|
||||
value_v *= 1.1;
|
||||
|
@ -80,7 +80,7 @@ float ADCSensor::sample() {
|
|||
#ifdef USE_ADC_SENSOR_VCC
|
||||
return ESP.getVcc() / 1024.0f;
|
||||
#else
|
||||
return analogRead(this->pin_) / 1024.0f;
|
||||
return analogRead(this->pin_) / 1024.0f; // NOLINT
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ void ADE7953::dump_config() {
|
|||
}
|
||||
|
||||
#define ADE_PUBLISH_(name, factor) \
|
||||
if (name) { \
|
||||
if (name && this->name##_sensor_) { \
|
||||
float value = *name / factor; \
|
||||
this->name##_sensor_->publish_state(value); \
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ from esphome.core import coroutine_with_priority
|
|||
|
||||
DEPENDENCIES = ['network']
|
||||
AUTO_LOAD = ['async_tcp']
|
||||
CODEOWNERS = ['@OttoWinter']
|
||||
|
||||
api_ns = cg.esphome_ns.namespace('api')
|
||||
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
|
||||
|
|
|
@ -301,12 +301,17 @@ message ListEntitiesFanResponse {
|
|||
|
||||
bool supports_oscillation = 5;
|
||||
bool supports_speed = 6;
|
||||
bool supports_direction = 7;
|
||||
}
|
||||
enum FanSpeed {
|
||||
FAN_SPEED_LOW = 0;
|
||||
FAN_SPEED_MEDIUM = 1;
|
||||
FAN_SPEED_HIGH = 2;
|
||||
}
|
||||
enum FanDirection {
|
||||
FAN_DIRECTION_FORWARD = 0;
|
||||
FAN_DIRECTION_REVERSE = 1;
|
||||
}
|
||||
message FanStateResponse {
|
||||
option (id) = 23;
|
||||
option (source) = SOURCE_SERVER;
|
||||
|
@ -317,6 +322,7 @@ message FanStateResponse {
|
|||
bool state = 2;
|
||||
bool oscillating = 3;
|
||||
FanSpeed speed = 4;
|
||||
FanDirection direction = 5;
|
||||
}
|
||||
message FanCommandRequest {
|
||||
option (id) = 31;
|
||||
|
@ -331,6 +337,8 @@ message FanCommandRequest {
|
|||
FanSpeed speed = 5;
|
||||
bool has_oscillating = 6;
|
||||
bool oscillating = 7;
|
||||
bool has_direction = 8;
|
||||
FanDirection direction = 9;
|
||||
}
|
||||
|
||||
// ==================== LIGHT ====================
|
||||
|
|
|
@ -248,6 +248,8 @@ bool APIConnection::send_fan_state(fan::FanState *fan) {
|
|||
resp.oscillating = fan->oscillating;
|
||||
if (traits.supports_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);
|
||||
}
|
||||
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.supports_oscillation = traits.supports_oscillation();
|
||||
msg.supports_speed = traits.supports_speed();
|
||||
msg.supports_direction = traits.supports_direction();
|
||||
return this->send_list_entities_fan_response(msg);
|
||||
}
|
||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
|
@ -273,6 +276,8 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
|
|||
call.set_oscillating(msg.oscillating);
|
||||
if (msg.has_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();
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -52,6 +52,16 @@ template<> const char *proto_enum_to_string<enums::FanSpeed>(enums::FanSpeed val
|
|||
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) {
|
||||
switch (value) {
|
||||
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();
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->supports_direction = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -799,6 +813,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_string(4, this->unique_id);
|
||||
buffer.encode_bool(5, this->supports_oscillation);
|
||||
buffer.encode_bool(6, this->supports_speed);
|
||||
buffer.encode_bool(7, this->supports_direction);
|
||||
}
|
||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
|
@ -827,6 +842,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
|||
out.append(" supports_speed: ");
|
||||
out.append(YESNO(this->supports_speed));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_direction: ");
|
||||
out.append(YESNO(this->supports_direction));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
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>();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->direction = value.as_enum<enums::FanDirection>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -862,6 +885,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_bool(2, this->state);
|
||||
buffer.encode_bool(3, this->oscillating);
|
||||
buffer.encode_enum<enums::FanSpeed>(4, this->speed);
|
||||
buffer.encode_enum<enums::FanDirection>(5, this->direction);
|
||||
}
|
||||
void FanStateResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
|
@ -882,6 +906,10 @@ void FanStateResponse::dump_to(std::string &out) const {
|
|||
out.append(" speed: ");
|
||||
out.append(proto_enum_to_string<enums::FanSpeed>(this->speed));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" direction: ");
|
||||
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
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();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->has_direction = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->direction = value.as_enum<enums::FanDirection>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -932,6 +968,8 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_enum<enums::FanSpeed>(5, this->speed);
|
||||
buffer.encode_bool(6, this->has_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 {
|
||||
char buffer[64];
|
||||
|
@ -964,6 +1002,14 @@ void FanCommandRequest::dump_to(std::string &out) const {
|
|||
out.append(" oscillating: ");
|
||||
out.append(YESNO(this->oscillating));
|
||||
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("}");
|
||||
}
|
||||
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
|
|
|
@ -28,6 +28,10 @@ enum FanSpeed : uint32_t {
|
|||
FAN_SPEED_MEDIUM = 1,
|
||||
FAN_SPEED_HIGH = 2,
|
||||
};
|
||||
enum FanDirection : uint32_t {
|
||||
FAN_DIRECTION_FORWARD = 0,
|
||||
FAN_DIRECTION_REVERSE = 1,
|
||||
};
|
||||
enum LogLevel : uint32_t {
|
||||
LOG_LEVEL_NONE = 0,
|
||||
LOG_LEVEL_ERROR = 1,
|
||||
|
@ -279,6 +283,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
|||
std::string unique_id{}; // NOLINT
|
||||
bool supports_oscillation{false}; // NOLINT
|
||||
bool supports_speed{false}; // NOLINT
|
||||
bool supports_direction{false}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
|
@ -289,10 +294,11 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
|||
};
|
||||
class FanStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool oscillating{false}; // NOLINT
|
||||
enums::FanSpeed speed{}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool oscillating{false}; // NOLINT
|
||||
enums::FanSpeed speed{}; // NOLINT
|
||||
enums::FanDirection direction{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
|
@ -302,13 +308,15 @@ class FanStateResponse : public ProtoMessage {
|
|||
};
|
||||
class FanCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_state{false}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool has_speed{false}; // NOLINT
|
||||
enums::FanSpeed speed{}; // NOLINT
|
||||
bool has_oscillating{false}; // NOLINT
|
||||
bool oscillating{false}; // NOLINT
|
||||
uint32_t key{0}; // NOLINT
|
||||
bool has_state{false}; // NOLINT
|
||||
bool state{false}; // NOLINT
|
||||
bool has_speed{false}; // NOLINT
|
||||
enums::FanSpeed speed{}; // NOLINT
|
||||
bool has_oscillating{false}; // NOLINT
|
||||
bool oscillating{false}; // NOLINT
|
||||
bool has_direction{false}; // NOLINT
|
||||
enums::FanDirection direction{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||
template<typename T> void add_variable(std::string key, T value) {
|
||||
this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||
}
|
||||
|
||||
void play(Ts... x) override {
|
||||
HomeassistantServiceResponse resp;
|
||||
resp.service = this->service_.value(x...);
|
||||
|
|
|
@ -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_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_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),
|
||||
})
|
||||
|
||||
|
|
|
@ -26,6 +26,9 @@ void AS3935Component::setup() {
|
|||
void AS3935Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "AS3935:");
|
||||
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; }
|
||||
|
|
|
@ -27,4 +27,4 @@ def to_code(config):
|
|||
if CONF_LIGHTNING_ENERGY in config:
|
||||
conf = config[CONF_LIGHTNING_ENERGY]
|
||||
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))
|
||||
|
|
|
@ -10,8 +10,8 @@ as3935_spi_ns = cg.esphome_ns.namespace('as3935_spi')
|
|||
SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
|
||||
cv.GenerateID(): cv.declare_id(SPIAS3935)
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(spi.SPI_DEVICE_SCHEMA))
|
||||
cv.GenerateID(): cv.declare_id(SPIAS3935),
|
||||
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=True)))
|
||||
|
||||
|
||||
def to_code(config):
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
import esphome.codegen as cg
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ['@OttoWinter']
|
||||
|
||||
|
||||
@coroutine_with_priority(200.0)
|
||||
def to_code(config):
|
||||
|
@ -10,4 +12,4 @@ def to_code(config):
|
|||
cg.add_library('AsyncTCP-esphome', '1.1.1')
|
||||
elif CORE.is_esp8266:
|
||||
# https://github.com/OttoWinter/ESPAsyncTCP
|
||||
cg.add_library('ESPAsyncTCP-esphome', '1.2.2')
|
||||
cg.add_library('ESPAsyncTCP-esphome', '1.2.3')
|
||||
|
|
|
@ -55,7 +55,7 @@ CONFIG_SCHEMA = cv.Schema({
|
|||
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_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):
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ['@OttoWinter']
|
|
@ -108,7 +108,7 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
|
|||
}
|
||||
|
||||
if (this->prev_trigger_ != nullptr) {
|
||||
this->prev_trigger_->stop();
|
||||
this->prev_trigger_->stop_action();
|
||||
this->prev_trigger_ = nullptr;
|
||||
}
|
||||
Trigger<> *trig;
|
||||
|
|
|
@ -7,6 +7,8 @@ namespace bh1750 {
|
|||
static const char *TAG = "bh1750.sensor";
|
||||
|
||||
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() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BH1750 '%s'...", this->name_.c_str());
|
||||
|
@ -14,7 +16,13 @@ void BH1750Sensor::setup() {
|
|||
this->mark_failed();
|
||||
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() {
|
||||
LOG_SENSOR("", "BH1750", this);
|
||||
LOG_I2C_DEVICE(this);
|
||||
|
@ -59,6 +67,7 @@ void BH1750Sensor::update() {
|
|||
|
||||
this->set_timeout("illuminance", wait, [this]() { this->read_data_(); });
|
||||
}
|
||||
|
||||
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void BH1750Sensor::read_data_() {
|
||||
uint16_t raw_value;
|
||||
|
@ -68,10 +77,12 @@ void BH1750Sensor::read_data_() {
|
|||
}
|
||||
|
||||
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);
|
||||
this->publish_state(lx);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
void BH1750Sensor::set_resolution(BH1750Resolution resolution) { this->resolution_ = resolution; }
|
||||
|
||||
} // namespace bh1750
|
||||
|
|
|
@ -28,6 +28,7 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
|
|||
* @param resolution The new resolution of the sensor.
|
||||
*/
|
||||
void set_resolution(BH1750Resolution resolution);
|
||||
void set_measurement_time(uint8_t measurement_time) { measurement_time_ = measurement_time; }
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (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_();
|
||||
|
||||
BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX};
|
||||
uint8_t measurement_time_;
|
||||
};
|
||||
|
||||
} // namespace bh1750
|
||||
|
|
|
@ -15,9 +15,11 @@ BH1750_RESOLUTIONS = {
|
|||
|
||||
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({
|
||||
cv.GenerateID(): cv.declare_id(BH1750Sensor),
|
||||
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))
|
||||
|
||||
|
||||
|
@ -28,3 +30,4 @@ def to_code(config):
|
|||
yield i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
||||
cg.add(var.set_measurement_time(config[CONF_MEASUREMENT_TIME]))
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
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
|
||||
|
||||
BinaryFan = binary_ns.class_('BinaryFan', cg.Component)
|
||||
|
@ -9,6 +10,7 @@ BinaryFan = binary_ns.class_('BinaryFan', cg.Component)
|
|||
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan),
|
||||
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),
|
||||
}).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
@ -25,3 +27,7 @@ def to_code(config):
|
|||
if CONF_OSCILLATION_OUTPUT in config:
|
||||
oscillation_output = yield cg.get_variable(config[CONF_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))
|
||||
|
|
|
@ -11,9 +11,12 @@ void binary::BinaryFan::dump_config() {
|
|||
if (this->fan_->get_traits().supports_oscillation()) {
|
||||
ESP_LOGCONFIG(TAG, " Oscillation: YES");
|
||||
}
|
||||
if (this->fan_->get_traits().supports_direction()) {
|
||||
ESP_LOGCONFIG(TAG, " Direction: YES");
|
||||
}
|
||||
}
|
||||
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_->add_on_state_callback([this]() { this->next_update_ = true; });
|
||||
}
|
||||
|
@ -41,6 +44,16 @@ void BinaryFan::loop() {
|
|||
}
|
||||
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; }
|
||||
|
||||
|
|
|
@ -16,11 +16,13 @@ class BinaryFan : public Component {
|
|||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; }
|
||||
void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; }
|
||||
|
||||
protected:
|
||||
fan::FanState *fan_;
|
||||
output::BinaryOutput *output_;
|
||||
output::BinaryOutput *oscillating_{nullptr};
|
||||
output::BinaryOutput *direction_{nullptr};
|
||||
bool next_update_{true};
|
||||
};
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \
|
|||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||
from esphome.util import Registry
|
||||
|
||||
CODEOWNERS = ['@esphome/core']
|
||||
DEVICE_CLASSES = [
|
||||
'', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas',
|
||||
'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):
|
||||
cg.add(var.set_name(config[CONF_NAME]))
|
||||
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:
|
||||
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||
if CONF_INVERTED in config:
|
||||
|
|
|
@ -137,6 +137,7 @@ template<typename... Ts> class BinarySensorPublishAction : public Action<Ts...>
|
|||
public:
|
||||
explicit BinarySensorPublishAction(BinarySensor *sensor) : sensor_(sensor) {}
|
||||
TEMPLATABLE_VALUE(bool, state)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto val = this->state_.value(x...);
|
||||
this->sensor_->publish_state(val);
|
||||
|
|
|
@ -7,6 +7,7 @@ from esphome.core import coroutine_with_priority
|
|||
|
||||
AUTO_LOAD = ['web_server_base']
|
||||
DEPENDENCIES = ['wifi']
|
||||
CODEOWNERS = ['@OttoWinter']
|
||||
|
||||
captive_portal_ns = cg.esphome_ns.namespace('captive_portal')
|
||||
CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component)
|
||||
|
|
|
@ -2,7 +2,7 @@ import esphome.codegen as cg
|
|||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
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']
|
||||
|
||||
|
@ -15,7 +15,7 @@ CONF_BASELINE = 'baseline'
|
|||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
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),
|
||||
cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0),
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ from esphome.core import CORE, coroutine, coroutine_with_priority
|
|||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
CODEOWNERS = ['@esphome/core']
|
||||
climate_ns = cg.esphome_ns.namespace('climate')
|
||||
|
||||
Climate = climate_ns.class_('Climate', cg.Nameable)
|
||||
|
|
|
@ -6,6 +6,7 @@ from esphome.const import CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, CONF_SENSOR
|
|||
from esphome.core import coroutine
|
||||
|
||||
AUTO_LOAD = ['sensor', 'remote_base']
|
||||
CODEOWNERS = ['@glmnet']
|
||||
|
||||
climate_ir_ns = cg.esphome_ns.namespace('climate_ir')
|
||||
ClimateIR = climate_ir_ns.class_('ClimateIR', climate.Climate, cg.Component,
|
||||
|
|
0
esphome/components/climate_ir_lg/__init__.py
Normal file
0
esphome/components/climate_ir_lg/__init__.py
Normal file
18
esphome/components/climate_ir_lg/climate.py
Normal file
18
esphome/components/climate_ir_lg/climate.py
Normal 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)
|
204
esphome/components/climate_ir_lg/climate_ir_lg.cpp
Normal file
204
esphome/components/climate_ir_lg/climate_ir_lg.cpp
Normal 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
|
44
esphome/components/climate_ir_lg/climate_ir_lg.h
Normal file
44
esphome/components/climate_ir_lg/climate_ir_lg.h
Normal 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
|
23
esphome/components/color/__init__.py
Normal file
23
esphome/components/color/__init__.py
Normal 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])))
|
|
@ -4,6 +4,7 @@ from esphome.components import climate_ir
|
|||
from esphome.const import CONF_ID
|
||||
|
||||
AUTO_LOAD = ['climate_ir']
|
||||
CODEOWNERS = ['@glmnet']
|
||||
|
||||
coolix_ns = cg.esphome_ns.namespace('coolix')
|
||||
CoolixClimate = coolix_ns.class_('CoolixClimate', climate_ir.ClimateIR)
|
||||
|
|
|
@ -9,9 +9,10 @@ from esphome.core import CORE, coroutine, coroutine_with_priority
|
|||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
CODEOWNERS = ['@esphome/core']
|
||||
DEVICE_CLASSES = [
|
||||
'', 'awning', 'blind', 'curtain', 'damper', 'door', 'garage',
|
||||
'shade', 'shutter', 'window'
|
||||
'gate', 'shade', 'shutter', 'window'
|
||||
]
|
||||
|
||||
cover_ns = cg.esphome_ns.namespace('cover')
|
||||
|
|
|
@ -41,6 +41,10 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
|||
public:
|
||||
explicit ControlAction(Cover *cover) : cover_(cover) {}
|
||||
|
||||
TEMPLATABLE_VALUE(bool, stop)
|
||||
TEMPLATABLE_VALUE(float, position)
|
||||
TEMPLATABLE_VALUE(float, tilt)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto call = this->cover_->make_call();
|
||||
if (this->stop_.has_value())
|
||||
|
@ -52,10 +56,6 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
|||
call.perform();
|
||||
}
|
||||
|
||||
TEMPLATABLE_VALUE(bool, stop)
|
||||
TEMPLATABLE_VALUE(float, position)
|
||||
TEMPLATABLE_VALUE(float, tilt)
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
@ -63,6 +63,10 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
|||
template<typename... Ts> class CoverPublishAction : public Action<Ts...> {
|
||||
public:
|
||||
CoverPublishAction(Cover *cover) : cover_(cover) {}
|
||||
TEMPLATABLE_VALUE(float, position)
|
||||
TEMPLATABLE_VALUE(float, tilt)
|
||||
TEMPLATABLE_VALUE(CoverOperation, current_operation)
|
||||
|
||||
void play(Ts... x) override {
|
||||
if (this->position_.has_value())
|
||||
this->cover_->position = this->position_.value(x...);
|
||||
|
@ -73,10 +77,6 @@ template<typename... Ts> class CoverPublishAction : public Action<Ts...> {
|
|||
this->cover_->publish_state();
|
||||
}
|
||||
|
||||
TEMPLATABLE_VALUE(float, position)
|
||||
TEMPLATABLE_VALUE(float, tilt)
|
||||
TEMPLATABLE_VALUE(CoverOperation, current_operation)
|
||||
|
||||
protected:
|
||||
Cover *cover_;
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ from esphome.components import sensor, voltage_sampler
|
|||
from esphome.const import CONF_SENSOR, CONF_ID, ICON_FLASH, UNIT_AMPERE
|
||||
|
||||
AUTO_LOAD = ['voltage_sampler']
|
||||
CODEOWNERS = ['@jesserockz']
|
||||
|
||||
CONF_SAMPLE_DURATION = 'sample_duration'
|
||||
|
||||
|
|
|
@ -1,18 +1,12 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_ID, CONF_PIN, \
|
||||
CONF_RESOLUTION, CONF_UNIT_OF_MEASUREMENT, UNIT_CELSIUS, \
|
||||
CONF_ICON, ICON_THERMOMETER, CONF_ACCURACY_DECIMALS
|
||||
from esphome.const import CONF_ID, CONF_PIN
|
||||
|
||||
MULTI_CONF = True
|
||||
AUTO_LOAD = ['sensor']
|
||||
|
||||
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')
|
||||
DallasComponent = dallas_ns.class_('DallasComponent', cg.PollingComponent)
|
||||
ESPOneWire = dallas_ns.class_('ESPOneWire')
|
||||
|
@ -21,12 +15,6 @@ CONFIG_SCHEMA = cv.Schema({
|
|||
cv.GenerateID(): cv.declare_id(DallasComponent),
|
||||
cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
|
||||
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'))
|
||||
|
||||
|
||||
|
@ -34,16 +22,4 @@ def to_code(config):
|
|||
pin = yield cg.gpio_pin_expression(config[CONF_PIN])
|
||||
one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin)
|
||||
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)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#include "dallas_component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dallas {
|
||||
|
@ -53,29 +52,6 @@ void DallasComponent::setup() {
|
|||
continue;
|
||||
}
|
||||
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_) {
|
||||
|
@ -180,25 +156,12 @@ void DallasComponent::update() {
|
|||
}
|
||||
}
|
||||
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)
|
||||
: parent_(parent) {
|
||||
this->set_address(address);
|
||||
this->set_resolution(resolution);
|
||||
}
|
||||
const uint64_t &DallasTemperatureSensor::get_address() const { return this->address_; }
|
||||
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
|
||||
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
|
||||
void DallasTemperatureSensor::set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
|
||||
|
|
|
@ -22,30 +22,12 @@ class DallasComponent : public PollingComponent {
|
|||
|
||||
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:
|
||||
friend DallasTemperatureSensor;
|
||||
|
||||
ESPOneWire *one_wire_;
|
||||
std::vector<DallasTemperatureSensor *> 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.
|
||||
|
@ -58,8 +40,6 @@ class DallasTemperatureSensor : public sensor::Sensor {
|
|||
/// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
|
||||
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.
|
||||
void set_address(uint64_t address);
|
||||
/// Get the index of this sensor. (0 if using address.)
|
||||
|
|
|
@ -30,7 +30,7 @@ bool HOT ICACHE_RAM_ATTR ESPOneWire::reset() {
|
|||
|
||||
// Switch into RX mode, letting the pin float
|
||||
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
|
||||
delayMicroseconds(70);
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import esphome.config_validation as cv
|
|||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ['@OttoWinter']
|
||||
DEPENDENCIES = ['logger']
|
||||
|
||||
debug_ns = cg.esphome_ns.namespace('debug')
|
||||
|
|
|
@ -5,6 +5,7 @@ from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE
|
|||
from esphome.components import uart
|
||||
|
||||
DEPENDENCIES = ['uart']
|
||||
CODEOWNERS = ['@glmnet']
|
||||
|
||||
dfplayer_ns = cg.esphome_ns.namespace('dfplayer')
|
||||
DFPlayer = dfplayer_ns.class_('DFPlayer', cg.Component)
|
||||
|
|
|
@ -104,7 +104,6 @@ class DFPlayer : public uart::UARTDevice, public Component {
|
|||
|
||||
#define DFPLAYER_SIMPLE_ACTION(ACTION_CLASS, ACTION_METHOD) \
|
||||
template<typename... Ts> class ACTION_CLASS : public Action<Ts...>, public Parented<DFPlayer> { \
|
||||
public: \
|
||||
void play(Ts... x) override { this->parent_->ACTION_METHOD(); } \
|
||||
};
|
||||
|
||||
|
@ -115,6 +114,7 @@ template<typename... Ts> class PlayFileAction : public Action<Ts...>, public Par
|
|||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, file)
|
||||
TEMPLATABLE_VALUE(boolean, loop)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto file = this->file_.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, file)
|
||||
TEMPLATABLE_VALUE(boolean, loop)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto folder = this->folder_.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> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(Device, device)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto device = this->device_.value(x...);
|
||||
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> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, volume)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto volume = this->volume_.value(x...);
|
||||
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> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(EqPreset, eq)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto eq = this->eq_.value(x...);
|
||||
this->parent_->set_eq(eq);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ['@OttoWinter']
|
|
@ -1,4 +1,5 @@
|
|||
#include "display_buffer.h"
|
||||
#include "esphome/core/color.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
|
@ -7,8 +8,8 @@ namespace display {
|
|||
|
||||
static const char *TAG = "display";
|
||||
|
||||
const uint8_t COLOR_OFF = 0;
|
||||
const uint8_t COLOR_ON = 1;
|
||||
const Color COLOR_OFF(0, 0, 0, 0);
|
||||
const Color COLOR_ON(1, 1, 1, 1);
|
||||
|
||||
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
||||
this->buffer_ = new uint8_t[buffer_length];
|
||||
|
@ -18,7 +19,7 @@ void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
|||
}
|
||||
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); }
|
||||
int DisplayBuffer::get_width() {
|
||||
switch (this->rotation_) {
|
||||
|
@ -43,7 +44,7 @@ int DisplayBuffer::get_height() {
|
|||
}
|
||||
}
|
||||
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_) {
|
||||
case DISPLAY_ROTATION_0_DEGREES:
|
||||
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);
|
||||
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 dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
|
||||
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.
|
||||
for (int i = x; i < x + width; i++)
|
||||
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.
|
||||
for (int i = y; i < y + height; i++)
|
||||
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 + height - 1, width, color);
|
||||
this->vertical_line(x1, 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.
|
||||
for (int i = y1; i < y1 + height; i++) {
|
||||
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 dy = 0;
|
||||
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);
|
||||
}
|
||||
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 dy = 0;
|
||||
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);
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
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];
|
||||
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
|
||||
if (ret > 0)
|
||||
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++) {
|
||||
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);
|
||||
}
|
||||
|
||||
void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color color_off) {
|
||||
switch (image->get_type()) {
|
||||
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,
|
||||
int *width, int *height) {
|
||||
int x_offset, baseline;
|
||||
|
@ -248,7 +269,7 @@ void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font,
|
|||
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);
|
||||
}
|
||||
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) {
|
||||
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_start(arg, format);
|
||||
this->vprintf_(x, y, font, color, align, format, 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_start(arg, format);
|
||||
this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
|
||||
|
@ -306,14 +327,14 @@ void DisplayBuffer::do_update_() {
|
|||
}
|
||||
}
|
||||
#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) {
|
||||
char buffer[64];
|
||||
size_t ret = time.strftime(buffer, sizeof(buffer), format);
|
||||
if (ret > 0)
|
||||
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);
|
||||
}
|
||||
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;
|
||||
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_height() const { return this->height_; }
|
||||
Image::Image(const uint8_t *data_start, int width, int height)
|
||||
: width_(width), height_(height), data_start_(data_start) {}
|
||||
ImageType Image::get_type() const { return this->type_; }
|
||||
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) {}
|
||||
void DisplayPage::show() { this->parent_->show_page(this); }
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/color.h"
|
||||
|
||||
#ifdef USE_TIME
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
|
@ -63,9 +64,11 @@ enum class TextAlign {
|
|||
};
|
||||
|
||||
/// Turn the pixel OFF.
|
||||
extern const uint8_t COLOR_OFF;
|
||||
extern const Color COLOR_OFF;
|
||||
/// 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 {
|
||||
DISPLAY_ROTATION_0_DEGREES = 0,
|
||||
|
@ -91,7 +94,7 @@ using display_writer_t = std::function<void(DisplayBuffer &)>;
|
|||
class DisplayBuffer {
|
||||
public:
|
||||
/// 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.
|
||||
void clear();
|
||||
|
||||
|
@ -100,29 +103,29 @@ class DisplayBuffer {
|
|||
/// Get the height of the image in pixels with rotation applied.
|
||||
int get_height();
|
||||
/// 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.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
/// [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].
|
||||
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.
|
||||
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.
|
||||
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`.
|
||||
*
|
||||
|
@ -133,7 +136,7 @@ class DisplayBuffer {
|
|||
* @param align The alignment of the text.
|
||||
* @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`.
|
||||
*
|
||||
|
@ -143,7 +146,7 @@ class DisplayBuffer {
|
|||
* @param color The color to draw the text with.
|
||||
* @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`.
|
||||
*
|
||||
|
@ -174,7 +177,7 @@ class DisplayBuffer {
|
|||
* @param format The format to use.
|
||||
* @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)));
|
||||
|
||||
/** 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 ... 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`.
|
||||
*
|
||||
|
@ -220,7 +223,7 @@ class DisplayBuffer {
|
|||
* @param format The strftime format to use.
|
||||
* @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)));
|
||||
|
||||
/** 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 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)));
|
||||
|
||||
/** 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)));
|
||||
#endif
|
||||
|
||||
/// Draw the `image` with the top-left corner at [x,y] to the screen.
|
||||
void image(int x, int y, Image *image);
|
||||
/** Draw the `image` with the top-left corner at [x,y] to the screen.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
|
@ -290,9 +300,9 @@ class DisplayBuffer {
|
|||
void set_rotation(DisplayRotation rotation);
|
||||
|
||||
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;
|
||||
|
||||
|
@ -377,20 +387,25 @@ class Font {
|
|||
|
||||
class Image {
|
||||
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;
|
||||
Color get_color_pixel(int x, int y) const;
|
||||
Color get_grayscale_pixel(int x, int y) const;
|
||||
int get_width() const;
|
||||
int get_height() const;
|
||||
ImageType get_type() const;
|
||||
|
||||
protected:
|
||||
int width_;
|
||||
int height_;
|
||||
ImageType type_;
|
||||
const uint8_t *data_start_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(DisplayPage *, page)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto *page = this->page_.value(x...);
|
||||
if (page != nullptr) {
|
||||
|
@ -402,18 +417,18 @@ template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
|
|||
template<typename... Ts> class DisplayPageShowNextAction : public Action<Ts...> {
|
||||
public:
|
||||
DisplayPageShowNextAction(DisplayBuffer *buffer) : buffer_(buffer) {}
|
||||
|
||||
void play(Ts... x) override { this->buffer_->show_next_page(); }
|
||||
|
||||
protected:
|
||||
DisplayBuffer *buffer_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisplayPageShowPrevAction : public Action<Ts...> {
|
||||
public:
|
||||
DisplayPageShowPrevAction(DisplayBuffer *buffer) : buffer_(buffer) {}
|
||||
|
||||
void play(Ts... x) override { this->buffer_->show_prev_page(); }
|
||||
|
||||
protected:
|
||||
DisplayBuffer *buffer_;
|
||||
};
|
||||
|
||||
|
|
49
esphome/components/e131/__init__.py
Normal file
49
esphome/components/e131/__init__.py
Normal 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
|
106
esphome/components/e131/e131.cpp
Normal file
106
esphome/components/e131/e131.cpp
Normal 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
|
57
esphome/components/e131/e131.h
Normal file
57
esphome/components/e131/e131.h
Normal 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
|
90
esphome/components/e131/e131_addressable_light_effect.cpp
Normal file
90
esphome/components/e131/e131_addressable_light_effect.cpp
Normal 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 ¤t_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
|
48
esphome/components/e131/e131_addressable_light_effect.h
Normal file
48
esphome/components/e131/e131_addressable_light_effect.h
Normal 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 ¤t_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
|
136
esphome/components/e131/e131_packet.cpp
Normal file
136
esphome/components/e131/e131_packet.cpp
Normal 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
|
|
@ -94,7 +94,7 @@ void EndstopCover::dump_config() {
|
|||
float EndstopCover::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void EndstopCover::stop_prev_trigger_() {
|
||||
if (this->prev_command_trigger_ != nullptr) {
|
||||
this->prev_command_trigger_->stop();
|
||||
this->prev_command_trigger_->stop_action();
|
||||
this->prev_command_trigger_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,14 +99,14 @@ bool ESP32BLETracker::ble_setup() {
|
|||
return false;
|
||||
}
|
||||
|
||||
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||||
|
||||
// Initialize the bluetooth controller with the default configuration
|
||||
if (!btStart()) {
|
||||
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||||
|
||||
err = esp_bluedroid_init();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err);
|
||||
|
|
|
@ -28,6 +28,7 @@ class ESPBTUUID {
|
|||
bool contains(uint8_t data1, uint8_t data2) const;
|
||||
|
||||
bool operator==(const ESPBTUUID &uuid) const;
|
||||
bool operator!=(const ESPBTUUID &uuid) const { return !(*this == uuid); }
|
||||
|
||||
esp_bt_uuid_t get_uuid();
|
||||
|
||||
|
@ -74,6 +75,8 @@ class ESPBTDevice {
|
|||
|
||||
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_; }
|
||||
int get_rssi() const { return rssi_; }
|
||||
const std::string &get_name() const { return this->name_; }
|
||||
|
|
|
@ -13,7 +13,7 @@ class ESP8266PWM : public output::FloatOutput, public Component {
|
|||
void set_pin(GPIOPin *pin) { pin_ = pin; }
|
||||
void set_frequency(float frequency) { this->frequency_ = frequency; }
|
||||
/// Dynamically update frequency
|
||||
void update_frequency(float frequency) {
|
||||
void update_frequency(float frequency) override {
|
||||
this->set_frequency(frequency);
|
||||
this->write_state(this->last_output_);
|
||||
}
|
||||
|
@ -43,7 +43,6 @@ template<typename... Ts> class SetFrequencyAction : public Action<Ts...> {
|
|||
this->parent_->update_frequency(freq);
|
||||
}
|
||||
|
||||
protected:
|
||||
ESP8266PWM *parent_;
|
||||
};
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ void EthernetComponent::setup() {
|
|||
}
|
||||
void EthernetComponent::loop() {
|
||||
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...");
|
||||
this->start_connect_();
|
||||
return;
|
||||
|
|
29
esphome/components/exposure_notifications/__init__.py
Normal file
29
esphome/components/exposure_notifications/__init__.py
Normal 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)
|
|
@ -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(¬ification.address[0], device.address(), 6);
|
||||
memcpy(¬ification.rolling_proximity_identifier[0], &data[0], 16);
|
||||
memcpy(¬ification.associated_encrypted_metadata[0], &data[16], 4);
|
||||
notification.rssi = device.get_rssi();
|
||||
this->trigger(notification);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace exposure_notifications
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
|
@ -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
|
|
@ -34,6 +34,10 @@ FAN_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
|
|||
cv.publish_topic),
|
||||
cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(cv.requires_component('mqtt'),
|
||||
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),
|
||||
})
|
||||
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ template<typename... Ts> class TurnOnAction : public Action<Ts...> {
|
|||
call.perform();
|
||||
}
|
||||
|
||||
protected:
|
||||
FanState *state_;
|
||||
};
|
||||
|
||||
|
@ -35,7 +34,6 @@ template<typename... Ts> class TurnOffAction : public Action<Ts...> {
|
|||
|
||||
void play(Ts... x) override { this->state_->turn_off().perform(); }
|
||||
|
||||
protected:
|
||||
FanState *state_;
|
||||
};
|
||||
|
||||
|
@ -45,7 +43,6 @@ template<typename... Ts> class ToggleAction : public Action<Ts...> {
|
|||
|
||||
void play(Ts... x) override { this->state_->toggle().perform(); }
|
||||
|
||||
protected:
|
||||
FanState *state_;
|
||||
};
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ struct FanStateRTCState {
|
|||
bool state;
|
||||
FanSpeed speed;
|
||||
bool oscillating;
|
||||
FanDirection direction;
|
||||
};
|
||||
|
||||
void FanState::setup() {
|
||||
|
@ -34,6 +35,7 @@ void FanState::setup() {
|
|||
call.set_state(recovered.state);
|
||||
call.set_speed(recovered.speed);
|
||||
call.set_oscillating(recovered.oscillating);
|
||||
call.set_direction(recovered.direction);
|
||||
call.perform();
|
||||
}
|
||||
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()) {
|
||||
this->state_->oscillating = *this->oscillating_;
|
||||
}
|
||||
if (this->direction_.has_value()) {
|
||||
this->state_->direction = *this->direction_;
|
||||
}
|
||||
if (this->speed_.has_value()) {
|
||||
switch (*this->speed_) {
|
||||
case FAN_SPEED_LOW:
|
||||
|
@ -63,6 +68,7 @@ void FanStateCall::perform() const {
|
|||
saved.state = this->state_->state;
|
||||
saved.speed = this->state_->speed;
|
||||
saved.oscillating = this->state_->oscillating;
|
||||
saved.direction = this->state_->direction;
|
||||
this->state_->rtc_.save(&saved);
|
||||
|
||||
this->state_->state_callback_.call();
|
||||
|
|
|
@ -15,6 +15,9 @@ enum FanSpeed {
|
|||
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 FanStateCall {
|
||||
|
@ -46,6 +49,14 @@ class FanStateCall {
|
|||
return *this;
|
||||
}
|
||||
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;
|
||||
|
||||
|
@ -54,6 +65,7 @@ class FanStateCall {
|
|||
optional<bool> binary_state_;
|
||||
optional<bool> oscillating_{};
|
||||
optional<FanSpeed> speed_{};
|
||||
optional<FanDirection> direction_{};
|
||||
};
|
||||
|
||||
class FanState : public Nameable, public Component {
|
||||
|
@ -76,6 +88,8 @@ class FanState : public Nameable, public Component {
|
|||
bool oscillating{false};
|
||||
/// The current fan speed.
|
||||
FanSpeed speed{FAN_SPEED_HIGH};
|
||||
/// The current direction of the fan
|
||||
FanDirection direction{FAN_DIRECTION_FORWARD};
|
||||
|
||||
FanStateCall turn_on();
|
||||
FanStateCall turn_off();
|
||||
|
|
|
@ -6,7 +6,8 @@ namespace fan {
|
|||
class FanTraits {
|
||||
public:
|
||||
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.
|
||||
bool supports_oscillation() const { return this->oscillation_; }
|
||||
|
@ -16,10 +17,15 @@ class FanTraits {
|
|||
bool supports_speed() const { return this->speed_; }
|
||||
/// Set whether this fan supports speed modes.
|
||||
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:
|
||||
bool oscillation_{false};
|
||||
bool speed_{false};
|
||||
bool direction_{false};
|
||||
};
|
||||
|
||||
} // namespace fan
|
||||
|
|
|
@ -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.core import coroutine
|
||||
|
||||
CODEOWNERS = ['@OttoWinter']
|
||||
fastled_base_ns = cg.esphome_ns.namespace('fastled_base')
|
||||
FastLEDLightOutput = fastled_base_ns.class_('FastLEDLightOutput', light.AddressableLight)
|
||||
|
||||
|
@ -35,5 +36,7 @@ def new_fastled_light(config):
|
|||
|
||||
yield light.register_light(var, config)
|
||||
# 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
|
||||
|
|
|
@ -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.core import coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ['@esphome/core']
|
||||
globals_ns = cg.esphome_ns.namespace('globals')
|
||||
GlobalsComponent = globals_ns.class_('GlobalsComponent', cg.Component)
|
||||
GlobalVarSetAction = globals_ns.class_('GlobalVarSetAction', automation.Action)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ['@esphome/core']
|
||||
gpio_ns = cg.esphome_ns.namespace('gpio')
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue