Merge pull request #65442 from home-assistant/rc

This commit is contained in:
Franck Nijhof 2022-02-02 19:44:34 +01:00 committed by GitHub
commit 2f638a6b5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6398 changed files with 178106 additions and 55363 deletions

View File

@ -6,7 +6,7 @@ core: &core
- homeassistant/helpers/* - homeassistant/helpers/*
- homeassistant/package_constraints.txt - homeassistant/package_constraints.txt
- homeassistant/util/* - homeassistant/util/*
- pyproject.yaml - pyproject.toml
- requirements.txt - requirements.txt
- setup.cfg - setup.cfg
@ -21,6 +21,7 @@ base_platforms: &base_platforms
- homeassistant/components/climate/* - homeassistant/components/climate/*
- homeassistant/components/cover/* - homeassistant/components/cover/*
- homeassistant/components/device_tracker/* - homeassistant/components/device_tracker/*
- homeassistant/components/diagnostics/*
- homeassistant/components/fan/* - homeassistant/components/fan/*
- homeassistant/components/geo_location/* - homeassistant/components/geo_location/*
- homeassistant/components/humidifier/* - homeassistant/components/humidifier/*
@ -65,6 +66,7 @@ components: &components
- homeassistant/components/homeassistant/** - homeassistant/components/homeassistant/**
- homeassistant/components/image/* - homeassistant/components/image/*
- homeassistant/components/input_boolean/* - homeassistant/components/input_boolean/*
- homeassistant/components/input_button/*
- homeassistant/components/input_datetime/* - homeassistant/components/input_datetime/*
- homeassistant/components/input_number/* - homeassistant/components/input_number/*
- homeassistant/components/input_select/* - homeassistant/components/input_select/*
@ -101,17 +103,29 @@ tests: &tests
- codecov.yaml - codecov.yaml
- requirements_test_pre_commit.txt - requirements_test_pre_commit.txt
- requirements_test.txt - requirements_test.txt
- tests/auth/**
- tests/backports/*
- tests/common.py - tests/common.py
- tests/conftest.py - tests/conftest.py
- tests/hassfest/*
- tests/helpers/*
- tests/ignore_uncaught_exceptions.py - tests/ignore_uncaught_exceptions.py
- tests/mock/* - tests/mock/*
- tests/scripts/*
- tests/test_util/* - tests/test_util/*
- tests/testing_config/** - tests/testing_config/**
- tests/util/**
other: &other other: &other
- .github/workflows/* - .github/workflows/*
- homeassistant/scripts/** - homeassistant/scripts/**
requirements:
- .github/workflows/*
- homeassistant/package_constraints.txt
- requirements*.txt
- setup.py
any: any:
- *base_platforms - *base_platforms
- *components - *components

View File

@ -27,6 +27,7 @@ omit =
homeassistant/components/adguard/sensor.py homeassistant/components/adguard/sensor.py
homeassistant/components/adguard/switch.py homeassistant/components/adguard/switch.py
homeassistant/components/ads/* homeassistant/components/ads/*
homeassistant/components/advantage_air/diagnostics.py
homeassistant/components/aemet/weather_update_coordinator.py homeassistant/components/aemet/weather_update_coordinator.py
homeassistant/components/aftership/* homeassistant/components/aftership/*
homeassistant/components/agent_dvr/alarm_control_panel.py homeassistant/components/agent_dvr/alarm_control_panel.py
@ -55,6 +56,7 @@ omit =
homeassistant/components/amcrest/* homeassistant/components/amcrest/*
homeassistant/components/ampio/* homeassistant/components/ampio/*
homeassistant/components/android_ip_webcam/* homeassistant/components/android_ip_webcam/*
homeassistant/components/androidtv/__init__.py
homeassistant/components/anel_pwrctrl/switch.py homeassistant/components/anel_pwrctrl/switch.py
homeassistant/components/anthemav/media_player.py homeassistant/components/anthemav/media_player.py
homeassistant/components/apcupsd/* homeassistant/components/apcupsd/*
@ -65,7 +67,6 @@ omit =
homeassistant/components/aquostv/media_player.py homeassistant/components/aquostv/media_player.py
homeassistant/components/arcam_fmj/media_player.py homeassistant/components/arcam_fmj/media_player.py
homeassistant/components/arcam_fmj/__init__.py homeassistant/components/arcam_fmj/__init__.py
homeassistant/components/arduino/*
homeassistant/components/arest/binary_sensor.py homeassistant/components/arest/binary_sensor.py
homeassistant/components/arest/sensor.py homeassistant/components/arest/sensor.py
homeassistant/components/arest/switch.py homeassistant/components/arest/switch.py
@ -73,6 +74,9 @@ omit =
homeassistant/components/arris_tg2492lg/* homeassistant/components/arris_tg2492lg/*
homeassistant/components/aruba/device_tracker.py homeassistant/components/aruba/device_tracker.py
homeassistant/components/arwn/sensor.py homeassistant/components/arwn/sensor.py
homeassistant/components/aseko_pool_live/__init__.py
homeassistant/components/aseko_pool_live/entity.py
homeassistant/components/aseko_pool_live/sensor.py
homeassistant/components/asterisk_cdr/mailbox.py homeassistant/components/asterisk_cdr/mailbox.py
homeassistant/components/asterisk_mbox/* homeassistant/components/asterisk_mbox/*
homeassistant/components/asuswrt/__init__.py homeassistant/components/asuswrt/__init__.py
@ -91,7 +95,6 @@ omit =
homeassistant/components/azure_service_bus/* homeassistant/components/azure_service_bus/*
homeassistant/components/baidu/tts.py homeassistant/components/baidu/tts.py
homeassistant/components/balboa/__init__.py homeassistant/components/balboa/__init__.py
homeassistant/components/balboa/entity.py
homeassistant/components/beewi_smartclim/sensor.py homeassistant/components/beewi_smartclim/sensor.py
homeassistant/components/bbb_gpio/* homeassistant/components/bbb_gpio/*
homeassistant/components/bbox/device_tracker.py homeassistant/components/bbox/device_tracker.py
@ -118,6 +121,7 @@ omit =
homeassistant/components/bmp280/sensor.py homeassistant/components/bmp280/sensor.py
homeassistant/components/bmw_connected_drive/__init__.py homeassistant/components/bmw_connected_drive/__init__.py
homeassistant/components/bmw_connected_drive/binary_sensor.py homeassistant/components/bmw_connected_drive/binary_sensor.py
homeassistant/components/bmw_connected_drive/button.py
homeassistant/components/bmw_connected_drive/device_tracker.py homeassistant/components/bmw_connected_drive/device_tracker.py
homeassistant/components/bmw_connected_drive/lock.py homeassistant/components/bmw_connected_drive/lock.py
homeassistant/components/bmw_connected_drive/notify.py homeassistant/components/bmw_connected_drive/notify.py
@ -176,7 +180,6 @@ omit =
homeassistant/components/coolmaster/climate.py homeassistant/components/coolmaster/climate.py
homeassistant/components/coolmaster/const.py homeassistant/components/coolmaster/const.py
homeassistant/components/cppm_tracker/device_tracker.py homeassistant/components/cppm_tracker/device_tracker.py
homeassistant/components/cpuspeed/sensor.py
homeassistant/components/crownstone/__init__.py homeassistant/components/crownstone/__init__.py
homeassistant/components/crownstone/const.py homeassistant/components/crownstone/const.py
homeassistant/components/crownstone/listeners.py homeassistant/components/crownstone/listeners.py
@ -203,7 +206,6 @@ omit =
homeassistant/components/devolo_home_control/climate.py homeassistant/components/devolo_home_control/climate.py
homeassistant/components/devolo_home_control/const.py homeassistant/components/devolo_home_control/const.py
homeassistant/components/devolo_home_control/cover.py homeassistant/components/devolo_home_control/cover.py
homeassistant/components/devolo_home_control/devolo_multi_level_switch.py
homeassistant/components/devolo_home_control/light.py homeassistant/components/devolo_home_control/light.py
homeassistant/components/devolo_home_control/sensor.py homeassistant/components/devolo_home_control/sensor.py
homeassistant/components/devolo_home_control/subscriber.py homeassistant/components/devolo_home_control/subscriber.py
@ -216,6 +218,7 @@ omit =
homeassistant/components/dlib_face_detect/image_processing.py homeassistant/components/dlib_face_detect/image_processing.py
homeassistant/components/dlib_face_identify/image_processing.py homeassistant/components/dlib_face_identify/image_processing.py
homeassistant/components/dlink/switch.py homeassistant/components/dlink/switch.py
homeassistant/components/dnsip/__init__.py
homeassistant/components/dnsip/sensor.py homeassistant/components/dnsip/sensor.py
homeassistant/components/dominos/* homeassistant/components/dominos/*
homeassistant/components/doods/* homeassistant/components/doods/*
@ -254,6 +257,10 @@ omit =
homeassistant/components/eight_sleep/* homeassistant/components/eight_sleep/*
homeassistant/components/eliqonline/sensor.py homeassistant/components/eliqonline/sensor.py
homeassistant/components/elkm1/* homeassistant/components/elkm1/*
homeassistant/components/elmax/__init__.py
homeassistant/components/elmax/common.py
homeassistant/components/elmax/const.py
homeassistant/components/elmax/switch.py
homeassistant/components/elv/* homeassistant/components/elv/*
homeassistant/components/emby/media_player.py homeassistant/components/emby/media_player.py
homeassistant/components/emoncms/sensor.py homeassistant/components/emoncms/sensor.py
@ -365,9 +372,11 @@ omit =
homeassistant/components/freebox/switch.py homeassistant/components/freebox/switch.py
homeassistant/components/fritz/__init__.py homeassistant/components/fritz/__init__.py
homeassistant/components/fritz/binary_sensor.py homeassistant/components/fritz/binary_sensor.py
homeassistant/components/fritz/button.py
homeassistant/components/fritz/common.py homeassistant/components/fritz/common.py
homeassistant/components/fritz/const.py homeassistant/components/fritz/const.py
homeassistant/components/fritz/device_tracker.py homeassistant/components/fritz/device_tracker.py
homeassistant/components/fritz/diagnostics.py
homeassistant/components/fritz/sensor.py homeassistant/components/fritz/sensor.py
homeassistant/components/fritz/services.py homeassistant/components/fritz/services.py
homeassistant/components/fritz/switch.py homeassistant/components/fritz/switch.py
@ -383,6 +392,8 @@ omit =
homeassistant/components/garages_amsterdam/sensor.py homeassistant/components/garages_amsterdam/sensor.py
homeassistant/components/gc100/* homeassistant/components/gc100/*
homeassistant/components/geniushub/* homeassistant/components/geniushub/*
homeassistant/components/github/__init__.py
homeassistant/components/github/coordinator.py
homeassistant/components/github/sensor.py homeassistant/components/github/sensor.py
homeassistant/components/gitlab_ci/sensor.py homeassistant/components/gitlab_ci/sensor.py
homeassistant/components/gitter/sensor.py homeassistant/components/gitter/sensor.py
@ -391,7 +402,12 @@ omit =
homeassistant/components/glances/sensor.py homeassistant/components/glances/sensor.py
homeassistant/components/gntp/notify.py homeassistant/components/gntp/notify.py
homeassistant/components/goalfeed/* homeassistant/components/goalfeed/*
homeassistant/components/google/* homeassistant/components/goodwe/__init__.py
homeassistant/components/goodwe/const.py
homeassistant/components/goodwe/number.py
homeassistant/components/goodwe/select.py
homeassistant/components/goodwe/sensor.py
homeassistant/components/google/__init__.py
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_pubsub/__init__.py homeassistant/components/google_pubsub/__init__.py
@ -448,6 +464,7 @@ omit =
homeassistant/components/homematic/* homeassistant/components/homematic/*
homeassistant/components/home_plus_control/api.py homeassistant/components/home_plus_control/api.py
homeassistant/components/home_plus_control/switch.py homeassistant/components/home_plus_control/switch.py
homeassistant/components/homewizard/diagnostics.py
homeassistant/components/homeworks/* homeassistant/components/homeworks/*
homeassistant/components/honeywell/__init__.py homeassistant/components/honeywell/__init__.py
homeassistant/components/honeywell/climate.py homeassistant/components/honeywell/climate.py
@ -496,6 +513,10 @@ omit =
homeassistant/components/insteon/schemas.py homeassistant/components/insteon/schemas.py
homeassistant/components/insteon/switch.py homeassistant/components/insteon/switch.py
homeassistant/components/insteon/utils.py homeassistant/components/insteon/utils.py
homeassistant/components/intellifire/__init__.py
homeassistant/components/intellifire/coordinator.py
homeassistant/components/intellifire/binary_sensor.py
homeassistant/components/intellifire/sensor.py
homeassistant/components/incomfort/* homeassistant/components/incomfort/*
homeassistant/components/intesishome/* homeassistant/components/intesishome/*
homeassistant/components/ios/* homeassistant/components/ios/*
@ -542,11 +563,7 @@ omit =
homeassistant/components/knx/__init__.py homeassistant/components/knx/__init__.py
homeassistant/components/knx/climate.py homeassistant/components/knx/climate.py
homeassistant/components/knx/cover.py homeassistant/components/knx/cover.py
homeassistant/components/knx/expose.py
homeassistant/components/knx/knx_entity.py
homeassistant/components/knx/light.py
homeassistant/components/knx/notify.py homeassistant/components/knx/notify.py
homeassistant/components/knx/schema.py
homeassistant/components/kodi/__init__.py homeassistant/components/kodi/__init__.py
homeassistant/components/kodi/browse_media.py homeassistant/components/kodi/browse_media.py
homeassistant/components/kodi/const.py homeassistant/components/kodi/const.py
@ -564,17 +581,17 @@ omit =
homeassistant/components/lametric/* homeassistant/components/lametric/*
homeassistant/components/lannouncer/notify.py homeassistant/components/lannouncer/notify.py
homeassistant/components/lastfm/sensor.py homeassistant/components/lastfm/sensor.py
homeassistant/components/launch_library/__init__.py
homeassistant/components/launch_library/const.py homeassistant/components/launch_library/const.py
homeassistant/components/launch_library/diagnostics.py
homeassistant/components/launch_library/sensor.py homeassistant/components/launch_library/sensor.py
homeassistant/components/lcn/binary_sensor.py homeassistant/components/lcn/binary_sensor.py
homeassistant/components/lcn/climate.py homeassistant/components/lcn/climate.py
homeassistant/components/lcn/cover.py homeassistant/components/lcn/cover.py
homeassistant/components/lcn/helpers.py homeassistant/components/lcn/helpers.py
homeassistant/components/lcn/light.py
homeassistant/components/lcn/scene.py homeassistant/components/lcn/scene.py
homeassistant/components/lcn/sensor.py homeassistant/components/lcn/sensor.py
homeassistant/components/lcn/services.py homeassistant/components/lcn/services.py
homeassistant/components/lcn/switch.py
homeassistant/components/lg_netcast/media_player.py homeassistant/components/lg_netcast/media_player.py
homeassistant/components/lg_soundbar/media_player.py homeassistant/components/lg_soundbar/media_player.py
homeassistant/components/life360/* homeassistant/components/life360/*
@ -593,12 +610,14 @@ omit =
homeassistant/components/logi_circle/sensor.py homeassistant/components/logi_circle/sensor.py
homeassistant/components/london_underground/sensor.py homeassistant/components/london_underground/sensor.py
homeassistant/components/lookin/__init__.py homeassistant/components/lookin/__init__.py
homeassistant/components/lookin/coordinator.py
homeassistant/components/lookin/entity.py homeassistant/components/lookin/entity.py
homeassistant/components/lookin/models.py homeassistant/components/lookin/models.py
homeassistant/components/lookin/sensor.py homeassistant/components/lookin/sensor.py
homeassistant/components/lookin/climate.py homeassistant/components/lookin/climate.py
homeassistant/components/lookin/media_player.py
homeassistant/components/lookin/light.py
homeassistant/components/luci/device_tracker.py homeassistant/components/luci/device_tracker.py
homeassistant/components/luftdaten/__init__.py
homeassistant/components/luftdaten/sensor.py homeassistant/components/luftdaten/sensor.py
homeassistant/components/lupusec/* homeassistant/components/lupusec/*
homeassistant/components/lutron/* homeassistant/components/lutron/*
@ -697,6 +716,7 @@ omit =
homeassistant/components/nad/media_player.py homeassistant/components/nad/media_player.py
homeassistant/components/nanoleaf/__init__.py homeassistant/components/nanoleaf/__init__.py
homeassistant/components/nanoleaf/button.py homeassistant/components/nanoleaf/button.py
homeassistant/components/nanoleaf/diagnostics.py
homeassistant/components/nanoleaf/entity.py homeassistant/components/nanoleaf/entity.py
homeassistant/components/nanoleaf/light.py homeassistant/components/nanoleaf/light.py
homeassistant/components/neato/__init__.py homeassistant/components/neato/__init__.py
@ -716,7 +736,9 @@ omit =
homeassistant/components/netgear_lte/* homeassistant/components/netgear_lte/*
homeassistant/components/netio/switch.py homeassistant/components/netio/switch.py
homeassistant/components/neurio_energy/sensor.py homeassistant/components/neurio_energy/sensor.py
homeassistant/components/nexia/entity.py
homeassistant/components/nexia/climate.py homeassistant/components/nexia/climate.py
homeassistant/components/nexia/switch.py
homeassistant/components/nextcloud/* homeassistant/components/nextcloud/*
homeassistant/components/nfandroidtv/__init__.py homeassistant/components/nfandroidtv/__init__.py
homeassistant/components/nfandroidtv/notify.py homeassistant/components/nfandroidtv/notify.py
@ -763,6 +785,8 @@ omit =
homeassistant/components/onvif/event.py homeassistant/components/onvif/event.py
homeassistant/components/onvif/parsers.py homeassistant/components/onvif/parsers.py
homeassistant/components/onvif/sensor.py homeassistant/components/onvif/sensor.py
homeassistant/components/open_meteo/diagnostics.py
homeassistant/components/open_meteo/weather.py
homeassistant/components/opencv/* homeassistant/components/opencv/*
homeassistant/components/openevse/sensor.py homeassistant/components/openevse/sensor.py
homeassistant/components/openexchangerates/sensor.py homeassistant/components/openexchangerates/sensor.py
@ -793,6 +817,22 @@ omit =
homeassistant/components/orvibo/switch.py homeassistant/components/orvibo/switch.py
homeassistant/components/osramlightify/light.py homeassistant/components/osramlightify/light.py
homeassistant/components/otp/sensor.py homeassistant/components/otp/sensor.py
homeassistant/components/overkiz/__init__.py
homeassistant/components/overkiz/binary_sensor.py
homeassistant/components/overkiz/button.py
homeassistant/components/overkiz/cover.py
homeassistant/components/overkiz/cover_entities/*
homeassistant/components/overkiz/coordinator.py
homeassistant/components/overkiz/diagnostics.py
homeassistant/components/overkiz/entity.py
homeassistant/components/overkiz/executor.py
homeassistant/components/overkiz/light.py
homeassistant/components/overkiz/lock.py
homeassistant/components/overkiz/number.py
homeassistant/components/overkiz/scene.py
homeassistant/components/overkiz/select.py
homeassistant/components/overkiz/sensor.py
homeassistant/components/overkiz/switch.py
homeassistant/components/ovo_energy/__init__.py homeassistant/components/ovo_energy/__init__.py
homeassistant/components/ovo_energy/const.py homeassistant/components/ovo_energy/const.py
homeassistant/components/ovo_energy/sensor.py homeassistant/components/ovo_energy/sensor.py
@ -808,6 +848,7 @@ omit =
homeassistant/components/philips_js/light.py homeassistant/components/philips_js/light.py
homeassistant/components/philips_js/media_player.py homeassistant/components/philips_js/media_player.py
homeassistant/components/philips_js/remote.py homeassistant/components/philips_js/remote.py
homeassistant/components/philips_js/switch.py
homeassistant/components/pi_hole/sensor.py homeassistant/components/pi_hole/sensor.py
homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py
homeassistant/components/pi4ioe5v9xxxx/switch.py homeassistant/components/pi4ioe5v9xxxx/switch.py
@ -837,7 +878,6 @@ omit =
homeassistant/components/progettihwsw/__init__.py homeassistant/components/progettihwsw/__init__.py
homeassistant/components/progettihwsw/binary_sensor.py homeassistant/components/progettihwsw/binary_sensor.py
homeassistant/components/progettihwsw/switch.py homeassistant/components/progettihwsw/switch.py
homeassistant/components/prometheus/*
homeassistant/components/prowl/notify.py homeassistant/components/prowl/notify.py
homeassistant/components/proxmoxve/* homeassistant/components/proxmoxve/*
homeassistant/components/proxy/camera.py homeassistant/components/proxy/camera.py
@ -846,7 +886,6 @@ omit =
homeassistant/components/pushbullet/sensor.py homeassistant/components/pushbullet/sensor.py
homeassistant/components/pushover/notify.py homeassistant/components/pushover/notify.py
homeassistant/components/pushsafer/notify.py homeassistant/components/pushsafer/notify.py
homeassistant/components/pvoutput/sensor.py
homeassistant/components/pyload/sensor.py homeassistant/components/pyload/sensor.py
homeassistant/components/qbittorrent/sensor.py homeassistant/components/qbittorrent/sensor.py
homeassistant/components/qnap/sensor.py homeassistant/components/qnap/sensor.py
@ -879,6 +918,7 @@ omit =
homeassistant/components/rest/switch.py homeassistant/components/rest/switch.py
homeassistant/components/ridwell/__init__.py homeassistant/components/ridwell/__init__.py
homeassistant/components/ridwell/sensor.py homeassistant/components/ridwell/sensor.py
homeassistant/components/ridwell/switch.py
homeassistant/components/ring/camera.py homeassistant/components/ring/camera.py
homeassistant/components/ripple/sensor.py homeassistant/components/ripple/sensor.py
homeassistant/components/rocketchat/notify.py homeassistant/components/rocketchat/notify.py
@ -907,6 +947,7 @@ omit =
homeassistant/components/sabnzbd/* homeassistant/components/sabnzbd/*
homeassistant/components/saj/sensor.py homeassistant/components/saj/sensor.py
homeassistant/components/samsungtv/bridge.py homeassistant/components/samsungtv/bridge.py
homeassistant/components/samsungtv/diagnostics.py
homeassistant/components/satel_integra/* homeassistant/components/satel_integra/*
homeassistant/components/schluter/* homeassistant/components/schluter/*
homeassistant/components/scrape/sensor.py homeassistant/components/scrape/sensor.py
@ -925,6 +966,14 @@ omit =
homeassistant/components/sense/sensor.py homeassistant/components/sense/sensor.py
homeassistant/components/sensehat/light.py homeassistant/components/sensehat/light.py
homeassistant/components/sensehat/sensor.py homeassistant/components/sensehat/sensor.py
homeassistant/components/senseme/__init__.py
homeassistant/components/senseme/binary_sensor.py
homeassistant/components/senseme/discovery.py
homeassistant/components/senseme/entity.py
homeassistant/components/senseme/fan.py
homeassistant/components/senseme/light.py
homeassistant/components/senseme/switch.py
homeassistant/components/sensibo/__init__.py
homeassistant/components/sensibo/climate.py homeassistant/components/sensibo/climate.py
homeassistant/components/serial/sensor.py homeassistant/components/serial/sensor.py
homeassistant/components/serial_pm/sensor.py homeassistant/components/serial_pm/sensor.py
@ -984,10 +1033,12 @@ omit =
homeassistant/components/solaredge/sensor.py homeassistant/components/solaredge/sensor.py
homeassistant/components/solaredge_local/sensor.py homeassistant/components/solaredge_local/sensor.py
homeassistant/components/solarlog/* homeassistant/components/solarlog/*
homeassistant/components/solax/__init__.py
homeassistant/components/solax/sensor.py homeassistant/components/solax/sensor.py
homeassistant/components/soma/__init__.py homeassistant/components/soma/__init__.py
homeassistant/components/soma/cover.py homeassistant/components/soma/cover.py
homeassistant/components/soma/sensor.py homeassistant/components/soma/sensor.py
homeassistant/components/soma/utils.py
homeassistant/components/somfy/__init__.py homeassistant/components/somfy/__init__.py
homeassistant/components/somfy/api.py homeassistant/components/somfy/api.py
homeassistant/components/somfy/climate.py homeassistant/components/somfy/climate.py
@ -996,7 +1047,17 @@ omit =
homeassistant/components/somfy/switch.py homeassistant/components/somfy/switch.py
homeassistant/components/somfy_mylink/__init__.py homeassistant/components/somfy_mylink/__init__.py
homeassistant/components/somfy_mylink/cover.py homeassistant/components/somfy_mylink/cover.py
homeassistant/components/sonos/* homeassistant/components/sonos/__init__.py
homeassistant/components/sonos/alarms.py
homeassistant/components/sonos/diagnostics.py
homeassistant/components/sonos/entity.py
homeassistant/components/sonos/favorites.py
homeassistant/components/sonos/helpers.py
homeassistant/components/sonos/household_coordinator.py
homeassistant/components/sonos/media_browser.py
homeassistant/components/sonos/media_player.py
homeassistant/components/sonos/speaker.py
homeassistant/components/sonos/switch.py
homeassistant/components/sony_projector/switch.py homeassistant/components/sony_projector/switch.py
homeassistant/components/spc/* homeassistant/components/spc/*
homeassistant/components/spider/* homeassistant/components/spider/*
@ -1013,6 +1074,7 @@ omit =
homeassistant/components/stiebel_eltron/* homeassistant/components/stiebel_eltron/*
homeassistant/components/stookalert/__init__.py homeassistant/components/stookalert/__init__.py
homeassistant/components/stookalert/binary_sensor.py homeassistant/components/stookalert/binary_sensor.py
homeassistant/components/stookalert/diagnostics.py
homeassistant/components/stream/* homeassistant/components/stream/*
homeassistant/components/streamlabswater/* homeassistant/components/streamlabswater/*
homeassistant/components/suez_water/* homeassistant/components/suez_water/*
@ -1041,8 +1103,12 @@ omit =
homeassistant/components/synology_chat/notify.py homeassistant/components/synology_chat/notify.py
homeassistant/components/synology_dsm/__init__.py homeassistant/components/synology_dsm/__init__.py
homeassistant/components/synology_dsm/binary_sensor.py homeassistant/components/synology_dsm/binary_sensor.py
homeassistant/components/synology_dsm/button.py
homeassistant/components/synology_dsm/camera.py homeassistant/components/synology_dsm/camera.py
homeassistant/components/synology_dsm/diagnostics.py
homeassistant/components/synology_dsm/common.py
homeassistant/components/synology_dsm/sensor.py homeassistant/components/synology_dsm/sensor.py
homeassistant/components/synology_dsm/service.py
homeassistant/components/synology_dsm/switch.py homeassistant/components/synology_dsm/switch.py
homeassistant/components/synology_srm/device_tracker.py homeassistant/components/synology_srm/device_tracker.py
homeassistant/components/syslog/notify.py homeassistant/components/syslog/notify.py
@ -1053,7 +1119,6 @@ omit =
homeassistant/components/system_bridge/sensor.py homeassistant/components/system_bridge/sensor.py
homeassistant/components/systemmonitor/sensor.py homeassistant/components/systemmonitor/sensor.py
homeassistant/components/tado/* homeassistant/components/tado/*
homeassistant/components/tahoma/*
homeassistant/components/tank_utility/sensor.py homeassistant/components/tank_utility/sensor.py
homeassistant/components/tankerkoenig/* homeassistant/components/tankerkoenig/*
homeassistant/components/tapsaff/binary_sensor.py homeassistant/components/tapsaff/binary_sensor.py
@ -1141,6 +1206,7 @@ omit =
homeassistant/components/transmission/errors.py homeassistant/components/transmission/errors.py
homeassistant/components/travisci/sensor.py homeassistant/components/travisci/sensor.py
homeassistant/components/tuya/__init__.py homeassistant/components/tuya/__init__.py
homeassistant/components/tuya/alarm_control_panel.py
homeassistant/components/tuya/base.py homeassistant/components/tuya/base.py
homeassistant/components/tuya/binary_sensor.py homeassistant/components/tuya/binary_sensor.py
homeassistant/components/tuya/button.py homeassistant/components/tuya/button.py
@ -1148,6 +1214,7 @@ omit =
homeassistant/components/tuya/climate.py homeassistant/components/tuya/climate.py
homeassistant/components/tuya/const.py homeassistant/components/tuya/const.py
homeassistant/components/tuya/cover.py homeassistant/components/tuya/cover.py
homeassistant/components/tuya/diagnostics.py
homeassistant/components/tuya/fan.py homeassistant/components/tuya/fan.py
homeassistant/components/tuya/humidifier.py homeassistant/components/tuya/humidifier.py
homeassistant/components/tuya/light.py homeassistant/components/tuya/light.py
@ -1175,7 +1242,11 @@ omit =
homeassistant/components/upnp/* homeassistant/components/upnp/*
homeassistant/components/upc_connect/* homeassistant/components/upc_connect/*
homeassistant/components/uscis/sensor.py homeassistant/components/uscis/sensor.py
homeassistant/components/vallox/* homeassistant/components/vallox/__init__.py
homeassistant/components/vallox/const.py
homeassistant/components/vallox/fan.py
homeassistant/components/vallox/sensor.py
homeassistant/components/vallox/binary_sensor.py
homeassistant/components/vasttrafik/sensor.py homeassistant/components/vasttrafik/sensor.py
homeassistant/components/velbus/__init__.py homeassistant/components/velbus/__init__.py
homeassistant/components/velbus/binary_sensor.py homeassistant/components/velbus/binary_sensor.py
@ -1195,6 +1266,7 @@ omit =
homeassistant/components/verisure/binary_sensor.py homeassistant/components/verisure/binary_sensor.py
homeassistant/components/verisure/camera.py homeassistant/components/verisure/camera.py
homeassistant/components/verisure/coordinator.py homeassistant/components/verisure/coordinator.py
homeassistant/components/verisure/diagnostics.py
homeassistant/components/verisure/lock.py homeassistant/components/verisure/lock.py
homeassistant/components/verisure/sensor.py homeassistant/components/verisure/sensor.py
homeassistant/components/verisure/switch.py homeassistant/components/verisure/switch.py
@ -1204,9 +1276,11 @@ omit =
homeassistant/components/vesync/const.py homeassistant/components/vesync/const.py
homeassistant/components/vesync/fan.py homeassistant/components/vesync/fan.py
homeassistant/components/vesync/light.py homeassistant/components/vesync/light.py
homeassistant/components/vesync/sensor.py
homeassistant/components/vesync/switch.py homeassistant/components/vesync/switch.py
homeassistant/components/viaggiatreno/sensor.py homeassistant/components/viaggiatreno/sensor.py
homeassistant/components/vicare/binary_sensor.py homeassistant/components/vicare/binary_sensor.py
homeassistant/components/vicare/button.py
homeassistant/components/vicare/climate.py homeassistant/components/vicare/climate.py
homeassistant/components/vicare/const.py homeassistant/components/vicare/const.py
homeassistant/components/vicare/__init__.py homeassistant/components/vicare/__init__.py
@ -1234,8 +1308,6 @@ omit =
homeassistant/components/waze_travel_time/__init__.py homeassistant/components/waze_travel_time/__init__.py
homeassistant/components/waze_travel_time/helpers.py homeassistant/components/waze_travel_time/helpers.py
homeassistant/components/waze_travel_time/sensor.py homeassistant/components/waze_travel_time/sensor.py
homeassistant/components/webostv/*
homeassistant/components/whois/sensor.py
homeassistant/components/wiffi/* homeassistant/components/wiffi/*
homeassistant/components/wirelesstag/* homeassistant/components/wirelesstag/*
homeassistant/components/wolflink/__init__.py homeassistant/components/wolflink/__init__.py
@ -1284,11 +1356,15 @@ omit =
homeassistant/components/xs1/* homeassistant/components/xs1/*
homeassistant/components/yale_smart_alarm/__init__.py homeassistant/components/yale_smart_alarm/__init__.py
homeassistant/components/yale_smart_alarm/alarm_control_panel.py homeassistant/components/yale_smart_alarm/alarm_control_panel.py
homeassistant/components/yale_smart_alarm/binary_sensor.py
homeassistant/components/yale_smart_alarm/const.py homeassistant/components/yale_smart_alarm/const.py
homeassistant/components/yale_smart_alarm/coordinator.py homeassistant/components/yale_smart_alarm/coordinator.py
homeassistant/components/yale_smart_alarm/entity.py
homeassistant/components/yale_smart_alarm/lock.py
homeassistant/components/yamaha_musiccast/__init__.py homeassistant/components/yamaha_musiccast/__init__.py
homeassistant/components/yamaha_musiccast/media_player.py homeassistant/components/yamaha_musiccast/media_player.py
homeassistant/components/yamaha_musiccast/number.py homeassistant/components/yamaha_musiccast/number.py
homeassistant/components/yamaha_musiccast/select.py
homeassistant/components/yandex_transport/* homeassistant/components/yandex_transport/*
homeassistant/components/yeelightsunflower/light.py homeassistant/components/yeelightsunflower/light.py
homeassistant/components/yi/camera.py homeassistant/components/yi/camera.py

View File

@ -107,7 +107,7 @@ To help with the load of incoming pull requests:
- [ ] I have reviewed two other [open pull requests][prs] in this repository. - [ ] I have reviewed two other [open pull requests][prs] in this repository.
[prs]: https://github.com/home-assistant/core/pulls?q=is%3Aopen+is%3Apr+-author%3A%40me+-draft%3Atrue+-label%3Awaiting-for-upstream+sort%3Acreated-desc+review%3Anone [prs]: https://github.com/home-assistant/core/pulls?q=is%3Aopen+is%3Apr+-author%3A%40me+-draft%3Atrue+-label%3Awaiting-for-upstream+sort%3Acreated-desc+review%3Anone+-status%3Afailure
<!-- <!--
Thank you for contributing <3 Thank you for contributing <3

View File

@ -10,11 +10,12 @@ on:
env: env:
BUILD_TYPE: core BUILD_TYPE: core
DEFAULT_PYTHON: 3.8 DEFAULT_PYTHON: 3.9
jobs: jobs:
init: init:
name: Initialize build name: Initialize build
if: github.repository_owner == 'home-assistant'
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
architectures: ${{ steps.info.outputs.architectures }} architectures: ${{ steps.info.outputs.architectures }}
@ -62,7 +63,7 @@ jobs:
name: Build PyPi package name: Build PyPi package
needs: init needs: init
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: needs.init.outputs.publish == 'true' if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.4.0 uses: actions/checkout@v2.4.0
@ -75,8 +76,10 @@ jobs:
- name: Build package - name: Build package
shell: bash shell: bash
run: | run: |
pip install twine wheel # Remove dist, build, and homeassistant.egg-info
python setup.py sdist bdist_wheel # when build locally for testing!
pip install twine build
python -m build
- name: Upload package - name: Upload package
shell: bash shell: bash
@ -88,6 +91,7 @@ jobs:
build_base: build_base:
name: Build ${{ matrix.arch }} base core image name: Build ${{ matrix.arch }} base core image
if: github.repository_owner == 'home-assistant'
needs: init needs: init
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@ -118,13 +122,13 @@ jobs:
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1.10.0 uses: docker/login-action@v1.12.0
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v1.10.0 uses: docker/login-action@v1.12.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@ -143,6 +147,7 @@ jobs:
build_machine: build_machine:
name: Build ${{ matrix.machine }} machine core image name: Build ${{ matrix.machine }} machine core image
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_base"] needs: ["init", "build_base"]
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@ -182,13 +187,13 @@ jobs:
fi fi
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1.10.0 uses: docker/login-action@v1.12.0
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v1.10.0 uses: docker/login-action@v1.12.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@ -206,6 +211,7 @@ jobs:
publish_ha: publish_ha:
name: Publish version files name: Publish version files
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_machine"] needs: ["init", "build_machine"]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -238,6 +244,7 @@ jobs:
publish_container: publish_container:
name: Publish meta container name: Publish meta container
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_base"] needs: ["init", "build_base"]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -245,13 +252,13 @@ jobs:
uses: actions/checkout@v2.4.0 uses: actions/checkout@v2.4.0
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1.10.0 uses: docker/login-action@v1.12.0
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v1.10.0 uses: docker/login-action@v1.12.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}

View File

@ -10,10 +10,13 @@ on:
pull_request: ~ pull_request: ~
env: env:
CACHE_VERSION: 3 CACHE_VERSION: 5
DEFAULT_PYTHON: 3.8 PIP_CACHE_VERSION: 1
DEFAULT_PYTHON: 3.9
PRE_COMMIT_CACHE: ~/.cache/pre-commit PRE_COMMIT_CACHE: ~/.cache/pre-commit
PIP_CACHE: /tmp/pip-cache
SQLALCHEMY_WARN_20: 1 SQLALCHEMY_WARN_20: 1
PYTHONASYNCIODEBUG: 1
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@ -33,6 +36,7 @@ jobs:
tests_glob: ${{ steps.info.outputs.tests_glob }} tests_glob: ${{ steps.info.outputs.tests_glob }}
test_groups: ${{ steps.info.outputs.test_groups }} test_groups: ${{ steps.info.outputs.test_groups }}
test_group_count: ${{ steps.info.outputs.test_group_count }} test_group_count: ${{ steps.info.outputs.test_group_count }}
requirements: ${{ steps.core.outputs.requirements }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
@ -129,6 +133,7 @@ jobs:
prepare-base: prepare-base:
name: Prepare base dependencies name: Prepare base dependencies
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 20
outputs: outputs:
python-key: ${{ steps.generate-python-key.outputs.key }} python-key: ${{ steps.generate-python-key.outputs.key }}
pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }} pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }}
@ -147,6 +152,11 @@ jobs:
hashFiles('requirements.txt') }}-${{ hashFiles('requirements.txt') }}-${{
hashFiles('requirements_test.txt') }}-${{ hashFiles('requirements_test.txt') }}-${{
hashFiles('homeassistant/package_constraints.txt') }}" hashFiles('homeassistant/package_constraints.txt') }}"
- name: Generate partial pip restore key
id: generate-pip-key
run: >-
echo "::set-output name=key::base-pip-${{ env.PIP_CACHE_VERSION }}-$(
date -u '+%Y-%m-%dT%H:%M:%s')"
- name: Restore base Python virtual environment - name: Restore base Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.7 uses: actions/cache@v2.1.7
@ -164,13 +174,24 @@ jobs:
# ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements_test.txt') }}- # ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements_test.txt') }}-
# ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}- # ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}-
# ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}- # ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-
- name: Restore pip wheel cache
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: actions/cache@v2.1.7
with:
path: ${{ env.PIP_CACHE }}
key: >-
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
steps.generate-pip-key.outputs.key }}
restore-keys: |
${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-pip-${{ env.PIP_CACHE_VERSION }}-
- name: Create Python virtual environment - name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
python -m venv venv python -m venv venv
. venv/bin/activate . venv/bin/activate
pip install -U "pip<20.3" setuptools python --version
pip install -r requirements.txt -r requirements_test.txt pip install --cache-dir=$PIP_CACHE -U "pip<20.3" setuptools wheel
pip install --cache-dir=$PIP_CACHE -r requirements.txt -r requirements_test.txt
- name: Generate partial pre-commit restore key - name: Generate partial pre-commit restore key
id: generate-pre-commit-key id: generate-pre-commit-key
run: >- run: >-
@ -238,7 +259,8 @@ jobs:
shell: bash shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual black --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/* --show-diff-on-failure shopt -s globstar
pre-commit run --hook-stage manual black --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* --show-diff-on-failure
lint-flake8: lint-flake8:
name: Check flake8 name: Check flake8
@ -290,7 +312,8 @@ jobs:
shell: bash shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual flake8 --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/* shopt -s globstar
pre-commit run --hook-stage manual flake8 --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/*
lint-isort: lint-isort:
name: Check isort name: Check isort
@ -380,7 +403,8 @@ jobs:
shell: bash shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual pyupgrade --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/* --show-diff-on-failure shopt -s globstar
pre-commit run --hook-stage manual pyupgrade --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* --show-diff-on-failure
- name: Register yamllint problem matcher - name: Register yamllint problem matcher
run: | run: |
@ -436,7 +460,8 @@ jobs:
shell: bash shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
pre-commit run --hook-stage manual bandit --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/* --show-diff-on-failure shopt -s globstar
pre-commit run --hook-stage manual bandit --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* --show-diff-on-failure
hassfest: hassfest:
name: Check hassfest name: Check hassfest
@ -444,7 +469,7 @@ jobs:
needs: prepare-tests needs: prepare-tests
strategy: strategy:
matrix: matrix:
python-version: [3.8] python-version: [3.9]
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
@ -498,9 +523,10 @@ jobs:
prepare-tests: prepare-tests:
name: Prepare tests for Python ${{ matrix.python-version }} name: Prepare tests for Python ${{ matrix.python-version }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 30
strategy: strategy:
matrix: matrix:
python-version: [3.8, 3.9] python-version: [3.9]
outputs: outputs:
python-key: ${{ steps.generate-python-key.outputs.key }} python-key: ${{ steps.generate-python-key.outputs.key }}
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
@ -514,6 +540,11 @@ jobs:
hashFiles('requirements_test.txt') }}-${{ hashFiles('requirements_test.txt') }}-${{
hashFiles('requirements_all.txt') }}-${{ hashFiles('requirements_all.txt') }}-${{
hashFiles('homeassistant/package_constraints.txt') }}" hashFiles('homeassistant/package_constraints.txt') }}"
- name: Generate partial pip restore key
id: generate-pip-key
run: >-
echo "::set-output name=key::pip-${{ env.PIP_CACHE_VERSION }}-$(
date -u '+%Y-%m-%dT%H:%M:%s')"
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.7 uses: actions/cache@v2.1.7
@ -531,6 +562,16 @@ jobs:
# ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements_test.txt') }}-${{ hashFiles('requirements_all.txt') }}- # ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements_test.txt') }}-${{ hashFiles('requirements_all.txt') }}-
# ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements_test.txt') }}- # ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements_test.txt') }}-
# ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}- # ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-
- name: Restore pip wheel cache
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: actions/cache@v2.1.7
with:
path: ${{ env.PIP_CACHE }}
key: >-
${{ runner.os }}-${{ matrix.python-version }}-${{
steps.generate-pip-key.outputs.key }}
restore-keys: |
${{ runner.os }}-${{ matrix.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}-
- name: Create full Python ${{ matrix.python-version }} virtual environment - name: Create full Python ${{ matrix.python-version }} virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true' if: steps.cache-venv.outputs.cache-hit != 'true'
run: | run: |
@ -540,20 +581,22 @@ jobs:
python -m venv venv python -m venv venv
. venv/bin/activate . venv/bin/activate
pip install -U "pip<20.3" setuptools wheel python --version
pip install -r requirements_all.txt pip install --cache-dir=$PIP_CACHE -U "pip<20.3" setuptools wheel
pip install -r requirements_test.txt pip install --cache-dir=$PIP_CACHE -r requirements_all.txt
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt
pip install -e . pip install -e .
pylint: pylint:
name: Check pylint name: Check pylint
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 20
needs: needs:
- changes - changes
- prepare-tests - prepare-tests
strategy: strategy:
matrix: matrix:
python-version: [3.8] python-version: [3.9]
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
@ -577,12 +620,14 @@ jobs:
if: needs.changes.outputs.test_full_suite == 'true' if: needs.changes.outputs.test_full_suite == 'true'
run: | run: |
. venv/bin/activate . venv/bin/activate
python --version
pylint homeassistant pylint homeassistant
- name: Run pylint (partially) - name: Run pylint (partially)
if: needs.changes.outputs.test_full_suite == 'false' if: needs.changes.outputs.test_full_suite == 'false'
shell: bash shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
python --version
pylint homeassistant/components/${{ needs.changes.outputs.integrations_glob }} pylint homeassistant/components/${{ needs.changes.outputs.integrations_glob }}
mypy: mypy:
@ -593,7 +638,7 @@ jobs:
- prepare-tests - prepare-tests
strategy: strategy:
matrix: matrix:
python-version: [3.8] python-version: [3.9]
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
@ -617,14 +662,48 @@ jobs:
if: needs.changes.outputs.test_full_suite == 'true' if: needs.changes.outputs.test_full_suite == 'true'
run: | run: |
. venv/bin/activate . venv/bin/activate
python --version
mypy homeassistant mypy homeassistant
- name: Run mypy (partially) - name: Run mypy (partially)
if: needs.changes.outputs.test_full_suite == 'false' if: needs.changes.outputs.test_full_suite == 'false'
shell: bash shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
python --version
mypy homeassistant/components/${{ needs.changes.outputs.integrations_glob }} mypy homeassistant/components/${{ needs.changes.outputs.integrations_glob }}
pip-check:
runs-on: ubuntu-latest
if: needs.changes.outputs.requirements == 'true'
needs:
- changes
- prepare-tests
strategy:
fail-fast: false
matrix:
python-version: [3.9]
name: Run pip check ${{ matrix.python-version }}
container: homeassistant/ci-azure:${{ matrix.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v2.4.0
- name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv
uses: actions/cache@v2.1.7
with:
path: venv
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
needs.prepare-tests.outputs.python-key }}
- name: Fail job if Python cache restore failed
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
echo "Failed to restore Python virtual environment from cache"
exit 1
- name: Run pip check
run: |
. venv/bin/activate
./script/pip_check $PIP_CACHE
pytest: pytest:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: needs.changes.outputs.test_full_suite == 'true' || needs.changes.outputs.tests_glob if: needs.changes.outputs.test_full_suite == 'true' || needs.changes.outputs.tests_glob
@ -641,7 +720,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
group: ${{ fromJson(needs.changes.outputs.test_groups) }} group: ${{ fromJson(needs.changes.outputs.test_groups) }}
python-version: [3.8, 3.9] python-version: [3.9]
name: >- name: >-
Run tests Python ${{ matrix.python-version }} (${{ matrix.group }}) Run tests Python ${{ matrix.python-version }} (${{ matrix.group }})
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
@ -675,8 +754,10 @@ jobs:
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
- name: Run pytest (fully) - name: Run pytest (fully)
if: needs.changes.outputs.test_full_suite == 'true' if: needs.changes.outputs.test_full_suite == 'true'
timeout-minutes: 45
run: | run: |
. venv/bin/activate . venv/bin/activate
python --version
python3 -X dev -m pytest \ python3 -X dev -m pytest \
-qq \ -qq \
--timeout=9 \ --timeout=9 \
@ -691,9 +772,18 @@ jobs:
-p no:sugar \ -p no:sugar \
tests tests
- name: Run pytest (partially) - name: Run pytest (partially)
if: needs.changes.outputs.test_full_suite == 'false' && matrix.python-version != '3.8' if: needs.changes.outputs.test_full_suite == 'false'
timeout-minutes: 10
shell: bash
run: | run: |
. venv/bin/activate . venv/bin/activate
python --version
if [[ ! -f "tests/components/${{ matrix.group }}/__init__.py" ]]; then
echo "::error:: missing file tests/components/${{ matrix.group }}/__init__.py"
exit 1
fi
python3 -X dev -m pytest \ python3 -X dev -m pytest \
-qq \ -qq \
--timeout=9 \ --timeout=9 \
@ -708,9 +798,11 @@ jobs:
-p no:sugar \ -p no:sugar \
tests/components/${{ matrix.group }} tests/components/${{ matrix.group }}
- name: Run pytest (partially); no coverage - name: Run pytest (partially); no coverage
if: needs.changes.outputs.test_full_suite == 'false' && matrix.python-version == '3.8' if: needs.changes.outputs.test_full_suite == 'false'
timeout-minutes: 10
run: | run: |
. venv/bin/activate . venv/bin/activate
python --version
python3 -X dev -m pytest \ python3 -X dev -m pytest \
-qq \ -qq \
--timeout=9 \ --timeout=9 \
@ -722,7 +814,7 @@ jobs:
-p no:sugar \ -p no:sugar \
tests/components/${{ matrix.group }} tests/components/${{ matrix.group }}
- name: Upload coverage artifact - name: Upload coverage artifact
uses: actions/upload-artifact@v2.2.4 uses: actions/upload-artifact@v2.3.1
with: with:
name: coverage-${{ matrix.python-version }}-${{ matrix.group }} name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
path: coverage.xml path: coverage.xml

View File

@ -7,6 +7,7 @@ on:
jobs: jobs:
lock: lock:
if: github.repository_owner == 'home-assistant'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v3 - uses: dessant/lock-threads@v3

View File

@ -12,11 +12,12 @@ on:
- "**strings.json" - "**strings.json"
env: env:
DEFAULT_PYTHON: 3.8 DEFAULT_PYTHON: 3.9
jobs: jobs:
upload: upload:
name: Upload name: Upload
if: github.repository_owner == 'home-assistant'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
@ -35,7 +36,7 @@ jobs:
download: download:
name: Download name: Download
needs: upload needs: upload
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' if: github.repository_owner == 'home-assistant' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository

View File

@ -16,6 +16,7 @@ on:
jobs: jobs:
init: init:
name: Initialize wheels builder name: Initialize wheels builder
if: github.repository_owner == 'home-assistant'
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
architectures: ${{ steps.info.outputs.architectures }} architectures: ${{ steps.info.outputs.architectures }}
@ -42,22 +43,27 @@ jobs:
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true" echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true" echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true" echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
# GRPC on armv7 needs -lexecinfo (issue #56669) since home assistant installs
# execinfo-dev when building wheels. The setup.py does not have an option for
# adding a single LDFLAG so copy all relevant linux flags here (as of 1.43.0)
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc -lexecinfo"
) > .env_file ) > .env_file
- name: Upload env_file - name: Upload env_file
uses: actions/upload-artifact@v2.2.4 uses: actions/upload-artifact@v2.3.1
with: with:
name: env_file name: env_file
path: ./.env_file path: ./.env_file
- name: Upload requirements_diff - name: Upload requirements_diff
uses: actions/upload-artifact@v2.2.4 uses: actions/upload-artifact@v2.3.1
with: with:
name: requirements_diff name: requirements_diff
path: ./requirements_diff.txt path: ./requirements_diff.txt
core: core:
name: Build wheels with ${{ matrix.tag }} (${{ matrix.arch }}) for core name: Build wheels with ${{ matrix.tag }} (${{ matrix.arch }}) for core
if: github.repository_owner == 'home-assistant'
needs: init needs: init
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@ -81,7 +87,7 @@ jobs:
name: requirements_diff name: requirements_diff
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@2021.07.0 uses: home-assistant/wheels@2022.01.2
with: with:
tag: ${{ matrix.tag }} tag: ${{ matrix.tag }}
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
@ -98,6 +104,7 @@ jobs:
integrations: integrations:
name: Build wheels with ${{ matrix.tag }} (${{ matrix.arch }}) for integrations name: Build wheels with ${{ matrix.tag }} (${{ matrix.arch }}) for integrations
if: github.repository_owner == 'home-assistant'
needs: init needs: init
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@ -150,7 +157,7 @@ jobs:
done done
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@2021.07.0 uses: home-assistant/wheels@2022.01.2
with: with:
tag: ${{ matrix.tag }} tag: ${{ matrix.tag }}
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
@ -160,7 +167,7 @@ jobs:
env-file: true env-file: true
apk: "build-base;cmake;git;linux-headers;libexecinfo-dev;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;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;cargo" apk: "build-base;cmake;git;linux-headers;libexecinfo-dev;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;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;cargo"
pip: "Cython;numpy;scikit-build" pip: "Cython;numpy;scikit-build"
skip-binary: aiohttp skip-binary: aiohttp,grpcio
constraints: "homeassistant/package_constraints.txt" constraints: "homeassistant/package_constraints.txt"
requirements-diff: 'requirements_diff.txt' requirements-diff: 'requirements_diff.txt'
requirements: "requirements_all.txt" requirements: "requirements_all.txt"

View File

@ -1,11 +1,11 @@
repos: repos:
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v2.29.0 rev: v2.31.0
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py38-plus] args: [--py39-plus]
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 21.11b1 rev: 21.12b0
hooks: hooks:
- id: black - id: black
args: args:
@ -13,11 +13,11 @@ repos:
- --quiet - --quiet
files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$ files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/codespell-project/codespell
rev: v2.0.0 rev: v2.1.0
hooks: hooks:
- id: codespell - id: codespell
args: args:
- --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba,haa - --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,iif,ines,ist,lightsensor,mut,nd,pres,referer,rime,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba,haa
- --skip="./.*,*.csv,*.json" - --skip="./.*,*.csv,*.json"
- --quiet-level=2 - --quiet-level=2
exclude_types: [csv, json] exclude_types: [csv, json]
@ -32,7 +32,7 @@ repos:
- flake8-docstrings==1.6.0 - flake8-docstrings==1.6.0
- pydocstyle==6.1.1 - pydocstyle==6.1.1
- flake8-comprehensions==3.7.0 - flake8-comprehensions==3.7.0
- flake8-noqa==1.2.0 - flake8-noqa==1.2.1
- mccabe==0.6.1 - mccabe==0.6.1
files: ^(homeassistant|script|tests)/.+\.py$ files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit - repo: https://github.com/PyCQA/bandit
@ -95,17 +95,30 @@ repos:
types: [python] types: [python]
require_serial: true require_serial: true
files: ^homeassistant/.+\.py$ files: ^homeassistant/.+\.py$
- id: pylint
name: pylint
entry: script/run-in-env.sh pylint -j 0
language: script
types: [python]
files: ^homeassistant/.+\.py$
- id: gen_requirements_all - id: gen_requirements_all
name: gen_requirements_all name: gen_requirements_all
entry: script/run-in-env.sh python3 -m script.gen_requirements_all entry: script/run-in-env.sh python3 -m script.gen_requirements_all
pass_filenames: false pass_filenames: false
language: script language: script
types: [text] types: [text]
files: ^(homeassistant/.+/manifest\.json|\.pre-commit-config\.yaml)$ files: ^(homeassistant/.+/manifest\.json|setup\.cfg|\.pre-commit-config\.yaml|script/gen_requirements_all\.py)$
- id: hassfest - id: hassfest
name: hassfest name: hassfest
entry: script/run-in-env.sh python3 -m script.hassfest entry: script/run-in-env.sh python3 -m script.hassfest
pass_filenames: false pass_filenames: false
language: script language: script
types: [text] types: [text]
files: ^(homeassistant/.+/(manifest|strings)\.json|\.coveragerc|homeassistant/.+/services\.yaml)$ files: ^(homeassistant/.+/(manifest|strings)\.json|\.coveragerc|\.strict-typing|homeassistant/.+/services\.yaml|script/hassfest/.+\.py)$
- id: hassfest-metadata
name: hassfest-metadata
entry: script/run-in-env.sh python3 -m script.hassfest -p metadata
pass_filenames: false
language: script
types: [text]
files: ^(script/hassfest/.+\.py|homeassistant/const\.py$|setup\.cfg)$

View File

@ -2,7 +2,31 @@
# If component is fully covered with type annotations, please add it here # If component is fully covered with type annotations, please add it here
# to enable strict mypy checks. # to enable strict mypy checks.
# Stict typing is enabled by default for core files.
# Add it here to add 'disallow_any_generics'.
# --- Only for core file! ---
homeassistant.exceptions
homeassistant.core
homeassistant.loader
homeassistant.requirements
homeassistant.runner
homeassistant.setup
homeassistant.auth.auth_store
homeassistant.auth.providers.*
homeassistant.helpers.area_registry
homeassistant.helpers.condition
homeassistant.helpers.discovery
homeassistant.helpers.entity_values
homeassistant.helpers.reload
homeassistant.helpers.script_variables
homeassistant.helpers.translation
homeassistant.util.color
homeassistant.util.process
homeassistant.util.unit_system
# --- Add components below this line ---
homeassistant.components homeassistant.components
homeassistant.components.abode.*
homeassistant.components.acer_projector.* homeassistant.components.acer_projector.*
homeassistant.components.accuweather.* homeassistant.components.accuweather.*
homeassistant.components.actiontec.* homeassistant.components.actiontec.*
@ -17,6 +41,7 @@ homeassistant.components.ambee.*
homeassistant.components.ambient_station.* homeassistant.components.ambient_station.*
homeassistant.components.amcrest.* homeassistant.components.amcrest.*
homeassistant.components.ampio.* homeassistant.components.ampio.*
homeassistant.components.aseko_pool_live.*
homeassistant.components.automation.* homeassistant.components.automation.*
homeassistant.components.binary_sensor.* homeassistant.components.binary_sensor.*
homeassistant.components.bluetooth_tracker.* homeassistant.components.bluetooth_tracker.*
@ -24,12 +49,14 @@ homeassistant.components.bmw_connected_drive.*
homeassistant.components.bond.* homeassistant.components.bond.*
homeassistant.components.braviatv.* homeassistant.components.braviatv.*
homeassistant.components.brother.* homeassistant.components.brother.*
homeassistant.components.browser.*
homeassistant.components.button.* homeassistant.components.button.*
homeassistant.components.calendar.* homeassistant.components.calendar.*
homeassistant.components.camera.* homeassistant.components.camera.*
homeassistant.components.canary.* homeassistant.components.canary.*
homeassistant.components.cover.* homeassistant.components.cover.*
homeassistant.components.crownstone.* homeassistant.components.crownstone.*
homeassistant.components.cpuspeed.*
homeassistant.components.device_automation.* homeassistant.components.device_automation.*
homeassistant.components.device_tracker.* homeassistant.components.device_tracker.*
homeassistant.components.devolo_home_control.* homeassistant.components.devolo_home_control.*
@ -60,10 +87,12 @@ homeassistant.components.group.*
homeassistant.components.guardian.* homeassistant.components.guardian.*
homeassistant.components.history.* homeassistant.components.history.*
homeassistant.components.homeassistant.triggers.event homeassistant.components.homeassistant.triggers.event
homeassistant.components.homewizard.*
homeassistant.components.http.* homeassistant.components.http.*
homeassistant.components.huawei_lte.* homeassistant.components.huawei_lte.*
homeassistant.components.hyperion.* homeassistant.components.hyperion.*
homeassistant.components.image_processing.* homeassistant.components.image_processing.*
homeassistant.components.input_button.*
homeassistant.components.input_select.* homeassistant.components.input_select.*
homeassistant.components.integration.* homeassistant.components.integration.*
homeassistant.components.iqvia.* homeassistant.components.iqvia.*
@ -71,11 +100,13 @@ homeassistant.components.jellyfin.*
homeassistant.components.jewish_calendar.* homeassistant.components.jewish_calendar.*
homeassistant.components.knx.* homeassistant.components.knx.*
homeassistant.components.kraken.* homeassistant.components.kraken.*
homeassistant.components.lametric.*
homeassistant.components.lcn.* homeassistant.components.lcn.*
homeassistant.components.light.* homeassistant.components.light.*
homeassistant.components.local_ip.* homeassistant.components.local_ip.*
homeassistant.components.lock.* homeassistant.components.lock.*
homeassistant.components.lookin.* homeassistant.components.lookin.*
homeassistant.components.luftdaten.*
homeassistant.components.mailbox.* homeassistant.components.mailbox.*
homeassistant.components.media_player.* homeassistant.components.media_player.*
homeassistant.components.modbus.* homeassistant.components.modbus.*
@ -89,15 +120,20 @@ homeassistant.components.nest.*
homeassistant.components.netatmo.* homeassistant.components.netatmo.*
homeassistant.components.network.* homeassistant.components.network.*
homeassistant.components.nfandroidtv.* homeassistant.components.nfandroidtv.*
homeassistant.components.nissan_leaf.*
homeassistant.components.no_ip.* homeassistant.components.no_ip.*
homeassistant.components.notify.* homeassistant.components.notify.*
homeassistant.components.notion.* homeassistant.components.notion.*
homeassistant.components.number.* homeassistant.components.number.*
homeassistant.components.oncue.*
homeassistant.components.onewire.* homeassistant.components.onewire.*
homeassistant.components.open_meteo.*
homeassistant.components.openuv.* homeassistant.components.openuv.*
homeassistant.components.overkiz.*
homeassistant.components.persistent_notification.* homeassistant.components.persistent_notification.*
homeassistant.components.pi_hole.* homeassistant.components.pi_hole.*
homeassistant.components.proximity.* homeassistant.components.proximity.*
homeassistant.components.pvoutput.*
homeassistant.components.rainmachine.* homeassistant.components.rainmachine.*
homeassistant.components.rdw.* homeassistant.components.rdw.*
homeassistant.components.recollect_waste.* homeassistant.components.recollect_waste.*
@ -109,16 +145,21 @@ homeassistant.components.renault.*
homeassistant.components.ridwell.* homeassistant.components.ridwell.*
homeassistant.components.rituals_perfume_genie.* homeassistant.components.rituals_perfume_genie.*
homeassistant.components.rpi_power.* homeassistant.components.rpi_power.*
homeassistant.components.rtsp_to_webrtc.*
homeassistant.components.samsungtv.* homeassistant.components.samsungtv.*
homeassistant.components.scene.* homeassistant.components.scene.*
homeassistant.components.select.* homeassistant.components.select.*
homeassistant.components.sensor.* homeassistant.components.sensor.*
homeassistant.components.senseme.*
homeassistant.components.shelly.* homeassistant.components.shelly.*
homeassistant.components.simplisafe.* homeassistant.components.simplisafe.*
homeassistant.components.slack.* homeassistant.components.slack.*
homeassistant.components.smhi.*
homeassistant.components.sonos.media_player homeassistant.components.sonos.media_player
homeassistant.components.ssdp.* homeassistant.components.ssdp.*
homeassistant.components.stookalert.* homeassistant.components.stookalert.*
homeassistant.components.statistics.*
homeassistant.components.steamist.*
homeassistant.components.stream.* homeassistant.components.stream.*
homeassistant.components.sun.* homeassistant.components.sun.*
homeassistant.components.surepetcare.* homeassistant.components.surepetcare.*
@ -135,8 +176,11 @@ homeassistant.components.tplink.*
homeassistant.components.tolo.* homeassistant.components.tolo.*
homeassistant.components.tractive.* homeassistant.components.tractive.*
homeassistant.components.tradfri.* homeassistant.components.tradfri.*
homeassistant.components.trafikverket_train.*
homeassistant.components.trafikverket_weatherstation.*
homeassistant.components.tts.* homeassistant.components.tts.*
homeassistant.components.twentemilieu.* homeassistant.components.twentemilieu.*
homeassistant.components.unifiprotect.*
homeassistant.components.upcloud.* homeassistant.components.upcloud.*
homeassistant.components.uptime.* homeassistant.components.uptime.*
homeassistant.components.uptimerobot.* homeassistant.components.uptimerobot.*
@ -148,7 +192,10 @@ homeassistant.components.wallbox.*
homeassistant.components.water_heater.* homeassistant.components.water_heater.*
homeassistant.components.watttime.* homeassistant.components.watttime.*
homeassistant.components.weather.* homeassistant.components.weather.*
homeassistant.components.webostv.*
homeassistant.components.websocket_api.* homeassistant.components.websocket_api.*
homeassistant.components.wemo.*
homeassistant.components.whois.*
homeassistant.components.zodiac.* homeassistant.components.zodiac.*
homeassistant.components.zeroconf.* homeassistant.components.zeroconf.*
homeassistant.components.zone.* homeassistant.components.zone.*

25
.vscode/tasks.json vendored
View File

@ -16,9 +16,7 @@
"label": "Pytest", "label": "Pytest",
"type": "shell", "type": "shell",
"command": "pytest --timeout=10 tests", "command": "pytest --timeout=10 tests",
"dependsOn": [ "dependsOn": ["Install all Test Requirements"],
"Install all Test Requirements"
],
"group": { "group": {
"kind": "test", "kind": "test",
"isDefault": true "isDefault": true
@ -47,9 +45,7 @@
"label": "Pylint", "label": "Pylint",
"type": "shell", "type": "shell",
"command": "pylint homeassistant", "command": "pylint homeassistant",
"dependsOn": [ "dependsOn": ["Install all Requirements"],
"Install all Requirements"
],
"group": { "group": {
"kind": "test", "kind": "test",
"isDefault": true "isDefault": true
@ -64,7 +60,7 @@
"label": "Code Coverage", "label": "Code Coverage",
"detail": "Generate code coverage report for a given integration.", "detail": "Generate code coverage report for a given integration.",
"type": "shell", "type": "shell",
"command": "pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing --durations-min=1 --durations=0", "command": "pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing --durations-min=1 --durations=0 --numprocesses=auto",
"group": { "group": {
"kind": "test", "kind": "test",
"isDefault": true "isDefault": true
@ -116,6 +112,21 @@
"panel": "new" "panel": "new"
}, },
"problemMatcher": [] "problemMatcher": []
},
{
"label": "Compile translations",
"detail": "In order to test changes to translation files, the translation strings must be compiled into Home Assistant's translation directories.",
"type": "shell",
"command": "python3 -m script.translations develop --integration ${input:integrationName}",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
} }
], ],
"inputs": [ "inputs": [

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@ RUN \
libswresample-dev \ libswresample-dev \
libavfilter-dev \ libavfilter-dev \
libpcap-dev \ libpcap-dev \
libturbojpeg0 \
git \ git \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*

View File

@ -1,4 +1,3 @@
include README.rst include README.rst
include LICENSE.md
graft homeassistant graft homeassistant
recursive-exclude * *.py[co] recursive-exclude * *.py[co]

View File

@ -8,7 +8,7 @@ coverage:
threshold: 0.09 threshold: 0.09
config-flows: config-flows:
target: auto target: auto
threshold: 0 threshold: 1
paths: paths:
- homeassistant/components/*/config_flow.py - homeassistant/components/*/config_flow.py
patch: patch:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 151 KiB

View File

@ -4,16 +4,21 @@ from __future__ import annotations
import argparse import argparse
import faulthandler import faulthandler
import os import os
import platform
import subprocess
import sys import sys
import threading import threading
from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__ from .const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__
FAULT_LOG_FILENAME = "home-assistant.log.fault" FAULT_LOG_FILENAME = "home-assistant.log.fault"
def validate_os() -> None:
"""Validate that Home Assistant is running in a supported operating system."""
if not sys.platform.startswith(("darwin", "linux")):
print("Home Assistant only supports Linux, OSX and Windows using WSL")
sys.exit(1)
def validate_python() -> None: def validate_python() -> None:
"""Validate that the right Python version is running.""" """Validate that the right Python version is running."""
if sys.version_info[:3] < REQUIRED_PYTHON_VER: if sys.version_info[:3] < REQUIRED_PYTHON_VER:
@ -27,7 +32,7 @@ def validate_python() -> None:
def ensure_config_path(config_dir: str) -> None: def ensure_config_path(config_dir: str) -> None:
"""Validate the configuration directory.""" """Validate the configuration directory."""
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
import homeassistant.config as config_util from . import config as config_util
lib_dir = os.path.join(config_dir, "deps") lib_dir = os.path.join(config_dir, "deps")
@ -61,10 +66,11 @@ def ensure_config_path(config_dir: str) -> None:
def get_arguments() -> argparse.Namespace: def get_arguments() -> argparse.Namespace:
"""Get parsed passed in arguments.""" """Get parsed passed in arguments."""
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
import homeassistant.config as config_util from . import config as config_util
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Home Assistant: Observe, Control, Automate." description="Home Assistant: Observe, Control, Automate.",
epilog=f"If restart is requested, exits with code {RESTART_EXIT_CODE}",
) )
parser.add_argument("--version", action="version", version=__version__) parser.add_argument("--version", action="version", version=__version__)
parser.add_argument( parser.add_argument(
@ -91,12 +97,6 @@ def get_arguments() -> argparse.Namespace:
parser.add_argument( parser.add_argument(
"-v", "--verbose", action="store_true", help="Enable verbose logging to file." "-v", "--verbose", action="store_true", help="Enable verbose logging to file."
) )
parser.add_argument(
"--pid-file",
metavar="path_to_pid_file",
default=None,
help="Path to PID file useful for running as daemon",
)
parser.add_argument( parser.add_argument(
"--log-rotate-days", "--log-rotate-days",
type=int, type=int,
@ -112,122 +112,32 @@ def get_arguments() -> argparse.Namespace:
parser.add_argument( parser.add_argument(
"--log-no-color", action="store_true", help="Disable color logs" "--log-no-color", action="store_true", help="Disable color logs"
) )
parser.add_argument(
"--runner",
action="store_true",
help=f"On restart exit with code {RESTART_EXIT_CODE}",
)
parser.add_argument( parser.add_argument(
"--script", nargs=argparse.REMAINDER, help="Run one of the embedded scripts" "--script", nargs=argparse.REMAINDER, help="Run one of the embedded scripts"
) )
if os.name == "posix": parser.add_argument(
parser.add_argument( "--ignore-os-check",
"--daemon", action="store_true", help="Run Home Assistant as daemon" action="store_true",
) help="Skips validation of operating system",
)
arguments = parser.parse_args() arguments = parser.parse_args()
if os.name != "posix" or arguments.debug or arguments.runner:
setattr(arguments, "daemon", False)
return arguments return arguments
def daemonize() -> None:
"""Move current process to daemon process."""
# Create first fork
if os.fork() > 0:
sys.exit(0)
# Decouple fork
os.setsid()
# Create second fork
if os.fork() > 0:
sys.exit(0)
# redirect standard file descriptors to devnull
# pylint: disable=consider-using-with
infd = open(os.devnull, encoding="utf8")
outfd = open(os.devnull, "a+", encoding="utf8")
sys.stdout.flush()
sys.stderr.flush()
os.dup2(infd.fileno(), sys.stdin.fileno())
os.dup2(outfd.fileno(), sys.stdout.fileno())
os.dup2(outfd.fileno(), sys.stderr.fileno())
def check_pid(pid_file: str) -> None:
"""Check that Home Assistant is not already running."""
# Check pid file
try:
with open(pid_file, encoding="utf8") as file:
pid = int(file.readline())
except OSError:
# PID File does not exist
return
# If we just restarted, we just found our own pidfile.
if pid == os.getpid():
return
try:
os.kill(pid, 0)
except OSError:
# PID does not exist
return
print("Fatal Error: Home Assistant is already running.")
sys.exit(1)
def write_pid(pid_file: str) -> None:
"""Create a PID File."""
pid = os.getpid()
try:
with open(pid_file, "w", encoding="utf8") as file:
file.write(str(pid))
except OSError:
print(f"Fatal Error: Unable to write pid file {pid_file}")
sys.exit(1)
def closefds_osx(min_fd: int, max_fd: int) -> None:
"""Make sure file descriptors get closed when we restart.
We cannot call close on guarded fds, and we cannot easily test which fds
are guarded. But we can set the close-on-exec flag on everything we want to
get rid of.
"""
# pylint: disable=import-outside-toplevel
from fcntl import F_GETFD, F_SETFD, FD_CLOEXEC, fcntl
for _fd in range(min_fd, max_fd):
try:
val = fcntl(_fd, F_GETFD)
if not val & FD_CLOEXEC:
fcntl(_fd, F_SETFD, val | FD_CLOEXEC)
except OSError:
pass
def cmdline() -> list[str]: def cmdline() -> list[str]:
"""Collect path and arguments to re-execute the current hass instance.""" """Collect path and arguments to re-execute the current hass instance."""
if os.path.basename(sys.argv[0]) == "__main__.py": if os.path.basename(sys.argv[0]) == "__main__.py":
modulepath = os.path.dirname(sys.argv[0]) modulepath = os.path.dirname(sys.argv[0])
os.environ["PYTHONPATH"] = os.path.dirname(modulepath) os.environ["PYTHONPATH"] = os.path.dirname(modulepath)
return [sys.executable] + [arg for arg in sys.argv if arg != "--daemon"] return [sys.executable, "-m", "homeassistant"] + list(sys.argv[1:])
return [arg for arg in sys.argv if arg != "--daemon"] return sys.argv
def try_to_restart() -> None: def check_threads() -> None:
"""Attempt to clean up state and start a new Home Assistant instance.""" """Check if there are any lingering threads."""
# Things should be mostly shut down already at this point, now just try
# to clean up things that may have been left behind.
sys.stderr.write("Home Assistant attempting to restart.\n")
# Count remaining threads, ideally there should only be one non-daemonized
# thread left (which is us). Nothing we really do with it, but it might be
# useful when debugging shutdown/restart issues.
try: try:
nthreads = sum( nthreads = sum(
thread.is_alive() and not thread.daemon for thread in threading.enumerate() thread.is_alive() and not thread.daemon for thread in threading.enumerate()
@ -241,64 +151,27 @@ def try_to_restart() -> None:
except AssertionError: except AssertionError:
sys.stderr.write("Failed to count non-daemonic threads.\n") sys.stderr.write("Failed to count non-daemonic threads.\n")
# Try to not leave behind open filedescriptors with the emphasis on try.
try:
max_fd = os.sysconf("SC_OPEN_MAX")
except ValueError:
max_fd = 256
if platform.system() == "Darwin":
closefds_osx(3, max_fd)
else:
os.closerange(3, max_fd)
# Now launch into a new instance of Home Assistant. If this fails we
# fall through and exit with error 100 (RESTART_EXIT_CODE) in which case
# systemd will restart us when RestartForceExitStatus=100 is set in the
# systemd.service file.
sys.stderr.write("Restarting Home Assistant\n")
args = cmdline()
os.execv(args[0], args)
def main() -> int: def main() -> int:
"""Start Home Assistant.""" """Start Home Assistant."""
validate_python() validate_python()
# Run a simple daemon runner process on Windows to handle restarts
if os.name == "nt" and "--runner" not in sys.argv:
nt_args = cmdline() + ["--runner"]
while True:
try:
subprocess.check_call(nt_args)
sys.exit(0)
except KeyboardInterrupt:
sys.exit(0)
except subprocess.CalledProcessError as exc:
if exc.returncode != RESTART_EXIT_CODE:
sys.exit(exc.returncode)
args = get_arguments() args = get_arguments()
if not args.ignore_os_check:
validate_os()
if args.script is not None: if args.script is not None:
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
from homeassistant import scripts from . import scripts
return scripts.run(args.script) return scripts.run(args.script)
config_dir = os.path.abspath(os.path.join(os.getcwd(), args.config)) config_dir = os.path.abspath(os.path.join(os.getcwd(), args.config))
ensure_config_path(config_dir) ensure_config_path(config_dir)
# Daemon functions
if args.pid_file:
check_pid(args.pid_file)
if args.daemon:
daemonize()
if args.pid_file:
write_pid(args.pid_file)
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
from homeassistant import runner from . import runner
runtime_conf = runner.RuntimeConfig( runtime_conf = runner.RuntimeConfig(
config_dir=config_dir, config_dir=config_dir,
@ -321,8 +194,7 @@ def main() -> int:
if os.path.getsize(fault_file_name) == 0: if os.path.getsize(fault_file_name) == 0:
os.remove(fault_file_name) os.remove(fault_file_name)
if exit_code == RESTART_EXIT_CODE and not args.runner: check_threads()
try_to_restart()
return exit_code return exit_code

View File

@ -6,7 +6,7 @@ from typing import Any
import async_timeout import async_timeout
from homeassistant.helpers.frame import report from .helpers.frame import report
def timeout( def timeout(
@ -17,7 +17,7 @@ def timeout(
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
else: else:
report( report(
"called async_timeout.timeout with loop keyword argument. The loop keyword argument is deprecated and calls will fail after Home Assistant 2022.2", "called async_timeout.timeout with loop keyword argument. The loop keyword argument is deprecated and calls will fail after Home Assistant 2022.3",
error_if_core=False, error_if_core=False,
) )
if delay is not None: if delay is not None:
@ -30,7 +30,7 @@ def timeout(
def current_task(loop: asyncio.AbstractEventLoop) -> asyncio.Task[Any] | None: def current_task(loop: asyncio.AbstractEventLoop) -> asyncio.Task[Any] | None:
"""Backwards compatible current_task.""" """Backwards compatible current_task."""
report( report(
"called async_timeout.current_task. The current_task call is deprecated and calls will fail after Home Assistant 2022.2; use asyncio.current_task instead", "called async_timeout.current_task. The current_task call is deprecated and calls will fail after Home Assistant 2022.3; use asyncio.current_task instead",
error_if_core=False, error_if_core=False,
) )
return asyncio.current_task() return asyncio.current_task()

View File

@ -3,8 +3,9 @@ from __future__ import annotations
import asyncio import asyncio
from collections import OrderedDict from collections import OrderedDict
from collections.abc import Mapping
from datetime import timedelta from datetime import timedelta
from typing import Any, Dict, Mapping, Optional, Tuple, cast from typing import Any, Optional, cast
import jwt import jwt
@ -21,9 +22,9 @@ from .providers import AuthProvider, LoginFlow, auth_provider_from_config
EVENT_USER_ADDED = "user_added" EVENT_USER_ADDED = "user_added"
EVENT_USER_REMOVED = "user_removed" EVENT_USER_REMOVED = "user_removed"
_MfaModuleDict = Dict[str, MultiFactorAuthModule] _MfaModuleDict = dict[str, MultiFactorAuthModule]
_ProviderKey = Tuple[str, Optional[str]] _ProviderKey = tuple[str, Optional[str]]
_ProviderDict = Dict[_ProviderKey, AuthProvider] _ProviderDict = dict[_ProviderKey, AuthProvider]
class InvalidAuthError(Exception): class InvalidAuthError(Exception):

View File

@ -8,17 +8,20 @@ import hmac
from logging import getLogger from logging import getLogger
from typing import Any from typing import Any
from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from . import models from . import models
from .const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY, GROUP_ID_USER from .const import (
from .permissions import PermissionLookup, system_policies ACCESS_TOKEN_EXPIRATION,
GROUP_ID_ADMIN,
GROUP_ID_READ_ONLY,
GROUP_ID_USER,
)
from .permissions import system_policies
from .permissions.models import PermissionLookup
from .permissions.types import PolicyType from .permissions.types import PolicyType
# mypy: disallow-any-generics
STORAGE_VERSION = 1 STORAGE_VERSION = 1
STORAGE_KEY = "auth" STORAGE_KEY = "auth"
GROUP_NAME_ADMIN = "Administrators" GROUP_NAME_ADMIN = "Administrators"

View File

@ -133,7 +133,7 @@ async def auth_mfa_module_from_config(
module = await _load_mfa_module(hass, module_name) module = await _load_mfa_module(hass, module_name)
try: try:
config = module.CONFIG_SCHEMA(config) # type: ignore config = module.CONFIG_SCHEMA(config)
except vol.Invalid as err: except vol.Invalid as err:
_LOGGER.error( _LOGGER.error(
"Invalid configuration for multi-factor module %s: %s", "Invalid configuration for multi-factor module %s: %s",
@ -168,7 +168,7 @@ async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.Modul
# https://github.com/python/mypy/issues/1424 # https://github.com/python/mypy/issues/1424
await requirements.async_process_requirements( await requirements.async_process_requirements(
hass, module_path, module.REQUIREMENTS # type: ignore hass, module_path, module.REQUIREMENTS
) )
processed.add(module_name) processed.add(module_name)

View File

@ -7,7 +7,7 @@ from __future__ import annotations
import asyncio import asyncio
from collections import OrderedDict from collections import OrderedDict
import logging import logging
from typing import Any, Dict from typing import Any
import attr import attr
import voluptuous as vol import voluptuous as vol
@ -86,7 +86,7 @@ class NotifySetting:
target: str | None = attr.ib(default=None) target: str | None = attr.ib(default=None)
_UsersDict = Dict[str, NotifySetting] _UsersDict = dict[str, NotifySetting]
@MULTI_FACTOR_AUTH_MODULES.register("notify") @MULTI_FACTOR_AUTH_MODULES.register("notify")

View File

@ -8,13 +8,23 @@ import voluptuous as vol
from .const import CAT_ENTITIES from .const import CAT_ENTITIES
from .entities import ENTITY_POLICY_SCHEMA, compile_entities from .entities import ENTITY_POLICY_SCHEMA, compile_entities
from .merge import merge_policies # noqa: F401 from .merge import merge_policies
from .models import PermissionLookup from .models import PermissionLookup
from .types import PolicyType from .types import PolicyType
from .util import test_all from .util import test_all
POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA}) POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA})
__all__ = [
"POLICY_SCHEMA",
"merge_policies",
"PermissionLookup",
"PolicyType",
"AbstractPermissions",
"PolicyPermissions",
"OwnerPermissions",
]
class AbstractPermissions: class AbstractPermissions:
"""Default permissions class.""" """Default permissions class."""

View File

@ -1,5 +1,6 @@
"""Common code for permissions.""" """Common code for permissions."""
from typing import Mapping, Union from collections.abc import Mapping
from typing import Union
# MyPy doesn't support recursion yet. So writing it out as far as we need. # MyPy doesn't support recursion yet. So writing it out as far as we need.

View File

@ -1,15 +1,16 @@
"""Helpers to deal with permissions.""" """Helpers to deal with permissions."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable
from functools import wraps from functools import wraps
from typing import Callable, Dict, Optional, cast from typing import Optional, cast
from .const import SUBCAT_ALL from .const import SUBCAT_ALL
from .models import PermissionLookup from .models import PermissionLookup
from .types import CategoryType, SubCategoryDict, ValueType from .types import CategoryType, SubCategoryDict, ValueType
LookupFunc = Callable[[PermissionLookup, SubCategoryDict, str], Optional[ValueType]] LookupFunc = Callable[[PermissionLookup, SubCategoryDict, str], Optional[ValueType]]
SubCatLookupType = Dict[str, LookupFunc] SubCatLookupType = dict[str, LookupFunc]
def lookup_all( def lookup_all(

View File

@ -22,8 +22,6 @@ from ..auth_store import AuthStore
from ..const import MFA_SESSION_EXPIRATION from ..const import MFA_SESSION_EXPIRATION
from ..models import Credentials, RefreshToken, User, UserMeta from ..models import Credentials, RefreshToken, User, UserMeta
# mypy: disallow-any-generics
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DATA_REQS = "auth_prov_reqs_processed" DATA_REQS = "auth_prov_reqs_processed"
@ -142,7 +140,7 @@ async def auth_provider_from_config(
module = await load_auth_provider_module(hass, provider_name) module = await load_auth_provider_module(hass, provider_name)
try: try:
config = module.CONFIG_SCHEMA(config) # type: ignore config = module.CONFIG_SCHEMA(config)
except vol.Invalid as err: except vol.Invalid as err:
_LOGGER.error( _LOGGER.error(
"Invalid configuration for auth provider %s: %s", "Invalid configuration for auth provider %s: %s",
@ -174,8 +172,7 @@ async def load_auth_provider_module(
elif provider in processed: elif provider in processed:
return module return module
# https://github.com/python/mypy/issues/1424 reqs = module.REQUIREMENTS
reqs = module.REQUIREMENTS # type: ignore
await requirements.async_process_requirements( await requirements.async_process_requirements(
hass, f"auth provider {provider}", reqs hass, f"auth provider {provider}", reqs
) )

View File

@ -16,8 +16,6 @@ from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
CONF_ARGS = "args" CONF_ARGS = "args"
CONF_META = "meta" CONF_META = "meta"

View File

@ -18,8 +18,6 @@ from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
STORAGE_VERSION = 1 STORAGE_VERSION = 1
STORAGE_KEY = "auth_provider.homeassistant" STORAGE_KEY = "auth_provider.homeassistant"

View File

@ -14,8 +14,6 @@ from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
USER_SCHEMA = vol.Schema( USER_SCHEMA = vol.Schema(
{ {
vol.Required("username"): str, vol.Required("username"): str,

View File

@ -19,8 +19,6 @@ import homeassistant.helpers.config_validation as cv
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
AUTH_PROVIDER_TYPE = "legacy_api_password" AUTH_PROVIDER_TYPE = "legacy_api_password"
CONF_API_PASSWORD = "api_password" CONF_API_PASSWORD = "api_password"

View File

@ -14,7 +14,7 @@ from ipaddress import (
ip_address, ip_address,
ip_network, ip_network,
) )
from typing import Any, Dict, List, Union, cast from typing import Any, Union, cast
import voluptuous as vol import voluptuous as vol
@ -27,8 +27,6 @@ from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from .. import InvalidAuthError from .. import InvalidAuthError
from ..models import Credentials, RefreshToken, UserMeta from ..models import Credentials, RefreshToken, UserMeta
# mypy: disallow-any-generics
IPAddress = Union[IPv4Address, IPv6Address] IPAddress = Union[IPv4Address, IPv6Address]
IPNetwork = Union[IPv4Network, IPv6Network] IPNetwork = Union[IPv4Network, IPv6Network]
@ -76,12 +74,12 @@ class TrustedNetworksAuthProvider(AuthProvider):
@property @property
def trusted_networks(self) -> list[IPNetwork]: def trusted_networks(self) -> list[IPNetwork]:
"""Return trusted networks.""" """Return trusted networks."""
return cast(List[IPNetwork], self.config[CONF_TRUSTED_NETWORKS]) return cast(list[IPNetwork], self.config[CONF_TRUSTED_NETWORKS])
@property @property
def trusted_users(self) -> dict[IPNetwork, Any]: def trusted_users(self) -> dict[IPNetwork, Any]:
"""Return trusted users per network.""" """Return trusted users per network."""
return cast(Dict[IPNetwork, Any], self.config[CONF_TRUSTED_USERS]) return cast(dict[IPNetwork, Any], self.config[CONF_TRUSTED_USERS])
@property @property
def trusted_proxies(self) -> list[IPNetwork]: def trusted_proxies(self) -> list[IPNetwork]:

View File

@ -14,7 +14,7 @@ class StrEnum(str, Enum):
"""Create a new StrEnum instance.""" """Create a new StrEnum instance."""
if not isinstance(value, str): if not isinstance(value, str):
raise TypeError(f"{value!r} is not a string") raise TypeError(f"{value!r} is not a string")
return super().__new__(cls, value, *args, **kwargs) # type: ignore[call-overload,no-any-return] return super().__new__(cls, value, *args, **kwargs)
def __str__(self) -> str: def __str__(self) -> str:
"""Return self.value.""" """Return self.value."""

View File

@ -1,14 +1,18 @@
"""Block I/O being done in asyncio.""" """Block blocking calls being done in asyncio."""
from http.client import HTTPConnection from http.client import HTTPConnection
import time
from homeassistant.util.async_ import protect_loop from .util.async_ import protect_loop
def enable() -> None: def enable() -> None:
"""Enable the detection of I/O in the event loop.""" """Enable the detection of blocking calls in the event loop."""
# Prevent urllib3 and requests doing I/O in event loop # Prevent urllib3 and requests doing I/O in event loop
HTTPConnection.putrequest = protect_loop(HTTPConnection.putrequest) # type: ignore HTTPConnection.putrequest = protect_loop(HTTPConnection.putrequest) # type: ignore
# Prevent sleeping in event loop. Non-strict since 2022.02
time.sleep = protect_loop(time.sleep, strict=False)
# Currently disabled. pytz doing I/O when getting timezone. # Currently disabled. pytz doing I/O when getting timezone.
# Prevent files being opened inside the event loop # Prevent files being opened inside the event loop
# builtins.open = protect_loop(builtins.open) # builtins.open = protect_loop(builtins.open)

View File

@ -15,27 +15,28 @@ from typing import TYPE_CHECKING, Any
import voluptuous as vol import voluptuous as vol
import yarl import yarl
from homeassistant import config as conf_util, config_entries, core, loader from . import config as conf_util, config_entries, core, loader
from homeassistant.components import http from .components import http, persistent_notification
from homeassistant.const import ( from .const import (
REQUIRED_NEXT_PYTHON_HA_RELEASE, REQUIRED_NEXT_PYTHON_HA_RELEASE,
REQUIRED_NEXT_PYTHON_VER, REQUIRED_NEXT_PYTHON_VER,
SIGNAL_BOOTSTRAP_INTEGRATONS,
) )
from homeassistant.exceptions import HomeAssistantError from .exceptions import HomeAssistantError
from homeassistant.helpers import area_registry, device_registry, entity_registry from .helpers import area_registry, device_registry, entity_registry
from homeassistant.helpers.dispatcher import async_dispatcher_send from .helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.typing import ConfigType from .helpers.typing import ConfigType
from homeassistant.setup import ( from .setup import (
DATA_SETUP, DATA_SETUP,
DATA_SETUP_STARTED, DATA_SETUP_STARTED,
DATA_SETUP_TIME, DATA_SETUP_TIME,
async_set_domains_to_be_loaded, async_set_domains_to_be_loaded,
async_setup_component, async_setup_component,
) )
from homeassistant.util.async_ import gather_with_concurrency from .util import dt as dt_util
import homeassistant.util.dt as dt_util from .util.async_ import gather_with_concurrency
from homeassistant.util.logging import async_activate_log_queue_handler from .util.logging import async_activate_log_queue_handler
from homeassistant.util.package import async_get_user_site, is_virtual_env from .util.package import async_get_user_site, is_virtual_env
if TYPE_CHECKING: if TYPE_CHECKING:
from .runner import RuntimeConfig from .runner import RuntimeConfig
@ -49,7 +50,6 @@ DATA_LOGGING = "logging"
LOG_SLOW_STARTUP_INTERVAL = 60 LOG_SLOW_STARTUP_INTERVAL = 60
SLOW_STARTUP_CHECK_INTERVAL = 1 SLOW_STARTUP_CHECK_INTERVAL = 1
SIGNAL_BOOTSTRAP_INTEGRATONS = "bootstrap_integrations"
STAGE_1_TIMEOUT = 120 STAGE_1_TIMEOUT = 120
STAGE_2_TIMEOUT = 300 STAGE_2_TIMEOUT = 300
@ -255,8 +255,8 @@ async def async_from_config_dict(
f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER[:2])}." f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER[:2])}."
) )
_LOGGER.warning(msg) _LOGGER.warning(msg)
hass.components.persistent_notification.async_create( persistent_notification.async_create(
msg, "Python version", "python_version" hass, msg, "Python version", "python_version"
) )
return hass return hass

View File

@ -1,14 +1,17 @@
"""Support for the Abode Security System.""" """Support for the Abode Security System."""
from __future__ import annotations
from functools import partial from functools import partial
from abodepy import Abode from abodepy import Abode, AbodeAutomation as AbodeAuto
from abodepy.devices import AbodeDevice as AbodeDev
from abodepy.exceptions import AbodeAuthenticationException, AbodeException from abodepy.exceptions import AbodeAuthenticationException, AbodeException
import abodepy.helpers.timeline as TIMELINE import abodepy.helpers.timeline as TIMELINE
from requests.exceptions import ConnectTimeout, HTTPError from requests.exceptions import ConnectTimeout, HTTPError
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION,
ATTR_DATE, ATTR_DATE,
ATTR_DEVICE_ID, ATTR_DEVICE_ID,
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
@ -18,14 +21,12 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
Platform, Platform,
) )
from homeassistant.core import Event, HomeAssistant, ServiceCall
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv, entity
from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.entity import DeviceInfo, Entity
from .const import ATTRIBUTION, DEFAULT_CACHEDB, DOMAIN, LOGGER from .const import ATTRIBUTION, CONF_POLLING, DEFAULT_CACHEDB, DOMAIN, LOGGER
CONF_POLLING = "polling"
SERVICE_SETTINGS = "change_setting" SERVICE_SETTINGS = "change_setting"
SERVICE_CAPTURE_IMAGE = "capture_image" SERVICE_CAPTURE_IMAGE = "capture_image"
@ -43,7 +44,7 @@ ATTR_APP_TYPE = "app_type"
ATTR_EVENT_BY = "event_by" ATTR_EVENT_BY = "event_by"
ATTR_VALUE = "value" ATTR_VALUE = "value"
CONFIG_SCHEMA = cv.deprecated(DOMAIN) CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
CHANGE_SETTING_SCHEMA = vol.Schema( CHANGE_SETTING_SCHEMA = vol.Schema(
{vol.Required(ATTR_SETTING): cv.string, vol.Required(ATTR_VALUE): cv.string} {vol.Required(ATTR_SETTING): cv.string, vol.Required(ATTR_VALUE): cv.string}
@ -68,25 +69,25 @@ PLATFORMS = [
class AbodeSystem: class AbodeSystem:
"""Abode System class.""" """Abode System class."""
def __init__(self, abode, polling): def __init__(self, abode: Abode, polling: bool) -> None:
"""Initialize the system.""" """Initialize the system."""
self.abode = abode self.abode = abode
self.polling = polling self.polling = polling
self.entity_ids = set() self.entity_ids: set[str | None] = set()
self.logout_listener = None self.logout_listener = None
async def async_setup_entry(hass, config_entry): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Abode integration from a config entry.""" """Set up Abode integration from a config entry."""
username = config_entry.data.get(CONF_USERNAME) username = entry.data[CONF_USERNAME]
password = config_entry.data.get(CONF_PASSWORD) password = entry.data[CONF_PASSWORD]
polling = config_entry.data.get(CONF_POLLING) polling = entry.data[CONF_POLLING]
cache = hass.config.path(DEFAULT_CACHEDB) cache = hass.config.path(DEFAULT_CACHEDB)
# For previous config entries where unique_id is None # For previous config entries where unique_id is None
if config_entry.unique_id is None: if entry.unique_id is None:
hass.config_entries.async_update_entry( hass.config_entries.async_update_entry(
config_entry, unique_id=config_entry.data[CONF_USERNAME] entry, unique_id=entry.data[CONF_USERNAME]
) )
try: try:
@ -102,7 +103,7 @@ async def async_setup_entry(hass, config_entry):
hass.data[DOMAIN] = AbodeSystem(abode, polling) hass.data[DOMAIN] = AbodeSystem(abode, polling)
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) hass.config_entries.async_setup_platforms(entry, PLATFORMS)
await setup_hass_events(hass) await setup_hass_events(hass)
await hass.async_add_executor_job(setup_hass_services, hass) await hass.async_add_executor_job(setup_hass_services, hass)
@ -111,15 +112,13 @@ async def async_setup_entry(hass, config_entry):
return True return True
async def async_unload_entry(hass, config_entry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
hass.services.async_remove(DOMAIN, SERVICE_SETTINGS) hass.services.async_remove(DOMAIN, SERVICE_SETTINGS)
hass.services.async_remove(DOMAIN, SERVICE_CAPTURE_IMAGE) hass.services.async_remove(DOMAIN, SERVICE_CAPTURE_IMAGE)
hass.services.async_remove(DOMAIN, SERVICE_TRIGGER_AUTOMATION) hass.services.async_remove(DOMAIN, SERVICE_TRIGGER_AUTOMATION)
unload_ok = await hass.config_entries.async_unload_platforms( unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
config_entry, PLATFORMS
)
await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.stop) await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.stop)
await hass.async_add_executor_job(hass.data[DOMAIN].abode.logout) await hass.async_add_executor_job(hass.data[DOMAIN].abode.logout)
@ -130,22 +129,22 @@ async def async_unload_entry(hass, config_entry):
return unload_ok return unload_ok
def setup_hass_services(hass): def setup_hass_services(hass: HomeAssistant) -> None:
"""Home Assistant services.""" """Home Assistant services."""
def change_setting(call): def change_setting(call: ServiceCall) -> None:
"""Change an Abode system setting.""" """Change an Abode system setting."""
setting = call.data.get(ATTR_SETTING) setting = call.data[ATTR_SETTING]
value = call.data.get(ATTR_VALUE) value = call.data[ATTR_VALUE]
try: try:
hass.data[DOMAIN].abode.set_setting(setting, value) hass.data[DOMAIN].abode.set_setting(setting, value)
except AbodeException as ex: except AbodeException as ex:
LOGGER.warning(ex) LOGGER.warning(ex)
def capture_image(call): def capture_image(call: ServiceCall) -> None:
"""Capture a new image.""" """Capture a new image."""
entity_ids = call.data.get(ATTR_ENTITY_ID) entity_ids = call.data[ATTR_ENTITY_ID]
target_entities = [ target_entities = [
entity_id entity_id
@ -157,9 +156,9 @@ def setup_hass_services(hass):
signal = f"abode_camera_capture_{entity_id}" signal = f"abode_camera_capture_{entity_id}"
dispatcher_send(hass, signal) dispatcher_send(hass, signal)
def trigger_automation(call): def trigger_automation(call: ServiceCall) -> None:
"""Trigger an Abode automation.""" """Trigger an Abode automation."""
entity_ids = call.data.get(ATTR_ENTITY_ID) entity_ids = call.data[ATTR_ENTITY_ID]
target_entities = [ target_entities = [
entity_id entity_id
@ -184,10 +183,10 @@ def setup_hass_services(hass):
) )
async def setup_hass_events(hass): async def setup_hass_events(hass: HomeAssistant) -> None:
"""Home Assistant start and stop callbacks.""" """Home Assistant start and stop callbacks."""
def logout(event): def logout(event: Event) -> None:
"""Logout of Abode.""" """Logout of Abode."""
if not hass.data[DOMAIN].polling: if not hass.data[DOMAIN].polling:
hass.data[DOMAIN].abode.events.stop() hass.data[DOMAIN].abode.events.stop()
@ -203,10 +202,10 @@ async def setup_hass_events(hass):
) )
def setup_abode_events(hass): def setup_abode_events(hass: HomeAssistant) -> None:
"""Event callbacks.""" """Event callbacks."""
def event_callback(event, event_json): def event_callback(event: str, event_json: dict[str, str]) -> None:
"""Handle an event callback from Abode.""" """Handle an event callback from Abode."""
data = { data = {
ATTR_DEVICE_ID: event_json.get(ATTR_DEVICE_ID, ""), ATTR_DEVICE_ID: event_json.get(ATTR_DEVICE_ID, ""),
@ -245,21 +244,17 @@ def setup_abode_events(hass):
) )
class AbodeEntity(Entity): class AbodeEntity(entity.Entity):
"""Representation of an Abode entity.""" """Representation of an Abode entity."""
def __init__(self, data): _attr_attribution = ATTRIBUTION
def __init__(self, data: AbodeSystem) -> None:
"""Initialize Abode entity.""" """Initialize Abode entity."""
self._data = data self._data = data
self._available = True
self._attr_should_poll = data.polling self._attr_should_poll = data.polling
@property async def async_added_to_hass(self) -> None:
def available(self):
"""Return the available state."""
return self._available
async def async_added_to_hass(self):
"""Subscribe to Abode connection status updates.""" """Subscribe to Abode connection status updates."""
await self.hass.async_add_executor_job( await self.hass.async_add_executor_job(
self._data.abode.events.add_connection_status_callback, self._data.abode.events.add_connection_status_callback,
@ -269,29 +264,29 @@ class AbodeEntity(Entity):
self.hass.data[DOMAIN].entity_ids.add(self.entity_id) self.hass.data[DOMAIN].entity_ids.add(self.entity_id)
async def async_will_remove_from_hass(self): async def async_will_remove_from_hass(self) -> None:
"""Unsubscribe from Abode connection status updates.""" """Unsubscribe from Abode connection status updates."""
await self.hass.async_add_executor_job( await self.hass.async_add_executor_job(
self._data.abode.events.remove_connection_status_callback, self.unique_id self._data.abode.events.remove_connection_status_callback, self.unique_id
) )
def _update_connection_status(self): def _update_connection_status(self) -> None:
"""Update the entity available property.""" """Update the entity available property."""
self._available = self._data.abode.events.connected self._attr_available = self._data.abode.events.connected
self.schedule_update_ha_state() self.schedule_update_ha_state()
class AbodeDevice(AbodeEntity): class AbodeDevice(AbodeEntity):
"""Representation of an Abode device.""" """Representation of an Abode device."""
def __init__(self, data, device): def __init__(self, data: AbodeSystem, device: AbodeDev) -> None:
"""Initialize Abode device.""" """Initialize Abode device."""
super().__init__(data) super().__init__(data)
self._device = device self._device = device
self._attr_name = device.name self._attr_name = device.name
self._attr_unique_id = device.device_uuid self._attr_unique_id = device.device_uuid
async def async_added_to_hass(self): async def async_added_to_hass(self) -> None:
"""Subscribe to device events.""" """Subscribe to device events."""
await super().async_added_to_hass() await super().async_added_to_hass()
await self.hass.async_add_executor_job( await self.hass.async_add_executor_job(
@ -300,22 +295,21 @@ class AbodeDevice(AbodeEntity):
self._update_callback, self._update_callback,
) )
async def async_will_remove_from_hass(self): async def async_will_remove_from_hass(self) -> None:
"""Unsubscribe from device events.""" """Unsubscribe from device events."""
await super().async_will_remove_from_hass() await super().async_will_remove_from_hass()
await self.hass.async_add_executor_job( await self.hass.async_add_executor_job(
self._data.abode.events.remove_all_device_callbacks, self._device.device_id self._data.abode.events.remove_all_device_callbacks, self._device.device_id
) )
def update(self): def update(self) -> None:
"""Update device state.""" """Update device state."""
self._device.refresh() self._device.refresh()
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, str]:
"""Return the state attributes.""" """Return the state attributes."""
return { return {
ATTR_ATTRIBUTION: ATTRIBUTION,
"device_id": self._device.device_id, "device_id": self._device.device_id,
"battery_low": self._device.battery_low, "battery_low": self._device.battery_low,
"no_response": self._device.no_response, "no_response": self._device.no_response,
@ -323,16 +317,16 @@ class AbodeDevice(AbodeEntity):
} }
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> entity.DeviceInfo:
"""Return device registry information for this entity.""" """Return device registry information for this entity."""
return DeviceInfo( return entity.DeviceInfo(
identifiers={(DOMAIN, self._device.device_id)}, identifiers={(DOMAIN, self._device.device_id)},
manufacturer="Abode", manufacturer="Abode",
model=self._device.type, model=self._device.type,
name=self._device.name, name=self._device.name,
) )
def _update_callback(self, device): def _update_callback(self, device: AbodeDev) -> None:
"""Update the device state.""" """Update the device state."""
self.schedule_update_ha_state() self.schedule_update_ha_state()
@ -340,17 +334,16 @@ class AbodeDevice(AbodeEntity):
class AbodeAutomation(AbodeEntity): class AbodeAutomation(AbodeEntity):
"""Representation of an Abode automation.""" """Representation of an Abode automation."""
def __init__(self, data, automation): def __init__(self, data: AbodeSystem, automation: AbodeAuto) -> None:
"""Initialize for Abode automation.""" """Initialize for Abode automation."""
super().__init__(data) super().__init__(data)
self._automation = automation self._automation = automation
self._attr_name = automation.name self._attr_name = automation.name
self._attr_unique_id = automation.automation_id self._attr_unique_id = automation.automation_id
self._attr_extra_state_attributes = { self._attr_extra_state_attributes = {
ATTR_ATTRIBUTION: ATTRIBUTION,
"type": "CUE automation", "type": "CUE automation",
} }
def update(self): def update(self) -> None:
"""Update automation state.""" """Update automation state."""
self._automation.refresh() self._automation.refresh()

View File

@ -1,25 +1,33 @@
"""Support for Abode Security System alarm control panels.""" """Support for Abode Security System alarm control panels."""
from __future__ import annotations
from abodepy.devices.alarm import AbodeAlarm as AbodeAl
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel.const import ( from homeassistant.components.alarm_control_panel.const import (
SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_HOME, SUPPORT_ALARM_ARM_HOME,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED, STATE_ALARM_DISARMED,
) )
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AbodeDevice from . import AbodeDevice, AbodeSystem
from .const import ATTRIBUTION, DOMAIN from .const import DOMAIN
ICON = "mdi:security" ICON = "mdi:security"
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode alarm control panel device.""" """Set up Abode alarm control panel device."""
data = hass.data[DOMAIN] data: AbodeSystem = hass.data[DOMAIN]
async_add_entities( async_add_entities(
[AbodeAlarm(data, await hass.async_add_executor_job(data.abode.get_alarm))] [AbodeAlarm(data, await hass.async_add_executor_job(data.abode.get_alarm))]
) )
@ -31,37 +39,35 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity):
_attr_icon = ICON _attr_icon = ICON
_attr_code_arm_required = False _attr_code_arm_required = False
_attr_supported_features = SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY _attr_supported_features = SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY
_device: AbodeAl
@property @property
def state(self): def state(self) -> str | None:
"""Return the state of the device.""" """Return the state of the device."""
if self._device.is_standby: if self._device.is_standby:
state = STATE_ALARM_DISARMED return STATE_ALARM_DISARMED
elif self._device.is_away: if self._device.is_away:
state = STATE_ALARM_ARMED_AWAY return STATE_ALARM_ARMED_AWAY
elif self._device.is_home: if self._device.is_home:
state = STATE_ALARM_ARMED_HOME return STATE_ALARM_ARMED_HOME
else: return None
state = None
return state
def alarm_disarm(self, code=None): def alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command.""" """Send disarm command."""
self._device.set_standby() self._device.set_standby()
def alarm_arm_home(self, code=None): def alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command.""" """Send arm home command."""
self._device.set_home() self._device.set_home()
def alarm_arm_away(self, code=None): def alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command.""" """Send arm away command."""
self._device.set_away() self._device.set_away()
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, str]:
"""Return the state attributes.""" """Return the state attributes."""
return { return {
ATTR_ATTRIBUTION: ATTRIBUTION,
"device_id": self._device.device_id, "device_id": self._device.device_id,
"battery_backup": self._device.battery, "battery_backup": self._device.battery,
"cellular_backup": self._device.is_cellular, "cellular_backup": self._device.is_cellular,

View File

@ -1,18 +1,26 @@
"""Support for Abode Security System binary sensors.""" """Support for Abode Security System binary sensors."""
from typing import cast
from abodepy.devices.binary_sensor import AbodeBinarySensor as ABBinarySensor
import abodepy.helpers.constants as CONST import abodepy.helpers.constants as CONST
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
DEVICE_CLASS_WINDOW, BinarySensorDeviceClass,
BinarySensorEntity, BinarySensorEntity,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AbodeDevice from . import AbodeDevice, AbodeSystem
from .const import DOMAIN from .const import DOMAIN
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode binary sensor devices.""" """Set up Abode binary sensor devices."""
data = hass.data[DOMAIN] data: AbodeSystem = hass.data[DOMAIN]
device_types = [ device_types = [
CONST.TYPE_CONNECTIVITY, CONST.TYPE_CONNECTIVITY,
@ -33,14 +41,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AbodeBinarySensor(AbodeDevice, BinarySensorEntity): class AbodeBinarySensor(AbodeDevice, BinarySensorEntity):
"""A binary sensor implementation for Abode device.""" """A binary sensor implementation for Abode device."""
@property _device: ABBinarySensor
def is_on(self):
"""Return True if the binary sensor is on."""
return self._device.is_on
@property @property
def device_class(self): def is_on(self) -> bool:
"""Return True if the binary sensor is on."""
return cast(bool, self._device.is_on)
@property
def device_class(self) -> str:
"""Return the class of the binary sensor.""" """Return the class of the binary sensor."""
if self._device.get_value("is_window") == "1": if self._device.get_value("is_window") == "1":
return DEVICE_CLASS_WINDOW return BinarySensorDeviceClass.WINDOW
return self._device.generic_type return cast(str, self._device.generic_type)

View File

@ -2,25 +2,32 @@
from __future__ import annotations from __future__ import annotations
from datetime import timedelta from datetime import timedelta
from typing import Any, cast
import abodepy.helpers.constants as CONST from abodepy.devices import CONST, AbodeDevice as AbodeDev
from abodepy.devices.camera import AbodeCamera as AbodeCam
import abodepy.helpers.timeline as TIMELINE import abodepy.helpers.timeline as TIMELINE
import requests import requests
from requests.models import Response
from homeassistant.components.camera import Camera from homeassistant.components.camera import Camera
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import Event, HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import Throttle from homeassistant.util import Throttle
from . import AbodeDevice from . import AbodeDevice, AbodeSystem
from .const import DOMAIN, LOGGER from .const import DOMAIN, LOGGER
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode camera devices.""" """Set up Abode camera devices."""
data = hass.data[DOMAIN] data: AbodeSystem = hass.data[DOMAIN]
entities = [] entities = []
for device in data.abode.get_devices(generic_type=CONST.TYPE_CAMERA): for device in data.abode.get_devices(generic_type=CONST.TYPE_CAMERA):
@ -32,14 +39,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AbodeCamera(AbodeDevice, Camera): class AbodeCamera(AbodeDevice, Camera):
"""Representation of an Abode camera.""" """Representation of an Abode camera."""
def __init__(self, data, device, event): _device: AbodeCam
def __init__(self, data: AbodeSystem, device: AbodeDev, event: Event) -> None:
"""Initialize the Abode device.""" """Initialize the Abode device."""
AbodeDevice.__init__(self, data, device) AbodeDevice.__init__(self, data, device)
Camera.__init__(self) Camera.__init__(self)
self._event = event self._event = event
self._response = None self._response: Response | None = None
async def async_added_to_hass(self): async def async_added_to_hass(self) -> None:
"""Subscribe Abode events.""" """Subscribe Abode events."""
await super().async_added_to_hass() await super().async_added_to_hass()
@ -52,17 +61,17 @@ class AbodeCamera(AbodeDevice, Camera):
signal = f"abode_camera_capture_{self.entity_id}" signal = f"abode_camera_capture_{self.entity_id}"
self.async_on_remove(async_dispatcher_connect(self.hass, signal, self.capture)) self.async_on_remove(async_dispatcher_connect(self.hass, signal, self.capture))
def capture(self): def capture(self) -> bool:
"""Request a new image capture.""" """Request a new image capture."""
return self._device.capture() return cast(bool, self._device.capture())
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def refresh_image(self): def refresh_image(self) -> None:
"""Find a new image on the timeline.""" """Find a new image on the timeline."""
if self._device.refresh_image(): if self._device.refresh_image():
self.get_image() self.get_image()
def get_image(self): def get_image(self) -> None:
"""Attempt to download the most recent capture.""" """Attempt to download the most recent capture."""
if self._device.image_url: if self._device.image_url:
try: try:
@ -86,21 +95,21 @@ class AbodeCamera(AbodeDevice, Camera):
return None return None
def turn_on(self): def turn_on(self) -> None:
"""Turn on camera.""" """Turn on camera."""
self._device.privacy_mode(False) self._device.privacy_mode(False)
def turn_off(self): def turn_off(self) -> None:
"""Turn off camera.""" """Turn off camera."""
self._device.privacy_mode(True) self._device.privacy_mode(True)
def _capture_callback(self, capture): def _capture_callback(self, capture: Any) -> None:
"""Update the image with the device then refresh device.""" """Update the image with the device then refresh device."""
self._device.update_image_location(capture) self._device.update_image_location(capture)
self.get_image() self.get_image()
self.schedule_update_ha_state() self.schedule_update_ha_state()
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if on.""" """Return true if on."""
return self._device.is_on return cast(bool, self._device.is_on)

View File

@ -1,5 +1,8 @@
"""Config flow for the Abode Security System component.""" """Config flow for the Abode Security System component."""
from __future__ import annotations
from http import HTTPStatus from http import HTTPStatus
from typing import Any, cast
from abodepy import Abode from abodepy import Abode
from abodepy.exceptions import AbodeAuthenticationException, AbodeException from abodepy.exceptions import AbodeAuthenticationException, AbodeException
@ -9,11 +12,11 @@ import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.data_entry_flow import FlowResult
from .const import DEFAULT_CACHEDB, DOMAIN, LOGGER from .const import CONF_POLLING, DEFAULT_CACHEDB, DOMAIN, LOGGER
CONF_MFA = "mfa_code" CONF_MFA = "mfa_code"
CONF_POLLING = "polling"
class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
@ -21,7 +24,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1 VERSION = 1
def __init__(self): def __init__(self) -> None:
"""Initialize.""" """Initialize."""
self.data_schema = { self.data_schema = {
vol.Required(CONF_USERNAME): str, vol.Required(CONF_USERNAME): str,
@ -31,13 +34,13 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
vol.Required(CONF_MFA): str, vol.Required(CONF_MFA): str,
} }
self._cache = None self._cache: str | None = None
self._mfa_code = None self._mfa_code: str | None = None
self._password = None self._password: str | None = None
self._polling = False self._polling: bool = False
self._username = None self._username: str | None = None
async def _async_abode_login(self, step_id): async def _async_abode_login(self, step_id: str) -> FlowResult:
"""Handle login with Abode.""" """Handle login with Abode."""
self._cache = self.hass.config.path(DEFAULT_CACHEDB) self._cache = self.hass.config.path(DEFAULT_CACHEDB)
errors = {} errors = {}
@ -47,7 +50,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
Abode, self._username, self._password, True, False, False, self._cache Abode, self._username, self._password, True, False, False, self._cache
) )
except (AbodeException, ConnectTimeout, HTTPError) as ex: except AbodeException as ex:
if ex.errcode == MFA_CODE_REQUIRED[0]: if ex.errcode == MFA_CODE_REQUIRED[0]:
return await self.async_step_mfa() return await self.async_step_mfa()
@ -59,6 +62,9 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
else: else:
errors = {"base": "cannot_connect"} errors = {"base": "cannot_connect"}
except (ConnectTimeout, HTTPError):
errors = {"base": "cannot_connect"}
if errors: if errors:
return self.async_show_form( return self.async_show_form(
step_id=step_id, data_schema=vol.Schema(self.data_schema), errors=errors step_id=step_id, data_schema=vol.Schema(self.data_schema), errors=errors
@ -66,7 +72,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return await self._async_create_entry() return await self._async_create_entry()
async def _async_abode_mfa_login(self): async def _async_abode_mfa_login(self) -> FlowResult:
"""Handle multi-factor authentication (MFA) login with Abode.""" """Handle multi-factor authentication (MFA) login with Abode."""
try: try:
# Create instance to access login method for passing MFA code # Create instance to access login method for passing MFA code
@ -89,7 +95,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return await self._async_create_entry() return await self._async_create_entry()
async def _async_create_entry(self): async def _async_create_entry(self) -> FlowResult:
"""Create the config entry.""" """Create the config entry."""
config_data = { config_data = {
CONF_USERNAME: self._username, CONF_USERNAME: self._username,
@ -109,9 +115,13 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_abort(reason="reauth_successful") return self.async_abort(reason="reauth_successful")
return self.async_create_entry(title=self._username, data=config_data) return self.async_create_entry(
title=cast(str, self._username), data=config_data
)
async def async_step_user(self, user_input=None): async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle a flow initialized by the user.""" """Handle a flow initialized by the user."""
if self._async_current_entries(): if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed") return self.async_abort(reason="single_instance_allowed")
@ -126,7 +136,9 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return await self._async_abode_login(step_id="user") return await self._async_abode_login(step_id="user")
async def async_step_mfa(self, user_input=None): async def async_step_mfa(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle a multi-factor authentication (MFA) flow.""" """Handle a multi-factor authentication (MFA) flow."""
if user_input is None: if user_input is None:
return self.async_show_form( return self.async_show_form(
@ -137,13 +149,15 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return await self._async_abode_mfa_login() return await self._async_abode_mfa_login()
async def async_step_reauth(self, config): async def async_step_reauth(self, config: dict[str, Any]) -> FlowResult:
"""Handle reauthorization request from Abode.""" """Handle reauthorization request from Abode."""
self._username = config[CONF_USERNAME] self._username = config[CONF_USERNAME]
return await self.async_step_reauth_confirm() return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(self, user_input=None): async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle reauthorization flow.""" """Handle reauthorization flow."""
if user_input is None: if user_input is None:
return self.async_show_form( return self.async_show_form(

View File

@ -7,3 +7,4 @@ DOMAIN = "abode"
ATTRIBUTION = "Data provided by goabode.com" ATTRIBUTION = "Data provided by goabode.com"
DEFAULT_CACHEDB = "abodepy_cache.pickle" DEFAULT_CACHEDB = "abodepy_cache.pickle"
CONF_POLLING = "polling"

View File

@ -1,15 +1,23 @@
"""Support for Abode Security System covers.""" """Support for Abode Security System covers."""
from typing import Any
from abodepy.devices.cover import AbodeCover as AbodeCV
import abodepy.helpers.constants as CONST import abodepy.helpers.constants as CONST
from homeassistant.components.cover import CoverEntity from homeassistant.components.cover import CoverEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AbodeDevice from . import AbodeDevice, AbodeSystem
from .const import DOMAIN from .const import DOMAIN
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode cover devices.""" """Set up Abode cover devices."""
data = hass.data[DOMAIN] data: AbodeSystem = hass.data[DOMAIN]
entities = [] entities = []
@ -22,15 +30,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AbodeCover(AbodeDevice, CoverEntity): class AbodeCover(AbodeDevice, CoverEntity):
"""Representation of an Abode cover.""" """Representation of an Abode cover."""
_device: AbodeCV
@property @property
def is_closed(self): def is_closed(self) -> bool:
"""Return true if cover is closed, else False.""" """Return true if cover is closed, else False."""
return not self._device.is_open return not self._device.is_open
def close_cover(self, **kwargs): def close_cover(self, **kwargs: Any) -> None:
"""Issue close command to cover.""" """Issue close command to cover."""
self._device.close_cover() self._device.close_cover()
def open_cover(self, **kwargs): def open_cover(self, **kwargs: Any) -> None:
"""Issue open command to cover.""" """Issue open command to cover."""
self._device.open_cover() self._device.open_cover()

View File

@ -1,6 +1,10 @@
"""Support for Abode Security System lights.""" """Support for Abode Security System lights."""
from math import ceil from __future__ import annotations
from math import ceil
from typing import Any
from abodepy.devices.light import AbodeLight as AbodeLT
import abodepy.helpers.constants as CONST import abodepy.helpers.constants as CONST
from homeassistant.components.light import ( from homeassistant.components.light import (
@ -12,18 +16,23 @@ from homeassistant.components.light import (
SUPPORT_COLOR_TEMP, SUPPORT_COLOR_TEMP,
LightEntity, LightEntity,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.color import ( from homeassistant.util.color import (
color_temperature_kelvin_to_mired, color_temperature_kelvin_to_mired,
color_temperature_mired_to_kelvin, color_temperature_mired_to_kelvin,
) )
from . import AbodeDevice from . import AbodeDevice, AbodeSystem
from .const import DOMAIN from .const import DOMAIN
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode light devices.""" """Set up Abode light devices."""
data = hass.data[DOMAIN] data: AbodeSystem = hass.data[DOMAIN]
entities = [] entities = []
@ -36,7 +45,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AbodeLight(AbodeDevice, LightEntity): class AbodeLight(AbodeDevice, LightEntity):
"""Representation of an Abode light.""" """Representation of an Abode light."""
def turn_on(self, **kwargs): _device: AbodeLT
def turn_on(self, **kwargs: Any) -> None:
"""Turn on the light.""" """Turn on the light."""
if ATTR_COLOR_TEMP in kwargs and self._device.is_color_capable: if ATTR_COLOR_TEMP in kwargs and self._device.is_color_capable:
self._device.set_color_temp( self._device.set_color_temp(
@ -56,40 +67,42 @@ class AbodeLight(AbodeDevice, LightEntity):
self._device.switch_on() self._device.switch_on()
def turn_off(self, **kwargs): def turn_off(self, **kwargs: Any) -> None:
"""Turn off the light.""" """Turn off the light."""
self._device.switch_off() self._device.switch_off()
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if device is on.""" """Return true if device is on."""
return self._device.is_on return bool(self._device.is_on)
@property @property
def brightness(self): def brightness(self) -> int | None:
"""Return the brightness of the light.""" """Return the brightness of the light."""
if self._device.is_dimmable and self._device.has_brightness: if self._device.is_dimmable and self._device.has_brightness:
brightness = int(self._device.brightness) brightness = int(self._device.brightness)
# Abode returns 100 during device initialization and device refresh # Abode returns 100 during device initialization and device refresh
if brightness == 100:
return 255
# Convert Abode brightness (0-99) to Home Assistant brightness (0-255) # Convert Abode brightness (0-99) to Home Assistant brightness (0-255)
return ceil(brightness * 255 / 99.0) return 255 if brightness == 100 else ceil(brightness * 255 / 99.0)
return None
@property @property
def color_temp(self): def color_temp(self) -> int | None:
"""Return the color temp of the light.""" """Return the color temp of the light."""
if self._device.has_color: if self._device.has_color:
return color_temperature_kelvin_to_mired(self._device.color_temp) return color_temperature_kelvin_to_mired(self._device.color_temp)
return None
@property @property
def hs_color(self): def hs_color(self) -> tuple[float, float] | None:
"""Return the color of the light.""" """Return the color of the light."""
_hs = None
if self._device.has_color: if self._device.has_color:
return self._device.color _hs = self._device.color
return _hs
@property @property
def supported_features(self): def supported_features(self) -> int:
"""Flag supported features.""" """Flag supported features."""
if self._device.is_dimmable and self._device.is_color_capable: if self._device.is_dimmable and self._device.is_color_capable:
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP return SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP

View File

@ -1,15 +1,23 @@
"""Support for the Abode Security System locks.""" """Support for the Abode Security System locks."""
from typing import Any
from abodepy.devices.lock import AbodeLock as AbodeLK
import abodepy.helpers.constants as CONST import abodepy.helpers.constants as CONST
from homeassistant.components.lock import LockEntity from homeassistant.components.lock import LockEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AbodeDevice from . import AbodeDevice, AbodeSystem
from .const import DOMAIN from .const import DOMAIN
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode lock devices.""" """Set up Abode lock devices."""
data = hass.data[DOMAIN] data: AbodeSystem = hass.data[DOMAIN]
entities = [] entities = []
@ -22,15 +30,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AbodeLock(AbodeDevice, LockEntity): class AbodeLock(AbodeDevice, LockEntity):
"""Representation of an Abode lock.""" """Representation of an Abode lock."""
def lock(self, **kwargs): _device: AbodeLK
def lock(self, **kwargs: Any) -> None:
"""Lock the device.""" """Lock the device."""
self._device.lock() self._device.lock()
def unlock(self, **kwargs): def unlock(self, **kwargs: Any) -> None:
"""Unlock the device.""" """Unlock the device."""
self._device.unlock() self._device.unlock()
@property @property
def is_locked(self): def is_locked(self) -> bool:
"""Return true if device is on.""" """Return true if device is on."""
return self._device.is_locked return bool(self._device.is_locked)

View File

@ -1,40 +1,46 @@
"""Support for Abode Security System sensors.""" """Support for Abode Security System sensors."""
from __future__ import annotations from __future__ import annotations
import abodepy.helpers.constants as CONST from typing import cast
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription from abodepy.devices.sensor import CONST, AbodeSensor as AbodeSense
from homeassistant.const import (
DEVICE_CLASS_HUMIDITY, from homeassistant.components.sensor import (
DEVICE_CLASS_ILLUMINANCE, SensorDeviceClass,
DEVICE_CLASS_TEMPERATURE, SensorEntity,
SensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AbodeDevice from . import AbodeDevice, AbodeSystem
from .const import DOMAIN from .const import DOMAIN
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription( SensorEntityDescription(
key=CONST.TEMP_STATUS_KEY, key=CONST.TEMP_STATUS_KEY,
name="Temperature", name="Temperature",
device_class=DEVICE_CLASS_TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
), ),
SensorEntityDescription( SensorEntityDescription(
key=CONST.HUMI_STATUS_KEY, key=CONST.HUMI_STATUS_KEY,
name="Humidity", name="Humidity",
device_class=DEVICE_CLASS_HUMIDITY, device_class=SensorDeviceClass.HUMIDITY,
), ),
SensorEntityDescription( SensorEntityDescription(
key=CONST.LUX_STATUS_KEY, key=CONST.LUX_STATUS_KEY,
name="Lux", name="Lux",
device_class=DEVICE_CLASS_ILLUMINANCE, device_class=SensorDeviceClass.ILLUMINANCE,
), ),
) )
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode sensor devices.""" """Set up Abode sensor devices."""
data = hass.data[DOMAIN] data: AbodeSystem = hass.data[DOMAIN]
entities = [] entities = []
@ -54,7 +60,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AbodeSensor(AbodeDevice, SensorEntity): class AbodeSensor(AbodeDevice, SensorEntity):
"""A sensor implementation for Abode devices.""" """A sensor implementation for Abode devices."""
def __init__(self, data, device, description: SensorEntityDescription): _device: AbodeSense
def __init__(
self,
data: AbodeSystem,
device: AbodeSense,
description: SensorEntityDescription,
) -> None:
"""Initialize a sensor for an Abode device.""" """Initialize a sensor for an Abode device."""
super().__init__(data, device) super().__init__(data, device)
self.entity_description = description self.entity_description = description
@ -68,11 +81,12 @@ class AbodeSensor(AbodeDevice, SensorEntity):
self._attr_native_unit_of_measurement = device.lux_unit self._attr_native_unit_of_measurement = device.lux_unit
@property @property
def native_value(self): def native_value(self) -> float | None:
"""Return the state of the sensor.""" """Return the state of the sensor."""
if self.entity_description.key == CONST.TEMP_STATUS_KEY: if self.entity_description.key == CONST.TEMP_STATUS_KEY:
return self._device.temp return cast(float, self._device.temp)
if self.entity_description.key == CONST.HUMI_STATUS_KEY: if self.entity_description.key == CONST.HUMI_STATUS_KEY:
return self._device.humidity return cast(float, self._device.humidity)
if self.entity_description.key == CONST.LUX_STATUS_KEY: if self.entity_description.key == CONST.LUX_STATUS_KEY:
return self._device.lux return cast(float, self._device.lux)
return None

View File

@ -1,10 +1,17 @@
"""Support for Abode Security System switches.""" """Support for Abode Security System switches."""
import abodepy.helpers.constants as CONST from __future__ import annotations
from typing import Any, cast
from abodepy.devices.switch import CONST, AbodeSwitch as AbodeSW
from homeassistant.components.switch import SwitchEntity from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AbodeAutomation, AbodeDevice from . import AbodeAutomation, AbodeDevice, AbodeSystem
from .const import DOMAIN from .const import DOMAIN
DEVICE_TYPES = [CONST.TYPE_SWITCH, CONST.TYPE_VALVE] DEVICE_TYPES = [CONST.TYPE_SWITCH, CONST.TYPE_VALVE]
@ -12,11 +19,13 @@ DEVICE_TYPES = [CONST.TYPE_SWITCH, CONST.TYPE_VALVE]
ICON = "mdi:robot" ICON = "mdi:robot"
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Abode switch devices.""" """Set up Abode switch devices."""
data = hass.data[DOMAIN] data: AbodeSystem = hass.data[DOMAIN]
entities = [] entities: list[SwitchEntity] = []
for device_type in DEVICE_TYPES: for device_type in DEVICE_TYPES:
for device in data.abode.get_devices(generic_type=device_type): for device in data.abode.get_devices(generic_type=device_type):
@ -31,18 +40,20 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AbodeSwitch(AbodeDevice, SwitchEntity): class AbodeSwitch(AbodeDevice, SwitchEntity):
"""Representation of an Abode switch.""" """Representation of an Abode switch."""
def turn_on(self, **kwargs): _device: AbodeSW
def turn_on(self, **kwargs: Any) -> None:
"""Turn on the device.""" """Turn on the device."""
self._device.switch_on() self._device.switch_on()
def turn_off(self, **kwargs): def turn_off(self, **kwargs: Any) -> None:
"""Turn off the device.""" """Turn off the device."""
self._device.switch_off() self._device.switch_off()
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return true if device is on.""" """Return true if device is on."""
return self._device.is_on return cast(bool, self._device.is_on)
class AbodeAutomationSwitch(AbodeAutomation, SwitchEntity): class AbodeAutomationSwitch(AbodeAutomation, SwitchEntity):
@ -50,28 +61,28 @@ class AbodeAutomationSwitch(AbodeAutomation, SwitchEntity):
_attr_icon = ICON _attr_icon = ICON
async def async_added_to_hass(self): async def async_added_to_hass(self) -> None:
"""Set up trigger automation service.""" """Set up trigger automation service."""
await super().async_added_to_hass() await super().async_added_to_hass()
signal = f"abode_trigger_automation_{self.entity_id}" signal = f"abode_trigger_automation_{self.entity_id}"
self.async_on_remove(async_dispatcher_connect(self.hass, signal, self.trigger)) self.async_on_remove(async_dispatcher_connect(self.hass, signal, self.trigger))
def turn_on(self, **kwargs): def turn_on(self, **kwargs: Any) -> None:
"""Enable the automation.""" """Enable the automation."""
if self._automation.enable(True): if self._automation.enable(True):
self.schedule_update_ha_state() self.schedule_update_ha_state()
def turn_off(self, **kwargs): def turn_off(self, **kwargs: Any) -> None:
"""Disable the automation.""" """Disable the automation."""
if self._automation.enable(False): if self._automation.enable(False):
self.schedule_update_ha_state() self.schedule_update_ha_state()
def trigger(self): def trigger(self) -> None:
"""Trigger the automation.""" """Trigger the automation."""
self._automation.trigger() self._automation.trigger()
@property @property
def is_on(self): def is_on(self) -> bool:
"""Return True if the automation is enabled.""" """Return True if the automation is enabled."""
return self._automation.is_enabled return bool(self._automation.is_enabled)

View File

@ -3,6 +3,11 @@
"error": { "error": {
"cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2",
"invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7" "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7"
},
"step": {
"reauth_confirm": {
"title": "\u03a3\u03c5\u03bc\u03c0\u03bb\u03b7\u03c1\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03ae\u03c2 \u03c3\u03b1\u03c2 \u03c3\u03c4\u03bf Abode"
}
} }
} }
} }

View File

@ -19,14 +19,14 @@
"reauth_confirm": { "reauth_confirm": {
"data": { "data": {
"password": "Password", "password": "Password",
"username": "E-mail" "username": "Email"
}, },
"title": "Inserisci le tue informazioni di accesso Abode" "title": "Inserisci le tue informazioni di accesso Abode"
}, },
"user": { "user": {
"data": { "data": {
"password": "Password", "password": "Password",
"username": "E-mail" "username": "Email"
}, },
"title": "Inserisci le tue informazioni di accesso Abode" "title": "Inserisci le tue informazioni di accesso Abode"
} }

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from datetime import timedelta from datetime import timedelta
import logging import logging
from typing import Any, Dict from typing import Any
from accuweather import AccuWeather, ApiError, InvalidApiKeyError, RequestsExceededError from accuweather import AccuWeather, ApiError, InvalidApiKeyError, RequestsExceededError
from aiohttp import ClientSession from aiohttp import ClientSession
@ -63,7 +63,7 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
await hass.config_entries.async_reload(entry.entry_id) await hass.config_entries.async_reload(entry.entry_id)
class AccuWeatherDataUpdateCoordinator(DataUpdateCoordinator[Dict[str, Any]]): class AccuWeatherDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Class to manage fetching AccuWeather data API.""" """Class to manage fetching AccuWeather data API."""
def __init__( def __init__(

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Final from typing import Final
from homeassistant.components.sensor import SensorStateClass from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_CLOUDY, ATTR_CONDITION_CLOUDY,
@ -22,7 +22,6 @@ from homeassistant.components.weather import (
) )
from homeassistant.const import ( from homeassistant.const import (
CONCENTRATION_PARTS_PER_CUBIC_METER, CONCENTRATION_PARTS_PER_CUBIC_METER,
DEVICE_CLASS_TEMPERATURE,
LENGTH_FEET, LENGTH_FEET,
LENGTH_INCHES, LENGTH_INCHES,
LENGTH_METERS, LENGTH_METERS,
@ -123,21 +122,21 @@ FORECAST_SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="RealFeelTemperatureMax", key="RealFeelTemperatureMax",
device_class=DEVICE_CLASS_TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel Temperature Max", name="RealFeel Temperature Max",
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="RealFeelTemperatureMin", key="RealFeelTemperatureMin",
device_class=DEVICE_CLASS_TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel Temperature Min", name="RealFeel Temperature Min",
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="RealFeelTemperatureShadeMax", key="RealFeelTemperatureShadeMax",
device_class=DEVICE_CLASS_TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel Temperature Shade Max", name="RealFeel Temperature Shade Max",
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
@ -145,7 +144,7 @@ FORECAST_SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="RealFeelTemperatureShadeMin", key="RealFeelTemperatureShadeMin",
device_class=DEVICE_CLASS_TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel Temperature Shade Min", name="RealFeel Temperature Shade Min",
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
@ -215,7 +214,7 @@ FORECAST_SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = ( SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="ApparentTemperature", key="ApparentTemperature",
device_class=DEVICE_CLASS_TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
name="Apparent Temperature", name="Apparent Temperature",
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
@ -241,7 +240,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="DewPoint", key="DewPoint",
device_class=DEVICE_CLASS_TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
name="Dew Point", name="Dew Point",
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
@ -250,7 +249,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="RealFeelTemperature", key="RealFeelTemperature",
device_class=DEVICE_CLASS_TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel Temperature", name="RealFeel Temperature",
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
@ -258,7 +257,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="RealFeelTemperatureShade", key="RealFeelTemperatureShade",
device_class=DEVICE_CLASS_TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
name="RealFeel Temperature Shade", name="RealFeel Temperature Shade",
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
@ -291,7 +290,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="WetBulbTemperature", key="WetBulbTemperature",
device_class=DEVICE_CLASS_TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
name="Wet Bulb Temperature", name="Wet Bulb Temperature",
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,
@ -300,7 +299,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
), ),
AccuWeatherSensorDescription( AccuWeatherSensorDescription(
key="WindChillTemperature", key="WindChillTemperature",
device_class=DEVICE_CLASS_TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
name="Wind Chill Temperature", name="Wind Chill Temperature",
unit_metric=TEMP_CELSIUS, unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT, unit_imperial=TEMP_FAHRENHEIT,

View File

@ -3,9 +3,9 @@ from __future__ import annotations
from typing import Any, cast from typing import Any, cast
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, DEVICE_CLASS_TEMPERATURE from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
@ -107,7 +107,7 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
def native_value(self) -> StateType: def native_value(self) -> StateType:
"""Return the state.""" """Return the state."""
if self.forecast_day is not None: if self.forecast_day is not None:
if self.entity_description.device_class == DEVICE_CLASS_TEMPERATURE: if self.entity_description.device_class == SensorDeviceClass.TEMPERATURE:
return cast(float, self._sensor_data["Value"]) return cast(float, self._sensor_data["Value"])
if self.entity_description.key == "UVIndex": if self.entity_description.key == "UVIndex":
return cast(int, self._sensor_data["Value"]) return cast(int, self._sensor_data["Value"])
@ -117,7 +117,7 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
return round(self._sensor_data[self._unit_system]["Value"]) return round(self._sensor_data[self._unit_system]["Value"])
if self.entity_description.key == "PressureTendency": if self.entity_description.key == "PressureTendency":
return cast(str, self._sensor_data["LocalizedText"].lower()) return cast(str, self._sensor_data["LocalizedText"].lower())
if self.entity_description.device_class == DEVICE_CLASS_TEMPERATURE: if self.entity_description.device_class == SensorDeviceClass.TEMPERATURE:
return cast(float, self._sensor_data[self._unit_system]["Value"]) return cast(float, self._sensor_data[self._unit_system]["Value"])
if self.entity_description.key == "Precipitation": if self.entity_description.key == "Precipitation":
return cast(float, self._sensor_data[self._unit_system]["Value"]) return cast(float, self._sensor_data[self._unit_system]["Value"])

View File

@ -0,0 +1,24 @@
{
"config": {
"step": {
"user": {
"description": "\u0391\u03bd \u03c7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c3\u03c4\u03b5 \u03b2\u03bf\u03ae\u03b8\u03b5\u03b9\u03b1 \u03bc\u03b5 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7, \u03c1\u03af\u03be\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03bc\u03b1\u03c4\u03b9\u03ac \u03b5\u03b4\u03ce: https://www.home-assistant.io/integrations/accuweather/\n\n\u039f\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03b9 \u03b1\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03b9 \u03b1\u03c0\u03cc \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03bf\u03c5\u03c2 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c3\u03c4\u03bf \u03bc\u03b7\u03c4\u03c1\u03ce\u03bf \u03bf\u03bd\u03c4\u03bf\u03c4\u03ae\u03c4\u03c9\u03bd \u03bc\u03b5\u03c4\u03ac \u03c4\u03b7 \u03b4\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2.\n\u0397 \u03c0\u03c1\u03cc\u03b3\u03bd\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03b7 \u03b1\u03c0\u03cc \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae. \u039c\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03b7\u03bd \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c3\u03c4\u03b9\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2.",
"title": "AccuWeather"
}
}
},
"options": {
"step": {
"user": {
"data": {
"forecast": "\u03a0\u03c1\u03cc\u03b3\u03bd\u03c9\u03c3\u03b7 \u03ba\u03b1\u03b9\u03c1\u03bf\u03cd"
}
}
}
},
"system_health": {
"info": {
"remaining_requests": "\u03a5\u03c0\u03bf\u03bb\u03b5\u03b9\u03c0\u03cc\u03bc\u03b5\u03bd\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03b5\u03c0\u03cc\u03bc\u03b5\u03bd\u03b1 \u03b1\u03b9\u03c4\u03ae\u03bc\u03b1\u03c4\u03b1"
}
}
}

View File

@ -34,7 +34,7 @@
}, },
"system_health": { "system_health": {
"info": { "info": {
"can_reach_server": "AccuWeather\u30b5\u30fc\u30d0\u30fc\u306b\u5230\u9054", "can_reach_server": "AccuWeather\u30b5\u30fc\u30d0\u30fc\u3078\u306e\u30a2\u30af\u30bb\u30b9",
"remaining_requests": "\u6b8b\u308a\u306e\u8a31\u53ef\u3055\u308c\u305f\u30ea\u30af\u30a8\u30b9\u30c8" "remaining_requests": "\u6b8b\u308a\u306e\u8a31\u53ef\u3055\u308c\u305f\u30ea\u30af\u30a8\u30b9\u30c8"
} }
} }

View File

@ -10,7 +10,7 @@
"step": { "step": {
"user": { "user": {
"data": { "data": {
"api_key": "API Key", "api_key": "Chave da API",
"latitude": "Latitude", "latitude": "Latitude",
"longitude": "Longitude", "longitude": "Longitude",
"name": "Nome" "name": "Nome"

View File

@ -5,13 +5,13 @@
}, },
"error": { "error": {
"cannot_connect": "\u9023\u7dda\u5931\u6557", "cannot_connect": "\u9023\u7dda\u5931\u6557",
"invalid_api_key": "API \u5bc6\u9470\u7121\u6548", "invalid_api_key": "API \u91d1\u9470\u7121\u6548",
"requests_exceeded": "\u5df2\u8d85\u904e Accuweather API \u5141\u8a31\u7684\u8acb\u6c42\u6b21\u6578\u3002\u5fc5\u9808\u7b49\u5019\u6216\u8b8a\u66f4 API \u5bc6\u9470\u3002" "requests_exceeded": "\u5df2\u8d85\u904e Accuweather API \u5141\u8a31\u7684\u8acb\u6c42\u6b21\u6578\u3002\u5fc5\u9808\u7b49\u5019\u6216\u8b8a\u66f4 API \u91d1\u9470\u3002"
}, },
"step": { "step": {
"user": { "user": {
"data": { "data": {
"api_key": "API \u5bc6\u9470", "api_key": "API \u91d1\u9470",
"latitude": "\u7def\u5ea6", "latitude": "\u7def\u5ea6",
"longitude": "\u7d93\u5ea6", "longitude": "\u7d93\u5ea6",
"name": "\u540d\u7a31" "name": "\u540d\u7a31"
@ -27,7 +27,7 @@
"data": { "data": {
"forecast": "\u5929\u6c23\u9810\u5831" "forecast": "\u5929\u6c23\u9810\u5831"
}, },
"description": "\u7531\u65bc AccuWeather API \u5bc6\u9470\u514d\u8cbb\u7248\u672c\u9650\u5236\uff0c\u7576\u958b\u555f\u5929\u6c23\u9810\u5831\u6642\u3001\u6578\u64da\u6703\u6bcf 80 \u5206\u9418\u66f4\u65b0\u4e00\u6b21\uff0c\u800c\u975e 40 \u5206\u9418\u3002", "description": "\u7531\u65bc AccuWeather API \u91d1\u9470\u514d\u8cbb\u7248\u672c\u9650\u5236\uff0c\u7576\u958b\u555f\u5929\u6c23\u9810\u5831\u6642\u3001\u6578\u64da\u6703\u6bcf 80 \u5206\u9418\u66f4\u65b0\u4e00\u6b21\uff0c\u800c\u975e 40 \u5206\u9418\u3002",
"title": "AccuWeather \u9078\u9805" "title": "AccuWeather \u9078\u9805"
} }
} }

View File

@ -53,7 +53,7 @@ def setup_platform(
hass: HomeAssistant, hass: HomeAssistant,
config: ConfigType, config: ConfigType,
add_entities: AddEntitiesCallback, add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType, discovery_info: DiscoveryInfoType | None = None,
) -> None: ) -> None:
"""Connect with serial port and return Acer Projector.""" """Connect with serial port and return Acer Projector."""
serial_port = config[CONF_FILENAME] serial_port = config[CONF_FILENAME]

View File

@ -1,7 +1,7 @@
"""The Rollease Acmeda Automate integration.""" """The Rollease Acmeda Automate integration."""
from homeassistant.config_entries import ConfigEntry
from homeassistant import config_entries, core
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN from .const import DOMAIN
from .hub import PulseHub from .hub import PulseHub
@ -11,26 +11,20 @@ CONF_HUBS = "hubs"
PLATFORMS = [Platform.COVER, Platform.SENSOR] PLATFORMS = [Platform.COVER, Platform.SENSOR]
async def async_setup_entry( async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
):
"""Set up Rollease Acmeda Automate hub from a config entry.""" """Set up Rollease Acmeda Automate hub from a config entry."""
hub = PulseHub(hass, config_entry) hub = PulseHub(hass, config_entry)
if not await hub.async_setup(): if not await hub.async_setup():
return False return False
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = hub
hass.data[DOMAIN][config_entry.entry_id] = hub
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
return True return True
async def async_unload_entry( async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
):
"""Unload a config entry.""" """Unload a config entry."""
hub = hass.data[DOMAIN][config_entry.entry_id] hub = hass.data[DOMAIN][config_entry.entry_id]

View File

@ -1,4 +1,6 @@
"""Support for Acmeda Roller Blinds.""" """Support for Acmeda Roller Blinds."""
from __future__ import annotations
from homeassistant.components.cover import ( from homeassistant.components.cover import (
ATTR_POSITION, ATTR_POSITION,
SUPPORT_CLOSE, SUPPORT_CLOSE,
@ -11,19 +13,25 @@ from homeassistant.components.cover import (
SUPPORT_STOP_TILT, SUPPORT_STOP_TILT,
CoverEntity, CoverEntity,
) )
from homeassistant.core import callback from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .base import AcmedaBase from .base import AcmedaBase
from .const import ACMEDA_HUB_UPDATE, DOMAIN from .const import ACMEDA_HUB_UPDATE, DOMAIN
from .helpers import async_add_acmeda_entities from .helpers import async_add_acmeda_entities
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Acmeda Rollers from a config entry.""" """Set up the Acmeda Rollers from a config entry."""
hub = hass.data[DOMAIN][config_entry.entry_id] hub = hass.data[DOMAIN][config_entry.entry_id]
current = set() current: set[int] = set()
@callback @callback
def async_add_acmeda_covers(): def async_add_acmeda_covers():

View File

@ -1,13 +1,21 @@
"""Helper functions for Acmeda Pulse.""" """Helper functions for Acmeda Pulse."""
from homeassistant.core import callback from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN, LOGGER from .const import DOMAIN, LOGGER
@callback @callback
def async_add_acmeda_entities( def async_add_acmeda_entities(
hass, entity_class, config_entry, current, async_add_entities hass: HomeAssistant,
entity_class: type,
config_entry: ConfigEntry,
current: set[int],
async_add_entities: AddEntitiesCallback,
): ):
"""Add any new entities.""" """Add any new entities."""
hub = hass.data[DOMAIN][config_entry.entry_id] hub = hass.data[DOMAIN][config_entry.entry_id]
@ -26,7 +34,7 @@ def async_add_acmeda_entities(
async_add_entities(new_items) async_add_entities(new_items)
async def update_devices(hass, config_entry, api): async def update_devices(hass: HomeAssistant, config_entry: ConfigEntry, api):
"""Tell hass that device info has been updated.""" """Tell hass that device info has been updated."""
dev_registry = await get_dev_reg(hass) dev_registry = await get_dev_reg(hass)

View File

@ -1,19 +1,27 @@
"""Support for Acmeda Roller Blind Batteries.""" """Support for Acmeda Roller Blind Batteries."""
from homeassistant.components.sensor import SensorEntity from __future__ import annotations
from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE
from homeassistant.core import callback from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .base import AcmedaBase from .base import AcmedaBase
from .const import ACMEDA_HUB_UPDATE, DOMAIN from .const import ACMEDA_HUB_UPDATE, DOMAIN
from .helpers import async_add_acmeda_entities from .helpers import async_add_acmeda_entities
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Acmeda Rollers from a config entry.""" """Set up the Acmeda Rollers from a config entry."""
hub = hass.data[DOMAIN][config_entry.entry_id] hub = hass.data[DOMAIN][config_entry.entry_id]
current = set() current: set[int] = set()
@callback @callback
def async_add_acmeda_sensors(): def async_add_acmeda_sensors():
@ -33,7 +41,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AcmedaBattery(AcmedaBase, SensorEntity): class AcmedaBattery(AcmedaBase, SensorEntity):
"""Representation of a Acmeda cover device.""" """Representation of a Acmeda cover device."""
device_class = DEVICE_CLASS_BATTERY device_class = SensorDeviceClass.BATTERY
_attr_native_unit_of_measurement = PERCENTAGE _attr_native_unit_of_measurement = PERCENTAGE
@property @property

View File

@ -0,0 +1,12 @@
{
"config": {
"step": {
"user": {
"data": {
"id": "\u0391\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03cc \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03bf\u03cd \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae"
},
"title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03ad\u03bd\u03b1\u03bd \u03ba\u03cc\u03bc\u03b2\u03bf \u03b3\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7"
}
}
}
}

View File

@ -6,7 +6,7 @@
"step": { "step": {
"user": { "user": {
"data": { "data": {
"id": "Gazdag\u00e9p azonos\u00edt\u00f3" "id": "G\u00e9p azonos\u00edt\u00f3"
}, },
"title": "V\u00e1lassza ki a hozz\u00e1adni k\u00edv\u00e1nt hubot" "title": "V\u00e1lassza ki a hozz\u00e1adni k\u00edv\u00e1nt hubot"
} }

View File

@ -8,7 +8,7 @@
"data": { "data": {
"id": "ID host" "id": "ID host"
}, },
"title": "Scegliere un hub da aggiungere" "title": "Scegli un hub da aggiungere"
} }
} }
} }

View File

@ -17,3 +17,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate old entry."""
# convert title and unique_id to string
if config_entry.version == 1:
if isinstance(config_entry.unique_id, int):
hass.config_entries.async_update_entry(
config_entry,
unique_id=str(config_entry.unique_id),
title=str(config_entry.title),
)
return True

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from typing import Any from typing import Any
from adax import Adax from adax import Adax
from adax_local import Adax as AdaxLocal
from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import ( from homeassistant.components.climate.const import (
@ -14,7 +15,10 @@ from homeassistant.components.climate.const import (
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_TEMPERATURE, ATTR_TEMPERATURE,
CONF_IP_ADDRESS,
CONF_PASSWORD, CONF_PASSWORD,
CONF_TOKEN,
CONF_UNIQUE_ID,
PRECISION_WHOLE, PRECISION_WHOLE,
TEMP_CELSIUS, TEMP_CELSIUS,
) )
@ -23,7 +27,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ACCOUNT_ID, DOMAIN from .const import ACCOUNT_ID, CONNECTION_TYPE, DOMAIN, LOCAL
async def async_setup_entry( async def async_setup_entry(
@ -32,6 +36,17 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the Adax thermostat with config flow.""" """Set up the Adax thermostat with config flow."""
if entry.data.get(CONNECTION_TYPE) == LOCAL:
adax_data_handler = AdaxLocal(
entry.data[CONF_IP_ADDRESS],
entry.data[CONF_TOKEN],
websession=async_get_clientsession(hass, verify_ssl=False),
)
async_add_entities(
[LocalAdaxDevice(adax_data_handler, entry.data[CONF_UNIQUE_ID])], True
)
return
adax_data_handler = Adax( adax_data_handler = Adax(
entry.data[ACCOUNT_ID], entry.data[ACCOUNT_ID],
entry.data[CONF_PASSWORD], entry.data[CONF_PASSWORD],
@ -107,3 +122,38 @@ class AdaxDevice(ClimateEntity):
self._attr_hvac_mode = HVAC_MODE_OFF self._attr_hvac_mode = HVAC_MODE_OFF
self._attr_icon = "mdi:radiator-off" self._attr_icon = "mdi:radiator-off"
return return
class LocalAdaxDevice(ClimateEntity):
"""Representation of a heater."""
_attr_hvac_modes = [HVAC_MODE_HEAT]
_attr_hvac_mode = HVAC_MODE_HEAT
_attr_max_temp = 35
_attr_min_temp = 5
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE
_attr_target_temperature_step = PRECISION_WHOLE
_attr_temperature_unit = TEMP_CELSIUS
def __init__(self, adax_data_handler, unique_id):
"""Initialize the heater."""
self._adax_data_handler = adax_data_handler
self._attr_unique_id = unique_id
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, unique_id)},
manufacturer="Adax",
)
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
await self._adax_data_handler.set_target_temperature(temperature)
async def async_update(self) -> None:
"""Get the latest data."""
data = await self._adax_data_handler.get_status()
self._attr_target_temperature = data["target_temperature"]
self._attr_current_temperature = data["current_temperature"]
self._attr_available = self._attr_current_temperature is not None

View File

@ -5,69 +5,140 @@ import logging
from typing import Any from typing import Any
import adax import adax
import adax_local
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD from homeassistant.const import (
from homeassistant.core import HomeAssistant CONF_IP_ADDRESS,
CONF_PASSWORD,
CONF_TOKEN,
CONF_UNIQUE_ID,
)
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import ACCOUNT_ID, DOMAIN from .const import (
ACCOUNT_ID,
_LOGGER = logging.getLogger(__name__) CLOUD,
CONNECTION_TYPE,
STEP_USER_DATA_SCHEMA = vol.Schema( DOMAIN,
{vol.Required(ACCOUNT_ID): int, vol.Required(CONF_PASSWORD): str} LOCAL,
WIFI_PSWD,
WIFI_SSID,
) )
_LOGGER = logging.getLogger(__name__)
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
"""Validate the user input allows us to connect."""
account_id = data[ACCOUNT_ID]
password = data[CONF_PASSWORD].replace(" ", "")
token = await adax.get_adax_token(
async_get_clientsession(hass), account_id, password
)
if token is None:
_LOGGER.info("Adax: Failed to login to retrieve token")
raise CannotConnect
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Adax.""" """Handle a config flow for Adax."""
VERSION = 1 VERSION = 2
async def async_step_user( async def async_step_user(self, user_input=None):
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step.""" """Handle the initial step."""
data_schema = vol.Schema(
{
vol.Required(CONNECTION_TYPE, default=CLOUD): vol.In(
(
CLOUD,
LOCAL,
)
)
}
)
if user_input is None: if user_input is None:
return self.async_show_form( return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA step_id="user",
data_schema=data_schema,
) )
if user_input[CONNECTION_TYPE] == LOCAL:
return await self.async_step_local()
return await self.async_step_cloud()
async def async_step_local(self, user_input=None):
"""Handle the local step."""
data_schema = vol.Schema(
{vol.Required(WIFI_SSID): str, vol.Required(WIFI_PSWD): str}
)
if user_input is None:
return self.async_show_form(
step_id="local",
data_schema=data_schema,
)
wifi_ssid = user_input[WIFI_SSID].replace(" ", "")
wifi_pswd = user_input[WIFI_PSWD].replace(" ", "")
configurator = adax_local.AdaxConfig(wifi_ssid, wifi_pswd)
try:
device_configured = await configurator.configure_device()
except adax_local.HeaterNotAvailable:
return self.async_abort(reason="heater_not_available")
except adax_local.HeaterNotFound:
return self.async_abort(reason="heater_not_found")
except adax_local.InvalidWifiCred:
return self.async_abort(reason="invalid_auth")
if not device_configured:
return self.async_show_form(
step_id="local",
data_schema=data_schema,
errors={"base": "cannot_connect"},
)
unique_id = str(configurator.mac_id)
await self.async_set_unique_id(unique_id)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=unique_id,
data={
CONF_IP_ADDRESS: configurator.device_ip,
CONF_TOKEN: configurator.access_token,
CONF_UNIQUE_ID: unique_id,
CONNECTION_TYPE: LOCAL,
},
)
async def async_step_cloud(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the cloud step."""
data_schema = vol.Schema(
{vol.Required(ACCOUNT_ID): int, vol.Required(CONF_PASSWORD): str}
)
if user_input is None:
return self.async_show_form(step_id="cloud", data_schema=data_schema)
errors = {} errors = {}
await self.async_set_unique_id(user_input[ACCOUNT_ID]) await self.async_set_unique_id(str(user_input[ACCOUNT_ID]))
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
try: account_id = user_input[ACCOUNT_ID]
await validate_input(self.hass, user_input) password = user_input[CONF_PASSWORD].replace(" ", "")
except CannotConnect:
token = await adax.get_adax_token(
async_get_clientsession(self.hass), account_id, password
)
if token is None:
_LOGGER.info("Adax: Failed to login to retrieve token")
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
else: return self.async_show_form(
return self.async_create_entry( step_id="cloud",
title=user_input[ACCOUNT_ID], data=user_input data_schema=data_schema,
errors=errors,
) )
return self.async_show_form( return self.async_create_entry(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors title=str(user_input[ACCOUNT_ID]),
data={
ACCOUNT_ID: account_id,
CONF_PASSWORD: password,
CONNECTION_TYPE: CLOUD,
},
) )
class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""

View File

@ -2,4 +2,9 @@
from typing import Final from typing import Final
ACCOUNT_ID: Final = "account_id" ACCOUNT_ID: Final = "account_id"
CLOUD = "Cloud"
CONNECTION_TYPE = "connection_type"
DOMAIN: Final = "adax" DOMAIN: Final = "adax"
LOCAL = "Local"
WIFI_SSID = "wifi_ssid"
WIFI_PSWD = "wifi_pswd"

View File

@ -4,10 +4,10 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/adax", "documentation": "https://www.home-assistant.io/integrations/adax",
"requirements": [ "requirements": [
"adax==0.2.0" "adax==0.2.0", "Adax-local==0.1.3"
], ],
"codeowners": [ "codeowners": [
"@danielhiversen" "@danielhiversen"
], ],
"iot_class": "cloud_polling" "iot_class": "local_polling"
} }

View File

@ -2,6 +2,19 @@
"config": { "config": {
"step": { "step": {
"user": { "user": {
"data": {
"connection_type": "Select connection type"
},
"description": "Select connection type. Local requires heaters with bluetooth"
},
"local": {
"data": {
"wifi_ssid": "Wi-Fi SSID",
"wifi_pswd": "Wi-Fi Password"
},
"description": "Reset the heater by pressing + and OK until display shows 'Reset'. Then press and hold OK button on the heater until the blue led starts blinking before pressing Submit. Configuring heater might take some minutes."
},
"cloud": {
"data": { "data": {
"account_id": "Account ID", "account_id": "Account ID",
"password": "[%key:common::config_flow::data::password%]" "password": "[%key:common::config_flow::data::password%]"
@ -12,7 +25,10 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
}, },
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"heater_not_available": "Heater not available. Try to reset the heater by pressing + and OK for some seconds.",
"heater_not_found": "Heater not found. Try to move the heater closer to Home Assistant computer.",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
} }
} }
} }

View File

@ -1,13 +1,25 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e" "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e",
"invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435"
}, },
"error": { "error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435", "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435",
"invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435" "invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435"
}, },
"step": { "step": {
"cloud": {
"data": {
"password": "\u041f\u0430\u0440\u043e\u043b\u0430"
}
},
"local": {
"data": {
"wifi_pswd": "Wi-Fi \u043f\u0430\u0440\u043e\u043b\u0430",
"wifi_ssid": "Wi-Fi SSID"
}
},
"user": { "user": {
"data": { "data": {
"host": "\u0425\u043e\u0441\u0442", "host": "\u0425\u043e\u0441\u0442",

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "El dispositiu ja est\u00e0 configurat" "already_configured": "El dispositiu ja est\u00e0 configurat",
"heater_not_available": "Escalfador no disponible. Intenta reiniciar l'escalfador prement '+' i 'OK' durant uns segons.",
"heater_not_found": "No s'ha trobat l'escalfador. Intenta apropar-lo a l'ordinador amb Home Assistant.",
"invalid_auth": "Autenticaci\u00f3 inv\u00e0lida"
}, },
"error": { "error": {
"cannot_connect": "Ha fallat la connexi\u00f3", "cannot_connect": "Ha fallat la connexi\u00f3",
"invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "ID del compte",
"password": "Contrasenya"
}
},
"local": {
"data": {
"wifi_pswd": "Contrasenya Wi-Fi",
"wifi_ssid": "SSID Wi-Fi"
},
"description": "Reinicia l'escalfador prement '+' i 'OK' fins que la pantalla mostri 'Reset'. A continuaci\u00f3 i abans de fer clic a Envia, mant\u00e9 premut el bot\u00f3 'OK' fins que el led blau comenci a parpellejar. La configuraci\u00f3 de l'escalfador pot trigar uns minuts."
},
"user": { "user": {
"data": { "data": {
"account_id": "ID del compte", "account_id": "ID del compte",
"connection_type": "Selecciona el tipus de connexi\u00f3",
"host": "Amfitri\u00f3", "host": "Amfitri\u00f3",
"password": "Contrasenya" "password": "Contrasenya"
} },
"description": "Selecciona el tipus de connexi\u00f3. La local necessita escalfadors amb Bluetooth"
} }
} }
} }

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno" "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno",
"heater_not_available": "Oh\u0159\u00edva\u010d nen\u00ed k dispozici. Zkuste resetovat oh\u0159\u00edva\u010d stisknut\u00edm tla\u010d\u00edtek + a OK na n\u011bkolik sekund.",
"heater_not_found": "Oh\u0159\u00edva\u010d nenalezen. Zkuste p\u0159em\u00edstit oh\u0159\u00edva\u010d bl\u00ed\u017ee k po\u010d\u00edta\u010di Home Assistant.",
"invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed"
}, },
"error": { "error": {
"cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit", "cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit",
"invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed" "invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "ID \u00fa\u010dtu",
"password": "Heslo"
}
},
"local": {
"data": {
"wifi_pswd": "Heslo Wi-Fi",
"wifi_ssid": "Wi-Fi SSID"
},
"description": "Resetujte oh\u0159\u00edva\u010d stisknut\u00edm + a OK, dokud se nezobraz\u00ed \"Reset\". Pot\u00e9 stiskn\u011bte a podr\u017ete tla\u010d\u00edtko OK na oh\u0159\u00edva\u010di, dokud modr\u00e1 led dioda neza\u010dne blikat, ne\u017e stisknete tla\u010d\u00edtko Odeslat. Konfigurace oh\u0159\u00edva\u010de m\u016f\u017ee trvat n\u011bkolik minut."
},
"user": { "user": {
"data": { "data": {
"account_id": "ID \u00fa\u010dtu", "account_id": "ID \u00fa\u010dtu",
"connection_type": "Vyberte typ p\u0159ipojen\u00ed",
"host": "Hostitel", "host": "Hostitel",
"password": "Heslo" "password": "Heslo"
} },
"description": "Vyberte typ p\u0159ipojen\u00ed. Lok\u00e1ln\u00ed vy\u017eaduje oh\u0159\u00edva\u010de s bluetooth"
} }
} }
} }

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Ger\u00e4t ist bereits konfiguriert" "already_configured": "Ger\u00e4t ist bereits konfiguriert",
"heater_not_available": "Heizger\u00e4t nicht verf\u00fcgbar. Versuche das Heizger\u00e4t zur\u00fcckzusetzen, indem du + und OK einige Sekunden lang dr\u00fcckst.",
"heater_not_found": "Heizger\u00e4t nicht gefunden. Versuche das Heizger\u00e4t n\u00e4her an den Home Assistant-Computer zu bringen.",
"invalid_auth": "Ung\u00fcltige Authentifizierung"
}, },
"error": { "error": {
"cannot_connect": "Verbindung fehlgeschlagen", "cannot_connect": "Verbindung fehlgeschlagen",
"invalid_auth": "Ung\u00fcltige Authentifizierung" "invalid_auth": "Ung\u00fcltige Authentifizierung"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "Konto-ID",
"password": "Passwort"
}
},
"local": {
"data": {
"wifi_pswd": "WLAN Passwort",
"wifi_ssid": "WLAN SSID"
},
"description": "Setze das Heizger\u00e4t zur\u00fcck, indem du + und OK dr\u00fcckst, bis auf dem Display \"Reset\" angezeigt wird. Halte dann die OK-Taste am Heizger\u00e4t gedr\u00fcckt, bis die blaue LED zu blinken beginnt, und dr\u00fccke dann auf Senden. Das Konfigurieren des Heizger\u00e4ts kann einige Minuten dauern."
},
"user": { "user": {
"data": { "data": {
"account_id": "Konto-ID", "account_id": "Konto-ID",
"connection_type": "Verbindungstyp ausw\u00e4hlen",
"host": "Host", "host": "Host",
"password": "Passwort" "password": "Passwort"
} },
"description": "Verbindungstyp ausw\u00e4hlen. Lokal erfordert Heizungen mit Bluetooth"
} }
} }
} }

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Device is already configured" "already_configured": "Device is already configured",
"heater_not_available": "Heater not available. Try to reset the heater by pressing + and OK for some seconds.",
"heater_not_found": "Heater not found. Try to move the heater closer to Home Assistant computer.",
"invalid_auth": "Invalid authentication"
}, },
"error": { "error": {
"cannot_connect": "Failed to connect", "cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication" "invalid_auth": "Invalid authentication"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "Account ID",
"password": "Password"
}
},
"local": {
"data": {
"wifi_pswd": "Wi-Fi Password",
"wifi_ssid": "Wi-Fi SSID"
},
"description": "Reset the heater by pressing + and OK until display shows 'Reset'. Then press and hold OK button on the heater until the blue led starts blinking before pressing Submit. Configuring heater might take some minutes."
},
"user": { "user": {
"data": { "data": {
"account_id": "Account ID", "account_id": "Account ID",
"connection_type": "Select connection type",
"host": "Host", "host": "Host",
"password": "Password" "password": "Password"
} },
"description": "Select connection type. Local requires heaters with bluetooth"
} }
} }
} }

View File

@ -0,0 +1,12 @@
{
"config": {
"step": {
"user": {
"data": {
"connection_type": "Seleccione el tipo de conexi\u00f3n"
},
"description": "Seleccione el tipo de conexi\u00f3n. Local requiere calentadores con bluetooth"
}
}
}
}

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "El dispositivo ya est\u00e1 configurado" "already_configured": "El dispositivo ya est\u00e1 configurado",
"heater_not_available": "Calentador no disponible. Intente restablecer el calentador pulsando + y OK durante algunos segundos.",
"heater_not_found": "No se encuentra el calefactor. Intente acercar el calefactor al ordenador del Asistente de Hogar.",
"invalid_auth": "Autenticaci\u00f3n no v\u00e1lida"
}, },
"error": { "error": {
"cannot_connect": "No se pudo conectar", "cannot_connect": "No se pudo conectar",
"invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "ID de la cuenta",
"password": "Contrase\u00f1a"
}
},
"local": {
"data": {
"wifi_pswd": "Contrase\u00f1a Wi-Fi",
"wifi_ssid": "SSID Wi-Fi"
},
"description": "Reinicie el calentador presionando + y OK hasta que la pantalla muestre 'Reiniciar'. Luego presione y mantenga presionado el bot\u00f3n OK en el calentador hasta que el LED azul comience a parpadear antes de presionar Enviar. La configuraci\u00f3n del calentador puede llevar algunos minutos."
},
"user": { "user": {
"data": { "data": {
"account_id": "ID de la cuenta", "account_id": "ID de la cuenta",
"connection_type": "Seleccione el tipo de conexi\u00f3n",
"host": "Host", "host": "Host",
"password": "Contrase\u00f1a" "password": "Contrase\u00f1a"
} },
"description": "Seleccione el tipo de conexi\u00f3n. Local requiere calentadores con bluetooth"
} }
} }
} }

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Seade on juba h\u00e4\u00e4lestatud" "already_configured": "Seade on juba h\u00e4\u00e4lestatud",
"heater_not_available": "K\u00fctteseade pole saadaval. Proovi k\u00fctteseadet l\u00e4htestada, vajutades m\u00f5ne sekundi jooksul + ja OK.",
"heater_not_found": "K\u00fctteseadet ei leitud. Proovi viia k\u00fctteseade Home Assistanti arvutile l\u00e4hemale.",
"invalid_auth": "Tuvastamine nurjus"
}, },
"error": { "error": {
"cannot_connect": "\u00dchendamine nurjus", "cannot_connect": "\u00dchendamine nurjus",
"invalid_auth": "Tuvastamise viga" "invalid_auth": "Tuvastamise viga"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "Konto ID",
"password": "Salas\u00f5na"
}
},
"local": {
"data": {
"wifi_pswd": "Wi-Fi salas\u00f5na",
"wifi_ssid": "Wi-Fi SSID"
},
"description": "L\u00e4htesta k\u00fctteseade, vajutades + ja OK kuni ekraanil kuvatakse \"Reset\". Seej\u00e4rel vajuta ja hoia kerisel nuppu OK kuni sinine led hakkab vilkuma enne nupu Edasta vajutamist. K\u00fctteseadme konfigureerimine v\u00f5ib v\u00f5tta aega m\u00f5ni minut."
},
"user": { "user": {
"data": { "data": {
"account_id": "Konto ID", "account_id": "Konto ID",
"connection_type": "Vali \u00fchenduse t\u00fc\u00fcp",
"host": "Host", "host": "Host",
"password": "Salas\u00f5na" "password": "Salas\u00f5na"
} },
"description": "Vali \u00fchenduse t\u00fc\u00fcp. Kohalik n\u00f5uab bluetoothiga k\u00fctteseadmeid"
} }
} }
} }

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9",
"heater_not_available": "Chauffage non disponible. Essayez de r\u00e9initialiser le chauffage en appuyant sur + et OK pendant quelques secondes.",
"heater_not_found": "Chauffage introuvable. Essayez de rapprocher le radiateur de l'ordinateur Home Assistant.",
"invalid_auth": "Authentification invalide"
}, },
"error": { "error": {
"cannot_connect": "\u00c9chec de connexion", "cannot_connect": "\u00c9chec de connexion",
"invalid_auth": "Authentification invalide" "invalid_auth": "Authentification invalide"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "Identifiant de compte",
"password": "Mot der passe"
}
},
"local": {
"data": {
"wifi_pswd": "Mot de passe WiFi",
"wifi_ssid": "identifiant Wifi"
},
"description": "R\u00e9initialisez le radiateur en appuyant sur + et OK jusqu'\u00e0 ce que l'\u00e9cran affiche \u00ab\u00a0Reset\u00a0\u00bb. Appuyez ensuite sur le bouton OK du radiateur et maintenez-le enfonc\u00e9 jusqu'\u00e0 ce que le voyant bleu commence \u00e0 clignoter avant d'appuyer sur Soumettre. La configuration du chauffage peut prendre quelques minutes."
},
"user": { "user": {
"data": { "data": {
"account_id": "identifiant de compte", "account_id": "identifiant de compte",
"connection_type": "S\u00e9lectionner le type de connexion",
"host": "H\u00f4te", "host": "H\u00f4te",
"password": "Mot de passe" "password": "Mot de passe"
} },
"description": "S\u00e9lectionnez le type de connexion. Local n\u00e9cessite des radiateurs avec Bluetooth"
} }
} }
} }

View File

@ -1,13 +1,19 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4" "already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4",
"invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9"
}, },
"error": { "error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4", "cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
"invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9" "invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9"
}, },
"step": { "step": {
"cloud": {
"data": {
"password": "\u05e1\u05d9\u05e1\u05de\u05d4"
}
},
"user": { "user": {
"data": { "data": {
"account_id": "\u05de\u05d6\u05d4\u05d4 \u05d7\u05e9\u05d1\u05d5\u05df", "account_id": "\u05de\u05d6\u05d4\u05d4 \u05d7\u05e9\u05d1\u05d5\u05df",

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van",
"heater_not_available": "A f\u0171t\u0151berendez\u00e9s nem \u00e1ll rendelkez\u00e9sre. Pr\u00f3b\u00e1lja meg gy\u00e1ri \u00e1llapotba vissza\u00e1ll\u00edtani a + \u00e9s az OK gomb nyomvatart\u00e1s\u00e1val n\u00e9h\u00e1ny m\u00e1sodpercig.",
"heater_not_found": "A f\u0171t\u0151berendez\u00e9s nem tal\u00e1lhat\u00f3. Pr\u00f3b\u00e1lja meg k\u00f6zelebb helyezni a Home Assistant sz\u00e1m\u00edt\u00f3g\u00e9phez.",
"invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s"
}, },
"error": { "error": {
"cannot_connect": "Nem siker\u00fclt csatlakozni", "cannot_connect": "Nem siker\u00fclt csatlakozni",
"invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "Fi\u00f3k ID",
"password": "Jelsz\u00f3"
}
},
"local": {
"data": {
"wifi_pswd": "WiFi jelsz\u00f3",
"wifi_ssid": "WiFi ssid"
},
"description": "\u00c1ll\u00edtsa vissza a f\u0171t\u0151berendez\u00e9st a + \u00e9s az OK gomb nyomvatart\u00e1s\u00e1val, m\u00edg a kijelz\u0151n a \"Reset\" (Vissza\u00e1ll\u00edt\u00e1s) felirat nem jelenik meg. Ezut\u00e1n nyomja meg \u00e9s tartsa lenyomva a f\u0171t\u0151berendez\u00e9s OK gombj\u00e1t, am\u00edg a k\u00e9k led villogni nem kezd, majd nyomja meg a K\u00fcld\u00e9s gombot. A f\u0171t\u0151berendez\u00e9s konfigur\u00e1l\u00e1sa n\u00e9h\u00e1ny percet vehet ig\u00e9nybe."
},
"user": { "user": {
"data": { "data": {
"account_id": "Fi\u00f3k ID", "account_id": "Fi\u00f3k ID",
"connection_type": "V\u00e1lassza ki a kapcsolat t\u00edpus\u00e1t",
"host": "C\u00edm", "host": "C\u00edm",
"password": "Jelsz\u00f3" "password": "Jelsz\u00f3"
} },
"description": "V\u00e1lassza ki a kapcsolat t\u00edpus\u00e1t. A Helyi kapcsolathoz bluetooth-os f\u0171t\u0151berendez\u00e9sekre van sz\u00fcks\u00e9g"
} }
} }
} }

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Perangkat sudah dikonfigurasi" "already_configured": "Perangkat sudah dikonfigurasi",
"heater_not_available": "Pemanas tidak tersedia. Cobalah untuk mengatur ulang pemanas dengan menekan + dan OK selama beberapa detik.",
"heater_not_found": "Pemanas tidak ditemukan. Coba dekatkan pemanas ke komputer Home Assistant.",
"invalid_auth": "Autentikasi tidak valid"
}, },
"error": { "error": {
"cannot_connect": "Gagal terhubung", "cannot_connect": "Gagal terhubung",
"invalid_auth": "Autentikasi tidak valid" "invalid_auth": "Autentikasi tidak valid"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "ID Akun",
"password": "Kata Sandi"
}
},
"local": {
"data": {
"wifi_pswd": "Kata sandi Wi-Fi",
"wifi_ssid": "SSID Wi-Fi"
},
"description": "Atur ulang pemanas dengan menekan + dan OK hingga muncul tampilan 'Reset'. Kemudian tekan dan tahan tombol OK pada pemanas sampai led biru mulai berkedip sebelum menekan Submit. Mengonfigurasi pemanas mungkin memerlukan waktu beberapa menit."
},
"user": { "user": {
"data": { "data": {
"account_id": "ID Akun", "account_id": "ID Akun",
"connection_type": "Pilih jenis koneksi",
"host": "Host", "host": "Host",
"password": "Kata Sandi" "password": "Kata Sandi"
} },
"description": "Pilih jenis koneksi. Lokal membutuhkan pemanas dengan bluetooth"
} }
} }
} }

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato",
"heater_not_available": "Riscaldatore non disponibile. Prova a ripristinare il riscaldatore premendo + e OK per alcuni secondi.",
"heater_not_found": "Riscaldatore non trovato. Prova ad avvicinare il riscaldatore al computer Home Assistant.",
"invalid_auth": "Autenticazione non valida"
}, },
"error": { "error": {
"cannot_connect": "Impossibile connettersi", "cannot_connect": "Impossibile connettersi",
"invalid_auth": "Autenticazione non valida" "invalid_auth": "Autenticazione non valida"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "ID account",
"password": "Password"
}
},
"local": {
"data": {
"wifi_pswd": "Password Wi-Fi",
"wifi_ssid": "SSID Wi-Fi"
},
"description": "Ripristina il riscaldatore premendo + e OK finch\u00e9 il display non mostra 'Reset'. Quindi premi e tieni premuto il pulsante OK sul riscaldatore fino a quando il led blu inizia a lampeggiare prima di premere Invia. La configurazione del riscaldatore potrebbe richiedere alcuni minuti."
},
"user": { "user": {
"data": { "data": {
"account_id": "ID account", "account_id": "ID account",
"connection_type": "Seleziona il tipo di connessione",
"host": "Host", "host": "Host",
"password": "Password" "password": "Password"
} },
"description": "Seleziona il tipo di connessione. Locale richiede riscaldatori con bluetooth"
} }
} }
} }

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059" "already_configured": "\u30c7\u30d0\u30a4\u30b9\u306f\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059",
"heater_not_available": "\u30d2\u30fc\u30bf\u30fc\u306f\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002\u6570\u79d2\u9593\u3001+ \u3068 OK \u3092\u62bc\u3057\u3066\u30d2\u30fc\u30bf\u30fc\u3092\u30ea\u30bb\u30c3\u30c8\u3057\u3066\u307f\u3066\u304f\u3060\u3055\u3044\u3002",
"heater_not_found": "\u30d2\u30fc\u30bf\u30fc\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002\u30d2\u30fc\u30bf\u30fc\u3092Home Assistant\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u306b\u8fd1\u3065\u3051\u3066\u307f\u3066\u304f\u3060\u3055\u3044\u3002",
"invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c"
}, },
"error": { "error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f", "cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c" "invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "\u30a2\u30ab\u30a6\u30f3\u30c8ID",
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9"
}
},
"local": {
"data": {
"wifi_pswd": "Wifi\u30d1\u30b9\u30ef\u30fc\u30c9",
"wifi_ssid": "Wifi ssid"
},
"description": "\u30c7\u30a3\u30b9\u30d7\u30ec\u30a4\u306b\u3001 'Reset ' \u3068\u8868\u793a\u3055\u308c\u308b\u307e\u3067 + \u3068 OK \u3092\u62bc\u3057\u3066\u3001\u30d2\u30fc\u30bf\u30fc\u3092\u30ea\u30bb\u30c3\u30c8\u3057\u307e\u3059\u3002\u305d\u306e\u5f8c\u3001\u30d2\u30fc\u30bf\u30fc\u306eOK\u30dc\u30bf\u30f3\u3092\u9752\u306eLED\u304c\u70b9\u6ec5\u3057\u59cb\u3081\u308b\u307e\u3067\u62bc\u3057\u7d9a\u3051\u3066\u304b\u3089\u3001Submit\u3092\u62bc\u3057\u307e\u3059\u3002\u30d2\u30fc\u30bf\u30fc\u306e\u8a2d\u5b9a\u306b\u306f\u6570\u5206\u304b\u304b\u308b\u5834\u5408\u304c\u3042\u308a\u307e\u3059\u3002"
},
"user": { "user": {
"data": { "data": {
"account_id": "\u30a2\u30ab\u30a6\u30f3\u30c8ID", "account_id": "\u30a2\u30ab\u30a6\u30f3\u30c8ID",
"connection_type": "\u63a5\u7d9a\u30bf\u30a4\u30d7\u306e\u9078\u629e",
"host": "\u30db\u30b9\u30c8", "host": "\u30db\u30b9\u30c8",
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9" "password": "\u30d1\u30b9\u30ef\u30fc\u30c9"
} },
"description": "\u63a5\u7d9a\u30bf\u30a4\u30d7\u3092\u9078\u629e\u3057\u307e\u3059\u3002\u30ed\u30fc\u30ab\u30eb\u306b\u306fBluetooth\u4ed8\u304d\u306e\u30d2\u30fc\u30bf\u30fc\u304c\u5fc5\u8981\u3067\u3059"
} }
} }
} }

View File

@ -0,0 +1,29 @@
{
"config": {
"abort": {
"heater_not_available": "\u0160ildytuvas nepasiekiamas. Pabandykite i\u0161 naujo nustatyti \u0161ildytuv\u0105 paspausdami + ir OK kelias sekundes.",
"heater_not_found": "\u0160ildytuvas nerastas. Pabandykite \u0161ildytuv\u0105 laikyti ar\u010diau Home Assistant serverio"
},
"step": {
"cloud": {
"data": {
"account_id": "Paskyros ID",
"password": "Slapta\u017eodis"
}
},
"local": {
"data": {
"wifi_pswd": "Wifi slapta\u017eodis",
"wifi_ssid": "Wifi ssid"
},
"description": "I\u0161 naujo nustatykite \u0161ildytuv\u0105 spausdami + ir OK, kol ekrane pasirodys \u201eReset\u201c. Tada paspauskite ir palaikykite OK mygtuk\u0105 ant \u0161ildytuvo, kol prad\u0117s mirks\u0117ti m\u0117lyna lemput\u0117, prie\u0161 paspausdami Patvirtinti. \u0160ildytuvo konfig\u016bravimas gali u\u017etrukti kelet\u0105 minu\u010di\u0173."
},
"user": {
"data": {
"connection_type": "Pasirinkite ry\u0161io tip\u0105"
},
"description": "Pasirinkite ry\u0161io tip\u0105. Vietiniams reikalingi \u0161ildytuvai su \u201eBluetooth\u201c."
}
}
}
}

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Apparaat is al geconfigureerd" "already_configured": "Apparaat is al geconfigureerd",
"heater_not_available": "Verwarming niet aanwezig. Probeer de kachel te resetten door enkele seconden op + en OK te drukken.",
"heater_not_found": "Verwarming niet gevonden. Probeer de verwarming dichter bij de Home Assistant-computer te plaatsen.",
"invalid_auth": "Ongeldige authenticatie"
}, },
"error": { "error": {
"cannot_connect": "Kan geen verbinding maken", "cannot_connect": "Kan geen verbinding maken",
"invalid_auth": "Ongeldige authenticatie" "invalid_auth": "Ongeldige authenticatie"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "Account ID",
"password": "Wachtwoord"
}
},
"local": {
"data": {
"wifi_pswd": "Wi-Fi Wachtwoord",
"wifi_ssid": "Wi-Fi SSID"
},
"description": "Reset de kachel door op + en OK te drukken totdat het display 'Reset' toont. Houd vervolgens de OK-knop op de verwarming ingedrukt totdat de blauwe led begint te knipperen voordat u op Verzenden drukt. Het configureren van de verwarming kan enkele minuten duren."
},
"user": { "user": {
"data": { "data": {
"account_id": "Account ID", "account_id": "Account ID",
"connection_type": "Selecteer verbindingstype",
"host": "Host", "host": "Host",
"password": "Wachtwoord" "password": "Wachtwoord"
} },
"description": "Selecteer verbindingstype. Lokaal vereist verwarming met bluetooth"
} }
} }
} }

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Enheten er allerede konfigurert" "already_configured": "Enheten er allerede konfigurert",
"heater_not_available": "Varmeapparat ikke tilgjengelig. Pr\u00f8v \u00e5 tilbakestille varmeapparatet ved \u00e5 trykke p\u00e5 + og OK i noen sekunder.",
"heater_not_found": "Fant ikke varmeovn. Pr\u00f8v \u00e5 flytte varmeren n\u00e6rmere Home Assistant-datamaskinen.",
"invalid_auth": "Ugyldig godkjenning"
}, },
"error": { "error": {
"cannot_connect": "Tilkobling mislyktes", "cannot_connect": "Tilkobling mislyktes",
"invalid_auth": "Ugyldig godkjenning" "invalid_auth": "Ugyldig godkjenning"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "Konto-ID",
"password": "Passord"
}
},
"local": {
"data": {
"wifi_pswd": "Wi-Fi-passord",
"wifi_ssid": "Wi-Fi SSID"
},
"description": "Tilbakestill varmeren ved \u00e5 trykke p\u00e5 + og OK til displayet viser 'Tilbakestill'. Trykk deretter p\u00e5 og hold inne OK-knappen p\u00e5 varmeren til den bl\u00e5 lampen begynner \u00e5 blinke f\u00f8r du trykker p\u00e5 Send. Det kan ta noen minutter \u00e5 konfigurere varmeapparatet."
},
"user": { "user": {
"data": { "data": {
"account_id": "Konto-ID", "account_id": "Konto-ID",
"connection_type": "Velg tilkoblingstype",
"host": "Vert", "host": "Vert",
"password": "Passord" "password": "Passord"
} },
"description": "Velg tilkoblingstype. Lokalt krever varmeovner med bluetooth"
} }
} }
} }

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane",
"heater_not_available": "Grzejnik niedost\u0119pny. Spr\u00f3buj go zresetowa\u0107, naciskaj\u0105c + i OK przez kilka sekund.",
"heater_not_found": "Nie znaleziono grzejnika. Spr\u00f3buj przesun\u0105\u0107 grzejnik bli\u017cej komputera z Home Assistant.",
"invalid_auth": "Niepoprawne uwierzytelnienie"
}, },
"error": { "error": {
"cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia",
"invalid_auth": "Niepoprawne uwierzytelnienie" "invalid_auth": "Niepoprawne uwierzytelnienie"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "Identyfikator konta",
"password": "Has\u0142o"
}
},
"local": {
"data": {
"wifi_pswd": "Has\u0142o WiFi",
"wifi_ssid": "SSID WiFi"
},
"description": "Zresetuj grzejnik, naciskaj\u0105c + i OK, a\u017c na wy\u015bwietlaczu pojawi si\u0119 \u201eReset\u201d. Nast\u0119pnie naci\u015bnij i przytrzymaj przycisk OK na grzejniku, a\u017c niebieska dioda zacznie miga\u0107 przed naci\u015bni\u0119ciem przycisku \"Zatwierd\u017a\". Konfiguracja grzejnika mo\u017ce zaj\u0105\u0107 kilka minut."
},
"user": { "user": {
"data": { "data": {
"account_id": "Identyfikator konta", "account_id": "Identyfikator konta",
"connection_type": "Wybierz typ po\u0142\u0105czenia",
"host": "Nazwa hosta lub adres IP", "host": "Nazwa hosta lub adres IP",
"password": "Has\u0142o" "password": "Has\u0142o"
} },
"description": "Wybierz typ po\u0142\u0105czenia. \"Lokalny\" wymaga grzejnik\u00f3w z bluetooth."
} }
} }
} }

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant." "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.",
"heater_not_available": "\u041d\u0430\u0433\u0440\u0435\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0435\u0433\u043e, \u043d\u0430\u0436\u0430\u0432 \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434 \u043a\u043d\u043e\u043f\u043a\u0438 + \u0438 \u041e\u041a.",
"heater_not_found": "\u041d\u0430\u0433\u0440\u0435\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u043e\u0431\u043e\u0433\u0440\u0435\u0432\u0430\u0442\u0435\u043b\u044c \u0431\u043b\u0438\u0436\u0435 \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 Home Assistant.",
"invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438."
}, },
"error": { "error": {
"cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.",
"invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438." "invalid_auth": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438."
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "ID \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438",
"password": "\u041f\u0430\u0440\u043e\u043b\u044c"
}
},
"local": {
"data": {
"wifi_pswd": "\u041f\u0430\u0440\u043e\u043b\u044c Wi-Fi",
"wifi_ssid": "Wi-Fi SSID"
},
"description": "\u0421\u0431\u0440\u043e\u0441\u044c\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0430\u0433\u0440\u0435\u0432\u0430\u0442\u0435\u043b\u044f, \u043d\u0430\u0436\u0438\u043c\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0438 + \u0438 OK, \u043f\u043e\u043a\u0430 \u043d\u0430 \u0434\u0438\u0441\u043f\u043b\u0435\u0435 \u043d\u0435 \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f \u043d\u0430\u0434\u043f\u0438\u0441\u044c 'Reset'. \u0417\u0430\u0442\u0435\u043c \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u0438 \u0443\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0439\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 OK \u043d\u0430 \u043d\u0430\u0433\u0440\u0435\u0432\u0430\u0442\u0435\u043b\u0435, \u043f\u043e\u043a\u0430 \u0441\u0438\u043d\u0438\u0439 \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u043d\u0435 \u043d\u0430\u0447\u043d\u0435\u0442 \u043c\u0438\u0433\u0430\u0442\u044c, \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u0437\u0434\u0435\u0441\u044c '\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c'. \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043d\u0430\u0433\u0440\u0435\u0432\u0430\u0442\u0435\u043b\u044f \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043d\u0443\u0442."
},
"user": { "user": {
"data": { "data": {
"account_id": "ID \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438", "account_id": "ID \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438",
"connection_type": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f",
"host": "\u0425\u043e\u0441\u0442", "host": "\u0425\u043e\u0441\u0442",
"password": "\u041f\u0430\u0440\u043e\u043b\u044c" "password": "\u041f\u0430\u0440\u043e\u043b\u044c"
} },
"description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u0414\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043e\u0431\u043e\u0433\u0440\u0435\u0432\u0430\u0442\u0435\u043b\u0438 \u0441 Bluetooth."
} }
} }
} }

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f" "already_configured": "Cihaz zaten yap\u0131land\u0131r\u0131lm\u0131\u015f",
"heater_not_available": "Is\u0131t\u0131c\u0131 mevcut de\u011fil. + ve OK tu\u015flar\u0131na birka\u00e7 saniye basarak \u0131s\u0131t\u0131c\u0131y\u0131 s\u0131f\u0131rlamay\u0131 deneyin.",
"heater_not_found": "Is\u0131t\u0131c\u0131 bulunamad\u0131. Is\u0131t\u0131c\u0131y\u0131 Home Assistant bilgisayar\u0131na yakla\u015ft\u0131rmay\u0131 deneyin.",
"invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama"
}, },
"error": { "error": {
"cannot_connect": "Ba\u011flanma hatas\u0131", "cannot_connect": "Ba\u011flanma hatas\u0131",
"invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama" "invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "Hesap Kimli\u011fi",
"password": "Parola"
}
},
"local": {
"data": {
"wifi_pswd": "Kablosuz a\u011f parolas\u0131",
"wifi_ssid": "Wifi A\u011f Ad\u0131"
},
"description": "Ekranda 'S\u0131f\u0131rla' g\u00f6r\u00fcnene kadar + ve OK tu\u015flar\u0131na basarak \u0131s\u0131t\u0131c\u0131y\u0131 s\u0131f\u0131rlay\u0131n. Ard\u0131ndan G\u00f6nder'e basmadan \u00f6nce mavi led yan\u0131p s\u00f6nmeye ba\u015flayana kadar \u0131s\u0131t\u0131c\u0131daki OK d\u00fc\u011fmesini bas\u0131l\u0131 tutun. Is\u0131t\u0131c\u0131y\u0131 yap\u0131land\u0131rmak birka\u00e7 dakika s\u00fcrebilir."
},
"user": { "user": {
"data": { "data": {
"account_id": "Hesap Kimli\u011fi", "account_id": "Hesap Kimli\u011fi",
"host": "Ana bilgisayar", "connection_type": "Ba\u011flant\u0131 t\u00fcr\u00fcn\u00fc se\u00e7in",
"host": "Sunucu",
"password": "Parola" "password": "Parola"
} },
"description": "Ba\u011flant\u0131 t\u00fcr\u00fcn\u00fc se\u00e7in. Yerel Bluetooth'lu \u0131s\u0131t\u0131c\u0131lar gerektirir"
} }
} }
} }

View File

@ -1,11 +1,27 @@
{ {
"config": { "config": {
"abort": {
"invalid_auth": "\u65e0\u6548\u7684\u6388\u6743"
},
"error": { "error": {
"cannot_connect": "\u8fde\u63a5\u5931\u8d25" "cannot_connect": "\u8fde\u63a5\u5931\u8d25"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "\u5e10\u6237ID",
"password": "\u5bc6\u7801"
}
},
"local": {
"data": {
"wifi_pswd": "Wi-Fi \u5bc6\u7801",
"wifi_ssid": "Wi-Fi \u540d\u79f0 (SSID)"
}
},
"user": { "user": {
"data": { "data": {
"connection_type": "\u9009\u62e9\u8fde\u63a5\u7c7b\u578b",
"password": "\u5bc6\u7801" "password": "\u5bc6\u7801"
} }
} }

View File

@ -1,19 +1,37 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" "already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210",
"heater_not_available": "\u627e\u4e0d\u5230\u52a0\u71b1\u5668\uff0c\u8acb\u8a66\u8457\u6309\u4f4f + \u8207 OK \u5e7e\u79d2\u9032\u884c\u91cd\u7f6e\u3002",
"heater_not_found": "\u627e\u4e0d\u5230\u52a0\u71b1\u5668\uff0c\u8acb\u8a66\u8457\u5c07\u52a0\u71b1\u5668\u5f80 Home Assistant \u4f3a\u670d\u5668\u9760\u8fd1\u3002",
"invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548"
}, },
"error": { "error": {
"cannot_connect": "\u9023\u7dda\u5931\u6557", "cannot_connect": "\u9023\u7dda\u5931\u6557",
"invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548"
}, },
"step": { "step": {
"cloud": {
"data": {
"account_id": "\u5e33\u865f ID",
"password": "\u5bc6\u78bc"
}
},
"local": {
"data": {
"wifi_pswd": "Wi-Fi \u5bc6\u78bc",
"wifi_ssid": "Wi-Fi SSID"
},
"description": "\u6309\u4f4f + \u8207 OK \u9032\u884c\u52a0\u71b1\u5668\u91cd\u7f6e\u3001\u76f4\u5230\u986f\u793a 'Reset'\u3002\u63a5\u8457\u6309\u4f4f\u52a0\u71b1\u5668\u4e0a\u7684 OK \u6309\u9215\u3001\u76f4\u5230\u85cd\u8272 LED \u71c8\u958b\u59cb\u9583\u720d\uff0c\u518d\u6309\u4e0b\u9001\u51fa\u3002\u52a0\u71b1\u5668\u8a2d\u5b9a\u53ef\u80fd\u9700\u8981\u5e7e\u5206\u9418\u6642\u9593\u3002"
},
"user": { "user": {
"data": { "data": {
"account_id": "\u5e33\u865f ID", "account_id": "\u5e33\u865f ID",
"connection_type": "\u9078\u64c7\u9023\u7dda\u985e\u578b",
"host": "\u4e3b\u6a5f\u7aef", "host": "\u4e3b\u6a5f\u7aef",
"password": "\u5bc6\u78bc" "password": "\u5bc6\u78bc"
} },
"description": "\u9078\u64c7\u9023\u7dda\u985e\u578b\u3002\u672c\u5730\u7aef\u5c07\u9700\u8981\u5177\u5099\u85cd\u82bd\u52a0\u71b1\u5668"
} }
} }
} }

View File

@ -18,7 +18,7 @@ from homeassistant.const import (
CONF_VERIFY_SSL, CONF_VERIFY_SSL,
Platform, Platform,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -72,31 +72,27 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.config_entries.async_setup_platforms(entry, PLATFORMS) hass.config_entries.async_setup_platforms(entry, PLATFORMS)
async def add_url(call) -> None: async def add_url(call: ServiceCall) -> None:
"""Service call to add a new filter subscription to AdGuard Home.""" """Service call to add a new filter subscription to AdGuard Home."""
await adguard.filtering.add_url( await adguard.filtering.add_url(
allowlist=False, name=call.data.get(CONF_NAME), url=call.data.get(CONF_URL) allowlist=False, name=call.data[CONF_NAME], url=call.data[CONF_URL]
) )
async def remove_url(call) -> None: async def remove_url(call: ServiceCall) -> None:
"""Service call to remove a filter subscription from AdGuard Home.""" """Service call to remove a filter subscription from AdGuard Home."""
await adguard.filtering.remove_url(allowlist=False, url=call.data.get(CONF_URL)) await adguard.filtering.remove_url(allowlist=False, url=call.data[CONF_URL])
async def enable_url(call) -> None: async def enable_url(call: ServiceCall) -> None:
"""Service call to enable a filter subscription in AdGuard Home.""" """Service call to enable a filter subscription in AdGuard Home."""
await adguard.filtering.enable_url(allowlist=False, url=call.data.get(CONF_URL)) await adguard.filtering.enable_url(allowlist=False, url=call.data[CONF_URL])
async def disable_url(call) -> None: async def disable_url(call: ServiceCall) -> None:
"""Service call to disable a filter subscription in AdGuard Home.""" """Service call to disable a filter subscription in AdGuard Home."""
await adguard.filtering.disable_url( await adguard.filtering.disable_url(allowlist=False, url=call.data[CONF_URL])
allowlist=False, url=call.data.get(CONF_URL)
)
async def refresh(call) -> None: async def refresh(call: ServiceCall) -> None:
"""Service call to refresh the filter subscriptions in AdGuard Home.""" """Service call to refresh the filter subscriptions in AdGuard Home."""
await adguard.filtering.refresh( await adguard.filtering.refresh(allowlist=False, force=call.data[CONF_FORCE])
allowlist=False, force=call.data.get(CONF_FORCE)
)
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_ADD_URL, add_url, schema=SERVICE_ADD_URL_SCHEMA DOMAIN, SERVICE_ADD_URL, add_url, schema=SERVICE_ADD_URL_SCHEMA

View File

@ -80,8 +80,8 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
adguard = AdGuardHome( adguard = AdGuardHome(
user_input[CONF_HOST], user_input[CONF_HOST],
port=user_input[CONF_PORT], port=user_input[CONF_PORT],
username=username, # type:ignore[arg-type] username=username,
password=password, # type:ignore[arg-type] password=password,
tls=user_input[CONF_SSL], tls=user_input[CONF_SSL],
verify_ssl=user_input[CONF_VERIFY_SSL], verify_ssl=user_input[CONF_VERIFY_SSL],
session=session, session=session,

View File

@ -3,7 +3,7 @@
"name": "AdGuard Home", "name": "AdGuard Home",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/adguard", "documentation": "https://www.home-assistant.io/integrations/adguard",
"requirements": ["adguardhome==0.5.0"], "requirements": ["adguardhome==0.5.1"],
"codeowners": ["@frenck"], "codeowners": ["@frenck"],
"iot_class": "local_polling" "iot_class": "local_polling"
} }

View File

@ -4,7 +4,7 @@
"existing_instance_updated": "\u0410\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0441\u044a\u0449\u0435\u0441\u0442\u0432\u0443\u0432\u0430\u0449\u0430\u0442\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." "existing_instance_updated": "\u0410\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0441\u044a\u0449\u0435\u0441\u0442\u0432\u0443\u0432\u0430\u0449\u0430\u0442\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f."
}, },
"error": { "error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435" "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {

View File

@ -2,6 +2,15 @@
"config": { "config": {
"error": { "error": {
"cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2"
},
"step": {
"hassio_confirm": {
"description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5 Home Assistant \u03ce\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03b5\u03c4\u03b1\u03b9 \u03bc\u03b5 \u03c4\u03bf AdGuard Home \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf: {addon};",
"title": "AdGuard Home \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 \u03c0\u03c1\u03cc\u03c3\u03b8\u03b5\u03c4\u03bf\u03c5 Home Assistant"
},
"user": {
"description": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03c4\u03b5 \u03c4\u03bf AdGuard Home \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c8\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03cd\u03b8\u03b7\u03c3\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf."
}
} }
} }
} }

View File

@ -9,8 +9,8 @@
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani Home Assistantot AdGuard Home-hoz val\u00f3 csatlakoz\u00e1shoz, {addon} kieg\u00e9sz\u00edt\u0151 \u00e1ltal?", "description": "Szeretn\u00e9 be\u00e1ll\u00edtani Home Assistantot AdGuard Home-hoz val\u00f3 csatlakoz\u00e1shoz, {addon} b\u0151v\u00edtm\u00e9ny \u00e1ltal?",
"title": "Az AdGuard Home a Home Assistant kieg\u00e9sz\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel" "title": "Az AdGuard Home a Home Assistant b\u0151v\u00edtm\u00e9nnyel"
}, },
"user": { "user": {
"data": { "data": {

View File

@ -19,7 +19,7 @@
"port": "Porta", "port": "Porta",
"ssl": "Utilizza un certificato SSL", "ssl": "Utilizza un certificato SSL",
"username": "Nome utente", "username": "Nome utente",
"verify_ssl": "Verificare il certificato SSL" "verify_ssl": "Verifica il certificato SSL"
}, },
"description": "Configura l'istanza di AdGuard Home per consentire il monitoraggio e il controllo." "description": "Configura l'istanza di AdGuard Home per consentire il monitoraggio e il controllo."
} }

View File

@ -9,7 +9,7 @@
}, },
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {
"description": "\u30a2\u30c9\u30aa\u30f3 {addon} \u304c\u3001\u63d0\u4f9b\u3059\u308bAdGuard Home\u306b\u63a5\u7d9a\u3059\u308b\u3088\u3046\u306bHome Assistant\u3092\u8a2d\u5b9a\u3057\u307e\u3059\u304b\uff1f", "description": "\u30a2\u30c9\u30aa\u30f3: {addon} \u306b\u3088\u3063\u3066\u63d0\u4f9b\u3055\u308c\u308b\u3001AdGuard Home\u306b\u63a5\u7d9a\u3059\u308b\u3088\u3046\u306bHome Assistant\u306e\u8a2d\u5b9a\u3092\u3057\u307e\u3059\u304b\uff1f",
"title": "Home Assistant\u30a2\u30c9\u30aa\u30f3\u7d4c\u7531\u306eAdGuard Home" "title": "Home Assistant\u30a2\u30c9\u30aa\u30f3\u7d4c\u7531\u306eAdGuard Home"
}, },
"user": { "user": {

View File

@ -14,7 +14,7 @@
}, },
"user": { "user": {
"data": { "data": {
"host": "Ana bilgisayar", "host": "Sunucu",
"password": "Parola", "password": "Parola",
"port": "Port", "port": "Port",
"ssl": "SSL sertifikas\u0131 kullan\u0131r", "ssl": "SSL sertifikas\u0131 kullan\u0131r",

View File

@ -13,7 +13,7 @@
"host": "\u4e3b\u673a\u5730\u5740", "host": "\u4e3b\u673a\u5730\u5740",
"password": "\u5bc6\u7801", "password": "\u5bc6\u7801",
"port": "\u7aef\u53e3", "port": "\u7aef\u53e3",
"ssl": "\u4f7f\u7528 SSL \u8bc1\u4e66\u51ed\u8bc1", "ssl": "\u4f7f\u7528 SSL \u8bc1\u4e66",
"username": "\u7528\u6237\u540d", "username": "\u7528\u6237\u540d",
"verify_ssl": "\u9a8c\u8bc1 SSL \u8bc1\u4e66\u51ed\u8bc1" "verify_ssl": "\u9a8c\u8bc1 SSL \u8bc1\u4e66\u51ed\u8bc1"
}, },

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