Merge pull request #26254 from home-assistant/rc

0.98.0
This commit is contained in:
Paulus Schoutsen 2019-08-28 15:21:47 -07:00 committed by GitHub
commit 688f5b7698
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
609 changed files with 13501 additions and 5361 deletions

View File

@ -158,7 +158,6 @@ omit =
homeassistant/components/ecovacs/*
homeassistant/components/eddystone_temperature/sensor.py
homeassistant/components/edimax/switch.py
homeassistant/components/edp_redy/*
homeassistant/components/egardia/*
homeassistant/components/eight_sleep/*
homeassistant/components/eliqonline/sensor.py
@ -242,7 +241,6 @@ omit =
homeassistant/components/google_cloud/tts.py
homeassistant/components/google_maps/device_tracker.py
homeassistant/components/google_travel_time/sensor.py
homeassistant/components/googlehome/*
homeassistant/components/gpmdp/media_player.py
homeassistant/components/gpsd/sensor.py
homeassistant/components/greeneye_monitor/*
@ -309,6 +307,7 @@ omit =
homeassistant/components/joaoapps_join/*
homeassistant/components/juicenet/*
homeassistant/components/kankun/switch.py
homeassistant/components/keba/*
homeassistant/components/keenetic_ndms2/device_tracker.py
homeassistant/components/keyboard/*
homeassistant/components/keyboard_remote/*
@ -317,6 +316,8 @@ omit =
homeassistant/components/knx/*
homeassistant/components/knx/climate.py
homeassistant/components/knx/cover.py
homeassistant/components/kodi/__init__.py
homeassistant/components/kodi/const.py
homeassistant/components/kodi/media_player.py
homeassistant/components/kodi/notify.py
homeassistant/components/konnected/*
@ -374,8 +375,9 @@ omit =
homeassistant/components/metoffice/weather.py
homeassistant/components/microsoft/tts.py
homeassistant/components/miflora/sensor.py
homeassistant/components/mikrotik/device_tracker.py
homeassistant/components/mikrotik/*
homeassistant/components/mill/climate.py
homeassistant/components/minio/*
homeassistant/components/mitemp_bt/sensor.py
homeassistant/components/mjpeg/camera.py
homeassistant/components/mobile_app/*
@ -467,6 +469,7 @@ omit =
homeassistant/components/plaato/*
homeassistant/components/plex/media_player.py
homeassistant/components/plex/sensor.py
homeassistant/components/plugwise/*
homeassistant/components/plum_lightpad/*
homeassistant/components/pocketcasts/sensor.py
homeassistant/components/point/*
@ -532,7 +535,6 @@ omit =
homeassistant/components/rtorrent/sensor.py
homeassistant/components/russound_rio/media_player.py
homeassistant/components/russound_rnet/media_player.py
homeassistant/components/ruter/sensor.py
homeassistant/components/sabnzbd/*
homeassistant/components/satel_integra/*
homeassistant/components/scrape/sensor.py
@ -740,6 +742,7 @@ omit =
homeassistant/components/zha/core/device.py
homeassistant/components/zha/core/gateway.py
homeassistant/components/zha/core/helpers.py
homeassistant/components/zha/core/registries.py
homeassistant/components/zha/device_entity.py
homeassistant/components/zha/entity.py
homeassistant/components/zha/light.py

View File

@ -2,11 +2,12 @@
{
"name": "Home Assistant Dev",
"context": "..",
"dockerFile": "Dockerfile",
"dockerFile": "../Dockerfile.dev",
"postCreateCommand": "pip3 install -e .",
"appPort": 8123,
"runArgs": [
"-e", "GIT_EDTIOR='code --wait'"
"-e",
"GIT_EDITOR=\"code --wait\""
],
"extensions": [
"ms-python.python",
@ -22,7 +23,6 @@
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true,
"editor.rulers": [80],
"terminal.integrated.shell.linux": "/bin/bash",
"yaml.customTags": [
"!secret scalar",
@ -32,4 +32,4 @@
"!include_dir_merge_named scalar"
]
}
}
}

6
.gitignore vendored
View File

@ -50,6 +50,7 @@ develop-eggs
.installed.cfg
lib
lib64
pip-wheel-metadata
# Logs
*.log
@ -58,9 +59,11 @@ pip-log.txt
# Unit test / coverage reports
.coverage
.tox
coverage.xml
nosetests.xml
htmlcov/
test-reports/
test-results.xml
# Translations
*.mo
@ -121,3 +124,6 @@ desktop.ini
# monkeytype
monkeytype.sqlite3
# This is left behind by Azure Restore Cache
tmp_cache

View File

@ -1,8 +1,15 @@
repos:
- repo: https://github.com/python/black
- repo: https://github.com/psf/black
rev: 19.3b0
hooks:
- id: black
args:
- --safe
- --quiet
- repo: https://gitlab.com/pycqa/flake8
rev: 3.7.8
hooks:
- id: flake8
additional_dependencies:
- flake8-docstrings==1.3.1
- pydocstyle==4.0.0

View File

@ -16,14 +16,18 @@ addons:
matrix:
fast_finish: true
include:
- python: "3.6"
- python: "3.6.0"
env: TOXENV=lint
- python: "3.6"
dist: trusty
- python: "3.6.0"
env: TOXENV=pylint
- python: "3.6"
dist: trusty
- python: "3.6.0"
env: TOXENV=typing
- python: "3.6"
dist: trusty
- python: "3.6.0"
env: TOXENV=py36
dist: trusty
- python: "3.7"
env: TOXENV=py37

View File

@ -9,10 +9,6 @@ homeassistant/*.py @home-assistant/core
homeassistant/helpers/* @home-assistant/core
homeassistant/util/* @home-assistant/core
# Virtualization
Dockerfile @home-assistant/docker
virtualization/Docker/* @home-assistant/docker
# Other code
homeassistant/scripts/check_config.py @kellerza
@ -24,6 +20,7 @@ homeassistant/components/alpha_vantage/* @fabaff
homeassistant/components/amazon_polly/* @robbiet480
homeassistant/components/ambiclimate/* @danielhiversen
homeassistant/components/ambient_station/* @bachya
homeassistant/components/androidtv/* @JeffLIrion
homeassistant/components/apache_kafka/* @bachya
homeassistant/components/api/* @home-assistant/core
homeassistant/components/aprs/* @PhilRW
@ -43,6 +40,7 @@ homeassistant/components/azure_event_hub/* @eavanvalkenburg
homeassistant/components/bitcoin/* @fabaff
homeassistant/components/bizkaibus/* @UgaitzEtxebarria
homeassistant/components/blink/* @fronzbot
homeassistant/components/bmw_connected_drive/* @gerard33
homeassistant/components/braviatv/* @robbiet480
homeassistant/components/broadlink/* @danielhiversen
homeassistant/components/brunt/* @eavanvalkenburg
@ -73,7 +71,6 @@ homeassistant/components/discogs/* @thibmaek
homeassistant/components/doorbird/* @oblogic7
homeassistant/components/dweet/* @fabaff
homeassistant/components/ecovacs/* @OverloadUT
homeassistant/components/edp_redy/* @abmantis
homeassistant/components/egardia/* @jeroenterheerdt
homeassistant/components/eight_sleep/* @mezz64
homeassistant/components/elv/* @majuss
@ -101,13 +98,13 @@ homeassistant/components/fronius/* @nielstron
homeassistant/components/frontend/* @home-assistant/frontend
homeassistant/components/gearbest/* @HerrHofrat
homeassistant/components/geniushub/* @zxdavb
homeassistant/components/geonetnz_quakes/* @exxamalte
homeassistant/components/gitter/* @fabaff
homeassistant/components/glances/* @fabaff
homeassistant/components/gntp/* @robbiet480
homeassistant/components/google_cloud/* @lufton
homeassistant/components/google_translate/* @awarecan
homeassistant/components/google_travel_time/* @robbiet480
homeassistant/components/googlehome/* @ludeeus
homeassistant/components/gpsd/* @fabaff
homeassistant/components/group/* @home-assistant/core
homeassistant/components/gtfs/* @robbiet480
@ -142,6 +139,7 @@ homeassistant/components/ipma/* @dgomes
homeassistant/components/iqvia/* @bachya
homeassistant/components/irish_rail_transport/* @ttroy50
homeassistant/components/jewish_calendar/* @tsvi
homeassistant/components/keba/* @dannerph
homeassistant/components/knx/* @Julius2342
homeassistant/components/kodi/* @armills
homeassistant/components/konnected/* @heythisisnate
@ -171,6 +169,7 @@ homeassistant/components/meteoalarm/* @rolfberkenbosch
homeassistant/components/miflora/* @danielhiversen @ChristianKuehnel
homeassistant/components/mill/* @danielhiversen
homeassistant/components/min_max/* @fabaff
homeassistant/components/minio/* @tkislan
homeassistant/components/mobile_app/* @robbiet480
homeassistant/components/monoprice/* @etsinko
homeassistant/components/moon/* @fabaff
@ -181,6 +180,7 @@ homeassistant/components/nello/* @pschmitt
homeassistant/components/ness_alarm/* @nickw444
homeassistant/components/nest/* @awarecan
homeassistant/components/netdata/* @fabaff
homeassistant/components/netgear_lte/* @amelchio
homeassistant/components/nextbus/* @vividboarder
homeassistant/components/nissan_leaf/* @filcole
homeassistant/components/nmbs/* @thibmaek
@ -203,6 +203,7 @@ homeassistant/components/philips_js/* @elupus
homeassistant/components/pi_hole/* @fabaff
homeassistant/components/plaato/* @JohNan
homeassistant/components/plant/* @ChristianKuehnel
homeassistant/components/plugwise/* @laetificat @CoMPaTech
homeassistant/components/point/* @fredrike
homeassistant/components/ps4/* @ktnrg45
homeassistant/components/ptvsd/* @swamp-ig
@ -220,7 +221,6 @@ homeassistant/components/repetier/* @MTrab
homeassistant/components/rfxtrx/* @danielhiversen
homeassistant/components/rmvtransport/* @cgtobi
homeassistant/components/roomba/* @pschmitt
homeassistant/components/ruter/* @ludeeus
homeassistant/components/scene/* @home-assistant/core
homeassistant/components/scrape/* @fabaff
homeassistant/components/script/* @home-assistant/core
@ -286,7 +286,7 @@ homeassistant/components/updater/* @home-assistant/core
homeassistant/components/upnp/* @robbiet480
homeassistant/components/uptimerobot/* @ludeeus
homeassistant/components/utility_meter/* @dgomes
homeassistant/components/velbus/* @ceral2nd
homeassistant/components/velbus/* @cereal2nd
homeassistant/components/velux/* @Julius2342
homeassistant/components/version/* @fabaff
homeassistant/components/vesync/* @markperdue @webdjoe

View File

@ -1,38 +0,0 @@
# Notice:
# When updating this file, please also update virtualization/Docker/Dockerfile.dev
# This way, the development image and the production image are kept in sync.
FROM python:3.7-buster
LABEL maintainer="Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
# Uncomment any of the following lines to disable the installation.
#ENV INSTALL_TELLSTICK no
#ENV INSTALL_OPENALPR no
#ENV INSTALL_FFMPEG no
#ENV INSTALL_LIBCEC no
#ENV INSTALL_SSOCR no
#ENV INSTALL_DLIB no
#ENV INSTALL_IPERF3 no
#ENV INSTALL_LOCALES no
VOLUME /config
WORKDIR /usr/src/app
# Copy build scripts
COPY virtualization/Docker/ virtualization/Docker/
RUN virtualization/Docker/setup_docker_prereqs
# Install hass component dependencies
COPY requirements_all.txt requirements_all.txt
RUN pip3 install --no-cache-dir -r requirements_all.txt && \
pip3 install --no-cache-dir mysqlclient psycopg2 uvloop==0.12.2 cchardet cython tensorflow
# Copy source
COPY . .
EXPOSE 8123
EXPOSE 8300
EXPOSE 51827
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]

View File

@ -16,14 +16,15 @@ RUN apt-get update \
WORKDIR /usr/src
# Setup hass-release
RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
&& cd hass-release \
&& pip3 install -e .
WORKDIR /workspace
WORKDIR /workspaces
# Install Python dependencies from requirements.txt if it exists
COPY requirements_test_all.txt homeassistant/package_constraints.txt /workspace/
COPY requirements_test_all.txt homeassistant/package_constraints.txt /workspaces/
RUN pip3 install -r requirements_test_all.txt -c package_constraints.txt
# Set the default shell to bash instead of sh

View File

@ -18,9 +18,12 @@ resources:
image: homeassistant/ci-azure:3.6
- container: 37
image: homeassistant/ci-azure:3.7
repositories:
- repository: azure
type: github
name: 'home-assistant/ci-azure'
endpoint: 'home-assistant'
variables:
- name: ArtifactFeed
value: '2df3ae11-3bf6-49bc-a809-ba0d340d6a6d'
- name: PythonMain
value: '36'
- group: codecov
@ -95,42 +98,23 @@ stages:
python.container: '37'
container: $[ variables['python.container'] ]
steps:
- script: |
python --version > .cache
displayName: 'Set python $(python.container) for requirement cache'
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: 'Restore artifacts based on Requirements'
inputs:
- template: templates/azp-step-cache.yaml@azure
parameters:
keyfile: 'requirements_test_all.txt, .cache, homeassistant/package_constraints.txt'
targetfolder: './venv'
vstsFeed: '$(ArtifactFeed)'
- script: |
set -e
python -m venv venv
build: |
set -e
python -m venv venv
. venv/bin/activate
pip install -U pip setuptools pytest-azurepipelines -c homeassistant/package_constraints.txt
pip install -r requirements_test_all.txt -c homeassistant/package_constraints.txt
# This is a TEMP. Eventually we should make sure our 4 dependencies drop typing.
# Find offending deps with `pipdeptree -r -p typing`
pip uninstall -y typing
displayName: 'Create Virtual Environment & Install Requirements'
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
# Explicit Cache Save (instead of using RestoreAndSaveCache)
# Dont wait with cache save for all the other task in this job to complete (±30 minutes), other parallel jobs might utilize this
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: 'Save artifacts based on Requirements'
inputs:
keyfile: 'requirements_test_all.txt, .cache, homeassistant/package_constraints.txt'
targetfolder: './venv'
vstsFeed: '$(ArtifactFeed)'
- script: |
. venv/bin/activate
pip install -e .
displayName: 'Install Home Assistant for python $(python.container)'
. venv/bin/activate
pip install -U pip setuptools pytest-azurepipelines -c homeassistant/package_constraints.txt
pip install -r requirements_test_all.txt -c homeassistant/package_constraints.txt
# This is a TEMP. Eventually we should make sure our 4 dependencies drop typing.
# Find offending deps with `pipdeptree -r -p typing`
pip uninstall -y typing
- script: |
. venv/bin/activate
pytest --timeout=9 --durations=10 --junitxml=test-results.xml -qq -o console_output_style=count -p no:sugar tests
script/check_dirty
displayName: 'Run pytest for python $(python.container)'
condition: and(succeeded(), ne(variables['python.container'], variables['PythonMain']))
- script: |
@ -139,6 +123,7 @@ stages:
. venv/bin/activate
pytest --timeout=9 --durations=10 --junitxml=test-results.xml --cov --cov-report=xml -qq -o console_output_style=count -p no:sugar tests
codecov --token $(codecovToken)
script/check_dirty
displayName: 'Run pytest for python $(python.container) / coverage'
condition: and(succeeded(), eq(variables['python.container'], variables['PythonMain']))
- task: PublishTestResults@2
@ -162,35 +147,17 @@ stages:
vmImage: 'ubuntu-latest'
container: $[ variables['PythonMain'] ]
steps:
- script: |
python --version > .cache
displayName: 'Set python $(PythonMain) for requirement cache'
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
displayName: 'Restore artifacts based on Requirements'
inputs:
- template: templates/azp-step-cache.yaml@azure
parameters:
keyfile: 'requirements_all.txt, requirements_test.txt, .cache, homeassistant/package_constraints.txt'
targetfolder: './venv'
vstsFeed: '$(ArtifactFeed)'
- script: |
set -e
python -m venv venv
build: |
set -e
python -m venv venv
. venv/bin/activate
pip install -U pip setuptools
pip install -r requirements_all.txt -c homeassistant/package_constraints.txt
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
displayName: 'Create Virtual Environment & Install Requirements'
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
displayName: 'Save artifacts based on Requirements'
inputs:
keyfile: 'requirements_all.txt, requirements_test.txt, .cache, homeassistant/package_constraints.txt'
targetfolder: './venv'
vstsFeed: '$(ArtifactFeed)'
- script: |
. venv/bin/activate
pip install -e .
displayName: 'Install Home Assistant for python $(PythonMain)'
. venv/bin/activate
pip install -U pip setuptools
pip install -r requirements_all.txt -c homeassistant/package_constraints.txt
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
- script: |
. venv/bin/activate
pylint homeassistant
@ -204,6 +171,7 @@ stages:
python -m venv venv
. venv/bin/activate
pip install -e .
pip install -r requirements_test.txt -c homeassistant/package_constraints.txt
displayName: 'Setup Env'
- script: |

View File

@ -3,37 +3,39 @@
trigger:
tags:
include:
- '*'
- '*'
pr: none
schedules:
- cron: "0 1 * * *"
displayName: "nightly builds"
branches:
include:
- dev
always: true
variables:
- name: versionBuilder
value: '5.2'
value: '6.3'
- group: docker
- group: github
- group: twine
resources:
repositories:
- repository: azure
type: github
name: 'home-assistant/ci-azure'
endpoint: 'home-assistant'
stages:
- stage: 'Validate'
jobs:
- job: 'VersionValidate'
- template: templates/azp-job-version.yaml@azure
parameters:
ignoreDev: true
- job: 'Permission'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
displayName: 'Use Python 3.7'
inputs:
versionSpec: '3.7'
- script: |
setup_version="$(python setup.py -V)"
branch_version="$(Build.SourceBranchName)"
if [ "${setup_version}" != "${branch_version}" ]; then
echo "Version of tag ${branch_version} don't match with ${setup_version}!"
exit 1
fi
displayName: 'Check version of branch/tag'
- script: |
sudo apt-get install -y --no-install-recommends \
jq curl
@ -48,10 +50,12 @@ stages:
echo "${created_by} is not allowed to create an release!"
exit 1
displayName: 'Check rights'
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags'))
- stage: 'Build'
jobs:
- job: 'ReleasePython'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
pool:
vmImage: 'ubuntu-latest'
steps:
@ -66,7 +70,7 @@ stages:
- script: |
export TWINE_USERNAME="$(twineUser)"
export TWINE_PASSWORD="$(twinePassword)"
twine upload dist/* --skip-existing
displayName: 'Upload pypi'
- job: 'ReleaseDocker'
@ -92,26 +96,29 @@ stages:
buildArch: 'aarch64'
buildMachine: 'qemuarm-64,raspberrypi3-64,raspberrypi4-64,odroid-c2,orangepi-prime'
steps:
- script: sudo docker login -u $(dockerUser) -p $(dockerPassword)
- template: templates/azp-step-ha-version.yaml@azure
- script: |
docker login -u $(dockerUser) -p $(dockerPassword)
displayName: 'Docker hub login'
- script: sudo docker pull homeassistant/amd64-builder:$(versionBuilder)
- script: docker pull homeassistant/amd64-builder:$(versionBuilder)
displayName: 'Install Builder'
- script: |
set -e
sudo docker run --rm --privileged \
-v ~/.docker:/root/.docker \
docker run --rm --privileged \
-v ~/.docker:/root/.docker:rw \
-v /run/docker.sock:/run/docker.sock:rw \
-v $(pwd):/homeassistant:ro \
homeassistant/amd64-builder:$(versionBuilder) \
--homeassistant $(Build.SourceBranchName) "--$(buildArch)" \
--homeassistant $(homeassistantRelease) "--$(buildArch)" \
-r https://github.com/home-assistant/hassio-homeassistant \
-t generic --docker-hub homeassistant
sudo docker run --rm --privileged \
docker run --rm --privileged \
-v ~/.docker:/root/.docker \
-v /run/docker.sock:/run/docker.sock:rw \
homeassistant/amd64-builder:$(versionBuilder) \
--homeassistant-machine "$(Build.SourceBranchName)=$(buildMachine)" \
--homeassistant-machine "$(homeassistantRelease)=$(buildMachine)" \
-r https://github.com/home-assistant/hassio-homeassistant \
-t machine --docker-hub homeassistant
displayName: 'Build Release'
@ -122,6 +129,7 @@ stages:
pool:
vmImage: 'ubuntu-latest'
steps:
- template: templates/azp-step-ha-version.yaml@azure
- script: |
sudo apt-get install -y --no-install-recommends \
git jq curl
@ -135,7 +143,7 @@ stages:
- script: |
set -e
version="$(Build.SourceBranchName)"
version="$(homeassistantRelease)"
git clone https://github.com/home-assistant/hassio-version
cd hassio-version
@ -144,11 +152,11 @@ stages:
beta_version="$(jq --raw-output '.homeassistant.default' beta.json)"
stable_version="$(jq --raw-output '.homeassistant.default' stable.json)"
if [[ "$version" =~ b ]]; then
if [[ "$version" =~ d ]]; then
sed -i "s|$dev_version|$version|g" dev.json
elif [[ "$version" =~ b ]]; then
sed -i "s|$beta_version|$version|g" beta.json
else
sed -i "s|$dev_version|$version|g" dev.json
sed -i "s|$beta_version|$version|g" beta.json
sed -i "s|$stable_version|$version|g" stable.json
fi
@ -156,3 +164,72 @@ stages:
git commit -am "Bump Home Assistant $version"
git push
displayName: 'Update version files'
- job: 'ReleaseDocker'
pool:
vmImage: 'ubuntu-latest'
steps:
- template: templates/azp-step-ha-version.yaml@azure
- script: |
docker login -u $(dockerUser) -p $(dockerPassword)
displayName: 'Docker login'
- script: |
set -e
export DOCKER_CLI_EXPERIMENTAL=enabled
function create_manifest() {
local tag_l=$1
local tag_r=$2
docker manifest create homeassistant/home-assistant:${tag_l} \
homeassistant/amd64-homeassistant:${tag_r} \
homeassistant/i386-homeassistant:${tag_r} \
homeassistant/armhf-homeassistant:${tag_r} \
homeassistant/armv7-homeassistant:${tag_r} \
homeassistant/aarch64-homeassistant:${tag_r}
docker manifest annotate homeassistant/home-assistant:${tag_l} \
homeassistant/amd64-homeassistant:${tag_r} \
--os linux --arch amd64
docker manifest annotate homeassistant/home-assistant:${tag_l} \
homeassistant/i386-homeassistant:${tag_r} \
--os linux --arch 386
docker manifest annotate homeassistant/home-assistant:${tag_l} \
homeassistant/armhf-homeassistant:${tag_r} \
--os linux --arch arm --variant=v6
docker manifest annotate homeassistant/home-assistant:${tag_l} \
homeassistant/armv7-homeassistant:${tag_r} \
--os linux --arch arm --variant=v7
docker manifest annotate homeassistant/home-assistant:${tag_l} \
homeassistant/aarch64-homeassistant:${tag_r} \
--os linux --arch arm64 --variant=v8
docker manifest push --purge homeassistant/home-assistant:${tag_l}
}
docker pull homeassistant/amd64-homeassistant:$(homeassistantRelease)
docker pull homeassistant/i386-homeassistant:$(homeassistantRelease)
docker pull homeassistant/armhf-homeassistant:$(homeassistantRelease)
docker pull homeassistant/armv7-homeassistant:$(homeassistantRelease)
docker pull homeassistant/aarch64-homeassistant:$(homeassistantRelease)
# Create version tag
create_manifest "$(homeassistantRelease)" "$(homeassistantRelease)"
# Create general tags
if [[ "$(homeassistantRelease)" =~ d ]]; then
create_manifest "dev" "$(homeassistantRelease)"
elif [[ "$(homeassistantRelease)" =~ b ]]; then
create_manifest "beta" "$(homeassistantRelease)"
create_manifest "rc" "$(homeassistantRelease)"
else
create_manifest "stable" "$(homeassistantRelease)"
create_manifest "latest" "$(homeassistantRelease)"
create_manifest "beta" "$(homeassistantRelease)"
create_manifest "rc" "$(homeassistantRelease)"
fi
displayName: 'Create Meta-Image'

View File

@ -9,91 +9,61 @@ trigger:
include:
- requirements_all.txt
pr: none
schedules:
- cron: '0 */8 * * *'
displayName: 'daily builds'
branches:
include:
- dev
always: true
variables:
- name: versionWheels
value: '1.0-3.7-alpine3.10'
- group: wheels
value: '1.1-3.7-alpine3.10'
resources:
repositories:
- repository: azure
type: github
name: 'home-assistant/ci-azure'
endpoint: 'home-assistant'
jobs:
- template: templates/azp-job-wheels.yaml@azure
parameters:
builderVersion: '$(versionWheels)'
builderApk: 'build-base;cmake;git;linux-headers;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;linux-headers;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev'
wheelsRequirement: 'requirements_wheels.txt'
wheelsRequirementDiff: 'requirements_diff.txt'
preBuild:
- script: |
cp requirements_all.txt requirements_wheels.txt
if [[ "$(Build.Reason)" =~ (Schedule|Manual) ]]; then
touch requirements_diff.txt
else
curl -s -o requirements_diff.txt https://raw.githubusercontent.com/home-assistant/home-assistant/master/requirements_all.txt
fi
- job: 'Wheels'
timeoutInMinutes: 360
pool:
vmImage: 'ubuntu-latest'
strategy:
maxParallel: 5
matrix:
amd64:
buildArch: 'amd64'
i386:
buildArch: 'i386'
armhf:
buildArch: 'armhf'
armv7:
buildArch: 'armv7'
aarch64:
buildArch: 'aarch64'
steps:
- script: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
qemu-user-static \
binfmt-support \
curl
sudo mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
sudo update-binfmts --enable qemu-arm
sudo update-binfmts --enable qemu-aarch64
displayName: 'Initial cross build'
- script: |
mkdir -p .ssh
echo -e "-----BEGIN RSA PRIVATE KEY-----\n$(wheelsSSH)\n-----END RSA PRIVATE KEY-----" >> .ssh/id_rsa
ssh-keyscan -H $(wheelsHost) >> .ssh/known_hosts
chmod 600 .ssh/*
displayName: 'Install ssh key'
- script: sudo docker pull homeassistant/$(buildArch)-wheels:$(versionWheels)
displayName: 'Install wheels builder'
- script: |
cp requirements_all.txt requirements_wheels.txt
if [[ "$(Build.Reason)" =~ (Schedule|Manual) ]]; then
touch requirements_diff.txt
else
curl -s -o requirements_diff.txt https://raw.githubusercontent.com/home-assistant/home-assistant/master/requirements_all.txt
fi
requirement_files="requirements_wheels.txt requirements_diff.txt"
for requirement_file in ${requirement_files}; do
sed -i "s|# pytradfri|pytradfri|g" ${requirement_file}
sed -i "s|# pybluez|pybluez|g" ${requirement_file}
sed -i "s|# bluepy|bluepy|g" ${requirement_file}
sed -i "s|# beacontools|beacontools|g" ${requirement_file}
sed -i "s|# RPi.GPIO|RPi.GPIO|g" ${requirement_file}
sed -i "s|# raspihats|raspihats|g" ${requirement_file}
sed -i "s|# rpi-rf|rpi-rf|g" ${requirement_file}
sed -i "s|# blinkt|blinkt|g" ${requirement_file}
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
sed -i "s|# evdev|evdev|g" ${requirement_file}
sed -i "s|# smbus-cffi|smbus-cffi|g" ${requirement_file}
sed -i "s|# i2csense|i2csense|g" ${requirement_file}
sed -i "s|# python-eq3bt|python-eq3bt|g" ${requirement_file}
sed -i "s|# pycups|pycups|g" ${requirement_file}
sed -i "s|# homekit|homekit|g" ${requirement_file}
sed -i "s|# decora_wifi|decora_wifi|g" ${requirement_file}
sed -i "s|# decora|decora|g" ${requirement_file}
sed -i "s|# PySwitchbot|PySwitchbot|g" ${requirement_file}
sed -i "s|# pySwitchmate|pySwitchmate|g" ${requirement_file}
sed -i "s|# face_recognition|face_recognition|g" ${requirement_file}
done
displayName: 'Prepare requirements files for Hass.io'
- script: |
sudo docker run --rm -v $(pwd):/data:ro -v $(pwd)/.ssh:/root/.ssh:rw \
homeassistant/$(buildArch)-wheels:$(versionWheels) \
--apk "build-base;cmake;git;linux-headers;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;linux-headers;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev" \
--index $(wheelsIndex) \
--requirement requirements_wheels.txt \
--requirement-diff requirements_diff.txt \
--upload rsync \
--remote wheels@$(wheelsHost):/opt/wheels
displayName: 'Run wheels build'
requirement_files="requirements_wheels.txt requirements_diff.txt"
for requirement_file in ${requirement_files}; do
sed -i "s|# pytradfri|pytradfri|g" ${requirement_file}
sed -i "s|# pybluez|pybluez|g" ${requirement_file}
sed -i "s|# bluepy|bluepy|g" ${requirement_file}
sed -i "s|# beacontools|beacontools|g" ${requirement_file}
sed -i "s|# RPi.GPIO|RPi.GPIO|g" ${requirement_file}
sed -i "s|# raspihats|raspihats|g" ${requirement_file}
sed -i "s|# rpi-rf|rpi-rf|g" ${requirement_file}
sed -i "s|# blinkt|blinkt|g" ${requirement_file}
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
sed -i "s|# evdev|evdev|g" ${requirement_file}
sed -i "s|# smbus-cffi|smbus-cffi|g" ${requirement_file}
sed -i "s|# i2csense|i2csense|g" ${requirement_file}
sed -i "s|# python-eq3bt|python-eq3bt|g" ${requirement_file}
sed -i "s|# pycups|pycups|g" ${requirement_file}
sed -i "s|# homekit|homekit|g" ${requirement_file}
sed -i "s|# decora_wifi|decora_wifi|g" ${requirement_file}
sed -i "s|# decora|decora|g" ${requirement_file}
sed -i "s|# PySwitchbot|PySwitchbot|g" ${requirement_file}
sed -i "s|# pySwitchmate|pySwitchmate|g" ${requirement_file}
sed -i "s|# face_recognition|face_recognition|g" ${requirement_file}
done
displayName: 'Prepare requirements files for Hass.io'

View File

@ -10,12 +10,7 @@ import threading
from typing import List, Dict, Any, TYPE_CHECKING # noqa pylint: disable=unused-import
from homeassistant import monkey_patch
from homeassistant.const import (
__version__,
EVENT_HOMEASSISTANT_START,
REQUIRED_PYTHON_VER,
RESTART_EXIT_CODE,
)
from homeassistant.const import __version__, REQUIRED_PYTHON_VER, RESTART_EXIT_CODE
if TYPE_CHECKING:
from homeassistant import core
@ -309,23 +304,10 @@ async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int:
log_no_color=args.log_no_color,
)
if args.open_ui:
# Imported here to avoid importing asyncio before monkey patch
from homeassistant.util.async_ import run_callback_threadsafe
if args.open_ui and hass.config.api is not None:
import webbrowser
def open_browser(_: Any) -> None:
"""Open the web interface in a browser."""
if hass.config.api is not None:
import webbrowser
webbrowser.open(hass.config.api.base_url)
run_callback_threadsafe(
hass.loop,
hass.bus.async_listen_once,
EVENT_HOMEASSISTANT_START,
open_browser,
)
hass.add_job(webbrowser.open, hass.config.api.base_url)
return await hass.async_run()

View File

@ -458,7 +458,7 @@ class AuthManager:
result["data"]
)
if flow.context is not None and flow.context.get("credential_only"):
if flow.context.get("credential_only"):
result["result"] = credentials
return result

View File

@ -164,14 +164,9 @@ async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.Modul
processed = hass.data[DATA_REQS] = set()
# https://github.com/python/mypy/issues/1424
req_success = await requirements.async_process_requirements(
await requirements.async_process_requirements(
hass, module_path, module.REQUIREMENTS # type: ignore
)
if not req_success:
raise HomeAssistantError(
"Unable to process requirements of mfa module {}".format(module_name)
)
processed.add(module_name)
return module

View File

@ -7,6 +7,7 @@ from typing import ( # noqa: F401
Dict,
List,
Mapping,
Optional,
Set,
Tuple,
Union,
@ -31,7 +32,7 @@ _LOGGER = logging.getLogger(__name__)
class AbstractPermissions:
"""Default permissions class."""
_cached_entity_func = None
_cached_entity_func: Optional[Callable[[str, str], bool]] = None
def _entity_func(self) -> Callable[[str, str], bool]:
"""Return a function that can test entity access."""

View File

@ -165,15 +165,10 @@ async def load_auth_provider_module(
# https://github.com/python/mypy/issues/1424
reqs = module.REQUIREMENTS # type: ignore
req_success = await requirements.async_process_requirements(
await requirements.async_process_requirements(
hass, "auth provider {}".format(provider), reqs
)
if not req_success:
raise HomeAssistantError(
"Unable to process requirements of auth provider {}".format(provider)
)
processed.add(provider)
return module

View File

@ -11,6 +11,9 @@ import logging
from homeassistant.core import split_entity_id
# mypy: allow-untyped-defs
_LOGGER = logging.getLogger(__name__)

View File

@ -20,7 +20,8 @@
"username": "Nombre de usuario",
"verify_ssl": "AdGuard Home utiliza un certificado adecuado"
},
"description": "Configure su instancia de AdGuard Home para permitir la supervisi\u00f3n y el control."
"description": "Configure su instancia de AdGuard Home para permitir la supervisi\u00f3n y el control.",
"title": "Enlace su AdGuard Home."
}
},
"title": "AdGuard Home"

View File

@ -19,6 +19,7 @@
"username": "Nom d'utilisateur"
}
}
}
},
"title": "AdGuard Home"
}
}

View File

@ -0,0 +1,7 @@
{
"config": {
"abort": {
"existing_instance_updated": "Postoje\u0107a konfiguracija je a\u017eurirana."
}
}
}

View File

@ -0,0 +1,15 @@
{
"config": {
"error": {
"connection_error": "Gagal terhubung."
},
"step": {
"user": {
"data": {
"password": "Kata sandi",
"port": "Port"
}
}
}
}
}

View File

@ -47,7 +47,7 @@ CONF_DISPLAY_CATEGORIES = "display_categories"
API_TEMP_UNITS = {TEMP_FAHRENHEIT: "FAHRENHEIT", TEMP_CELSIUS: "CELSIUS"}
# Needs to be ordered dict for `async_api_set_thermostat_mode` which does a
# reverse mapping of this dict and we want to map the first occurrance of OFF
# reverse mapping of this dict and we want to map the first occurrence of OFF
# back to HA state.
API_THERMOSTAT_MODES = OrderedDict(
[

View File

@ -264,7 +264,9 @@ class ClimateCapabilities(AlexaEntity):
def interfaces(self):
"""Yield the supported interfaces."""
# If we support two modes, one being off, we allow turning on too.
if climate.HVAC_MODE_OFF in self.entity.attributes[climate.ATTR_HVAC_MODES]:
if climate.HVAC_MODE_OFF in self.entity.attributes.get(
climate.ATTR_HVAC_MODES, []
):
yield AlexaPowerController(self.entity)
yield AlexaThermostatController(self.hass, self.entity)

View File

@ -163,7 +163,7 @@ class AlexaResponse:
The Alexa response includes a list of properties which provides
feedback on how states have changed. For example if a user asks,
"Alexa, set theromstat to 20 degrees", the API expects a response with
"Alexa, set thermostat to 20 degrees", the API expects a response with
the new value of the property, and Alexa will respond to the user
"Thermostat set to 20 degrees".

View File

@ -45,7 +45,7 @@ SUPPORTED_VOICES = [
"Ruben",
"Lotte", # Dutch
"Russell",
"Nicole", # English Austrailian
"Nicole", # English Australian
"Brian",
"Amy",
"Emma", # English

View File

@ -7,6 +7,17 @@
},
"create_entry": {
"default": "Autenticaci\u00f3n exitosa con Ambiclimate"
}
},
"error": {
"follow_link": "Por favor, siga el enlace y autent\u00edquese antes de presionar Enviar",
"no_token": "No autenticado con Ambiclimate"
},
"step": {
"auth": {
"description": "Por favor, siga este [link]('authorization_url') y <b>Permitir</b> acceso a su cuenta de Ambiclimate, luego vuelva y presione <b>Enviar</b> a continuaci\u00f3n.\n(Aseg\u00farese de que la url de devoluci\u00f3n de llamada especificada es {cb_url})",
"title": "Autenticaci\u00f3n de Ambiclimate"
}
},
"title": "Ambiclimate"
}
}

View File

@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/components/ambient_station",
"requirements": [
"aioambient==0.3.1"
"aioambient==0.3.2"
],
"dependencies": [],
"codeowners": [

View File

@ -3,8 +3,8 @@
"name": "Androidtv",
"documentation": "https://www.home-assistant.io/components/androidtv",
"requirements": [
"androidtv==0.0.18"
"androidtv==0.0.24"
],
"dependencies": [],
"codeowners": []
"codeowners": ["@JeffLIrion"]
}

View File

@ -3,6 +3,9 @@ import functools
import logging
import voluptuous as vol
from androidtv import setup, ha_state_detection_rules_validator
from androidtv.constants import APPS, KEYS
from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA
from homeassistant.components.media_player.const import (
SUPPORT_NEXT_TRACK,
@ -64,6 +67,7 @@ CONF_ADB_SERVER_IP = "adb_server_ip"
CONF_ADB_SERVER_PORT = "adb_server_port"
CONF_APPS = "apps"
CONF_GET_SOURCES = "get_sources"
CONF_STATE_DETECTION_RULES = "state_detection_rules"
CONF_TURN_ON_COMMAND = "turn_on_command"
CONF_TURN_OFF_COMMAND = "turn_off_command"
@ -99,6 +103,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
vol.Optional(CONF_APPS, default=dict()): vol.Schema({cv.string: cv.string}),
vol.Optional(CONF_TURN_ON_COMMAND): cv.string,
vol.Optional(CONF_TURN_OFF_COMMAND): cv.string,
vol.Optional(CONF_STATE_DETECTION_RULES, default={}): vol.Schema(
{cv.string: ha_state_detection_rules_validator(vol.Invalid)}
),
}
)
@ -114,8 +121,6 @@ ANDROIDTV_STATES = {
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Android TV / Fire TV platform."""
from androidtv import setup
hass.data.setdefault(ANDROIDTV_DOMAIN, {})
host = "{0}:{1}".format(config[CONF_HOST], config[CONF_PORT])
@ -125,12 +130,19 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
adb_log = "using Python ADB implementation "
if CONF_ADBKEY in config:
aftv = setup(
host, config[CONF_ADBKEY], device_class=config[CONF_DEVICE_CLASS]
host,
config[CONF_ADBKEY],
device_class=config[CONF_DEVICE_CLASS],
state_detection_rules=config[CONF_STATE_DETECTION_RULES],
)
adb_log += "with adbkey='{0}'".format(config[CONF_ADBKEY])
else:
aftv = setup(host, device_class=config[CONF_DEVICE_CLASS])
aftv = setup(
host,
device_class=config[CONF_DEVICE_CLASS],
state_detection_rules=config[CONF_STATE_DETECTION_RULES],
)
adb_log += "without adbkey authentication"
else:
# Use "pure-python-adb" (communicate with ADB server)
@ -139,6 +151,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
adb_server_ip=config[CONF_ADB_SERVER_IP],
adb_server_port=config[CONF_ADB_SERVER_PORT],
device_class=config[CONF_DEVICE_CLASS],
state_detection_rules=config[CONF_STATE_DETECTION_RULES],
)
adb_log = "using ADB server at {0}:{1}".format(
config[CONF_ADB_SERVER_IP], config[CONF_ADB_SERVER_PORT]
@ -182,7 +195,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
device_name = config[CONF_NAME] if CONF_NAME in config else "Fire TV"
add_entities([device])
_LOGGER.debug("Setup %s at %s%s", device_name, host, adb_log)
_LOGGER.debug("Setup %s at %s %s", device_name, host, adb_log)
hass.data[ANDROIDTV_DOMAIN][host] = device
if hass.services.has_service(ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND):
@ -251,14 +264,15 @@ class ADBDevice(MediaPlayerDevice):
def __init__(self, aftv, name, apps, turn_on_command, turn_off_command):
"""Initialize the Android TV / Fire TV device."""
from androidtv.constants import APPS, KEYS
self.aftv = aftv
self._name = name
self._apps = APPS
self._apps = APPS.copy()
self._apps.update(apps)
self._keys = KEYS
self._device_properties = self.aftv.device_properties
self._unique_id = self._device_properties.get("serialno")
self.turn_on_command = turn_on_command
self.turn_off_command = turn_off_command
@ -327,6 +341,11 @@ class ADBDevice(MediaPlayerDevice):
"""Return the state of the player."""
return self._state
@property
def unique_id(self):
"""Return the device unique id."""
return self._unique_id
@adb_decorator()
def media_play(self):
"""Send play command."""
@ -401,9 +420,7 @@ class AndroidTVDevice(ADBDevice):
super().__init__(aftv, name, apps, turn_on_command, turn_off_command)
self._device = None
self._device_properties = self.aftv.device_properties
self._is_volume_muted = None
self._unique_id = self._device_properties.get("serialno")
self._volume_level = None
@adb_decorator(override_available=True)
@ -443,11 +460,6 @@ class AndroidTVDevice(ADBDevice):
"""Flag media player features that are supported."""
return SUPPORT_ANDROIDTV
@property
def unique_id(self):
"""Return the device unique id."""
return self._unique_id
@property
def volume_level(self):
"""Return the volume level."""

View File

@ -1,5 +0,0 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -1,5 +0,0 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -1,17 +0,0 @@
{
"config": {
"abort": {
"one": "Een",
"other": "Ander"
},
"error": {
"one": "Een",
"other": "Ander"
},
"step": {
"one": "Een",
"other": "Ander"
},
"title": "Arcam FMJ"
}
}

View File

@ -1,23 +0,0 @@
{
"config": {
"abort": {
"few": "kilka",
"many": "wiele",
"one": "jeden",
"other": "inne"
},
"error": {
"few": "kilka",
"many": "wiele",
"one": "jeden",
"other": "inne"
},
"step": {
"few": "kilka",
"many": "wiele",
"one": "jeden",
"other": "inne"
},
"title": "Arcam FMJ"
}
}

View File

@ -1,5 +0,0 @@
{
"config": {
"title": "Arcam FMJ"
}
}

View File

@ -124,7 +124,7 @@ class ArcamFmj(MediaPlayerDevice):
return support
async def async_added_to_hass(self):
"""Once registed add listener for events."""
"""Once registered, add listener for events."""
await self._state.start()
@callback

View File

@ -1,8 +1,5 @@
{
"config": {
"title": "Arcam FMJ",
"step": {},
"error": {},
"abort": {}
"title": "Arcam FMJ"
}
}

View File

@ -16,9 +16,13 @@
"description": "Se ha enviado una contrase\u00f1a \u00fanica a trav\u00e9s de **notify.{notify_service}**. Por favor ingr\u00e9selo a continuaci\u00f3n:",
"title": "Verificar la configuracion"
}
}
},
"title": "Notificar contrase\u00f1a de un solo uso"
},
"totp": {
"error": {
"invalid_code": "C\u00f3digo no v\u00e1lido, por favor vuelva a intentarlo. Si recibe este error constantemente, aseg\u00farese de que el reloj de su sistema Home Assistant sea exacto."
},
"step": {
"init": {
"description": "Para activar la autenticaci\u00f3n de dos factores utilizando contrase\u00f1as de un solo uso basadas en el tiempo, escanee el c\u00f3digo QR con su aplicaci\u00f3n de autenticaci\u00f3n. Si no tiene uno, le recomendamos [Autenticador de Google] (https://support.google.com/accounts/answer/1066447) o [Authy] (https://authy.com/). \n\n {qr_code} \n \n Despu\u00e9s de escanear el c\u00f3digo, ingrese el c\u00f3digo de seis d\u00edgitos de su aplicaci\u00f3n para verificar la configuraci\u00f3n. Si tiene problemas para escanear el c\u00f3digo QR, realice una configuraci\u00f3n manual con el c\u00f3digo ** ` {code} ` **.",

View File

@ -31,7 +31,7 @@ async def async_setup(hass):
"""Init mfa setup flow manager."""
async def _async_create_setup_flow(handler, context, data):
"""Create a setup flow. hanlder is a mfa module."""
"""Create a setup flow. handler is a mfa module."""
mfa_module = hass.auth.get_auth_mfa_module(handler)
if mfa_module is None:
raise ValueError("Mfa module {} is not found".format(handler))

View File

@ -30,6 +30,10 @@ from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.loader import bind_hass
from homeassistant.util.dt import parse_datetime, utcnow
# mypy: allow-incomplete-defs, allow-untyped-calls, allow-untyped-defs
# mypy: no-check-untyped-defs, no-warn-return-any
DOMAIN = "automation"
ENTITY_ID_FORMAT = DOMAIN + ".{}"

View File

@ -5,6 +5,8 @@ from homeassistant.const import CONF_DOMAIN, CONF_PLATFORM
from homeassistant.loader import async_get_integration
# mypy: allow-untyped-defs, no-check-untyped-defs
TRIGGER_SCHEMA = vol.Schema(
{vol.Required(CONF_PLATFORM): "device", vol.Required(CONF_DOMAIN): str},
extra=vol.ALLOW_EXTRA,

View File

@ -7,6 +7,9 @@ from homeassistant.core import callback
from homeassistant.const import CONF_PLATFORM
from homeassistant.helpers import config_validation as cv
# mypy: allow-untyped-defs
CONF_EVENT_TYPE = "event_type"
CONF_EVENT_DATA = "event_data"

View File

@ -13,6 +13,9 @@ from homeassistant.const import (
from homeassistant.helpers import condition, config_validation as cv
from homeassistant.helpers.config_validation import entity_domain
# mypy: allow-untyped-defs, no-check-untyped-defs
EVENT_ENTER = "enter"
EVENT_LEAVE = "leave"
DEFAULT_EVENT = EVENT_ENTER

View File

@ -6,6 +6,9 @@ import voluptuous as vol
from homeassistant.core import callback, CoreState
from homeassistant.const import CONF_PLATFORM, CONF_EVENT, EVENT_HOMEASSISTANT_STOP
# mypy: allow-untyped-defs
EVENT_START = "start"
EVENT_SHUTDOWN = "shutdown"
_LOGGER = logging.getLogger(__name__)

View File

@ -9,6 +9,9 @@ import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_utc_time
# mypy: allow-untyped-defs, no-check-untyped-defs
_LOGGER = logging.getLogger(__name__)
CONF_NUMBER = "number"

View File

@ -8,6 +8,9 @@ from homeassistant.components import mqtt
from homeassistant.const import CONF_PLATFORM, CONF_PAYLOAD
import homeassistant.helpers.config_validation as cv
# mypy: allow-untyped-defs
CONF_ENCODING = "encoding"
CONF_TOPIC = "topic"
DEFAULT_ENCODING = "utf-8"

View File

@ -16,6 +16,9 @@ from homeassistant.const import (
from homeassistant.helpers.event import async_track_state_change, async_track_same_state
from homeassistant.helpers import condition, config_validation as cv, template
# mypy: allow-untyped-defs, no-check-untyped-defs
TRIGGER_SCHEMA = vol.All(
vol.Schema(
{

View File

@ -9,6 +9,9 @@ from homeassistant.const import MATCH_ALL, CONF_PLATFORM, CONF_FOR
from homeassistant.helpers import config_validation as cv, template
from homeassistant.helpers.event import async_track_state_change, async_track_same_state
# mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs
_LOGGER = logging.getLogger(__name__)
CONF_ENTITY_ID = "entity_id"

View File

@ -14,6 +14,9 @@ from homeassistant.const import (
from homeassistant.helpers.event import async_track_sunrise, async_track_sunset
import homeassistant.helpers.config_validation as cv
# mypy: allow-untyped-defs, no-check-untyped-defs
_LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = vol.Schema(

View File

@ -10,6 +10,9 @@ from homeassistant.helpers import condition
from homeassistant.helpers.event import async_track_same_state, async_track_template
from homeassistant.helpers import config_validation as cv, template
# mypy: allow-untyped-defs, no-check-untyped-defs
_LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema(

View File

@ -8,6 +8,9 @@ from homeassistant.const import CONF_AT, CONF_PLATFORM
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import async_track_time_change
# mypy: allow-untyped-defs, no-check-untyped-defs
_LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = vol.Schema(

View File

@ -8,6 +8,9 @@ from homeassistant.const import CONF_PLATFORM
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import async_track_time_change
# mypy: allow-untyped-defs, no-check-untyped-defs
CONF_HOURS = "hours"
CONF_MINUTES = "minutes"
CONF_SECONDS = "seconds"

View File

@ -11,6 +11,9 @@ import homeassistant.helpers.config_validation as cv
from . import DOMAIN as AUTOMATION_DOMAIN
# mypy: allow-untyped-defs
DEPENDENCIES = ("webhook",)
_LOGGER = logging.getLogger(__name__)

View File

@ -12,6 +12,9 @@ from homeassistant.const import (
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers import condition, config_validation as cv, location
# mypy: allow-untyped-defs, no-check-untyped-defs
EVENT_ENTER = "enter"
EVENT_LEAVE = "leave"
DEFAULT_EVENT = EVENT_ENTER

View File

@ -2,10 +2,13 @@
"config": {
"abort": {
"already_configured": "El dispositivo ya est\u00e1 configurado",
"bad_config_file": "Datos err\u00f3neos del archivo de configuraci\u00f3n"
"bad_config_file": "Datos err\u00f3neos del archivo de configuraci\u00f3n",
"link_local_address": "Las direcciones locales de enlace no son compatibles",
"not_axis_device": "El dispositivo descubierto no es un dispositivo de Axis"
},
"error": {
"already_configured": "El dispositivo ya est\u00e1 configurado",
"already_in_progress": "El flujo de configuraci\u00f3n para el dispositivo ya est\u00e1 en progreso.",
"device_unavailable": "El dispositivo no est\u00e1 disponible",
"faulty_credentials": "Credenciales de usuario incorrectas"
},
@ -15,8 +18,10 @@
"password": "Contrase\u00f1a",
"port": "Puerto",
"username": "Nombre de usuario"
}
},
"title": "Configurar dispositivo Axis"
}
}
},
"title": "Dispositivo Axis"
}
}

View File

@ -3,7 +3,8 @@
"abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"bad_config_file": "Mauvaises donn\u00e9es du fichier de configuration",
"link_local_address": "Les adresses locales ne sont pas prises en charge"
"link_local_address": "Les adresses locales ne sont pas prises en charge",
"not_axis_device": "L'appareil d\u00e9couvert n'est pas un appareil Axis"
},
"error": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",

View File

@ -13,6 +13,9 @@ from homeassistant.helpers.config_validation import ( # noqa
PLATFORM_SCHEMA_BASE,
)
# mypy: allow-untyped-defs, no-check-untyped-defs
DOMAIN = "binary_sensor"
SCAN_INTERVAL = timedelta(seconds=30)

View File

@ -143,7 +143,10 @@ class BMWConnectedDriveAccount:
for listener in self._update_listeners:
listener()
except IOError as exception:
_LOGGER.error("Error updating the vehicle state")
_LOGGER.error(
"Could not connect to the BMW Connected Drive portal. "
"The vehicle state could not be updated."
)
_LOGGER.exception(exception)
def add_update_listener(self, listener):

View File

@ -9,17 +9,17 @@ from . import DOMAIN as BMW_DOMAIN
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
"lids": ["Doors", "opening"],
"windows": ["Windows", "opening"],
"door_lock_state": ["Door lock state", "safety"],
"lights_parking": ["Parking lights", "light"],
"condition_based_services": ["Condition based services", "problem"],
"check_control_messages": ["Control messages", "problem"],
"lids": ["Doors", "opening", "mdi:car-door"],
"windows": ["Windows", "opening", "mdi:car-door"],
"door_lock_state": ["Door lock state", "safety", "mdi:car-key"],
"lights_parking": ["Parking lights", "light", "mdi:car-parking-lights"],
"condition_based_services": ["Condition based services", "problem", "mdi:wrench"],
"check_control_messages": ["Control messages", "problem", "mdi:car-tire-alert"],
}
SENSOR_TYPES_ELEC = {
"charging_status": ["Charging status", "power"],
"connection_status": ["Connection status", "plug"],
"charging_status": ["Charging status", "power", "mdi:ev-station"],
"connection_status": ["Connection status", "plug", "mdi:car-electric"],
}
SENSOR_TYPES_ELEC.update(SENSOR_TYPES)
@ -35,24 +35,28 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
if vehicle.has_hv_battery:
_LOGGER.debug("BMW with a high voltage battery")
for key, value in sorted(SENSOR_TYPES_ELEC.items()):
device = BMWConnectedDriveSensor(
account, vehicle, key, value[0], value[1]
)
devices.append(device)
if key in vehicle.available_attributes:
device = BMWConnectedDriveSensor(
account, vehicle, key, value[0], value[1], value[2]
)
devices.append(device)
elif vehicle.has_internal_combustion_engine:
_LOGGER.debug("BMW with an internal combustion engine")
for key, value in sorted(SENSOR_TYPES.items()):
device = BMWConnectedDriveSensor(
account, vehicle, key, value[0], value[1]
)
devices.append(device)
if key in vehicle.available_attributes:
device = BMWConnectedDriveSensor(
account, vehicle, key, value[0], value[1], value[2]
)
devices.append(device)
add_entities(devices, True)
class BMWConnectedDriveSensor(BinarySensorDevice):
"""Representation of a BMW vehicle binary sensor."""
def __init__(self, account, vehicle, attribute: str, sensor_name, device_class):
def __init__(
self, account, vehicle, attribute: str, sensor_name, device_class, icon
):
"""Constructor."""
self._account = account
self._vehicle = vehicle
@ -61,6 +65,7 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
self._unique_id = "{}-{}".format(self._vehicle.vin, self._attribute)
self._sensor_name = sensor_name
self._device_class = device_class
self._icon = icon
self._state = None
@property
@ -81,6 +86,11 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
"""Return the name of the binary sensor."""
return self._name
@property
def icon(self):
"""Icon to use in the frontend, if any."""
return self._icon
@property
def device_class(self):
"""Return the class of the binary sensor."""
@ -112,23 +122,19 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
for report in vehicle_state.condition_based_services:
result.update(self._format_cbs_report(report))
elif self._attribute == "check_control_messages":
check_control_messages = vehicle_state.check_control_messages
if not check_control_messages:
result["check_control_messages"] = "OK"
else:
check_control_messages = vehicle_state.has_check_control_messages
if check_control_messages:
cbs_list = []
for message in check_control_messages:
cbs_list.append(message["ccmDescriptionShort"])
result["check_control_messages"] = cbs_list
else:
result["check_control_messages"] = "OK"
elif self._attribute == "charging_status":
result["charging_status"] = vehicle_state.charging_status.value
# pylint: disable=protected-access
result["last_charging_end_result"] = vehicle_state._attributes[
"lastChargingEndResult"
]
if self._attribute == "connection_status":
# pylint: disable=protected-access
result["connection_status"] = vehicle_state._attributes["connectionStatus"]
result["last_charging_end_result"] = vehicle_state.last_charging_end_result
elif self._attribute == "connection_status":
result["connection_status"] = vehicle_state.connection_status
return sorted(result.items())
@ -166,8 +172,7 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
# device class plug: On means device is plugged in,
# Off means device is unplugged
if self._attribute == "connection_status":
# pylint: disable=protected-access
self._state = vehicle_state._attributes["connectionStatus"] == "CONNECTED"
self._state = vehicle_state.connection_status == "CONNECTED"
def _format_cbs_report(self, report):
result = {}

View File

@ -1,11 +1,12 @@
{
"domain": "bmw_connected_drive",
"name": "Bmw connected drive",
"name": "BMW Connected Drive",
"documentation": "https://www.home-assistant.io/components/bmw_connected_drive",
"requirements": [
"bimmer_connected==0.5.3"
"bimmer_connected==0.6.0"
],
"dependencies": [],
"codeowners": [
"@gerard33"
]
}
}

View File

@ -51,14 +51,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
for account in accounts:
for vehicle in account.account.vehicles:
for attribute_name in vehicle.drive_train_attributes:
device = BMWConnectedDriveSensor(
account, vehicle, attribute_name, attribute_info
)
devices.append(device)
device = BMWConnectedDriveSensor(
account, vehicle, "mileage", attribute_info
)
devices.append(device)
if attribute_name in vehicle.available_attributes:
device = BMWConnectedDriveSensor(
account, vehicle, attribute_name, attribute_info
)
devices.append(device)
add_entities(devices, True)

View File

@ -34,7 +34,9 @@ TIME_BETWEEN_UPDATES = timedelta(seconds=5)
DEFAULT_NAME = "Broadlink switch"
DEFAULT_TIMEOUT = 10
DEFAULT_RETRY = 2
CONF_SLOTS = "slots"
CONF_RETRY = "retry"
RM_TYPES = [
"rm",
@ -82,6 +84,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
vol.Optional(CONF_FRIENDLY_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_TYPE, default=SWITCH_TYPES[0]): vol.In(SWITCH_TYPES),
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_RETRY, default=DEFAULT_RETRY): cv.positive_int,
}
)
@ -96,6 +99,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
friendly_name = config.get(CONF_FRIENDLY_NAME)
mac_addr = binascii.unhexlify(config.get(CONF_MAC).encode().replace(b":", b""))
switch_type = config.get(CONF_TYPE)
retry_times = config.get(CONF_RETRY)
def _get_mp1_slot_name(switch_friendly_name, slot):
"""Get slot name."""
@ -116,21 +120,26 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
broadlink_device,
device_config.get(CONF_COMMAND_ON),
device_config.get(CONF_COMMAND_OFF),
retry_times,
)
)
elif switch_type in SP1_TYPES:
broadlink_device = broadlink.sp1((ip_addr, 80), mac_addr, None)
switches = [BroadlinkSP1Switch(friendly_name, broadlink_device)]
switches = [BroadlinkSP1Switch(friendly_name, broadlink_device, retry_times)]
elif switch_type in SP2_TYPES:
broadlink_device = broadlink.sp2((ip_addr, 80), mac_addr, None)
switches = [BroadlinkSP2Switch(friendly_name, broadlink_device)]
switches = [BroadlinkSP2Switch(friendly_name, broadlink_device, retry_times)]
elif switch_type in MP1_TYPES:
switches = []
broadlink_device = broadlink.mp1((ip_addr, 80), mac_addr, None)
parent_device = BroadlinkMP1Switch(broadlink_device)
parent_device = BroadlinkMP1Switch(broadlink_device, retry_times)
for i in range(1, 5):
slot = BroadlinkMP1Slot(
_get_mp1_slot_name(friendly_name, i), broadlink_device, i, parent_device
_get_mp1_slot_name(friendly_name, i),
broadlink_device,
i,
parent_device,
retry_times,
)
switches.append(slot)
@ -146,7 +155,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class BroadlinkRMSwitch(SwitchDevice, RestoreEntity):
"""Representation of an Broadlink switch."""
def __init__(self, name, friendly_name, device, command_on, command_off):
def __init__(
self, name, friendly_name, device, command_on, command_off, retry_times
):
"""Initialize the switch."""
self.entity_id = ENTITY_ID_FORMAT.format(slugify(name))
self._name = friendly_name
@ -155,6 +166,8 @@ class BroadlinkRMSwitch(SwitchDevice, RestoreEntity):
self._command_off = command_off
self._device = device
self._is_available = False
self._retry_times = retry_times
_LOGGER.debug("_retry_times : %s", self._retry_times)
async def async_added_to_hass(self):
"""Call when entity about to be added to hass."""
@ -190,17 +203,17 @@ class BroadlinkRMSwitch(SwitchDevice, RestoreEntity):
def turn_on(self, **kwargs):
"""Turn the device on."""
if self._sendpacket(self._command_on):
if self._sendpacket(self._command_on, self._retry_times):
self._state = True
self.schedule_update_ha_state()
def turn_off(self, **kwargs):
"""Turn the device off."""
if self._sendpacket(self._command_off):
if self._sendpacket(self._command_off, self._retry_times):
self._state = False
self.schedule_update_ha_state()
def _sendpacket(self, packet, retry=2):
def _sendpacket(self, packet, retry):
"""Send packet to device."""
if packet is None:
_LOGGER.debug("Empty packet")
@ -211,12 +224,13 @@ class BroadlinkRMSwitch(SwitchDevice, RestoreEntity):
if retry < 1:
_LOGGER.error("Error during sending a packet: %s", error)
return False
if not self._auth():
if not self._auth(self._retry_times):
return False
return self._sendpacket(packet, retry - 1)
return True
def _auth(self, retry=2):
def _auth(self, retry):
_LOGGER.debug("_auth : retry=%s", retry)
try:
auth = self._device.auth()
except OSError:
@ -231,14 +245,14 @@ class BroadlinkRMSwitch(SwitchDevice, RestoreEntity):
class BroadlinkSP1Switch(BroadlinkRMSwitch):
"""Representation of an Broadlink switch."""
def __init__(self, friendly_name, device):
def __init__(self, friendly_name, device, retry_times):
"""Initialize the switch."""
super().__init__(friendly_name, friendly_name, device, None, None)
super().__init__(friendly_name, friendly_name, device, None, None, retry_times)
self._command_on = 1
self._command_off = 0
self._load_power = None
def _sendpacket(self, packet, retry=2):
def _sendpacket(self, packet, retry):
"""Send packet to device."""
try:
self._device.set_power(packet)
@ -246,7 +260,7 @@ class BroadlinkSP1Switch(BroadlinkRMSwitch):
if retry < 1:
_LOGGER.error("Error during sending a packet: %s", error)
return False
if not self._auth():
if not self._auth(self._retry_times):
return False
return self._sendpacket(packet, retry - 1)
return True
@ -275,10 +289,11 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch):
def update(self):
"""Synchronize state with switch."""
self._update()
self._update(self._retry_times)
def _update(self, retry=2):
def _update(self, retry):
"""Update the state of the device."""
_LOGGER.debug("_update : retry=%s", retry)
try:
state = self._device.check_power()
load_power = self._device.get_energy()
@ -287,7 +302,7 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch):
_LOGGER.error("Error during updating the state: %s", error)
self._is_available = False
return
if not self._auth():
if not self._auth(self._retry_times):
return
return self._update(retry - 1)
if state is None and retry > 0:
@ -300,9 +315,9 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch):
class BroadlinkMP1Slot(BroadlinkRMSwitch):
"""Representation of a slot of Broadlink switch."""
def __init__(self, friendly_name, device, slot, parent_device):
def __init__(self, friendly_name, device, slot, parent_device, retry_times):
"""Initialize the slot of switch."""
super().__init__(friendly_name, friendly_name, device, None, None)
super().__init__(friendly_name, friendly_name, device, None, None, retry_times)
self._command_on = 1
self._command_off = 0
self._slot = slot
@ -313,7 +328,7 @@ class BroadlinkMP1Slot(BroadlinkRMSwitch):
"""Return true if unable to access real state of entity."""
return False
def _sendpacket(self, packet, retry=2):
def _sendpacket(self, packet, retry):
"""Send packet to device."""
try:
self._device.set_power(self._slot, packet)
@ -322,7 +337,7 @@ class BroadlinkMP1Slot(BroadlinkRMSwitch):
_LOGGER.error("Error during sending a packet: %s", error)
self._is_available = False
return False
if not self._auth():
if not self._auth(self._retry_times):
return False
return self._sendpacket(packet, max(0, retry - 1))
self._is_available = True
@ -337,15 +352,20 @@ class BroadlinkMP1Slot(BroadlinkRMSwitch):
"""Trigger update for all switches on the parent device."""
self._parent_device.update()
self._state = self._parent_device.get_outlet_status(self._slot)
if self._state is None:
self._is_available = False
else:
self._is_available = True
class BroadlinkMP1Switch:
"""Representation of a Broadlink switch - To fetch states of all slots."""
def __init__(self, device):
def __init__(self, device, retry_times):
"""Initialize the switch."""
self._device = device
self._states = None
self._retry_times = retry_times
def get_outlet_status(self, slot):
"""Get status of outlet from cached status list."""
@ -356,9 +376,9 @@ class BroadlinkMP1Switch:
@Throttle(TIME_BETWEEN_UPDATES)
def update(self):
"""Fetch new state data for this device."""
self._update()
self._update(self._retry_times)
def _update(self, retry=2):
def _update(self, retry):
"""Update the state of the device."""
try:
states = self._device.check_power()
@ -366,14 +386,14 @@ class BroadlinkMP1Switch:
if retry < 1:
_LOGGER.error("Error during updating the state: %s", error)
return
if not self._auth():
if not self._auth(self._retry_times):
return
return self._update(max(0, retry - 1))
if states is None and retry > 0:
return self._update(max(0, retry - 1))
self._states = states
def _auth(self, retry=2):
def _auth(self, retry):
"""Authenticate the device."""
try:
auth = self._device.auth()

View File

@ -17,6 +17,9 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.template import DATE_STR_FORMAT
from homeassistant.util import dt
# mypy: allow-untyped-defs, no-check-untyped-defs
_LOGGER = logging.getLogger(__name__)
DOMAIN = "calendar"

View File

@ -52,6 +52,9 @@ from homeassistant.setup import async_when_setup
from .const import DOMAIN, DATA_CAMERA_PREFS
from .prefs import CameraPreferences
# mypy: allow-untyped-calls, allow-untyped-defs
_LOGGER = logging.getLogger(__name__)
SERVICE_ENABLE_MOTION = "enable_motion_detection"
@ -311,7 +314,7 @@ class Camera(Entity):
"""Initialize a camera."""
self.is_streaming = False
self.content_type = DEFAULT_CONTENT_TYPE
self.access_tokens = collections.deque([], 2)
self.access_tokens: collections.deque = collections.deque([], 2)
self.async_update_token()
@property

View File

@ -1,6 +1,9 @@
"""Preference management for camera component."""
from .const import DOMAIN, PREF_PRELOAD_STREAM
# mypy: allow-untyped-defs, no-check-untyped-defs
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
_UNDEF = object()

View File

@ -0,0 +1,10 @@
{
"config": {
"step": {
"confirm": {
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@ -70,7 +70,6 @@ from .const import (
SUPPORT_TARGET_TEMPERATURE_RANGE,
SUPPORT_TARGET_TEMPERATURE,
)
from .reproduce_state import async_reproduce_states # noqa
DEFAULT_MIN_TEMP = 7
DEFAULT_MAX_TEMP = 35

View File

@ -108,7 +108,7 @@ ATTR_TARGET_TEMP_STEP = "target_temp_step"
DEFAULT_MIN_TEMP = 7
DEFAULT_MAX_TEMP = 35
DEFAULT_MIN_HUMITIDY = 30
DEFAULT_MIN_HUMIDITY = 30
DEFAULT_MAX_HUMIDITY = 99
DOMAIN = "climate"

View File

@ -5,7 +5,6 @@ from typing import Iterable, Optional
from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.core import Context, State
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.loader import bind_hass
from .const import (
ATTR_AUX_HEAT,
@ -69,7 +68,6 @@ async def _async_reproduce_states(
await call_service(SERVICE_SET_HUMIDITY, [ATTR_HUMIDITY])
@bind_hass
async def async_reproduce_states(
hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None
) -> None:

View File

@ -2,7 +2,7 @@
"domain": "cloud",
"name": "Cloud",
"documentation": "https://www.home-assistant.io/components/cloud",
"requirements": ["hass-nabucasa==0.16"],
"requirements": ["hass-nabucasa==0.17"],
"dependencies": ["http", "webhook"],
"codeowners": ["@home-assistant/cloud"]
}

View File

@ -1,6 +1,10 @@
"""Http views to control the config manager."""
import aiohttp.web_exceptions
import voluptuous as vol
from homeassistant import config_entries, data_entry_flow
from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES
from homeassistant.components import websocket_api
from homeassistant.components.http import HomeAssistantView
from homeassistant.exceptions import Unauthorized
from homeassistant.helpers.data_entry_flow import (
@ -17,12 +21,18 @@ async def async_setup(hass):
hass.http.register_view(ConfigManagerFlowIndexView(hass.config_entries.flow))
hass.http.register_view(ConfigManagerFlowResourceView(hass.config_entries.flow))
hass.http.register_view(ConfigManagerAvailableFlowView)
hass.http.register_view(
OptionManagerFlowIndexView(hass.config_entries.options.flow)
)
hass.http.register_view(
OptionManagerFlowResourceView(hass.config_entries.options.flow)
)
hass.components.websocket_api.async_register_command(config_entries_progress)
hass.components.websocket_api.async_register_command(system_options_list)
hass.components.websocket_api.async_register_command(system_options_update)
return True
@ -54,8 +64,18 @@ class ConfigManagerEntryIndexView(HomeAssistantView):
"""List available config entries."""
hass = request.app["hass"]
return self.json(
[
results = []
for entry in hass.config_entries.async_entries():
handler = config_entries.HANDLERS.get(entry.domain)
supports_options = (
# Guard in case handler is no longer registered (custom compnoent etc)
handler is not None
# pylint: disable=comparison-with-callable
and handler.async_get_options_flow
!= config_entries.ConfigFlow.async_get_options_flow
)
results.append(
{
"entry_id": entry.entry_id,
"domain": entry.domain,
@ -63,14 +83,11 @@ class ConfigManagerEntryIndexView(HomeAssistantView):
"source": entry.source,
"state": entry.state,
"connection_class": entry.connection_class,
"supports_options": hasattr(
config_entries.HANDLERS.get(entry.domain),
"async_get_options_flow",
),
"supports_options": supports_options,
}
for entry in hass.config_entries.async_entries()
]
)
)
return self.json(results)
class ConfigManagerEntryResourceView(HomeAssistantView):
@ -101,23 +118,8 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView):
name = "api:config:config_entries:flow"
async def get(self, request):
"""List flows that are in progress but not started by a user.
Example of a non-user initiated flow is a discovered Hue hub that
requires user interaction to finish setup.
"""
if not request["hass_user"].is_admin:
raise Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add")
hass = request.app["hass"]
return self.json(
[
flw
for flw in hass.config_entries.flow.async_progress()
if flw["context"]["source"] != config_entries.SOURCE_USER
]
)
"""Not implemented."""
raise aiohttp.web_exceptions.HTTPMethodNotAllowed("GET", ["POST"])
# pylint: disable=arguments-differ
async def post(self, request):
@ -187,8 +189,8 @@ class ConfigManagerAvailableFlowView(HomeAssistantView):
class OptionManagerFlowIndexView(FlowManagerIndexView):
"""View to create option flows."""
url = "/api/config/config_entries/entry/option/flow"
name = "api:config:config_entries:entry:resource:option:flow"
url = "/api/config/config_entries/options/flow"
name = "api:config:config_entries:option:flow"
# pylint: disable=arguments-differ
async def post(self, request):
@ -224,3 +226,62 @@ class OptionManagerFlowResourceView(FlowManagerResourceView):
# pylint: disable=no-value-for-parameter
return await super().post(request, flow_id)
@websocket_api.require_admin
@websocket_api.websocket_command({"type": "config_entries/flow/progress"})
def config_entries_progress(hass, connection, msg):
"""List flows that are in progress but not started by a user.
Example of a non-user initiated flow is a discovered Hue hub that
requires user interaction to finish setup.
"""
connection.send_result(
msg["id"],
[
flw
for flw in hass.config_entries.flow.async_progress()
if flw["context"]["source"] != config_entries.SOURCE_USER
],
)
@websocket_api.require_admin
@websocket_api.async_response
@websocket_api.websocket_command(
{"type": "config_entries/system_options/list", "entry_id": str}
)
async def system_options_list(hass, connection, msg):
"""List all system options for a config entry."""
entry_id = msg["entry_id"]
entry = hass.config_entries.async_get_entry(entry_id)
if entry:
connection.send_result(msg["id"], entry.system_options.as_dict())
@websocket_api.require_admin
@websocket_api.async_response
@websocket_api.websocket_command(
{
"type": "config_entries/system_options/update",
"entry_id": str,
vol.Optional("disable_new_entities"): bool,
}
)
async def system_options_update(hass, connection, msg):
"""Update config entry system options."""
changes = dict(msg)
changes.pop("id")
changes.pop("type")
entry_id = changes.pop("entry_id")
entry = hass.config_entries.async_get_entry(entry_id)
if entry is None:
connection.send_error(
msg["id"], websocket_api.const.ERR_NOT_FOUND, "Config entry not found"
)
return
hass.config_entries.async_update_entry(entry, system_options=changes)
connection.send_result(msg["id"], entry.system_options.as_dict())

View File

@ -11,51 +11,18 @@ from homeassistant.components.websocket_api.decorators import (
)
from homeassistant.helpers import config_validation as cv
WS_TYPE_LIST = "config/entity_registry/list"
SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_LIST}
)
WS_TYPE_GET = "config/entity_registry/get"
SCHEMA_WS_GET = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_GET, vol.Required("entity_id"): cv.entity_id}
)
WS_TYPE_UPDATE = "config/entity_registry/update"
SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{
vol.Required("type"): WS_TYPE_UPDATE,
vol.Required("entity_id"): cv.entity_id,
# If passed in, we update value. Passing None will remove old value.
vol.Optional("name"): vol.Any(str, None),
vol.Optional("new_entity_id"): str,
}
)
WS_TYPE_REMOVE = "config/entity_registry/remove"
SCHEMA_WS_REMOVE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_REMOVE, vol.Required("entity_id"): cv.entity_id}
)
async def async_setup(hass):
"""Enable the Entity Registry views."""
hass.components.websocket_api.async_register_command(
WS_TYPE_LIST, websocket_list_entities, SCHEMA_WS_LIST
)
hass.components.websocket_api.async_register_command(
WS_TYPE_GET, websocket_get_entity, SCHEMA_WS_GET
)
hass.components.websocket_api.async_register_command(
WS_TYPE_UPDATE, websocket_update_entity, SCHEMA_WS_UPDATE
)
hass.components.websocket_api.async_register_command(
WS_TYPE_REMOVE, websocket_remove_entity, SCHEMA_WS_REMOVE
)
hass.components.websocket_api.async_register_command(websocket_list_entities)
hass.components.websocket_api.async_register_command(websocket_get_entity)
hass.components.websocket_api.async_register_command(websocket_update_entity)
hass.components.websocket_api.async_register_command(websocket_remove_entity)
return True
@async_response
@websocket_api.websocket_command({vol.Required("type"): "config/entity_registry/list"})
async def websocket_list_entities(hass, connection, msg):
"""Handle list registry entries command.
@ -70,6 +37,12 @@ async def websocket_list_entities(hass, connection, msg):
@async_response
@websocket_api.websocket_command(
{
vol.Required("type"): "config/entity_registry/get",
vol.Required("entity_id"): cv.entity_id,
}
)
async def websocket_get_entity(hass, connection, msg):
"""Handle get entity registry entry command.
@ -89,6 +62,17 @@ async def websocket_get_entity(hass, connection, msg):
@require_admin
@async_response
@websocket_api.websocket_command(
{
vol.Required("type"): "config/entity_registry/update",
vol.Required("entity_id"): cv.entity_id,
# If passed in, we update value. Passing None will remove old value.
vol.Optional("name"): vol.Any(str, None),
vol.Optional("new_entity_id"): str,
# We only allow setting disabled_by user via API.
vol.Optional("disabled_by"): vol.Any("user", None),
}
)
async def websocket_update_entity(hass, connection, msg):
"""Handle update entity websocket command.
@ -107,6 +91,9 @@ async def websocket_update_entity(hass, connection, msg):
if "name" in msg:
changes["name"] = msg["name"]
if "disabled_by" in msg:
changes["disabled_by"] = msg["disabled_by"]
if "new_entity_id" in msg and msg["new_entity_id"] != msg["entity_id"]:
changes["new_entity_id"] = msg["new_entity_id"]
if hass.states.get(msg["new_entity_id"]) is not None:
@ -132,6 +119,12 @@ async def websocket_update_entity(hass, connection, msg):
@require_admin
@async_response
@websocket_api.websocket_command(
{
vol.Required("type"): "config/entity_registry/remove",
vol.Required("entity_id"): cv.entity_id,
}
)
async def websocket_remove_entity(hass, connection, msg):
"""Handle remove entity websocket command.

View File

@ -7,7 +7,7 @@ import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate.const import (
HVAC_MODE_OFF,
HVAC_MODE_AUTO,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_COOL,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
@ -33,14 +33,14 @@ AVAILABLE_MODES = [
HVAC_MODE_HEAT,
HVAC_MODE_COOL,
HVAC_MODE_DRY,
HVAC_MODE_AUTO,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_FAN_ONLY,
]
CM_TO_HA_STATE = {
"heat": HVAC_MODE_HEAT,
"cool": HVAC_MODE_COOL,
"auto": HVAC_MODE_AUTO,
"auto": HVAC_MODE_HEAT_COOL,
"dry": HVAC_MODE_DRY,
"fan": HVAC_MODE_FAN_ONLY,
}

View File

@ -32,6 +32,9 @@ from homeassistant.const import (
STATE_CLOSING,
)
# mypy: allow-untyped-calls, allow-incomplete-defs, allow-untyped-defs
_LOGGER = logging.getLogger(__name__)
DOMAIN = "cover"

View File

@ -2,7 +2,9 @@
"config": {
"abort": {
"already_configured": "El Bridge ya est\u00e1 configurado",
"already_in_progress": "El flujo de configuraci\u00f3n para el puente ya est\u00e1 en progreso.",
"no_bridges": "No se descubrieron puentes deCONZ",
"not_deconz_bridge": "No es un puente deCONZ",
"one_instance_only": "El componente solo admite una instancia deCONZ"
},
"error": {
@ -13,7 +15,8 @@
"data": {
"allow_clip_sensor": "Permitir la importaci\u00f3n de sensores virtuales",
"allow_deconz_groups": "Permitir la importaci\u00f3n de grupos deCONZ"
}
},
"description": "\u00bfDesea configurar Home Assistant para conectarse a la puerta de enlace deCONZ proporcionada por el complemento hass.io {addon}?"
},
"init": {
"data": {
@ -23,6 +26,7 @@
"title": "Definir el gateway deCONZ"
},
"link": {
"description": "Desbloquee su puerta de enlace deCONZ para registrarse con Home Assistant. \n\n 1. Vaya a Configuraci\u00f3n deCONZ - > Gateway - > Avanzado \n 2. Presione el bot\u00f3n \"Autenticar aplicaci\u00f3n\"",
"title": "Enlazar con deCONZ"
},
"options": {

View File

@ -0,0 +1,17 @@
{
"config": {
"step": {
"init": {
"data": {
"host": "Host",
"port": "Port"
}
},
"options": {
"data": {
"allow_clip_sensor": "Dopusti uvoz virtualnih senzora"
}
}
}
}
}

View File

@ -25,10 +25,10 @@
"host": "\u4e3b\u6a5f\u7aef",
"port": "\u901a\u8a0a\u57e0"
},
"title": "\u5b9a\u7fa9 deCONZ \u7db2\u95dc"
"title": "\u5b9a\u7fa9 deCONZ \u9598\u9053\u5668"
},
"link": {
"description": "\u89e3\u9664 deCONZ \u7db2\u95dc\u9396\u5b9a\uff0c\u4ee5\u65bc Home Assistant \u9032\u884c\u8a3b\u518a\u3002\n\n1. \u9032\u5165 deCONZ \u7cfb\u7d71\u8a2d\u5b9a -> \u7db2\u95dc -> \u9032\u968e\u8a2d\u5b9a\n2. \u6309\u4e0b\u300c\u8a8d\u8b49\u7a0b\u5f0f\uff08Authenticate app\uff09\u300d\u6309\u9215",
"description": "\u89e3\u9664 deCONZ \u9598\u9053\u5668\u9396\u5b9a\uff0c\u4ee5\u65bc Home Assistant \u9032\u884c\u8a3b\u518a\u3002\n\n1. \u9032\u5165 deCONZ \u7cfb\u7d71\u8a2d\u5b9a -> \u9598\u9053\u5668 -> \u9032\u968e\u8a2d\u5b9a\n2. \u6309\u4e0b\u300c\u8a8d\u8b49\u7a0b\u5f0f\uff08Authenticate app\uff09\u300d\u6309\u9215",
"title": "\u9023\u7d50\u81f3 deCONZ"
},
"options": {
@ -39,6 +39,6 @@
"title": "deCONZ \u9644\u52a0\u8a2d\u5b9a\u9078\u9805"
}
},
"title": "deCONZ Zigbee \u7db2\u95dc"
"title": "deCONZ Zigbee \u9598\u9053\u5668"
}
}

View File

@ -1,5 +1,6 @@
"""Config flow to configure deCONZ component."""
import asyncio
from copy import copy
import async_timeout
import voluptuous as vol
@ -12,7 +13,13 @@ from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client
from .const import CONF_BRIDGEID, DEFAULT_PORT, DOMAIN
from .const import (
CONF_ALLOW_CLIP_SENSOR,
CONF_ALLOW_DECONZ_GROUPS,
CONF_BRIDGEID,
DEFAULT_PORT,
DOMAIN,
)
DECONZ_MANUFACTURERURL = "http://www.dresden-elektronik.de"
CONF_SERIAL = "serial"
@ -45,6 +52,12 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
_hassio_discovery = None
@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get the options flow for this handler."""
return DeconzOptionsFlowHandler(config_entry)
def __init__(self):
"""Initialize the deCONZ config flow."""
self.bridges = []
@ -234,3 +247,41 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
step_id="hassio_confirm",
description_placeholders={"addon": self._hassio_discovery["addon"]},
)
class DeconzOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle deCONZ options."""
def __init__(self, config_entry):
"""Initialize deCONZ options flow."""
self.config_entry = config_entry
self.options = copy(config_entry.options)
async def async_step_init(self, user_input=None):
"""Manage the deCONZ options."""
return await self.async_step_deconz_devices()
async def async_step_deconz_devices(self, user_input=None):
"""Manage the deconz devices options."""
if user_input is not None:
self.options[CONF_ALLOW_CLIP_SENSOR] = user_input[CONF_ALLOW_CLIP_SENSOR]
self.options[CONF_ALLOW_DECONZ_GROUPS] = user_input[
CONF_ALLOW_DECONZ_GROUPS
]
return self.async_create_entry(title="", data=self.options)
return self.async_show_form(
step_id="deconz_devices",
data_schema=vol.Schema(
{
vol.Optional(
CONF_ALLOW_CLIP_SENSOR,
default=self.config_entry.options[CONF_ALLOW_CLIP_SENSOR],
): bool,
vol.Optional(
CONF_ALLOW_DECONZ_GROUPS,
default=self.config_entry.options[CONF_ALLOW_DECONZ_GROUPS],
): bool,
}
),
)

View File

@ -14,8 +14,6 @@ from .const import COVER_TYPES, DAMPERS, NEW_LIGHT, WINDOW_COVERS
from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
ZIGBEE_SPEC = ["lumi.curtain"]
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Old way of setting up deCONZ platforms."""
@ -35,13 +33,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entities = []
for light in lights:
if light.type in COVER_TYPES:
if light.modelid in ZIGBEE_SPEC:
entities.append(DeconzCoverZigbeeSpec(light, gateway))
else:
entities.append(DeconzCover(light, gateway))
entities.append(DeconzCover(light, gateway))
async_add_entities(entities, True)
@ -69,14 +62,12 @@ class DeconzCover(DeconzDevice, CoverDevice):
@property
def current_cover_position(self):
"""Return the current position of the cover."""
if self.is_closed:
return 0
return int(self._device.brightness / 255 * 100)
return 100 - int(self._device.brightness / 255 * 100)
@property
def is_closed(self):
"""Return if the cover is closed."""
return not self._device.state
return self._device.state
@property
def device_class(self):
@ -96,9 +87,9 @@ class DeconzCover(DeconzDevice, CoverDevice):
position = kwargs[ATTR_POSITION]
data = {"on": False}
if position > 0:
if position < 100:
data["on"] = True
data["bri"] = int(position / 100 * 255)
data["bri"] = 255 - int(position / 100 * 255)
await self._device.async_set_state(data)
@ -116,28 +107,3 @@ class DeconzCover(DeconzDevice, CoverDevice):
"""Stop cover."""
data = {"bri_inc": 0}
await self._device.async_set_state(data)
class DeconzCoverZigbeeSpec(DeconzCover):
"""Zigbee spec is the inverse of how deCONZ normally reports attributes."""
@property
def current_cover_position(self):
"""Return the current position of the cover."""
return 100 - int(self._device.brightness / 255 * 100)
@property
def is_closed(self):
"""Return if the cover is closed."""
return self._device.state
async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
position = kwargs[ATTR_POSITION]
data = {"on": False}
if position < 100:
data["on"] = True
data["bri"] = 255 - int(position / 100 * 255)
await self._device.async_set_state(data)

View File

@ -40,5 +40,16 @@
"one_instance_only": "Component only supports one deCONZ instance",
"updated_instance": "Updated deCONZ instance with new host address"
}
},
"options": {
"step": {
"async_step_deconz_devices": {
"description": "Configure visibility of deCONZ device types",
"data": {
"allow_clip_sensor": "Allow deCONZ CLIP sensors",
"allow_deconz_groups": "Allow deCONZ light groups"
}
}
}
}
}

View File

@ -50,7 +50,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
)
await line.get_passages()
if line.passages is None:
_LOGGER.warning("No data recieved from De Lijn")
_LOGGER.warning("No data received from De Lijn")
return
sensors.append(DeLijnPublicTransportSensor(line, name))
@ -71,7 +71,7 @@ class DeLijnPublicTransportSensor(Entity):
"""Get the latest data from the De Lijn API."""
await self.line.get_passages()
if self.line.passages is None:
_LOGGER.warning("No data recieved from De Lijn")
_LOGGER.warning("No data received from De Lijn")
return
try:
first = self.line.passages[0]

View File

@ -176,8 +176,15 @@ async def async_setup(hass, config):
async def finish_setup(hass, config):
"""Finish set up once demo platforms are set up."""
lights = sorted(hass.states.async_entity_ids("light"))
switches = sorted(hass.states.async_entity_ids("switch"))
switches = None
lights = None
while not switches and not lights:
# Not all platforms might be loaded.
if switches is not None:
await asyncio.sleep(0)
switches = sorted(hass.states.async_entity_ids("switch"))
lights = sorted(hass.states.async_entity_ids("light"))
# Set up history graph
await bootstrap.async_setup_component(

View File

@ -51,7 +51,7 @@ NORMAL_INPUTS = {
"Dvd": "DVD",
"Blue ray": "BD",
"TV": "TV",
"Satelite / Cable": "SAT/CBL",
"Satellite / Cable": "SAT/CBL",
"Game": "GAME",
"Game2": "GAME2",
"Video Aux": "V.AUX",

View File

@ -5,7 +5,7 @@
"one_instance_allowed": "Wymagana jest tylko jedna instancja."
},
"create_entry": {
"default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Dialogflow Webhook]({twilio_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y."
"default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Dialogflow Webhook]({dialogflow_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y."
},
"step": {
"user": {

View File

@ -17,6 +17,9 @@ SOURCE = "Home Assistant Dialogflow"
CONFIG_SCHEMA = vol.Schema({DOMAIN: {}}, extra=vol.ALLOW_EXTRA)
V1 = 1
V2 = 2
class DialogFlowError(HomeAssistantError):
"""Raised when a DialogFlow error happens."""
@ -84,23 +87,45 @@ async_remove_entry = config_entry_flow.webhook_async_remove_entry
def dialogflow_error_response(message, error):
"""Return a response saying the error message."""
dialogflow_response = DialogflowResponse(message["result"]["parameters"])
api_version = get_api_version(message)
if api_version is V1:
parameters = message["result"]["parameters"]
elif api_version is V2:
parameters = message["queryResult"]["parameters"]
dialogflow_response = DialogflowResponse(parameters, api_version)
dialogflow_response.add_speech(error)
return dialogflow_response.as_dict()
def get_api_version(message):
"""Get API version of Dialogflow message."""
if message.get("id") is not None:
return V1
if message.get("responseId") is not None:
return V2
async def async_handle_message(hass, message):
"""Handle a DialogFlow message."""
req = message.get("result")
action_incomplete = req["actionIncomplete"]
_api_version = get_api_version(message)
if _api_version is V1:
_LOGGER.warning(
"Dialogflow V1 API will be removed on October 23, 2019. Please change your DialogFlow settings to use the V2 api"
)
req = message.get("result")
action_incomplete = req.get("actionIncomplete", True)
if action_incomplete:
return
if action_incomplete:
return None
elif _api_version is V2:
req = message.get("queryResult")
if req.get("allRequiredParamsPresent", False) is False:
return
action = req.get("action", "")
parameters = req.get("parameters").copy()
parameters["dialogflow_query"] = message
dialogflow_response = DialogflowResponse(parameters)
dialogflow_response = DialogflowResponse(parameters, _api_version)
if action == "":
raise DialogFlowError(
@ -123,10 +148,11 @@ async def async_handle_message(hass, message):
class DialogflowResponse:
"""Help generating the response for Dialogflow."""
def __init__(self, parameters):
def __init__(self, parameters, api_version):
"""Initialize the Dialogflow response."""
self.speech = None
self.parameters = {}
self.api_version = api_version
# Parameter names replace '.' and '-' for '_'
for key, value in parameters.items():
underscored_key = key.replace(".", "_").replace("-", "_")
@ -143,4 +169,8 @@ class DialogflowResponse:
def as_dict(self):
"""Return response in a Dialogflow valid dictionary."""
return {"speech": self.speech, "displayText": self.speech, "source": SOURCE}
if self.api_version is V1:
return {"speech": self.speech, "displayText": self.speech, "source": SOURCE}
if self.api_version is V2:
return {"fulfillmentText": self.speech, "source": SOURCE}

View File

@ -63,6 +63,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
["Power Consumption", obis_ref.CURRENT_ELECTRICITY_USAGE],
["Power Production", obis_ref.CURRENT_ELECTRICITY_DELIVERY],
["Power Tariff", obis_ref.ELECTRICITY_ACTIVE_TARIFF],
["Power Consumption (total)", obis_ref.ELECTRICITY_IMPORTED_TOTAL],
["Power Consumption (low)", obis_ref.ELECTRICITY_USED_TARIFF_1],
["Power Consumption (normal)", obis_ref.ELECTRICITY_USED_TARIFF_2],
["Power Production (low)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_1],

View File

@ -304,7 +304,7 @@ class Thermostat(ClimateDevice):
self.vacation = event["name"]
return PRESET_VACATION
return None
return self._preset_modes[self.thermostat["program"]["currentClimateRef"]]
@property
def hvac_mode(self):
@ -357,6 +357,9 @@ class Thermostat(ClimateDevice):
status = self.thermostat["equipmentStatus"]
return {
"fan": self.fan,
"climate_mode": self._preset_modes[
self.thermostat["program"]["currentClimateRef"]
],
"equipment_running": status,
"fan_min_on_time": self.thermostat["settings"]["fanMinOnTime"],
}

View File

@ -1,135 +0,0 @@
"""Support for EDP re:dy."""
from datetime import timedelta
import logging
import voluptuous as vol
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_START
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client, discovery, dispatcher
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_time
from homeassistant.util import dt as dt_util
_LOGGER = logging.getLogger(__name__)
DOMAIN = "edp_redy"
EDP_REDY = "edp_redy"
DATA_UPDATE_TOPIC = "{0}_data_update".format(DOMAIN)
UPDATE_INTERVAL = 60
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
}
)
},
extra=vol.ALLOW_EXTRA,
)
async def async_setup(hass, config):
"""Set up the EDP re:dy component."""
from edp_redy import EdpRedySession
session = EdpRedySession(
config[DOMAIN][CONF_USERNAME],
config[DOMAIN][CONF_PASSWORD],
aiohttp_client.async_get_clientsession(hass),
hass.loop,
)
hass.data[EDP_REDY] = session
platform_loaded = False
async def async_update_and_sched(time):
update_success = await session.async_update()
if update_success:
nonlocal platform_loaded
# pylint: disable=used-before-assignment
if not platform_loaded:
for component in ["sensor", "switch"]:
await discovery.async_load_platform(
hass, component, DOMAIN, {}, config
)
platform_loaded = True
dispatcher.async_dispatcher_send(hass, DATA_UPDATE_TOPIC)
# schedule next update
async_track_point_in_time(
hass, async_update_and_sched, time + timedelta(seconds=UPDATE_INTERVAL)
)
async def start_component(event):
_LOGGER.debug("Starting updates")
await async_update_and_sched(dt_util.utcnow())
# only start fetching data after HA boots to prevent delaying the boot
# process
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_component)
return True
class EdpRedyDevice(Entity):
"""Representation a base re:dy device."""
def __init__(self, session, device_id, name):
"""Initialize the device."""
self._session = session
self._state = None
self._is_available = True
self._device_state_attributes = {}
self._id = device_id
self._unique_id = device_id
self._name = name if name else device_id
async def async_added_to_hass(self):
"""Subscribe to the data updates topic."""
dispatcher.async_dispatcher_connect(
self.hass, DATA_UPDATE_TOPIC, self._data_updated
)
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def unique_id(self) -> str:
"""Return a unique ID."""
return self._unique_id
@property
def available(self):
"""Return True if entity is available."""
return self._is_available
@property
def should_poll(self):
"""Return the polling state. No polling needed."""
return False
@property
def device_state_attributes(self):
"""Return the state attributes."""
return self._device_state_attributes
@callback
def _data_updated(self):
"""Update state, trigger updates."""
self.async_schedule_update_ha_state(True)
def _parse_data(self, data):
"""Parse data received from the server."""
if "OutOfOrder" in data:
try:
self._is_available = not data["OutOfOrder"]
except ValueError:
_LOGGER.error("Could not parse OutOfOrder for %s", self._id)
self._is_available = False

View File

@ -1,12 +0,0 @@
{
"domain": "edp_redy",
"name": "Edp redy",
"documentation": "https://www.home-assistant.io/components/edp_redy",
"requirements": [
"edp_redy==0.0.3"
],
"dependencies": [],
"codeowners": [
"@abmantis"
]
}

View File

@ -1,115 +0,0 @@
"""Support for EDP re:dy sensors."""
import logging
from homeassistant.const import POWER_WATT
from homeassistant.helpers.entity import Entity
from . import EDP_REDY, EdpRedyDevice
_LOGGER = logging.getLogger(__name__)
# Load power in watts (W)
ATTR_ACTIVE_POWER = "active_power"
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Perform the setup for re:dy devices."""
from edp_redy.session import ACTIVE_POWER_ID
session = hass.data[EDP_REDY]
devices = []
# Create sensors for modules
for device_json in session.modules_dict.values():
if "HA_POWER_METER" not in device_json["Capabilities"]:
continue
devices.append(EdpRedyModuleSensor(session, device_json))
# Create a sensor for global active power
devices.append(
EdpRedySensor(session, ACTIVE_POWER_ID, "Power Home", "mdi:flash", POWER_WATT)
)
async_add_entities(devices, True)
class EdpRedySensor(EdpRedyDevice, Entity):
"""Representation of a EDP re:dy generic sensor."""
def __init__(self, session, sensor_id, name, icon, unit):
"""Initialize the sensor."""
super().__init__(session, sensor_id, name)
self._icon = icon
self._unit = unit
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def icon(self):
"""Return the icon to use in the frontend."""
return self._icon
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this sensor."""
return self._unit
async def async_update(self):
"""Parse the data for this sensor."""
if self._id in self._session.values_dict:
self._state = self._session.values_dict[self._id]
self._is_available = True
else:
self._is_available = False
class EdpRedyModuleSensor(EdpRedyDevice, Entity):
"""Representation of a EDP re:dy module sensor."""
def __init__(self, session, device_json):
"""Initialize the sensor."""
super().__init__(
session, device_json["PKID"], "Power {0}".format(device_json["Name"])
)
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def icon(self):
"""Return the icon to use in the frontend."""
return "mdi:flash"
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this sensor."""
return POWER_WATT
async def async_update(self):
"""Parse the data for this sensor."""
if self._id in self._session.modules_dict:
device_json = self._session.modules_dict[self._id]
self._parse_data(device_json)
else:
self._is_available = False
def _parse_data(self, data):
"""Parse data received from the server."""
super()._parse_data(data)
_LOGGER.debug("Sensor data: %s", str(data))
for state_var in data["StateVars"]:
if state_var["Name"] == "ActivePower":
try:
self._state = float(state_var["Value"]) * 1000
except ValueError:
_LOGGER.error("Could not parse power for %s", self._id)
self._state = 0
self._is_available = False

View File

@ -1,91 +0,0 @@
"""Support for EDP re:dy plugs/switches."""
import logging
from homeassistant.components.switch import SwitchDevice
from . import EDP_REDY, EdpRedyDevice
_LOGGER = logging.getLogger(__name__)
# Load power in watts (W)
ATTR_ACTIVE_POWER = "active_power"
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Perform the setup for re:dy devices."""
session = hass.data[EDP_REDY]
devices = []
for device_json in session.modules_dict.values():
if "HA_SWITCH" not in device_json["Capabilities"]:
continue
devices.append(EdpRedySwitch(session, device_json))
async_add_entities(devices, True)
class EdpRedySwitch(EdpRedyDevice, SwitchDevice):
"""Representation of a Edp re:dy switch (plugs, switches, etc)."""
def __init__(self, session, device_json):
"""Initialize the switch."""
super().__init__(session, device_json["PKID"], device_json["Name"])
self._active_power = None
@property
def icon(self):
"""Return the icon to use in the frontend."""
return "mdi:power-plug"
@property
def is_on(self):
"""Return true if it is on."""
return self._state
@property
def device_state_attributes(self):
"""Return the state attributes."""
if self._active_power is not None:
attrs = {ATTR_ACTIVE_POWER: self._active_power}
else:
attrs = {}
attrs.update(super().device_state_attributes)
return attrs
async def async_turn_on(self, **kwargs):
"""Turn the switch on."""
if await self._async_send_state_cmd(True):
self._state = True
self.async_schedule_update_ha_state()
async def async_turn_off(self, **kwargs):
"""Turn the switch off."""
if await self._async_send_state_cmd(False):
self._state = False
self.async_schedule_update_ha_state()
async def _async_send_state_cmd(self, state):
state_json = {"devModuleId": self._id, "key": "RelayState", "value": state}
return await self._session.async_set_state_var(state_json)
async def async_update(self):
"""Parse the data for this switch."""
if self._id in self._session.modules_dict:
device_json = self._session.modules_dict[self._id]
self._parse_data(device_json)
else:
self._is_available = False
def _parse_data(self, data):
"""Parse data received from the server."""
super()._parse_data(data)
for state_var in data["StateVars"]:
if state_var["Name"] == "RelayState":
self._state = state_var["Value"] == "true"
elif state_var["Name"] == "ActivePower":
try:
self._active_power = float(state_var["Value"]) * 1000
except ValueError:
_LOGGER.error("Could not parse power for %s", self._id)
self._active_power = None

View File

@ -1,4 +1,4 @@
"""Support for local control of entities by emulating a Phillips Hue bridge."""
"""Support for local control of entities by emulating a Philips Hue bridge."""
import logging
from aiohttp import web

View File

@ -562,17 +562,27 @@ def get_entity_state(config, entity):
def entity_to_json(config, entity, state):
"""Convert an entity to its Hue bridge JSON representation."""
entity_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if (entity_features & SUPPORT_BRIGHTNESS) or entity.domain != light.DOMAIN:
return {
"state": {
HUE_API_STATE_ON: state[STATE_ON],
HUE_API_STATE_BRI: state[STATE_BRIGHTNESS],
HUE_API_STATE_HUE: state[STATE_HUE],
HUE_API_STATE_SAT: state[STATE_SATURATION],
"reachable": True,
},
"type": "Dimmable light",
"name": config.get_entity_name(entity),
"modelid": "HASS123",
"uniqueid": entity.entity_id,
"swversion": "123",
}
return {
"state": {
HUE_API_STATE_ON: state[STATE_ON],
HUE_API_STATE_BRI: state[STATE_BRIGHTNESS],
HUE_API_STATE_HUE: state[STATE_HUE],
HUE_API_STATE_SAT: state[STATE_SATURATION],
"reachable": True,
},
"type": "Dimmable light",
"state": {HUE_API_STATE_ON: state[STATE_ON], "reachable": True},
"type": "On/off light",
"name": config.get_entity_name(entity),
"modelid": "HASS123",
"modelid": "HASS321",
"uniqueid": entity.entity_id,
"swversion": "123",
}

View File

@ -23,6 +23,7 @@ _LOGGER = logging.getLogger(__name__)
ATTR_STATION = "station"
ATTR_LOCATION = "location"
ATTR_UPDATED = "updated"
CONF_ATTRIBUTION = "Data provided by Environment Canada"
CONF_STATION = "station"
@ -35,7 +36,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_LOOP, default=True): cv.boolean,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_STATION): cv.string,
vol.Optional(CONF_STATION): cv.matches_regex(r"^C[A-Z]{4}$|^[A-Z]{3}$"),
vol.Inclusive(CONF_LATITUDE, "latlon"): cv.latitude,
vol.Inclusive(CONF_LONGITUDE, "latlon"): cv.longitude,
vol.Optional(CONF_PRECIP_TYPE): ["RAIN", "SNOW"],
@ -70,6 +71,7 @@ class ECCamera(Camera):
self.camera_name = camera_name
self.content_type = "image/gif"
self.image = None
self.timestamp = None
def camera_image(self):
"""Return bytes of camera image."""
@ -90,6 +92,7 @@ class ECCamera(Camera):
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
ATTR_LOCATION: self.radar_object.station_name,
ATTR_STATION: self.radar_object.station_code,
ATTR_UPDATED: self.timestamp,
}
return attr
@ -101,3 +104,4 @@ class ECCamera(Camera):
self.image = self.radar_object.get_loop()
else:
self.image = self.radar_object.get_latest_frame()
self.timestamp = self.radar_object.timestamp.isoformat()

View File

@ -3,7 +3,7 @@
"name": "Environment Canada",
"documentation": "https://www.home-assistant.io/components/environment_canada",
"requirements": [
"env_canada==0.0.20"
"env_canada==0.0.24"
],
"dependencies": [],
"codeowners": [

View File

@ -133,10 +133,15 @@ class ECSensor(Entity):
ATTR_TIME: " | ".join([str(s.get("date")) for s in value]),
}
)
elif self.sensor_type == "tendency":
self._state = str(value).capitalize()
else:
self._state = value
if sensor_data.get("unit") == "C":
if sensor_data.get("unit") == "C" or self.sensor_type in [
"wind_chill",
"humidex",
]:
self._unit = TEMP_CELSIUS
else:
self._unit = sensor_data.get("unit")

View File

@ -91,7 +91,7 @@ class EQ3BTSmartThermostat(ClimateDevice):
@property
def available(self) -> bool:
"""Return if thermostat is available."""
return self._thermostat.mode > 0
return self._thermostat.mode >= 0
@property
def name(self):

View File

@ -8,6 +8,7 @@
"invalid_password": "\u00a1Contrase\u00f1a invalida!",
"resolve_error": "No se puede resolver la direcci\u00f3n de la ESP. Si este error persiste, configure una direcci\u00f3n IP est\u00e1tica: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips"
},
"flow_title": "ESPHome: {name}",
"step": {
"authenticate": {
"data": {
@ -17,6 +18,7 @@
"title": "Escriba la contrase\u00f1a"
},
"discovery_confirm": {
"description": "\u00bfDesea agregar el nodo ESPHome `{name}` a Home Assistant?",
"title": "Nodo ESPHome descubierto"
},
"user": {

View File

@ -2,7 +2,7 @@
"domain": "essent",
"name": "Essent",
"documentation": "https://www.home-assistant.io/components/essent",
"requirements": ["PyEssent==0.12"],
"requirements": ["PyEssent==0.13"],
"dependencies": [],
"codeowners": ["@TheLastProject"]
}

View File

@ -273,10 +273,10 @@ class EvoBroker:
else:
self.timers["statusUpdated"] = utcnow()
_LOGGER.debug("Status = %s", status)
_LOGGER.debug("Status = %s", status)
# inform the evohome devices that state data has been updated
async_dispatcher_send(self.hass, DOMAIN, {"signal": "refresh"})
# inform the evohome devices that state data has been updated
async_dispatcher_send(self.hass, DOMAIN, {"signal": "refresh"})
class EvoDevice(Entity):

View File

@ -2,6 +2,7 @@
from datetime import timedelta
import functools as ft
import logging
from typing import Optional
import voluptuous as vol
@ -74,7 +75,7 @@ FAN_SET_DIRECTION_SCHEMA = ENTITY_SERVICE_SCHEMA.extend(
@bind_hass
def is_on(hass, entity_id: str = None) -> bool:
def is_on(hass, entity_id: Optional[str] = None) -> bool:
"""Return if the fans are on based on the statemachine."""
entity_id = entity_id or ENTITY_ID_ALL_FANS
state = hass.states.get(entity_id)
@ -149,12 +150,12 @@ class FanEntity(ToggleEntity):
return self.hass.async_add_job(self.set_direction, direction)
# pylint: disable=arguments-differ
def turn_on(self, speed: str = None, **kwargs) -> None:
def turn_on(self, speed: Optional[str] = None, **kwargs) -> None:
"""Turn on the fan."""
raise NotImplementedError()
# pylint: disable=arguments-differ
def async_turn_on(self, speed: str = None, **kwargs):
def async_turn_on(self, speed: Optional[str] = None, **kwargs):
"""Turn on the fan.
This method must be run in the event loop and returns a coroutine.
@ -180,7 +181,7 @@ class FanEntity(ToggleEntity):
return self.speed not in [SPEED_OFF, None]
@property
def speed(self) -> str:
def speed(self) -> Optional[str]:
"""Return the current speed."""
return None
@ -190,7 +191,7 @@ class FanEntity(ToggleEntity):
return []
@property
def current_direction(self) -> str:
def current_direction(self) -> Optional[str]:
"""Return the current direction of the fan."""
return None

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