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

View File

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

6
.gitignore vendored
View File

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

View File

@ -1,8 +1,15 @@
repos: repos:
- repo: https://github.com/python/black - repo: https://github.com/psf/black
rev: 19.3b0 rev: 19.3b0
hooks: hooks:
- id: black - id: black
args: args:
- --safe - --safe
- --quiet - --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: matrix:
fast_finish: true fast_finish: true
include: include:
- python: "3.6" - python: "3.6.0"
env: TOXENV=lint env: TOXENV=lint
- python: "3.6" dist: trusty
- python: "3.6.0"
env: TOXENV=pylint env: TOXENV=pylint
- python: "3.6" dist: trusty
- python: "3.6.0"
env: TOXENV=typing env: TOXENV=typing
- python: "3.6" dist: trusty
- python: "3.6.0"
env: TOXENV=py36 env: TOXENV=py36
dist: trusty
- python: "3.7" - python: "3.7"
env: TOXENV=py37 env: TOXENV=py37

View File

@ -9,10 +9,6 @@ homeassistant/*.py @home-assistant/core
homeassistant/helpers/* @home-assistant/core homeassistant/helpers/* @home-assistant/core
homeassistant/util/* @home-assistant/core homeassistant/util/* @home-assistant/core
# Virtualization
Dockerfile @home-assistant/docker
virtualization/Docker/* @home-assistant/docker
# Other code # Other code
homeassistant/scripts/check_config.py @kellerza homeassistant/scripts/check_config.py @kellerza
@ -24,6 +20,7 @@ homeassistant/components/alpha_vantage/* @fabaff
homeassistant/components/amazon_polly/* @robbiet480 homeassistant/components/amazon_polly/* @robbiet480
homeassistant/components/ambiclimate/* @danielhiversen homeassistant/components/ambiclimate/* @danielhiversen
homeassistant/components/ambient_station/* @bachya homeassistant/components/ambient_station/* @bachya
homeassistant/components/androidtv/* @JeffLIrion
homeassistant/components/apache_kafka/* @bachya homeassistant/components/apache_kafka/* @bachya
homeassistant/components/api/* @home-assistant/core homeassistant/components/api/* @home-assistant/core
homeassistant/components/aprs/* @PhilRW homeassistant/components/aprs/* @PhilRW
@ -43,6 +40,7 @@ homeassistant/components/azure_event_hub/* @eavanvalkenburg
homeassistant/components/bitcoin/* @fabaff homeassistant/components/bitcoin/* @fabaff
homeassistant/components/bizkaibus/* @UgaitzEtxebarria homeassistant/components/bizkaibus/* @UgaitzEtxebarria
homeassistant/components/blink/* @fronzbot homeassistant/components/blink/* @fronzbot
homeassistant/components/bmw_connected_drive/* @gerard33
homeassistant/components/braviatv/* @robbiet480 homeassistant/components/braviatv/* @robbiet480
homeassistant/components/broadlink/* @danielhiversen homeassistant/components/broadlink/* @danielhiversen
homeassistant/components/brunt/* @eavanvalkenburg homeassistant/components/brunt/* @eavanvalkenburg
@ -73,7 +71,6 @@ homeassistant/components/discogs/* @thibmaek
homeassistant/components/doorbird/* @oblogic7 homeassistant/components/doorbird/* @oblogic7
homeassistant/components/dweet/* @fabaff homeassistant/components/dweet/* @fabaff
homeassistant/components/ecovacs/* @OverloadUT homeassistant/components/ecovacs/* @OverloadUT
homeassistant/components/edp_redy/* @abmantis
homeassistant/components/egardia/* @jeroenterheerdt homeassistant/components/egardia/* @jeroenterheerdt
homeassistant/components/eight_sleep/* @mezz64 homeassistant/components/eight_sleep/* @mezz64
homeassistant/components/elv/* @majuss homeassistant/components/elv/* @majuss
@ -101,13 +98,13 @@ homeassistant/components/fronius/* @nielstron
homeassistant/components/frontend/* @home-assistant/frontend homeassistant/components/frontend/* @home-assistant/frontend
homeassistant/components/gearbest/* @HerrHofrat homeassistant/components/gearbest/* @HerrHofrat
homeassistant/components/geniushub/* @zxdavb homeassistant/components/geniushub/* @zxdavb
homeassistant/components/geonetnz_quakes/* @exxamalte
homeassistant/components/gitter/* @fabaff homeassistant/components/gitter/* @fabaff
homeassistant/components/glances/* @fabaff homeassistant/components/glances/* @fabaff
homeassistant/components/gntp/* @robbiet480 homeassistant/components/gntp/* @robbiet480
homeassistant/components/google_cloud/* @lufton homeassistant/components/google_cloud/* @lufton
homeassistant/components/google_translate/* @awarecan homeassistant/components/google_translate/* @awarecan
homeassistant/components/google_travel_time/* @robbiet480 homeassistant/components/google_travel_time/* @robbiet480
homeassistant/components/googlehome/* @ludeeus
homeassistant/components/gpsd/* @fabaff homeassistant/components/gpsd/* @fabaff
homeassistant/components/group/* @home-assistant/core homeassistant/components/group/* @home-assistant/core
homeassistant/components/gtfs/* @robbiet480 homeassistant/components/gtfs/* @robbiet480
@ -142,6 +139,7 @@ homeassistant/components/ipma/* @dgomes
homeassistant/components/iqvia/* @bachya homeassistant/components/iqvia/* @bachya
homeassistant/components/irish_rail_transport/* @ttroy50 homeassistant/components/irish_rail_transport/* @ttroy50
homeassistant/components/jewish_calendar/* @tsvi homeassistant/components/jewish_calendar/* @tsvi
homeassistant/components/keba/* @dannerph
homeassistant/components/knx/* @Julius2342 homeassistant/components/knx/* @Julius2342
homeassistant/components/kodi/* @armills homeassistant/components/kodi/* @armills
homeassistant/components/konnected/* @heythisisnate homeassistant/components/konnected/* @heythisisnate
@ -171,6 +169,7 @@ homeassistant/components/meteoalarm/* @rolfberkenbosch
homeassistant/components/miflora/* @danielhiversen @ChristianKuehnel homeassistant/components/miflora/* @danielhiversen @ChristianKuehnel
homeassistant/components/mill/* @danielhiversen homeassistant/components/mill/* @danielhiversen
homeassistant/components/min_max/* @fabaff homeassistant/components/min_max/* @fabaff
homeassistant/components/minio/* @tkislan
homeassistant/components/mobile_app/* @robbiet480 homeassistant/components/mobile_app/* @robbiet480
homeassistant/components/monoprice/* @etsinko homeassistant/components/monoprice/* @etsinko
homeassistant/components/moon/* @fabaff homeassistant/components/moon/* @fabaff
@ -181,6 +180,7 @@ homeassistant/components/nello/* @pschmitt
homeassistant/components/ness_alarm/* @nickw444 homeassistant/components/ness_alarm/* @nickw444
homeassistant/components/nest/* @awarecan homeassistant/components/nest/* @awarecan
homeassistant/components/netdata/* @fabaff homeassistant/components/netdata/* @fabaff
homeassistant/components/netgear_lte/* @amelchio
homeassistant/components/nextbus/* @vividboarder homeassistant/components/nextbus/* @vividboarder
homeassistant/components/nissan_leaf/* @filcole homeassistant/components/nissan_leaf/* @filcole
homeassistant/components/nmbs/* @thibmaek homeassistant/components/nmbs/* @thibmaek
@ -203,6 +203,7 @@ homeassistant/components/philips_js/* @elupus
homeassistant/components/pi_hole/* @fabaff homeassistant/components/pi_hole/* @fabaff
homeassistant/components/plaato/* @JohNan homeassistant/components/plaato/* @JohNan
homeassistant/components/plant/* @ChristianKuehnel homeassistant/components/plant/* @ChristianKuehnel
homeassistant/components/plugwise/* @laetificat @CoMPaTech
homeassistant/components/point/* @fredrike homeassistant/components/point/* @fredrike
homeassistant/components/ps4/* @ktnrg45 homeassistant/components/ps4/* @ktnrg45
homeassistant/components/ptvsd/* @swamp-ig homeassistant/components/ptvsd/* @swamp-ig
@ -220,7 +221,6 @@ homeassistant/components/repetier/* @MTrab
homeassistant/components/rfxtrx/* @danielhiversen homeassistant/components/rfxtrx/* @danielhiversen
homeassistant/components/rmvtransport/* @cgtobi homeassistant/components/rmvtransport/* @cgtobi
homeassistant/components/roomba/* @pschmitt homeassistant/components/roomba/* @pschmitt
homeassistant/components/ruter/* @ludeeus
homeassistant/components/scene/* @home-assistant/core homeassistant/components/scene/* @home-assistant/core
homeassistant/components/scrape/* @fabaff homeassistant/components/scrape/* @fabaff
homeassistant/components/script/* @home-assistant/core homeassistant/components/script/* @home-assistant/core
@ -286,7 +286,7 @@ homeassistant/components/updater/* @home-assistant/core
homeassistant/components/upnp/* @robbiet480 homeassistant/components/upnp/* @robbiet480
homeassistant/components/uptimerobot/* @ludeeus homeassistant/components/uptimerobot/* @ludeeus
homeassistant/components/utility_meter/* @dgomes homeassistant/components/utility_meter/* @dgomes
homeassistant/components/velbus/* @ceral2nd homeassistant/components/velbus/* @cereal2nd
homeassistant/components/velux/* @Julius2342 homeassistant/components/velux/* @Julius2342
homeassistant/components/version/* @fabaff homeassistant/components/version/* @fabaff
homeassistant/components/vesync/* @markperdue @webdjoe 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 WORKDIR /usr/src
# Setup hass-release
RUN git clone --depth 1 https://github.com/home-assistant/hass-release \ RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
&& cd hass-release \ && cd hass-release \
&& pip3 install -e . && pip3 install -e .
WORKDIR /workspace WORKDIR /workspaces
# Install Python dependencies from requirements.txt if it exists # 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 RUN pip3 install -r requirements_test_all.txt -c package_constraints.txt
# Set the default shell to bash instead of sh # Set the default shell to bash instead of sh

View File

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

View File

@ -3,37 +3,39 @@
trigger: trigger:
tags: tags:
include: include:
- '*' - '*'
pr: none pr: none
schedules:
- cron: "0 1 * * *"
displayName: "nightly builds"
branches:
include:
- dev
always: true
variables: variables:
- name: versionBuilder - name: versionBuilder
value: '5.2' value: '6.3'
- group: docker - group: docker
- group: github - group: github
- group: twine - group: twine
resources:
repositories:
- repository: azure
type: github
name: 'home-assistant/ci-azure'
endpoint: 'home-assistant'
stages: stages:
- stage: 'Validate' - stage: 'Validate'
jobs: jobs:
- job: 'VersionValidate' - template: templates/azp-job-version.yaml@azure
parameters:
ignoreDev: true
- job: 'Permission'
pool: pool:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-latest'
steps: 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: | - script: |
sudo apt-get install -y --no-install-recommends \ sudo apt-get install -y --no-install-recommends \
jq curl jq curl
@ -48,10 +50,12 @@ stages:
echo "${created_by} is not allowed to create an release!" echo "${created_by} is not allowed to create an release!"
exit 1 exit 1
displayName: 'Check rights' displayName: 'Check rights'
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags'))
- stage: 'Build' - stage: 'Build'
jobs: jobs:
- job: 'ReleasePython' - job: 'ReleasePython'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
pool: pool:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-latest'
steps: steps:
@ -92,26 +96,29 @@ stages:
buildArch: 'aarch64' buildArch: 'aarch64'
buildMachine: 'qemuarm-64,raspberrypi3-64,raspberrypi4-64,odroid-c2,orangepi-prime' buildMachine: 'qemuarm-64,raspberrypi3-64,raspberrypi4-64,odroid-c2,orangepi-prime'
steps: 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' displayName: 'Docker hub login'
- script: sudo docker pull homeassistant/amd64-builder:$(versionBuilder) - script: docker pull homeassistant/amd64-builder:$(versionBuilder)
displayName: 'Install Builder' displayName: 'Install Builder'
- script: | - script: |
set -e set -e
sudo docker run --rm --privileged \ docker run --rm --privileged \
-v ~/.docker:/root/.docker \ -v ~/.docker:/root/.docker:rw \
-v /run/docker.sock:/run/docker.sock:rw \ -v /run/docker.sock:/run/docker.sock:rw \
-v $(pwd):/homeassistant:ro \
homeassistant/amd64-builder:$(versionBuilder) \ homeassistant/amd64-builder:$(versionBuilder) \
--homeassistant $(Build.SourceBranchName) "--$(buildArch)" \ --homeassistant $(homeassistantRelease) "--$(buildArch)" \
-r https://github.com/home-assistant/hassio-homeassistant \ -r https://github.com/home-assistant/hassio-homeassistant \
-t generic --docker-hub homeassistant -t generic --docker-hub homeassistant
sudo docker run --rm --privileged \ docker run --rm --privileged \
-v ~/.docker:/root/.docker \ -v ~/.docker:/root/.docker \
-v /run/docker.sock:/run/docker.sock:rw \ -v /run/docker.sock:/run/docker.sock:rw \
homeassistant/amd64-builder:$(versionBuilder) \ homeassistant/amd64-builder:$(versionBuilder) \
--homeassistant-machine "$(Build.SourceBranchName)=$(buildMachine)" \ --homeassistant-machine "$(homeassistantRelease)=$(buildMachine)" \
-r https://github.com/home-assistant/hassio-homeassistant \ -r https://github.com/home-assistant/hassio-homeassistant \
-t machine --docker-hub homeassistant -t machine --docker-hub homeassistant
displayName: 'Build Release' displayName: 'Build Release'
@ -122,6 +129,7 @@ stages:
pool: pool:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-latest'
steps: steps:
- template: templates/azp-step-ha-version.yaml@azure
- script: | - script: |
sudo apt-get install -y --no-install-recommends \ sudo apt-get install -y --no-install-recommends \
git jq curl git jq curl
@ -135,7 +143,7 @@ stages:
- script: | - script: |
set -e set -e
version="$(Build.SourceBranchName)" version="$(homeassistantRelease)"
git clone https://github.com/home-assistant/hassio-version git clone https://github.com/home-assistant/hassio-version
cd hassio-version cd hassio-version
@ -144,11 +152,11 @@ stages:
beta_version="$(jq --raw-output '.homeassistant.default' beta.json)" beta_version="$(jq --raw-output '.homeassistant.default' beta.json)"
stable_version="$(jq --raw-output '.homeassistant.default' stable.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 sed -i "s|$dev_version|$version|g" dev.json
elif [[ "$version" =~ b ]]; then
sed -i "s|$beta_version|$version|g" beta.json sed -i "s|$beta_version|$version|g" beta.json
else else
sed -i "s|$dev_version|$version|g" dev.json
sed -i "s|$beta_version|$version|g" beta.json sed -i "s|$beta_version|$version|g" beta.json
sed -i "s|$stable_version|$version|g" stable.json sed -i "s|$stable_version|$version|g" stable.json
fi fi
@ -156,3 +164,72 @@ stages:
git commit -am "Bump Home Assistant $version" git commit -am "Bump Home Assistant $version"
git push git push
displayName: 'Update version files' 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: include:
- requirements_all.txt - requirements_all.txt
pr: none pr: none
schedules:
- cron: '0 */8 * * *'
displayName: 'daily builds'
branches:
include:
- dev
always: true
variables: variables:
- name: versionWheels - name: versionWheels
value: '1.0-3.7-alpine3.10' value: '1.1-3.7-alpine3.10'
- group: wheels resources:
repositories:
- repository: azure
type: github
name: 'home-assistant/ci-azure'
endpoint: 'home-assistant'
jobs: 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' requirement_files="requirements_wheels.txt requirements_diff.txt"
timeoutInMinutes: 360 for requirement_file in ${requirement_files}; do
pool: sed -i "s|# pytradfri|pytradfri|g" ${requirement_file}
vmImage: 'ubuntu-latest' sed -i "s|# pybluez|pybluez|g" ${requirement_file}
strategy: sed -i "s|# bluepy|bluepy|g" ${requirement_file}
maxParallel: 5 sed -i "s|# beacontools|beacontools|g" ${requirement_file}
matrix: sed -i "s|# RPi.GPIO|RPi.GPIO|g" ${requirement_file}
amd64: sed -i "s|# raspihats|raspihats|g" ${requirement_file}
buildArch: 'amd64' sed -i "s|# rpi-rf|rpi-rf|g" ${requirement_file}
i386: sed -i "s|# blinkt|blinkt|g" ${requirement_file}
buildArch: 'i386' sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
armhf: sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
buildArch: 'armhf' sed -i "s|# evdev|evdev|g" ${requirement_file}
armv7: sed -i "s|# smbus-cffi|smbus-cffi|g" ${requirement_file}
buildArch: 'armv7' sed -i "s|# i2csense|i2csense|g" ${requirement_file}
aarch64: sed -i "s|# python-eq3bt|python-eq3bt|g" ${requirement_file}
buildArch: 'aarch64' sed -i "s|# pycups|pycups|g" ${requirement_file}
steps: sed -i "s|# homekit|homekit|g" ${requirement_file}
- script: | sed -i "s|# decora_wifi|decora_wifi|g" ${requirement_file}
sudo apt-get update sed -i "s|# decora|decora|g" ${requirement_file}
sudo apt-get install -y --no-install-recommends \ sed -i "s|# PySwitchbot|PySwitchbot|g" ${requirement_file}
qemu-user-static \ sed -i "s|# pySwitchmate|pySwitchmate|g" ${requirement_file}
binfmt-support \ sed -i "s|# face_recognition|face_recognition|g" ${requirement_file}
curl done
displayName: 'Prepare requirements files for Hass.io'
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'

View File

@ -10,12 +10,7 @@ import threading
from typing import List, Dict, Any, TYPE_CHECKING # noqa pylint: disable=unused-import from typing import List, Dict, Any, TYPE_CHECKING # noqa pylint: disable=unused-import
from homeassistant import monkey_patch from homeassistant import monkey_patch
from homeassistant.const import ( from homeassistant.const import __version__, REQUIRED_PYTHON_VER, RESTART_EXIT_CODE
__version__,
EVENT_HOMEASSISTANT_START,
REQUIRED_PYTHON_VER,
RESTART_EXIT_CODE,
)
if TYPE_CHECKING: if TYPE_CHECKING:
from homeassistant import core 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, log_no_color=args.log_no_color,
) )
if args.open_ui: if args.open_ui and hass.config.api is not None:
# Imported here to avoid importing asyncio before monkey patch import webbrowser
from homeassistant.util.async_ import run_callback_threadsafe
def open_browser(_: Any) -> None: hass.add_job(webbrowser.open, hass.config.api.base_url)
"""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,
)
return await hass.async_run() return await hass.async_run()

View File

@ -458,7 +458,7 @@ class AuthManager:
result["data"] result["data"]
) )
if flow.context is not None and flow.context.get("credential_only"): if flow.context.get("credential_only"):
result["result"] = credentials result["result"] = credentials
return result 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() processed = hass.data[DATA_REQS] = set()
# https://github.com/python/mypy/issues/1424 # 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 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) processed.add(module_name)
return module return module

View File

@ -7,6 +7,7 @@ from typing import ( # noqa: F401
Dict, Dict,
List, List,
Mapping, Mapping,
Optional,
Set, Set,
Tuple, Tuple,
Union, Union,
@ -31,7 +32,7 @@ _LOGGER = logging.getLogger(__name__)
class AbstractPermissions: class AbstractPermissions:
"""Default permissions class.""" """Default permissions class."""
_cached_entity_func = None _cached_entity_func: Optional[Callable[[str, str], bool]] = None
def _entity_func(self) -> Callable[[str, str], bool]: def _entity_func(self) -> Callable[[str, str], bool]:
"""Return a function that can test entity access.""" """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 # https://github.com/python/mypy/issues/1424
reqs = module.REQUIREMENTS # type: ignore reqs = module.REQUIREMENTS # type: ignore
req_success = await requirements.async_process_requirements( await requirements.async_process_requirements(
hass, "auth provider {}".format(provider), reqs hass, "auth provider {}".format(provider), reqs
) )
if not req_success:
raise HomeAssistantError(
"Unable to process requirements of auth provider {}".format(provider)
)
processed.add(provider) processed.add(provider)
return module return module

View File

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

View File

@ -20,7 +20,8 @@
"username": "Nombre de usuario", "username": "Nombre de usuario",
"verify_ssl": "AdGuard Home utiliza un certificado adecuado" "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" "title": "AdGuard Home"

View File

@ -19,6 +19,7 @@
"username": "Nom d'utilisateur" "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"} API_TEMP_UNITS = {TEMP_FAHRENHEIT: "FAHRENHEIT", TEMP_CELSIUS: "CELSIUS"}
# Needs to be ordered dict for `async_api_set_thermostat_mode` which does a # 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. # back to HA state.
API_THERMOSTAT_MODES = OrderedDict( API_THERMOSTAT_MODES = OrderedDict(
[ [

View File

@ -264,7 +264,9 @@ class ClimateCapabilities(AlexaEntity):
def interfaces(self): def interfaces(self):
"""Yield the supported interfaces.""" """Yield the supported interfaces."""
# If we support two modes, one being off, we allow turning on too. # 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 AlexaPowerController(self.entity)
yield AlexaThermostatController(self.hass, 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 The Alexa response includes a list of properties which provides
feedback on how states have changed. For example if a user asks, 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 the new value of the property, and Alexa will respond to the user
"Thermostat set to 20 degrees". "Thermostat set to 20 degrees".

View File

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

View File

@ -7,6 +7,17 @@
}, },
"create_entry": { "create_entry": {
"default": "Autenticaci\u00f3n exitosa con Ambiclimate" "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, "config_flow": true,
"documentation": "https://www.home-assistant.io/components/ambient_station", "documentation": "https://www.home-assistant.io/components/ambient_station",
"requirements": [ "requirements": [
"aioambient==0.3.1" "aioambient==0.3.2"
], ],
"dependencies": [], "dependencies": [],
"codeowners": [ "codeowners": [

View File

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

View File

@ -3,6 +3,9 @@ import functools
import logging import logging
import voluptuous as vol 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 import MediaPlayerDevice, PLATFORM_SCHEMA
from homeassistant.components.media_player.const import ( from homeassistant.components.media_player.const import (
SUPPORT_NEXT_TRACK, SUPPORT_NEXT_TRACK,
@ -64,6 +67,7 @@ CONF_ADB_SERVER_IP = "adb_server_ip"
CONF_ADB_SERVER_PORT = "adb_server_port" CONF_ADB_SERVER_PORT = "adb_server_port"
CONF_APPS = "apps" CONF_APPS = "apps"
CONF_GET_SOURCES = "get_sources" CONF_GET_SOURCES = "get_sources"
CONF_STATE_DETECTION_RULES = "state_detection_rules"
CONF_TURN_ON_COMMAND = "turn_on_command" CONF_TURN_ON_COMMAND = "turn_on_command"
CONF_TURN_OFF_COMMAND = "turn_off_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_APPS, default=dict()): vol.Schema({cv.string: cv.string}),
vol.Optional(CONF_TURN_ON_COMMAND): cv.string, vol.Optional(CONF_TURN_ON_COMMAND): cv.string,
vol.Optional(CONF_TURN_OFF_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): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Android TV / Fire TV platform.""" """Set up the Android TV / Fire TV platform."""
from androidtv import setup
hass.data.setdefault(ANDROIDTV_DOMAIN, {}) hass.data.setdefault(ANDROIDTV_DOMAIN, {})
host = "{0}:{1}".format(config[CONF_HOST], config[CONF_PORT]) 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 " adb_log = "using Python ADB implementation "
if CONF_ADBKEY in config: if CONF_ADBKEY in config:
aftv = setup( 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]) adb_log += "with adbkey='{0}'".format(config[CONF_ADBKEY])
else: 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" adb_log += "without adbkey authentication"
else: else:
# Use "pure-python-adb" (communicate with ADB server) # 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_ip=config[CONF_ADB_SERVER_IP],
adb_server_port=config[CONF_ADB_SERVER_PORT], adb_server_port=config[CONF_ADB_SERVER_PORT],
device_class=config[CONF_DEVICE_CLASS], device_class=config[CONF_DEVICE_CLASS],
state_detection_rules=config[CONF_STATE_DETECTION_RULES],
) )
adb_log = "using ADB server at {0}:{1}".format( adb_log = "using ADB server at {0}:{1}".format(
config[CONF_ADB_SERVER_IP], config[CONF_ADB_SERVER_PORT] 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" device_name = config[CONF_NAME] if CONF_NAME in config else "Fire TV"
add_entities([device]) 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 hass.data[ANDROIDTV_DOMAIN][host] = device
if hass.services.has_service(ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND): 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): def __init__(self, aftv, name, apps, turn_on_command, turn_off_command):
"""Initialize the Android TV / Fire TV device.""" """Initialize the Android TV / Fire TV device."""
from androidtv.constants import APPS, KEYS
self.aftv = aftv self.aftv = aftv
self._name = name self._name = name
self._apps = APPS self._apps = APPS.copy()
self._apps.update(apps) self._apps.update(apps)
self._keys = KEYS 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_on_command = turn_on_command
self.turn_off_command = turn_off_command self.turn_off_command = turn_off_command
@ -327,6 +341,11 @@ class ADBDevice(MediaPlayerDevice):
"""Return the state of the player.""" """Return the state of the player."""
return self._state return self._state
@property
def unique_id(self):
"""Return the device unique id."""
return self._unique_id
@adb_decorator() @adb_decorator()
def media_play(self): def media_play(self):
"""Send play command.""" """Send play command."""
@ -401,9 +420,7 @@ class AndroidTVDevice(ADBDevice):
super().__init__(aftv, name, apps, turn_on_command, turn_off_command) super().__init__(aftv, name, apps, turn_on_command, turn_off_command)
self._device = None self._device = None
self._device_properties = self.aftv.device_properties
self._is_volume_muted = None self._is_volume_muted = None
self._unique_id = self._device_properties.get("serialno")
self._volume_level = None self._volume_level = None
@adb_decorator(override_available=True) @adb_decorator(override_available=True)
@ -443,11 +460,6 @@ class AndroidTVDevice(ADBDevice):
"""Flag media player features that are supported.""" """Flag media player features that are supported."""
return SUPPORT_ANDROIDTV return SUPPORT_ANDROIDTV
@property
def unique_id(self):
"""Return the device unique id."""
return self._unique_id
@property @property
def volume_level(self): def volume_level(self):
"""Return the volume level.""" """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 return support
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Once registed add listener for events.""" """Once registered, add listener for events."""
await self._state.start() await self._state.start()
@callback @callback

View File

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

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:", "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": "Verificar la configuracion"
} }
} },
"title": "Notificar contrase\u00f1a de un solo uso"
}, },
"totp": { "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": { "step": {
"init": { "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} ` **.", "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.""" """Init mfa setup flow manager."""
async def _async_create_setup_flow(handler, context, data): 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) mfa_module = hass.auth.get_auth_mfa_module(handler)
if mfa_module is None: if mfa_module is None:
raise ValueError("Mfa module {} is not found".format(handler)) 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.loader import bind_hass
from homeassistant.util.dt import parse_datetime, utcnow 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" DOMAIN = "automation"
ENTITY_ID_FORMAT = DOMAIN + ".{}" 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 from homeassistant.loader import async_get_integration
# mypy: allow-untyped-defs, no-check-untyped-defs
TRIGGER_SCHEMA = vol.Schema( TRIGGER_SCHEMA = vol.Schema(
{vol.Required(CONF_PLATFORM): "device", vol.Required(CONF_DOMAIN): str}, {vol.Required(CONF_PLATFORM): "device", vol.Required(CONF_DOMAIN): str},
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,

View File

@ -7,6 +7,9 @@ from homeassistant.core import callback
from homeassistant.const import CONF_PLATFORM from homeassistant.const import CONF_PLATFORM
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
# mypy: allow-untyped-defs
CONF_EVENT_TYPE = "event_type" CONF_EVENT_TYPE = "event_type"
CONF_EVENT_DATA = "event_data" 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 import condition, config_validation as cv
from homeassistant.helpers.config_validation import entity_domain from homeassistant.helpers.config_validation import entity_domain
# mypy: allow-untyped-defs, no-check-untyped-defs
EVENT_ENTER = "enter" EVENT_ENTER = "enter"
EVENT_LEAVE = "leave" EVENT_LEAVE = "leave"
DEFAULT_EVENT = EVENT_ENTER DEFAULT_EVENT = EVENT_ENTER

View File

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

View File

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

View File

@ -8,6 +8,9 @@ from homeassistant.components import mqtt
from homeassistant.const import CONF_PLATFORM, CONF_PAYLOAD from homeassistant.const import CONF_PLATFORM, CONF_PAYLOAD
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
# mypy: allow-untyped-defs
CONF_ENCODING = "encoding" CONF_ENCODING = "encoding"
CONF_TOPIC = "topic" CONF_TOPIC = "topic"
DEFAULT_ENCODING = "utf-8" 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.event import async_track_state_change, async_track_same_state
from homeassistant.helpers import condition, config_validation as cv, template from homeassistant.helpers import condition, config_validation as cv, template
# mypy: allow-untyped-defs, no-check-untyped-defs
TRIGGER_SCHEMA = vol.All( TRIGGER_SCHEMA = vol.All(
vol.Schema( 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 import config_validation as cv, template
from homeassistant.helpers.event import async_track_state_change, async_track_same_state 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__) _LOGGER = logging.getLogger(__name__)
CONF_ENTITY_ID = "entity_id" 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 from homeassistant.helpers.event import async_track_sunrise, async_track_sunset
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
# mypy: allow-untyped-defs, no-check-untyped-defs
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = vol.Schema( 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.event import async_track_same_state, async_track_template
from homeassistant.helpers import config_validation as cv, template from homeassistant.helpers import config_validation as cv, template
# mypy: allow-untyped-defs, no-check-untyped-defs
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema( 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 import config_validation as cv
from homeassistant.helpers.event import async_track_time_change from homeassistant.helpers.event import async_track_time_change
# mypy: allow-untyped-defs, no-check-untyped-defs
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = vol.Schema( 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 import config_validation as cv
from homeassistant.helpers.event import async_track_time_change from homeassistant.helpers.event import async_track_time_change
# mypy: allow-untyped-defs, no-check-untyped-defs
CONF_HOURS = "hours" CONF_HOURS = "hours"
CONF_MINUTES = "minutes" CONF_MINUTES = "minutes"
CONF_SECONDS = "seconds" CONF_SECONDS = "seconds"

View File

@ -11,6 +11,9 @@ import homeassistant.helpers.config_validation as cv
from . import DOMAIN as AUTOMATION_DOMAIN from . import DOMAIN as AUTOMATION_DOMAIN
# mypy: allow-untyped-defs
DEPENDENCIES = ("webhook",) DEPENDENCIES = ("webhook",)
_LOGGER = logging.getLogger(__name__) _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.event import async_track_state_change
from homeassistant.helpers import condition, config_validation as cv, location from homeassistant.helpers import condition, config_validation as cv, location
# mypy: allow-untyped-defs, no-check-untyped-defs
EVENT_ENTER = "enter" EVENT_ENTER = "enter"
EVENT_LEAVE = "leave" EVENT_LEAVE = "leave"
DEFAULT_EVENT = EVENT_ENTER DEFAULT_EVENT = EVENT_ENTER

View File

@ -2,10 +2,13 @@
"config": { "config": {
"abort": { "abort": {
"already_configured": "El dispositivo ya est\u00e1 configurado", "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": { "error": {
"already_configured": "El dispositivo ya est\u00e1 configurado", "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", "device_unavailable": "El dispositivo no est\u00e1 disponible",
"faulty_credentials": "Credenciales de usuario incorrectas" "faulty_credentials": "Credenciales de usuario incorrectas"
}, },
@ -15,8 +18,10 @@
"password": "Contrase\u00f1a", "password": "Contrase\u00f1a",
"port": "Puerto", "port": "Puerto",
"username": "Nombre de usuario" "username": "Nombre de usuario"
} },
"title": "Configurar dispositivo Axis"
} }
} },
"title": "Dispositivo Axis"
} }
} }

View File

@ -3,7 +3,8 @@
"abort": { "abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"bad_config_file": "Mauvaises donn\u00e9es du fichier de configuration", "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": { "error": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "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, PLATFORM_SCHEMA_BASE,
) )
# mypy: allow-untyped-defs, no-check-untyped-defs
DOMAIN = "binary_sensor" DOMAIN = "binary_sensor"
SCAN_INTERVAL = timedelta(seconds=30) SCAN_INTERVAL = timedelta(seconds=30)

View File

@ -143,7 +143,10 @@ class BMWConnectedDriveAccount:
for listener in self._update_listeners: for listener in self._update_listeners:
listener() listener()
except IOError as exception: 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) _LOGGER.exception(exception)
def add_update_listener(self, listener): def add_update_listener(self, listener):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,9 @@
"""Preference management for camera component.""" """Preference management for camera component."""
from .const import DOMAIN, PREF_PRELOAD_STREAM from .const import DOMAIN, PREF_PRELOAD_STREAM
# mypy: allow-untyped-defs, no-check-untyped-defs
STORAGE_KEY = DOMAIN STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1 STORAGE_VERSION = 1
_UNDEF = object() _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_RANGE,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE,
) )
from .reproduce_state import async_reproduce_states # noqa
DEFAULT_MIN_TEMP = 7 DEFAULT_MIN_TEMP = 7
DEFAULT_MAX_TEMP = 35 DEFAULT_MAX_TEMP = 35

View File

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

View File

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

View File

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

View File

@ -1,6 +1,10 @@
"""Http views to control the config manager.""" """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 import config_entries, data_entry_flow
from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES
from homeassistant.components import websocket_api
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
from homeassistant.exceptions import Unauthorized from homeassistant.exceptions import Unauthorized
from homeassistant.helpers.data_entry_flow import ( 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(ConfigManagerFlowIndexView(hass.config_entries.flow))
hass.http.register_view(ConfigManagerFlowResourceView(hass.config_entries.flow)) hass.http.register_view(ConfigManagerFlowResourceView(hass.config_entries.flow))
hass.http.register_view(ConfigManagerAvailableFlowView) hass.http.register_view(ConfigManagerAvailableFlowView)
hass.http.register_view( hass.http.register_view(
OptionManagerFlowIndexView(hass.config_entries.options.flow) OptionManagerFlowIndexView(hass.config_entries.options.flow)
) )
hass.http.register_view( hass.http.register_view(
OptionManagerFlowResourceView(hass.config_entries.options.flow) 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 return True
@ -54,8 +64,18 @@ class ConfigManagerEntryIndexView(HomeAssistantView):
"""List available config entries.""" """List available config entries."""
hass = request.app["hass"] 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, "entry_id": entry.entry_id,
"domain": entry.domain, "domain": entry.domain,
@ -63,14 +83,11 @@ class ConfigManagerEntryIndexView(HomeAssistantView):
"source": entry.source, "source": entry.source,
"state": entry.state, "state": entry.state,
"connection_class": entry.connection_class, "connection_class": entry.connection_class,
"supports_options": hasattr( "supports_options": supports_options,
config_entries.HANDLERS.get(entry.domain),
"async_get_options_flow",
),
} }
for entry in hass.config_entries.async_entries() )
]
) return self.json(results)
class ConfigManagerEntryResourceView(HomeAssistantView): class ConfigManagerEntryResourceView(HomeAssistantView):
@ -101,23 +118,8 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView):
name = "api:config:config_entries:flow" name = "api:config:config_entries:flow"
async def get(self, request): async def get(self, request):
"""List flows that are in progress but not started by a user. """Not implemented."""
raise aiohttp.web_exceptions.HTTPMethodNotAllowed("GET", ["POST"])
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
]
)
# pylint: disable=arguments-differ # pylint: disable=arguments-differ
async def post(self, request): async def post(self, request):
@ -187,8 +189,8 @@ class ConfigManagerAvailableFlowView(HomeAssistantView):
class OptionManagerFlowIndexView(FlowManagerIndexView): class OptionManagerFlowIndexView(FlowManagerIndexView):
"""View to create option flows.""" """View to create option flows."""
url = "/api/config/config_entries/entry/option/flow" url = "/api/config/config_entries/options/flow"
name = "api:config:config_entries:entry:resource:option:flow" name = "api:config:config_entries:option:flow"
# pylint: disable=arguments-differ # pylint: disable=arguments-differ
async def post(self, request): async def post(self, request):
@ -224,3 +226,62 @@ class OptionManagerFlowResourceView(FlowManagerResourceView):
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
return await super().post(request, flow_id) 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 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): async def async_setup(hass):
"""Enable the Entity Registry views.""" """Enable the Entity Registry views."""
hass.components.websocket_api.async_register_command( hass.components.websocket_api.async_register_command(websocket_list_entities)
WS_TYPE_LIST, websocket_list_entities, SCHEMA_WS_LIST 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( hass.components.websocket_api.async_register_command(websocket_remove_entity)
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
)
return True return True
@async_response @async_response
@websocket_api.websocket_command({vol.Required("type"): "config/entity_registry/list"})
async def websocket_list_entities(hass, connection, msg): async def websocket_list_entities(hass, connection, msg):
"""Handle list registry entries command. """Handle list registry entries command.
@ -70,6 +37,12 @@ async def websocket_list_entities(hass, connection, msg):
@async_response @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): async def websocket_get_entity(hass, connection, msg):
"""Handle get entity registry entry command. """Handle get entity registry entry command.
@ -89,6 +62,17 @@ async def websocket_get_entity(hass, connection, msg):
@require_admin @require_admin
@async_response @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): async def websocket_update_entity(hass, connection, msg):
"""Handle update entity websocket command. """Handle update entity websocket command.
@ -107,6 +91,9 @@ async def websocket_update_entity(hass, connection, msg):
if "name" in msg: if "name" in msg:
changes["name"] = msg["name"] 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"]: if "new_entity_id" in msg and msg["new_entity_id"] != msg["entity_id"]:
changes["new_entity_id"] = msg["new_entity_id"] changes["new_entity_id"] = msg["new_entity_id"]
if hass.states.get(msg["new_entity_id"]) is not None: 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 @require_admin
@async_response @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): async def websocket_remove_entity(hass, connection, msg):
"""Handle remove entity websocket command. """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 import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.components.climate.const import ( from homeassistant.components.climate.const import (
HVAC_MODE_OFF, HVAC_MODE_OFF,
HVAC_MODE_AUTO, HVAC_MODE_HEAT_COOL,
HVAC_MODE_COOL, HVAC_MODE_COOL,
HVAC_MODE_DRY, HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY, HVAC_MODE_FAN_ONLY,
@ -33,14 +33,14 @@ AVAILABLE_MODES = [
HVAC_MODE_HEAT, HVAC_MODE_HEAT,
HVAC_MODE_COOL, HVAC_MODE_COOL,
HVAC_MODE_DRY, HVAC_MODE_DRY,
HVAC_MODE_AUTO, HVAC_MODE_HEAT_COOL,
HVAC_MODE_FAN_ONLY, HVAC_MODE_FAN_ONLY,
] ]
CM_TO_HA_STATE = { CM_TO_HA_STATE = {
"heat": HVAC_MODE_HEAT, "heat": HVAC_MODE_HEAT,
"cool": HVAC_MODE_COOL, "cool": HVAC_MODE_COOL,
"auto": HVAC_MODE_AUTO, "auto": HVAC_MODE_HEAT_COOL,
"dry": HVAC_MODE_DRY, "dry": HVAC_MODE_DRY,
"fan": HVAC_MODE_FAN_ONLY, "fan": HVAC_MODE_FAN_ONLY,
} }

View File

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

View File

@ -2,7 +2,9 @@
"config": { "config": {
"abort": { "abort": {
"already_configured": "El Bridge ya est\u00e1 configurado", "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", "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" "one_instance_only": "El componente solo admite una instancia deCONZ"
}, },
"error": { "error": {
@ -13,7 +15,8 @@
"data": { "data": {
"allow_clip_sensor": "Permitir la importaci\u00f3n de sensores virtuales", "allow_clip_sensor": "Permitir la importaci\u00f3n de sensores virtuales",
"allow_deconz_groups": "Permitir la importaci\u00f3n de grupos deCONZ" "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": { "init": {
"data": { "data": {
@ -23,6 +26,7 @@
"title": "Definir el gateway deCONZ" "title": "Definir el gateway deCONZ"
}, },
"link": { "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" "title": "Enlazar con deCONZ"
}, },
"options": { "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", "host": "\u4e3b\u6a5f\u7aef",
"port": "\u901a\u8a0a\u57e0" "port": "\u901a\u8a0a\u57e0"
}, },
"title": "\u5b9a\u7fa9 deCONZ \u7db2\u95dc" "title": "\u5b9a\u7fa9 deCONZ \u9598\u9053\u5668"
}, },
"link": { "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" "title": "\u9023\u7d50\u81f3 deCONZ"
}, },
"options": { "options": {
@ -39,6 +39,6 @@
"title": "deCONZ \u9644\u52a0\u8a2d\u5b9a\u9078\u9805" "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.""" """Config flow to configure deCONZ component."""
import asyncio import asyncio
from copy import copy
import async_timeout import async_timeout
import voluptuous as vol 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.core import callback
from homeassistant.helpers import aiohttp_client 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" DECONZ_MANUFACTURERURL = "http://www.dresden-elektronik.de"
CONF_SERIAL = "serial" CONF_SERIAL = "serial"
@ -45,6 +52,12 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
_hassio_discovery = None _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): def __init__(self):
"""Initialize the deCONZ config flow.""" """Initialize the deCONZ config flow."""
self.bridges = [] self.bridges = []
@ -234,3 +247,41 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
step_id="hassio_confirm", step_id="hassio_confirm",
description_placeholders={"addon": self._hassio_discovery["addon"]}, 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 .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry 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): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Old way of setting up deCONZ platforms.""" """Old way of setting up deCONZ platforms."""
@ -35,13 +33,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entities = [] entities = []
for light in lights: for light in lights:
if light.type in COVER_TYPES: if light.type in COVER_TYPES:
if light.modelid in ZIGBEE_SPEC: entities.append(DeconzCover(light, gateway))
entities.append(DeconzCoverZigbeeSpec(light, gateway))
else:
entities.append(DeconzCover(light, gateway))
async_add_entities(entities, True) async_add_entities(entities, True)
@ -69,14 +62,12 @@ class DeconzCover(DeconzDevice, CoverDevice):
@property @property
def current_cover_position(self): def current_cover_position(self):
"""Return the current position of the cover.""" """Return the current position of the cover."""
if self.is_closed: return 100 - int(self._device.brightness / 255 * 100)
return 0
return int(self._device.brightness / 255 * 100)
@property @property
def is_closed(self): def is_closed(self):
"""Return if the cover is closed.""" """Return if the cover is closed."""
return not self._device.state return self._device.state
@property @property
def device_class(self): def device_class(self):
@ -96,9 +87,9 @@ class DeconzCover(DeconzDevice, CoverDevice):
position = kwargs[ATTR_POSITION] position = kwargs[ATTR_POSITION]
data = {"on": False} data = {"on": False}
if position > 0: if position < 100:
data["on"] = True data["on"] = True
data["bri"] = int(position / 100 * 255) data["bri"] = 255 - int(position / 100 * 255)
await self._device.async_set_state(data) await self._device.async_set_state(data)
@ -116,28 +107,3 @@ class DeconzCover(DeconzDevice, CoverDevice):
"""Stop cover.""" """Stop cover."""
data = {"bri_inc": 0} data = {"bri_inc": 0}
await self._device.async_set_state(data) 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", "one_instance_only": "Component only supports one deCONZ instance",
"updated_instance": "Updated deCONZ instance with new host address" "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() await line.get_passages()
if line.passages is None: if line.passages is None:
_LOGGER.warning("No data recieved from De Lijn") _LOGGER.warning("No data received from De Lijn")
return return
sensors.append(DeLijnPublicTransportSensor(line, name)) sensors.append(DeLijnPublicTransportSensor(line, name))
@ -71,7 +71,7 @@ class DeLijnPublicTransportSensor(Entity):
"""Get the latest data from the De Lijn API.""" """Get the latest data from the De Lijn API."""
await self.line.get_passages() await self.line.get_passages()
if self.line.passages is None: if self.line.passages is None:
_LOGGER.warning("No data recieved from De Lijn") _LOGGER.warning("No data received from De Lijn")
return return
try: try:
first = self.line.passages[0] first = self.line.passages[0]

View File

@ -176,8 +176,15 @@ async def async_setup(hass, config):
async def finish_setup(hass, config): async def finish_setup(hass, config):
"""Finish set up once demo platforms are set up.""" """Finish set up once demo platforms are set up."""
lights = sorted(hass.states.async_entity_ids("light")) switches = None
switches = sorted(hass.states.async_entity_ids("switch")) 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 # Set up history graph
await bootstrap.async_setup_component( await bootstrap.async_setup_component(

View File

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

View File

@ -5,7 +5,7 @@
"one_instance_allowed": "Wymagana jest tylko jedna instancja." "one_instance_allowed": "Wymagana jest tylko jedna instancja."
}, },
"create_entry": { "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": { "step": {
"user": { "user": {

View File

@ -17,6 +17,9 @@ SOURCE = "Home Assistant Dialogflow"
CONFIG_SCHEMA = vol.Schema({DOMAIN: {}}, extra=vol.ALLOW_EXTRA) CONFIG_SCHEMA = vol.Schema({DOMAIN: {}}, extra=vol.ALLOW_EXTRA)
V1 = 1
V2 = 2
class DialogFlowError(HomeAssistantError): class DialogFlowError(HomeAssistantError):
"""Raised when a DialogFlow error happens.""" """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): def dialogflow_error_response(message, error):
"""Return a response saying the error message.""" """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) dialogflow_response.add_speech(error)
return dialogflow_response.as_dict() 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): async def async_handle_message(hass, message):
"""Handle a DialogFlow message.""" """Handle a DialogFlow message."""
req = message.get("result") _api_version = get_api_version(message)
action_incomplete = req["actionIncomplete"] 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: elif _api_version is V2:
return None req = message.get("queryResult")
if req.get("allRequiredParamsPresent", False) is False:
return
action = req.get("action", "") action = req.get("action", "")
parameters = req.get("parameters").copy() parameters = req.get("parameters").copy()
parameters["dialogflow_query"] = message parameters["dialogflow_query"] = message
dialogflow_response = DialogflowResponse(parameters) dialogflow_response = DialogflowResponse(parameters, _api_version)
if action == "": if action == "":
raise DialogFlowError( raise DialogFlowError(
@ -123,10 +148,11 @@ async def async_handle_message(hass, message):
class DialogflowResponse: class DialogflowResponse:
"""Help generating the response for Dialogflow.""" """Help generating the response for Dialogflow."""
def __init__(self, parameters): def __init__(self, parameters, api_version):
"""Initialize the Dialogflow response.""" """Initialize the Dialogflow response."""
self.speech = None self.speech = None
self.parameters = {} self.parameters = {}
self.api_version = api_version
# Parameter names replace '.' and '-' for '_' # Parameter names replace '.' and '-' for '_'
for key, value in parameters.items(): for key, value in parameters.items():
underscored_key = key.replace(".", "_").replace("-", "_") underscored_key = key.replace(".", "_").replace("-", "_")
@ -143,4 +169,8 @@ class DialogflowResponse:
def as_dict(self): def as_dict(self):
"""Return response in a Dialogflow valid dictionary.""" """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 Consumption", obis_ref.CURRENT_ELECTRICITY_USAGE],
["Power Production", obis_ref.CURRENT_ELECTRICITY_DELIVERY], ["Power Production", obis_ref.CURRENT_ELECTRICITY_DELIVERY],
["Power Tariff", obis_ref.ELECTRICITY_ACTIVE_TARIFF], ["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 (low)", obis_ref.ELECTRICITY_USED_TARIFF_1],
["Power Consumption (normal)", obis_ref.ELECTRICITY_USED_TARIFF_2], ["Power Consumption (normal)", obis_ref.ELECTRICITY_USED_TARIFF_2],
["Power Production (low)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_1], ["Power Production (low)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_1],

View File

@ -304,7 +304,7 @@ class Thermostat(ClimateDevice):
self.vacation = event["name"] self.vacation = event["name"]
return PRESET_VACATION return PRESET_VACATION
return None return self._preset_modes[self.thermostat["program"]["currentClimateRef"]]
@property @property
def hvac_mode(self): def hvac_mode(self):
@ -357,6 +357,9 @@ class Thermostat(ClimateDevice):
status = self.thermostat["equipmentStatus"] status = self.thermostat["equipmentStatus"]
return { return {
"fan": self.fan, "fan": self.fan,
"climate_mode": self._preset_modes[
self.thermostat["program"]["currentClimateRef"]
],
"equipment_running": status, "equipment_running": status,
"fan_min_on_time": self.thermostat["settings"]["fanMinOnTime"], "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 import logging
from aiohttp import web from aiohttp import web

View File

@ -562,17 +562,27 @@ def get_entity_state(config, entity):
def entity_to_json(config, entity, state): def entity_to_json(config, entity, state):
"""Convert an entity to its Hue bridge JSON representation.""" """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 { return {
"state": { "state": {HUE_API_STATE_ON: state[STATE_ON], "reachable": True},
HUE_API_STATE_ON: state[STATE_ON], "type": "On/off light",
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), "name": config.get_entity_name(entity),
"modelid": "HASS123", "modelid": "HASS321",
"uniqueid": entity.entity_id, "uniqueid": entity.entity_id,
"swversion": "123", "swversion": "123",
} }

View File

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

View File

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

View File

@ -133,10 +133,15 @@ class ECSensor(Entity):
ATTR_TIME: " | ".join([str(s.get("date")) for s in value]), ATTR_TIME: " | ".join([str(s.get("date")) for s in value]),
} }
) )
elif self.sensor_type == "tendency":
self._state = str(value).capitalize()
else: else:
self._state = value 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 self._unit = TEMP_CELSIUS
else: else:
self._unit = sensor_data.get("unit") self._unit = sensor_data.get("unit")

View File

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

View File

@ -8,6 +8,7 @@
"invalid_password": "\u00a1Contrase\u00f1a invalida!", "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" "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": { "step": {
"authenticate": { "authenticate": {
"data": { "data": {
@ -17,6 +18,7 @@
"title": "Escriba la contrase\u00f1a" "title": "Escriba la contrase\u00f1a"
}, },
"discovery_confirm": { "discovery_confirm": {
"description": "\u00bfDesea agregar el nodo ESPHome `{name}` a Home Assistant?",
"title": "Nodo ESPHome descubierto" "title": "Nodo ESPHome descubierto"
}, },
"user": { "user": {

View File

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

View File

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

View File

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

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