mirror of
https://github.com/esphome/esphome.git
synced 2024-11-26 08:55:22 +01:00
Merge branch 'beta' into bump-1.15.0
This commit is contained in:
commit
ff90f6a440
645 changed files with 28595 additions and 5875 deletions
2
.coveragerc
Normal file
2
.coveragerc
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[run]
|
||||||
|
omit = esphome/components/*
|
32
.devcontainer/devcontainer.json
Normal file
32
.devcontainer/devcontainer.json
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"name": "ESPHome Dev",
|
||||||
|
"context": "..",
|
||||||
|
"dockerFile": "../docker/Dockerfile.dev",
|
||||||
|
"postCreateCommand": "mkdir -p config && pip3 install -e .",
|
||||||
|
"runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"],
|
||||||
|
"appPort": 6052,
|
||||||
|
"extensions": [
|
||||||
|
"ms-python.python",
|
||||||
|
"visualstudioexptteam.vscodeintellicode",
|
||||||
|
"redhat.vscode-yaml"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"python.pythonPath": "/usr/local/bin/python",
|
||||||
|
"python.linting.pylintEnabled": true,
|
||||||
|
"python.linting.enabled": true,
|
||||||
|
"python.formatting.provider": "black",
|
||||||
|
"editor.formatOnPaste": false,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.formatOnType": true,
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"terminal.integrated.shell.linux": "/bin/bash",
|
||||||
|
"yaml.customTags": [
|
||||||
|
"!secret scalar",
|
||||||
|
"!lambda scalar",
|
||||||
|
"!include_dir_named scalar",
|
||||||
|
"!include_dir_list scalar",
|
||||||
|
"!include_dir_merge_list scalar",
|
||||||
|
"!include_dir_merge_named scalar"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
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.
|
||||||
|
|
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
|
36
.github/lock.yml
vendored
Normal file
36
.github/lock.yml
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
||||||
|
|
||||||
|
# Number of days of inactivity before a closed issue or pull request is locked
|
||||||
|
daysUntilLock: 7
|
||||||
|
|
||||||
|
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||||
|
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||||
|
skipCreatedBefore: false
|
||||||
|
|
||||||
|
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||||
|
exemptLabels:
|
||||||
|
- keep-open
|
||||||
|
|
||||||
|
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||||
|
lockLabel: false
|
||||||
|
|
||||||
|
# Comment to post before locking. Set to `false` to disable
|
||||||
|
lockComment: false
|
||||||
|
|
||||||
|
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||||
|
setLockReason: false
|
||||||
|
|
||||||
|
# Limit to only `issues` or `pulls`
|
||||||
|
# only: issues
|
||||||
|
|
||||||
|
# Optionally, specify configuration settings just for `issues` or `pulls`
|
||||||
|
# issues:
|
||||||
|
# exemptLabels:
|
||||||
|
# - help-wanted
|
||||||
|
# lockLabel: outdated
|
||||||
|
|
||||||
|
# pulls:
|
||||||
|
# daysUntilLock: 30
|
||||||
|
|
||||||
|
# Repository to extend settings from
|
||||||
|
# _extends: repo
|
59
.github/stale.yml
vendored
Normal file
59
.github/stale.yml
vendored
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
# Configuration for probot-stale - https://github.com/probot/stale
|
||||||
|
|
||||||
|
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||||
|
daysUntilStale: 60
|
||||||
|
|
||||||
|
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||||
|
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||||
|
daysUntilClose: 7
|
||||||
|
|
||||||
|
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||||
|
onlyLabels: []
|
||||||
|
|
||||||
|
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||||
|
exemptLabels:
|
||||||
|
- not-stale
|
||||||
|
|
||||||
|
# Set to true to ignore issues in a project (defaults to false)
|
||||||
|
exemptProjects: false
|
||||||
|
|
||||||
|
# Set to true to ignore issues in a milestone (defaults to false)
|
||||||
|
exemptMilestones: true
|
||||||
|
|
||||||
|
# Set to true to ignore issues with an assignee (defaults to false)
|
||||||
|
exemptAssignees: false
|
||||||
|
|
||||||
|
# Label to use when marking as stale
|
||||||
|
staleLabel: stale
|
||||||
|
|
||||||
|
# Comment to post when marking as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
||||||
|
|
||||||
|
# Comment to post when removing the stale label.
|
||||||
|
# unmarkComment: >
|
||||||
|
# Your comment here.
|
||||||
|
|
||||||
|
# Comment to post when closing a stale Issue or Pull Request.
|
||||||
|
# closeComment: >
|
||||||
|
# Your comment here.
|
||||||
|
|
||||||
|
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||||
|
limitPerRun: 10
|
||||||
|
|
||||||
|
# Limit to only `issues` or `pulls`
|
||||||
|
only: pulls
|
||||||
|
|
||||||
|
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||||
|
# pulls:
|
||||||
|
# daysUntilStale: 30
|
||||||
|
# markComment: >
|
||||||
|
# This pull request has been automatically marked as stale because it has not had
|
||||||
|
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
# for your contributions.
|
||||||
|
|
||||||
|
# issues:
|
||||||
|
# exemptLabels:
|
||||||
|
# - confirmed
|
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}" \
|
||||||
|
.
|
215
.github/workflows/ci.yml
vendored
Normal file
215
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
# 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:
|
||||||
|
# A fast overview job that checks only changed files
|
||||||
|
overview:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: esphome/esphome-lint:latest
|
||||||
|
steps:
|
||||||
|
# Also fetch history and dev branch so that we can check which files changed
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Fetch dev branch
|
||||||
|
run: git fetch origin dev
|
||||||
|
|
||||||
|
# Cache the .pio directory with (primarily) library dependencies
|
||||||
|
- name: Cache .pio lib_deps
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: .pio
|
||||||
|
key: lint-cpp-pio-${{ hashFiles('platformio.ini') }}
|
||||||
|
restore-keys: |
|
||||||
|
lint-cpp-pio-
|
||||||
|
- name: Set up python environment
|
||||||
|
run: script/setup
|
||||||
|
# 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/ci-custom.json"
|
||||||
|
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||||
|
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||||
|
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
|
||||||
|
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||||
|
- name: Run a quick lint over all changed files
|
||||||
|
run: script/quicklint
|
||||||
|
- name: Suggest changes
|
||||||
|
run: script/ci-suggest-changes
|
||||||
|
|
||||||
|
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\"}}"
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -10,6 +10,9 @@ __pycache__/
|
||||||
*.sublime-project
|
*.sublime-project
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Intellij Idea
|
||||||
|
.idea
|
||||||
|
|
||||||
# Hide some OS X stuff
|
# Hide some OS X stuff
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.AppleDouble
|
.AppleDouble
|
||||||
|
@ -48,8 +51,10 @@ htmlcov/
|
||||||
.coverage
|
.coverage
|
||||||
.coverage.*
|
.coverage.*
|
||||||
.cache
|
.cache
|
||||||
|
.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.1.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:5.0.0-2 --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"
|
|
|
@ -2,5 +2,5 @@ ports:
|
||||||
- port: 6052
|
- port: 6052
|
||||||
onOpen: open-preview
|
onOpen: open-preview
|
||||||
tasks:
|
tasks:
|
||||||
- before: script/setup
|
- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup
|
||||||
command: python -m esphome config dashboard
|
command: python -m esphome config dashboard
|
||||||
|
|
11
.pre-commit-config.yaml
Normal file
11
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v2.4.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-added-large-files
|
||||||
|
- id: flake8
|
49
.travis.yml
49
.travis.yml
|
@ -1,49 +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
|
|
||||||
- python: "2.7"
|
|
||||||
env: TARGET=Test2.7
|
|
||||||
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.1.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"]
|
||||||
|
|
13
docker/Dockerfile.dev
Normal file
13
docker/Dockerfile.dev
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
FROM esphome/esphome-base-amd64:2.6.0
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
python3-wheel \
|
||||||
|
net-tools \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /workspaces
|
||||||
|
ENV SHELL /bin/bash
|
|
@ -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,20 +1,7 @@
|
||||||
FROM esphome/esphome-base-amd64:2.1.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
|
|
||||||
|
|
||||||
RUN ln -s /usr/bin/pip3 /usr/bin/pip && ln -f -s /usr/bin/python3 /usr/bin/python
|
|
||||||
|
|
||||||
VOLUME ["/esphome"]
|
VOLUME ["/esphome"]
|
||||||
WORKDIR /esphome
|
WORKDIR /esphome
|
||||||
|
|
10
docker/rootfs/etc/cont-init.d/30-esphome.sh
Normal file → Executable file
10
docker/rootfs/etc/cont-init.d/30-esphome.sh
Normal file → Executable file
|
@ -8,7 +8,15 @@ declare esphome_version
|
||||||
|
|
||||||
if bashio::config.has_value 'esphome_version'; then
|
if bashio::config.has_value 'esphome_version'; then
|
||||||
esphome_version=$(bashio::config 'esphome_version')
|
esphome_version=$(bashio::config 'esphome_version')
|
||||||
full_url="https://github.com/esphome/esphome/archive/${esphome_version}.zip"
|
if [[ $esphome_version == *":"* ]]; then
|
||||||
|
IFS=':' read -r -a array <<< "$esphome_version"
|
||||||
|
username=${array[0]}
|
||||||
|
ref=${array[1]}
|
||||||
|
else
|
||||||
|
username="esphome"
|
||||||
|
ref=$esphome_version
|
||||||
|
fi
|
||||||
|
full_url="https://github.com/${username}/esphome/archive/${ref}.zip"
|
||||||
bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..."
|
bashio::log.info "Installing esphome version '${esphome_version}' (${full_url})..."
|
||||||
pip3 install -U --no-cache-dir "${full_url}" \
|
pip3 install -U --no-cache-dir "${full_url}" \
|
||||||
|| bashio::exit.nok "Failed installing esphome pinned version."
|
|| bashio::exit.nok "Failed installing esphome pinned version."
|
||||||
|
|
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
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
|
@ -14,40 +12,27 @@ 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.py_compat import IS_PY2, safe_input, IS_PY3
|
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]
|
||||||
|
|
||||||
safe_print(u"Found multiple options, please choose one:")
|
safe_print("Found multiple options, please choose one:")
|
||||||
for i, (desc, _) in enumerate(options):
|
for i, (desc, _) in enumerate(options):
|
||||||
safe_print(u" [{}] {}".format(i + 1, desc))
|
safe_print(f" [{i+1}] {desc}")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
opt = safe_input('(number): ')
|
opt = input('(number): ')
|
||||||
if opt in options:
|
if opt in options:
|
||||||
opt = options.index(opt)
|
opt = options.index(opt)
|
||||||
break
|
break
|
||||||
|
@ -57,20 +42,20 @@ def choose_prompt(options):
|
||||||
raise ValueError
|
raise ValueError
|
||||||
break
|
break
|
||||||
except ValueError:
|
except ValueError:
|
||||||
safe_print(color('red', u"Invalid option: '{}'".format(opt)))
|
safe_print(color('red', f"Invalid option: '{opt}'"))
|
||||||
return options[opt - 1][1]
|
return options[opt - 1][1]
|
||||||
|
|
||||||
|
|
||||||
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((u"{} ({})".format(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((u"Over The Air ({})".format(CORE.address), CORE.address))
|
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||||
if default == 'OTA':
|
if default == 'OTA':
|
||||||
return CORE.address
|
return CORE.address
|
||||||
if show_mqtt and 'mqtt' in CORE.config:
|
if show_mqtt and 'mqtt' in CORE.config:
|
||||||
options.append((u"MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT'))
|
options.append(("MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT'))
|
||||||
if default == 'OTA':
|
if default == 'OTA':
|
||||||
return 'MQTT'
|
return 'MQTT'
|
||||||
if default is not None:
|
if default is not None:
|
||||||
|
@ -108,11 +93,7 @@ def run_miniterm(config, port):
|
||||||
except serial.SerialException:
|
except serial.SerialException:
|
||||||
_LOGGER.error("Serial port closed!")
|
_LOGGER.error("Serial port closed!")
|
||||||
return
|
return
|
||||||
if IS_PY2:
|
line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', 'backslashreplace')
|
||||||
line = raw.replace('\r', '').replace('\n', '')
|
|
||||||
else:
|
|
||||||
line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8',
|
|
||||||
'backslashreplace')
|
|
||||||
time = datetime.now().time().strftime('[%H:%M:%S]')
|
time = datetime.now().time().strftime('[%H:%M:%S]')
|
||||||
message = time + line
|
message = time + line
|
||||||
safe_print(message)
|
safe_print(message)
|
||||||
|
@ -127,11 +108,9 @@ def wrap_to_code(name, comp):
|
||||||
@functools.wraps(comp.to_code)
|
@functools.wraps(comp.to_code)
|
||||||
@coroutine_with_priority(coro.priority)
|
@coroutine_with_priority(coro.priority)
|
||||||
def wrapped(conf):
|
def wrapped(conf):
|
||||||
cg.add(cg.LineComment(u"{}:".format(name)))
|
cg.add(cg.LineComment(f"{name}:"))
|
||||||
if comp.config_schema is not None:
|
if comp.config_schema is not None:
|
||||||
conf_str = yaml_util.dump(conf)
|
conf_str = yaml_util.dump(conf)
|
||||||
if IS_PY2:
|
|
||||||
conf_str = conf_str.decode('utf-8')
|
|
||||||
conf_str = conf_str.replace('//', '')
|
conf_str = conf_str.replace('//', '')
|
||||||
cg.add(cg.LineComment(indent(conf_str)))
|
cg.add(cg.LineComment(indent(conf_str)))
|
||||||
yield coro(conf)
|
yield coro(conf)
|
||||||
|
@ -140,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):
|
||||||
|
@ -149,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)
|
||||||
|
@ -199,6 +185,10 @@ def upload_program(config, args, host):
|
||||||
|
|
||||||
from esphome import espota2
|
from esphome import espota2
|
||||||
|
|
||||||
|
if CONF_OTA not in config:
|
||||||
|
raise EsphomeError("Cannot upload Over the Air as the config does not include the ota: "
|
||||||
|
"component")
|
||||||
|
|
||||||
ota_conf = config[CONF_OTA]
|
ota_conf = config[CONF_OTA]
|
||||||
remote_port = ota_conf[CONF_PORT]
|
remote_port = ota_conf[CONF_PORT]
|
||||||
password = ota_conf[CONF_PASSWORD]
|
password = ota_conf[CONF_PASSWORD]
|
||||||
|
@ -239,7 +229,7 @@ def setup_log(debug=False, quiet=False):
|
||||||
log_level = logging.INFO
|
log_level = logging.INFO
|
||||||
logging.basicConfig(level=log_level)
|
logging.basicConfig(level=log_level)
|
||||||
fmt = "%(levelname)s %(message)s"
|
fmt = "%(levelname)s %(message)s"
|
||||||
colorfmt = "%(log_color)s{}%(reset)s".format(fmt)
|
colorfmt = f"%(log_color)s{fmt}%(reset)s"
|
||||||
datefmt = '%H:%M:%S'
|
datefmt = '%H:%M:%S'
|
||||||
|
|
||||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||||
|
@ -288,12 +278,12 @@ def command_compile(args, config):
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
if args.only_generate:
|
if args.only_generate:
|
||||||
_LOGGER.info(u"Successfully generated source code.")
|
_LOGGER.info("Successfully generated source code.")
|
||||||
return 0
|
return 0
|
||||||
exit_code = compile_program(args, config)
|
exit_code = compile_program(args, config)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
_LOGGER.info(u"Successfully compiled program.")
|
_LOGGER.info("Successfully compiled program.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -303,7 +293,7 @@ def command_upload(args, config):
|
||||||
exit_code = upload_program(config, args, port)
|
exit_code = upload_program(config, args, port)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
_LOGGER.info(u"Successfully uploaded program.")
|
_LOGGER.info("Successfully uploaded program.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -320,13 +310,13 @@ def command_run(args, config):
|
||||||
exit_code = compile_program(args, config)
|
exit_code = compile_program(args, config)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
_LOGGER.info(u"Successfully compiled program.")
|
_LOGGER.info("Successfully compiled program.")
|
||||||
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
port = choose_upload_log_host(default=args.upload_port, check_default=None,
|
||||||
show_ota=True, show_mqtt=False, show_api=True)
|
show_ota=True, show_mqtt=False, show_api=True)
|
||||||
exit_code = upload_program(config, args, port)
|
exit_code = upload_program(config, args, port)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
_LOGGER.info(u"Successfully uploaded program.")
|
_LOGGER.info("Successfully uploaded program.")
|
||||||
if args.no_logs:
|
if args.no_logs:
|
||||||
return 0
|
return 0
|
||||||
port = choose_upload_log_host(default=args.upload_port, check_default=port,
|
port = choose_upload_log_host(default=args.upload_port, check_default=port,
|
||||||
|
@ -345,7 +335,7 @@ def command_mqtt_fingerprint(args, config):
|
||||||
|
|
||||||
|
|
||||||
def command_version(args):
|
def command_version(args):
|
||||||
safe_print(u"Version: {}".format(const.__version__))
|
safe_print(f"Version: {const.__version__}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -373,10 +363,10 @@ def command_update_all(args):
|
||||||
twidth = 60
|
twidth = 60
|
||||||
|
|
||||||
def print_bar(middle_text):
|
def print_bar(middle_text):
|
||||||
middle_text = " {} ".format(middle_text)
|
middle_text = f" {middle_text} "
|
||||||
width = len(click.unstyle(middle_text))
|
width = len(click.unstyle(middle_text))
|
||||||
half_line = "=" * ((twidth - width) // 2)
|
half_line = "=" * ((twidth - width) // 2)
|
||||||
click.echo("%s%s%s" % (half_line, middle_text, half_line))
|
click.echo(f"{half_line}{middle_text}{half_line}")
|
||||||
|
|
||||||
for f in files:
|
for f in files:
|
||||||
print("Updating {}".format(color('cyan', f)))
|
print("Updating {}".format(color('cyan', f)))
|
||||||
|
@ -427,12 +417,14 @@ POST_CONFIG_ACTIONS = {
|
||||||
|
|
||||||
|
|
||||||
def parse_args(argv):
|
def parse_args(argv):
|
||||||
parser = argparse.ArgumentParser(description='ESPHome v{}'.format(const.__version__))
|
parser = argparse.ArgumentParser(description=f'ESPHome v{const.__version__}')
|
||||||
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
|
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
|
||||||
action='store_true')
|
action='store_true')
|
||||||
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')
|
||||||
|
@ -521,14 +513,10 @@ def run_esphome(argv):
|
||||||
_LOGGER.error("Missing configuration parameter, see esphome --help.")
|
_LOGGER.error("Missing configuration parameter, see esphome --help.")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if IS_PY2:
|
if sys.version_info < (3, 6, 0):
|
||||||
_LOGGER.warning("You're using ESPHome with python 2. Support for python 2 is deprecated "
|
_LOGGER.error("You're running ESPHome with Python <3.6. ESPHome is no longer compatible "
|
||||||
"and will be removed in 1.15.0. Please reinstall ESPHome with python 3.6 "
|
"with this Python version. Please reinstall ESPHome with Python 3.6+")
|
||||||
"or higher.")
|
return 1
|
||||||
elif IS_PY3 and sys.version_info < (3, 6, 0):
|
|
||||||
_LOGGER.warning("You're using ESPHome with python 3.5. Support for python 3.5 is "
|
|
||||||
"deprecated and will be removed in 1.15.0. Please reinstall ESPHome with "
|
|
||||||
"python 3.6 or higher.")
|
|
||||||
|
|
||||||
if args.command in PRE_CONFIG_ACTIONS:
|
if args.command in PRE_CONFIG_ACTIONS:
|
||||||
try:
|
try:
|
||||||
|
@ -541,13 +529,13 @@ 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
|
||||||
|
|
||||||
if args.command not in POST_CONFIG_ACTIONS:
|
if args.command not in POST_CONFIG_ACTIONS:
|
||||||
safe_print(u"Unknown command {}".format(args.command))
|
safe_print(f"Unknown command {args.command}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
rc = POST_CONFIG_ACTIONS[args.command](args, config)
|
||||||
|
|
|
@ -14,7 +14,6 @@ import esphome.api.api_pb2 as pb
|
||||||
from esphome.const import CONF_PASSWORD, CONF_PORT
|
from esphome.const import CONF_PASSWORD, CONF_PORT
|
||||||
from esphome.core import EsphomeError
|
from esphome.core import EsphomeError
|
||||||
from esphome.helpers import resolve_ip_address, indent, color
|
from esphome.helpers import resolve_ip_address, indent, color
|
||||||
from esphome.py_compat import text_type, IS_PY2, byte_to_bytes, char_to_byte
|
|
||||||
from esphome.util import safe_print
|
from esphome.util import safe_print
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -67,16 +66,16 @@ MESSAGE_TYPE_TO_PROTO = {
|
||||||
|
|
||||||
def _varuint_to_bytes(value):
|
def _varuint_to_bytes(value):
|
||||||
if value <= 0x7F:
|
if value <= 0x7F:
|
||||||
return byte_to_bytes(value)
|
return bytes([value])
|
||||||
|
|
||||||
ret = bytes()
|
ret = bytes()
|
||||||
while value:
|
while value:
|
||||||
temp = value & 0x7F
|
temp = value & 0x7F
|
||||||
value >>= 7
|
value >>= 7
|
||||||
if value:
|
if value:
|
||||||
ret += byte_to_bytes(temp | 0x80)
|
ret += bytes([temp | 0x80])
|
||||||
else:
|
else:
|
||||||
ret += byte_to_bytes(temp)
|
ret += bytes([temp])
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@ -84,8 +83,7 @@ def _varuint_to_bytes(value):
|
||||||
def _bytes_to_varuint(value):
|
def _bytes_to_varuint(value):
|
||||||
result = 0
|
result = 0
|
||||||
bitpos = 0
|
bitpos = 0
|
||||||
for c in value:
|
for val in value:
|
||||||
val = char_to_byte(c)
|
|
||||||
result |= (val & 0x7F) << bitpos
|
result |= (val & 0x7F) << bitpos
|
||||||
bitpos += 7
|
bitpos += 7
|
||||||
if (val & 0x80) == 0:
|
if (val & 0x80) == 0:
|
||||||
|
@ -191,8 +189,8 @@ class APIClient(threading.Thread):
|
||||||
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
try:
|
try:
|
||||||
self._socket.connect((ip, self._port))
|
self._socket.connect((ip, self._port))
|
||||||
except socket.error as err:
|
except OSError as err:
|
||||||
err = APIConnectionError("Error connecting to {}: {}".format(ip, err))
|
err = APIConnectionError(f"Error connecting to {ip}: {err}")
|
||||||
self._fatal_error(err)
|
self._fatal_error(err)
|
||||||
raise err
|
raise err
|
||||||
self._socket.settimeout(0.1)
|
self._socket.settimeout(0.1)
|
||||||
|
@ -200,7 +198,7 @@ class APIClient(threading.Thread):
|
||||||
self._socket_open_event.set()
|
self._socket_open_event.set()
|
||||||
|
|
||||||
hello = pb.HelloRequest()
|
hello = pb.HelloRequest()
|
||||||
hello.client_info = 'ESPHome v{}'.format(const.__version__)
|
hello.client_info = f'ESPHome v{const.__version__}'
|
||||||
try:
|
try:
|
||||||
resp = self._send_message_await_response(hello, pb.HelloResponse)
|
resp = self._send_message_await_response(hello, pb.HelloResponse)
|
||||||
except APIConnectionError as err:
|
except APIConnectionError as err:
|
||||||
|
@ -251,8 +249,8 @@ class APIClient(threading.Thread):
|
||||||
with self._socket_write_lock:
|
with self._socket_write_lock:
|
||||||
try:
|
try:
|
||||||
self._socket.sendall(data)
|
self._socket.sendall(data)
|
||||||
except socket.error as err:
|
except OSError as err:
|
||||||
err = APIConnectionError("Error while writing data: {}".format(err))
|
err = APIConnectionError(f"Error while writing data: {err}")
|
||||||
self._fatal_error(err)
|
self._fatal_error(err)
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
|
@ -265,10 +263,7 @@ class APIClient(threading.Thread):
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
encoded = msg.SerializeToString()
|
encoded = msg.SerializeToString()
|
||||||
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(text_type(msg)))
|
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg)))
|
||||||
if IS_PY2:
|
|
||||||
req = chr(0x00)
|
|
||||||
else:
|
|
||||||
req = bytes([0])
|
req = bytes([0])
|
||||||
req += _varuint_to_bytes(len(encoded))
|
req += _varuint_to_bytes(len(encoded))
|
||||||
req += _varuint_to_bytes(message_type)
|
req += _varuint_to_bytes(message_type)
|
||||||
|
@ -355,14 +350,14 @@ class APIClient(threading.Thread):
|
||||||
raise APIConnectionError("Socket was closed")
|
raise APIConnectionError("Socket was closed")
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
continue
|
continue
|
||||||
except socket.error as err:
|
except OSError as err:
|
||||||
raise APIConnectionError("Error while receiving data: {}".format(err))
|
raise APIConnectionError(f"Error while receiving data: {err}")
|
||||||
ret += val
|
ret += val
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _recv_varint(self):
|
def _recv_varint(self):
|
||||||
raw = bytes()
|
raw = bytes()
|
||||||
while not raw or char_to_byte(raw[-1]) & 0x80:
|
while not raw or raw[-1] & 0x80:
|
||||||
raw += self._recv(1)
|
raw += self._recv(1)
|
||||||
return _bytes_to_varuint(raw)
|
return _bytes_to_varuint(raw)
|
||||||
|
|
||||||
|
@ -371,7 +366,7 @@ class APIClient(threading.Thread):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Preamble
|
# Preamble
|
||||||
if char_to_byte(self._recv(1)[0]) != 0x00:
|
if self._recv(1)[0] != 0x00:
|
||||||
raise APIConnectionError("Invalid preamble")
|
raise APIConnectionError("Invalid preamble")
|
||||||
|
|
||||||
length = self._recv_varint()
|
length = self._recv_varint()
|
||||||
|
@ -436,7 +431,7 @@ def run_logs(config, address):
|
||||||
return
|
return
|
||||||
|
|
||||||
if err:
|
if err:
|
||||||
_LOGGER.warning(u"Disconnected from API: %s", err)
|
_LOGGER.warning("Disconnected from API: %s", err)
|
||||||
|
|
||||||
while retry_timer:
|
while retry_timer:
|
||||||
retry_timer.pop(0).cancel()
|
retry_timer.pop(0).cancel()
|
||||||
|
@ -454,18 +449,18 @@ def run_logs(config, address):
|
||||||
|
|
||||||
wait_time = int(min(1.5**min(tries, 100), 30))
|
wait_time = int(min(1.5**min(tries, 100), 30))
|
||||||
if not has_connects:
|
if not has_connects:
|
||||||
_LOGGER.warning(u"Initial connection failed. The ESP might not be connected "
|
_LOGGER.warning("Initial connection failed. The ESP might not be connected "
|
||||||
u"to WiFi yet (%s). Re-Trying in %s seconds",
|
"to WiFi yet (%s). Re-Trying in %s seconds",
|
||||||
error, wait_time)
|
error, wait_time)
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning(u"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
_LOGGER.warning("Couldn't connect to API (%s). Trying to reconnect in %s seconds",
|
||||||
error, wait_time)
|
error, wait_time)
|
||||||
timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1))
|
timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1))
|
||||||
timer.start()
|
timer.start()
|
||||||
retry_timer.append(timer)
|
retry_timer.append(timer)
|
||||||
|
|
||||||
def on_log(msg):
|
def on_log(msg):
|
||||||
time_ = datetime.now().time().strftime(u'[%H:%M:%S]')
|
time_ = datetime.now().time().strftime('[%H:%M:%S]')
|
||||||
text = msg.message
|
text = msg.message
|
||||||
if msg.send_failed:
|
if msg.send_failed:
|
||||||
text = color('white', '(Message skipped because it was too big to fit in '
|
text = color('white', '(Message skipped because it was too big to fit in '
|
||||||
|
|
|
@ -7,13 +7,17 @@ from esphome.util import Registry
|
||||||
|
|
||||||
|
|
||||||
def maybe_simple_id(*validators):
|
def maybe_simple_id(*validators):
|
||||||
|
return maybe_conf(CONF_ID, *validators)
|
||||||
|
|
||||||
|
|
||||||
|
def maybe_conf(conf, *validators):
|
||||||
validator = cv.All(*validators)
|
validator = cv.All(*validators)
|
||||||
|
|
||||||
def validate(value):
|
def validate(value):
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
return validator(value)
|
return validator(value)
|
||||||
with cv.remove_prepend_path([CONF_ID]):
|
with cv.remove_prepend_path([conf]):
|
||||||
return validator({CONF_ID: value})
|
return validator({conf: value})
|
||||||
|
|
||||||
return validate
|
return validate
|
||||||
|
|
||||||
|
@ -79,9 +83,9 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||||
try:
|
try:
|
||||||
return cv.Schema([schema])(value)
|
return cv.Schema([schema])(value)
|
||||||
except cv.Invalid as err2:
|
except cv.Invalid as err2:
|
||||||
if u'extra keys not allowed' in str(err2) and len(err2.path) == 2:
|
if 'extra keys not allowed' in str(err2) and len(err2.path) == 2:
|
||||||
raise err
|
raise err
|
||||||
if u'Unable to find action' in str(err):
|
if 'Unable to find action' in str(err):
|
||||||
raise err2
|
raise err2
|
||||||
raise cv.MultipleInvalid([err, err2])
|
raise cv.MultipleInvalid([err, err2])
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
|
|
|
@ -19,7 +19,7 @@ from esphome.cpp_helpers import ( # noqa
|
||||||
gpio_pin_expression, register_component, build_registry_entry,
|
gpio_pin_expression, register_component, build_registry_entry,
|
||||||
build_registry_list, extract_registry_entry_config, register_parented)
|
build_registry_list, extract_registry_entry_config, register_parented)
|
||||||
from esphome.cpp_types import ( # noqa
|
from esphome.cpp_types import ( # noqa
|
||||||
global_ns, void, nullptr, float_, double, bool_, std_ns, std_string,
|
global_ns, void, nullptr, float_, double, bool_, int_, std_ns, std_string,
|
||||||
std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN,
|
std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN,
|
||||||
esphome_ns, App, Nameable, Component, ComponentPtr,
|
esphome_ns, App, Nameable, Component, ComponentPtr,
|
||||||
PollingComponent, Application, optional, arduino_json_ns, JsonObject,
|
PollingComponent, Application, optional, arduino_json_ns, JsonObject,
|
||||||
|
|
0
esphome/components/ac_dimmer/__init__.py
Normal file
0
esphome/components/ac_dimmer/__init__.py
Normal file
217
esphome/components/ac_dimmer/ac_dimmer.cpp
Normal file
217
esphome/components/ac_dimmer/ac_dimmer.cpp
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
#include "ac_dimmer.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP8266
|
||||||
|
#include <core_esp8266_waveform.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ac_dimmer {
|
||||||
|
|
||||||
|
static const char *TAG = "ac_dimmer";
|
||||||
|
|
||||||
|
// Global array to store dimmer objects
|
||||||
|
static AcDimmerDataStore *all_dimmers[32];
|
||||||
|
|
||||||
|
/// Time in microseconds the gate should be held high
|
||||||
|
/// 10µs should be long enough for most triacs
|
||||||
|
/// For reference: BT136 datasheet says 2µs nominal (page 7)
|
||||||
|
static uint32_t GATE_ENABLE_TIME = 10;
|
||||||
|
|
||||||
|
/// Function called from timer interrupt
|
||||||
|
/// Input is current time in microseconds (micros())
|
||||||
|
/// Returns when next "event" is expected in µs, or 0 if no such event known.
|
||||||
|
uint32_t ICACHE_RAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) {
|
||||||
|
// If no ZC signal received yet.
|
||||||
|
if (this->crossed_zero_at == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
uint32_t time_since_zc = now - this->crossed_zero_at;
|
||||||
|
if (this->value == 65535 || this->value == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->enable_time_us != 0 && time_since_zc >= this->enable_time_us) {
|
||||||
|
this->enable_time_us = 0;
|
||||||
|
this->gate_pin->digital_write(true);
|
||||||
|
// Prevent too short pulses
|
||||||
|
this->disable_time_us = max(this->disable_time_us, time_since_zc + GATE_ENABLE_TIME);
|
||||||
|
}
|
||||||
|
if (this->disable_time_us != 0 && time_since_zc >= this->disable_time_us) {
|
||||||
|
this->disable_time_us = 0;
|
||||||
|
this->gate_pin->digital_write(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time_since_zc < this->enable_time_us)
|
||||||
|
// Next event is enable, return time until that event
|
||||||
|
return this->enable_time_us - time_since_zc;
|
||||||
|
else if (time_since_zc < disable_time_us) {
|
||||||
|
// Next event is disable, return time until that event
|
||||||
|
return this->disable_time_us - time_since_zc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time_since_zc >= this->cycle_time_us) {
|
||||||
|
// Already past last cycle time, schedule next call shortly
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->cycle_time_us - time_since_zc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run timer interrupt code and return in how many µs the next event is expected
|
||||||
|
uint32_t ICACHE_RAM_ATTR HOT timer_interrupt() {
|
||||||
|
// run at least with 1kHz
|
||||||
|
uint32_t min_dt_us = 1000;
|
||||||
|
uint32_t now = micros();
|
||||||
|
for (auto *dimmer : all_dimmers) {
|
||||||
|
if (dimmer == nullptr)
|
||||||
|
// no more dimmers
|
||||||
|
break;
|
||||||
|
uint32_t res = dimmer->timer_intr(now);
|
||||||
|
if (res != 0 && res < min_dt_us)
|
||||||
|
min_dt_us = res;
|
||||||
|
}
|
||||||
|
// return time until next timer1 interrupt in µs
|
||||||
|
return min_dt_us;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GPIO interrupt routine, called when ZC pin triggers
|
||||||
|
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::gpio_intr() {
|
||||||
|
uint32_t prev_crossed = this->crossed_zero_at;
|
||||||
|
|
||||||
|
// 50Hz mains frequency should give a half cycle of 10ms a 60Hz will give 8.33ms
|
||||||
|
// in any case the cycle last at least 5ms
|
||||||
|
this->crossed_zero_at = micros();
|
||||||
|
uint32_t cycle_time = this->crossed_zero_at - prev_crossed;
|
||||||
|
if (cycle_time > 5000) {
|
||||||
|
this->cycle_time_us = cycle_time;
|
||||||
|
} else {
|
||||||
|
// Otherwise this is noise and this is 2nd (or 3rd...) fall in the same pulse
|
||||||
|
// Consider this is the right fall edge and accumulate the cycle time instead
|
||||||
|
this->cycle_time_us += cycle_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->value == 65535) {
|
||||||
|
// fully on, enable output immediately
|
||||||
|
this->gate_pin->digital_write(true);
|
||||||
|
} else if (this->init_cycle) {
|
||||||
|
// send a full cycle
|
||||||
|
this->init_cycle = false;
|
||||||
|
this->enable_time_us = 0;
|
||||||
|
this->disable_time_us = cycle_time_us;
|
||||||
|
} else if (this->value == 0) {
|
||||||
|
// fully off, disable output immediately
|
||||||
|
this->gate_pin->digital_write(false);
|
||||||
|
} else {
|
||||||
|
if (this->method == DIM_METHOD_TRAILING) {
|
||||||
|
this->enable_time_us = 1; // cannot be 0
|
||||||
|
this->disable_time_us = max((uint32_t) 10, this->value * this->cycle_time_us / 65535);
|
||||||
|
} else {
|
||||||
|
// calculate time until enable in µs: (1.0-value)*cycle_time, but with integer arithmetic
|
||||||
|
// also take into account min_power
|
||||||
|
auto min_us = this->cycle_time_us * this->min_power / 1000;
|
||||||
|
this->enable_time_us = max((uint32_t) 1, ((65535 - this->value) * (this->cycle_time_us - min_us)) / 65535);
|
||||||
|
if (this->method == DIM_METHOD_LEADING_PULSE) {
|
||||||
|
// Minimum pulse time should be enough for the triac to trigger when it is close to the ZC zone
|
||||||
|
// this is for brightness near 99%
|
||||||
|
this->disable_time_us = max(this->enable_time_us + GATE_ENABLE_TIME, (uint32_t) cycle_time_us / 10);
|
||||||
|
} else {
|
||||||
|
this->gate_pin->digital_write(false);
|
||||||
|
this->disable_time_us = this->cycle_time_us;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_gpio_intr(AcDimmerDataStore *store) {
|
||||||
|
// Attaching pin interrupts on the same pin will override the previous interupt
|
||||||
|
// However, the user expects that multiple dimmers sharing the same ZC pin will work.
|
||||||
|
// We solve this in a bit of a hacky way: On each pin interrupt, we check all dimmers
|
||||||
|
// if any of them are using the same ZC pin, and also trigger the interrupt for *them*.
|
||||||
|
for (auto *dimmer : all_dimmers) {
|
||||||
|
if (dimmer == nullptr)
|
||||||
|
break;
|
||||||
|
if (dimmer->zero_cross_pin_number == store->zero_cross_pin_number) {
|
||||||
|
dimmer->gpio_intr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
// ESP32 implementation, uses basically the same code but needs to wrap
|
||||||
|
// timer_interrupt() function to auto-reschedule
|
||||||
|
static hw_timer_t *dimmer_timer = nullptr;
|
||||||
|
void ICACHE_RAM_ATTR HOT AcDimmerDataStore::s_timer_intr() { timer_interrupt(); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void AcDimmer::setup() {
|
||||||
|
// extend all_dimmers array with our dimmer
|
||||||
|
|
||||||
|
// Need to be sure the zero cross pin is setup only once, ESP8266 fails and ESP32 seems to fail silently
|
||||||
|
auto setup_zero_cross_pin = true;
|
||||||
|
|
||||||
|
for (auto &all_dimmer : all_dimmers) {
|
||||||
|
if (all_dimmer == nullptr) {
|
||||||
|
all_dimmer = &this->store_;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (all_dimmer->zero_cross_pin_number == this->zero_cross_pin_->get_pin()) {
|
||||||
|
setup_zero_cross_pin = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->gate_pin_->setup();
|
||||||
|
this->store_.gate_pin = this->gate_pin_->to_isr();
|
||||||
|
this->store_.zero_cross_pin_number = this->zero_cross_pin_->get_pin();
|
||||||
|
this->store_.min_power = static_cast<uint16_t>(this->min_power_ * 1000);
|
||||||
|
this->min_power_ = 0;
|
||||||
|
this->store_.method = this->method_;
|
||||||
|
|
||||||
|
if (setup_zero_cross_pin) {
|
||||||
|
this->zero_cross_pin_->setup();
|
||||||
|
this->store_.zero_cross_pin = this->zero_cross_pin_->to_isr();
|
||||||
|
this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_, FALLING);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP8266
|
||||||
|
// Uses ESP8266 waveform (soft PWM) class
|
||||||
|
// PWM and AcDimmer can even run at the same time this way
|
||||||
|
setTimer1Callback(&timer_interrupt);
|
||||||
|
#endif
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
// 80 Divider -> 1 count=1µs
|
||||||
|
dimmer_timer = timerBegin(0, 80, true);
|
||||||
|
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true);
|
||||||
|
// For ESP32, we can't use dynamic interval calculation because the timerX functions
|
||||||
|
// are not callable from ISR (placed in flash storage).
|
||||||
|
// Here we just use an interrupt firing every 50 µs.
|
||||||
|
timerAlarmWrite(dimmer_timer, 50, true);
|
||||||
|
timerAlarmEnable(dimmer_timer);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
void AcDimmer::write_state(float state) {
|
||||||
|
auto new_value = static_cast<uint16_t>(roundf(state * 65535));
|
||||||
|
if (new_value != 0 && this->store_.value == 0)
|
||||||
|
this->store_.init_cycle = this->init_with_half_cycle_;
|
||||||
|
this->store_.value = new_value;
|
||||||
|
}
|
||||||
|
void AcDimmer::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "AcDimmer:");
|
||||||
|
LOG_PIN(" Output Pin: ", this->gate_pin_);
|
||||||
|
LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Min Power: %.1f%%", this->store_.min_power / 10.0f);
|
||||||
|
ESP_LOGCONFIG(TAG, " Init with half cycle: %s", YESNO(this->init_with_half_cycle_));
|
||||||
|
if (method_ == DIM_METHOD_LEADING_PULSE)
|
||||||
|
ESP_LOGCONFIG(TAG, " Method: leading pulse");
|
||||||
|
else if (method_ == DIM_METHOD_LEADING)
|
||||||
|
ESP_LOGCONFIG(TAG, " Method: leading");
|
||||||
|
else
|
||||||
|
ESP_LOGCONFIG(TAG, " Method: trailing");
|
||||||
|
|
||||||
|
LOG_FLOAT_OUTPUT(this);
|
||||||
|
ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ac_dimmer
|
||||||
|
} // namespace esphome
|
66
esphome/components/ac_dimmer/ac_dimmer.h
Normal file
66
esphome/components/ac_dimmer/ac_dimmer.h
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/esphal.h"
|
||||||
|
#include "esphome/components/output/float_output.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ac_dimmer {
|
||||||
|
|
||||||
|
enum DimMethod { DIM_METHOD_LEADING_PULSE = 0, DIM_METHOD_LEADING, DIM_METHOD_TRAILING };
|
||||||
|
|
||||||
|
struct AcDimmerDataStore {
|
||||||
|
/// Zero-cross pin
|
||||||
|
ISRInternalGPIOPin *zero_cross_pin;
|
||||||
|
/// Zero-cross pin number - used to share ZC pin across multiple dimmers
|
||||||
|
uint8_t zero_cross_pin_number;
|
||||||
|
/// Output pin to write to
|
||||||
|
ISRInternalGPIOPin *gate_pin;
|
||||||
|
/// Value of the dimmer - 0 to 65535.
|
||||||
|
uint16_t value;
|
||||||
|
/// Minimum power for activation
|
||||||
|
uint16_t min_power;
|
||||||
|
/// Time between the last two ZC pulses
|
||||||
|
uint32_t cycle_time_us;
|
||||||
|
/// Time (in micros()) of last ZC signal
|
||||||
|
uint32_t crossed_zero_at;
|
||||||
|
/// Time since last ZC pulse to enable gate pin. 0 means not set.
|
||||||
|
uint32_t enable_time_us;
|
||||||
|
/// Time since last ZC pulse to disable gate pin. 0 means no disable.
|
||||||
|
uint32_t disable_time_us;
|
||||||
|
/// Set to send the first half ac cycle complete
|
||||||
|
bool init_cycle;
|
||||||
|
/// Dimmer method
|
||||||
|
DimMethod method;
|
||||||
|
|
||||||
|
uint32_t timer_intr(uint32_t now);
|
||||||
|
|
||||||
|
void gpio_intr();
|
||||||
|
static void s_gpio_intr(AcDimmerDataStore *store);
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
static void s_timer_intr();
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
class AcDimmer : public output::FloatOutput, public Component {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
void set_gate_pin(GPIOPin *gate_pin) { gate_pin_ = gate_pin; }
|
||||||
|
void set_zero_cross_pin(GPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; }
|
||||||
|
void set_init_with_half_cycle(bool init_with_half_cycle) { init_with_half_cycle_ = init_with_half_cycle; }
|
||||||
|
void set_method(DimMethod method) { method_ = method; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void write_state(float state) override;
|
||||||
|
|
||||||
|
GPIOPin *gate_pin_;
|
||||||
|
GPIOPin *zero_cross_pin_;
|
||||||
|
AcDimmerDataStore store_;
|
||||||
|
bool init_with_half_cycle_;
|
||||||
|
DimMethod method_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ac_dimmer
|
||||||
|
} // namespace esphome
|
45
esphome/components/ac_dimmer/output.py
Normal file
45
esphome/components/ac_dimmer/output.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome import pins
|
||||||
|
from esphome.components import output
|
||||||
|
from esphome.const import CONF_ID, CONF_MIN_POWER, CONF_METHOD
|
||||||
|
|
||||||
|
CODEOWNERS = ['@glmnet']
|
||||||
|
|
||||||
|
ac_dimmer_ns = cg.esphome_ns.namespace('ac_dimmer')
|
||||||
|
AcDimmer = ac_dimmer_ns.class_('AcDimmer', output.FloatOutput, cg.Component)
|
||||||
|
|
||||||
|
DimMethod = ac_dimmer_ns.enum('DimMethod')
|
||||||
|
DIM_METHODS = {
|
||||||
|
'LEADING_PULSE': DimMethod.DIM_METHOD_LEADING_PULSE,
|
||||||
|
'LEADING': DimMethod.DIM_METHOD_LEADING,
|
||||||
|
'TRAILING': DimMethod.DIM_METHOD_TRAILING,
|
||||||
|
}
|
||||||
|
|
||||||
|
CONF_GATE_PIN = 'gate_pin'
|
||||||
|
CONF_ZERO_CROSS_PIN = 'zero_cross_pin'
|
||||||
|
CONF_INIT_WITH_HALF_CYCLE = 'init_with_half_cycle'
|
||||||
|
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({
|
||||||
|
cv.Required(CONF_ID): cv.declare_id(AcDimmer),
|
||||||
|
cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
|
||||||
|
cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
|
||||||
|
cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_METHOD, default='leading pulse'): cv.enum(DIM_METHODS, upper=True, space='_'),
|
||||||
|
}).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
|
||||||
|
# override default min power to 10%
|
||||||
|
if CONF_MIN_POWER not in config:
|
||||||
|
config[CONF_MIN_POWER] = 0.1
|
||||||
|
yield output.register_output(var, config)
|
||||||
|
|
||||||
|
pin = yield cg.gpio_pin_expression(config[CONF_GATE_PIN])
|
||||||
|
cg.add(var.set_gate_pin(pin))
|
||||||
|
pin = yield cg.gpio_pin_expression(config[CONF_ZERO_CROSS_PIN])
|
||||||
|
cg.add(var.set_zero_cross_pin(pin))
|
||||||
|
cg.add(var.set_init_with_half_cycle(config[CONF_INIT_WITH_HALF_CYCLE]))
|
||||||
|
cg.add(var.set_method(config[CONF_METHOD]))
|
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); \
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,4 +36,4 @@ def to_code(config):
|
||||||
continue
|
continue
|
||||||
conf = config[key]
|
conf = config[key]
|
||||||
sens = yield sensor.new_sensor(conf)
|
sens = yield sensor.new_sensor(conf)
|
||||||
cg.add(getattr(var, 'set_{}_sensor'.format(key))(sens))
|
cg.add(getattr(var, f'set_{key}_sensor')(sens))
|
||||||
|
|
|
@ -2,7 +2,6 @@ import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor, voltage_sampler
|
from esphome.components import sensor, voltage_sampler
|
||||||
from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID
|
from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID
|
||||||
from esphome.py_compat import string_types
|
|
||||||
from . import ads1115_ns, ADS1115Component
|
from . import ads1115_ns, ADS1115Component
|
||||||
|
|
||||||
DEPENDENCIES = ['ads1115']
|
DEPENDENCIES = ['ads1115']
|
||||||
|
@ -32,9 +31,9 @@ GAIN = {
|
||||||
|
|
||||||
def validate_gain(value):
|
def validate_gain(value):
|
||||||
if isinstance(value, float):
|
if isinstance(value, float):
|
||||||
value = u'{:0.03f}'.format(value)
|
value = f'{value:0.03f}'
|
||||||
elif not isinstance(value, string_types):
|
elif not isinstance(value, str):
|
||||||
raise cv.Invalid('invalid gain "{}"'.format(value))
|
raise cv.Invalid(f'invalid gain "{value}"')
|
||||||
|
|
||||||
return cv.enum(GAIN)(value)
|
return cv.enum(GAIN)(value)
|
||||||
|
|
||||||
|
|
0
esphome/components/aht10/__init__.py
Normal file
0
esphome/components/aht10/__init__.py
Normal file
127
esphome/components/aht10/aht10.cpp
Normal file
127
esphome/components/aht10/aht10.cpp
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
// Implementation based on:
|
||||||
|
// - AHT10: https://github.com/Thinary/AHT10
|
||||||
|
// - Official Datasheet (cn):
|
||||||
|
// http://www.aosong.com/userfiles/files/media/aht10%E8%A7%84%E6%A0%BC%E4%B9%A6v1_1%EF%BC%8820191015%EF%BC%89.pdf
|
||||||
|
// - Unofficial Translated Datasheet (en):
|
||||||
|
// https://wiki.liutyi.info/download/attachments/30507639/Aosong_AHT10_en_draft_0c.pdf
|
||||||
|
//
|
||||||
|
// When configured for humidity, the log 'Components should block for at most 20-30ms in loop().' will be generated in
|
||||||
|
// verbose mode. This is due to technical specs of the sensor and can not be avoided.
|
||||||
|
//
|
||||||
|
// According to the datasheet, the component is supposed to respond in more than 75ms. In fact, it can answer almost
|
||||||
|
// immediately for temperature. But for humidity, it takes >90ms to get a valid data. From experience, we have best
|
||||||
|
// results making successive requests; the current implementation make 3 attemps with a delay of 30ms each time.
|
||||||
|
|
||||||
|
#include "aht10.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace aht10 {
|
||||||
|
|
||||||
|
static const char *TAG = "aht10";
|
||||||
|
static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1};
|
||||||
|
static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
|
||||||
|
static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for calibration and temperature measurement
|
||||||
|
static const uint8_t AHT10_HUMIDITY_DELAY = 30; // ms
|
||||||
|
static const uint8_t AHT10_ATTEMPS = 3; // safety margin, normally 3 attemps are enough: 3*30=90ms
|
||||||
|
|
||||||
|
void AHT10Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up AHT10...");
|
||||||
|
|
||||||
|
if (!this->write_bytes(0, AHT10_CALIBRATE_CMD, sizeof(AHT10_CALIBRATE_CMD))) {
|
||||||
|
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t data;
|
||||||
|
if (!this->read_byte(0, &data, AHT10_DEFAULT_DELAY)) {
|
||||||
|
ESP_LOGD(TAG, "Communication with AHT10 failed!");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((data & 0x68) != 0x08) { // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED
|
||||||
|
ESP_LOGE(TAG, "AHT10 calibration failed!");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "AHT10 calibrated");
|
||||||
|
}
|
||||||
|
|
||||||
|
void AHT10Component::update() {
|
||||||
|
if (!this->write_bytes(0, AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD))) {
|
||||||
|
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t data[6];
|
||||||
|
uint8_t delay = AHT10_DEFAULT_DELAY;
|
||||||
|
if (this->humidity_sensor_ != nullptr)
|
||||||
|
delay = AHT10_HUMIDITY_DELAY;
|
||||||
|
for (int i = 0; i < AHT10_ATTEMPS; ++i) {
|
||||||
|
ESP_LOGVV(TAG, "Attemps %u at %6ld", i, millis());
|
||||||
|
if (!this->read_bytes(0, data, 6, delay)) {
|
||||||
|
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
|
||||||
|
} else if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
|
||||||
|
ESP_LOGD(TAG, "AHT10 is busy, waiting...");
|
||||||
|
} else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
|
||||||
|
// Unrealistic humidity (0x0)
|
||||||
|
if (this->humidity_sensor_ == nullptr) {
|
||||||
|
ESP_LOGVV(TAG, "ATH10 Unrealistic humidity (0x0), but humidity is not required");
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "ATH10 Unrealistic humidity (0x0), retrying...");
|
||||||
|
if (!this->write_bytes(0, AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD))) {
|
||||||
|
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// data is valid, we can break the loop
|
||||||
|
ESP_LOGVV(TAG, "Answer at %6ld", millis());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((data[0] & 0x80) == 0x80) {
|
||||||
|
ESP_LOGE(TAG, "Measurements reading timed-out!");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
|
||||||
|
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
|
||||||
|
|
||||||
|
float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0;
|
||||||
|
float humidity;
|
||||||
|
if (raw_humidity == 0) { // unrealistic value
|
||||||
|
humidity = NAN;
|
||||||
|
} else {
|
||||||
|
humidity = (float) raw_humidity * 100.0 / 1048576.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->temperature_sensor_ != nullptr) {
|
||||||
|
this->temperature_sensor_->publish_state(temperature);
|
||||||
|
}
|
||||||
|
if (this->humidity_sensor_ != nullptr) {
|
||||||
|
if (isnan(humidity))
|
||||||
|
ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
|
||||||
|
this->humidity_sensor_->publish_state(humidity);
|
||||||
|
}
|
||||||
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
|
||||||
|
float AHT10Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void AHT10Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "AHT10:");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||||
|
}
|
||||||
|
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace aht10
|
||||||
|
} // namespace esphome
|
26
esphome/components/aht10/aht10.h
Normal file
26
esphome/components/aht10/aht10.h
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace aht10 {
|
||||||
|
|
||||||
|
class AHT10Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void update() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
|
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||||
|
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
sensor::Sensor *temperature_sensor_;
|
||||||
|
sensor::Sensor *humidity_sensor_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace aht10
|
||||||
|
} // namespace esphome
|
30
esphome/components/aht10/sensor.py
Normal file
30
esphome/components/aht10/sensor.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
from esphome.const import CONF_HUMIDITY, CONF_ID, CONF_TEMPERATURE, \
|
||||||
|
UNIT_CELSIUS, ICON_THERMOMETER, ICON_WATER_PERCENT, UNIT_PERCENT
|
||||||
|
|
||||||
|
DEPENDENCIES = ['i2c']
|
||||||
|
|
||||||
|
aht10_ns = cg.esphome_ns.namespace('aht10')
|
||||||
|
AHT10Component = aht10_ns.class_('AHT10Component', cg.PollingComponent, i2c.I2CDevice)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
|
cv.GenerateID(): cv.declare_id(AHT10Component),
|
||||||
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2),
|
||||||
|
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 2),
|
||||||
|
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38))
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
yield i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
|
if CONF_TEMPERATURE in config:
|
||||||
|
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||||
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
|
||||||
|
if CONF_HUMIDITY in config:
|
||||||
|
sens = yield sensor.new_sensor(config[CONF_HUMIDITY])
|
||||||
|
cg.add(var.set_humidity_sensor(sens))
|
|
@ -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)
|
||||||
|
@ -102,7 +103,7 @@ def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||||
|
|
||||||
def validate_homeassistant_event(value):
|
def validate_homeassistant_event(value):
|
||||||
value = cv.string(value)
|
value = cv.string(value)
|
||||||
if not value.startswith(u'esphome.'):
|
if not value.startswith('esphome.'):
|
||||||
raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with "
|
raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with "
|
||||||
"esphome. For example 'esphome.xyz'")
|
"esphome. For example 'esphome.xyz'")
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -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 ====================
|
||||||
|
@ -653,12 +661,34 @@ enum ClimateMode {
|
||||||
CLIMATE_MODE_AUTO = 1;
|
CLIMATE_MODE_AUTO = 1;
|
||||||
CLIMATE_MODE_COOL = 2;
|
CLIMATE_MODE_COOL = 2;
|
||||||
CLIMATE_MODE_HEAT = 3;
|
CLIMATE_MODE_HEAT = 3;
|
||||||
|
CLIMATE_MODE_FAN_ONLY = 4;
|
||||||
|
CLIMATE_MODE_DRY = 5;
|
||||||
|
}
|
||||||
|
enum ClimateFanMode {
|
||||||
|
CLIMATE_FAN_ON = 0;
|
||||||
|
CLIMATE_FAN_OFF = 1;
|
||||||
|
CLIMATE_FAN_AUTO = 2;
|
||||||
|
CLIMATE_FAN_LOW = 3;
|
||||||
|
CLIMATE_FAN_MEDIUM = 4;
|
||||||
|
CLIMATE_FAN_HIGH = 5;
|
||||||
|
CLIMATE_FAN_MIDDLE = 6;
|
||||||
|
CLIMATE_FAN_FOCUS = 7;
|
||||||
|
CLIMATE_FAN_DIFFUSE = 8;
|
||||||
|
}
|
||||||
|
enum ClimateSwingMode {
|
||||||
|
CLIMATE_SWING_OFF = 0;
|
||||||
|
CLIMATE_SWING_BOTH = 1;
|
||||||
|
CLIMATE_SWING_VERTICAL = 2;
|
||||||
|
CLIMATE_SWINT_HORIZONTAL = 3;
|
||||||
}
|
}
|
||||||
enum ClimateAction {
|
enum ClimateAction {
|
||||||
CLIMATE_ACTION_OFF = 0;
|
CLIMATE_ACTION_OFF = 0;
|
||||||
// values same as mode for readability
|
// values same as mode for readability
|
||||||
CLIMATE_ACTION_COOLING = 2;
|
CLIMATE_ACTION_COOLING = 2;
|
||||||
CLIMATE_ACTION_HEATING = 3;
|
CLIMATE_ACTION_HEATING = 3;
|
||||||
|
CLIMATE_ACTION_IDLE = 4;
|
||||||
|
CLIMATE_ACTION_DRYING = 5;
|
||||||
|
CLIMATE_ACTION_FAN = 6;
|
||||||
}
|
}
|
||||||
message ListEntitiesClimateResponse {
|
message ListEntitiesClimateResponse {
|
||||||
option (id) = 46;
|
option (id) = 46;
|
||||||
|
@ -678,6 +708,8 @@ message ListEntitiesClimateResponse {
|
||||||
float visual_temperature_step = 10;
|
float visual_temperature_step = 10;
|
||||||
bool supports_away = 11;
|
bool supports_away = 11;
|
||||||
bool supports_action = 12;
|
bool supports_action = 12;
|
||||||
|
repeated ClimateFanMode supported_fan_modes = 13;
|
||||||
|
repeated ClimateSwingMode supported_swing_modes = 14;
|
||||||
}
|
}
|
||||||
message ClimateStateResponse {
|
message ClimateStateResponse {
|
||||||
option (id) = 47;
|
option (id) = 47;
|
||||||
|
@ -693,6 +725,8 @@ message ClimateStateResponse {
|
||||||
float target_temperature_high = 6;
|
float target_temperature_high = 6;
|
||||||
bool away = 7;
|
bool away = 7;
|
||||||
ClimateAction action = 8;
|
ClimateAction action = 8;
|
||||||
|
ClimateFanMode fan_mode = 9;
|
||||||
|
ClimateSwingMode swing_mode = 10;
|
||||||
}
|
}
|
||||||
message ClimateCommandRequest {
|
message ClimateCommandRequest {
|
||||||
option (id) = 48;
|
option (id) = 48;
|
||||||
|
@ -711,4 +745,8 @@ message ClimateCommandRequest {
|
||||||
float target_temperature_high = 9;
|
float target_temperature_high = 9;
|
||||||
bool has_away = 10;
|
bool has_away = 10;
|
||||||
bool away = 11;
|
bool away = 11;
|
||||||
|
bool has_fan_mode = 12;
|
||||||
|
ClimateFanMode fan_mode = 13;
|
||||||
|
bool has_swing_mode = 14;
|
||||||
|
ClimateSwingMode swing_mode = 15;
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,7 +137,6 @@ void APIConnection::loop() {
|
||||||
// bool done = 3;
|
// bool done = 3;
|
||||||
bool done = this->image_reader_.available() == to_send;
|
bool done = this->image_reader_.available() == to_send;
|
||||||
buffer.encode_bool(3, done);
|
buffer.encode_bool(3, done);
|
||||||
this->set_nodelay(false);
|
|
||||||
bool success = this->send_buffer(buffer, 44);
|
bool success = this->send_buffer(buffer, 44);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
|
@ -249,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) {
|
||||||
|
@ -260,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) {
|
||||||
|
@ -274,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
|
||||||
|
@ -458,6 +462,10 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
|
||||||
}
|
}
|
||||||
if (traits.get_supports_away())
|
if (traits.get_supports_away())
|
||||||
resp.away = climate->away;
|
resp.away = climate->away;
|
||||||
|
if (traits.get_supports_fan_modes())
|
||||||
|
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode);
|
||||||
|
if (traits.get_supports_swing_modes())
|
||||||
|
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
||||||
return this->send_climate_state_response(resp);
|
return this->send_climate_state_response(resp);
|
||||||
}
|
}
|
||||||
bool APIConnection::send_climate_info(climate::Climate *climate) {
|
bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||||
|
@ -470,7 +478,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||||
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
msg.supports_current_temperature = traits.get_supports_current_temperature();
|
||||||
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
|
||||||
for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
|
for (auto mode : {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL,
|
||||||
climate::CLIMATE_MODE_HEAT}) {
|
climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY}) {
|
||||||
if (traits.supports_mode(mode))
|
if (traits.supports_mode(mode))
|
||||||
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
|
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
|
||||||
}
|
}
|
||||||
|
@ -479,6 +487,17 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
|
||||||
msg.visual_temperature_step = traits.get_visual_temperature_step();
|
msg.visual_temperature_step = traits.get_visual_temperature_step();
|
||||||
msg.supports_away = traits.get_supports_away();
|
msg.supports_away = traits.get_supports_away();
|
||||||
msg.supports_action = traits.get_supports_action();
|
msg.supports_action = traits.get_supports_action();
|
||||||
|
for (auto fan_mode : {climate::CLIMATE_FAN_ON, climate::CLIMATE_FAN_OFF, climate::CLIMATE_FAN_AUTO,
|
||||||
|
climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH,
|
||||||
|
climate::CLIMATE_FAN_MIDDLE, climate::CLIMATE_FAN_FOCUS, climate::CLIMATE_FAN_DIFFUSE}) {
|
||||||
|
if (traits.supports_fan_mode(fan_mode))
|
||||||
|
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
|
||||||
|
}
|
||||||
|
for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL,
|
||||||
|
climate::CLIMATE_SWING_HORIZONTAL}) {
|
||||||
|
if (traits.supports_swing_mode(swing_mode))
|
||||||
|
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
|
||||||
|
}
|
||||||
return this->send_list_entities_climate_response(msg);
|
return this->send_list_entities_climate_response(msg);
|
||||||
}
|
}
|
||||||
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||||
|
@ -497,6 +516,10 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||||
call.set_target_temperature_high(msg.target_temperature_high);
|
call.set_target_temperature_high(msg.target_temperature_high);
|
||||||
if (msg.has_away)
|
if (msg.has_away)
|
||||||
call.set_away(msg.away);
|
call.set_away(msg.away);
|
||||||
|
if (msg.has_fan_mode)
|
||||||
|
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
|
||||||
|
if (msg.has_swing_mode)
|
||||||
|
call.set_swing_mode(static_cast<climate::ClimateSwingMode>(msg.swing_mode));
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -539,8 +562,6 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
|
||||||
if (this->log_subscription_ < level)
|
if (this->log_subscription_ < level)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
this->set_nodelay(false);
|
|
||||||
|
|
||||||
// Send raw so that we don't copy too much
|
// Send raw so that we don't copy too much
|
||||||
auto buffer = this->create_buffer();
|
auto buffer = this->create_buffer();
|
||||||
// LogLevel level = 1;
|
// LogLevel level = 1;
|
||||||
|
|
|
@ -138,12 +138,6 @@ class APIConnection : public APIServerConnection {
|
||||||
void on_timeout_(uint32_t time);
|
void on_timeout_(uint32_t time);
|
||||||
void on_data_(uint8_t *buf, size_t len);
|
void on_data_(uint8_t *buf, size_t len);
|
||||||
void parse_recv_buffer_();
|
void parse_recv_buffer_();
|
||||||
void set_nodelay(bool nodelay) override {
|
|
||||||
if (nodelay == this->current_nodelay_)
|
|
||||||
return;
|
|
||||||
this->client_->setNoDelay(nodelay);
|
|
||||||
this->current_nodelay_ = nodelay;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class ConnectionState {
|
enum class ConnectionState {
|
||||||
WAITING_FOR_HELLO,
|
WAITING_FOR_HELLO,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// This file was automatically generated with a tool.
|
||||||
|
// See scripts/api_protobuf/api_protobuf.py
|
||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
@ -50,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:
|
||||||
|
@ -102,6 +114,48 @@ template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMo
|
||||||
return "CLIMATE_MODE_COOL";
|
return "CLIMATE_MODE_COOL";
|
||||||
case enums::CLIMATE_MODE_HEAT:
|
case enums::CLIMATE_MODE_HEAT:
|
||||||
return "CLIMATE_MODE_HEAT";
|
return "CLIMATE_MODE_HEAT";
|
||||||
|
case enums::CLIMATE_MODE_FAN_ONLY:
|
||||||
|
return "CLIMATE_MODE_FAN_ONLY";
|
||||||
|
case enums::CLIMATE_MODE_DRY:
|
||||||
|
return "CLIMATE_MODE_DRY";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template<> const char *proto_enum_to_string<enums::ClimateFanMode>(enums::ClimateFanMode value) {
|
||||||
|
switch (value) {
|
||||||
|
case enums::CLIMATE_FAN_ON:
|
||||||
|
return "CLIMATE_FAN_ON";
|
||||||
|
case enums::CLIMATE_FAN_OFF:
|
||||||
|
return "CLIMATE_FAN_OFF";
|
||||||
|
case enums::CLIMATE_FAN_AUTO:
|
||||||
|
return "CLIMATE_FAN_AUTO";
|
||||||
|
case enums::CLIMATE_FAN_LOW:
|
||||||
|
return "CLIMATE_FAN_LOW";
|
||||||
|
case enums::CLIMATE_FAN_MEDIUM:
|
||||||
|
return "CLIMATE_FAN_MEDIUM";
|
||||||
|
case enums::CLIMATE_FAN_HIGH:
|
||||||
|
return "CLIMATE_FAN_HIGH";
|
||||||
|
case enums::CLIMATE_FAN_MIDDLE:
|
||||||
|
return "CLIMATE_FAN_MIDDLE";
|
||||||
|
case enums::CLIMATE_FAN_FOCUS:
|
||||||
|
return "CLIMATE_FAN_FOCUS";
|
||||||
|
case enums::CLIMATE_FAN_DIFFUSE:
|
||||||
|
return "CLIMATE_FAN_DIFFUSE";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template<> const char *proto_enum_to_string<enums::ClimateSwingMode>(enums::ClimateSwingMode value) {
|
||||||
|
switch (value) {
|
||||||
|
case enums::CLIMATE_SWING_OFF:
|
||||||
|
return "CLIMATE_SWING_OFF";
|
||||||
|
case enums::CLIMATE_SWING_BOTH:
|
||||||
|
return "CLIMATE_SWING_BOTH";
|
||||||
|
case enums::CLIMATE_SWING_VERTICAL:
|
||||||
|
return "CLIMATE_SWING_VERTICAL";
|
||||||
|
case enums::CLIMATE_SWINT_HORIZONTAL:
|
||||||
|
return "CLIMATE_SWINT_HORIZONTAL";
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
@ -114,6 +168,12 @@ template<> const char *proto_enum_to_string<enums::ClimateAction>(enums::Climate
|
||||||
return "CLIMATE_ACTION_COOLING";
|
return "CLIMATE_ACTION_COOLING";
|
||||||
case enums::CLIMATE_ACTION_HEATING:
|
case enums::CLIMATE_ACTION_HEATING:
|
||||||
return "CLIMATE_ACTION_HEATING";
|
return "CLIMATE_ACTION_HEATING";
|
||||||
|
case enums::CLIMATE_ACTION_IDLE:
|
||||||
|
return "CLIMATE_ACTION_IDLE";
|
||||||
|
case enums::CLIMATE_ACTION_DRYING:
|
||||||
|
return "CLIMATE_ACTION_DRYING";
|
||||||
|
case enums::CLIMATE_ACTION_FAN:
|
||||||
|
return "CLIMATE_ACTION_FAN";
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
@ -710,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;
|
||||||
}
|
}
|
||||||
|
@ -749,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];
|
||||||
|
@ -777,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) {
|
||||||
|
@ -793,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;
|
||||||
}
|
}
|
||||||
|
@ -812,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];
|
||||||
|
@ -832,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) {
|
||||||
|
@ -860,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;
|
||||||
}
|
}
|
||||||
|
@ -882,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];
|
||||||
|
@ -914,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) {
|
||||||
|
@ -2458,6 +2554,14 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
|
||||||
this->supports_action = value.as_bool();
|
this->supports_action = value.as_bool();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 13: {
|
||||||
|
this->supported_fan_modes.push_back(value.as_enum<enums::ClimateFanMode>());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 14: {
|
||||||
|
this->supported_swing_modes.push_back(value.as_enum<enums::ClimateSwingMode>());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2517,6 +2621,12 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_float(10, this->visual_temperature_step);
|
buffer.encode_float(10, this->visual_temperature_step);
|
||||||
buffer.encode_bool(11, this->supports_away);
|
buffer.encode_bool(11, this->supports_away);
|
||||||
buffer.encode_bool(12, this->supports_action);
|
buffer.encode_bool(12, this->supports_action);
|
||||||
|
for (auto &it : this->supported_fan_modes) {
|
||||||
|
buffer.encode_enum<enums::ClimateFanMode>(13, it, true);
|
||||||
|
}
|
||||||
|
for (auto &it : this->supported_swing_modes) {
|
||||||
|
buffer.encode_enum<enums::ClimateSwingMode>(14, it, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
|
@ -2574,6 +2684,18 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||||
out.append(" supports_action: ");
|
out.append(" supports_action: ");
|
||||||
out.append(YESNO(this->supports_action));
|
out.append(YESNO(this->supports_action));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
for (const auto &it : this->supported_fan_modes) {
|
||||||
|
out.append(" supported_fan_modes: ");
|
||||||
|
out.append(proto_enum_to_string<enums::ClimateFanMode>(it));
|
||||||
|
out.append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &it : this->supported_swing_modes) {
|
||||||
|
out.append(" supported_swing_modes: ");
|
||||||
|
out.append(proto_enum_to_string<enums::ClimateSwingMode>(it));
|
||||||
|
out.append("\n");
|
||||||
|
}
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
@ -2590,6 +2712,14 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
this->action = value.as_enum<enums::ClimateAction>();
|
this->action = value.as_enum<enums::ClimateAction>();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 9: {
|
||||||
|
this->fan_mode = value.as_enum<enums::ClimateFanMode>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 10: {
|
||||||
|
this->swing_mode = value.as_enum<enums::ClimateSwingMode>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2629,6 +2759,8 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_float(6, this->target_temperature_high);
|
buffer.encode_float(6, this->target_temperature_high);
|
||||||
buffer.encode_bool(7, this->away);
|
buffer.encode_bool(7, this->away);
|
||||||
buffer.encode_enum<enums::ClimateAction>(8, this->action);
|
buffer.encode_enum<enums::ClimateAction>(8, this->action);
|
||||||
|
buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode);
|
||||||
|
buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode);
|
||||||
}
|
}
|
||||||
void ClimateStateResponse::dump_to(std::string &out) const {
|
void ClimateStateResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
|
@ -2669,6 +2801,14 @@ void ClimateStateResponse::dump_to(std::string &out) const {
|
||||||
out.append(" action: ");
|
out.append(" action: ");
|
||||||
out.append(proto_enum_to_string<enums::ClimateAction>(this->action));
|
out.append(proto_enum_to_string<enums::ClimateAction>(this->action));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" fan_mode: ");
|
||||||
|
out.append(proto_enum_to_string<enums::ClimateFanMode>(this->fan_mode));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" swing_mode: ");
|
||||||
|
out.append(proto_enum_to_string<enums::ClimateSwingMode>(this->swing_mode));
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
@ -2701,6 +2841,22 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
|
||||||
this->away = value.as_bool();
|
this->away = value.as_bool();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 12: {
|
||||||
|
this->has_fan_mode = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 13: {
|
||||||
|
this->fan_mode = value.as_enum<enums::ClimateFanMode>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 14: {
|
||||||
|
this->has_swing_mode = value.as_bool();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 15: {
|
||||||
|
this->swing_mode = value.as_enum<enums::ClimateSwingMode>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2739,6 +2895,10 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||||
buffer.encode_float(9, this->target_temperature_high);
|
buffer.encode_float(9, this->target_temperature_high);
|
||||||
buffer.encode_bool(10, this->has_away);
|
buffer.encode_bool(10, this->has_away);
|
||||||
buffer.encode_bool(11, this->away);
|
buffer.encode_bool(11, this->away);
|
||||||
|
buffer.encode_bool(12, this->has_fan_mode);
|
||||||
|
buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode);
|
||||||
|
buffer.encode_bool(14, this->has_swing_mode);
|
||||||
|
buffer.encode_enum<enums::ClimateSwingMode>(15, this->swing_mode);
|
||||||
}
|
}
|
||||||
void ClimateCommandRequest::dump_to(std::string &out) const {
|
void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
|
@ -2790,6 +2950,22 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||||
out.append(" away: ");
|
out.append(" away: ");
|
||||||
out.append(YESNO(this->away));
|
out.append(YESNO(this->away));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" has_fan_mode: ");
|
||||||
|
out.append(YESNO(this->has_fan_mode));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" fan_mode: ");
|
||||||
|
out.append(proto_enum_to_string<enums::ClimateFanMode>(this->fan_mode));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" has_swing_mode: ");
|
||||||
|
out.append(YESNO(this->has_swing_mode));
|
||||||
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" swing_mode: ");
|
||||||
|
out.append(proto_enum_to_string<enums::ClimateSwingMode>(this->swing_mode));
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// This file was automatically generated with a tool.
|
||||||
|
// See scripts/api_protobuf/api_protobuf.py
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "proto.h"
|
#include "proto.h"
|
||||||
|
@ -26,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,
|
||||||
|
@ -50,11 +56,33 @@ enum ClimateMode : uint32_t {
|
||||||
CLIMATE_MODE_AUTO = 1,
|
CLIMATE_MODE_AUTO = 1,
|
||||||
CLIMATE_MODE_COOL = 2,
|
CLIMATE_MODE_COOL = 2,
|
||||||
CLIMATE_MODE_HEAT = 3,
|
CLIMATE_MODE_HEAT = 3,
|
||||||
|
CLIMATE_MODE_FAN_ONLY = 4,
|
||||||
|
CLIMATE_MODE_DRY = 5,
|
||||||
|
};
|
||||||
|
enum ClimateFanMode : uint32_t {
|
||||||
|
CLIMATE_FAN_ON = 0,
|
||||||
|
CLIMATE_FAN_OFF = 1,
|
||||||
|
CLIMATE_FAN_AUTO = 2,
|
||||||
|
CLIMATE_FAN_LOW = 3,
|
||||||
|
CLIMATE_FAN_MEDIUM = 4,
|
||||||
|
CLIMATE_FAN_HIGH = 5,
|
||||||
|
CLIMATE_FAN_MIDDLE = 6,
|
||||||
|
CLIMATE_FAN_FOCUS = 7,
|
||||||
|
CLIMATE_FAN_DIFFUSE = 8,
|
||||||
|
};
|
||||||
|
enum ClimateSwingMode : uint32_t {
|
||||||
|
CLIMATE_SWING_OFF = 0,
|
||||||
|
CLIMATE_SWING_BOTH = 1,
|
||||||
|
CLIMATE_SWING_VERTICAL = 2,
|
||||||
|
CLIMATE_SWINT_HORIZONTAL = 3,
|
||||||
};
|
};
|
||||||
enum ClimateAction : uint32_t {
|
enum ClimateAction : uint32_t {
|
||||||
CLIMATE_ACTION_OFF = 0,
|
CLIMATE_ACTION_OFF = 0,
|
||||||
CLIMATE_ACTION_COOLING = 2,
|
CLIMATE_ACTION_COOLING = 2,
|
||||||
CLIMATE_ACTION_HEATING = 3,
|
CLIMATE_ACTION_HEATING = 3,
|
||||||
|
CLIMATE_ACTION_IDLE = 4,
|
||||||
|
CLIMATE_ACTION_DRYING = 5,
|
||||||
|
CLIMATE_ACTION_FAN = 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace enums
|
} // namespace enums
|
||||||
|
@ -255,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;
|
||||||
|
|
||||||
|
@ -269,6 +298,7 @@ class FanStateResponse : public ProtoMessage {
|
||||||
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;
|
||||||
|
|
||||||
|
@ -285,6 +315,8 @@ class FanCommandRequest : public ProtoMessage {
|
||||||
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;
|
||||||
|
|
||||||
|
@ -655,6 +687,8 @@ class ListEntitiesClimateResponse : public ProtoMessage {
|
||||||
float visual_temperature_step{0.0f}; // NOLINT
|
float visual_temperature_step{0.0f}; // NOLINT
|
||||||
bool supports_away{false}; // NOLINT
|
bool supports_away{false}; // NOLINT
|
||||||
bool supports_action{false}; // NOLINT
|
bool supports_action{false}; // NOLINT
|
||||||
|
std::vector<enums::ClimateFanMode> supported_fan_modes{}; // NOLINT
|
||||||
|
std::vector<enums::ClimateSwingMode> supported_swing_modes{}; // 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;
|
||||||
|
|
||||||
|
@ -673,6 +707,8 @@ class ClimateStateResponse : public ProtoMessage {
|
||||||
float target_temperature_high{0.0f}; // NOLINT
|
float target_temperature_high{0.0f}; // NOLINT
|
||||||
bool away{false}; // NOLINT
|
bool away{false}; // NOLINT
|
||||||
enums::ClimateAction action{}; // NOLINT
|
enums::ClimateAction action{}; // NOLINT
|
||||||
|
enums::ClimateFanMode fan_mode{}; // NOLINT
|
||||||
|
enums::ClimateSwingMode swing_mode{}; // 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;
|
||||||
|
|
||||||
|
@ -693,6 +729,10 @@ class ClimateCommandRequest : public ProtoMessage {
|
||||||
float target_temperature_high{0.0f}; // NOLINT
|
float target_temperature_high{0.0f}; // NOLINT
|
||||||
bool has_away{false}; // NOLINT
|
bool has_away{false}; // NOLINT
|
||||||
bool away{false}; // NOLINT
|
bool away{false}; // NOLINT
|
||||||
|
bool has_fan_mode{false}; // NOLINT
|
||||||
|
enums::ClimateFanMode fan_mode{}; // NOLINT
|
||||||
|
bool has_swing_mode{false}; // NOLINT
|
||||||
|
enums::ClimateSwingMode swing_mode{}; // 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;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// This file was automatically generated with a tool.
|
||||||
|
// See scripts/api_protobuf/api_protobuf.py
|
||||||
#include "api_pb2_service.h"
|
#include "api_pb2_service.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
@ -8,69 +10,57 @@ static const char *TAG = "api.service";
|
||||||
|
|
||||||
bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) {
|
bool APIServerConnectionBase::send_hello_response(const HelloResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_hello_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<HelloResponse>(msg, 2);
|
return this->send_message_<HelloResponse>(msg, 2);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) {
|
bool APIServerConnectionBase::send_connect_response(const ConnectResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_connect_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<ConnectResponse>(msg, 4);
|
return this->send_message_<ConnectResponse>(msg, 4);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) {
|
bool APIServerConnectionBase::send_disconnect_request(const DisconnectRequest &msg) {
|
||||||
ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_disconnect_request: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<DisconnectRequest>(msg, 5);
|
return this->send_message_<DisconnectRequest>(msg, 5);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) {
|
bool APIServerConnectionBase::send_disconnect_response(const DisconnectResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_disconnect_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<DisconnectResponse>(msg, 6);
|
return this->send_message_<DisconnectResponse>(msg, 6);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) {
|
bool APIServerConnectionBase::send_ping_request(const PingRequest &msg) {
|
||||||
ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_ping_request: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<PingRequest>(msg, 7);
|
return this->send_message_<PingRequest>(msg, 7);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) {
|
bool APIServerConnectionBase::send_ping_response(const PingResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_ping_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<PingResponse>(msg, 8);
|
return this->send_message_<PingResponse>(msg, 8);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) {
|
bool APIServerConnectionBase::send_device_info_response(const DeviceInfoResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_device_info_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<DeviceInfoResponse>(msg, 10);
|
return this->send_message_<DeviceInfoResponse>(msg, 10);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_done_response(const ListEntitiesDoneResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_done_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<ListEntitiesDoneResponse>(msg, 19);
|
return this->send_message_<ListEntitiesDoneResponse>(msg, 19);
|
||||||
}
|
}
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
bool APIServerConnectionBase::send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_binary_sensor_response(const ListEntitiesBinarySensorResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_binary_sensor_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_binary_sensor_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesBinarySensorResponse>(msg, 12);
|
return this->send_message_<ListEntitiesBinarySensorResponse>(msg, 12);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) {
|
bool APIServerConnectionBase::send_binary_sensor_state_response(const BinarySensorStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_binary_sensor_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<BinarySensorStateResponse>(msg, 21);
|
return this->send_message_<BinarySensorStateResponse>(msg, 21);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_COVER
|
#ifdef USE_COVER
|
||||||
bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_cover_response(const ListEntitiesCoverResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_cover_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesCoverResponse>(msg, 13);
|
return this->send_message_<ListEntitiesCoverResponse>(msg, 13);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_COVER
|
#ifdef USE_COVER
|
||||||
bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) {
|
bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_cover_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<CoverStateResponse>(msg, 22);
|
return this->send_message_<CoverStateResponse>(msg, 22);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -79,14 +69,12 @@ bool APIServerConnectionBase::send_cover_state_response(const CoverStateResponse
|
||||||
#ifdef USE_FAN
|
#ifdef USE_FAN
|
||||||
bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_fan_response(const ListEntitiesFanResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_fan_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesFanResponse>(msg, 14);
|
return this->send_message_<ListEntitiesFanResponse>(msg, 14);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_FAN
|
#ifdef USE_FAN
|
||||||
bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) {
|
bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_fan_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<FanStateResponse>(msg, 23);
|
return this->send_message_<FanStateResponse>(msg, 23);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -95,14 +83,12 @@ bool APIServerConnectionBase::send_fan_state_response(const FanStateResponse &ms
|
||||||
#ifdef USE_LIGHT
|
#ifdef USE_LIGHT
|
||||||
bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_light_response(const ListEntitiesLightResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_light_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesLightResponse>(msg, 15);
|
return this->send_message_<ListEntitiesLightResponse>(msg, 15);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_LIGHT
|
#ifdef USE_LIGHT
|
||||||
bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) {
|
bool APIServerConnectionBase::send_light_state_response(const LightStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_light_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<LightStateResponse>(msg, 24);
|
return this->send_message_<LightStateResponse>(msg, 24);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -111,28 +97,24 @@ bool APIServerConnectionBase::send_light_state_response(const LightStateResponse
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_sensor_response(const ListEntitiesSensorResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_sensor_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesSensorResponse>(msg, 16);
|
return this->send_message_<ListEntitiesSensorResponse>(msg, 16);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) {
|
bool APIServerConnectionBase::send_sensor_state_response(const SensorStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_sensor_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<SensorStateResponse>(msg, 25);
|
return this->send_message_<SensorStateResponse>(msg, 25);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_switch_response(const ListEntitiesSwitchResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_switch_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesSwitchResponse>(msg, 17);
|
return this->send_message_<ListEntitiesSwitchResponse>(msg, 17);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) {
|
bool APIServerConnectionBase::send_switch_state_response(const SwitchStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_switch_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<SwitchStateResponse>(msg, 26);
|
return this->send_message_<SwitchStateResponse>(msg, 26);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -141,58 +123,48 @@ bool APIServerConnectionBase::send_switch_state_response(const SwitchStateRespon
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
bool APIServerConnectionBase::send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_text_sensor_response(const ListEntitiesTextSensorResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_text_sensor_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_text_sensor_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesTextSensorResponse>(msg, 18);
|
return this->send_message_<ListEntitiesTextSensorResponse>(msg, 18);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) {
|
bool APIServerConnectionBase::send_text_sensor_state_response(const TextSensorStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_text_sensor_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<TextSensorStateResponse>(msg, 27);
|
return this->send_message_<TextSensorStateResponse>(msg, 27);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
|
bool APIServerConnectionBase::send_subscribe_logs_response(const SubscribeLogsResponse &msg) {
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<SubscribeLogsResponse>(msg, 29);
|
return this->send_message_<SubscribeLogsResponse>(msg, 29);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
|
bool APIServerConnectionBase::send_homeassistant_service_response(const HomeassistantServiceResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_homeassistant_service_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<HomeassistantServiceResponse>(msg, 35);
|
return this->send_message_<HomeassistantServiceResponse>(msg, 35);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_subscribe_home_assistant_state_response(
|
bool APIServerConnectionBase::send_subscribe_home_assistant_state_response(
|
||||||
const SubscribeHomeAssistantStateResponse &msg) {
|
const SubscribeHomeAssistantStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_subscribe_home_assistant_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<SubscribeHomeAssistantStateResponse>(msg, 39);
|
return this->send_message_<SubscribeHomeAssistantStateResponse>(msg, 39);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) {
|
bool APIServerConnectionBase::send_get_time_request(const GetTimeRequest &msg) {
|
||||||
ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_get_time_request: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<GetTimeRequest>(msg, 36);
|
return this->send_message_<GetTimeRequest>(msg, 36);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) {
|
bool APIServerConnectionBase::send_get_time_response(const GetTimeResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_get_time_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<GetTimeResponse>(msg, 37);
|
return this->send_message_<GetTimeResponse>(msg, 37);
|
||||||
}
|
}
|
||||||
bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_services_response(const ListEntitiesServicesResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_services_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesServicesResponse>(msg, 41);
|
return this->send_message_<ListEntitiesServicesResponse>(msg, 41);
|
||||||
}
|
}
|
||||||
#ifdef USE_ESP32_CAMERA
|
#ifdef USE_ESP32_CAMERA
|
||||||
bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_camera_response(const ListEntitiesCameraResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_camera_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesCameraResponse>(msg, 43);
|
return this->send_message_<ListEntitiesCameraResponse>(msg, 43);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ESP32_CAMERA
|
#ifdef USE_ESP32_CAMERA
|
||||||
bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) {
|
bool APIServerConnectionBase::send_camera_image_response(const CameraImageResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_camera_image_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<CameraImageResponse>(msg, 44);
|
return this->send_message_<CameraImageResponse>(msg, 44);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -201,14 +173,12 @@ bool APIServerConnectionBase::send_camera_image_response(const CameraImageRespon
|
||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) {
|
bool APIServerConnectionBase::send_list_entities_climate_response(const ListEntitiesClimateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_list_entities_climate_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(false);
|
|
||||||
return this->send_message_<ListEntitiesClimateResponse>(msg, 46);
|
return this->send_message_<ListEntitiesClimateResponse>(msg, 46);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) {
|
bool APIServerConnectionBase::send_climate_state_response(const ClimateStateResponse &msg) {
|
||||||
ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str());
|
ESP_LOGVV(TAG, "send_climate_state_response: %s", msg.dump().c_str());
|
||||||
this->set_nodelay(true);
|
|
||||||
return this->send_message_<ClimateStateResponse>(msg, 47);
|
return this->send_message_<ClimateStateResponse>(msg, 47);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// This file was automatically generated with a tool.
|
||||||
|
// See scripts/api_protobuf/api_protobuf.py
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
|
|
|
@ -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...);
|
||||||
|
|
|
@ -266,7 +266,6 @@ class ProtoService {
|
||||||
virtual ProtoWriteBuffer create_buffer() = 0;
|
virtual ProtoWriteBuffer create_buffer() = 0;
|
||||||
virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0;
|
virtual bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) = 0;
|
||||||
virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
|
virtual bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
|
||||||
virtual void set_nodelay(bool nodelay) = 0;
|
|
||||||
|
|
||||||
template<class C> bool send_message_(const C &msg, uint32_t message_type) {
|
template<class C> bool send_message_(const C &msg, uint32_t message_type) {
|
||||||
auto buffer = this->create_buffer();
|
auto buffer = this->create_buffer();
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -40,9 +40,30 @@ void ATM90E32Component::update() {
|
||||||
if (this->phase_[2].power_sensor_ != nullptr) {
|
if (this->phase_[2].power_sensor_ != nullptr) {
|
||||||
this->phase_[2].power_sensor_->publish_state(this->get_active_power_c_());
|
this->phase_[2].power_sensor_->publish_state(this->get_active_power_c_());
|
||||||
}
|
}
|
||||||
|
if (this->phase_[0].reactive_power_sensor_ != nullptr) {
|
||||||
|
this->phase_[0].reactive_power_sensor_->publish_state(this->get_reactive_power_a_());
|
||||||
|
}
|
||||||
|
if (this->phase_[1].reactive_power_sensor_ != nullptr) {
|
||||||
|
this->phase_[1].reactive_power_sensor_->publish_state(this->get_reactive_power_b_());
|
||||||
|
}
|
||||||
|
if (this->phase_[2].reactive_power_sensor_ != nullptr) {
|
||||||
|
this->phase_[2].reactive_power_sensor_->publish_state(this->get_reactive_power_c_());
|
||||||
|
}
|
||||||
|
if (this->phase_[0].power_factor_sensor_ != nullptr) {
|
||||||
|
this->phase_[0].power_factor_sensor_->publish_state(this->get_power_factor_a_());
|
||||||
|
}
|
||||||
|
if (this->phase_[1].power_factor_sensor_ != nullptr) {
|
||||||
|
this->phase_[1].power_factor_sensor_->publish_state(this->get_power_factor_b_());
|
||||||
|
}
|
||||||
|
if (this->phase_[2].power_factor_sensor_ != nullptr) {
|
||||||
|
this->phase_[2].power_factor_sensor_->publish_state(this->get_power_factor_c_());
|
||||||
|
}
|
||||||
if (this->freq_sensor_ != nullptr) {
|
if (this->freq_sensor_ != nullptr) {
|
||||||
this->freq_sensor_->publish_state(this->get_frequency_());
|
this->freq_sensor_->publish_state(this->get_frequency_());
|
||||||
}
|
}
|
||||||
|
if (this->chip_temperature_sensor_ != nullptr) {
|
||||||
|
this->chip_temperature_sensor_->publish_state(this->get_chip_temperature_());
|
||||||
|
}
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,9 +71,14 @@ void ATM90E32Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component...");
|
ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component...");
|
||||||
this->spi_setup();
|
this->spi_setup();
|
||||||
|
|
||||||
uint16_t mmode0 = 0x185;
|
uint16_t mmode0 = 0x87; // 3P4W 50Hz
|
||||||
if (line_freq_ == 60) {
|
if (line_freq_ == 60) {
|
||||||
mmode0 |= 1 << 12;
|
mmode0 |= 1 << 12; // sets 12th bit to 1, 60Hz
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_phases_ == 2) {
|
||||||
|
mmode0 |= 1 << 8; // sets 8th bit to 1, 3P3W
|
||||||
|
mmode0 |= 0 << 1; // sets 1st bit to 0, phase b is not counted into the all-phase sum energy/power (P/Q/S)
|
||||||
}
|
}
|
||||||
|
|
||||||
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
|
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
|
||||||
|
@ -63,13 +89,15 @@ void ATM90E32Component::setup() {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this->write16_(ATM90E32_REGISTER_PLCONSTH, 0x0861); // PL Constant MSB (default) = 140625000
|
||||||
this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0x0A55); // ZX2, ZX1, ZX0 pin config
|
this->write16_(ATM90E32_REGISTER_PLCONSTL, 0xC468); // PL Constant LSB (default)
|
||||||
|
this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654); // ZX2, ZX1, ZX0 pin config
|
||||||
this->write16_(ATM90E32_REGISTER_MMODE0, mmode0); // Mode Config (frequency set in main program)
|
this->write16_(ATM90E32_REGISTER_MMODE0, mmode0); // Mode Config (frequency set in main program)
|
||||||
this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_); // PGA Gain Configuration for Current Channels
|
this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_); // PGA Gain Configuration for Current Channels
|
||||||
this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x0AFC); // Active Startup Power Threshold = 50%
|
this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x1D4C); // All Active Startup Power Threshold - 0.02A/0.00032 = 7500
|
||||||
this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x0AEC); // Reactive Startup Power Threshold = 50%
|
this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
|
||||||
this->write16_(ATM90E32_REGISTER_PPHASETH, 0x00BC); // Active Phase Threshold = 10%
|
this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750
|
||||||
|
this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10%
|
||||||
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[0].volt_gain_); // A Voltage rms gain
|
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[0].volt_gain_); // A Voltage rms gain
|
||||||
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[0].ct_gain_); // A line current gain
|
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[0].ct_gain_); // A line current gain
|
||||||
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[1].volt_gain_); // B Voltage rms gain
|
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[1].volt_gain_); // B Voltage rms gain
|
||||||
|
@ -89,13 +117,20 @@ void ATM90E32Component::dump_config() {
|
||||||
LOG_SENSOR(" ", "Voltage A", this->phase_[0].voltage_sensor_);
|
LOG_SENSOR(" ", "Voltage A", this->phase_[0].voltage_sensor_);
|
||||||
LOG_SENSOR(" ", "Current A", this->phase_[0].current_sensor_);
|
LOG_SENSOR(" ", "Current A", this->phase_[0].current_sensor_);
|
||||||
LOG_SENSOR(" ", "Power A", this->phase_[0].power_sensor_);
|
LOG_SENSOR(" ", "Power A", this->phase_[0].power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Reactive Power A", this->phase_[0].reactive_power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "PF A", this->phase_[0].power_factor_sensor_);
|
||||||
LOG_SENSOR(" ", "Voltage B", this->phase_[1].voltage_sensor_);
|
LOG_SENSOR(" ", "Voltage B", this->phase_[1].voltage_sensor_);
|
||||||
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_);
|
LOG_SENSOR(" ", "Current B", this->phase_[1].current_sensor_);
|
||||||
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_);
|
LOG_SENSOR(" ", "Power B", this->phase_[1].power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Reactive Power B", this->phase_[1].reactive_power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "PF B", this->phase_[1].power_factor_sensor_);
|
||||||
LOG_SENSOR(" ", "Voltage C", this->phase_[2].voltage_sensor_);
|
LOG_SENSOR(" ", "Voltage C", this->phase_[2].voltage_sensor_);
|
||||||
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_);
|
LOG_SENSOR(" ", "Current C", this->phase_[2].current_sensor_);
|
||||||
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_);
|
LOG_SENSOR(" ", "Power C", this->phase_[2].power_sensor_);
|
||||||
LOG_SENSOR(" ", "Frequency", this->freq_sensor_)
|
LOG_SENSOR(" ", "Reactive Power C", this->phase_[2].reactive_power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "PF C", this->phase_[2].power_factor_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
|
||||||
}
|
}
|
||||||
float ATM90E32Component::get_setup_priority() const { return setup_priority::DATA; }
|
float ATM90E32Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
@ -180,9 +215,37 @@ float ATM90E32Component::get_active_power_c_() {
|
||||||
int val = this->read32_(ATM90E32_REGISTER_PMEANC, ATM90E32_REGISTER_PMEANCLSB);
|
int val = this->read32_(ATM90E32_REGISTER_PMEANC, ATM90E32_REGISTER_PMEANCLSB);
|
||||||
return val * 0.00032f;
|
return val * 0.00032f;
|
||||||
}
|
}
|
||||||
|
float ATM90E32Component::get_reactive_power_a_() {
|
||||||
|
int val = this->read32_(ATM90E32_REGISTER_QMEANA, ATM90E32_REGISTER_QMEANALSB);
|
||||||
|
return val * 0.00032f;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_reactive_power_b_() {
|
||||||
|
int val = this->read32_(ATM90E32_REGISTER_QMEANB, ATM90E32_REGISTER_QMEANBLSB);
|
||||||
|
return val * 0.00032f;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_reactive_power_c_() {
|
||||||
|
int val = this->read32_(ATM90E32_REGISTER_QMEANC, ATM90E32_REGISTER_QMEANCLSB);
|
||||||
|
return val * 0.00032f;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_power_factor_a_() {
|
||||||
|
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANA);
|
||||||
|
return (float) pf / 1000;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_power_factor_b_() {
|
||||||
|
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANB);
|
||||||
|
return (float) pf / 1000;
|
||||||
|
}
|
||||||
|
float ATM90E32Component::get_power_factor_c_() {
|
||||||
|
int16_t pf = this->read16_(ATM90E32_REGISTER_PFMEANC);
|
||||||
|
return (float) pf / 1000;
|
||||||
|
}
|
||||||
float ATM90E32Component::get_frequency_() {
|
float ATM90E32Component::get_frequency_() {
|
||||||
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
|
||||||
return (float) freq / 100;
|
return (float) freq / 100;
|
||||||
}
|
}
|
||||||
|
float ATM90E32Component::get_chip_temperature_() {
|
||||||
|
uint16_t ctemp = this->read16_(ATM90E32_REGISTER_TEMP);
|
||||||
|
return (float) ctemp;
|
||||||
|
}
|
||||||
} // namespace atm90e32
|
} // namespace atm90e32
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -19,11 +19,17 @@ class ATM90E32Component : public PollingComponent,
|
||||||
void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; }
|
void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; }
|
||||||
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
|
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
|
||||||
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
|
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
|
||||||
|
void set_reactive_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].reactive_power_sensor_ = obj; }
|
||||||
|
void set_power_factor_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_factor_sensor_ = obj; }
|
||||||
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; }
|
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].volt_gain_ = gain; }
|
||||||
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
|
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
|
||||||
|
|
||||||
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
|
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
|
||||||
|
void set_chip_temperature_sensor(sensor::Sensor *chip_temperature_sensor) {
|
||||||
|
chip_temperature_sensor_ = chip_temperature_sensor;
|
||||||
|
}
|
||||||
void set_line_freq(int freq) { line_freq_ = freq; }
|
void set_line_freq(int freq) { line_freq_ = freq; }
|
||||||
|
void set_current_phases(int phases) { current_phases_ = phases; }
|
||||||
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
|
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -40,18 +46,29 @@ class ATM90E32Component : public PollingComponent,
|
||||||
float get_active_power_a_();
|
float get_active_power_a_();
|
||||||
float get_active_power_b_();
|
float get_active_power_b_();
|
||||||
float get_active_power_c_();
|
float get_active_power_c_();
|
||||||
|
float get_reactive_power_a_();
|
||||||
|
float get_reactive_power_b_();
|
||||||
|
float get_reactive_power_c_();
|
||||||
|
float get_power_factor_a_();
|
||||||
|
float get_power_factor_b_();
|
||||||
|
float get_power_factor_c_();
|
||||||
float get_frequency_();
|
float get_frequency_();
|
||||||
|
float get_chip_temperature_();
|
||||||
|
|
||||||
struct ATM90E32Phase {
|
struct ATM90E32Phase {
|
||||||
uint16_t volt_gain_{41820};
|
uint16_t volt_gain_{7305};
|
||||||
uint16_t ct_gain_{25498};
|
uint16_t ct_gain_{27961};
|
||||||
sensor::Sensor *voltage_sensor_{nullptr};
|
sensor::Sensor *voltage_sensor_{nullptr};
|
||||||
sensor::Sensor *current_sensor_{nullptr};
|
sensor::Sensor *current_sensor_{nullptr};
|
||||||
sensor::Sensor *power_sensor_{nullptr};
|
sensor::Sensor *power_sensor_{nullptr};
|
||||||
|
sensor::Sensor *reactive_power_sensor_{nullptr};
|
||||||
|
sensor::Sensor *power_factor_sensor_{nullptr};
|
||||||
} phase_[3];
|
} phase_[3];
|
||||||
sensor::Sensor *freq_sensor_{nullptr};
|
sensor::Sensor *freq_sensor_{nullptr};
|
||||||
|
sensor::Sensor *chip_temperature_sensor_{nullptr};
|
||||||
uint16_t pga_gain_{0x15};
|
uint16_t pga_gain_{0x15};
|
||||||
int line_freq_{60};
|
int line_freq_{60};
|
||||||
|
int current_phases_{3};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace atm90e32
|
} // namespace atm90e32
|
||||||
|
|
|
@ -234,12 +234,12 @@ static const uint16_t ATM90E32_REGISTER_IRMSBLSB = 0xEE; // Lower Word (B RMS
|
||||||
static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF; // Lower Word (C RMS Current)
|
static const uint16_t ATM90E32_REGISTER_IRMSCLSB = 0xEF; // Lower Word (C RMS Current)
|
||||||
|
|
||||||
/* THD, FREQUENCY, ANGLE & TEMPTEMP REGISTERS*/
|
/* THD, FREQUENCY, ANGLE & TEMPTEMP REGISTERS*/
|
||||||
static const uint16_t ATM90E32_REGISTER_THDNUA = 0xF1; // A Voltage THD+N
|
static const uint16_t ATM90E32_REGISTER_UPEAKA = 0xF1; // A Voltage Peak
|
||||||
static const uint16_t ATM90E32_REGISTER_THDNUB = 0xF2; // B Voltage THD+N
|
static const uint16_t ATM90E32_REGISTER_UPEAKB = 0xF2; // B Voltage Peak
|
||||||
static const uint16_t ATM90E32_REGISTER_THDNUC = 0xF3; // C Voltage THD+N
|
static const uint16_t ATM90E32_REGISTER_UPEAKC = 0xF3; // C Voltage Peak
|
||||||
static const uint16_t ATM90E32_REGISTER_THDNIA = 0xF5; // A Current THD+N
|
static const uint16_t ATM90E32_REGISTER_IPEAKA = 0xF5; // A Current Peak
|
||||||
static const uint16_t ATM90E32_REGISTER_THDNIB = 0xF6; // B Current THD+N
|
static const uint16_t ATM90E32_REGISTER_IPEAKB = 0xF6; // B Current Peak
|
||||||
static const uint16_t ATM90E32_REGISTER_THDNIC = 0xF7; // C Current THD+N
|
static const uint16_t ATM90E32_REGISTER_IPEAKC = 0xF7; // C Current Peak
|
||||||
static const uint16_t ATM90E32_REGISTER_FREQ = 0xF8; // Frequency
|
static const uint16_t ATM90E32_REGISTER_FREQ = 0xF8; // Frequency
|
||||||
static const uint16_t ATM90E32_REGISTER_PANGLEA = 0xF9; // A Mean Phase Angle
|
static const uint16_t ATM90E32_REGISTER_PANGLEA = 0xF9; // A Mean Phase Angle
|
||||||
static const uint16_t ATM90E32_REGISTER_PANGLEB = 0xFA; // B Mean Phase Angle
|
static const uint16_t ATM90E32_REGISTER_PANGLEB = 0xFA; // B Mean Phase Angle
|
||||||
|
|
|
@ -2,21 +2,29 @@ import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor, spi
|
from esphome.components import sensor, spi
|
||||||
from esphome.const import \
|
from esphome.const import \
|
||||||
CONF_ID, CONF_VOLTAGE, CONF_CURRENT, CONF_POWER, CONF_FREQUENCY, \
|
CONF_ID, CONF_VOLTAGE, CONF_CURRENT, CONF_POWER, CONF_POWER_FACTOR, CONF_FREQUENCY, \
|
||||||
ICON_FLASH, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, UNIT_HERTZ, ICON_CURRENT_AC
|
ICON_FLASH, ICON_LIGHTBULB, ICON_CURRENT_AC, ICON_THERMOMETER, \
|
||||||
|
UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, UNIT_EMPTY, UNIT_CELSIUS, UNIT_VOLT_AMPS_REACTIVE
|
||||||
|
|
||||||
CONF_PHASE_A = 'phase_a'
|
CONF_PHASE_A = 'phase_a'
|
||||||
CONF_PHASE_B = 'phase_b'
|
CONF_PHASE_B = 'phase_b'
|
||||||
CONF_PHASE_C = 'phase_c'
|
CONF_PHASE_C = 'phase_c'
|
||||||
|
|
||||||
|
CONF_REACTIVE_POWER = 'reactive_power'
|
||||||
CONF_LINE_FREQUENCY = 'line_frequency'
|
CONF_LINE_FREQUENCY = 'line_frequency'
|
||||||
|
CONF_CHIP_TEMPERATURE = 'chip_temperature'
|
||||||
CONF_GAIN_PGA = 'gain_pga'
|
CONF_GAIN_PGA = 'gain_pga'
|
||||||
|
CONF_CURRENT_PHASES = 'current_phases'
|
||||||
CONF_GAIN_VOLTAGE = 'gain_voltage'
|
CONF_GAIN_VOLTAGE = 'gain_voltage'
|
||||||
CONF_GAIN_CT = 'gain_ct'
|
CONF_GAIN_CT = 'gain_ct'
|
||||||
LINE_FREQS = {
|
LINE_FREQS = {
|
||||||
'50HZ': 50,
|
'50HZ': 50,
|
||||||
'60HZ': 60,
|
'60HZ': 60,
|
||||||
}
|
}
|
||||||
|
CURRENT_PHASES = {
|
||||||
|
'2': 2,
|
||||||
|
'3': 3,
|
||||||
|
}
|
||||||
PGA_GAINS = {
|
PGA_GAINS = {
|
||||||
'1X': 0x0,
|
'1X': 0x0,
|
||||||
'2X': 0x15,
|
'2X': 0x15,
|
||||||
|
@ -28,10 +36,13 @@ ATM90E32Component = atm90e32_ns.class_('ATM90E32Component', cg.PollingComponent,
|
||||||
|
|
||||||
ATM90E32_PHASE_SCHEMA = cv.Schema({
|
ATM90E32_PHASE_SCHEMA = cv.Schema({
|
||||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2),
|
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2),
|
||||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
|
cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_CURRENT_AC, 2),
|
||||||
cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2),
|
cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2),
|
||||||
cv.Optional(CONF_GAIN_VOLTAGE, default=41820): cv.uint16_t,
|
cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(UNIT_VOLT_AMPS_REACTIVE,
|
||||||
cv.Optional(CONF_GAIN_CT, default=25498): cv.uint16_t,
|
ICON_LIGHTBULB, 2),
|
||||||
|
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 2),
|
||||||
|
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
|
||||||
|
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
|
||||||
})
|
})
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema({
|
CONFIG_SCHEMA = cv.Schema({
|
||||||
|
@ -40,9 +51,11 @@ CONFIG_SCHEMA = cv.Schema({
|
||||||
cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
|
cv.Optional(CONF_PHASE_B): ATM90E32_PHASE_SCHEMA,
|
||||||
cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
|
cv.Optional(CONF_PHASE_C): ATM90E32_PHASE_SCHEMA,
|
||||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(UNIT_HERTZ, ICON_CURRENT_AC, 1),
|
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(UNIT_HERTZ, ICON_CURRENT_AC, 1),
|
||||||
|
cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1),
|
||||||
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_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):
|
||||||
|
@ -65,8 +78,18 @@ def to_code(config):
|
||||||
if CONF_POWER in conf:
|
if CONF_POWER in conf:
|
||||||
sens = yield sensor.new_sensor(conf[CONF_POWER])
|
sens = yield sensor.new_sensor(conf[CONF_POWER])
|
||||||
cg.add(var.set_power_sensor(i, sens))
|
cg.add(var.set_power_sensor(i, sens))
|
||||||
|
if CONF_REACTIVE_POWER in conf:
|
||||||
|
sens = yield sensor.new_sensor(conf[CONF_REACTIVE_POWER])
|
||||||
|
cg.add(var.set_reactive_power_sensor(i, sens))
|
||||||
|
if CONF_POWER_FACTOR in conf:
|
||||||
|
sens = yield sensor.new_sensor(conf[CONF_POWER_FACTOR])
|
||||||
|
cg.add(var.set_power_factor_sensor(i, sens))
|
||||||
if CONF_FREQUENCY in config:
|
if CONF_FREQUENCY in config:
|
||||||
sens = yield sensor.new_sensor(config[CONF_FREQUENCY])
|
sens = yield sensor.new_sensor(config[CONF_FREQUENCY])
|
||||||
cg.add(var.set_freq_sensor(sens))
|
cg.add(var.set_freq_sensor(sens))
|
||||||
|
if CONF_CHIP_TEMPERATURE in config:
|
||||||
|
sens = yield sensor.new_sensor(config[CONF_CHIP_TEMPERATURE])
|
||||||
|
cg.add(var.set_chip_temperature_sensor(sens))
|
||||||
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
|
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
|
||||||
|
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
|
||||||
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
|
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ['@OttoWinter']
|
|
@ -51,12 +51,15 @@ climate::ClimateTraits BangBangClimate::traits() {
|
||||||
}
|
}
|
||||||
void BangBangClimate::compute_state_() {
|
void BangBangClimate::compute_state_() {
|
||||||
if (this->mode != climate::CLIMATE_MODE_AUTO) {
|
if (this->mode != climate::CLIMATE_MODE_AUTO) {
|
||||||
// in non-auto mode
|
// in non-auto mode, switch directly to appropriate action
|
||||||
|
// - HEAT mode -> HEATING action
|
||||||
|
// - COOL mode -> COOLING action
|
||||||
|
// - OFF mode -> OFF action (not IDLE!)
|
||||||
this->switch_to_action_(static_cast<climate::ClimateAction>(this->mode));
|
this->switch_to_action_(static_cast<climate::ClimateAction>(this->mode));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) {
|
if (isnan(this->current_temperature) || isnan(this->target_temperature_low) || isnan(this->target_temperature_high)) {
|
||||||
// if any control values are nan, go to OFF (idle) mode
|
// if any control parameters are nan, go to OFF action (not IDLE!)
|
||||||
this->switch_to_action_(climate::CLIMATE_ACTION_OFF);
|
this->switch_to_action_(climate::CLIMATE_ACTION_OFF);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -69,18 +72,18 @@ void BangBangClimate::compute_state_() {
|
||||||
if (this->supports_heat_)
|
if (this->supports_heat_)
|
||||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||||
else
|
else
|
||||||
target_action = climate::CLIMATE_ACTION_OFF;
|
target_action = climate::CLIMATE_ACTION_IDLE;
|
||||||
} else if (too_hot) {
|
} else if (too_hot) {
|
||||||
// too hot -> enable cooling if possible, else idle
|
// too hot -> enable cooling if possible, else idle
|
||||||
if (this->supports_cool_)
|
if (this->supports_cool_)
|
||||||
target_action = climate::CLIMATE_ACTION_COOLING;
|
target_action = climate::CLIMATE_ACTION_COOLING;
|
||||||
else
|
else
|
||||||
target_action = climate::CLIMATE_ACTION_OFF;
|
target_action = climate::CLIMATE_ACTION_IDLE;
|
||||||
} else {
|
} else {
|
||||||
// neither too hot nor too cold -> in range
|
// neither too hot nor too cold -> in range
|
||||||
if (this->supports_cool_ && this->supports_heat_) {
|
if (this->supports_cool_ && this->supports_heat_) {
|
||||||
// if supports both ends, go to idle mode
|
// if supports both ends, go to idle action
|
||||||
target_action = climate::CLIMATE_ACTION_OFF;
|
target_action = climate::CLIMATE_ACTION_IDLE;
|
||||||
} else {
|
} else {
|
||||||
// else use current mode and don't change (hysteresis)
|
// else use current mode and don't change (hysteresis)
|
||||||
target_action = this->action;
|
target_action = this->action;
|
||||||
|
@ -94,13 +97,24 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
|
||||||
// already in target mode
|
// already in target mode
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if ((action == climate::CLIMATE_ACTION_OFF && this->action == climate::CLIMATE_ACTION_IDLE) ||
|
||||||
|
(action == climate::CLIMATE_ACTION_IDLE && this->action == climate::CLIMATE_ACTION_OFF)) {
|
||||||
|
// switching from OFF to IDLE or vice-versa
|
||||||
|
// these only have visual difference. OFF means user manually disabled,
|
||||||
|
// IDLE means it's in auto mode but value is in target range.
|
||||||
|
this->action = action;
|
||||||
|
this->publish_state();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case climate::CLIMATE_ACTION_OFF:
|
case climate::CLIMATE_ACTION_OFF:
|
||||||
|
case climate::CLIMATE_ACTION_IDLE:
|
||||||
trig = this->idle_trigger_;
|
trig = this->idle_trigger_;
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_ACTION_COOLING:
|
case climate::CLIMATE_ACTION_COOLING:
|
||||||
|
@ -112,14 +126,12 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
|
||||||
default:
|
default:
|
||||||
trig = nullptr;
|
trig = nullptr;
|
||||||
}
|
}
|
||||||
if (trig != nullptr) {
|
assert(trig != nullptr);
|
||||||
// trig should never be null, but still check so that we don't crash
|
|
||||||
trig->trigger();
|
trig->trigger();
|
||||||
this->action = action;
|
this->action = action;
|
||||||
this->prev_trigger_ = trig;
|
this->prev_trigger_ = trig;
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
void BangBangClimate::change_away_(bool away) {
|
void BangBangClimate::change_away_(bool away) {
|
||||||
if (!away) {
|
if (!away) {
|
||||||
this->target_temperature_low = this->normal_config_.default_temperature_low;
|
this->target_temperature_low = this->normal_config_.default_temperature_low;
|
||||||
|
|
|
@ -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};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,9 @@ from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \
|
||||||
CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \
|
CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \
|
||||||
CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID
|
CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID
|
||||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
from esphome.core import CORE, coroutine, coroutine_with_priority
|
||||||
from esphome.py_compat import string_types
|
|
||||||
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',
|
||||||
|
@ -94,7 +94,7 @@ MULTI_CLICK_TIMING_SCHEMA = cv.Schema({
|
||||||
|
|
||||||
|
|
||||||
def parse_multi_click_timing_str(value):
|
def parse_multi_click_timing_str(value):
|
||||||
if not isinstance(value, string_types):
|
if not isinstance(value, str):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
parts = value.lower().split(' ')
|
parts = value.lower().split(' ')
|
||||||
|
@ -104,10 +104,10 @@ def parse_multi_click_timing_str(value):
|
||||||
try:
|
try:
|
||||||
state = cv.boolean(parts[0])
|
state = cv.boolean(parts[0])
|
||||||
except cv.Invalid:
|
except cv.Invalid:
|
||||||
raise cv.Invalid(u"First word must either be ON or OFF, not {}".format(parts[0]))
|
raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0]))
|
||||||
|
|
||||||
if parts[1] != 'for':
|
if parts[1] != 'for':
|
||||||
raise cv.Invalid(u"Second word must be 'for', got {}".format(parts[1]))
|
raise cv.Invalid("Second word must be 'for', got {}".format(parts[1]))
|
||||||
|
|
||||||
if parts[2] == 'at':
|
if parts[2] == 'at':
|
||||||
if parts[3] == 'least':
|
if parts[3] == 'least':
|
||||||
|
@ -115,12 +115,12 @@ def parse_multi_click_timing_str(value):
|
||||||
elif parts[3] == 'most':
|
elif parts[3] == 'most':
|
||||||
key = CONF_MAX_LENGTH
|
key = CONF_MAX_LENGTH
|
||||||
else:
|
else:
|
||||||
raise cv.Invalid(u"Third word after at must either be 'least' or 'most', got {}"
|
raise cv.Invalid("Third word after at must either be 'least' or 'most', got {}"
|
||||||
u"".format(parts[3]))
|
"".format(parts[3]))
|
||||||
try:
|
try:
|
||||||
length = cv.positive_time_period_milliseconds(parts[4])
|
length = cv.positive_time_period_milliseconds(parts[4])
|
||||||
except cv.Invalid as err:
|
except cv.Invalid as err:
|
||||||
raise cv.Invalid(u"Multi Click Grammar Parsing length failed: {}".format(err))
|
raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {err}")
|
||||||
return {
|
return {
|
||||||
CONF_STATE: state,
|
CONF_STATE: state,
|
||||||
key: str(length)
|
key: str(length)
|
||||||
|
@ -132,12 +132,12 @@ def parse_multi_click_timing_str(value):
|
||||||
try:
|
try:
|
||||||
min_length = cv.positive_time_period_milliseconds(parts[2])
|
min_length = cv.positive_time_period_milliseconds(parts[2])
|
||||||
except cv.Invalid as err:
|
except cv.Invalid as err:
|
||||||
raise cv.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err))
|
raise cv.Invalid(f"Multi Click Grammar Parsing minimum length failed: {err}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
max_length = cv.positive_time_period_milliseconds(parts[4])
|
max_length = cv.positive_time_period_milliseconds(parts[4])
|
||||||
except cv.Invalid as err:
|
except cv.Invalid as err:
|
||||||
raise cv.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err))
|
raise cv.Invalid(f"Multi Click Grammar Parsing minimum length failed: {err}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
CONF_STATE: state,
|
CONF_STATE: state,
|
||||||
|
@ -225,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);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
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 binary_sensor, esp32_ble_tracker
|
from esphome.components import binary_sensor, esp32_ble_tracker
|
||||||
from esphome.const import CONF_MAC_ADDRESS, CONF_ID
|
from esphome.const import CONF_MAC_ADDRESS, CONF_SERVICE_UUID, CONF_ID
|
||||||
|
|
||||||
DEPENDENCIES = ['esp32_ble_tracker']
|
DEPENDENCIES = ['esp32_ble_tracker']
|
||||||
|
|
||||||
|
@ -9,10 +9,12 @@ ble_presence_ns = cg.esphome_ns.namespace('ble_presence')
|
||||||
BLEPresenceDevice = ble_presence_ns.class_('BLEPresenceDevice', binary_sensor.BinarySensor,
|
BLEPresenceDevice = ble_presence_ns.class_('BLEPresenceDevice', binary_sensor.BinarySensor,
|
||||||
cg.Component, esp32_ble_tracker.ESPBTDeviceListener)
|
cg.Component, esp32_ble_tracker.ESPBTDeviceListener)
|
||||||
|
|
||||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
CONFIG_SCHEMA = cv.All(binary_sensor.BINARY_SENSOR_SCHEMA.extend({
|
||||||
cv.GenerateID(): cv.declare_id(BLEPresenceDevice),
|
cv.GenerateID(): cv.declare_id(BLEPresenceDevice),
|
||||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)
|
cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||||
|
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(
|
||||||
|
cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID))
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
|
@ -21,4 +23,14 @@ def to_code(config):
|
||||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||||
yield binary_sensor.register_binary_sensor(var, config)
|
yield binary_sensor.register_binary_sensor(var, config)
|
||||||
|
|
||||||
|
if CONF_MAC_ADDRESS in config:
|
||||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||||
|
|
||||||
|
if CONF_SERVICE_UUID in config:
|
||||||
|
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||||
|
cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])))
|
||||||
|
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
|
||||||
|
cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])))
|
||||||
|
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||||
|
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
|
||||||
|
cg.add(var.set_service_uuid128(uuid128))
|
||||||
|
|
|
@ -13,18 +13,43 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
||||||
public esp32_ble_tracker::ESPBTDeviceListener,
|
public esp32_ble_tracker::ESPBTDeviceListener,
|
||||||
public Component {
|
public Component {
|
||||||
public:
|
public:
|
||||||
void set_address(uint64_t address) { address_ = address; }
|
void set_address(uint64_t address) {
|
||||||
|
this->by_address_ = true;
|
||||||
|
this->address_ = address;
|
||||||
|
}
|
||||||
|
void set_service_uuid16(uint16_t uuid) {
|
||||||
|
this->by_address_ = false;
|
||||||
|
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid);
|
||||||
|
}
|
||||||
|
void set_service_uuid32(uint32_t uuid) {
|
||||||
|
this->by_address_ = false;
|
||||||
|
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint32(uuid);
|
||||||
|
}
|
||||||
|
void set_service_uuid128(uint8_t *uuid) {
|
||||||
|
this->by_address_ = false;
|
||||||
|
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid);
|
||||||
|
}
|
||||||
void on_scan_end() override {
|
void on_scan_end() override {
|
||||||
if (!this->found_)
|
if (!this->found_)
|
||||||
this->publish_state(false);
|
this->publish_state(false);
|
||||||
this->found_ = false;
|
this->found_ = false;
|
||||||
}
|
}
|
||||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||||
|
if (this->by_address_) {
|
||||||
if (device.address_uint64() == this->address_) {
|
if (device.address_uint64() == this->address_) {
|
||||||
this->publish_state(true);
|
this->publish_state(true);
|
||||||
this->found_ = true;
|
this->found_ = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
for (auto uuid : device.get_service_uuids()) {
|
||||||
|
if (this->uuid_ == uuid) {
|
||||||
|
this->publish_state(device.get_rssi());
|
||||||
|
this->found_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
@ -32,7 +57,9 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool found_{false};
|
bool found_{false};
|
||||||
|
bool by_address_{false};
|
||||||
uint64_t address_;
|
uint64_t address_;
|
||||||
|
esp32_ble_tracker::ESPBTUUID uuid_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ble_presence
|
} // namespace ble_presence
|
||||||
|
|
|
@ -11,18 +11,43 @@ namespace ble_rssi {
|
||||||
|
|
||||||
class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||||
public:
|
public:
|
||||||
void set_address(uint64_t address) { address_ = address; }
|
void set_address(uint64_t address) {
|
||||||
|
this->by_address_ = true;
|
||||||
|
this->address_ = address;
|
||||||
|
}
|
||||||
|
void set_service_uuid16(uint16_t uuid) {
|
||||||
|
this->by_address_ = false;
|
||||||
|
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint16(uuid);
|
||||||
|
}
|
||||||
|
void set_service_uuid32(uint32_t uuid) {
|
||||||
|
this->by_address_ = false;
|
||||||
|
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_uint32(uuid);
|
||||||
|
}
|
||||||
|
void set_service_uuid128(uint8_t *uuid) {
|
||||||
|
this->by_address_ = false;
|
||||||
|
this->uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(uuid);
|
||||||
|
}
|
||||||
void on_scan_end() override {
|
void on_scan_end() override {
|
||||||
if (!this->found_)
|
if (!this->found_)
|
||||||
this->publish_state(NAN);
|
this->publish_state(NAN);
|
||||||
this->found_ = false;
|
this->found_ = false;
|
||||||
}
|
}
|
||||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||||
|
if (this->by_address_) {
|
||||||
if (device.address_uint64() == this->address_) {
|
if (device.address_uint64() == this->address_) {
|
||||||
this->publish_state(device.get_rssi());
|
this->publish_state(device.get_rssi());
|
||||||
this->found_ = true;
|
this->found_ = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
for (auto uuid : device.get_service_uuids()) {
|
||||||
|
if (this->uuid_ == uuid) {
|
||||||
|
this->publish_state(device.get_rssi());
|
||||||
|
this->found_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
@ -30,7 +55,9 @@ class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDevi
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool found_{false};
|
bool found_{false};
|
||||||
|
bool by_address_{false};
|
||||||
uint64_t address_;
|
uint64_t address_;
|
||||||
|
esp32_ble_tracker::ESPBTUUID uuid_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ble_rssi
|
} // namespace ble_rssi
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
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 sensor, esp32_ble_tracker
|
from esphome.components import sensor, esp32_ble_tracker
|
||||||
from esphome.const import CONF_MAC_ADDRESS, CONF_ID, UNIT_DECIBEL, ICON_SIGNAL
|
from esphome.const import CONF_SERVICE_UUID, CONF_MAC_ADDRESS, CONF_ID, UNIT_DECIBEL, ICON_SIGNAL
|
||||||
|
|
||||||
DEPENDENCIES = ['esp32_ble_tracker']
|
DEPENDENCIES = ['esp32_ble_tracker']
|
||||||
|
|
||||||
|
@ -9,10 +9,12 @@ ble_rssi_ns = cg.esphome_ns.namespace('ble_rssi')
|
||||||
BLERSSISensor = ble_rssi_ns.class_('BLERSSISensor', sensor.Sensor, cg.Component,
|
BLERSSISensor = ble_rssi_ns.class_('BLERSSISensor', sensor.Sensor, cg.Component,
|
||||||
esp32_ble_tracker.ESPBTDeviceListener)
|
esp32_ble_tracker.ESPBTDeviceListener)
|
||||||
|
|
||||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_DECIBEL, ICON_SIGNAL, 0).extend({
|
CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_DECIBEL, ICON_SIGNAL, 0).extend({
|
||||||
cv.GenerateID(): cv.declare_id(BLERSSISensor),
|
cv.GenerateID(): cv.declare_id(BLERSSISensor),
|
||||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA)
|
cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||||
|
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(
|
||||||
|
cv.COMPONENT_SCHEMA), cv.has_exactly_one_key(CONF_MAC_ADDRESS, CONF_SERVICE_UUID))
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
def to_code(config):
|
||||||
|
@ -21,4 +23,14 @@ def to_code(config):
|
||||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||||
yield sensor.register_sensor(var, config)
|
yield sensor.register_sensor(var, config)
|
||||||
|
|
||||||
|
if CONF_MAC_ADDRESS in config:
|
||||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||||
|
|
||||||
|
if CONF_SERVICE_UUID in config:
|
||||||
|
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||||
|
cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])))
|
||||||
|
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
|
||||||
|
cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])))
|
||||||
|
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||||
|
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID])
|
||||||
|
cg.add(var.set_service_uuid128(uuid128))
|
||||||
|
|
0
esphome/components/ble_scanner/__init__.py
Normal file
0
esphome/components/ble_scanner/__init__.py
Normal file
16
esphome/components/ble_scanner/ble_scanner.cpp
Normal file
16
esphome/components/ble_scanner/ble_scanner.cpp
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#include "ble_scanner.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ble_scanner {
|
||||||
|
|
||||||
|
static const char *TAG = "ble_scanner";
|
||||||
|
|
||||||
|
void BLEScanner::dump_config() { LOG_TEXT_SENSOR("", "BLE Scanner", this); }
|
||||||
|
|
||||||
|
} // namespace ble_scanner
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
38
esphome/components/ble_scanner/ble_scanner.h
Normal file
38
esphome/components/ble_scanner/ble_scanner.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ctime>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||||
|
#include "esphome/components/text_sensor/text_sensor.h"
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ble_scanner {
|
||||||
|
|
||||||
|
class BLEScanner : public text_sensor::TextSensor, public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||||
|
public:
|
||||||
|
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override {
|
||||||
|
this->publish_state("{\"timestamp\":" + to_string(::time(NULL)) +
|
||||||
|
","
|
||||||
|
"\"address\":\"" +
|
||||||
|
device.address_str() +
|
||||||
|
"\","
|
||||||
|
"\"rssi\":" +
|
||||||
|
to_string(device.get_rssi()) +
|
||||||
|
","
|
||||||
|
"\"name\":\"" +
|
||||||
|
device.get_name() + "\"}");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ble_scanner
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
22
esphome/components/ble_scanner/text_sensor.py
Normal file
22
esphome/components/ble_scanner/text_sensor.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import text_sensor, esp32_ble_tracker
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
DEPENDENCIES = ['esp32_ble_tracker']
|
||||||
|
|
||||||
|
ble_scanner_ns = cg.esphome_ns.namespace('ble_scanner')
|
||||||
|
BLEScanner = ble_scanner_ns.class_('BLEScanner', text_sensor.TextSensor, cg.Component,
|
||||||
|
esp32_ble_tracker.ESPBTDeviceListener)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(text_sensor.TEXT_SENSOR_SCHEMA.extend({
|
||||||
|
cv.GenerateID(): cv.declare_id(BLEScanner),
|
||||||
|
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(
|
||||||
|
cv.COMPONENT_SCHEMA))
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||||
|
yield text_sensor.register_text_sensor(var, config)
|
|
@ -146,7 +146,7 @@ void BME280Component::dump_config() {
|
||||||
ESP_LOGE(TAG, "Communication with BME280 failed!");
|
ESP_LOGE(TAG, "Communication with BME280 failed!");
|
||||||
break;
|
break;
|
||||||
case WRONG_CHIP_ID:
|
case WRONG_CHIP_ID:
|
||||||
ESP_LOGE(TAG, "BMP280 has wrong chip ID! Is it a BMP280?");
|
ESP_LOGE(TAG, "BME280 has wrong chip ID! Is it a BME280?");
|
||||||
break;
|
break;
|
||||||
case NONE:
|
case NONE:
|
||||||
default:
|
default:
|
||||||
|
@ -172,7 +172,7 @@ void BME280Component::update() {
|
||||||
uint8_t meas_register = 0;
|
uint8_t meas_register = 0;
|
||||||
meas_register |= (this->temperature_oversampling_ & 0b111) << 5;
|
meas_register |= (this->temperature_oversampling_ & 0b111) << 5;
|
||||||
meas_register |= (this->pressure_oversampling_ & 0b111) << 2;
|
meas_register |= (this->pressure_oversampling_ & 0b111) << 2;
|
||||||
meas_register |= 0b01; // Forced mode
|
meas_register |= BME280_MODE_FORCED;
|
||||||
if (!this->write_byte(BME280_REGISTER_CONTROL, meas_register)) {
|
if (!this->write_byte(BME280_REGISTER_CONTROL, meas_register)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -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),
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,12 @@ from esphome.components import mqtt
|
||||||
from esphome.const import CONF_AWAY, CONF_ID, CONF_INTERNAL, CONF_MAX_TEMPERATURE, \
|
from esphome.const import CONF_AWAY, CONF_ID, CONF_INTERNAL, CONF_MAX_TEMPERATURE, \
|
||||||
CONF_MIN_TEMPERATURE, CONF_MODE, CONF_TARGET_TEMPERATURE, \
|
CONF_MIN_TEMPERATURE, CONF_MODE, CONF_TARGET_TEMPERATURE, \
|
||||||
CONF_TARGET_TEMPERATURE_HIGH, CONF_TARGET_TEMPERATURE_LOW, CONF_TEMPERATURE_STEP, CONF_VISUAL, \
|
CONF_TARGET_TEMPERATURE_HIGH, CONF_TARGET_TEMPERATURE_LOW, CONF_TEMPERATURE_STEP, CONF_VISUAL, \
|
||||||
CONF_MQTT_ID, CONF_NAME
|
CONF_MQTT_ID, CONF_NAME, CONF_FAN_MODE, CONF_SWING_MODE
|
||||||
from esphome.core import CORE, coroutine, coroutine_with_priority
|
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)
|
||||||
|
@ -22,9 +23,35 @@ CLIMATE_MODES = {
|
||||||
'AUTO': ClimateMode.CLIMATE_MODE_AUTO,
|
'AUTO': ClimateMode.CLIMATE_MODE_AUTO,
|
||||||
'COOL': ClimateMode.CLIMATE_MODE_COOL,
|
'COOL': ClimateMode.CLIMATE_MODE_COOL,
|
||||||
'HEAT': ClimateMode.CLIMATE_MODE_HEAT,
|
'HEAT': ClimateMode.CLIMATE_MODE_HEAT,
|
||||||
|
'DRY': ClimateMode.CLIMATE_MODE_DRY,
|
||||||
|
'FAN_ONLY': ClimateMode.CLIMATE_MODE_FAN_ONLY,
|
||||||
|
}
|
||||||
|
validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True)
|
||||||
|
|
||||||
|
ClimateFanMode = climate_ns.enum('ClimateFanMode')
|
||||||
|
CLIMATE_FAN_MODES = {
|
||||||
|
'ON': ClimateFanMode.CLIMATE_FAN_ON,
|
||||||
|
'OFF': ClimateFanMode.CLIMATE_FAN_OFF,
|
||||||
|
'AUTO': ClimateFanMode.CLIMATE_FAN_AUTO,
|
||||||
|
'LOW': ClimateFanMode.CLIMATE_FAN_LOW,
|
||||||
|
'MEDIUM': ClimateFanMode.CLIMATE_FAN_MEDIUM,
|
||||||
|
'HIGH': ClimateFanMode.CLIMATE_FAN_HIGH,
|
||||||
|
'MIDDLE': ClimateFanMode.CLIMATE_FAN_MIDDLE,
|
||||||
|
'FOCUS': ClimateFanMode.CLIMATE_FAN_FOCUS,
|
||||||
|
'DIFFUSE': ClimateFanMode.CLIMATE_FAN_DIFFUSE,
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True)
|
validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True)
|
||||||
|
|
||||||
|
ClimateSwingMode = climate_ns.enum('ClimateSwingMode')
|
||||||
|
CLIMATE_SWING_MODES = {
|
||||||
|
'OFF': ClimateSwingMode.CLIMATE_SWING_OFF,
|
||||||
|
'BOTH': ClimateSwingMode.CLIMATE_SWING_BOTH,
|
||||||
|
'VERTICAL': ClimateSwingMode.CLIMATE_SWING_VERTICAL,
|
||||||
|
'HORIZONTAL': ClimateSwingMode.CLIMATE_SWING_HORIZONTAL,
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
|
||||||
|
|
||||||
# Actions
|
# Actions
|
||||||
ControlAction = climate_ns.class_('ControlAction', automation.Action)
|
ControlAction = climate_ns.class_('ControlAction', automation.Action)
|
||||||
|
@ -74,6 +101,8 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema({
|
||||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),
|
cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),
|
||||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
|
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
|
||||||
cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
|
cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
|
||||||
|
cv.Optional(CONF_FAN_MODE): cv.templatable(validate_climate_fan_mode),
|
||||||
|
cv.Optional(CONF_SWING_MODE): cv.templatable(validate_climate_swing_mode),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,6 +125,12 @@ def climate_control_to_code(config, action_id, template_arg, args):
|
||||||
if CONF_AWAY in config:
|
if CONF_AWAY in config:
|
||||||
template_ = yield cg.templatable(config[CONF_AWAY], args, bool)
|
template_ = yield cg.templatable(config[CONF_AWAY], args, bool)
|
||||||
cg.add(var.set_away(template_))
|
cg.add(var.set_away(template_))
|
||||||
|
if CONF_FAN_MODE in config:
|
||||||
|
template_ = yield cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode)
|
||||||
|
cg.add(var.set_fan_mode(template_))
|
||||||
|
if CONF_SWING_MODE in config:
|
||||||
|
template_ = yield cg.templatable(config[CONF_SWING_MODE], args, ClimateSwingMode)
|
||||||
|
cg.add(var.set_swing_mode(template_))
|
||||||
yield var
|
yield var
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||||
TEMPLATABLE_VALUE(float, target_temperature_low)
|
TEMPLATABLE_VALUE(float, target_temperature_low)
|
||||||
TEMPLATABLE_VALUE(float, target_temperature_high)
|
TEMPLATABLE_VALUE(float, target_temperature_high)
|
||||||
TEMPLATABLE_VALUE(bool, away)
|
TEMPLATABLE_VALUE(bool, away)
|
||||||
|
TEMPLATABLE_VALUE(ClimateFanMode, fan_mode)
|
||||||
|
TEMPLATABLE_VALUE(ClimateSwingMode, swing_mode)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
auto call = this->climate_->make_call();
|
auto call = this->climate_->make_call();
|
||||||
|
@ -23,6 +25,8 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
|
||||||
call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...));
|
call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...));
|
||||||
call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...));
|
call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...));
|
||||||
call.set_away(this->away_.optional_value(x...));
|
call.set_away(this->away_.optional_value(x...));
|
||||||
|
call.set_fan_mode(this->fan_mode_.optional_value(x...));
|
||||||
|
call.set_swing_mode(this->swing_mode_.optional_value(x...));
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,14 @@ void ClimateCall::perform() {
|
||||||
const char *mode_s = climate_mode_to_string(*this->mode_);
|
const char *mode_s = climate_mode_to_string(*this->mode_);
|
||||||
ESP_LOGD(TAG, " Mode: %s", mode_s);
|
ESP_LOGD(TAG, " Mode: %s", mode_s);
|
||||||
}
|
}
|
||||||
|
if (this->fan_mode_.has_value()) {
|
||||||
|
const char *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_);
|
||||||
|
ESP_LOGD(TAG, " Fan: %s", fan_mode_s);
|
||||||
|
}
|
||||||
|
if (this->swing_mode_.has_value()) {
|
||||||
|
const char *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_);
|
||||||
|
ESP_LOGD(TAG, " Swing: %s", swing_mode_s);
|
||||||
|
}
|
||||||
if (this->target_temperature_.has_value()) {
|
if (this->target_temperature_.has_value()) {
|
||||||
ESP_LOGD(TAG, " Target Temperature: %.2f", *this->target_temperature_);
|
ESP_LOGD(TAG, " Target Temperature: %.2f", *this->target_temperature_);
|
||||||
}
|
}
|
||||||
|
@ -36,6 +44,20 @@ void ClimateCall::validate_() {
|
||||||
this->mode_.reset();
|
this->mode_.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this->fan_mode_.has_value()) {
|
||||||
|
auto fan_mode = *this->fan_mode_;
|
||||||
|
if (!traits.supports_fan_mode(fan_mode)) {
|
||||||
|
ESP_LOGW(TAG, " Fan Mode %s is not supported by this device!", climate_fan_mode_to_string(fan_mode));
|
||||||
|
this->fan_mode_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this->swing_mode_.has_value()) {
|
||||||
|
auto swing_mode = *this->swing_mode_;
|
||||||
|
if (!traits.supports_swing_mode(swing_mode)) {
|
||||||
|
ESP_LOGW(TAG, " Swing Mode %s is not supported by this device!", climate_swing_mode_to_string(swing_mode));
|
||||||
|
this->swing_mode_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this->target_temperature_.has_value()) {
|
if (this->target_temperature_.has_value()) {
|
||||||
auto target = *this->target_temperature_;
|
auto target = *this->target_temperature_;
|
||||||
if (traits.get_supports_two_point_target_temperature()) {
|
if (traits.get_supports_two_point_target_temperature()) {
|
||||||
|
@ -91,11 +113,63 @@ ClimateCall &ClimateCall::set_mode(const std::string &mode) {
|
||||||
this->set_mode(CLIMATE_MODE_COOL);
|
this->set_mode(CLIMATE_MODE_COOL);
|
||||||
} else if (str_equals_case_insensitive(mode, "HEAT")) {
|
} else if (str_equals_case_insensitive(mode, "HEAT")) {
|
||||||
this->set_mode(CLIMATE_MODE_HEAT);
|
this->set_mode(CLIMATE_MODE_HEAT);
|
||||||
|
} else if (str_equals_case_insensitive(mode, "FAN_ONLY")) {
|
||||||
|
this->set_mode(CLIMATE_MODE_FAN_ONLY);
|
||||||
|
} else if (str_equals_case_insensitive(mode, "DRY")) {
|
||||||
|
this->set_mode(CLIMATE_MODE_DRY);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode.c_str());
|
ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode.c_str());
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
ClimateCall &ClimateCall::set_fan_mode(ClimateFanMode fan_mode) {
|
||||||
|
this->fan_mode_ = fan_mode;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
|
||||||
|
if (str_equals_case_insensitive(fan_mode, "ON")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_ON);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "OFF")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_OFF);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "AUTO")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_AUTO);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "LOW")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_LOW);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "MEDIUM")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_MEDIUM);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "HIGH")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_HIGH);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "MIDDLE")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_MIDDLE);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "FOCUS")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_FOCUS);
|
||||||
|
} else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) {
|
||||||
|
this->set_fan_mode(CLIMATE_FAN_DIFFUSE);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), fan_mode.c_str());
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClimateCall &ClimateCall::set_swing_mode(ClimateSwingMode swing_mode) {
|
||||||
|
this->swing_mode_ = swing_mode;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ClimateCall &ClimateCall::set_swing_mode(const std::string &swing_mode) {
|
||||||
|
if (str_equals_case_insensitive(swing_mode, "OFF")) {
|
||||||
|
this->set_swing_mode(CLIMATE_SWING_OFF);
|
||||||
|
} else if (str_equals_case_insensitive(swing_mode, "BOTH")) {
|
||||||
|
this->set_swing_mode(CLIMATE_SWING_BOTH);
|
||||||
|
} else if (str_equals_case_insensitive(swing_mode, "VERTICAL")) {
|
||||||
|
this->set_swing_mode(CLIMATE_SWING_VERTICAL);
|
||||||
|
} else if (str_equals_case_insensitive(swing_mode, "HORIZONTAL")) {
|
||||||
|
this->set_swing_mode(CLIMATE_SWING_HORIZONTAL);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "'%s' - Unrecognized swing mode %s", this->parent_->get_name().c_str(), swing_mode.c_str());
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
ClimateCall &ClimateCall::set_target_temperature(float target_temperature) {
|
ClimateCall &ClimateCall::set_target_temperature(float target_temperature) {
|
||||||
this->target_temperature_ = target_temperature;
|
this->target_temperature_ = target_temperature;
|
||||||
return *this;
|
return *this;
|
||||||
|
@ -113,6 +187,8 @@ const optional<float> &ClimateCall::get_target_temperature() const { return this
|
||||||
const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
|
const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
|
||||||
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
|
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
|
||||||
const optional<bool> &ClimateCall::get_away() const { return this->away_; }
|
const optional<bool> &ClimateCall::get_away() const { return this->away_; }
|
||||||
|
const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; }
|
||||||
|
const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; }
|
||||||
ClimateCall &ClimateCall::set_away(bool away) {
|
ClimateCall &ClimateCall::set_away(bool away) {
|
||||||
this->away_ = away;
|
this->away_ = away;
|
||||||
return *this;
|
return *this;
|
||||||
|
@ -137,6 +213,14 @@ ClimateCall &ClimateCall::set_mode(optional<ClimateMode> mode) {
|
||||||
this->mode_ = mode;
|
this->mode_ = mode;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
ClimateCall &ClimateCall::set_fan_mode(optional<ClimateFanMode> fan_mode) {
|
||||||
|
this->fan_mode_ = fan_mode;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ClimateCall &ClimateCall::set_swing_mode(optional<ClimateSwingMode> swing_mode) {
|
||||||
|
this->swing_mode_ = swing_mode;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
void Climate::add_on_state_callback(std::function<void()> &&callback) {
|
void Climate::add_on_state_callback(std::function<void()> &&callback) {
|
||||||
this->state_callback_.add(std::move(callback));
|
this->state_callback_.add(std::move(callback));
|
||||||
|
@ -165,6 +249,12 @@ void Climate::save_state_() {
|
||||||
if (traits.get_supports_away()) {
|
if (traits.get_supports_away()) {
|
||||||
state.away = this->away;
|
state.away = this->away;
|
||||||
}
|
}
|
||||||
|
if (traits.get_supports_fan_modes()) {
|
||||||
|
state.fan_mode = this->fan_mode;
|
||||||
|
}
|
||||||
|
if (traits.get_supports_swing_modes()) {
|
||||||
|
state.swing_mode = this->swing_mode;
|
||||||
|
}
|
||||||
|
|
||||||
this->rtc_.save(&state);
|
this->rtc_.save(&state);
|
||||||
}
|
}
|
||||||
|
@ -176,6 +266,12 @@ void Climate::publish_state() {
|
||||||
if (traits.get_supports_action()) {
|
if (traits.get_supports_action()) {
|
||||||
ESP_LOGD(TAG, " Action: %s", climate_action_to_string(this->action));
|
ESP_LOGD(TAG, " Action: %s", climate_action_to_string(this->action));
|
||||||
}
|
}
|
||||||
|
if (traits.get_supports_fan_modes()) {
|
||||||
|
ESP_LOGD(TAG, " Fan Mode: %s", climate_fan_mode_to_string(this->fan_mode));
|
||||||
|
}
|
||||||
|
if (traits.get_supports_swing_modes()) {
|
||||||
|
ESP_LOGD(TAG, " Swing Mode: %s", climate_swing_mode_to_string(this->swing_mode));
|
||||||
|
}
|
||||||
if (traits.get_supports_current_temperature()) {
|
if (traits.get_supports_current_temperature()) {
|
||||||
ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature);
|
ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature);
|
||||||
}
|
}
|
||||||
|
@ -236,6 +332,12 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) {
|
||||||
if (traits.get_supports_away()) {
|
if (traits.get_supports_away()) {
|
||||||
call.set_away(this->away);
|
call.set_away(this->away);
|
||||||
}
|
}
|
||||||
|
if (traits.get_supports_fan_modes()) {
|
||||||
|
call.set_fan_mode(this->fan_mode);
|
||||||
|
}
|
||||||
|
if (traits.get_supports_swing_modes()) {
|
||||||
|
call.set_swing_mode(this->swing_mode);
|
||||||
|
}
|
||||||
return call;
|
return call;
|
||||||
}
|
}
|
||||||
void ClimateDeviceRestoreState::apply(Climate *climate) {
|
void ClimateDeviceRestoreState::apply(Climate *climate) {
|
||||||
|
@ -250,6 +352,12 @@ void ClimateDeviceRestoreState::apply(Climate *climate) {
|
||||||
if (traits.get_supports_away()) {
|
if (traits.get_supports_away()) {
|
||||||
climate->away = this->away;
|
climate->away = this->away;
|
||||||
}
|
}
|
||||||
|
if (traits.get_supports_fan_modes()) {
|
||||||
|
climate->fan_mode = this->fan_mode;
|
||||||
|
}
|
||||||
|
if (traits.get_supports_swing_modes()) {
|
||||||
|
climate->swing_mode = this->swing_mode;
|
||||||
|
}
|
||||||
climate->publish_state();
|
climate->publish_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,18 @@ class ClimateCall {
|
||||||
ClimateCall &set_target_temperature_high(optional<float> target_temperature_high);
|
ClimateCall &set_target_temperature_high(optional<float> target_temperature_high);
|
||||||
ClimateCall &set_away(bool away);
|
ClimateCall &set_away(bool away);
|
||||||
ClimateCall &set_away(optional<bool> away);
|
ClimateCall &set_away(optional<bool> away);
|
||||||
|
/// Set the fan mode of the climate device.
|
||||||
|
ClimateCall &set_fan_mode(ClimateFanMode fan_mode);
|
||||||
|
/// Set the fan mode of the climate device.
|
||||||
|
ClimateCall &set_fan_mode(optional<ClimateFanMode> fan_mode);
|
||||||
|
/// Set the fan mode of the climate device based on a string.
|
||||||
|
ClimateCall &set_fan_mode(const std::string &fan_mode);
|
||||||
|
/// Set the swing mode of the climate device.
|
||||||
|
ClimateCall &set_swing_mode(ClimateSwingMode swing_mode);
|
||||||
|
/// Set the swing mode of the climate device.
|
||||||
|
ClimateCall &set_swing_mode(optional<ClimateSwingMode> swing_mode);
|
||||||
|
/// Set the swing mode of the climate device based on a string.
|
||||||
|
ClimateCall &set_swing_mode(const std::string &swing_mode);
|
||||||
|
|
||||||
void perform();
|
void perform();
|
||||||
|
|
||||||
|
@ -72,6 +84,8 @@ class ClimateCall {
|
||||||
const optional<float> &get_target_temperature_low() const;
|
const optional<float> &get_target_temperature_low() const;
|
||||||
const optional<float> &get_target_temperature_high() const;
|
const optional<float> &get_target_temperature_high() const;
|
||||||
const optional<bool> &get_away() const;
|
const optional<bool> &get_away() const;
|
||||||
|
const optional<ClimateFanMode> &get_fan_mode() const;
|
||||||
|
const optional<ClimateSwingMode> &get_swing_mode() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void validate_();
|
void validate_();
|
||||||
|
@ -82,12 +96,16 @@ class ClimateCall {
|
||||||
optional<float> target_temperature_low_;
|
optional<float> target_temperature_low_;
|
||||||
optional<float> target_temperature_high_;
|
optional<float> target_temperature_high_;
|
||||||
optional<bool> away_;
|
optional<bool> away_;
|
||||||
|
optional<ClimateFanMode> fan_mode_;
|
||||||
|
optional<ClimateSwingMode> swing_mode_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Struct used to save the state of the climate device in restore memory.
|
/// Struct used to save the state of the climate device in restore memory.
|
||||||
struct ClimateDeviceRestoreState {
|
struct ClimateDeviceRestoreState {
|
||||||
ClimateMode mode;
|
ClimateMode mode;
|
||||||
bool away;
|
bool away;
|
||||||
|
ClimateFanMode fan_mode;
|
||||||
|
ClimateSwingMode swing_mode;
|
||||||
union {
|
union {
|
||||||
float target_temperature;
|
float target_temperature;
|
||||||
struct {
|
struct {
|
||||||
|
@ -149,6 +167,12 @@ class Climate : public Nameable {
|
||||||
*/
|
*/
|
||||||
bool away{false};
|
bool away{false};
|
||||||
|
|
||||||
|
/// The active fan mode of the climate device.
|
||||||
|
ClimateFanMode fan_mode;
|
||||||
|
|
||||||
|
/// The active swing mode of the climate device.
|
||||||
|
ClimateSwingMode swing_mode;
|
||||||
|
|
||||||
/** Add a callback for the climate device state, each time the state of the climate device is updated
|
/** Add a callback for the climate device state, each time the state of the climate device is updated
|
||||||
* (using publish_state), this callback will be called.
|
* (using publish_state), this callback will be called.
|
||||||
*
|
*
|
||||||
|
|
|
@ -13,6 +13,10 @@ const char *climate_mode_to_string(ClimateMode mode) {
|
||||||
return "COOL";
|
return "COOL";
|
||||||
case CLIMATE_MODE_HEAT:
|
case CLIMATE_MODE_HEAT:
|
||||||
return "HEAT";
|
return "HEAT";
|
||||||
|
case CLIMATE_MODE_FAN_ONLY:
|
||||||
|
return "FAN_ONLY";
|
||||||
|
case CLIMATE_MODE_DRY:
|
||||||
|
return "DRY";
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
@ -25,6 +29,52 @@ const char *climate_action_to_string(ClimateAction action) {
|
||||||
return "COOLING";
|
return "COOLING";
|
||||||
case CLIMATE_ACTION_HEATING:
|
case CLIMATE_ACTION_HEATING:
|
||||||
return "HEATING";
|
return "HEATING";
|
||||||
|
case CLIMATE_ACTION_IDLE:
|
||||||
|
return "IDLE";
|
||||||
|
case CLIMATE_ACTION_DRYING:
|
||||||
|
return "DRYING";
|
||||||
|
case CLIMATE_ACTION_FAN:
|
||||||
|
return "FAN";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *climate_fan_mode_to_string(ClimateFanMode fan_mode) {
|
||||||
|
switch (fan_mode) {
|
||||||
|
case climate::CLIMATE_FAN_ON:
|
||||||
|
return "ON";
|
||||||
|
case climate::CLIMATE_FAN_OFF:
|
||||||
|
return "OFF";
|
||||||
|
case climate::CLIMATE_FAN_AUTO:
|
||||||
|
return "AUTO";
|
||||||
|
case climate::CLIMATE_FAN_LOW:
|
||||||
|
return "LOW";
|
||||||
|
case climate::CLIMATE_FAN_MEDIUM:
|
||||||
|
return "MEDIUM";
|
||||||
|
case climate::CLIMATE_FAN_HIGH:
|
||||||
|
return "HIGH";
|
||||||
|
case climate::CLIMATE_FAN_MIDDLE:
|
||||||
|
return "MIDDLE";
|
||||||
|
case climate::CLIMATE_FAN_FOCUS:
|
||||||
|
return "FOCUS";
|
||||||
|
case climate::CLIMATE_FAN_DIFFUSE:
|
||||||
|
return "DIFFUSE";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) {
|
||||||
|
switch (swing_mode) {
|
||||||
|
case climate::CLIMATE_SWING_OFF:
|
||||||
|
return "OFF";
|
||||||
|
case climate::CLIMATE_SWING_BOTH:
|
||||||
|
return "BOTH";
|
||||||
|
case climate::CLIMATE_SWING_VERTICAL:
|
||||||
|
return "VERTICAL";
|
||||||
|
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||||
|
return "HORIZONTAL";
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,10 @@ enum ClimateMode : uint8_t {
|
||||||
CLIMATE_MODE_COOL = 2,
|
CLIMATE_MODE_COOL = 2,
|
||||||
/// The climate device is manually set to heat mode (not in auto mode!)
|
/// The climate device is manually set to heat mode (not in auto mode!)
|
||||||
CLIMATE_MODE_HEAT = 3,
|
CLIMATE_MODE_HEAT = 3,
|
||||||
|
/// The climate device is manually set to fan only mode
|
||||||
|
CLIMATE_MODE_FAN_ONLY = 4,
|
||||||
|
/// The climate device is manually set to dry mode
|
||||||
|
CLIMATE_MODE_DRY = 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Enum for the current action of the climate device. Values match those of ClimateMode.
|
/// Enum for the current action of the climate device. Values match those of ClimateMode.
|
||||||
|
@ -25,11 +29,59 @@ enum ClimateAction : uint8_t {
|
||||||
CLIMATE_ACTION_COOLING = 2,
|
CLIMATE_ACTION_COOLING = 2,
|
||||||
/// The climate device is actively heating (usually in heat or auto mode)
|
/// The climate device is actively heating (usually in heat or auto mode)
|
||||||
CLIMATE_ACTION_HEATING = 3,
|
CLIMATE_ACTION_HEATING = 3,
|
||||||
|
/// The climate device is idle (monitoring climate but no action needed)
|
||||||
|
CLIMATE_ACTION_IDLE = 4,
|
||||||
|
/// The climate device is drying (either mode DRY or AUTO)
|
||||||
|
CLIMATE_ACTION_DRYING = 5,
|
||||||
|
/// The climate device is in fan only mode (either mode FAN_ONLY or AUTO)
|
||||||
|
CLIMATE_ACTION_FAN = 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Enum for all modes a climate fan can be in
|
||||||
|
enum ClimateFanMode : uint8_t {
|
||||||
|
/// The fan mode is set to On
|
||||||
|
CLIMATE_FAN_ON = 0,
|
||||||
|
/// The fan mode is set to Off
|
||||||
|
CLIMATE_FAN_OFF = 1,
|
||||||
|
/// The fan mode is set to Auto
|
||||||
|
CLIMATE_FAN_AUTO = 2,
|
||||||
|
/// The fan mode is set to Low
|
||||||
|
CLIMATE_FAN_LOW = 3,
|
||||||
|
/// The fan mode is set to Medium
|
||||||
|
CLIMATE_FAN_MEDIUM = 4,
|
||||||
|
/// The fan mode is set to High
|
||||||
|
CLIMATE_FAN_HIGH = 5,
|
||||||
|
/// The fan mode is set to Middle
|
||||||
|
CLIMATE_FAN_MIDDLE = 6,
|
||||||
|
/// The fan mode is set to Focus
|
||||||
|
CLIMATE_FAN_FOCUS = 7,
|
||||||
|
/// The fan mode is set to Diffuse
|
||||||
|
CLIMATE_FAN_DIFFUSE = 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Enum for all modes a climate swing can be in
|
||||||
|
enum ClimateSwingMode : uint8_t {
|
||||||
|
/// The sing mode is set to Off
|
||||||
|
CLIMATE_SWING_OFF = 0,
|
||||||
|
/// The fan mode is set to Both
|
||||||
|
CLIMATE_SWING_BOTH = 1,
|
||||||
|
/// The fan mode is set to Vertical
|
||||||
|
CLIMATE_SWING_VERTICAL = 2,
|
||||||
|
/// The fan mode is set to Horizontal
|
||||||
|
CLIMATE_SWING_HORIZONTAL = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Convert the given ClimateMode to a human-readable string.
|
/// Convert the given ClimateMode to a human-readable string.
|
||||||
const char *climate_mode_to_string(ClimateMode mode);
|
const char *climate_mode_to_string(ClimateMode mode);
|
||||||
|
|
||||||
|
/// Convert the given ClimateAction to a human-readable string.
|
||||||
const char *climate_action_to_string(ClimateAction action);
|
const char *climate_action_to_string(ClimateAction action);
|
||||||
|
|
||||||
|
/// Convert the given ClimateFanMode to a human-readable string.
|
||||||
|
const char *climate_fan_mode_to_string(ClimateFanMode mode);
|
||||||
|
|
||||||
|
/// Convert the given ClimateSwingMode to a human-readable string.
|
||||||
|
const char *climate_swing_mode_to_string(ClimateSwingMode mode);
|
||||||
|
|
||||||
} // namespace climate
|
} // namespace climate
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -14,6 +14,10 @@ bool ClimateTraits::supports_mode(ClimateMode mode) const {
|
||||||
return this->supports_cool_mode_;
|
return this->supports_cool_mode_;
|
||||||
case CLIMATE_MODE_HEAT:
|
case CLIMATE_MODE_HEAT:
|
||||||
return this->supports_heat_mode_;
|
return this->supports_heat_mode_;
|
||||||
|
case CLIMATE_MODE_FAN_ONLY:
|
||||||
|
return this->supports_fan_only_mode_;
|
||||||
|
case CLIMATE_MODE_DRY:
|
||||||
|
return this->supports_dry_mode_;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -29,6 +33,10 @@ void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_
|
||||||
void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; }
|
void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; }
|
||||||
void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; }
|
void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; }
|
||||||
void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; }
|
void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; }
|
||||||
|
void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) {
|
||||||
|
supports_fan_only_mode_ = supports_fan_only_mode;
|
||||||
|
}
|
||||||
|
void ClimateTraits::set_supports_dry_mode(bool supports_dry_mode) { supports_dry_mode_ = supports_dry_mode; }
|
||||||
void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; }
|
void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; }
|
||||||
void ClimateTraits::set_supports_action(bool supports_action) { supports_action_ = supports_action; }
|
void ClimateTraits::set_supports_action(bool supports_action) { supports_action_ = supports_action; }
|
||||||
float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; }
|
float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; }
|
||||||
|
@ -55,5 +63,91 @@ void ClimateTraits::set_visual_temperature_step(float temperature_step) { visual
|
||||||
bool ClimateTraits::get_supports_away() const { return supports_away_; }
|
bool ClimateTraits::get_supports_away() const { return supports_away_; }
|
||||||
bool ClimateTraits::get_supports_action() const { return supports_action_; }
|
bool ClimateTraits::get_supports_action() const { return supports_action_; }
|
||||||
|
|
||||||
|
void ClimateTraits::set_supports_fan_mode_on(bool supports_fan_mode_on) {
|
||||||
|
this->supports_fan_mode_on_ = supports_fan_mode_on;
|
||||||
|
}
|
||||||
|
void ClimateTraits::set_supports_fan_mode_off(bool supports_fan_mode_off) {
|
||||||
|
this->supports_fan_mode_off_ = supports_fan_mode_off;
|
||||||
|
}
|
||||||
|
void ClimateTraits::set_supports_fan_mode_auto(bool supports_fan_mode_auto) {
|
||||||
|
this->supports_fan_mode_auto_ = supports_fan_mode_auto;
|
||||||
|
}
|
||||||
|
void ClimateTraits::set_supports_fan_mode_low(bool supports_fan_mode_low) {
|
||||||
|
this->supports_fan_mode_low_ = supports_fan_mode_low;
|
||||||
|
}
|
||||||
|
void ClimateTraits::set_supports_fan_mode_medium(bool supports_fan_mode_medium) {
|
||||||
|
this->supports_fan_mode_medium_ = supports_fan_mode_medium;
|
||||||
|
}
|
||||||
|
void ClimateTraits::set_supports_fan_mode_high(bool supports_fan_mode_high) {
|
||||||
|
this->supports_fan_mode_high_ = supports_fan_mode_high;
|
||||||
|
}
|
||||||
|
void ClimateTraits::set_supports_fan_mode_middle(bool supports_fan_mode_middle) {
|
||||||
|
this->supports_fan_mode_middle_ = supports_fan_mode_middle;
|
||||||
|
}
|
||||||
|
void ClimateTraits::set_supports_fan_mode_focus(bool supports_fan_mode_focus) {
|
||||||
|
this->supports_fan_mode_focus_ = supports_fan_mode_focus;
|
||||||
|
}
|
||||||
|
void ClimateTraits::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) {
|
||||||
|
this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse;
|
||||||
|
}
|
||||||
|
bool ClimateTraits::supports_fan_mode(ClimateFanMode fan_mode) const {
|
||||||
|
switch (fan_mode) {
|
||||||
|
case climate::CLIMATE_FAN_ON:
|
||||||
|
return this->supports_fan_mode_on_;
|
||||||
|
case climate::CLIMATE_FAN_OFF:
|
||||||
|
return this->supports_fan_mode_off_;
|
||||||
|
case climate::CLIMATE_FAN_AUTO:
|
||||||
|
return this->supports_fan_mode_auto_;
|
||||||
|
case climate::CLIMATE_FAN_LOW:
|
||||||
|
return this->supports_fan_mode_low_;
|
||||||
|
case climate::CLIMATE_FAN_MEDIUM:
|
||||||
|
return this->supports_fan_mode_medium_;
|
||||||
|
case climate::CLIMATE_FAN_HIGH:
|
||||||
|
return this->supports_fan_mode_high_;
|
||||||
|
case climate::CLIMATE_FAN_MIDDLE:
|
||||||
|
return this->supports_fan_mode_middle_;
|
||||||
|
case climate::CLIMATE_FAN_FOCUS:
|
||||||
|
return this->supports_fan_mode_focus_;
|
||||||
|
case climate::CLIMATE_FAN_DIFFUSE:
|
||||||
|
return this->supports_fan_mode_diffuse_;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool ClimateTraits::get_supports_fan_modes() const {
|
||||||
|
return this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ ||
|
||||||
|
this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ ||
|
||||||
|
this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_;
|
||||||
|
}
|
||||||
|
void ClimateTraits::set_supports_swing_mode_off(bool supports_swing_mode_off) {
|
||||||
|
this->supports_swing_mode_off_ = supports_swing_mode_off;
|
||||||
|
}
|
||||||
|
void ClimateTraits::set_supports_swing_mode_both(bool supports_swing_mode_both) {
|
||||||
|
this->supports_swing_mode_both_ = supports_swing_mode_both;
|
||||||
|
}
|
||||||
|
void ClimateTraits::set_supports_swing_mode_vertical(bool supports_swing_mode_vertical) {
|
||||||
|
this->supports_swing_mode_vertical_ = supports_swing_mode_vertical;
|
||||||
|
}
|
||||||
|
void ClimateTraits::set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal) {
|
||||||
|
this->supports_swing_mode_horizontal_ = supports_swing_mode_horizontal;
|
||||||
|
}
|
||||||
|
bool ClimateTraits::supports_swing_mode(ClimateSwingMode swing_mode) const {
|
||||||
|
switch (swing_mode) {
|
||||||
|
case climate::CLIMATE_SWING_OFF:
|
||||||
|
return this->supports_swing_mode_off_;
|
||||||
|
case climate::CLIMATE_SWING_BOTH:
|
||||||
|
return this->supports_swing_mode_both_;
|
||||||
|
case climate::CLIMATE_SWING_VERTICAL:
|
||||||
|
return this->supports_swing_mode_vertical_;
|
||||||
|
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||||
|
return this->supports_swing_mode_horizontal_;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool ClimateTraits::get_supports_swing_modes() const {
|
||||||
|
return this->supports_swing_mode_off_ || this->supports_swing_mode_both_ || supports_swing_mode_vertical_ ||
|
||||||
|
supports_swing_mode_horizontal_;
|
||||||
|
}
|
||||||
} // namespace climate
|
} // namespace climate
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -21,10 +21,16 @@ namespace climate {
|
||||||
* - auto mode (automatic control)
|
* - auto mode (automatic control)
|
||||||
* - cool mode (lowers current temperature)
|
* - cool mode (lowers current temperature)
|
||||||
* - heat mode (increases current temperature)
|
* - heat mode (increases current temperature)
|
||||||
|
* - dry mode (removes humidity from air)
|
||||||
|
* - fan mode (only turns on fan)
|
||||||
* - supports away - away mode means that the climate device supports two different
|
* - supports away - away mode means that the climate device supports two different
|
||||||
* target temperature settings: one target temp setting for "away" mode and one for non-away mode.
|
* target temperature settings: one target temp setting for "away" mode and one for non-away mode.
|
||||||
* - supports action - if the climate device supports reporting the active
|
* - supports action - if the climate device supports reporting the active
|
||||||
* current action of the device with the action property.
|
* current action of the device with the action property.
|
||||||
|
* - supports fan modes - optionally, if it has a fan which can be configured in different ways:
|
||||||
|
* - on, off, auto, high, medium, low, middle, focus, diffuse
|
||||||
|
* - supports swing modes - optionally, if it has a swing which can be configured in different ways:
|
||||||
|
* - off, both, vertical, horizontal
|
||||||
*
|
*
|
||||||
* This class also contains static data for the climate device display:
|
* This class also contains static data for the climate device display:
|
||||||
* - visual min/max temperature - tells the frontend what range of temperatures the climate device
|
* - visual min/max temperature - tells the frontend what range of temperatures the climate device
|
||||||
|
@ -41,11 +47,30 @@ class ClimateTraits {
|
||||||
void set_supports_auto_mode(bool supports_auto_mode);
|
void set_supports_auto_mode(bool supports_auto_mode);
|
||||||
void set_supports_cool_mode(bool supports_cool_mode);
|
void set_supports_cool_mode(bool supports_cool_mode);
|
||||||
void set_supports_heat_mode(bool supports_heat_mode);
|
void set_supports_heat_mode(bool supports_heat_mode);
|
||||||
|
void set_supports_fan_only_mode(bool supports_fan_only_mode);
|
||||||
|
void set_supports_dry_mode(bool supports_dry_mode);
|
||||||
void set_supports_away(bool supports_away);
|
void set_supports_away(bool supports_away);
|
||||||
bool get_supports_away() const;
|
bool get_supports_away() const;
|
||||||
void set_supports_action(bool supports_action);
|
void set_supports_action(bool supports_action);
|
||||||
bool get_supports_action() const;
|
bool get_supports_action() const;
|
||||||
bool supports_mode(ClimateMode mode) const;
|
bool supports_mode(ClimateMode mode) const;
|
||||||
|
void set_supports_fan_mode_on(bool supports_fan_mode_on);
|
||||||
|
void set_supports_fan_mode_off(bool supports_fan_mode_off);
|
||||||
|
void set_supports_fan_mode_auto(bool supports_fan_mode_auto);
|
||||||
|
void set_supports_fan_mode_low(bool supports_fan_mode_low);
|
||||||
|
void set_supports_fan_mode_medium(bool supports_fan_mode_medium);
|
||||||
|
void set_supports_fan_mode_high(bool supports_fan_mode_high);
|
||||||
|
void set_supports_fan_mode_middle(bool supports_fan_mode_middle);
|
||||||
|
void set_supports_fan_mode_focus(bool supports_fan_mode_focus);
|
||||||
|
void set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse);
|
||||||
|
bool supports_fan_mode(ClimateFanMode fan_mode) const;
|
||||||
|
bool get_supports_fan_modes() const;
|
||||||
|
void set_supports_swing_mode_off(bool supports_swing_mode_off);
|
||||||
|
void set_supports_swing_mode_both(bool supports_swing_mode_both);
|
||||||
|
void set_supports_swing_mode_vertical(bool supports_swing_mode_vertical);
|
||||||
|
void set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal);
|
||||||
|
bool supports_swing_mode(ClimateSwingMode swing_mode) const;
|
||||||
|
bool get_supports_swing_modes() const;
|
||||||
|
|
||||||
float get_visual_min_temperature() const;
|
float get_visual_min_temperature() const;
|
||||||
void set_visual_min_temperature(float visual_min_temperature);
|
void set_visual_min_temperature(float visual_min_temperature);
|
||||||
|
@ -61,8 +86,23 @@ class ClimateTraits {
|
||||||
bool supports_auto_mode_{false};
|
bool supports_auto_mode_{false};
|
||||||
bool supports_cool_mode_{false};
|
bool supports_cool_mode_{false};
|
||||||
bool supports_heat_mode_{false};
|
bool supports_heat_mode_{false};
|
||||||
|
bool supports_fan_only_mode_{false};
|
||||||
|
bool supports_dry_mode_{false};
|
||||||
bool supports_away_{false};
|
bool supports_away_{false};
|
||||||
bool supports_action_{false};
|
bool supports_action_{false};
|
||||||
|
bool supports_fan_mode_on_{false};
|
||||||
|
bool supports_fan_mode_off_{false};
|
||||||
|
bool supports_fan_mode_auto_{false};
|
||||||
|
bool supports_fan_mode_low_{false};
|
||||||
|
bool supports_fan_mode_medium_{false};
|
||||||
|
bool supports_fan_mode_high_{false};
|
||||||
|
bool supports_fan_mode_middle_{false};
|
||||||
|
bool supports_fan_mode_focus_{false};
|
||||||
|
bool supports_fan_mode_diffuse_{false};
|
||||||
|
bool supports_swing_mode_off_{false};
|
||||||
|
bool supports_swing_mode_both_{false};
|
||||||
|
bool supports_swing_mode_vertical_{false};
|
||||||
|
bool supports_swing_mode_horizontal_{false};
|
||||||
|
|
||||||
float visual_min_temperature_{10};
|
float visual_min_temperature_{10};
|
||||||
float visual_max_temperature_{30};
|
float visual_max_temperature_{30};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -12,11 +12,60 @@ climate::ClimateTraits ClimateIR::traits() {
|
||||||
traits.set_supports_auto_mode(true);
|
traits.set_supports_auto_mode(true);
|
||||||
traits.set_supports_cool_mode(this->supports_cool_);
|
traits.set_supports_cool_mode(this->supports_cool_);
|
||||||
traits.set_supports_heat_mode(this->supports_heat_);
|
traits.set_supports_heat_mode(this->supports_heat_);
|
||||||
|
traits.set_supports_dry_mode(this->supports_dry_);
|
||||||
|
traits.set_supports_fan_only_mode(this->supports_fan_only_);
|
||||||
traits.set_supports_two_point_target_temperature(false);
|
traits.set_supports_two_point_target_temperature(false);
|
||||||
traits.set_supports_away(false);
|
traits.set_supports_away(false);
|
||||||
traits.set_visual_min_temperature(this->minimum_temperature_);
|
traits.set_visual_min_temperature(this->minimum_temperature_);
|
||||||
traits.set_visual_max_temperature(this->maximum_temperature_);
|
traits.set_visual_max_temperature(this->maximum_temperature_);
|
||||||
traits.set_visual_temperature_step(this->temperature_step_);
|
traits.set_visual_temperature_step(this->temperature_step_);
|
||||||
|
for (auto fan_mode : this->fan_modes_) {
|
||||||
|
switch (fan_mode) {
|
||||||
|
case climate::CLIMATE_FAN_AUTO:
|
||||||
|
traits.set_supports_fan_mode_auto(true);
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_DIFFUSE:
|
||||||
|
traits.set_supports_fan_mode_diffuse(true);
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_FOCUS:
|
||||||
|
traits.set_supports_fan_mode_focus(true);
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_HIGH:
|
||||||
|
traits.set_supports_fan_mode_high(true);
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_LOW:
|
||||||
|
traits.set_supports_fan_mode_low(true);
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_MEDIUM:
|
||||||
|
traits.set_supports_fan_mode_medium(true);
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_MIDDLE:
|
||||||
|
traits.set_supports_fan_mode_middle(true);
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_OFF:
|
||||||
|
traits.set_supports_fan_mode_off(true);
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_FAN_ON:
|
||||||
|
traits.set_supports_fan_mode_on(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto swing_mode : this->swing_modes_) {
|
||||||
|
switch (swing_mode) {
|
||||||
|
case climate::CLIMATE_SWING_OFF:
|
||||||
|
traits.set_supports_swing_mode_off(true);
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_SWING_BOTH:
|
||||||
|
traits.set_supports_swing_mode_both(true);
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_SWING_VERTICAL:
|
||||||
|
traits.set_supports_swing_mode_vertical(true);
|
||||||
|
break;
|
||||||
|
case climate::CLIMATE_SWING_HORIZONTAL:
|
||||||
|
traits.set_supports_swing_mode_horizontal(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
return traits;
|
return traits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +89,8 @@ void ClimateIR::setup() {
|
||||||
// initialize target temperature to some value so that it's not NAN
|
// initialize target temperature to some value so that it's not NAN
|
||||||
this->target_temperature =
|
this->target_temperature =
|
||||||
roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_));
|
roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_));
|
||||||
|
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||||
}
|
}
|
||||||
// Never send nan to HA
|
// Never send nan to HA
|
||||||
if (isnan(this->target_temperature))
|
if (isnan(this->target_temperature))
|
||||||
|
@ -51,7 +102,10 @@ void ClimateIR::control(const climate::ClimateCall &call) {
|
||||||
this->mode = *call.get_mode();
|
this->mode = *call.get_mode();
|
||||||
if (call.get_target_temperature().has_value())
|
if (call.get_target_temperature().has_value())
|
||||||
this->target_temperature = *call.get_target_temperature();
|
this->target_temperature = *call.get_target_temperature();
|
||||||
|
if (call.get_fan_mode().has_value())
|
||||||
|
this->fan_mode = *call.get_fan_mode();
|
||||||
|
if (call.get_swing_mode().has_value())
|
||||||
|
this->swing_mode = *call.get_swing_mode();
|
||||||
this->transmit_state();
|
this->transmit_state();
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,17 @@ namespace climate_ir {
|
||||||
*/
|
*/
|
||||||
class ClimateIR : public climate::Climate, public Component, public remote_base::RemoteReceiverListener {
|
class ClimateIR : public climate::Climate, public Component, public remote_base::RemoteReceiverListener {
|
||||||
public:
|
public:
|
||||||
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f) {
|
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
|
||||||
|
bool supports_dry = false, bool supports_fan_only = false,
|
||||||
|
std::vector<climate::ClimateFanMode> fan_modes = {},
|
||||||
|
std::vector<climate::ClimateSwingMode> swing_modes = {}) {
|
||||||
this->minimum_temperature_ = minimum_temperature;
|
this->minimum_temperature_ = minimum_temperature;
|
||||||
this->maximum_temperature_ = maximum_temperature;
|
this->maximum_temperature_ = maximum_temperature;
|
||||||
this->temperature_step_ = temperature_step;
|
this->temperature_step_ = temperature_step;
|
||||||
|
this->supports_dry_ = supports_dry;
|
||||||
|
this->supports_fan_only_ = supports_fan_only;
|
||||||
|
this->fan_modes_ = fan_modes;
|
||||||
|
this->swing_modes_ = swing_modes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
@ -49,6 +56,10 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
|
||||||
|
|
||||||
bool supports_cool_{true};
|
bool supports_cool_{true};
|
||||||
bool supports_heat_{true};
|
bool supports_heat_{true};
|
||||||
|
bool supports_dry_{false};
|
||||||
|
bool supports_fan_only_{false};
|
||||||
|
std::vector<climate::ClimateFanMode> fan_modes_ = {};
|
||||||
|
std::vector<climate::ClimateSwingMode> swing_modes_ = {};
|
||||||
|
|
||||||
remote_transmitter::RemoteTransmitterComponent *transmitter_;
|
remote_transmitter::RemoteTransmitterComponent *transmitter_;
|
||||||
sensor::Sensor *sensor_{nullptr};
|
sensor::Sensor *sensor_{nullptr};
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue