mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
commit
688f5b7698
11
.coveragerc
11
.coveragerc
@ -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
|
||||||
|
@ -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
6
.gitignore
vendored
@ -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
|
||||||
|
@ -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
|
||||||
|
12
.travis.yml
12
.travis.yml
@ -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
|
||||||
|
|
||||||
|
16
CODEOWNERS
16
CODEOWNERS
@ -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
|
||||||
|
38
Dockerfile
38
Dockerfile
@ -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" ]
|
|
@ -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
|
@ -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,16 +98,10 @@ 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)'
|
|
||||||
- script: |
|
|
||||||
set -e
|
set -e
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
|
|
||||||
@ -114,23 +111,10 @@ stages:
|
|||||||
# 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,16 +147,10 @@ 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)'
|
|
||||||
- script: |
|
|
||||||
set -e
|
set -e
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
|
|
||||||
@ -179,18 +158,6 @@ stages:
|
|||||||
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: |
|
||||||
|
@ -5,35 +5,37 @@ trigger:
|
|||||||
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'
|
||||||
|
@ -9,51 +9,31 @@ 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
|
||||||
- job: 'Wheels'
|
parameters:
|
||||||
timeoutInMinutes: 360
|
builderVersion: '$(versionWheels)'
|
||||||
pool:
|
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'
|
||||||
vmImage: 'ubuntu-latest'
|
wheelsRequirement: 'requirements_wheels.txt'
|
||||||
strategy:
|
wheelsRequirementDiff: 'requirements_diff.txt'
|
||||||
maxParallel: 5
|
preBuild:
|
||||||
matrix:
|
|
||||||
amd64:
|
|
||||||
buildArch: 'amd64'
|
|
||||||
i386:
|
|
||||||
buildArch: 'i386'
|
|
||||||
armhf:
|
|
||||||
buildArch: 'armhf'
|
|
||||||
armv7:
|
|
||||||
buildArch: 'armv7'
|
|
||||||
aarch64:
|
|
||||||
buildArch: 'aarch64'
|
|
||||||
steps:
|
|
||||||
- script: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y --no-install-recommends \
|
|
||||||
qemu-user-static \
|
|
||||||
binfmt-support \
|
|
||||||
curl
|
|
||||||
|
|
||||||
sudo mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
|
|
||||||
sudo update-binfmts --enable qemu-arm
|
|
||||||
sudo update-binfmts --enable qemu-aarch64
|
|
||||||
displayName: 'Initial cross build'
|
|
||||||
- script: |
|
|
||||||
mkdir -p .ssh
|
|
||||||
echo -e "-----BEGIN RSA PRIVATE KEY-----\n$(wheelsSSH)\n-----END RSA PRIVATE KEY-----" >> .ssh/id_rsa
|
|
||||||
ssh-keyscan -H $(wheelsHost) >> .ssh/known_hosts
|
|
||||||
chmod 600 .ssh/*
|
|
||||||
displayName: 'Install ssh key'
|
|
||||||
- script: sudo docker pull homeassistant/$(buildArch)-wheels:$(versionWheels)
|
|
||||||
displayName: 'Install wheels builder'
|
|
||||||
- script: |
|
- script: |
|
||||||
cp requirements_all.txt requirements_wheels.txt
|
cp requirements_all.txt requirements_wheels.txt
|
||||||
if [[ "$(Build.Reason)" =~ (Schedule|Manual) ]]; then
|
if [[ "$(Build.Reason)" =~ (Schedule|Manual) ]]; then
|
||||||
@ -87,13 +67,3 @@ jobs:
|
|||||||
sed -i "s|# face_recognition|face_recognition|g" ${requirement_file}
|
sed -i "s|# face_recognition|face_recognition|g" ${requirement_file}
|
||||||
done
|
done
|
||||||
displayName: 'Prepare requirements files for Hass.io'
|
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'
|
|
||||||
|
@ -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
|
|
||||||
from homeassistant.util.async_ import run_callback_threadsafe
|
|
||||||
|
|
||||||
def open_browser(_: Any) -> None:
|
|
||||||
"""Open the web interface in a browser."""
|
|
||||||
if hass.config.api is not None:
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
webbrowser.open(hass.config.api.base_url)
|
hass.add_job(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()
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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."""
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"username": "Nom d'utilisateur"
|
"username": "Nom d'utilisateur"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"title": "AdGuard Home"
|
||||||
}
|
}
|
||||||
}
|
}
|
7
homeassistant/components/adguard/.translations/hr.json
Normal file
7
homeassistant/components/adguard/.translations/hr.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"existing_instance_updated": "Postoje\u0107a konfiguracija je a\u017eurirana."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
homeassistant/components/adguard/.translations/id.json
Normal file
15
homeassistant/components/adguard/.translations/id.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"connection_error": "Gagal terhubung."
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"password": "Kata sandi",
|
||||||
|
"port": "Port"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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(
|
||||||
[
|
[
|
||||||
|
@ -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)
|
||||||
|
@ -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".
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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": [
|
||||||
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
@ -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]
|
||||||
@ -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."""
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"title": "Arcam FMJ"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"title": "Arcam FMJ"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"abort": {
|
|
||||||
"one": "Een",
|
|
||||||
"other": "Ander"
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"one": "Een",
|
|
||||||
"other": "Ander"
|
|
||||||
},
|
|
||||||
"step": {
|
|
||||||
"one": "Een",
|
|
||||||
"other": "Ander"
|
|
||||||
},
|
|
||||||
"title": "Arcam FMJ"
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"title": "Arcam FMJ"
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"title": "Arcam FMJ",
|
"title": "Arcam FMJ"
|
||||||
"step": {},
|
|
||||||
"error": {},
|
|
||||||
"abort": {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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} ` **.",
|
||||||
|
@ -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))
|
||||||
|
@ -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 + ".{}"
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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__)
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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(
|
||||||
{
|
{
|
||||||
|
@ -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"
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
@ -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"
|
||||||
|
@ -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__)
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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",
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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,15 +35,17 @@ 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()):
|
||||||
|
if key in vehicle.available_attributes:
|
||||||
device = BMWConnectedDriveSensor(
|
device = BMWConnectedDriveSensor(
|
||||||
account, vehicle, key, value[0], value[1]
|
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()):
|
||||||
|
if key in vehicle.available_attributes:
|
||||||
device = BMWConnectedDriveSensor(
|
device = BMWConnectedDriveSensor(
|
||||||
account, vehicle, key, value[0], value[1]
|
account, vehicle, key, value[0], value[1], value[2]
|
||||||
)
|
)
|
||||||
devices.append(device)
|
devices.append(device)
|
||||||
add_entities(devices, True)
|
add_entities(devices, True)
|
||||||
@ -52,7 +54,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
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 = {}
|
||||||
|
@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -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:
|
||||||
|
if attribute_name in vehicle.available_attributes:
|
||||||
device = BMWConnectedDriveSensor(
|
device = BMWConnectedDriveSensor(
|
||||||
account, vehicle, attribute_name, attribute_info
|
account, vehicle, attribute_name, attribute_info
|
||||||
)
|
)
|
||||||
devices.append(device)
|
devices.append(device)
|
||||||
device = BMWConnectedDriveSensor(
|
|
||||||
account, vehicle, "mileage", attribute_info
|
|
||||||
)
|
|
||||||
devices.append(device)
|
|
||||||
add_entities(devices, True)
|
add_entities(devices, True)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
10
homeassistant/components/cast/.translations/hr.json
Normal file
10
homeassistant/components/cast/.translations/hr.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"confirm": {
|
||||||
|
"title": "Google Cast"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Google Cast"
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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:
|
||||||
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
@ -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,15 +83,12 @@ 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):
|
||||||
"""View to interact with a config entry."""
|
"""View to interact with a config entry."""
|
||||||
@ -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())
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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": {
|
||||||
|
17
homeassistant/components/deconz/.translations/hr.json
Normal file
17
homeassistant/components/deconz/.translations/hr.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"port": "Port"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"data": {
|
||||||
|
"allow_clip_sensor": "Dopusti uvoz virtualnih senzora"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@ -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,12 +33,7 @@ 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(DeconzCoverZigbeeSpec(light, gateway))
|
|
||||||
|
|
||||||
else:
|
|
||||||
entities.append(DeconzCover(light, gateway))
|
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)
|
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
|
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"))
|
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(
|
||||||
|
@ -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",
|
||||||
|
@ -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": {
|
||||||
|
@ -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."""
|
||||||
|
_api_version = get_api_version(message)
|
||||||
|
if _api_version is V1:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Dialogflow V1 API will be removed on October 23, 2019. Please change your DialogFlow settings to use the V2 api"
|
||||||
|
)
|
||||||
req = message.get("result")
|
req = message.get("result")
|
||||||
action_incomplete = req["actionIncomplete"]
|
action_incomplete = req.get("actionIncomplete", True)
|
||||||
|
|
||||||
if action_incomplete:
|
if action_incomplete:
|
||||||
return None
|
return
|
||||||
|
|
||||||
|
elif _api_version is V2:
|
||||||
|
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."""
|
||||||
|
if self.api_version is V1:
|
||||||
return {"speech": self.speech, "displayText": self.speech, "source": SOURCE}
|
return {"speech": self.speech, "displayText": self.speech, "source": SOURCE}
|
||||||
|
|
||||||
|
if self.api_version is V2:
|
||||||
|
return {"fulfillmentText": self.speech, "source": SOURCE}
|
||||||
|
@ -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],
|
||||||
|
@ -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"],
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
@ -562,6 +562,8 @@ 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 {
|
return {
|
||||||
"state": {
|
"state": {
|
||||||
HUE_API_STATE_ON: state[STATE_ON],
|
HUE_API_STATE_ON: state[STATE_ON],
|
||||||
@ -576,6 +578,14 @@ def entity_to_json(config, entity, state):
|
|||||||
"uniqueid": entity.entity_id,
|
"uniqueid": entity.entity_id,
|
||||||
"swversion": "123",
|
"swversion": "123",
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
"state": {HUE_API_STATE_ON: state[STATE_ON], "reachable": True},
|
||||||
|
"type": "On/off light",
|
||||||
|
"name": config.get_entity_name(entity),
|
||||||
|
"modelid": "HASS321",
|
||||||
|
"uniqueid": entity.entity_id,
|
||||||
|
"swversion": "123",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def create_hue_success_response(entity_id, attr, value):
|
def create_hue_success_response(entity_id, attr, value):
|
||||||
|
@ -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()
|
||||||
|
@ -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": [
|
||||||
|
@ -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")
|
||||||
|
@ -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):
|
||||||
|
@ -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": {
|
||||||
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -44,6 +44,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
"""Set up the Fedex platform."""
|
"""Set up the Fedex platform."""
|
||||||
import fedexdeliverymanager
|
import fedexdeliverymanager
|
||||||
|
|
||||||
|
_LOGGER.warning(
|
||||||
|
"The fedex integration is deprecated and will be removed "
|
||||||
|
"in Home Assistant 0.100.0. For more information see ADR-0004:"
|
||||||
|
"https://github.com/home-assistant/architecture/blob/master/adr/0004-webscraping.md"
|
||||||
|
)
|
||||||
|
|
||||||
name = config.get(CONF_NAME)
|
name = config.get(CONF_NAME)
|
||||||
update_interval = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL)
|
update_interval = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL)
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user