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
|
.esphome
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
|
cov.xml
|
||||||
*.cover
|
*.cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
.pytest_cache/
|
.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 LICENSE
|
||||||
include README.md
|
include README.md
|
||||||
|
include requirements.txt
|
||||||
include esphome/dashboard/templates/*.html
|
include esphome/dashboard/templates/*.html
|
||||||
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
|
recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE
|
||||||
recursive-include esphome *.cpp *.h *.tcc
|
recursive-include esphome *.cpp *.h *.tcc
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
ARG BUILD_FROM=esphome/esphome-base-amd64:2.0.1
|
ARG BUILD_FROM=esphome/esphome-base-amd64:2.6.0
|
||||||
FROM ${BUILD_FROM}
|
FROM ${BUILD_FROM}
|
||||||
|
|
||||||
|
# First install requirements to leverage caching when requirements don't change
|
||||||
|
COPY requirements.txt /
|
||||||
|
RUN pip3 install --no-cache-dir -r /requirements.txt
|
||||||
|
|
||||||
|
# Then copy esphome and install
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN pip3 install --no-cache-dir -e .
|
RUN pip3 install --no-cache-dir -e .
|
||||||
|
|
||||||
ENV USERNAME=""
|
# Settings for dashboard
|
||||||
ENV PASSWORD=""
|
ENV USERNAME="" PASSWORD=""
|
||||||
|
|
||||||
|
# The directory the user should mount their configuration files to
|
||||||
WORKDIR /config
|
WORKDIR /config
|
||||||
|
# Set entrypoint to esphome so that the user doesn't have to type 'esphome'
|
||||||
|
# in every docker command twice
|
||||||
ENTRYPOINT ["esphome"]
|
ENTRYPOINT ["esphome"]
|
||||||
|
# When no arguments given, start the dashboard in the workdir
|
||||||
CMD ["/config", "dashboard"]
|
CMD ["/config", "dashboard"]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM esphome/esphome-base-amd64:2.0.1
|
FROM esphome/esphome-base-amd64:2.6.0
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
ARG BUILD_FROM
|
ARG BUILD_FROM
|
||||||
FROM ${BUILD_FROM}
|
FROM ${BUILD_FROM}
|
||||||
|
|
||||||
|
# First install requirements to leverage caching when requirements don't change
|
||||||
|
COPY requirements.txt /
|
||||||
|
RUN pip3 install --no-cache-dir -r /requirements.txt
|
||||||
|
|
||||||
# Copy root filesystem
|
# Copy root filesystem
|
||||||
COPY docker/rootfs/ /
|
COPY docker/rootfs/ /
|
||||||
COPY setup.py setup.cfg MANIFEST.in /opt/esphome/
|
|
||||||
COPY esphome /opt/esphome/esphome
|
|
||||||
|
|
||||||
|
# Then copy esphome and install
|
||||||
|
COPY . /opt/esphome/
|
||||||
RUN pip3 install --no-cache-dir -e /opt/esphome
|
RUN pip3 install --no-cache-dir -e /opt/esphome
|
||||||
|
|
||||||
# Build arguments
|
# Build arguments
|
||||||
|
|
|
@ -1,18 +1,7 @@
|
||||||
FROM esphome/esphome-base-amd64:2.0.1
|
FROM esphome/esphome-lint-base:2.6.0
|
||||||
|
|
||||||
RUN \
|
COPY requirements.txt requirements_test.txt /
|
||||||
apt-get update \
|
RUN pip3 install --no-cache-dir -r /requirements.txt -r /requirements_test.txt
|
||||||
&& apt-get install -y --no-install-recommends \
|
|
||||||
clang-format-7 \
|
|
||||||
clang-tidy-7 \
|
|
||||||
patch \
|
|
||||||
&& rm -rf \
|
|
||||||
/tmp/* \
|
|
||||||
/var/{cache,log}/* \
|
|
||||||
/var/lib/apt/lists/*
|
|
||||||
|
|
||||||
COPY requirements_test.txt /requirements_test.txt
|
|
||||||
RUN pip3 install --no-cache-dir wheel && pip3 install --no-cache-dir -r /requirements_test.txt
|
|
||||||
|
|
||||||
VOLUME ["/esphome"]
|
VOLUME ["/esphome"]
|
||||||
WORKDIR /esphome
|
WORKDIR /esphome
|
||||||
|
|
0
docker/rootfs/etc/cont-init.d/30-esphome.sh
Normal file → Executable file
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
|
CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS
|
||||||
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
|
||||||
from esphome.helpers import color, indent
|
from esphome.helpers import color, indent
|
||||||
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files
|
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files, \
|
||||||
|
get_serial_ports
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_serial_ports():
|
|
||||||
# from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py
|
|
||||||
from serial.tools.list_ports import comports
|
|
||||||
result = []
|
|
||||||
for port, desc, info in comports(include_links=True):
|
|
||||||
if not port:
|
|
||||||
continue
|
|
||||||
if "VID:PID" in info:
|
|
||||||
result.append((port, desc))
|
|
||||||
result.sort(key=lambda x: x[0])
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def choose_prompt(options):
|
def choose_prompt(options):
|
||||||
if not options:
|
if not options:
|
||||||
raise EsphomeError("Found no valid options for upload/logging, please make sure relevant "
|
raise EsphomeError("Found no valid options for upload/logging, please make sure relevant "
|
||||||
"sections (ota, mqtt, ...) are in your configuration and/or the device "
|
"sections (ota, api, mqtt, ...) are in your configuration and/or the "
|
||||||
"is plugged in.")
|
"device is plugged in.")
|
||||||
|
|
||||||
if len(options) == 1:
|
if len(options) == 1:
|
||||||
return options[0][1]
|
return options[0][1]
|
||||||
|
@ -60,8 +48,8 @@ def choose_prompt(options):
|
||||||
|
|
||||||
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
|
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
|
||||||
options = []
|
options = []
|
||||||
for res, desc in get_serial_ports():
|
for port in get_serial_ports():
|
||||||
options.append((f"{res} ({desc})", res))
|
options.append((f"{port.path} ({port.description})", port.path))
|
||||||
if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config):
|
if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config):
|
||||||
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||||
if default == 'OTA':
|
if default == 'OTA':
|
||||||
|
@ -131,6 +119,11 @@ def wrap_to_code(name, comp):
|
||||||
|
|
||||||
|
|
||||||
def write_cpp(config):
|
def write_cpp(config):
|
||||||
|
generate_cpp_contents(config)
|
||||||
|
return write_cpp_file()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_cpp_contents(config):
|
||||||
_LOGGER.info("Generating C++ source...")
|
_LOGGER.info("Generating C++ source...")
|
||||||
|
|
||||||
for name, component, conf in iter_components(CORE.config):
|
for name, component, conf in iter_components(CORE.config):
|
||||||
|
@ -140,6 +133,8 @@ def write_cpp(config):
|
||||||
|
|
||||||
CORE.flush_tasks()
|
CORE.flush_tasks()
|
||||||
|
|
||||||
|
|
||||||
|
def write_cpp_file():
|
||||||
writer.write_platformio_project()
|
writer.write_platformio_project()
|
||||||
|
|
||||||
code_s = indent(CORE.cpp_main_section)
|
code_s = indent(CORE.cpp_main_section)
|
||||||
|
@ -428,6 +423,8 @@ def parse_args(argv):
|
||||||
parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
|
parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
|
||||||
action='store_true')
|
action='store_true')
|
||||||
parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
|
parser.add_argument('--dashboard', help=argparse.SUPPRESS, action='store_true')
|
||||||
|
parser.add_argument('-s', '--substitution', nargs=2, action='append',
|
||||||
|
help='Add a substitution', metavar=('key', 'value'))
|
||||||
parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*')
|
parser.add_argument('configuration', help='Your YAML configuration file.', nargs='*')
|
||||||
|
|
||||||
subparsers = parser.add_subparsers(help='Commands', dest='command')
|
subparsers = parser.add_subparsers(help='Commands', dest='command')
|
||||||
|
@ -532,7 +529,7 @@ def run_esphome(argv):
|
||||||
CORE.config_path = conf_path
|
CORE.config_path = conf_path
|
||||||
CORE.dashboard = args.dashboard
|
CORE.dashboard = args.dashboard
|
||||||
|
|
||||||
config = read_config()
|
config = read_config(dict(args.substitution) if args.substitution else {})
|
||||||
if config is None:
|
if config is None:
|
||||||
return 1
|
return 1
|
||||||
CORE.config = config
|
CORE.config = config
|
||||||
|
|
|
@ -4,6 +4,8 @@ from esphome import pins
|
||||||
from esphome.components import output
|
from esphome.components import output
|
||||||
from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD
|
from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD
|
||||||
|
|
||||||
|
CODEOWNERS = ['@glmnet']
|
||||||
|
|
||||||
ac_dimmer_ns = cg.esphome_ns.namespace('ac_dimmer')
|
ac_dimmer_ns = cg.esphome_ns.namespace('ac_dimmer')
|
||||||
AcDimmer = ac_dimmer_ns.class_('AcDimmer', output.FloatOutput, cg.Component)
|
AcDimmer = ac_dimmer_ns.class_('AcDimmer', output.FloatOutput, cg.Component)
|
||||||
|
|
||||||
|
|
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() {
|
float ADCSensor::sample() {
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
float value_v = analogRead(this->pin_) / 4095.0f;
|
float value_v = analogRead(this->pin_) / 4095.0f; // NOLINT
|
||||||
switch (this->attenuation_) {
|
switch (this->attenuation_) {
|
||||||
case ADC_0db:
|
case ADC_0db:
|
||||||
value_v *= 1.1;
|
value_v *= 1.1;
|
||||||
|
@ -80,7 +80,7 @@ float ADCSensor::sample() {
|
||||||
#ifdef USE_ADC_SENSOR_VCC
|
#ifdef USE_ADC_SENSOR_VCC
|
||||||
return ESP.getVcc() / 1024.0f;
|
return ESP.getVcc() / 1024.0f;
|
||||||
#else
|
#else
|
||||||
return analogRead(this->pin_) / 1024.0f;
|
return analogRead(this->pin_) / 1024.0f; // NOLINT
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ void ADE7953::dump_config() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#define ADE_PUBLISH_(name, factor) \
|
#define ADE_PUBLISH_(name, factor) \
|
||||||
if (name) { \
|
if (name && this->name##_sensor_) { \
|
||||||
float value = *name / factor; \
|
float value = *name / factor; \
|
||||||
this->name##_sensor_->publish_state(value); \
|
this->name##_sensor_->publish_state(value); \
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
DEPENDENCIES = ['network']
|
DEPENDENCIES = ['network']
|
||||||
AUTO_LOAD = ['async_tcp']
|
AUTO_LOAD = ['async_tcp']
|
||||||
|
CODEOWNERS = ['@OttoWinter']
|
||||||
|
|
||||||
api_ns = cg.esphome_ns.namespace('api')
|
api_ns = cg.esphome_ns.namespace('api')
|
||||||
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
|
APIServer = api_ns.class_('APIServer', cg.Component, cg.Controller)
|
||||||
|
|
|
@ -301,12 +301,17 @@ message ListEntitiesFanResponse {
|
||||||
|
|
||||||
bool supports_oscillation = 5;
|
bool supports_oscillation = 5;
|
||||||
bool supports_speed = 6;
|
bool supports_speed = 6;
|
||||||
|
bool supports_direction = 7;
|
||||||
}
|
}
|
||||||
enum FanSpeed {
|
enum FanSpeed {
|
||||||
FAN_SPEED_LOW = 0;
|
FAN_SPEED_LOW = 0;
|
||||||
FAN_SPEED_MEDIUM = 1;
|
FAN_SPEED_MEDIUM = 1;
|
||||||
FAN_SPEED_HIGH = 2;
|
FAN_SPEED_HIGH = 2;
|
||||||
}
|
}
|
||||||
|
enum FanDirection {
|
||||||
|
FAN_DIRECTION_FORWARD = 0;
|
||||||
|
FAN_DIRECTION_REVERSE = 1;
|
||||||
|
}
|
||||||
message FanStateResponse {
|
message FanStateResponse {
|
||||||
option (id) = 23;
|
option (id) = 23;
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
|
@ -317,6 +322,7 @@ message FanStateResponse {
|
||||||
bool state = 2;
|
bool state = 2;
|
||||||
bool oscillating = 3;
|
bool oscillating = 3;
|
||||||
FanSpeed speed = 4;
|
FanSpeed speed = 4;
|
||||||
|
FanDirection direction = 5;
|
||||||
}
|
}
|
||||||
message FanCommandRequest {
|
message FanCommandRequest {
|
||||||
option (id) = 31;
|
option (id) = 31;
|
||||||
|
@ -331,6 +337,8 @@ message FanCommandRequest {
|
||||||
FanSpeed speed = 5;
|
FanSpeed speed = 5;
|
||||||
bool has_oscillating = 6;
|
bool has_oscillating = 6;
|
||||||
bool oscillating = 7;
|
bool oscillating = 7;
|
||||||
|
bool has_direction = 8;
|
||||||
|
FanDirection direction = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== LIGHT ====================
|
// ==================== LIGHT ====================
|
||||||
|
|
|
@ -248,6 +248,8 @@ bool APIConnection::send_fan_state(fan::FanState *fan) {
|
||||||
resp.oscillating = fan->oscillating;
|
resp.oscillating = fan->oscillating;
|
||||||
if (traits.supports_speed())
|
if (traits.supports_speed())
|
||||||
resp.speed = static_cast<enums::FanSpeed>(fan->speed);
|
resp.speed = static_cast<enums::FanSpeed>(fan->speed);
|
||||||
|
if (traits.supports_direction())
|
||||||
|
resp.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||||
return this->send_fan_state_response(resp);
|
return this->send_fan_state_response(resp);
|
||||||
}
|
}
|
||||||
bool APIConnection::send_fan_info(fan::FanState *fan) {
|
bool APIConnection::send_fan_info(fan::FanState *fan) {
|
||||||
|
@ -259,6 +261,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
|
||||||
msg.unique_id = get_default_unique_id("fan", fan);
|
msg.unique_id = get_default_unique_id("fan", fan);
|
||||||
msg.supports_oscillation = traits.supports_oscillation();
|
msg.supports_oscillation = traits.supports_oscillation();
|
||||||
msg.supports_speed = traits.supports_speed();
|
msg.supports_speed = traits.supports_speed();
|
||||||
|
msg.supports_direction = traits.supports_direction();
|
||||||
return this->send_list_entities_fan_response(msg);
|
return this->send_list_entities_fan_response(msg);
|
||||||
}
|
}
|
||||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||||
|
@ -273,6 +276,8 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||||
call.set_oscillating(msg.oscillating);
|
call.set_oscillating(msg.oscillating);
|
||||||
if (msg.has_speed)
|
if (msg.has_speed)
|
||||||
call.set_speed(static_cast<fan::FanSpeed>(msg.speed));
|
call.set_speed(static_cast<fan::FanSpeed>(msg.speed));
|
||||||
|
if (msg.has_direction)
|
||||||
|
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -52,6 +52,16 @@ template<> const char *proto_enum_to_string<enums::FanSpeed>(enums::FanSpeed val
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirection value) {
|
||||||
|
switch (value) {
|
||||||
|
case enums::FAN_DIRECTION_FORWARD:
|
||||||
|
return "FAN_DIRECTION_FORWARD";
|
||||||
|
case enums::FAN_DIRECTION_REVERSE:
|
||||||
|
return "FAN_DIRECTION_REVERSE";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
|
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case enums::LOG_LEVEL_NONE:
|
case enums::LOG_LEVEL_NONE:
|
||||||
|
@ -760,6 +770,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
|
||||||
this->supports_speed = value.as_bool();
|
this->supports_speed = value.as_bool();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 7: {
|
||||||
|
this->supports_direction = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -799,6 +813,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_string(4, this->unique_id);
|
buffer.encode_string(4, this->unique_id);
|
||||||
buffer.encode_bool(5, this->supports_oscillation);
|
buffer.encode_bool(5, this->supports_oscillation);
|
||||||
buffer.encode_bool(6, this->supports_speed);
|
buffer.encode_bool(6, this->supports_speed);
|
||||||
|
buffer.encode_bool(7, this->supports_direction);
|
||||||
}
|
}
|
||||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
|
@ -827,6 +842,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||||
out.append(" supports_speed: ");
|
out.append(" supports_speed: ");
|
||||||
out.append(YESNO(this->supports_speed));
|
out.append(YESNO(this->supports_speed));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" supports_direction: ");
|
||||||
|
out.append(YESNO(this->supports_direction));
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
@ -843,6 +862,10 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
this->speed = value.as_enum<enums::FanSpeed>();
|
this->speed = value.as_enum<enums::FanSpeed>();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 5: {
|
||||||
|
this->direction = value.as_enum<enums::FanDirection>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -862,6 +885,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_bool(2, this->state);
|
buffer.encode_bool(2, this->state);
|
||||||
buffer.encode_bool(3, this->oscillating);
|
buffer.encode_bool(3, this->oscillating);
|
||||||
buffer.encode_enum<enums::FanSpeed>(4, this->speed);
|
buffer.encode_enum<enums::FanSpeed>(4, this->speed);
|
||||||
|
buffer.encode_enum<enums::FanDirection>(5, this->direction);
|
||||||
}
|
}
|
||||||
void FanStateResponse::dump_to(std::string &out) const {
|
void FanStateResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
|
@ -882,6 +906,10 @@ void FanStateResponse::dump_to(std::string &out) const {
|
||||||
out.append(" speed: ");
|
out.append(" speed: ");
|
||||||
out.append(proto_enum_to_string<enums::FanSpeed>(this->speed));
|
out.append(proto_enum_to_string<enums::FanSpeed>(this->speed));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" direction: ");
|
||||||
|
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
@ -910,6 +938,14 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
this->oscillating = value.as_bool();
|
this->oscillating = value.as_bool();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 8: {
|
||||||
|
this->has_direction = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 9: {
|
||||||
|
this->direction = value.as_enum<enums::FanDirection>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -932,6 +968,8 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_enum<enums::FanSpeed>(5, this->speed);
|
buffer.encode_enum<enums::FanSpeed>(5, this->speed);
|
||||||
buffer.encode_bool(6, this->has_oscillating);
|
buffer.encode_bool(6, this->has_oscillating);
|
||||||
buffer.encode_bool(7, this->oscillating);
|
buffer.encode_bool(7, this->oscillating);
|
||||||
|
buffer.encode_bool(8, this->has_direction);
|
||||||
|
buffer.encode_enum<enums::FanDirection>(9, this->direction);
|
||||||
}
|
}
|
||||||
void FanCommandRequest::dump_to(std::string &out) const {
|
void FanCommandRequest::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
|
@ -964,6 +1002,14 @@ void FanCommandRequest::dump_to(std::string &out) const {
|
||||||
out.append(" oscillating: ");
|
out.append(" oscillating: ");
|
||||||
out.append(YESNO(this->oscillating));
|
out.append(YESNO(this->oscillating));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" has_direction: ");
|
||||||
|
out.append(YESNO(this->has_direction));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" direction: ");
|
||||||
|
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
|
|
@ -28,6 +28,10 @@ enum FanSpeed : uint32_t {
|
||||||
FAN_SPEED_MEDIUM = 1,
|
FAN_SPEED_MEDIUM = 1,
|
||||||
FAN_SPEED_HIGH = 2,
|
FAN_SPEED_HIGH = 2,
|
||||||
};
|
};
|
||||||
|
enum FanDirection : uint32_t {
|
||||||
|
FAN_DIRECTION_FORWARD = 0,
|
||||||
|
FAN_DIRECTION_REVERSE = 1,
|
||||||
|
};
|
||||||
enum LogLevel : uint32_t {
|
enum LogLevel : uint32_t {
|
||||||
LOG_LEVEL_NONE = 0,
|
LOG_LEVEL_NONE = 0,
|
||||||
LOG_LEVEL_ERROR = 1,
|
LOG_LEVEL_ERROR = 1,
|
||||||
|
@ -279,6 +283,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
||||||
std::string unique_id{}; // NOLINT
|
std::string unique_id{}; // NOLINT
|
||||||
bool supports_oscillation{false}; // NOLINT
|
bool supports_oscillation{false}; // NOLINT
|
||||||
bool supports_speed{false}; // NOLINT
|
bool supports_speed{false}; // NOLINT
|
||||||
|
bool supports_direction{false}; // NOLINT
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
@ -289,10 +294,11 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
||||||
};
|
};
|
||||||
class FanStateResponse : public ProtoMessage {
|
class FanStateResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0}; // NOLINT
|
||||||
bool state{false}; // NOLINT
|
bool state{false}; // NOLINT
|
||||||
bool oscillating{false}; // NOLINT
|
bool oscillating{false}; // NOLINT
|
||||||
enums::FanSpeed speed{}; // NOLINT
|
enums::FanSpeed speed{}; // NOLINT
|
||||||
|
enums::FanDirection direction{}; // NOLINT
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
@ -302,13 +308,15 @@ class FanStateResponse : public ProtoMessage {
|
||||||
};
|
};
|
||||||
class FanCommandRequest : public ProtoMessage {
|
class FanCommandRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t key{0}; // NOLINT
|
uint32_t key{0}; // NOLINT
|
||||||
bool has_state{false}; // NOLINT
|
bool has_state{false}; // NOLINT
|
||||||
bool state{false}; // NOLINT
|
bool state{false}; // NOLINT
|
||||||
bool has_speed{false}; // NOLINT
|
bool has_speed{false}; // NOLINT
|
||||||
enums::FanSpeed speed{}; // NOLINT
|
enums::FanSpeed speed{}; // NOLINT
|
||||||
bool has_oscillating{false}; // NOLINT
|
bool has_oscillating{false}; // NOLINT
|
||||||
bool oscillating{false}; // NOLINT
|
bool oscillating{false}; // NOLINT
|
||||||
|
bool has_direction{false}; // NOLINT
|
||||||
|
enums::FanDirection direction{}; // NOLINT
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||||
template<typename T> void add_variable(std::string key, T value) {
|
template<typename T> void add_variable(std::string key, T value) {
|
||||||
this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
HomeassistantServiceResponse resp;
|
HomeassistantServiceResponse resp;
|
||||||
resp.service = this->service_.value(x...);
|
resp.service = this->service_.value(x...);
|
||||||
|
|
|
@ -25,7 +25,7 @@ AS3935_SCHEMA = cv.Schema({
|
||||||
cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11),
|
cv.Optional(CONF_SPIKE_REJECTION, default=2): cv.int_range(min=1, max=11),
|
||||||
cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True),
|
cv.Optional(CONF_LIGHTNING_THRESHOLD, default=1): cv.one_of(1, 5, 9, 16, int=True),
|
||||||
cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean,
|
cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 22, 64, 128, int=True),
|
cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True),
|
||||||
cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15),
|
cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,9 @@ void AS3935Component::setup() {
|
||||||
void AS3935Component::dump_config() {
|
void AS3935Component::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "AS3935:");
|
ESP_LOGCONFIG(TAG, "AS3935:");
|
||||||
LOG_PIN(" Interrupt Pin: ", this->irq_pin_);
|
LOG_PIN(" Interrupt Pin: ", this->irq_pin_);
|
||||||
|
LOG_BINARY_SENSOR(" ", "Thunder alert", this->thunder_alert_binary_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Distance", this->distance_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Lightning energy", this->energy_sensor_);
|
||||||
}
|
}
|
||||||
|
|
||||||
float AS3935Component::get_setup_priority() const { return setup_priority::DATA; }
|
float AS3935Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
|
@ -27,4 +27,4 @@ def to_code(config):
|
||||||
if CONF_LIGHTNING_ENERGY in config:
|
if CONF_LIGHTNING_ENERGY in config:
|
||||||
conf = config[CONF_LIGHTNING_ENERGY]
|
conf = config[CONF_LIGHTNING_ENERGY]
|
||||||
lightning_energy_sensor = yield sensor.new_sensor(conf)
|
lightning_energy_sensor = yield sensor.new_sensor(conf)
|
||||||
cg.add(hub.set_distance_sensor(lightning_energy_sensor))
|
cg.add(hub.set_energy_sensor(lightning_energy_sensor))
|
||||||
|
|
|
@ -10,8 +10,8 @@ as3935_spi_ns = cg.esphome_ns.namespace('as3935_spi')
|
||||||
SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice)
|
SPIAS3935 = as3935_spi_ns.class_('SPIAS3935Component', as3935.AS3935, spi.SPIDevice)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
|
CONFIG_SCHEMA = cv.All(as3935.AS3935_SCHEMA.extend({
|
||||||
cv.GenerateID(): cv.declare_id(SPIAS3935)
|
cv.GenerateID(): cv.declare_id(SPIAS3935),
|
||||||
}).extend(cv.COMPONENT_SCHEMA).extend(spi.SPI_DEVICE_SCHEMA))
|
}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema(cs_pin_required=True)))
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
|
CODEOWNERS = ['@OttoWinter']
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(200.0)
|
@coroutine_with_priority(200.0)
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
|
@ -10,4 +12,4 @@ def to_code(config):
|
||||||
cg.add_library('AsyncTCP-esphome', '1.1.1')
|
cg.add_library('AsyncTCP-esphome', '1.1.1')
|
||||||
elif CORE.is_esp8266:
|
elif CORE.is_esp8266:
|
||||||
# https://github.com/OttoWinter/ESPAsyncTCP
|
# https://github.com/OttoWinter/ESPAsyncTCP
|
||||||
cg.add_library('ESPAsyncTCP-esphome', '1.2.2')
|
cg.add_library('ESPAsyncTCP-esphome', '1.2.3')
|
||||||
|
|
|
@ -55,7 +55,7 @@ CONFIG_SCHEMA = cv.Schema({
|
||||||
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
|
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
|
||||||
cv.Optional(CONF_CURRENT_PHASES, default='3'): cv.enum(CURRENT_PHASES, upper=True),
|
cv.Optional(CONF_CURRENT_PHASES, default='3'): cv.enum(CURRENT_PHASES, upper=True),
|
||||||
cv.Optional(CONF_GAIN_PGA, default='2X'): cv.enum(PGA_GAINS, upper=True),
|
cv.Optional(CONF_GAIN_PGA, default='2X'): cv.enum(PGA_GAINS, upper=True),
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(spi.SPI_DEVICE_SCHEMA)
|
}).extend(cv.polling_component_schema('60s')).extend(spi.spi_device_schema())
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ['@OttoWinter']
|
|
@ -108,7 +108,7 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->prev_trigger_ != nullptr) {
|
if (this->prev_trigger_ != nullptr) {
|
||||||
this->prev_trigger_->stop();
|
this->prev_trigger_->stop_action();
|
||||||
this->prev_trigger_ = nullptr;
|
this->prev_trigger_ = nullptr;
|
||||||
}
|
}
|
||||||
Trigger<> *trig;
|
Trigger<> *trig;
|
||||||
|
|
|
@ -7,6 +7,8 @@ namespace bh1750 {
|
||||||
static const char *TAG = "bh1750.sensor";
|
static const char *TAG = "bh1750.sensor";
|
||||||
|
|
||||||
static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001;
|
static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001;
|
||||||
|
static const uint8_t BH1750_COMMAND_MT_REG_HI = 0b01000000; // last 3 bits
|
||||||
|
static const uint8_t BH1750_COMMAND_MT_REG_LO = 0b01100000; // last 5 bits
|
||||||
|
|
||||||
void BH1750Sensor::setup() {
|
void BH1750Sensor::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up BH1750 '%s'...", this->name_.c_str());
|
ESP_LOGCONFIG(TAG, "Setting up BH1750 '%s'...", this->name_.c_str());
|
||||||
|
@ -14,7 +16,13 @@ void BH1750Sensor::setup() {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t mtreg_hi = (this->measurement_time_ >> 5) & 0b111;
|
||||||
|
uint8_t mtreg_lo = (this->measurement_time_ >> 0) & 0b11111;
|
||||||
|
this->write_bytes(BH1750_COMMAND_MT_REG_HI | mtreg_hi, nullptr, 0);
|
||||||
|
this->write_bytes(BH1750_COMMAND_MT_REG_LO | mtreg_lo, nullptr, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BH1750Sensor::dump_config() {
|
void BH1750Sensor::dump_config() {
|
||||||
LOG_SENSOR("", "BH1750", this);
|
LOG_SENSOR("", "BH1750", this);
|
||||||
LOG_I2C_DEVICE(this);
|
LOG_I2C_DEVICE(this);
|
||||||
|
@ -59,6 +67,7 @@ void BH1750Sensor::update() {
|
||||||
|
|
||||||
this->set_timeout("illuminance", wait, [this]() { this->read_data_(); });
|
this->set_timeout("illuminance", wait, [this]() { this->read_data_(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
void BH1750Sensor::read_data_() {
|
void BH1750Sensor::read_data_() {
|
||||||
uint16_t raw_value;
|
uint16_t raw_value;
|
||||||
|
@ -68,10 +77,12 @@ void BH1750Sensor::read_data_() {
|
||||||
}
|
}
|
||||||
|
|
||||||
float lx = float(raw_value) / 1.2f;
|
float lx = float(raw_value) / 1.2f;
|
||||||
|
lx *= 69.0f / this->measurement_time_;
|
||||||
ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
|
ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
|
||||||
this->publish_state(lx);
|
this->publish_state(lx);
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BH1750Sensor::set_resolution(BH1750Resolution resolution) { this->resolution_ = resolution; }
|
void BH1750Sensor::set_resolution(BH1750Resolution resolution) { this->resolution_ = resolution; }
|
||||||
|
|
||||||
} // namespace bh1750
|
} // namespace bh1750
|
||||||
|
|
|
@ -28,6 +28,7 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
|
||||||
* @param resolution The new resolution of the sensor.
|
* @param resolution The new resolution of the sensor.
|
||||||
*/
|
*/
|
||||||
void set_resolution(BH1750Resolution resolution);
|
void set_resolution(BH1750Resolution resolution);
|
||||||
|
void set_measurement_time(uint8_t measurement_time) { measurement_time_ = measurement_time; }
|
||||||
|
|
||||||
// ========== INTERNAL METHODS ==========
|
// ========== INTERNAL METHODS ==========
|
||||||
// (In most use cases you won't need these)
|
// (In most use cases you won't need these)
|
||||||
|
@ -40,6 +41,7 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
|
||||||
void read_data_();
|
void read_data_();
|
||||||
|
|
||||||
BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX};
|
BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX};
|
||||||
|
uint8_t measurement_time_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace bh1750
|
} // namespace bh1750
|
||||||
|
|
|
@ -15,9 +15,11 @@ BH1750_RESOLUTIONS = {
|
||||||
|
|
||||||
BH1750Sensor = bh1750_ns.class_('BH1750Sensor', sensor.Sensor, cg.PollingComponent, i2c.I2CDevice)
|
BH1750Sensor = bh1750_ns.class_('BH1750Sensor', sensor.Sensor, cg.PollingComponent, i2c.I2CDevice)
|
||||||
|
|
||||||
|
CONF_MEASUREMENT_TIME = 'measurement_time'
|
||||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({
|
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 1).extend({
|
||||||
cv.GenerateID(): cv.declare_id(BH1750Sensor),
|
cv.GenerateID(): cv.declare_id(BH1750Sensor),
|
||||||
cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(BH1750_RESOLUTIONS, float=True),
|
cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum(BH1750_RESOLUTIONS, float=True),
|
||||||
|
cv.Optional(CONF_MEASUREMENT_TIME, default=69): cv.int_range(min=31, max=254),
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x23))
|
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x23))
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,3 +30,4 @@ def to_code(config):
|
||||||
yield i2c.register_i2c_device(var, config)
|
yield i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
||||||
|
cg.add(var.set_measurement_time(config[CONF_MEASUREMENT_TIME]))
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import fan, output
|
from esphome.components import fan, output
|
||||||
from esphome.const import CONF_OSCILLATION_OUTPUT, CONF_OUTPUT, CONF_OUTPUT_ID
|
from esphome.const import CONF_DIRECTION_OUTPUT, CONF_OSCILLATION_OUTPUT, \
|
||||||
|
CONF_OUTPUT, CONF_OUTPUT_ID
|
||||||
from .. import binary_ns
|
from .. import binary_ns
|
||||||
|
|
||||||
BinaryFan = binary_ns.class_('BinaryFan', cg.Component)
|
BinaryFan = binary_ns.class_('BinaryFan', cg.Component)
|
||||||
|
@ -9,6 +10,7 @@ BinaryFan = binary_ns.class_('BinaryFan', cg.Component)
|
||||||
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({
|
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({
|
||||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan),
|
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan),
|
||||||
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
|
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||||
|
cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||||
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
|
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||||
}).extend(cv.COMPONENT_SCHEMA)
|
}).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
@ -25,3 +27,7 @@ def to_code(config):
|
||||||
if CONF_OSCILLATION_OUTPUT in config:
|
if CONF_OSCILLATION_OUTPUT in config:
|
||||||
oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT])
|
oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT])
|
||||||
cg.add(var.set_oscillating(oscillation_output))
|
cg.add(var.set_oscillating(oscillation_output))
|
||||||
|
|
||||||
|
if CONF_DIRECTION_OUTPUT in config:
|
||||||
|
direction_output = yield cg.get_variable(config[CONF_DIRECTION_OUTPUT])
|
||||||
|
cg.add(var.set_direction(direction_output))
|
||||||
|
|
|
@ -11,9 +11,12 @@ void binary::BinaryFan::dump_config() {
|
||||||
if (this->fan_->get_traits().supports_oscillation()) {
|
if (this->fan_->get_traits().supports_oscillation()) {
|
||||||
ESP_LOGCONFIG(TAG, " Oscillation: YES");
|
ESP_LOGCONFIG(TAG, " Oscillation: YES");
|
||||||
}
|
}
|
||||||
|
if (this->fan_->get_traits().supports_direction()) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Direction: YES");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void BinaryFan::setup() {
|
void BinaryFan::setup() {
|
||||||
auto traits = fan::FanTraits(this->oscillating_ != nullptr, false);
|
auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr);
|
||||||
this->fan_->set_traits(traits);
|
this->fan_->set_traits(traits);
|
||||||
this->fan_->add_on_state_callback([this]() { this->next_update_ = true; });
|
this->fan_->add_on_state_callback([this]() { this->next_update_ = true; });
|
||||||
}
|
}
|
||||||
|
@ -41,6 +44,16 @@ void BinaryFan::loop() {
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable));
|
ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this->direction_ != nullptr) {
|
||||||
|
bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE;
|
||||||
|
if (enable) {
|
||||||
|
this->direction_->turn_on();
|
||||||
|
} else {
|
||||||
|
this->direction_->turn_off();
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
float BinaryFan::get_setup_priority() const { return setup_priority::DATA; }
|
float BinaryFan::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
|
|
@ -16,11 +16,13 @@ class BinaryFan : public Component {
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; }
|
void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; }
|
||||||
|
void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
fan::FanState *fan_;
|
fan::FanState *fan_;
|
||||||
output::BinaryOutput *output_;
|
output::BinaryOutput *output_;
|
||||||
output::BinaryOutput *oscillating_{nullptr};
|
output::BinaryOutput *oscillating_{nullptr};
|
||||||
|
output::BinaryOutput *direction_{nullptr};
|
||||||
bool next_update_{true};
|
bool next_update_{true};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \
|
||||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||||
from esphome.util import Registry
|
from esphome.util import Registry
|
||||||
|
|
||||||
|
CODEOWNERS = ['@esphome/core']
|
||||||
DEVICE_CLASSES = [
|
DEVICE_CLASSES = [
|
||||||
'', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas',
|
'', 'battery', 'cold', 'connectivity', 'door', 'garage_door', 'gas',
|
||||||
'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy',
|
'heat', 'light', 'lock', 'moisture', 'motion', 'moving', 'occupancy',
|
||||||
|
@ -224,7 +225,7 @@ BINARY_SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend({
|
||||||
def setup_binary_sensor_core_(var, config):
|
def setup_binary_sensor_core_(var, config):
|
||||||
cg.add(var.set_name(config[CONF_NAME]))
|
cg.add(var.set_name(config[CONF_NAME]))
|
||||||
if CONF_INTERNAL in config:
|
if CONF_INTERNAL in config:
|
||||||
cg.add(var.set_internal(CONF_INTERNAL))
|
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||||
if CONF_DEVICE_CLASS in config:
|
if CONF_DEVICE_CLASS in config:
|
||||||
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
|
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||||
if CONF_INVERTED in config:
|
if CONF_INVERTED in config:
|
||||||
|
|
|
@ -137,6 +137,7 @@ template<typename... Ts> class BinarySensorPublishAction : public Action<Ts...>
|
||||||
public:
|
public:
|
||||||
explicit BinarySensorPublishAction(BinarySensor *sensor) : sensor_(sensor) {}
|
explicit BinarySensorPublishAction(BinarySensor *sensor) : sensor_(sensor) {}
|
||||||
TEMPLATABLE_VALUE(bool, state)
|
TEMPLATABLE_VALUE(bool, state)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
auto val = this->state_.value(x...);
|
auto val = this->state_.value(x...);
|
||||||
this->sensor_->publish_state(val);
|
this->sensor_->publish_state(val);
|
||||||
|
|
|
@ -7,6 +7,7 @@ from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
AUTO_LOAD = ['web_server_base']
|
AUTO_LOAD = ['web_server_base']
|
||||||
DEPENDENCIES = ['wifi']
|
DEPENDENCIES = ['wifi']
|
||||||
|
CODEOWNERS = ['@OttoWinter']
|
||||||
|
|
||||||
captive_portal_ns = cg.esphome_ns.namespace('captive_portal')
|
captive_portal_ns = cg.esphome_ns.namespace('captive_portal')
|
||||||
CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component)
|
CaptivePortal = captive_portal_ns.class_('CaptivePortal', cg.Component)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \
|
from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \
|
||||||
UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_PERIODIC_TABLE_CO2
|
UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_MOLECULE_CO2
|
||||||
|
|
||||||
DEPENDENCIES = ['i2c']
|
DEPENDENCIES = ['i2c']
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ CONF_BASELINE = 'baseline'
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
cv.GenerateID(): cv.declare_id(CCS811Component),
|
cv.GenerateID(): cv.declare_id(CCS811Component),
|
||||||
cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_PERIODIC_TABLE_CO2,
|
cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_MOLECULE_CO2,
|
||||||
0),
|
0),
|
||||||
cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0),
|
cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0),
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
CODEOWNERS = ['@esphome/core']
|
||||||
climate_ns = cg.esphome_ns.namespace('climate')
|
climate_ns = cg.esphome_ns.namespace('climate')
|
||||||
|
|
||||||
Climate = climate_ns.class_('Climate', cg.Nameable)
|
Climate = climate_ns.class_('Climate', cg.Nameable)
|
||||||
|
|
|
@ -6,6 +6,7 @@ from esphome.const import CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, CONF_SENSOR
|
||||||
from esphome.core import coroutine
|
from esphome.core import coroutine
|
||||||
|
|
||||||
AUTO_LOAD = ['sensor', 'remote_base']
|
AUTO_LOAD = ['sensor', 'remote_base']
|
||||||
|
CODEOWNERS = ['@glmnet']
|
||||||
|
|
||||||
climate_ir_ns = cg.esphome_ns.namespace('climate_ir')
|
climate_ir_ns = cg.esphome_ns.namespace('climate_ir')
|
||||||
ClimateIR = climate_ir_ns.class_('ClimateIR', climate.Climate, cg.Component,
|
ClimateIR = climate_ir_ns.class_('ClimateIR', climate.Climate, cg.Component,
|
||||||
|
|
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
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
AUTO_LOAD = ['climate_ir']
|
AUTO_LOAD = ['climate_ir']
|
||||||
|
CODEOWNERS = ['@glmnet']
|
||||||
|
|
||||||
coolix_ns = cg.esphome_ns.namespace('coolix')
|
coolix_ns = cg.esphome_ns.namespace('coolix')
|
||||||
CoolixClimate = coolix_ns.class_('CoolixClimate', climate_ir.ClimateIR)
|
CoolixClimate = coolix_ns.class_('CoolixClimate', climate_ir.ClimateIR)
|
||||||
|
|
|
@ -9,9 +9,10 @@ from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
CODEOWNERS = ['@esphome/core']
|
||||||
DEVICE_CLASSES = [
|
DEVICE_CLASSES = [
|
||||||
'', 'awning', 'blind', 'curtain', 'damper', 'door', 'garage',
|
'', 'awning', 'blind', 'curtain', 'damper', 'door', 'garage',
|
||||||
'shade', 'shutter', 'window'
|
'gate', 'shade', 'shutter', 'window'
|
||||||
]
|
]
|
||||||
|
|
||||||
cover_ns = cg.esphome_ns.namespace('cover')
|
cover_ns = cg.esphome_ns.namespace('cover')
|
||||||
|
|
|
@ -41,6 +41,10 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
explicit ControlAction(Cover *cover) : cover_(cover) {}
|
explicit ControlAction(Cover *cover) : cover_(cover) {}
|
||||||
|
|
||||||
|
TEMPLATABLE_VALUE(bool, stop)
|
||||||
|
TEMPLATABLE_VALUE(float, position)
|
||||||
|
TEMPLATABLE_VALUE(float, tilt)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
auto call = this->cover_->make_call();
|
auto call = this->cover_->make_call();
|
||||||
if (this->stop_.has_value())
|
if (this->stop_.has_value())
|
||||||
|
@ -52,10 +56,6 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEMPLATABLE_VALUE(bool, stop)
|
|
||||||
TEMPLATABLE_VALUE(float, position)
|
|
||||||
TEMPLATABLE_VALUE(float, tilt)
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Cover *cover_;
|
Cover *cover_;
|
||||||
};
|
};
|
||||||
|
@ -63,6 +63,10 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||||
template<typename... Ts> class CoverPublishAction : public Action<Ts...> {
|
template<typename... Ts> class CoverPublishAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
CoverPublishAction(Cover *cover) : cover_(cover) {}
|
CoverPublishAction(Cover *cover) : cover_(cover) {}
|
||||||
|
TEMPLATABLE_VALUE(float, position)
|
||||||
|
TEMPLATABLE_VALUE(float, tilt)
|
||||||
|
TEMPLATABLE_VALUE(CoverOperation, current_operation)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
if (this->position_.has_value())
|
if (this->position_.has_value())
|
||||||
this->cover_->position = this->position_.value(x...);
|
this->cover_->position = this->position_.value(x...);
|
||||||
|
@ -73,10 +77,6 @@ template<typename... Ts> class CoverPublishAction : public Action<Ts...> {
|
||||||
this->cover_->publish_state();
|
this->cover_->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEMPLATABLE_VALUE(float, position)
|
|
||||||
TEMPLATABLE_VALUE(float, tilt)
|
|
||||||
TEMPLATABLE_VALUE(CoverOperation, current_operation)
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Cover *cover_;
|
Cover *cover_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,7 @@ from esphome.components import sensor, voltage_sampler
|
||||||
from esphome.const import CONF_SENSOR, CONF_ID, ICON_FLASH, UNIT_AMPERE
|
from esphome.const import CONF_SENSOR, CONF_ID, ICON_FLASH, UNIT_AMPERE
|
||||||
|
|
||||||
AUTO_LOAD = ['voltage_sampler']
|
AUTO_LOAD = ['voltage_sampler']
|
||||||
|
CODEOWNERS = ['@jesserockz']
|
||||||
|
|
||||||
CONF_SAMPLE_DURATION = 'sample_duration'
|
CONF_SAMPLE_DURATION = 'sample_duration'
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,12 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
from esphome.const import CONF_ID, CONF_PIN, \
|
from esphome.const import CONF_ID, CONF_PIN
|
||||||
CONF_RESOLUTION, CONF_UNIT_OF_MEASUREMENT, UNIT_CELSIUS, \
|
|
||||||
CONF_ICON, ICON_THERMOMETER, CONF_ACCURACY_DECIMALS
|
|
||||||
|
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
AUTO_LOAD = ['sensor']
|
AUTO_LOAD = ['sensor']
|
||||||
|
|
||||||
CONF_ONE_WIRE_ID = 'one_wire_id'
|
CONF_ONE_WIRE_ID = 'one_wire_id'
|
||||||
CONF_AUTO_SETUP_SENSORS = 'auto_setup_sensors'
|
|
||||||
CONF_SENSOR_NAME_TEMPLATE = 'sensor_name_template'
|
|
||||||
SENSOR_NAME_TEMPLATE_DEFAULT = '%s.%s'
|
|
||||||
|
|
||||||
dallas_ns = cg.esphome_ns.namespace('dallas')
|
dallas_ns = cg.esphome_ns.namespace('dallas')
|
||||||
DallasComponent = dallas_ns.class_('DallasComponent', cg.PollingComponent)
|
DallasComponent = dallas_ns.class_('DallasComponent', cg.PollingComponent)
|
||||||
ESPOneWire = dallas_ns.class_('ESPOneWire')
|
ESPOneWire = dallas_ns.class_('ESPOneWire')
|
||||||
|
@ -21,12 +15,6 @@ CONFIG_SCHEMA = cv.Schema({
|
||||||
cv.GenerateID(): cv.declare_id(DallasComponent),
|
cv.GenerateID(): cv.declare_id(DallasComponent),
|
||||||
cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
|
cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
|
||||||
cv.Required(CONF_PIN): pins.gpio_input_pin_schema,
|
cv.Required(CONF_PIN): pins.gpio_input_pin_schema,
|
||||||
cv.Optional(CONF_AUTO_SETUP_SENSORS, default=False): cv.boolean,
|
|
||||||
cv.Optional(CONF_SENSOR_NAME_TEMPLATE, default=SENSOR_NAME_TEMPLATE_DEFAULT): cv.string_strict,
|
|
||||||
cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
|
|
||||||
cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=UNIT_CELSIUS): cv.string_strict,
|
|
||||||
cv.Optional(CONF_ICON, default=ICON_THERMOMETER): cv.icon,
|
|
||||||
cv.Optional(CONF_ACCURACY_DECIMALS, default=1): cv.int_,
|
|
||||||
}).extend(cv.polling_component_schema('60s'))
|
}).extend(cv.polling_component_schema('60s'))
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,16 +22,4 @@ def to_code(config):
|
||||||
pin = yield cg.gpio_pin_expression(config[CONF_PIN])
|
pin = yield cg.gpio_pin_expression(config[CONF_PIN])
|
||||||
one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin)
|
one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin)
|
||||||
var = cg.new_Pvariable(config[CONF_ID], one_wire)
|
var = cg.new_Pvariable(config[CONF_ID], one_wire)
|
||||||
if CONF_AUTO_SETUP_SENSORS in config:
|
|
||||||
cg.add(var.set_auto_setup_sensors(config[CONF_AUTO_SETUP_SENSORS]))
|
|
||||||
if CONF_SENSOR_NAME_TEMPLATE in config:
|
|
||||||
cg.add(var.set_sensor_name_template(config[CONF_SENSOR_NAME_TEMPLATE]))
|
|
||||||
if CONF_RESOLUTION in config:
|
|
||||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
|
||||||
if CONF_UNIT_OF_MEASUREMENT in config:
|
|
||||||
cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT]))
|
|
||||||
if CONF_ICON in config:
|
|
||||||
cg.add(var.set_icon(config[CONF_ICON]))
|
|
||||||
if CONF_ACCURACY_DECIMALS in config:
|
|
||||||
cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS]))
|
|
||||||
yield cg.register_component(var, config)
|
yield cg.register_component(var, config)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#include "dallas_component.h"
|
#include "dallas_component.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/application.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace dallas {
|
namespace dallas {
|
||||||
|
@ -53,29 +52,6 @@ void DallasComponent::setup() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this->found_sensors_.push_back(address);
|
this->found_sensors_.push_back(address);
|
||||||
|
|
||||||
if (this->auto_setup_sensors_) {
|
|
||||||
// avoid re-generating pre-configured sensors
|
|
||||||
bool skip = false;
|
|
||||||
for (auto sensor : this->sensors_) {
|
|
||||||
if (sensor->get_address() == address) {
|
|
||||||
skip = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!skip) {
|
|
||||||
auto dallastemperaturesensor = this->get_sensor_by_address(address, this->resolution_);
|
|
||||||
char sensor_name[64];
|
|
||||||
snprintf(sensor_name, sizeof(sensor_name), this->sensor_name_template_.c_str(), App.get_name().c_str(),
|
|
||||||
s.c_str());
|
|
||||||
dallastemperaturesensor->set_name(sensor_name);
|
|
||||||
dallastemperaturesensor->set_unit_of_measurement(this->unit_of_measurement_);
|
|
||||||
dallastemperaturesensor->set_icon(this->icon_);
|
|
||||||
dallastemperaturesensor->set_accuracy_decimals(this->accuracy_decimals_);
|
|
||||||
dallastemperaturesensor->set_force_update(false);
|
|
||||||
App.register_sensor(dallastemperaturesensor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto sensor : this->sensors_) {
|
for (auto sensor : this->sensors_) {
|
||||||
|
@ -180,25 +156,12 @@ void DallasComponent::update() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {}
|
DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {}
|
||||||
void DallasComponent::set_auto_setup_sensors(bool auto_setup_sensors) {
|
|
||||||
this->auto_setup_sensors_ = auto_setup_sensors;
|
|
||||||
}
|
|
||||||
void DallasComponent::set_sensor_name_template(const std::string &sensor_name_template) {
|
|
||||||
this->sensor_name_template_ = sensor_name_template;
|
|
||||||
}
|
|
||||||
void DallasComponent::set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
|
|
||||||
void DallasComponent::set_unit_of_measurement(const std::string &unit_of_measurement) {
|
|
||||||
this->unit_of_measurement_ = unit_of_measurement;
|
|
||||||
}
|
|
||||||
void DallasComponent::set_icon(const std::string &icon) { this->icon_ = icon; }
|
|
||||||
void DallasComponent::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; }
|
|
||||||
|
|
||||||
DallasTemperatureSensor::DallasTemperatureSensor(uint64_t address, uint8_t resolution, DallasComponent *parent)
|
DallasTemperatureSensor::DallasTemperatureSensor(uint64_t address, uint8_t resolution, DallasComponent *parent)
|
||||||
: parent_(parent) {
|
: parent_(parent) {
|
||||||
this->set_address(address);
|
this->set_address(address);
|
||||||
this->set_resolution(resolution);
|
this->set_resolution(resolution);
|
||||||
}
|
}
|
||||||
const uint64_t &DallasTemperatureSensor::get_address() const { return this->address_; }
|
|
||||||
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
|
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
|
||||||
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
|
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
|
||||||
void DallasTemperatureSensor::set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
|
void DallasTemperatureSensor::set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
|
||||||
|
|
|
@ -22,30 +22,12 @@ class DallasComponent : public PollingComponent {
|
||||||
|
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
/// Automatic sensors instantiation
|
|
||||||
bool get_auto_setup_sensors() const;
|
|
||||||
void set_auto_setup_sensors(bool auto_setup_sensors);
|
|
||||||
|
|
||||||
/// Get/Set properties for automatically generated sensors.
|
|
||||||
void set_sensor_name_template(const std::string &sensor_name_template);
|
|
||||||
void set_resolution(uint8_t resolution);
|
|
||||||
void set_unit_of_measurement(const std::string &unit_of_measurement);
|
|
||||||
void set_icon(const std::string &icon);
|
|
||||||
void set_accuracy_decimals(int8_t accuracy_decimals);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend DallasTemperatureSensor;
|
friend DallasTemperatureSensor;
|
||||||
|
|
||||||
ESPOneWire *one_wire_;
|
ESPOneWire *one_wire_;
|
||||||
std::vector<DallasTemperatureSensor *> sensors_;
|
std::vector<DallasTemperatureSensor *> sensors_;
|
||||||
std::vector<uint64_t> found_sensors_;
|
std::vector<uint64_t> found_sensors_;
|
||||||
|
|
||||||
bool auto_setup_sensors_;
|
|
||||||
std::string sensor_name_template_;
|
|
||||||
uint8_t resolution_;
|
|
||||||
std::string unit_of_measurement_;
|
|
||||||
std::string icon_;
|
|
||||||
int8_t accuracy_decimals_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Internal class that helps us create multiple sensors for one Dallas hub.
|
/// Internal class that helps us create multiple sensors for one Dallas hub.
|
||||||
|
@ -58,8 +40,6 @@ class DallasTemperatureSensor : public sensor::Sensor {
|
||||||
/// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
|
/// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
|
||||||
const std::string &get_address_name();
|
const std::string &get_address_name();
|
||||||
|
|
||||||
/// Get the 64-bit unsigned address of this sensor.
|
|
||||||
const uint64_t &get_address() const;
|
|
||||||
/// Set the 64-bit unsigned address for this sensor.
|
/// Set the 64-bit unsigned address for this sensor.
|
||||||
void set_address(uint64_t address);
|
void set_address(uint64_t address);
|
||||||
/// Get the index of this sensor. (0 if using address.)
|
/// Get the index of this sensor. (0 if using address.)
|
||||||
|
|
|
@ -30,7 +30,7 @@ bool HOT ICACHE_RAM_ATTR ESPOneWire::reset() {
|
||||||
|
|
||||||
// Switch into RX mode, letting the pin float
|
// Switch into RX mode, letting the pin float
|
||||||
this->pin_->pin_mode(INPUT_PULLUP);
|
this->pin_->pin_mode(INPUT_PULLUP);
|
||||||
// after 15µs-60µs wait time, slave pulls low for 60µs-240µs
|
// after 15µs-60µs wait time, responder pulls low for 60µs-240µs
|
||||||
// let's have 70µs just in case
|
// let's have 70µs just in case
|
||||||
delayMicroseconds(70);
|
delayMicroseconds(70);
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
CODEOWNERS = ['@OttoWinter']
|
||||||
DEPENDENCIES = ['logger']
|
DEPENDENCIES = ['logger']
|
||||||
|
|
||||||
debug_ns = cg.esphome_ns.namespace('debug')
|
debug_ns = cg.esphome_ns.namespace('debug')
|
||||||
|
|
|
@ -5,6 +5,7 @@ from esphome.const import CONF_ID, CONF_TRIGGER_ID, CONF_FILE, CONF_DEVICE
|
||||||
from esphome.components import uart
|
from esphome.components import uart
|
||||||
|
|
||||||
DEPENDENCIES = ['uart']
|
DEPENDENCIES = ['uart']
|
||||||
|
CODEOWNERS = ['@glmnet']
|
||||||
|
|
||||||
dfplayer_ns = cg.esphome_ns.namespace('dfplayer')
|
dfplayer_ns = cg.esphome_ns.namespace('dfplayer')
|
||||||
DFPlayer = dfplayer_ns.class_('DFPlayer', cg.Component)
|
DFPlayer = dfplayer_ns.class_('DFPlayer', cg.Component)
|
||||||
|
|
|
@ -104,7 +104,6 @@ class DFPlayer : public uart::UARTDevice, public Component {
|
||||||
|
|
||||||
#define DFPLAYER_SIMPLE_ACTION(ACTION_CLASS, ACTION_METHOD) \
|
#define DFPLAYER_SIMPLE_ACTION(ACTION_CLASS, ACTION_METHOD) \
|
||||||
template<typename... Ts> class ACTION_CLASS : public Action<Ts...>, public Parented<DFPlayer> { \
|
template<typename... Ts> class ACTION_CLASS : public Action<Ts...>, public Parented<DFPlayer> { \
|
||||||
public: \
|
|
||||||
void play(Ts... x) override { this->parent_->ACTION_METHOD(); } \
|
void play(Ts... x) override { this->parent_->ACTION_METHOD(); } \
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -115,6 +114,7 @@ template<typename... Ts> class PlayFileAction : public Action<Ts...>, public Par
|
||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(uint16_t, file)
|
TEMPLATABLE_VALUE(uint16_t, file)
|
||||||
TEMPLATABLE_VALUE(boolean, loop)
|
TEMPLATABLE_VALUE(boolean, loop)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
auto file = this->file_.value(x...);
|
auto file = this->file_.value(x...);
|
||||||
auto loop = this->loop_.value(x...);
|
auto loop = this->loop_.value(x...);
|
||||||
|
@ -131,6 +131,7 @@ template<typename... Ts> class PlayFolderAction : public Action<Ts...>, public P
|
||||||
TEMPLATABLE_VALUE(uint16_t, folder)
|
TEMPLATABLE_VALUE(uint16_t, folder)
|
||||||
TEMPLATABLE_VALUE(uint16_t, file)
|
TEMPLATABLE_VALUE(uint16_t, file)
|
||||||
TEMPLATABLE_VALUE(boolean, loop)
|
TEMPLATABLE_VALUE(boolean, loop)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
auto folder = this->folder_.value(x...);
|
auto folder = this->folder_.value(x...);
|
||||||
auto file = this->file_.value(x...);
|
auto file = this->file_.value(x...);
|
||||||
|
@ -146,6 +147,7 @@ template<typename... Ts> class PlayFolderAction : public Action<Ts...>, public P
|
||||||
template<typename... Ts> class SetDeviceAction : public Action<Ts...>, public Parented<DFPlayer> {
|
template<typename... Ts> class SetDeviceAction : public Action<Ts...>, public Parented<DFPlayer> {
|
||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(Device, device)
|
TEMPLATABLE_VALUE(Device, device)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
auto device = this->device_.value(x...);
|
auto device = this->device_.value(x...);
|
||||||
this->parent_->set_device(device);
|
this->parent_->set_device(device);
|
||||||
|
@ -155,6 +157,7 @@ template<typename... Ts> class SetDeviceAction : public Action<Ts...>, public Pa
|
||||||
template<typename... Ts> class SetVolumeAction : public Action<Ts...>, public Parented<DFPlayer> {
|
template<typename... Ts> class SetVolumeAction : public Action<Ts...>, public Parented<DFPlayer> {
|
||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(uint8_t, volume)
|
TEMPLATABLE_VALUE(uint8_t, volume)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
auto volume = this->volume_.value(x...);
|
auto volume = this->volume_.value(x...);
|
||||||
this->parent_->set_volume(volume);
|
this->parent_->set_volume(volume);
|
||||||
|
@ -164,6 +167,7 @@ template<typename... Ts> class SetVolumeAction : public Action<Ts...>, public Pa
|
||||||
template<typename... Ts> class SetEqAction : public Action<Ts...>, public Parented<DFPlayer> {
|
template<typename... Ts> class SetEqAction : public Action<Ts...>, public Parented<DFPlayer> {
|
||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(EqPreset, eq)
|
TEMPLATABLE_VALUE(EqPreset, eq)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
auto eq = this->eq_.value(x...);
|
auto eq = this->eq_.value(x...);
|
||||||
this->parent_->set_eq(eq);
|
this->parent_->set_eq(eq);
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ['@OttoWinter']
|
|
@ -1,4 +1,5 @@
|
||||||
#include "display_buffer.h"
|
#include "display_buffer.h"
|
||||||
|
#include "esphome/core/color.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
|
|
||||||
|
@ -7,8 +8,8 @@ namespace display {
|
||||||
|
|
||||||
static const char *TAG = "display";
|
static const char *TAG = "display";
|
||||||
|
|
||||||
const uint8_t COLOR_OFF = 0;
|
const Color COLOR_OFF(0, 0, 0, 0);
|
||||||
const uint8_t COLOR_ON = 1;
|
const Color COLOR_ON(1, 1, 1, 1);
|
||||||
|
|
||||||
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
||||||
this->buffer_ = new uint8_t[buffer_length];
|
this->buffer_ = new uint8_t[buffer_length];
|
||||||
|
@ -18,7 +19,7 @@ void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
||||||
}
|
}
|
||||||
this->clear();
|
this->clear();
|
||||||
}
|
}
|
||||||
void DisplayBuffer::fill(int color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
|
void DisplayBuffer::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
|
||||||
void DisplayBuffer::clear() { this->fill(COLOR_OFF); }
|
void DisplayBuffer::clear() { this->fill(COLOR_OFF); }
|
||||||
int DisplayBuffer::get_width() {
|
int DisplayBuffer::get_width() {
|
||||||
switch (this->rotation_) {
|
switch (this->rotation_) {
|
||||||
|
@ -43,7 +44,7 @@ int DisplayBuffer::get_height() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
|
void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
|
||||||
void HOT DisplayBuffer::draw_pixel_at(int x, int y, int color) {
|
void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
|
||||||
switch (this->rotation_) {
|
switch (this->rotation_) {
|
||||||
case DISPLAY_ROTATION_0_DEGREES:
|
case DISPLAY_ROTATION_0_DEGREES:
|
||||||
break;
|
break;
|
||||||
|
@ -63,7 +64,7 @@ void HOT DisplayBuffer::draw_pixel_at(int x, int y, int color) {
|
||||||
this->draw_absolute_pixel_internal(x, y, color);
|
this->draw_absolute_pixel_internal(x, y, color);
|
||||||
App.feed_wdt();
|
App.feed_wdt();
|
||||||
}
|
}
|
||||||
void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, int color) {
|
void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, Color color) {
|
||||||
const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
|
const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
|
||||||
const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
|
const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
|
||||||
int32_t err = dx + dy;
|
int32_t err = dx + dy;
|
||||||
|
@ -83,29 +84,29 @@ void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, int color) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void HOT DisplayBuffer::horizontal_line(int x, int y, int width, int color) {
|
void HOT DisplayBuffer::horizontal_line(int x, int y, int width, Color color) {
|
||||||
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
|
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
|
||||||
for (int i = x; i < x + width; i++)
|
for (int i = x; i < x + width; i++)
|
||||||
this->draw_pixel_at(i, y, color);
|
this->draw_pixel_at(i, y, color);
|
||||||
}
|
}
|
||||||
void HOT DisplayBuffer::vertical_line(int x, int y, int height, int color) {
|
void HOT DisplayBuffer::vertical_line(int x, int y, int height, Color color) {
|
||||||
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
|
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
|
||||||
for (int i = y; i < y + height; i++)
|
for (int i = y; i < y + height; i++)
|
||||||
this->draw_pixel_at(x, i, color);
|
this->draw_pixel_at(x, i, color);
|
||||||
}
|
}
|
||||||
void DisplayBuffer::rectangle(int x1, int y1, int width, int height, int color) {
|
void DisplayBuffer::rectangle(int x1, int y1, int width, int height, Color color) {
|
||||||
this->horizontal_line(x1, y1, width, color);
|
this->horizontal_line(x1, y1, width, color);
|
||||||
this->horizontal_line(x1, y1 + height - 1, width, color);
|
this->horizontal_line(x1, y1 + height - 1, width, color);
|
||||||
this->vertical_line(x1, y1, height, color);
|
this->vertical_line(x1, y1, height, color);
|
||||||
this->vertical_line(x1 + width - 1, y1, height, color);
|
this->vertical_line(x1 + width - 1, y1, height, color);
|
||||||
}
|
}
|
||||||
void DisplayBuffer::filled_rectangle(int x1, int y1, int width, int height, int color) {
|
void DisplayBuffer::filled_rectangle(int x1, int y1, int width, int height, Color color) {
|
||||||
// Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
|
// Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
|
||||||
for (int i = y1; i < y1 + height; i++) {
|
for (int i = y1; i < y1 + height; i++) {
|
||||||
this->horizontal_line(x1, i, width, color);
|
this->horizontal_line(x1, i, width, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, int color) {
|
void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, Color color) {
|
||||||
int dx = -radius;
|
int dx = -radius;
|
||||||
int dy = 0;
|
int dy = 0;
|
||||||
int err = 2 - 2 * radius;
|
int err = 2 - 2 * radius;
|
||||||
|
@ -128,7 +129,7 @@ void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, int colo
|
||||||
}
|
}
|
||||||
} while (dx <= 0);
|
} while (dx <= 0);
|
||||||
}
|
}
|
||||||
void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, int color) {
|
void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, Color color) {
|
||||||
int dx = -int32_t(radius);
|
int dx = -int32_t(radius);
|
||||||
int dy = 0;
|
int dy = 0;
|
||||||
int err = 2 - 2 * radius;
|
int err = 2 - 2 * radius;
|
||||||
|
@ -155,7 +156,7 @@ void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, int co
|
||||||
} while (dx <= 0);
|
} while (dx <= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayBuffer::print(int x, int y, Font *font, int color, TextAlign align, const char *text) {
|
void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align, const char *text) {
|
||||||
int x_start, y_start;
|
int x_start, y_start;
|
||||||
int width, height;
|
int width, height;
|
||||||
this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
|
this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
|
||||||
|
@ -197,19 +198,39 @@ void DisplayBuffer::print(int x, int y, Font *font, int color, TextAlign align,
|
||||||
i += match_length;
|
i += match_length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void DisplayBuffer::vprintf_(int x, int y, Font *font, int color, TextAlign align, const char *format, va_list arg) {
|
void DisplayBuffer::vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg) {
|
||||||
char buffer[256];
|
char buffer[256];
|
||||||
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
|
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
|
||||||
if (ret > 0)
|
if (ret > 0)
|
||||||
this->print(x, y, font, color, align, buffer);
|
this->print(x, y, font, color, align, buffer);
|
||||||
}
|
}
|
||||||
void DisplayBuffer::image(int x, int y, Image *image) {
|
|
||||||
for (int img_x = 0; img_x < image->get_width(); img_x++) {
|
void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color color_off) {
|
||||||
for (int img_y = 0; img_y < image->get_height(); img_y++) {
|
switch (image->get_type()) {
|
||||||
this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? COLOR_ON : COLOR_OFF);
|
case IMAGE_TYPE_BINARY:
|
||||||
}
|
for (int img_x = 0; img_x < image->get_width(); img_x++) {
|
||||||
|
for (int img_y = 0; img_y < image->get_height(); img_y++) {
|
||||||
|
this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? color_on : color_off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IMAGE_TYPE_GRAYSCALE:
|
||||||
|
for (int img_x = 0; img_x < image->get_width(); img_x++) {
|
||||||
|
for (int img_y = 0; img_y < image->get_height(); img_y++) {
|
||||||
|
this->draw_pixel_at(x + img_x, y + img_y, image->get_grayscale_pixel(img_x, img_y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IMAGE_TYPE_RGB24:
|
||||||
|
for (int img_x = 0; img_x < image->get_width(); img_x++) {
|
||||||
|
for (int img_y = 0; img_y < image->get_height(); img_y++) {
|
||||||
|
this->draw_pixel_at(x + img_x, y + img_y, image->get_color_pixel(img_x, img_y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1,
|
void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1,
|
||||||
int *width, int *height) {
|
int *width, int *height) {
|
||||||
int x_offset, baseline;
|
int x_offset, baseline;
|
||||||
|
@ -248,7 +269,7 @@ void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void DisplayBuffer::print(int x, int y, Font *font, int color, const char *text) {
|
void DisplayBuffer::print(int x, int y, Font *font, Color color, const char *text) {
|
||||||
this->print(x, y, font, color, TextAlign::TOP_LEFT, text);
|
this->print(x, y, font, color, TextAlign::TOP_LEFT, text);
|
||||||
}
|
}
|
||||||
void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char *text) {
|
void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char *text) {
|
||||||
|
@ -257,13 +278,13 @@ void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char
|
||||||
void DisplayBuffer::print(int x, int y, Font *font, const char *text) {
|
void DisplayBuffer::print(int x, int y, Font *font, const char *text) {
|
||||||
this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
|
this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
|
||||||
}
|
}
|
||||||
void DisplayBuffer::printf(int x, int y, Font *font, int color, TextAlign align, const char *format, ...) {
|
void DisplayBuffer::printf(int x, int y, Font *font, Color color, TextAlign align, const char *format, ...) {
|
||||||
va_list arg;
|
va_list arg;
|
||||||
va_start(arg, format);
|
va_start(arg, format);
|
||||||
this->vprintf_(x, y, font, color, align, format, arg);
|
this->vprintf_(x, y, font, color, align, format, arg);
|
||||||
va_end(arg);
|
va_end(arg);
|
||||||
}
|
}
|
||||||
void DisplayBuffer::printf(int x, int y, Font *font, int color, const char *format, ...) {
|
void DisplayBuffer::printf(int x, int y, Font *font, Color color, const char *format, ...) {
|
||||||
va_list arg;
|
va_list arg;
|
||||||
va_start(arg, format);
|
va_start(arg, format);
|
||||||
this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
|
this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
|
||||||
|
@ -306,14 +327,14 @@ void DisplayBuffer::do_update_() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef USE_TIME
|
#ifdef USE_TIME
|
||||||
void DisplayBuffer::strftime(int x, int y, Font *font, int color, TextAlign align, const char *format,
|
void DisplayBuffer::strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format,
|
||||||
time::ESPTime time) {
|
time::ESPTime time) {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
size_t ret = time.strftime(buffer, sizeof(buffer), format);
|
size_t ret = time.strftime(buffer, sizeof(buffer), format);
|
||||||
if (ret > 0)
|
if (ret > 0)
|
||||||
this->print(x, y, font, color, align, buffer);
|
this->print(x, y, font, color, align, buffer);
|
||||||
}
|
}
|
||||||
void DisplayBuffer::strftime(int x, int y, Font *font, int color, const char *format, time::ESPTime time) {
|
void DisplayBuffer::strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time) {
|
||||||
this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
|
this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
|
||||||
}
|
}
|
||||||
void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) {
|
void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) {
|
||||||
|
@ -431,10 +452,27 @@ bool Image::get_pixel(int x, int y) const {
|
||||||
const uint32_t pos = x + y * width_8;
|
const uint32_t pos = x + y * width_8;
|
||||||
return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
|
return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
|
||||||
}
|
}
|
||||||
|
Color Image::get_color_pixel(int x, int y) const {
|
||||||
|
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||||
|
return 0;
|
||||||
|
const uint32_t pos = (x + y * this->width_) * 3;
|
||||||
|
const uint32_t color32 = (pgm_read_byte(this->data_start_ + pos + 2) << 0) |
|
||||||
|
(pgm_read_byte(this->data_start_ + pos + 1) << 8) |
|
||||||
|
(pgm_read_byte(this->data_start_ + pos + 0) << 16);
|
||||||
|
return Color(color32);
|
||||||
|
}
|
||||||
|
Color Image::get_grayscale_pixel(int x, int y) const {
|
||||||
|
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||||
|
return 0;
|
||||||
|
const uint32_t pos = (x + y * this->width_);
|
||||||
|
const uint8_t gray = pgm_read_byte(this->data_start_ + pos);
|
||||||
|
return Color(gray | gray << 8 | gray << 16 | gray << 24);
|
||||||
|
}
|
||||||
int Image::get_width() const { return this->width_; }
|
int Image::get_width() const { return this->width_; }
|
||||||
int Image::get_height() const { return this->height_; }
|
int Image::get_height() const { return this->height_; }
|
||||||
Image::Image(const uint8_t *data_start, int width, int height)
|
ImageType Image::get_type() const { return this->type_; }
|
||||||
: width_(width), height_(height), data_start_(data_start) {}
|
Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
|
||||||
|
: width_(width), height_(height), type_(type), data_start_(data_start) {}
|
||||||
|
|
||||||
DisplayPage::DisplayPage(const display_writer_t &writer) : writer_(writer) {}
|
DisplayPage::DisplayPage(const display_writer_t &writer) : writer_(writer) {}
|
||||||
void DisplayPage::show() { this->parent_->show_page(this); }
|
void DisplayPage::show() { this->parent_->show_page(this); }
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/core/color.h"
|
||||||
|
|
||||||
#ifdef USE_TIME
|
#ifdef USE_TIME
|
||||||
#include "esphome/components/time/real_time_clock.h"
|
#include "esphome/components/time/real_time_clock.h"
|
||||||
|
@ -63,9 +64,11 @@ enum class TextAlign {
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Turn the pixel OFF.
|
/// Turn the pixel OFF.
|
||||||
extern const uint8_t COLOR_OFF;
|
extern const Color COLOR_OFF;
|
||||||
/// Turn the pixel ON.
|
/// Turn the pixel ON.
|
||||||
extern const uint8_t COLOR_ON;
|
extern const Color COLOR_ON;
|
||||||
|
|
||||||
|
enum ImageType { IMAGE_TYPE_BINARY = 0, IMAGE_TYPE_GRAYSCALE = 1, IMAGE_TYPE_RGB24 = 2 };
|
||||||
|
|
||||||
enum DisplayRotation {
|
enum DisplayRotation {
|
||||||
DISPLAY_ROTATION_0_DEGREES = 0,
|
DISPLAY_ROTATION_0_DEGREES = 0,
|
||||||
|
@ -91,7 +94,7 @@ using display_writer_t = std::function<void(DisplayBuffer &)>;
|
||||||
class DisplayBuffer {
|
class DisplayBuffer {
|
||||||
public:
|
public:
|
||||||
/// Fill the entire screen with the given color.
|
/// Fill the entire screen with the given color.
|
||||||
virtual void fill(int color);
|
virtual void fill(Color color);
|
||||||
/// Clear the entire screen by filling it with OFF pixels.
|
/// Clear the entire screen by filling it with OFF pixels.
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
@ -100,29 +103,29 @@ class DisplayBuffer {
|
||||||
/// Get the height of the image in pixels with rotation applied.
|
/// Get the height of the image in pixels with rotation applied.
|
||||||
int get_height();
|
int get_height();
|
||||||
/// Set a single pixel at the specified coordinates to the given color.
|
/// Set a single pixel at the specified coordinates to the given color.
|
||||||
void draw_pixel_at(int x, int y, int color = COLOR_ON);
|
void draw_pixel_at(int x, int y, Color color = COLOR_ON);
|
||||||
|
|
||||||
/// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
|
/// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
|
||||||
void line(int x1, int y1, int x2, int y2, int color = COLOR_ON);
|
void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON);
|
||||||
|
|
||||||
/// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
|
/// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
|
||||||
void horizontal_line(int x, int y, int width, int color = COLOR_ON);
|
void horizontal_line(int x, int y, int width, Color color = COLOR_ON);
|
||||||
|
|
||||||
/// Draw a vertical line from the point [x,y] to [x,y+width] with the given color.
|
/// Draw a vertical line from the point [x,y] to [x,y+width] with the given color.
|
||||||
void vertical_line(int x, int y, int height, int color = COLOR_ON);
|
void vertical_line(int x, int y, int height, Color color = COLOR_ON);
|
||||||
|
|
||||||
/// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at
|
/// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at
|
||||||
/// [x1+width,y1+height].
|
/// [x1+width,y1+height].
|
||||||
void rectangle(int x1, int y1, int width, int height, int color = COLOR_ON);
|
void rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
|
||||||
|
|
||||||
/// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height].
|
/// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height].
|
||||||
void filled_rectangle(int x1, int y1, int width, int height, int color = COLOR_ON);
|
void filled_rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
|
||||||
|
|
||||||
/// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color.
|
/// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color.
|
||||||
void circle(int center_x, int center_xy, int radius, int color = COLOR_ON);
|
void circle(int center_x, int center_xy, int radius, Color color = COLOR_ON);
|
||||||
|
|
||||||
/// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
|
/// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
|
||||||
void filled_circle(int center_x, int center_y, int radius, int color = COLOR_ON);
|
void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON);
|
||||||
|
|
||||||
/** Print `text` with the anchor point at [x,y] with `font`.
|
/** Print `text` with the anchor point at [x,y] with `font`.
|
||||||
*
|
*
|
||||||
|
@ -133,7 +136,7 @@ class DisplayBuffer {
|
||||||
* @param align The alignment of the text.
|
* @param align The alignment of the text.
|
||||||
* @param text The text to draw.
|
* @param text The text to draw.
|
||||||
*/
|
*/
|
||||||
void print(int x, int y, Font *font, int color, TextAlign align, const char *text);
|
void print(int x, int y, Font *font, Color color, TextAlign align, const char *text);
|
||||||
|
|
||||||
/** Print `text` with the top left at [x,y] with `font`.
|
/** Print `text` with the top left at [x,y] with `font`.
|
||||||
*
|
*
|
||||||
|
@ -143,7 +146,7 @@ class DisplayBuffer {
|
||||||
* @param color The color to draw the text with.
|
* @param color The color to draw the text with.
|
||||||
* @param text The text to draw.
|
* @param text The text to draw.
|
||||||
*/
|
*/
|
||||||
void print(int x, int y, Font *font, int color, const char *text);
|
void print(int x, int y, Font *font, Color color, const char *text);
|
||||||
|
|
||||||
/** Print `text` with the anchor point at [x,y] with `font`.
|
/** Print `text` with the anchor point at [x,y] with `font`.
|
||||||
*
|
*
|
||||||
|
@ -174,7 +177,7 @@ class DisplayBuffer {
|
||||||
* @param format The format to use.
|
* @param format The format to use.
|
||||||
* @param ... The arguments to use for the text formatting.
|
* @param ... The arguments to use for the text formatting.
|
||||||
*/
|
*/
|
||||||
void printf(int x, int y, Font *font, int color, TextAlign align, const char *format, ...)
|
void printf(int x, int y, Font *font, Color color, TextAlign align, const char *format, ...)
|
||||||
__attribute__((format(printf, 7, 8)));
|
__attribute__((format(printf, 7, 8)));
|
||||||
|
|
||||||
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
|
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
|
||||||
|
@ -186,7 +189,7 @@ class DisplayBuffer {
|
||||||
* @param format The format to use.
|
* @param format The format to use.
|
||||||
* @param ... The arguments to use for the text formatting.
|
* @param ... The arguments to use for the text formatting.
|
||||||
*/
|
*/
|
||||||
void printf(int x, int y, Font *font, int color, const char *format, ...) __attribute__((format(printf, 6, 7)));
|
void printf(int x, int y, Font *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7)));
|
||||||
|
|
||||||
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
|
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
|
||||||
*
|
*
|
||||||
|
@ -220,7 +223,7 @@ class DisplayBuffer {
|
||||||
* @param format The strftime format to use.
|
* @param format The strftime format to use.
|
||||||
* @param time The time to format.
|
* @param time The time to format.
|
||||||
*/
|
*/
|
||||||
void strftime(int x, int y, Font *font, int color, TextAlign align, const char *format, time::ESPTime time)
|
void strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, time::ESPTime time)
|
||||||
__attribute__((format(strftime, 7, 0)));
|
__attribute__((format(strftime, 7, 0)));
|
||||||
|
|
||||||
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
|
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
|
||||||
|
@ -232,7 +235,7 @@ class DisplayBuffer {
|
||||||
* @param format The strftime format to use.
|
* @param format The strftime format to use.
|
||||||
* @param time The time to format.
|
* @param time The time to format.
|
||||||
*/
|
*/
|
||||||
void strftime(int x, int y, Font *font, int color, const char *format, time::ESPTime time)
|
void strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time)
|
||||||
__attribute__((format(strftime, 6, 0)));
|
__attribute__((format(strftime, 6, 0)));
|
||||||
|
|
||||||
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
|
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
|
||||||
|
@ -259,8 +262,15 @@ class DisplayBuffer {
|
||||||
__attribute__((format(strftime, 5, 0)));
|
__attribute__((format(strftime, 5, 0)));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Draw the `image` with the top-left corner at [x,y] to the screen.
|
/** Draw the `image` with the top-left corner at [x,y] to the screen.
|
||||||
void image(int x, int y, Image *image);
|
*
|
||||||
|
* @param x The x coordinate of the upper left corner.
|
||||||
|
* @param y The y coordinate of the upper left corner.
|
||||||
|
* @param image The image to draw
|
||||||
|
* @param color_on The color to replace in binary images for the on bits.
|
||||||
|
* @param color_off The color to replace in binary images for the off bits.
|
||||||
|
*/
|
||||||
|
void image(int x, int y, Image *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
|
||||||
|
|
||||||
/** Get the text bounds of the given string.
|
/** Get the text bounds of the given string.
|
||||||
*
|
*
|
||||||
|
@ -290,9 +300,9 @@ class DisplayBuffer {
|
||||||
void set_rotation(DisplayRotation rotation);
|
void set_rotation(DisplayRotation rotation);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void vprintf_(int x, int y, Font *font, int color, TextAlign align, const char *format, va_list arg);
|
void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
|
||||||
|
|
||||||
virtual void draw_absolute_pixel_internal(int x, int y, int color) = 0;
|
virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0;
|
||||||
|
|
||||||
virtual int get_height_internal() = 0;
|
virtual int get_height_internal() = 0;
|
||||||
|
|
||||||
|
@ -377,20 +387,25 @@ class Font {
|
||||||
|
|
||||||
class Image {
|
class Image {
|
||||||
public:
|
public:
|
||||||
Image(const uint8_t *data_start, int width, int height);
|
Image(const uint8_t *data_start, int width, int height, ImageType type);
|
||||||
bool get_pixel(int x, int y) const;
|
bool get_pixel(int x, int y) const;
|
||||||
|
Color get_color_pixel(int x, int y) const;
|
||||||
|
Color get_grayscale_pixel(int x, int y) const;
|
||||||
int get_width() const;
|
int get_width() const;
|
||||||
int get_height() const;
|
int get_height() const;
|
||||||
|
ImageType get_type() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int width_;
|
int width_;
|
||||||
int height_;
|
int height_;
|
||||||
|
ImageType type_;
|
||||||
const uint8_t *data_start_;
|
const uint8_t *data_start_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
|
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(DisplayPage *, page)
|
TEMPLATABLE_VALUE(DisplayPage *, page)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
auto *page = this->page_.value(x...);
|
auto *page = this->page_.value(x...);
|
||||||
if (page != nullptr) {
|
if (page != nullptr) {
|
||||||
|
@ -402,18 +417,18 @@ template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
|
||||||
template<typename... Ts> class DisplayPageShowNextAction : public Action<Ts...> {
|
template<typename... Ts> class DisplayPageShowNextAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
DisplayPageShowNextAction(DisplayBuffer *buffer) : buffer_(buffer) {}
|
DisplayPageShowNextAction(DisplayBuffer *buffer) : buffer_(buffer) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->buffer_->show_next_page(); }
|
void play(Ts... x) override { this->buffer_->show_next_page(); }
|
||||||
|
|
||||||
protected:
|
|
||||||
DisplayBuffer *buffer_;
|
DisplayBuffer *buffer_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class DisplayPageShowPrevAction : public Action<Ts...> {
|
template<typename... Ts> class DisplayPageShowPrevAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
DisplayPageShowPrevAction(DisplayBuffer *buffer) : buffer_(buffer) {}
|
DisplayPageShowPrevAction(DisplayBuffer *buffer) : buffer_(buffer) {}
|
||||||
|
|
||||||
void play(Ts... x) override { this->buffer_->show_prev_page(); }
|
void play(Ts... x) override { this->buffer_->show_prev_page(); }
|
||||||
|
|
||||||
protected:
|
|
||||||
DisplayBuffer *buffer_;
|
DisplayBuffer *buffer_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
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; }
|
float EndstopCover::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
void EndstopCover::stop_prev_trigger_() {
|
void EndstopCover::stop_prev_trigger_() {
|
||||||
if (this->prev_command_trigger_ != nullptr) {
|
if (this->prev_command_trigger_ != nullptr) {
|
||||||
this->prev_command_trigger_->stop();
|
this->prev_command_trigger_->stop_action();
|
||||||
this->prev_command_trigger_ = nullptr;
|
this->prev_command_trigger_ = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,14 +99,14 @@ bool ESP32BLETracker::ble_setup() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||||||
|
|
||||||
// Initialize the bluetooth controller with the default configuration
|
// Initialize the bluetooth controller with the default configuration
|
||||||
if (!btStart()) {
|
if (!btStart()) {
|
||||||
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
|
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
|
||||||
|
|
||||||
err = esp_bluedroid_init();
|
err = esp_bluedroid_init();
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err);
|
ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err);
|
||||||
|
|
|
@ -28,6 +28,7 @@ class ESPBTUUID {
|
||||||
bool contains(uint8_t data1, uint8_t data2) const;
|
bool contains(uint8_t data1, uint8_t data2) const;
|
||||||
|
|
||||||
bool operator==(const ESPBTUUID &uuid) const;
|
bool operator==(const ESPBTUUID &uuid) const;
|
||||||
|
bool operator!=(const ESPBTUUID &uuid) const { return !(*this == uuid); }
|
||||||
|
|
||||||
esp_bt_uuid_t get_uuid();
|
esp_bt_uuid_t get_uuid();
|
||||||
|
|
||||||
|
@ -74,6 +75,8 @@ class ESPBTDevice {
|
||||||
|
|
||||||
uint64_t address_uint64() const;
|
uint64_t address_uint64() const;
|
||||||
|
|
||||||
|
const uint8_t *address() const { return address_; }
|
||||||
|
|
||||||
esp_ble_addr_type_t get_address_type() const { return this->address_type_; }
|
esp_ble_addr_type_t get_address_type() const { return this->address_type_; }
|
||||||
int get_rssi() const { return rssi_; }
|
int get_rssi() const { return rssi_; }
|
||||||
const std::string &get_name() const { return this->name_; }
|
const std::string &get_name() const { return this->name_; }
|
||||||
|
|
|
@ -13,7 +13,7 @@ class ESP8266PWM : public output::FloatOutput, public Component {
|
||||||
void set_pin(GPIOPin *pin) { pin_ = pin; }
|
void set_pin(GPIOPin *pin) { pin_ = pin; }
|
||||||
void set_frequency(float frequency) { this->frequency_ = frequency; }
|
void set_frequency(float frequency) { this->frequency_ = frequency; }
|
||||||
/// Dynamically update frequency
|
/// Dynamically update frequency
|
||||||
void update_frequency(float frequency) {
|
void update_frequency(float frequency) override {
|
||||||
this->set_frequency(frequency);
|
this->set_frequency(frequency);
|
||||||
this->write_state(this->last_output_);
|
this->write_state(this->last_output_);
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,6 @@ template<typename... Ts> class SetFrequencyAction : public Action<Ts...> {
|
||||||
this->parent_->update_frequency(freq);
|
this->parent_->update_frequency(freq);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
|
||||||
ESP8266PWM *parent_;
|
ESP8266PWM *parent_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ void EthernetComponent::setup() {
|
||||||
}
|
}
|
||||||
void EthernetComponent::loop() {
|
void EthernetComponent::loop() {
|
||||||
const uint32_t now = millis();
|
const uint32_t now = millis();
|
||||||
if (!this->connected_ && !this->last_connected_ && now - this->last_connected_ > 15000) {
|
if (!this->connected_ && !this->last_connected_ && now - this->connect_begin_ > 15000) {
|
||||||
ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting...");
|
ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting...");
|
||||||
this->start_connect_();
|
this->start_connect_();
|
||||||
return;
|
return;
|
||||||
|
|
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.publish_topic),
|
||||||
cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(cv.requires_component('mqtt'),
|
cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(cv.requires_component('mqtt'),
|
||||||
cv.subscribe_topic),
|
cv.subscribe_topic),
|
||||||
|
cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All(cv.requires_component('mqtt'),
|
||||||
|
cv.publish_topic),
|
||||||
|
cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All(cv.requires_component('mqtt'),
|
||||||
|
cv.subscribe_topic),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ template<typename... Ts> class TurnOnAction : public Action<Ts...> {
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
|
||||||
FanState *state_;
|
FanState *state_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,7 +34,6 @@ template<typename... Ts> class TurnOffAction : public Action<Ts...> {
|
||||||
|
|
||||||
void play(Ts... x) override { this->state_->turn_off().perform(); }
|
void play(Ts... x) override { this->state_->turn_off().perform(); }
|
||||||
|
|
||||||
protected:
|
|
||||||
FanState *state_;
|
FanState *state_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -45,7 +43,6 @@ template<typename... Ts> class ToggleAction : public Action<Ts...> {
|
||||||
|
|
||||||
void play(Ts... x) override { this->state_->toggle().perform(); }
|
void play(Ts... x) override { this->state_->toggle().perform(); }
|
||||||
|
|
||||||
protected:
|
|
||||||
FanState *state_;
|
FanState *state_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ struct FanStateRTCState {
|
||||||
bool state;
|
bool state;
|
||||||
FanSpeed speed;
|
FanSpeed speed;
|
||||||
bool oscillating;
|
bool oscillating;
|
||||||
|
FanDirection direction;
|
||||||
};
|
};
|
||||||
|
|
||||||
void FanState::setup() {
|
void FanState::setup() {
|
||||||
|
@ -34,6 +35,7 @@ void FanState::setup() {
|
||||||
call.set_state(recovered.state);
|
call.set_state(recovered.state);
|
||||||
call.set_speed(recovered.speed);
|
call.set_speed(recovered.speed);
|
||||||
call.set_oscillating(recovered.oscillating);
|
call.set_oscillating(recovered.oscillating);
|
||||||
|
call.set_direction(recovered.direction);
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
float FanState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
|
float FanState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
|
||||||
|
@ -46,6 +48,9 @@ void FanStateCall::perform() const {
|
||||||
if (this->oscillating_.has_value()) {
|
if (this->oscillating_.has_value()) {
|
||||||
this->state_->oscillating = *this->oscillating_;
|
this->state_->oscillating = *this->oscillating_;
|
||||||
}
|
}
|
||||||
|
if (this->direction_.has_value()) {
|
||||||
|
this->state_->direction = *this->direction_;
|
||||||
|
}
|
||||||
if (this->speed_.has_value()) {
|
if (this->speed_.has_value()) {
|
||||||
switch (*this->speed_) {
|
switch (*this->speed_) {
|
||||||
case FAN_SPEED_LOW:
|
case FAN_SPEED_LOW:
|
||||||
|
@ -63,6 +68,7 @@ void FanStateCall::perform() const {
|
||||||
saved.state = this->state_->state;
|
saved.state = this->state_->state;
|
||||||
saved.speed = this->state_->speed;
|
saved.speed = this->state_->speed;
|
||||||
saved.oscillating = this->state_->oscillating;
|
saved.oscillating = this->state_->oscillating;
|
||||||
|
saved.direction = this->state_->direction;
|
||||||
this->state_->rtc_.save(&saved);
|
this->state_->rtc_.save(&saved);
|
||||||
|
|
||||||
this->state_->state_callback_.call();
|
this->state_->state_callback_.call();
|
||||||
|
|
|
@ -15,6 +15,9 @@ enum FanSpeed {
|
||||||
FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed.
|
FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Simple enum to represent the direction of a fan
|
||||||
|
enum FanDirection { FAN_DIRECTION_FORWARD = 0, FAN_DIRECTION_REVERSE = 1 };
|
||||||
|
|
||||||
class FanState;
|
class FanState;
|
||||||
|
|
||||||
class FanStateCall {
|
class FanStateCall {
|
||||||
|
@ -46,6 +49,14 @@ class FanStateCall {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
FanStateCall &set_speed(const char *speed);
|
FanStateCall &set_speed(const char *speed);
|
||||||
|
FanStateCall &set_direction(FanDirection direction) {
|
||||||
|
this->direction_ = direction;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
FanStateCall &set_direction(optional<FanDirection> direction) {
|
||||||
|
this->direction_ = direction;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
void perform() const;
|
void perform() const;
|
||||||
|
|
||||||
|
@ -54,6 +65,7 @@ class FanStateCall {
|
||||||
optional<bool> binary_state_;
|
optional<bool> binary_state_;
|
||||||
optional<bool> oscillating_{};
|
optional<bool> oscillating_{};
|
||||||
optional<FanSpeed> speed_{};
|
optional<FanSpeed> speed_{};
|
||||||
|
optional<FanDirection> direction_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class FanState : public Nameable, public Component {
|
class FanState : public Nameable, public Component {
|
||||||
|
@ -76,6 +88,8 @@ class FanState : public Nameable, public Component {
|
||||||
bool oscillating{false};
|
bool oscillating{false};
|
||||||
/// The current fan speed.
|
/// The current fan speed.
|
||||||
FanSpeed speed{FAN_SPEED_HIGH};
|
FanSpeed speed{FAN_SPEED_HIGH};
|
||||||
|
/// The current direction of the fan
|
||||||
|
FanDirection direction{FAN_DIRECTION_FORWARD};
|
||||||
|
|
||||||
FanStateCall turn_on();
|
FanStateCall turn_on();
|
||||||
FanStateCall turn_off();
|
FanStateCall turn_off();
|
||||||
|
|
|
@ -6,7 +6,8 @@ namespace fan {
|
||||||
class FanTraits {
|
class FanTraits {
|
||||||
public:
|
public:
|
||||||
FanTraits() = default;
|
FanTraits() = default;
|
||||||
FanTraits(bool oscillation, bool speed) : oscillation_(oscillation), speed_(speed) {}
|
FanTraits(bool oscillation, bool speed, bool direction)
|
||||||
|
: oscillation_(oscillation), speed_(speed), direction_(direction) {}
|
||||||
|
|
||||||
/// Return if this fan supports oscillation.
|
/// Return if this fan supports oscillation.
|
||||||
bool supports_oscillation() const { return this->oscillation_; }
|
bool supports_oscillation() const { return this->oscillation_; }
|
||||||
|
@ -16,10 +17,15 @@ class FanTraits {
|
||||||
bool supports_speed() const { return this->speed_; }
|
bool supports_speed() const { return this->speed_; }
|
||||||
/// Set whether this fan supports speed modes.
|
/// Set whether this fan supports speed modes.
|
||||||
void set_speed(bool speed) { this->speed_ = speed; }
|
void set_speed(bool speed) { this->speed_ = speed; }
|
||||||
|
/// Return if this fan supports changing direction
|
||||||
|
bool supports_direction() const { return this->direction_; }
|
||||||
|
/// Set whether this fan supports changing direction
|
||||||
|
void set_direction(bool direction) { this->direction_ = direction; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool oscillation_{false};
|
bool oscillation_{false};
|
||||||
bool speed_{false};
|
bool speed_{false};
|
||||||
|
bool direction_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace fan
|
} // namespace fan
|
||||||
|
|
|
@ -4,6 +4,7 @@ from esphome.components import light
|
||||||
from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_RGB_ORDER, CONF_MAX_REFRESH_RATE
|
from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_RGB_ORDER, CONF_MAX_REFRESH_RATE
|
||||||
from esphome.core import coroutine
|
from esphome.core import coroutine
|
||||||
|
|
||||||
|
CODEOWNERS = ['@OttoWinter']
|
||||||
fastled_base_ns = cg.esphome_ns.namespace('fastled_base')
|
fastled_base_ns = cg.esphome_ns.namespace('fastled_base')
|
||||||
FastLEDLightOutput = fastled_base_ns.class_('FastLEDLightOutput', light.AddressableLight)
|
FastLEDLightOutput = fastled_base_ns.class_('FastLEDLightOutput', light.AddressableLight)
|
||||||
|
|
||||||
|
@ -35,5 +36,7 @@ def new_fastled_light(config):
|
||||||
|
|
||||||
yield light.register_light(var, config)
|
yield light.register_light(var, config)
|
||||||
# https://github.com/FastLED/FastLED/blob/master/library.json
|
# https://github.com/FastLED/FastLED/blob/master/library.json
|
||||||
cg.add_library('FastLED', '3.3.3')
|
# 3.3.3 has an issue on ESP32 with RMT and fastled_clockless:
|
||||||
|
# https://github.com/esphome/issues/issues/1375
|
||||||
|
cg.add_library('FastLED', '3.3.2')
|
||||||
yield var
|
yield var
|
||||||
|
|
|
@ -5,6 +5,7 @@ from esphome import codegen as cg
|
||||||
from esphome.const import CONF_ID, CONF_INITIAL_VALUE, CONF_RESTORE_VALUE, CONF_TYPE, CONF_VALUE
|
from esphome.const import CONF_ID, CONF_INITIAL_VALUE, CONF_RESTORE_VALUE, CONF_TYPE, CONF_VALUE
|
||||||
from esphome.core import coroutine_with_priority
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
|
CODEOWNERS = ['@esphome/core']
|
||||||
globals_ns = cg.esphome_ns.namespace('globals')
|
globals_ns = cg.esphome_ns.namespace('globals')
|
||||||
GlobalsComponent = globals_ns.class_('GlobalsComponent', cg.Component)
|
GlobalsComponent = globals_ns.class_('GlobalsComponent', cg.Component)
|
||||||
GlobalVarSetAction = globals_ns.class_('GlobalVarSetAction', automation.Action)
|
GlobalVarSetAction = globals_ns.class_('GlobalVarSetAction', automation.Action)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
|
||||||
|
CODEOWNERS = ['@esphome/core']
|
||||||
gpio_ns = cg.esphome_ns.namespace('gpio')
|
gpio_ns = cg.esphome_ns.namespace('gpio')
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue