mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Merge pull request #65442 from home-assistant/rc
This commit is contained in:
commit
2f638a6b5e
@ -6,7 +6,7 @@ core: &core
|
||||
- homeassistant/helpers/*
|
||||
- homeassistant/package_constraints.txt
|
||||
- homeassistant/util/*
|
||||
- pyproject.yaml
|
||||
- pyproject.toml
|
||||
- requirements.txt
|
||||
- setup.cfg
|
||||
|
||||
@ -21,6 +21,7 @@ base_platforms: &base_platforms
|
||||
- homeassistant/components/climate/*
|
||||
- homeassistant/components/cover/*
|
||||
- homeassistant/components/device_tracker/*
|
||||
- homeassistant/components/diagnostics/*
|
||||
- homeassistant/components/fan/*
|
||||
- homeassistant/components/geo_location/*
|
||||
- homeassistant/components/humidifier/*
|
||||
@ -65,6 +66,7 @@ components: &components
|
||||
- homeassistant/components/homeassistant/**
|
||||
- homeassistant/components/image/*
|
||||
- homeassistant/components/input_boolean/*
|
||||
- homeassistant/components/input_button/*
|
||||
- homeassistant/components/input_datetime/*
|
||||
- homeassistant/components/input_number/*
|
||||
- homeassistant/components/input_select/*
|
||||
@ -101,17 +103,29 @@ tests: &tests
|
||||
- codecov.yaml
|
||||
- requirements_test_pre_commit.txt
|
||||
- requirements_test.txt
|
||||
- tests/auth/**
|
||||
- tests/backports/*
|
||||
- tests/common.py
|
||||
- tests/conftest.py
|
||||
- tests/hassfest/*
|
||||
- tests/helpers/*
|
||||
- tests/ignore_uncaught_exceptions.py
|
||||
- tests/mock/*
|
||||
- tests/scripts/*
|
||||
- tests/test_util/*
|
||||
- tests/testing_config/**
|
||||
- tests/util/**
|
||||
|
||||
other: &other
|
||||
- .github/workflows/*
|
||||
- homeassistant/scripts/**
|
||||
|
||||
requirements:
|
||||
- .github/workflows/*
|
||||
- homeassistant/package_constraints.txt
|
||||
- requirements*.txt
|
||||
- setup.py
|
||||
|
||||
any:
|
||||
- *base_platforms
|
||||
- *components
|
||||
|
114
.coveragerc
114
.coveragerc
@ -27,6 +27,7 @@ omit =
|
||||
homeassistant/components/adguard/sensor.py
|
||||
homeassistant/components/adguard/switch.py
|
||||
homeassistant/components/ads/*
|
||||
homeassistant/components/advantage_air/diagnostics.py
|
||||
homeassistant/components/aemet/weather_update_coordinator.py
|
||||
homeassistant/components/aftership/*
|
||||
homeassistant/components/agent_dvr/alarm_control_panel.py
|
||||
@ -55,6 +56,7 @@ omit =
|
||||
homeassistant/components/amcrest/*
|
||||
homeassistant/components/ampio/*
|
||||
homeassistant/components/android_ip_webcam/*
|
||||
homeassistant/components/androidtv/__init__.py
|
||||
homeassistant/components/anel_pwrctrl/switch.py
|
||||
homeassistant/components/anthemav/media_player.py
|
||||
homeassistant/components/apcupsd/*
|
||||
@ -65,7 +67,6 @@ omit =
|
||||
homeassistant/components/aquostv/media_player.py
|
||||
homeassistant/components/arcam_fmj/media_player.py
|
||||
homeassistant/components/arcam_fmj/__init__.py
|
||||
homeassistant/components/arduino/*
|
||||
homeassistant/components/arest/binary_sensor.py
|
||||
homeassistant/components/arest/sensor.py
|
||||
homeassistant/components/arest/switch.py
|
||||
@ -73,6 +74,9 @@ omit =
|
||||
homeassistant/components/arris_tg2492lg/*
|
||||
homeassistant/components/aruba/device_tracker.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_mbox/*
|
||||
homeassistant/components/asuswrt/__init__.py
|
||||
@ -91,7 +95,6 @@ omit =
|
||||
homeassistant/components/azure_service_bus/*
|
||||
homeassistant/components/baidu/tts.py
|
||||
homeassistant/components/balboa/__init__.py
|
||||
homeassistant/components/balboa/entity.py
|
||||
homeassistant/components/beewi_smartclim/sensor.py
|
||||
homeassistant/components/bbb_gpio/*
|
||||
homeassistant/components/bbox/device_tracker.py
|
||||
@ -118,6 +121,7 @@ omit =
|
||||
homeassistant/components/bmp280/sensor.py
|
||||
homeassistant/components/bmw_connected_drive/__init__.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/lock.py
|
||||
homeassistant/components/bmw_connected_drive/notify.py
|
||||
@ -176,7 +180,6 @@ omit =
|
||||
homeassistant/components/coolmaster/climate.py
|
||||
homeassistant/components/coolmaster/const.py
|
||||
homeassistant/components/cppm_tracker/device_tracker.py
|
||||
homeassistant/components/cpuspeed/sensor.py
|
||||
homeassistant/components/crownstone/__init__.py
|
||||
homeassistant/components/crownstone/const.py
|
||||
homeassistant/components/crownstone/listeners.py
|
||||
@ -203,7 +206,6 @@ omit =
|
||||
homeassistant/components/devolo_home_control/climate.py
|
||||
homeassistant/components/devolo_home_control/const.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/sensor.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_identify/image_processing.py
|
||||
homeassistant/components/dlink/switch.py
|
||||
homeassistant/components/dnsip/__init__.py
|
||||
homeassistant/components/dnsip/sensor.py
|
||||
homeassistant/components/dominos/*
|
||||
homeassistant/components/doods/*
|
||||
@ -254,6 +257,10 @@ omit =
|
||||
homeassistant/components/eight_sleep/*
|
||||
homeassistant/components/eliqonline/sensor.py
|
||||
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/emby/media_player.py
|
||||
homeassistant/components/emoncms/sensor.py
|
||||
@ -365,9 +372,11 @@ omit =
|
||||
homeassistant/components/freebox/switch.py
|
||||
homeassistant/components/fritz/__init__.py
|
||||
homeassistant/components/fritz/binary_sensor.py
|
||||
homeassistant/components/fritz/button.py
|
||||
homeassistant/components/fritz/common.py
|
||||
homeassistant/components/fritz/const.py
|
||||
homeassistant/components/fritz/device_tracker.py
|
||||
homeassistant/components/fritz/diagnostics.py
|
||||
homeassistant/components/fritz/sensor.py
|
||||
homeassistant/components/fritz/services.py
|
||||
homeassistant/components/fritz/switch.py
|
||||
@ -383,6 +392,8 @@ omit =
|
||||
homeassistant/components/garages_amsterdam/sensor.py
|
||||
homeassistant/components/gc100/*
|
||||
homeassistant/components/geniushub/*
|
||||
homeassistant/components/github/__init__.py
|
||||
homeassistant/components/github/coordinator.py
|
||||
homeassistant/components/github/sensor.py
|
||||
homeassistant/components/gitlab_ci/sensor.py
|
||||
homeassistant/components/gitter/sensor.py
|
||||
@ -391,7 +402,12 @@ omit =
|
||||
homeassistant/components/glances/sensor.py
|
||||
homeassistant/components/gntp/notify.py
|
||||
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_maps/device_tracker.py
|
||||
homeassistant/components/google_pubsub/__init__.py
|
||||
@ -448,6 +464,7 @@ omit =
|
||||
homeassistant/components/homematic/*
|
||||
homeassistant/components/home_plus_control/api.py
|
||||
homeassistant/components/home_plus_control/switch.py
|
||||
homeassistant/components/homewizard/diagnostics.py
|
||||
homeassistant/components/homeworks/*
|
||||
homeassistant/components/honeywell/__init__.py
|
||||
homeassistant/components/honeywell/climate.py
|
||||
@ -496,6 +513,10 @@ omit =
|
||||
homeassistant/components/insteon/schemas.py
|
||||
homeassistant/components/insteon/switch.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/intesishome/*
|
||||
homeassistant/components/ios/*
|
||||
@ -542,11 +563,7 @@ omit =
|
||||
homeassistant/components/knx/__init__.py
|
||||
homeassistant/components/knx/climate.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/schema.py
|
||||
homeassistant/components/kodi/__init__.py
|
||||
homeassistant/components/kodi/browse_media.py
|
||||
homeassistant/components/kodi/const.py
|
||||
@ -564,17 +581,17 @@ omit =
|
||||
homeassistant/components/lametric/*
|
||||
homeassistant/components/lannouncer/notify.py
|
||||
homeassistant/components/lastfm/sensor.py
|
||||
homeassistant/components/launch_library/__init__.py
|
||||
homeassistant/components/launch_library/const.py
|
||||
homeassistant/components/launch_library/diagnostics.py
|
||||
homeassistant/components/launch_library/sensor.py
|
||||
homeassistant/components/lcn/binary_sensor.py
|
||||
homeassistant/components/lcn/climate.py
|
||||
homeassistant/components/lcn/cover.py
|
||||
homeassistant/components/lcn/helpers.py
|
||||
homeassistant/components/lcn/light.py
|
||||
homeassistant/components/lcn/scene.py
|
||||
homeassistant/components/lcn/sensor.py
|
||||
homeassistant/components/lcn/services.py
|
||||
homeassistant/components/lcn/switch.py
|
||||
homeassistant/components/lg_netcast/media_player.py
|
||||
homeassistant/components/lg_soundbar/media_player.py
|
||||
homeassistant/components/life360/*
|
||||
@ -593,12 +610,14 @@ omit =
|
||||
homeassistant/components/logi_circle/sensor.py
|
||||
homeassistant/components/london_underground/sensor.py
|
||||
homeassistant/components/lookin/__init__.py
|
||||
homeassistant/components/lookin/coordinator.py
|
||||
homeassistant/components/lookin/entity.py
|
||||
homeassistant/components/lookin/models.py
|
||||
homeassistant/components/lookin/sensor.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/luftdaten/__init__.py
|
||||
homeassistant/components/luftdaten/sensor.py
|
||||
homeassistant/components/lupusec/*
|
||||
homeassistant/components/lutron/*
|
||||
@ -697,6 +716,7 @@ omit =
|
||||
homeassistant/components/nad/media_player.py
|
||||
homeassistant/components/nanoleaf/__init__.py
|
||||
homeassistant/components/nanoleaf/button.py
|
||||
homeassistant/components/nanoleaf/diagnostics.py
|
||||
homeassistant/components/nanoleaf/entity.py
|
||||
homeassistant/components/nanoleaf/light.py
|
||||
homeassistant/components/neato/__init__.py
|
||||
@ -716,7 +736,9 @@ omit =
|
||||
homeassistant/components/netgear_lte/*
|
||||
homeassistant/components/netio/switch.py
|
||||
homeassistant/components/neurio_energy/sensor.py
|
||||
homeassistant/components/nexia/entity.py
|
||||
homeassistant/components/nexia/climate.py
|
||||
homeassistant/components/nexia/switch.py
|
||||
homeassistant/components/nextcloud/*
|
||||
homeassistant/components/nfandroidtv/__init__.py
|
||||
homeassistant/components/nfandroidtv/notify.py
|
||||
@ -763,6 +785,8 @@ omit =
|
||||
homeassistant/components/onvif/event.py
|
||||
homeassistant/components/onvif/parsers.py
|
||||
homeassistant/components/onvif/sensor.py
|
||||
homeassistant/components/open_meteo/diagnostics.py
|
||||
homeassistant/components/open_meteo/weather.py
|
||||
homeassistant/components/opencv/*
|
||||
homeassistant/components/openevse/sensor.py
|
||||
homeassistant/components/openexchangerates/sensor.py
|
||||
@ -793,6 +817,22 @@ omit =
|
||||
homeassistant/components/orvibo/switch.py
|
||||
homeassistant/components/osramlightify/light.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/const.py
|
||||
homeassistant/components/ovo_energy/sensor.py
|
||||
@ -808,6 +848,7 @@ omit =
|
||||
homeassistant/components/philips_js/light.py
|
||||
homeassistant/components/philips_js/media_player.py
|
||||
homeassistant/components/philips_js/remote.py
|
||||
homeassistant/components/philips_js/switch.py
|
||||
homeassistant/components/pi_hole/sensor.py
|
||||
homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py
|
||||
homeassistant/components/pi4ioe5v9xxxx/switch.py
|
||||
@ -837,7 +878,6 @@ omit =
|
||||
homeassistant/components/progettihwsw/__init__.py
|
||||
homeassistant/components/progettihwsw/binary_sensor.py
|
||||
homeassistant/components/progettihwsw/switch.py
|
||||
homeassistant/components/prometheus/*
|
||||
homeassistant/components/prowl/notify.py
|
||||
homeassistant/components/proxmoxve/*
|
||||
homeassistant/components/proxy/camera.py
|
||||
@ -846,7 +886,6 @@ omit =
|
||||
homeassistant/components/pushbullet/sensor.py
|
||||
homeassistant/components/pushover/notify.py
|
||||
homeassistant/components/pushsafer/notify.py
|
||||
homeassistant/components/pvoutput/sensor.py
|
||||
homeassistant/components/pyload/sensor.py
|
||||
homeassistant/components/qbittorrent/sensor.py
|
||||
homeassistant/components/qnap/sensor.py
|
||||
@ -879,6 +918,7 @@ omit =
|
||||
homeassistant/components/rest/switch.py
|
||||
homeassistant/components/ridwell/__init__.py
|
||||
homeassistant/components/ridwell/sensor.py
|
||||
homeassistant/components/ridwell/switch.py
|
||||
homeassistant/components/ring/camera.py
|
||||
homeassistant/components/ripple/sensor.py
|
||||
homeassistant/components/rocketchat/notify.py
|
||||
@ -907,6 +947,7 @@ omit =
|
||||
homeassistant/components/sabnzbd/*
|
||||
homeassistant/components/saj/sensor.py
|
||||
homeassistant/components/samsungtv/bridge.py
|
||||
homeassistant/components/samsungtv/diagnostics.py
|
||||
homeassistant/components/satel_integra/*
|
||||
homeassistant/components/schluter/*
|
||||
homeassistant/components/scrape/sensor.py
|
||||
@ -925,6 +966,14 @@ omit =
|
||||
homeassistant/components/sense/sensor.py
|
||||
homeassistant/components/sensehat/light.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/serial/sensor.py
|
||||
homeassistant/components/serial_pm/sensor.py
|
||||
@ -984,10 +1033,12 @@ omit =
|
||||
homeassistant/components/solaredge/sensor.py
|
||||
homeassistant/components/solaredge_local/sensor.py
|
||||
homeassistant/components/solarlog/*
|
||||
homeassistant/components/solax/__init__.py
|
||||
homeassistant/components/solax/sensor.py
|
||||
homeassistant/components/soma/__init__.py
|
||||
homeassistant/components/soma/cover.py
|
||||
homeassistant/components/soma/sensor.py
|
||||
homeassistant/components/soma/utils.py
|
||||
homeassistant/components/somfy/__init__.py
|
||||
homeassistant/components/somfy/api.py
|
||||
homeassistant/components/somfy/climate.py
|
||||
@ -996,7 +1047,17 @@ omit =
|
||||
homeassistant/components/somfy/switch.py
|
||||
homeassistant/components/somfy_mylink/__init__.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/spc/*
|
||||
homeassistant/components/spider/*
|
||||
@ -1013,6 +1074,7 @@ omit =
|
||||
homeassistant/components/stiebel_eltron/*
|
||||
homeassistant/components/stookalert/__init__.py
|
||||
homeassistant/components/stookalert/binary_sensor.py
|
||||
homeassistant/components/stookalert/diagnostics.py
|
||||
homeassistant/components/stream/*
|
||||
homeassistant/components/streamlabswater/*
|
||||
homeassistant/components/suez_water/*
|
||||
@ -1041,8 +1103,12 @@ omit =
|
||||
homeassistant/components/synology_chat/notify.py
|
||||
homeassistant/components/synology_dsm/__init__.py
|
||||
homeassistant/components/synology_dsm/binary_sensor.py
|
||||
homeassistant/components/synology_dsm/button.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/service.py
|
||||
homeassistant/components/synology_dsm/switch.py
|
||||
homeassistant/components/synology_srm/device_tracker.py
|
||||
homeassistant/components/syslog/notify.py
|
||||
@ -1053,7 +1119,6 @@ omit =
|
||||
homeassistant/components/system_bridge/sensor.py
|
||||
homeassistant/components/systemmonitor/sensor.py
|
||||
homeassistant/components/tado/*
|
||||
homeassistant/components/tahoma/*
|
||||
homeassistant/components/tank_utility/sensor.py
|
||||
homeassistant/components/tankerkoenig/*
|
||||
homeassistant/components/tapsaff/binary_sensor.py
|
||||
@ -1141,6 +1206,7 @@ omit =
|
||||
homeassistant/components/transmission/errors.py
|
||||
homeassistant/components/travisci/sensor.py
|
||||
homeassistant/components/tuya/__init__.py
|
||||
homeassistant/components/tuya/alarm_control_panel.py
|
||||
homeassistant/components/tuya/base.py
|
||||
homeassistant/components/tuya/binary_sensor.py
|
||||
homeassistant/components/tuya/button.py
|
||||
@ -1148,6 +1214,7 @@ omit =
|
||||
homeassistant/components/tuya/climate.py
|
||||
homeassistant/components/tuya/const.py
|
||||
homeassistant/components/tuya/cover.py
|
||||
homeassistant/components/tuya/diagnostics.py
|
||||
homeassistant/components/tuya/fan.py
|
||||
homeassistant/components/tuya/humidifier.py
|
||||
homeassistant/components/tuya/light.py
|
||||
@ -1175,7 +1242,11 @@ omit =
|
||||
homeassistant/components/upnp/*
|
||||
homeassistant/components/upc_connect/*
|
||||
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/velbus/__init__.py
|
||||
homeassistant/components/velbus/binary_sensor.py
|
||||
@ -1195,6 +1266,7 @@ omit =
|
||||
homeassistant/components/verisure/binary_sensor.py
|
||||
homeassistant/components/verisure/camera.py
|
||||
homeassistant/components/verisure/coordinator.py
|
||||
homeassistant/components/verisure/diagnostics.py
|
||||
homeassistant/components/verisure/lock.py
|
||||
homeassistant/components/verisure/sensor.py
|
||||
homeassistant/components/verisure/switch.py
|
||||
@ -1204,9 +1276,11 @@ omit =
|
||||
homeassistant/components/vesync/const.py
|
||||
homeassistant/components/vesync/fan.py
|
||||
homeassistant/components/vesync/light.py
|
||||
homeassistant/components/vesync/sensor.py
|
||||
homeassistant/components/vesync/switch.py
|
||||
homeassistant/components/viaggiatreno/sensor.py
|
||||
homeassistant/components/vicare/binary_sensor.py
|
||||
homeassistant/components/vicare/button.py
|
||||
homeassistant/components/vicare/climate.py
|
||||
homeassistant/components/vicare/const.py
|
||||
homeassistant/components/vicare/__init__.py
|
||||
@ -1234,8 +1308,6 @@ omit =
|
||||
homeassistant/components/waze_travel_time/__init__.py
|
||||
homeassistant/components/waze_travel_time/helpers.py
|
||||
homeassistant/components/waze_travel_time/sensor.py
|
||||
homeassistant/components/webostv/*
|
||||
homeassistant/components/whois/sensor.py
|
||||
homeassistant/components/wiffi/*
|
||||
homeassistant/components/wirelesstag/*
|
||||
homeassistant/components/wolflink/__init__.py
|
||||
@ -1284,11 +1356,15 @@ omit =
|
||||
homeassistant/components/xs1/*
|
||||
homeassistant/components/yale_smart_alarm/__init__.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/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/media_player.py
|
||||
homeassistant/components/yamaha_musiccast/number.py
|
||||
homeassistant/components/yamaha_musiccast/select.py
|
||||
homeassistant/components/yandex_transport/*
|
||||
homeassistant/components/yeelightsunflower/light.py
|
||||
homeassistant/components/yi/camera.py
|
||||
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -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.
|
||||
|
||||
[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
|
||||
|
27
.github/workflows/builder.yml
vendored
27
.github/workflows/builder.yml
vendored
@ -10,11 +10,12 @@ on:
|
||||
|
||||
env:
|
||||
BUILD_TYPE: core
|
||||
DEFAULT_PYTHON: 3.8
|
||||
DEFAULT_PYTHON: 3.9
|
||||
|
||||
jobs:
|
||||
init:
|
||||
name: Initialize build
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
architectures: ${{ steps.info.outputs.architectures }}
|
||||
@ -62,7 +63,7 @@ jobs:
|
||||
name: Build PyPi package
|
||||
needs: init
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v2.4.0
|
||||
@ -75,8 +76,10 @@ jobs:
|
||||
- name: Build package
|
||||
shell: bash
|
||||
run: |
|
||||
pip install twine wheel
|
||||
python setup.py sdist bdist_wheel
|
||||
# Remove dist, build, and homeassistant.egg-info
|
||||
# when build locally for testing!
|
||||
pip install twine build
|
||||
python -m build
|
||||
|
||||
- name: Upload package
|
||||
shell: bash
|
||||
@ -88,6 +91,7 @@ jobs:
|
||||
|
||||
build_base:
|
||||
name: Build ${{ matrix.arch }} base core image
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: init
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@ -118,13 +122,13 @@ jobs:
|
||||
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1.10.0
|
||||
uses: docker/login-action@v1.12.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1.10.0
|
||||
uses: docker/login-action@v1.12.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@ -143,6 +147,7 @@ jobs:
|
||||
|
||||
build_machine:
|
||||
name: Build ${{ matrix.machine }} machine core image
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: ["init", "build_base"]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@ -182,13 +187,13 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1.10.0
|
||||
uses: docker/login-action@v1.12.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1.10.0
|
||||
uses: docker/login-action@v1.12.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@ -206,6 +211,7 @@ jobs:
|
||||
|
||||
publish_ha:
|
||||
name: Publish version files
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: ["init", "build_machine"]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@ -238,6 +244,7 @@ jobs:
|
||||
|
||||
publish_container:
|
||||
name: Publish meta container
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: ["init", "build_base"]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@ -245,13 +252,13 @@ jobs:
|
||||
uses: actions/checkout@v2.4.0
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1.10.0
|
||||
uses: docker/login-action@v1.12.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1.10.0
|
||||
uses: docker/login-action@v1.12.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
130
.github/workflows/ci.yaml
vendored
130
.github/workflows/ci.yaml
vendored
@ -10,10 +10,13 @@ on:
|
||||
pull_request: ~
|
||||
|
||||
env:
|
||||
CACHE_VERSION: 3
|
||||
DEFAULT_PYTHON: 3.8
|
||||
CACHE_VERSION: 5
|
||||
PIP_CACHE_VERSION: 1
|
||||
DEFAULT_PYTHON: 3.9
|
||||
PRE_COMMIT_CACHE: ~/.cache/pre-commit
|
||||
PIP_CACHE: /tmp/pip-cache
|
||||
SQLALCHEMY_WARN_20: 1
|
||||
PYTHONASYNCIODEBUG: 1
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
@ -33,6 +36,7 @@ jobs:
|
||||
tests_glob: ${{ steps.info.outputs.tests_glob }}
|
||||
test_groups: ${{ steps.info.outputs.test_groups }}
|
||||
test_group_count: ${{ steps.info.outputs.test_group_count }}
|
||||
requirements: ${{ steps.core.outputs.requirements }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
@ -129,6 +133,7 @@ jobs:
|
||||
prepare-base:
|
||||
name: Prepare base dependencies
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
outputs:
|
||||
python-key: ${{ steps.generate-python-key.outputs.key }}
|
||||
pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }}
|
||||
@ -147,6 +152,11 @@ jobs:
|
||||
hashFiles('requirements.txt') }}-${{
|
||||
hashFiles('requirements_test.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
|
||||
id: cache-venv
|
||||
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') }}-
|
||||
# ${{ 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
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
pip install -U "pip<20.3" setuptools
|
||||
pip install -r requirements.txt -r requirements_test.txt
|
||||
python --version
|
||||
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
|
||||
id: generate-pre-commit-key
|
||||
run: >-
|
||||
@ -238,7 +259,8 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
. 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:
|
||||
name: Check flake8
|
||||
@ -290,7 +312,8 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
. 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:
|
||||
name: Check isort
|
||||
@ -380,7 +403,8 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
. 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
|
||||
run: |
|
||||
@ -436,7 +460,8 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
. 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:
|
||||
name: Check hassfest
|
||||
@ -444,7 +469,7 @@ jobs:
|
||||
needs: prepare-tests
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
python-version: [3.9]
|
||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
@ -498,9 +523,10 @@ jobs:
|
||||
prepare-tests:
|
||||
name: Prepare tests for Python ${{ matrix.python-version }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8, 3.9]
|
||||
python-version: [3.9]
|
||||
outputs:
|
||||
python-key: ${{ steps.generate-python-key.outputs.key }}
|
||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
||||
@ -514,6 +540,11 @@ jobs:
|
||||
hashFiles('requirements_test.txt') }}-${{
|
||||
hashFiles('requirements_all.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
|
||||
id: cache-venv
|
||||
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') }}-
|
||||
# ${{ 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
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
@ -540,20 +581,22 @@ jobs:
|
||||
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
pip install -U "pip<20.3" setuptools wheel
|
||||
pip install -r requirements_all.txt
|
||||
pip install -r requirements_test.txt
|
||||
python --version
|
||||
pip install --cache-dir=$PIP_CACHE -U "pip<20.3" setuptools wheel
|
||||
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt
|
||||
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt
|
||||
pip install -e .
|
||||
|
||||
pylint:
|
||||
name: Check pylint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
needs:
|
||||
- changes
|
||||
- prepare-tests
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
python-version: [3.9]
|
||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
@ -577,12 +620,14 @@ jobs:
|
||||
if: needs.changes.outputs.test_full_suite == 'true'
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pylint homeassistant
|
||||
- name: Run pylint (partially)
|
||||
if: needs.changes.outputs.test_full_suite == 'false'
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pylint homeassistant/components/${{ needs.changes.outputs.integrations_glob }}
|
||||
|
||||
mypy:
|
||||
@ -593,7 +638,7 @@ jobs:
|
||||
- prepare-tests
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
python-version: [3.9]
|
||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
@ -617,14 +662,48 @@ jobs:
|
||||
if: needs.changes.outputs.test_full_suite == 'true'
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
mypy homeassistant
|
||||
- name: Run mypy (partially)
|
||||
if: needs.changes.outputs.test_full_suite == 'false'
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
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:
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.changes.outputs.test_full_suite == 'true' || needs.changes.outputs.tests_glob
|
||||
@ -641,7 +720,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
group: ${{ fromJson(needs.changes.outputs.test_groups) }}
|
||||
python-version: [3.8, 3.9]
|
||||
python-version: [3.9]
|
||||
name: >-
|
||||
Run tests Python ${{ matrix.python-version }} (${{ matrix.group }})
|
||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
||||
@ -675,8 +754,10 @@ jobs:
|
||||
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
||||
- name: Run pytest (fully)
|
||||
if: needs.changes.outputs.test_full_suite == 'true'
|
||||
timeout-minutes: 45
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
python3 -X dev -m pytest \
|
||||
-qq \
|
||||
--timeout=9 \
|
||||
@ -691,9 +772,18 @@ jobs:
|
||||
-p no:sugar \
|
||||
tests
|
||||
- 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: |
|
||||
. 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 \
|
||||
-qq \
|
||||
--timeout=9 \
|
||||
@ -708,9 +798,11 @@ jobs:
|
||||
-p no:sugar \
|
||||
tests/components/${{ matrix.group }}
|
||||
- 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: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
python3 -X dev -m pytest \
|
||||
-qq \
|
||||
--timeout=9 \
|
||||
@ -722,7 +814,7 @@ jobs:
|
||||
-p no:sugar \
|
||||
tests/components/${{ matrix.group }}
|
||||
- name: Upload coverage artifact
|
||||
uses: actions/upload-artifact@v2.2.4
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
||||
path: coverage.xml
|
||||
|
1
.github/workflows/lock.yml
vendored
1
.github/workflows/lock.yml
vendored
@ -7,6 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v3
|
||||
|
5
.github/workflows/translations.yaml
vendored
5
.github/workflows/translations.yaml
vendored
@ -12,11 +12,12 @@ on:
|
||||
- "**strings.json"
|
||||
|
||||
env:
|
||||
DEFAULT_PYTHON: 3.8
|
||||
DEFAULT_PYTHON: 3.9
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
name: Upload
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
@ -35,7 +36,7 @@ jobs:
|
||||
download:
|
||||
name: Download
|
||||
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
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
|
17
.github/workflows/wheels.yml
vendored
17
.github/workflows/wheels.yml
vendored
@ -16,6 +16,7 @@ on:
|
||||
jobs:
|
||||
init:
|
||||
name: Initialize wheels builder
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
architectures: ${{ steps.info.outputs.architectures }}
|
||||
@ -42,22 +43,27 @@ jobs:
|
||||
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
|
||||
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=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
|
||||
|
||||
- name: Upload env_file
|
||||
uses: actions/upload-artifact@v2.2.4
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
with:
|
||||
name: env_file
|
||||
path: ./.env_file
|
||||
|
||||
- name: Upload requirements_diff
|
||||
uses: actions/upload-artifact@v2.2.4
|
||||
uses: actions/upload-artifact@v2.3.1
|
||||
with:
|
||||
name: requirements_diff
|
||||
path: ./requirements_diff.txt
|
||||
|
||||
core:
|
||||
name: Build wheels with ${{ matrix.tag }} (${{ matrix.arch }}) for core
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: init
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@ -81,7 +87,7 @@ jobs:
|
||||
name: requirements_diff
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2021.07.0
|
||||
uses: home-assistant/wheels@2022.01.2
|
||||
with:
|
||||
tag: ${{ matrix.tag }}
|
||||
arch: ${{ matrix.arch }}
|
||||
@ -98,6 +104,7 @@ jobs:
|
||||
|
||||
integrations:
|
||||
name: Build wheels with ${{ matrix.tag }} (${{ matrix.arch }}) for integrations
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: init
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@ -150,7 +157,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2021.07.0
|
||||
uses: home-assistant/wheels@2022.01.2
|
||||
with:
|
||||
tag: ${{ matrix.tag }}
|
||||
arch: ${{ matrix.arch }}
|
||||
@ -160,7 +167,7 @@ jobs:
|
||||
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"
|
||||
pip: "Cython;numpy;scikit-build"
|
||||
skip-binary: aiohttp
|
||||
skip-binary: aiohttp,grpcio
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: 'requirements_diff.txt'
|
||||
requirements: "requirements_all.txt"
|
||||
|
@ -1,11 +1,11 @@
|
||||
repos:
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.29.0
|
||||
rev: v2.31.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
args: [--py39-plus]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 21.11b1
|
||||
rev: 21.12b0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
@ -13,11 +13,11 @@ repos:
|
||||
- --quiet
|
||||
files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.0.0
|
||||
rev: v2.1.0
|
||||
hooks:
|
||||
- id: codespell
|
||||
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"
|
||||
- --quiet-level=2
|
||||
exclude_types: [csv, json]
|
||||
@ -32,7 +32,7 @@ repos:
|
||||
- flake8-docstrings==1.6.0
|
||||
- pydocstyle==6.1.1
|
||||
- flake8-comprehensions==3.7.0
|
||||
- flake8-noqa==1.2.0
|
||||
- flake8-noqa==1.2.1
|
||||
- mccabe==0.6.1
|
||||
files: ^(homeassistant|script|tests)/.+\.py$
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
@ -95,17 +95,30 @@ repos:
|
||||
types: [python]
|
||||
require_serial: true
|
||||
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
|
||||
name: gen_requirements_all
|
||||
entry: script/run-in-env.sh python3 -m script.gen_requirements_all
|
||||
pass_filenames: false
|
||||
language: script
|
||||
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
|
||||
name: hassfest
|
||||
entry: script/run-in-env.sh python3 -m script.hassfest
|
||||
pass_filenames: false
|
||||
language: script
|
||||
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)$
|
||||
|
@ -2,7 +2,31 @@
|
||||
# If component is fully covered with type annotations, please add it here
|
||||
# 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.abode.*
|
||||
homeassistant.components.acer_projector.*
|
||||
homeassistant.components.accuweather.*
|
||||
homeassistant.components.actiontec.*
|
||||
@ -17,6 +41,7 @@ homeassistant.components.ambee.*
|
||||
homeassistant.components.ambient_station.*
|
||||
homeassistant.components.amcrest.*
|
||||
homeassistant.components.ampio.*
|
||||
homeassistant.components.aseko_pool_live.*
|
||||
homeassistant.components.automation.*
|
||||
homeassistant.components.binary_sensor.*
|
||||
homeassistant.components.bluetooth_tracker.*
|
||||
@ -24,12 +49,14 @@ homeassistant.components.bmw_connected_drive.*
|
||||
homeassistant.components.bond.*
|
||||
homeassistant.components.braviatv.*
|
||||
homeassistant.components.brother.*
|
||||
homeassistant.components.browser.*
|
||||
homeassistant.components.button.*
|
||||
homeassistant.components.calendar.*
|
||||
homeassistant.components.camera.*
|
||||
homeassistant.components.canary.*
|
||||
homeassistant.components.cover.*
|
||||
homeassistant.components.crownstone.*
|
||||
homeassistant.components.cpuspeed.*
|
||||
homeassistant.components.device_automation.*
|
||||
homeassistant.components.device_tracker.*
|
||||
homeassistant.components.devolo_home_control.*
|
||||
@ -60,10 +87,12 @@ homeassistant.components.group.*
|
||||
homeassistant.components.guardian.*
|
||||
homeassistant.components.history.*
|
||||
homeassistant.components.homeassistant.triggers.event
|
||||
homeassistant.components.homewizard.*
|
||||
homeassistant.components.http.*
|
||||
homeassistant.components.huawei_lte.*
|
||||
homeassistant.components.hyperion.*
|
||||
homeassistant.components.image_processing.*
|
||||
homeassistant.components.input_button.*
|
||||
homeassistant.components.input_select.*
|
||||
homeassistant.components.integration.*
|
||||
homeassistant.components.iqvia.*
|
||||
@ -71,11 +100,13 @@ homeassistant.components.jellyfin.*
|
||||
homeassistant.components.jewish_calendar.*
|
||||
homeassistant.components.knx.*
|
||||
homeassistant.components.kraken.*
|
||||
homeassistant.components.lametric.*
|
||||
homeassistant.components.lcn.*
|
||||
homeassistant.components.light.*
|
||||
homeassistant.components.local_ip.*
|
||||
homeassistant.components.lock.*
|
||||
homeassistant.components.lookin.*
|
||||
homeassistant.components.luftdaten.*
|
||||
homeassistant.components.mailbox.*
|
||||
homeassistant.components.media_player.*
|
||||
homeassistant.components.modbus.*
|
||||
@ -89,15 +120,20 @@ homeassistant.components.nest.*
|
||||
homeassistant.components.netatmo.*
|
||||
homeassistant.components.network.*
|
||||
homeassistant.components.nfandroidtv.*
|
||||
homeassistant.components.nissan_leaf.*
|
||||
homeassistant.components.no_ip.*
|
||||
homeassistant.components.notify.*
|
||||
homeassistant.components.notion.*
|
||||
homeassistant.components.number.*
|
||||
homeassistant.components.oncue.*
|
||||
homeassistant.components.onewire.*
|
||||
homeassistant.components.open_meteo.*
|
||||
homeassistant.components.openuv.*
|
||||
homeassistant.components.overkiz.*
|
||||
homeassistant.components.persistent_notification.*
|
||||
homeassistant.components.pi_hole.*
|
||||
homeassistant.components.proximity.*
|
||||
homeassistant.components.pvoutput.*
|
||||
homeassistant.components.rainmachine.*
|
||||
homeassistant.components.rdw.*
|
||||
homeassistant.components.recollect_waste.*
|
||||
@ -109,16 +145,21 @@ homeassistant.components.renault.*
|
||||
homeassistant.components.ridwell.*
|
||||
homeassistant.components.rituals_perfume_genie.*
|
||||
homeassistant.components.rpi_power.*
|
||||
homeassistant.components.rtsp_to_webrtc.*
|
||||
homeassistant.components.samsungtv.*
|
||||
homeassistant.components.scene.*
|
||||
homeassistant.components.select.*
|
||||
homeassistant.components.sensor.*
|
||||
homeassistant.components.senseme.*
|
||||
homeassistant.components.shelly.*
|
||||
homeassistant.components.simplisafe.*
|
||||
homeassistant.components.slack.*
|
||||
homeassistant.components.smhi.*
|
||||
homeassistant.components.sonos.media_player
|
||||
homeassistant.components.ssdp.*
|
||||
homeassistant.components.stookalert.*
|
||||
homeassistant.components.statistics.*
|
||||
homeassistant.components.steamist.*
|
||||
homeassistant.components.stream.*
|
||||
homeassistant.components.sun.*
|
||||
homeassistant.components.surepetcare.*
|
||||
@ -135,8 +176,11 @@ homeassistant.components.tplink.*
|
||||
homeassistant.components.tolo.*
|
||||
homeassistant.components.tractive.*
|
||||
homeassistant.components.tradfri.*
|
||||
homeassistant.components.trafikverket_train.*
|
||||
homeassistant.components.trafikverket_weatherstation.*
|
||||
homeassistant.components.tts.*
|
||||
homeassistant.components.twentemilieu.*
|
||||
homeassistant.components.unifiprotect.*
|
||||
homeassistant.components.upcloud.*
|
||||
homeassistant.components.uptime.*
|
||||
homeassistant.components.uptimerobot.*
|
||||
@ -148,7 +192,10 @@ homeassistant.components.wallbox.*
|
||||
homeassistant.components.water_heater.*
|
||||
homeassistant.components.watttime.*
|
||||
homeassistant.components.weather.*
|
||||
homeassistant.components.webostv.*
|
||||
homeassistant.components.websocket_api.*
|
||||
homeassistant.components.wemo.*
|
||||
homeassistant.components.whois.*
|
||||
homeassistant.components.zodiac.*
|
||||
homeassistant.components.zeroconf.*
|
||||
homeassistant.components.zone.*
|
||||
|
25
.vscode/tasks.json
vendored
25
.vscode/tasks.json
vendored
@ -16,9 +16,7 @@
|
||||
"label": "Pytest",
|
||||
"type": "shell",
|
||||
"command": "pytest --timeout=10 tests",
|
||||
"dependsOn": [
|
||||
"Install all Test Requirements"
|
||||
],
|
||||
"dependsOn": ["Install all Test Requirements"],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
@ -47,9 +45,7 @@
|
||||
"label": "Pylint",
|
||||
"type": "shell",
|
||||
"command": "pylint homeassistant",
|
||||
"dependsOn": [
|
||||
"Install all Requirements"
|
||||
],
|
||||
"dependsOn": ["Install all Requirements"],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
@ -64,7 +60,7 @@
|
||||
"label": "Code Coverage",
|
||||
"detail": "Generate code coverage report for a given integration.",
|
||||
"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": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
@ -116,6 +112,21 @@
|
||||
"panel": "new"
|
||||
},
|
||||
"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": [
|
||||
|
541
CODEOWNERS
541
CODEOWNERS
File diff suppressed because it is too large
Load Diff
@ -17,6 +17,7 @@ RUN \
|
||||
libswresample-dev \
|
||||
libavfilter-dev \
|
||||
libpcap-dev \
|
||||
libturbojpeg0 \
|
||||
git \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
@ -1,4 +1,3 @@
|
||||
include README.rst
|
||||
include LICENSE.md
|
||||
graft homeassistant
|
||||
recursive-exclude * *.py[co]
|
||||
|
@ -8,7 +8,7 @@ coverage:
|
||||
threshold: 0.09
|
||||
config-flows:
|
||||
target: auto
|
||||
threshold: 0
|
||||
threshold: 1
|
||||
paths:
|
||||
- homeassistant/components/*/config_flow.py
|
||||
patch:
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 151 KiB |
@ -4,16 +4,21 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import faulthandler
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
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"
|
||||
|
||||
|
||||
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:
|
||||
"""Validate that the right Python version is running."""
|
||||
if sys.version_info[:3] < REQUIRED_PYTHON_VER:
|
||||
@ -27,7 +32,7 @@ def validate_python() -> None:
|
||||
def ensure_config_path(config_dir: str) -> None:
|
||||
"""Validate the configuration directory."""
|
||||
# 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")
|
||||
|
||||
@ -61,10 +66,11 @@ def ensure_config_path(config_dir: str) -> None:
|
||||
def get_arguments() -> argparse.Namespace:
|
||||
"""Get parsed passed in arguments."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import homeassistant.config as config_util
|
||||
from . import config as config_util
|
||||
|
||||
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(
|
||||
@ -91,12 +97,6 @@ def get_arguments() -> argparse.Namespace:
|
||||
parser.add_argument(
|
||||
"-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(
|
||||
"--log-rotate-days",
|
||||
type=int,
|
||||
@ -112,122 +112,32 @@ def get_arguments() -> argparse.Namespace:
|
||||
parser.add_argument(
|
||||
"--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(
|
||||
"--script", nargs=argparse.REMAINDER, help="Run one of the embedded scripts"
|
||||
)
|
||||
if os.name == "posix":
|
||||
parser.add_argument(
|
||||
"--daemon", action="store_true", help="Run Home Assistant as daemon"
|
||||
"--ignore-os-check",
|
||||
action="store_true",
|
||||
help="Skips validation of operating system",
|
||||
)
|
||||
|
||||
arguments = parser.parse_args()
|
||||
if os.name != "posix" or arguments.debug or arguments.runner:
|
||||
setattr(arguments, "daemon", False)
|
||||
|
||||
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]:
|
||||
"""Collect path and arguments to re-execute the current hass instance."""
|
||||
if os.path.basename(sys.argv[0]) == "__main__.py":
|
||||
modulepath = os.path.dirname(sys.argv[0])
|
||||
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:
|
||||
"""Attempt to clean up state and start a new Home Assistant instance."""
|
||||
# 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.
|
||||
def check_threads() -> None:
|
||||
"""Check if there are any lingering threads."""
|
||||
try:
|
||||
nthreads = sum(
|
||||
thread.is_alive() and not thread.daemon for thread in threading.enumerate()
|
||||
@ -241,64 +151,27 @@ def try_to_restart() -> None:
|
||||
except AssertionError:
|
||||
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:
|
||||
"""Start Home Assistant."""
|
||||
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()
|
||||
|
||||
if not args.ignore_os_check:
|
||||
validate_os()
|
||||
|
||||
if args.script is not None:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from homeassistant import scripts
|
||||
from . import scripts
|
||||
|
||||
return scripts.run(args.script)
|
||||
|
||||
config_dir = os.path.abspath(os.path.join(os.getcwd(), args.config))
|
||||
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
|
||||
from homeassistant import runner
|
||||
from . import runner
|
||||
|
||||
runtime_conf = runner.RuntimeConfig(
|
||||
config_dir=config_dir,
|
||||
@ -321,8 +194,7 @@ def main() -> int:
|
||||
if os.path.getsize(fault_file_name) == 0:
|
||||
os.remove(fault_file_name)
|
||||
|
||||
if exit_code == RESTART_EXIT_CODE and not args.runner:
|
||||
try_to_restart()
|
||||
check_threads()
|
||||
|
||||
return exit_code
|
||||
|
||||
|
@ -6,7 +6,7 @@ from typing import Any
|
||||
|
||||
import async_timeout
|
||||
|
||||
from homeassistant.helpers.frame import report
|
||||
from .helpers.frame import report
|
||||
|
||||
|
||||
def timeout(
|
||||
@ -17,7 +17,7 @@ def timeout(
|
||||
loop = asyncio.get_running_loop()
|
||||
else:
|
||||
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,
|
||||
)
|
||||
if delay is not None:
|
||||
@ -30,7 +30,7 @@ def timeout(
|
||||
def current_task(loop: asyncio.AbstractEventLoop) -> asyncio.Task[Any] | None:
|
||||
"""Backwards compatible current_task."""
|
||||
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,
|
||||
)
|
||||
return asyncio.current_task()
|
||||
|
@ -3,8 +3,9 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Mapping
|
||||
from datetime import timedelta
|
||||
from typing import Any, Dict, Mapping, Optional, Tuple, cast
|
||||
from typing import Any, Optional, cast
|
||||
|
||||
import jwt
|
||||
|
||||
@ -21,9 +22,9 @@ from .providers import AuthProvider, LoginFlow, auth_provider_from_config
|
||||
EVENT_USER_ADDED = "user_added"
|
||||
EVENT_USER_REMOVED = "user_removed"
|
||||
|
||||
_MfaModuleDict = Dict[str, MultiFactorAuthModule]
|
||||
_ProviderKey = Tuple[str, Optional[str]]
|
||||
_ProviderDict = Dict[_ProviderKey, AuthProvider]
|
||||
_MfaModuleDict = dict[str, MultiFactorAuthModule]
|
||||
_ProviderKey = tuple[str, Optional[str]]
|
||||
_ProviderDict = dict[_ProviderKey, AuthProvider]
|
||||
|
||||
|
||||
class InvalidAuthError(Exception):
|
||||
|
@ -8,17 +8,20 @@ import hmac
|
||||
from logging import getLogger
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import models
|
||||
from .const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY, GROUP_ID_USER
|
||||
from .permissions import PermissionLookup, system_policies
|
||||
from .const import (
|
||||
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
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_KEY = "auth"
|
||||
GROUP_NAME_ADMIN = "Administrators"
|
||||
|
@ -133,7 +133,7 @@ async def auth_mfa_module_from_config(
|
||||
module = await _load_mfa_module(hass, module_name)
|
||||
|
||||
try:
|
||||
config = module.CONFIG_SCHEMA(config) # type: ignore
|
||||
config = module.CONFIG_SCHEMA(config)
|
||||
except vol.Invalid as err:
|
||||
_LOGGER.error(
|
||||
"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
|
||||
await requirements.async_process_requirements(
|
||||
hass, module_path, module.REQUIREMENTS # type: ignore
|
||||
hass, module_path, module.REQUIREMENTS
|
||||
)
|
||||
|
||||
processed.add(module_name)
|
||||
|
@ -7,7 +7,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
from typing import Any, Dict
|
||||
from typing import Any
|
||||
|
||||
import attr
|
||||
import voluptuous as vol
|
||||
@ -86,7 +86,7 @@ class NotifySetting:
|
||||
target: str | None = attr.ib(default=None)
|
||||
|
||||
|
||||
_UsersDict = Dict[str, NotifySetting]
|
||||
_UsersDict = dict[str, NotifySetting]
|
||||
|
||||
|
||||
@MULTI_FACTOR_AUTH_MODULES.register("notify")
|
||||
|
@ -8,13 +8,23 @@ import voluptuous as vol
|
||||
|
||||
from .const import CAT_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 .types import PolicyType
|
||||
from .util import test_all
|
||||
|
||||
POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA})
|
||||
|
||||
__all__ = [
|
||||
"POLICY_SCHEMA",
|
||||
"merge_policies",
|
||||
"PermissionLookup",
|
||||
"PolicyType",
|
||||
"AbstractPermissions",
|
||||
"PolicyPermissions",
|
||||
"OwnerPermissions",
|
||||
]
|
||||
|
||||
|
||||
class AbstractPermissions:
|
||||
"""Default permissions class."""
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""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.
|
||||
|
||||
|
@ -1,15 +1,16 @@
|
||||
"""Helpers to deal with permissions."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from functools import wraps
|
||||
from typing import Callable, Dict, Optional, cast
|
||||
from typing import Optional, cast
|
||||
|
||||
from .const import SUBCAT_ALL
|
||||
from .models import PermissionLookup
|
||||
from .types import CategoryType, SubCategoryDict, ValueType
|
||||
|
||||
LookupFunc = Callable[[PermissionLookup, SubCategoryDict, str], Optional[ValueType]]
|
||||
SubCatLookupType = Dict[str, LookupFunc]
|
||||
SubCatLookupType = dict[str, LookupFunc]
|
||||
|
||||
|
||||
def lookup_all(
|
||||
|
@ -22,8 +22,6 @@ from ..auth_store import AuthStore
|
||||
from ..const import MFA_SESSION_EXPIRATION
|
||||
from ..models import Credentials, RefreshToken, User, UserMeta
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
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)
|
||||
|
||||
try:
|
||||
config = module.CONFIG_SCHEMA(config) # type: ignore
|
||||
config = module.CONFIG_SCHEMA(config)
|
||||
except vol.Invalid as err:
|
||||
_LOGGER.error(
|
||||
"Invalid configuration for auth provider %s: %s",
|
||||
@ -174,8 +172,7 @@ async def load_auth_provider_module(
|
||||
elif provider in processed:
|
||||
return module
|
||||
|
||||
# https://github.com/python/mypy/issues/1424
|
||||
reqs = module.REQUIREMENTS # type: ignore
|
||||
reqs = module.REQUIREMENTS
|
||||
await requirements.async_process_requirements(
|
||||
hass, f"auth provider {provider}", reqs
|
||||
)
|
||||
|
@ -16,8 +16,6 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from ..models import Credentials, UserMeta
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
CONF_ARGS = "args"
|
||||
CONF_META = "meta"
|
||||
|
||||
|
@ -18,8 +18,6 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from ..models import Credentials, UserMeta
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_KEY = "auth_provider.homeassistant"
|
||||
|
||||
|
@ -14,8 +14,6 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from ..models import Credentials, UserMeta
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
USER_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("username"): str,
|
||||
|
@ -19,8 +19,6 @@ import homeassistant.helpers.config_validation as cv
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from ..models import Credentials, UserMeta
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
AUTH_PROVIDER_TYPE = "legacy_api_password"
|
||||
CONF_API_PASSWORD = "api_password"
|
||||
|
||||
|
@ -14,7 +14,7 @@ from ipaddress import (
|
||||
ip_address,
|
||||
ip_network,
|
||||
)
|
||||
from typing import Any, Dict, List, Union, cast
|
||||
from typing import Any, Union, cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@ -27,8 +27,6 @@ from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from .. import InvalidAuthError
|
||||
from ..models import Credentials, RefreshToken, UserMeta
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
IPAddress = Union[IPv4Address, IPv6Address]
|
||||
IPNetwork = Union[IPv4Network, IPv6Network]
|
||||
|
||||
@ -76,12 +74,12 @@ class TrustedNetworksAuthProvider(AuthProvider):
|
||||
@property
|
||||
def trusted_networks(self) -> list[IPNetwork]:
|
||||
"""Return trusted networks."""
|
||||
return cast(List[IPNetwork], self.config[CONF_TRUSTED_NETWORKS])
|
||||
return cast(list[IPNetwork], self.config[CONF_TRUSTED_NETWORKS])
|
||||
|
||||
@property
|
||||
def trusted_users(self) -> dict[IPNetwork, Any]:
|
||||
"""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
|
||||
def trusted_proxies(self) -> list[IPNetwork]:
|
||||
|
@ -14,7 +14,7 @@ class StrEnum(str, Enum):
|
||||
"""Create a new StrEnum instance."""
|
||||
if not isinstance(value, str):
|
||||
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:
|
||||
"""Return self.value."""
|
||||
|
@ -1,14 +1,18 @@
|
||||
"""Block I/O being done in asyncio."""
|
||||
"""Block blocking calls being done in asyncio."""
|
||||
from http.client import HTTPConnection
|
||||
import time
|
||||
|
||||
from homeassistant.util.async_ import protect_loop
|
||||
from .util.async_ import protect_loop
|
||||
|
||||
|
||||
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
|
||||
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.
|
||||
# Prevent files being opened inside the event loop
|
||||
# builtins.open = protect_loop(builtins.open)
|
||||
|
@ -15,27 +15,28 @@ from typing import TYPE_CHECKING, Any
|
||||
import voluptuous as vol
|
||||
import yarl
|
||||
|
||||
from homeassistant import config as conf_util, config_entries, core, loader
|
||||
from homeassistant.components import http
|
||||
from homeassistant.const import (
|
||||
from . import config as conf_util, config_entries, core, loader
|
||||
from .components import http, persistent_notification
|
||||
from .const import (
|
||||
REQUIRED_NEXT_PYTHON_HA_RELEASE,
|
||||
REQUIRED_NEXT_PYTHON_VER,
|
||||
SIGNAL_BOOTSTRAP_INTEGRATONS,
|
||||
)
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import area_registry, device_registry, entity_registry
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.setup import (
|
||||
from .exceptions import HomeAssistantError
|
||||
from .helpers import area_registry, device_registry, entity_registry
|
||||
from .helpers.dispatcher import async_dispatcher_send
|
||||
from .helpers.typing import ConfigType
|
||||
from .setup import (
|
||||
DATA_SETUP,
|
||||
DATA_SETUP_STARTED,
|
||||
DATA_SETUP_TIME,
|
||||
async_set_domains_to_be_loaded,
|
||||
async_setup_component,
|
||||
)
|
||||
from homeassistant.util.async_ import gather_with_concurrency
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util.logging import async_activate_log_queue_handler
|
||||
from homeassistant.util.package import async_get_user_site, is_virtual_env
|
||||
from .util import dt as dt_util
|
||||
from .util.async_ import gather_with_concurrency
|
||||
from .util.logging import async_activate_log_queue_handler
|
||||
from .util.package import async_get_user_site, is_virtual_env
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .runner import RuntimeConfig
|
||||
@ -49,7 +50,6 @@ DATA_LOGGING = "logging"
|
||||
|
||||
LOG_SLOW_STARTUP_INTERVAL = 60
|
||||
SLOW_STARTUP_CHECK_INTERVAL = 1
|
||||
SIGNAL_BOOTSTRAP_INTEGRATONS = "bootstrap_integrations"
|
||||
|
||||
STAGE_1_TIMEOUT = 120
|
||||
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])}."
|
||||
)
|
||||
_LOGGER.warning(msg)
|
||||
hass.components.persistent_notification.async_create(
|
||||
msg, "Python version", "python_version"
|
||||
persistent_notification.async_create(
|
||||
hass, msg, "Python version", "python_version"
|
||||
)
|
||||
|
||||
return hass
|
||||
|
@ -1,14 +1,17 @@
|
||||
"""Support for the Abode Security System."""
|
||||
from __future__ import annotations
|
||||
|
||||
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
|
||||
import abodepy.helpers.timeline as TIMELINE
|
||||
from requests.exceptions import ConnectTimeout, HTTPError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
ATTR_DATE,
|
||||
ATTR_DEVICE_ID,
|
||||
ATTR_ENTITY_ID,
|
||||
@ -18,14 +21,12 @@ from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant, ServiceCall
|
||||
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.entity import DeviceInfo, Entity
|
||||
|
||||
from .const import ATTRIBUTION, DEFAULT_CACHEDB, DOMAIN, LOGGER
|
||||
|
||||
CONF_POLLING = "polling"
|
||||
from .const import ATTRIBUTION, CONF_POLLING, DEFAULT_CACHEDB, DOMAIN, LOGGER
|
||||
|
||||
SERVICE_SETTINGS = "change_setting"
|
||||
SERVICE_CAPTURE_IMAGE = "capture_image"
|
||||
@ -43,7 +44,7 @@ ATTR_APP_TYPE = "app_type"
|
||||
ATTR_EVENT_BY = "event_by"
|
||||
ATTR_VALUE = "value"
|
||||
|
||||
CONFIG_SCHEMA = cv.deprecated(DOMAIN)
|
||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||
|
||||
CHANGE_SETTING_SCHEMA = vol.Schema(
|
||||
{vol.Required(ATTR_SETTING): cv.string, vol.Required(ATTR_VALUE): cv.string}
|
||||
@ -68,25 +69,25 @@ PLATFORMS = [
|
||||
class AbodeSystem:
|
||||
"""Abode System class."""
|
||||
|
||||
def __init__(self, abode, polling):
|
||||
def __init__(self, abode: Abode, polling: bool) -> None:
|
||||
"""Initialize the system."""
|
||||
self.abode = abode
|
||||
self.polling = polling
|
||||
self.entity_ids = set()
|
||||
self.entity_ids: set[str | None] = set()
|
||||
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."""
|
||||
username = config_entry.data.get(CONF_USERNAME)
|
||||
password = config_entry.data.get(CONF_PASSWORD)
|
||||
polling = config_entry.data.get(CONF_POLLING)
|
||||
username = entry.data[CONF_USERNAME]
|
||||
password = entry.data[CONF_PASSWORD]
|
||||
polling = entry.data[CONF_POLLING]
|
||||
cache = hass.config.path(DEFAULT_CACHEDB)
|
||||
|
||||
# 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(
|
||||
config_entry, unique_id=config_entry.data[CONF_USERNAME]
|
||||
entry, unique_id=entry.data[CONF_USERNAME]
|
||||
)
|
||||
|
||||
try:
|
||||
@ -102,7 +103,7 @@ async def async_setup_entry(hass, config_entry):
|
||||
|
||||
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 hass.async_add_executor_job(setup_hass_services, hass)
|
||||
@ -111,15 +112,13 @@ async def async_setup_entry(hass, config_entry):
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, config_entry):
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
hass.services.async_remove(DOMAIN, SERVICE_SETTINGS)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_CAPTURE_IMAGE)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_TRIGGER_AUTOMATION)
|
||||
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(
|
||||
config_entry, PLATFORMS
|
||||
)
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.stop)
|
||||
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
|
||||
|
||||
|
||||
def setup_hass_services(hass):
|
||||
def setup_hass_services(hass: HomeAssistant) -> None:
|
||||
"""Home Assistant services."""
|
||||
|
||||
def change_setting(call):
|
||||
def change_setting(call: ServiceCall) -> None:
|
||||
"""Change an Abode system setting."""
|
||||
setting = call.data.get(ATTR_SETTING)
|
||||
value = call.data.get(ATTR_VALUE)
|
||||
setting = call.data[ATTR_SETTING]
|
||||
value = call.data[ATTR_VALUE]
|
||||
|
||||
try:
|
||||
hass.data[DOMAIN].abode.set_setting(setting, value)
|
||||
except AbodeException as ex:
|
||||
LOGGER.warning(ex)
|
||||
|
||||
def capture_image(call):
|
||||
def capture_image(call: ServiceCall) -> None:
|
||||
"""Capture a new image."""
|
||||
entity_ids = call.data.get(ATTR_ENTITY_ID)
|
||||
entity_ids = call.data[ATTR_ENTITY_ID]
|
||||
|
||||
target_entities = [
|
||||
entity_id
|
||||
@ -157,9 +156,9 @@ def setup_hass_services(hass):
|
||||
signal = f"abode_camera_capture_{entity_id}"
|
||||
dispatcher_send(hass, signal)
|
||||
|
||||
def trigger_automation(call):
|
||||
def trigger_automation(call: ServiceCall) -> None:
|
||||
"""Trigger an Abode automation."""
|
||||
entity_ids = call.data.get(ATTR_ENTITY_ID)
|
||||
entity_ids = call.data[ATTR_ENTITY_ID]
|
||||
|
||||
target_entities = [
|
||||
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."""
|
||||
|
||||
def logout(event):
|
||||
def logout(event: Event) -> None:
|
||||
"""Logout of Abode."""
|
||||
if not hass.data[DOMAIN].polling:
|
||||
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."""
|
||||
|
||||
def event_callback(event, event_json):
|
||||
def event_callback(event: str, event_json: dict[str, str]) -> None:
|
||||
"""Handle an event callback from Abode."""
|
||||
data = {
|
||||
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."""
|
||||
|
||||
def __init__(self, data):
|
||||
_attr_attribution = ATTRIBUTION
|
||||
|
||||
def __init__(self, data: AbodeSystem) -> None:
|
||||
"""Initialize Abode entity."""
|
||||
self._data = data
|
||||
self._available = True
|
||||
self._attr_should_poll = data.polling
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return the available state."""
|
||||
return self._available
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Subscribe to Abode connection status updates."""
|
||||
await self.hass.async_add_executor_job(
|
||||
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)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Unsubscribe from Abode connection status updates."""
|
||||
await self.hass.async_add_executor_job(
|
||||
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."""
|
||||
self._available = self._data.abode.events.connected
|
||||
self._attr_available = self._data.abode.events.connected
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
|
||||
class AbodeDevice(AbodeEntity):
|
||||
"""Representation of an Abode device."""
|
||||
|
||||
def __init__(self, data, device):
|
||||
def __init__(self, data: AbodeSystem, device: AbodeDev) -> None:
|
||||
"""Initialize Abode device."""
|
||||
super().__init__(data)
|
||||
self._device = device
|
||||
self._attr_name = device.name
|
||||
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."""
|
||||
await super().async_added_to_hass()
|
||||
await self.hass.async_add_executor_job(
|
||||
@ -300,22 +295,21 @@ class AbodeDevice(AbodeEntity):
|
||||
self._update_callback,
|
||||
)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Unsubscribe from device events."""
|
||||
await super().async_will_remove_from_hass()
|
||||
await self.hass.async_add_executor_job(
|
||||
self._data.abode.events.remove_all_device_callbacks, self._device.device_id
|
||||
)
|
||||
|
||||
def update(self):
|
||||
def update(self) -> None:
|
||||
"""Update device state."""
|
||||
self._device.refresh()
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
def extra_state_attributes(self) -> dict[str, str]:
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
"device_id": self._device.device_id,
|
||||
"battery_low": self._device.battery_low,
|
||||
"no_response": self._device.no_response,
|
||||
@ -323,16 +317,16 @@ class AbodeDevice(AbodeEntity):
|
||||
}
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
def device_info(self) -> entity.DeviceInfo:
|
||||
"""Return device registry information for this entity."""
|
||||
return DeviceInfo(
|
||||
return entity.DeviceInfo(
|
||||
identifiers={(DOMAIN, self._device.device_id)},
|
||||
manufacturer="Abode",
|
||||
model=self._device.type,
|
||||
name=self._device.name,
|
||||
)
|
||||
|
||||
def _update_callback(self, device):
|
||||
def _update_callback(self, device: AbodeDev) -> None:
|
||||
"""Update the device state."""
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@ -340,17 +334,16 @@ class AbodeDevice(AbodeEntity):
|
||||
class AbodeAutomation(AbodeEntity):
|
||||
"""Representation of an Abode automation."""
|
||||
|
||||
def __init__(self, data, automation):
|
||||
def __init__(self, data: AbodeSystem, automation: AbodeAuto) -> None:
|
||||
"""Initialize for Abode automation."""
|
||||
super().__init__(data)
|
||||
self._automation = automation
|
||||
self._attr_name = automation.name
|
||||
self._attr_unique_id = automation.automation_id
|
||||
self._attr_extra_state_attributes = {
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
"type": "CUE automation",
|
||||
}
|
||||
|
||||
def update(self):
|
||||
def update(self) -> None:
|
||||
"""Update automation state."""
|
||||
self._automation.refresh()
|
||||
|
@ -1,25 +1,33 @@
|
||||
"""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
|
||||
from homeassistant.components.alarm_control_panel.const import (
|
||||
SUPPORT_ALARM_ARM_AWAY,
|
||||
SUPPORT_ALARM_ARM_HOME,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_DISARMED,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AbodeDevice
|
||||
from .const import ATTRIBUTION, DOMAIN
|
||||
from . import AbodeDevice, AbodeSystem
|
||||
from .const import DOMAIN
|
||||
|
||||
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."""
|
||||
data = hass.data[DOMAIN]
|
||||
data: AbodeSystem = hass.data[DOMAIN]
|
||||
async_add_entities(
|
||||
[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_code_arm_required = False
|
||||
_attr_supported_features = SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY
|
||||
_device: AbodeAl
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
def state(self) -> str | None:
|
||||
"""Return the state of the device."""
|
||||
if self._device.is_standby:
|
||||
state = STATE_ALARM_DISARMED
|
||||
elif self._device.is_away:
|
||||
state = STATE_ALARM_ARMED_AWAY
|
||||
elif self._device.is_home:
|
||||
state = STATE_ALARM_ARMED_HOME
|
||||
else:
|
||||
state = None
|
||||
return state
|
||||
return STATE_ALARM_DISARMED
|
||||
if self._device.is_away:
|
||||
return STATE_ALARM_ARMED_AWAY
|
||||
if self._device.is_home:
|
||||
return STATE_ALARM_ARMED_HOME
|
||||
return None
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
def alarm_disarm(self, code: str | None = None) -> None:
|
||||
"""Send disarm command."""
|
||||
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."""
|
||||
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."""
|
||||
self._device.set_away()
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
def extra_state_attributes(self) -> dict[str, str]:
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||
"device_id": self._device.device_id,
|
||||
"battery_backup": self._device.battery,
|
||||
"cellular_backup": self._device.is_cellular,
|
||||
|
@ -1,18 +1,26 @@
|
||||
"""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
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DEVICE_CLASS_WINDOW,
|
||||
BinarySensorDeviceClass,
|
||||
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
|
||||
|
||||
|
||||
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."""
|
||||
data = hass.data[DOMAIN]
|
||||
data: AbodeSystem = hass.data[DOMAIN]
|
||||
|
||||
device_types = [
|
||||
CONST.TYPE_CONNECTIVITY,
|
||||
@ -33,14 +41,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AbodeBinarySensor(AbodeDevice, BinarySensorEntity):
|
||||
"""A binary sensor implementation for Abode device."""
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True if the binary sensor is on."""
|
||||
return self._device.is_on
|
||||
_device: ABBinarySensor
|
||||
|
||||
@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."""
|
||||
if self._device.get_value("is_window") == "1":
|
||||
return DEVICE_CLASS_WINDOW
|
||||
return self._device.generic_type
|
||||
return BinarySensorDeviceClass.WINDOW
|
||||
return cast(str, self._device.generic_type)
|
||||
|
@ -2,25 +2,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
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 requests
|
||||
from requests.models import Response
|
||||
|
||||
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.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from . import AbodeDevice
|
||||
from . import AbodeDevice, AbodeSystem
|
||||
from .const import DOMAIN, LOGGER
|
||||
|
||||
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."""
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
data: AbodeSystem = hass.data[DOMAIN]
|
||||
entities = []
|
||||
|
||||
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):
|
||||
"""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."""
|
||||
AbodeDevice.__init__(self, data, device)
|
||||
Camera.__init__(self)
|
||||
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."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
@ -52,17 +61,17 @@ class AbodeCamera(AbodeDevice, Camera):
|
||||
signal = f"abode_camera_capture_{self.entity_id}"
|
||||
self.async_on_remove(async_dispatcher_connect(self.hass, signal, self.capture))
|
||||
|
||||
def capture(self):
|
||||
def capture(self) -> bool:
|
||||
"""Request a new image capture."""
|
||||
return self._device.capture()
|
||||
return cast(bool, self._device.capture())
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def refresh_image(self):
|
||||
def refresh_image(self) -> None:
|
||||
"""Find a new image on the timeline."""
|
||||
if self._device.refresh_image():
|
||||
self.get_image()
|
||||
|
||||
def get_image(self):
|
||||
def get_image(self) -> None:
|
||||
"""Attempt to download the most recent capture."""
|
||||
if self._device.image_url:
|
||||
try:
|
||||
@ -86,21 +95,21 @@ class AbodeCamera(AbodeDevice, Camera):
|
||||
|
||||
return None
|
||||
|
||||
def turn_on(self):
|
||||
def turn_on(self) -> None:
|
||||
"""Turn on camera."""
|
||||
self._device.privacy_mode(False)
|
||||
|
||||
def turn_off(self):
|
||||
def turn_off(self) -> None:
|
||||
"""Turn off camera."""
|
||||
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."""
|
||||
self._device.update_image_location(capture)
|
||||
self.get_image()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if on."""
|
||||
return self._device.is_on
|
||||
return cast(bool, self._device.is_on)
|
||||
|
@ -1,5 +1,8 @@
|
||||
"""Config flow for the Abode Security System component."""
|
||||
from __future__ import annotations
|
||||
|
||||
from http import HTTPStatus
|
||||
from typing import Any, cast
|
||||
|
||||
from abodepy import Abode
|
||||
from abodepy.exceptions import AbodeAuthenticationException, AbodeException
|
||||
@ -9,11 +12,11 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
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_POLLING = "polling"
|
||||
|
||||
|
||||
class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
@ -21,7 +24,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
"""Initialize."""
|
||||
self.data_schema = {
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
@ -31,13 +34,13 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
vol.Required(CONF_MFA): str,
|
||||
}
|
||||
|
||||
self._cache = None
|
||||
self._mfa_code = None
|
||||
self._password = None
|
||||
self._polling = False
|
||||
self._username = None
|
||||
self._cache: str | None = None
|
||||
self._mfa_code: str | None = None
|
||||
self._password: str | None = None
|
||||
self._polling: bool = False
|
||||
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."""
|
||||
self._cache = self.hass.config.path(DEFAULT_CACHEDB)
|
||||
errors = {}
|
||||
@ -47,7 +50,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
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]:
|
||||
return await self.async_step_mfa()
|
||||
|
||||
@ -59,6 +62,9 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
else:
|
||||
errors = {"base": "cannot_connect"}
|
||||
|
||||
except (ConnectTimeout, HTTPError):
|
||||
errors = {"base": "cannot_connect"}
|
||||
|
||||
if errors:
|
||||
return self.async_show_form(
|
||||
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()
|
||||
|
||||
async def _async_abode_mfa_login(self):
|
||||
async def _async_abode_mfa_login(self) -> FlowResult:
|
||||
"""Handle multi-factor authentication (MFA) login with Abode."""
|
||||
try:
|
||||
# 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()
|
||||
|
||||
async def _async_create_entry(self):
|
||||
async def _async_create_entry(self) -> FlowResult:
|
||||
"""Create the config entry."""
|
||||
config_data = {
|
||||
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_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."""
|
||||
if self._async_current_entries():
|
||||
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")
|
||||
|
||||
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."""
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
@ -137,13 +149,15 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
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."""
|
||||
self._username = config[CONF_USERNAME]
|
||||
|
||||
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."""
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
|
@ -7,3 +7,4 @@ DOMAIN = "abode"
|
||||
ATTRIBUTION = "Data provided by goabode.com"
|
||||
|
||||
DEFAULT_CACHEDB = "abodepy_cache.pickle"
|
||||
CONF_POLLING = "polling"
|
||||
|
@ -1,15 +1,23 @@
|
||||
"""Support for Abode Security System covers."""
|
||||
from typing import Any
|
||||
|
||||
from abodepy.devices.cover import AbodeCover as AbodeCV
|
||||
import abodepy.helpers.constants as CONST
|
||||
|
||||
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
|
||||
|
||||
|
||||
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."""
|
||||
data = hass.data[DOMAIN]
|
||||
data: AbodeSystem = hass.data[DOMAIN]
|
||||
|
||||
entities = []
|
||||
|
||||
@ -22,15 +30,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AbodeCover(AbodeDevice, CoverEntity):
|
||||
"""Representation of an Abode cover."""
|
||||
|
||||
_device: AbodeCV
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
def is_closed(self) -> bool:
|
||||
"""Return true if cover is closed, else False."""
|
||||
return not self._device.is_open
|
||||
|
||||
def close_cover(self, **kwargs):
|
||||
def close_cover(self, **kwargs: Any) -> None:
|
||||
"""Issue close command to cover."""
|
||||
self._device.close_cover()
|
||||
|
||||
def open_cover(self, **kwargs):
|
||||
def open_cover(self, **kwargs: Any) -> None:
|
||||
"""Issue open command to cover."""
|
||||
self._device.open_cover()
|
||||
|
@ -1,6 +1,10 @@
|
||||
"""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
|
||||
|
||||
from homeassistant.components.light import (
|
||||
@ -12,18 +16,23 @@ from homeassistant.components.light import (
|
||||
SUPPORT_COLOR_TEMP,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util.color import (
|
||||
color_temperature_kelvin_to_mired,
|
||||
color_temperature_mired_to_kelvin,
|
||||
)
|
||||
|
||||
from . import AbodeDevice
|
||||
from . import AbodeDevice, AbodeSystem
|
||||
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."""
|
||||
data = hass.data[DOMAIN]
|
||||
data: AbodeSystem = hass.data[DOMAIN]
|
||||
|
||||
entities = []
|
||||
|
||||
@ -36,7 +45,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AbodeLight(AbodeDevice, LightEntity):
|
||||
"""Representation of an Abode light."""
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
_device: AbodeLT
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on the light."""
|
||||
if ATTR_COLOR_TEMP in kwargs and self._device.is_color_capable:
|
||||
self._device.set_color_temp(
|
||||
@ -56,40 +67,42 @@ class AbodeLight(AbodeDevice, LightEntity):
|
||||
|
||||
self._device.switch_on()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn off the light."""
|
||||
self._device.switch_off()
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if device is on."""
|
||||
return self._device.is_on
|
||||
return bool(self._device.is_on)
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
def brightness(self) -> int | None:
|
||||
"""Return the brightness of the light."""
|
||||
if self._device.is_dimmable and self._device.has_brightness:
|
||||
brightness = int(self._device.brightness)
|
||||
# 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)
|
||||
return ceil(brightness * 255 / 99.0)
|
||||
return 255 if brightness == 100 else ceil(brightness * 255 / 99.0)
|
||||
return None
|
||||
|
||||
@property
|
||||
def color_temp(self):
|
||||
def color_temp(self) -> int | None:
|
||||
"""Return the color temp of the light."""
|
||||
if self._device.has_color:
|
||||
return color_temperature_kelvin_to_mired(self._device.color_temp)
|
||||
return None
|
||||
|
||||
@property
|
||||
def hs_color(self):
|
||||
def hs_color(self) -> tuple[float, float] | None:
|
||||
"""Return the color of the light."""
|
||||
_hs = None
|
||||
if self._device.has_color:
|
||||
return self._device.color
|
||||
_hs = self._device.color
|
||||
return _hs
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
if self._device.is_dimmable and self._device.is_color_capable:
|
||||
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP
|
||||
|
@ -1,15 +1,23 @@
|
||||
"""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
|
||||
|
||||
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
|
||||
|
||||
|
||||
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."""
|
||||
data = hass.data[DOMAIN]
|
||||
data: AbodeSystem = hass.data[DOMAIN]
|
||||
|
||||
entities = []
|
||||
|
||||
@ -22,15 +30,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AbodeLock(AbodeDevice, LockEntity):
|
||||
"""Representation of an Abode lock."""
|
||||
|
||||
def lock(self, **kwargs):
|
||||
_device: AbodeLK
|
||||
|
||||
def lock(self, **kwargs: Any) -> None:
|
||||
"""Lock the device."""
|
||||
self._device.lock()
|
||||
|
||||
def unlock(self, **kwargs):
|
||||
def unlock(self, **kwargs: Any) -> None:
|
||||
"""Unlock the device."""
|
||||
self._device.unlock()
|
||||
|
||||
@property
|
||||
def is_locked(self):
|
||||
def is_locked(self) -> bool:
|
||||
"""Return true if device is on."""
|
||||
return self._device.is_locked
|
||||
return bool(self._device.is_locked)
|
||||
|
@ -1,40 +1,46 @@
|
||||
"""Support for Abode Security System sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
import abodepy.helpers.constants as CONST
|
||||
from typing import cast
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
|
||||
from homeassistant.const import (
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
from abodepy.devices.sensor import CONST, AbodeSensor as AbodeSense
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
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
|
||||
|
||||
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key=CONST.TEMP_STATUS_KEY,
|
||||
name="Temperature",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=CONST.HUMI_STATUS_KEY,
|
||||
name="Humidity",
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=CONST.LUX_STATUS_KEY,
|
||||
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."""
|
||||
data = hass.data[DOMAIN]
|
||||
data: AbodeSystem = hass.data[DOMAIN]
|
||||
|
||||
entities = []
|
||||
|
||||
@ -54,7 +60,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AbodeSensor(AbodeDevice, SensorEntity):
|
||||
"""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."""
|
||||
super().__init__(data, device)
|
||||
self.entity_description = description
|
||||
@ -68,11 +81,12 @@ class AbodeSensor(AbodeDevice, SensorEntity):
|
||||
self._attr_native_unit_of_measurement = device.lux_unit
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
def native_value(self) -> float | None:
|
||||
"""Return the state of the sensor."""
|
||||
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:
|
||||
return self._device.humidity
|
||||
return cast(float, self._device.humidity)
|
||||
if self.entity_description.key == CONST.LUX_STATUS_KEY:
|
||||
return self._device.lux
|
||||
return cast(float, self._device.lux)
|
||||
return None
|
||||
|
@ -1,10 +1,17 @@
|
||||
"""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.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
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
|
||||
|
||||
DEVICE_TYPES = [CONST.TYPE_SWITCH, CONST.TYPE_VALVE]
|
||||
@ -12,11 +19,13 @@ DEVICE_TYPES = [CONST.TYPE_SWITCH, CONST.TYPE_VALVE]
|
||||
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."""
|
||||
data = hass.data[DOMAIN]
|
||||
data: AbodeSystem = hass.data[DOMAIN]
|
||||
|
||||
entities = []
|
||||
entities: list[SwitchEntity] = []
|
||||
|
||||
for device_type in DEVICE_TYPES:
|
||||
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):
|
||||
"""Representation of an Abode switch."""
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
_device: AbodeSW
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on the device."""
|
||||
self._device.switch_on()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn off the device."""
|
||||
self._device.switch_off()
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if device is on."""
|
||||
return self._device.is_on
|
||||
return cast(bool, self._device.is_on)
|
||||
|
||||
|
||||
class AbodeAutomationSwitch(AbodeAutomation, SwitchEntity):
|
||||
@ -50,28 +61,28 @@ class AbodeAutomationSwitch(AbodeAutomation, SwitchEntity):
|
||||
|
||||
_attr_icon = ICON
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Set up trigger automation service."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
signal = f"abode_trigger_automation_{self.entity_id}"
|
||||
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."""
|
||||
if self._automation.enable(True):
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Disable the automation."""
|
||||
if self._automation.enable(False):
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def trigger(self):
|
||||
def trigger(self) -> None:
|
||||
"""Trigger the automation."""
|
||||
self._automation.trigger()
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if the automation is enabled."""
|
||||
return self._automation.is_enabled
|
||||
return bool(self._automation.is_enabled)
|
||||
|
@ -3,6 +3,11 @@
|
||||
"error": {
|
||||
"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"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -19,14 +19,14 @@
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"password": "Password",
|
||||
"username": "E-mail"
|
||||
"username": "Email"
|
||||
},
|
||||
"title": "Inserisci le tue informazioni di accesso Abode"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Password",
|
||||
"username": "E-mail"
|
||||
"username": "Email"
|
||||
},
|
||||
"title": "Inserisci le tue informazioni di accesso Abode"
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any, Dict
|
||||
from typing import Any
|
||||
|
||||
from accuweather import AccuWeather, ApiError, InvalidApiKeyError, RequestsExceededError
|
||||
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)
|
||||
|
||||
|
||||
class AccuWeatherDataUpdateCoordinator(DataUpdateCoordinator[Dict[str, Any]]):
|
||||
class AccuWeatherDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
"""Class to manage fetching AccuWeather data API."""
|
||||
|
||||
def __init__(
|
||||
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
|
||||
from homeassistant.components.sensor import SensorStateClass
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_CONDITION_CLEAR_NIGHT,
|
||||
ATTR_CONDITION_CLOUDY,
|
||||
@ -22,7 +22,6 @@ from homeassistant.components.weather import (
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
LENGTH_FEET,
|
||||
LENGTH_INCHES,
|
||||
LENGTH_METERS,
|
||||
@ -123,21 +122,21 @@ FORECAST_SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureMax",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel Temperature Max",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureMin",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel Temperature Min",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShadeMax",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel Temperature Shade Max",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
@ -145,7 +144,7 @@ FORECAST_SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShadeMin",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel Temperature Shade Min",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
@ -215,7 +214,7 @@ FORECAST_SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||
SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||
AccuWeatherSensorDescription(
|
||||
key="ApparentTemperature",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Apparent Temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
@ -241,7 +240,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="DewPoint",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Dew Point",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
@ -250,7 +249,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperature",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel Temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
@ -258,7 +257,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShade",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel Temperature Shade",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
@ -291,7 +290,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WetBulbTemperature",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Wet Bulb Temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
@ -300,7 +299,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindChillTemperature",
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Wind Chill Temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
|
@ -3,9 +3,9 @@ from __future__ import annotations
|
||||
|
||||
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.const import CONF_NAME, DEVICE_CLASS_TEMPERATURE
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
@ -107,7 +107,7 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state."""
|
||||
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"])
|
||||
if self.entity_description.key == "UVIndex":
|
||||
return cast(int, self._sensor_data["Value"])
|
||||
@ -117,7 +117,7 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
|
||||
return round(self._sensor_data[self._unit_system]["Value"])
|
||||
if self.entity_description.key == "PressureTendency":
|
||||
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"])
|
||||
if self.entity_description.key == "Precipitation":
|
||||
return cast(float, self._sensor_data[self._unit_system]["Value"])
|
||||
|
24
homeassistant/components/accuweather/translations/el.json
Normal file
24
homeassistant/components/accuweather/translations/el.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
@ -34,7 +34,7 @@
|
||||
},
|
||||
"system_health": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API Key",
|
||||
"api_key": "Chave da API",
|
||||
"latitude": "Latitude",
|
||||
"longitude": "Longitude",
|
||||
"name": "Nome"
|
||||
|
@ -5,13 +5,13 @@
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "\u9023\u7dda\u5931\u6557",
|
||||
"invalid_api_key": "API \u5bc6\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"
|
||||
"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 \u91d1\u9470\u3002"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API \u5bc6\u9470",
|
||||
"api_key": "API \u91d1\u9470",
|
||||
"latitude": "\u7def\u5ea6",
|
||||
"longitude": "\u7d93\u5ea6",
|
||||
"name": "\u540d\u7a31"
|
||||
@ -27,7 +27,7 @@
|
||||
"data": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ def setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Connect with serial port and return Acer Projector."""
|
||||
serial_port = config[CONF_FILENAME]
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""The Rollease Acmeda Automate integration."""
|
||||
|
||||
from homeassistant import config_entries, core
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .hub import PulseHub
|
||||
@ -11,26 +11,20 @@ CONF_HUBS = "hubs"
|
||||
PLATFORMS = [Platform.COVER, Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
|
||||
):
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Set up Rollease Acmeda Automate hub from a config entry."""
|
||||
hub = PulseHub(hass, config_entry)
|
||||
|
||||
if not await hub.async_setup():
|
||||
return False
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][config_entry.entry_id] = hub
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = hub
|
||||
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(
|
||||
hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
|
||||
):
|
||||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
hub = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
"""Support for Acmeda Roller Blinds."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.cover import (
|
||||
ATTR_POSITION,
|
||||
SUPPORT_CLOSE,
|
||||
@ -11,19 +13,25 @@ from homeassistant.components.cover import (
|
||||
SUPPORT_STOP_TILT,
|
||||
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.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .base import AcmedaBase
|
||||
from .const import ACMEDA_HUB_UPDATE, DOMAIN
|
||||
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."""
|
||||
hub = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
current = set()
|
||||
current: set[int] = set()
|
||||
|
||||
@callback
|
||||
def async_add_acmeda_covers():
|
||||
|
@ -1,13 +1,21 @@
|
||||
"""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.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
|
||||
|
||||
@callback
|
||||
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."""
|
||||
hub = hass.data[DOMAIN][config_entry.entry_id]
|
||||
@ -26,7 +34,7 @@ def async_add_acmeda_entities(
|
||||
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."""
|
||||
dev_registry = await get_dev_reg(hass)
|
||||
|
||||
|
@ -1,19 +1,27 @@
|
||||
"""Support for Acmeda Roller Blind Batteries."""
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.const import DEVICE_CLASS_BATTERY, PERCENTAGE
|
||||
from homeassistant.core import callback
|
||||
from __future__ import annotations
|
||||
|
||||
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.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .base import AcmedaBase
|
||||
from .const import ACMEDA_HUB_UPDATE, DOMAIN
|
||||
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."""
|
||||
hub = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
current = set()
|
||||
current: set[int] = set()
|
||||
|
||||
@callback
|
||||
def async_add_acmeda_sensors():
|
||||
@ -33,7 +41,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
class AcmedaBattery(AcmedaBase, SensorEntity):
|
||||
"""Representation of a Acmeda cover device."""
|
||||
|
||||
device_class = DEVICE_CLASS_BATTERY
|
||||
device_class = SensorDeviceClass.BATTERY
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
|
||||
@property
|
||||
|
12
homeassistant/components/acmeda/translations/el.json
Normal file
12
homeassistant/components/acmeda/translations/el.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"id": "Gazdag\u00e9p azonos\u00edt\u00f3"
|
||||
"id": "G\u00e9p azonos\u00edt\u00f3"
|
||||
},
|
||||
"title": "V\u00e1lassza ki a hozz\u00e1adni k\u00edv\u00e1nt hubot"
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
"data": {
|
||||
"id": "ID host"
|
||||
},
|
||||
"title": "Scegliere un hub da aggiungere"
|
||||
"title": "Scegli un hub da aggiungere"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,3 +17,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
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
|
||||
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from adax import Adax
|
||||
from adax_local import Adax as AdaxLocal
|
||||
|
||||
from homeassistant.components.climate import ClimateEntity
|
||||
from homeassistant.components.climate.const import (
|
||||
@ -14,7 +15,10 @@ from homeassistant.components.climate.const import (
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE,
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_PASSWORD,
|
||||
CONF_TOKEN,
|
||||
CONF_UNIQUE_ID,
|
||||
PRECISION_WHOLE,
|
||||
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_platform import AddEntitiesCallback
|
||||
|
||||
from .const import ACCOUNT_ID, DOMAIN
|
||||
from .const import ACCOUNT_ID, CONNECTION_TYPE, DOMAIN, LOCAL
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@ -32,6 +36,17 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""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(
|
||||
entry.data[ACCOUNT_ID],
|
||||
entry.data[CONF_PASSWORD],
|
||||
@ -107,3 +122,38 @@ class AdaxDevice(ClimateEntity):
|
||||
self._attr_hvac_mode = HVAC_MODE_OFF
|
||||
self._attr_icon = "mdi:radiator-off"
|
||||
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
|
||||
|
@ -5,69 +5,140 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
import adax
|
||||
import adax_local
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_PASSWORD
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import (
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_PASSWORD,
|
||||
CONF_TOKEN,
|
||||
CONF_UNIQUE_ID,
|
||||
)
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import ACCOUNT_ID, DOMAIN
|
||||
from .const import (
|
||||
ACCOUNT_ID,
|
||||
CLOUD,
|
||||
CONNECTION_TYPE,
|
||||
DOMAIN,
|
||||
LOCAL,
|
||||
WIFI_PSWD,
|
||||
WIFI_SSID,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{vol.Required(ACCOUNT_ID): int, vol.Required(CONF_PASSWORD): str}
|
||||
)
|
||||
|
||||
|
||||
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):
|
||||
"""Handle a config flow for Adax."""
|
||||
|
||||
VERSION = 1
|
||||
VERSION = 2
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the initial step."""
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
vol.Required(CONNECTION_TYPE, default=CLOUD): vol.In(
|
||||
(
|
||||
CLOUD,
|
||||
LOCAL,
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if user_input is None:
|
||||
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 = {}
|
||||
|
||||
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()
|
||||
|
||||
try:
|
||||
await validate_input(self.hass, user_input)
|
||||
except CannotConnect:
|
||||
account_id = user_input[ACCOUNT_ID]
|
||||
password = user_input[CONF_PASSWORD].replace(" ", "")
|
||||
|
||||
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"
|
||||
else:
|
||||
return self.async_create_entry(
|
||||
title=user_input[ACCOUNT_ID], data=user_input
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||
step_id="cloud",
|
||||
data_schema=data_schema,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
|
||||
class CannotConnect(HomeAssistantError):
|
||||
"""Error to indicate we cannot connect."""
|
||||
return self.async_create_entry(
|
||||
title=str(user_input[ACCOUNT_ID]),
|
||||
data={
|
||||
ACCOUNT_ID: account_id,
|
||||
CONF_PASSWORD: password,
|
||||
CONNECTION_TYPE: CLOUD,
|
||||
},
|
||||
)
|
||||
|
@ -2,4 +2,9 @@
|
||||
from typing import Final
|
||||
|
||||
ACCOUNT_ID: Final = "account_id"
|
||||
CLOUD = "Cloud"
|
||||
CONNECTION_TYPE = "connection_type"
|
||||
DOMAIN: Final = "adax"
|
||||
LOCAL = "Local"
|
||||
WIFI_SSID = "wifi_ssid"
|
||||
WIFI_PSWD = "wifi_pswd"
|
||||
|
@ -4,10 +4,10 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/adax",
|
||||
"requirements": [
|
||||
"adax==0.2.0"
|
||||
"adax==0.2.0", "Adax-local==0.1.3"
|
||||
],
|
||||
"codeowners": [
|
||||
"@danielhiversen"
|
||||
],
|
||||
"iot_class": "cloud_polling"
|
||||
"iot_class": "local_polling"
|
||||
}
|
||||
|
@ -2,6 +2,19 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"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": {
|
||||
"account_id": "Account ID",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
@ -12,7 +25,10 @@
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"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%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,25 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"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"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"host": "\u0425\u043e\u0441\u0442",
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "Ha fallat la connexi\u00f3",
|
||||
"invalid_auth": "Autenticaci\u00f3 inv\u00e0lida"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "ID del compte",
|
||||
"connection_type": "Selecciona el tipus de connexi\u00f3",
|
||||
"host": "Amfitri\u00f3",
|
||||
"password": "Contrasenya"
|
||||
}
|
||||
},
|
||||
"description": "Selecciona el tipus de connexi\u00f3. La local necessita escalfadors amb Bluetooth"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit",
|
||||
"invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "ID \u00fa\u010dtu",
|
||||
"connection_type": "Vyberte typ p\u0159ipojen\u00ed",
|
||||
"host": "Hostitel",
|
||||
"password": "Heslo"
|
||||
}
|
||||
},
|
||||
"description": "Vyberte typ p\u0159ipojen\u00ed. Lok\u00e1ln\u00ed vy\u017eaduje oh\u0159\u00edva\u010de s bluetooth"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "Verbindung fehlgeschlagen",
|
||||
"invalid_auth": "Ung\u00fcltige Authentifizierung"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "Konto-ID",
|
||||
"connection_type": "Verbindungstyp ausw\u00e4hlen",
|
||||
"host": "Host",
|
||||
"password": "Passwort"
|
||||
}
|
||||
},
|
||||
"description": "Verbindungstyp ausw\u00e4hlen. Lokal erfordert Heizungen mit Bluetooth"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "Failed to connect",
|
||||
"invalid_auth": "Invalid authentication"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "Account ID",
|
||||
"connection_type": "Select connection type",
|
||||
"host": "Host",
|
||||
"password": "Password"
|
||||
}
|
||||
},
|
||||
"description": "Select connection type. Local requires heaters with bluetooth"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
homeassistant/components/adax/translations/es-419.json
Normal file
12
homeassistant/components/adax/translations/es-419.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "No se pudo conectar",
|
||||
"invalid_auth": "Autenticaci\u00f3n no v\u00e1lida"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "ID de la cuenta",
|
||||
"connection_type": "Seleccione el tipo de conexi\u00f3n",
|
||||
"host": "Host",
|
||||
"password": "Contrase\u00f1a"
|
||||
}
|
||||
},
|
||||
"description": "Seleccione el tipo de conexi\u00f3n. Local requiere calentadores con bluetooth"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "\u00dchendamine nurjus",
|
||||
"invalid_auth": "Tuvastamise viga"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "Konto ID",
|
||||
"connection_type": "Vali \u00fchenduse t\u00fc\u00fcp",
|
||||
"host": "Host",
|
||||
"password": "Salas\u00f5na"
|
||||
}
|
||||
},
|
||||
"description": "Vali \u00fchenduse t\u00fc\u00fcp. Kohalik n\u00f5uab bluetoothiga k\u00fctteseadmeid"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "\u00c9chec de connexion",
|
||||
"invalid_auth": "Authentification invalide"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "identifiant de compte",
|
||||
"connection_type": "S\u00e9lectionner le type de connexion",
|
||||
"host": "H\u00f4te",
|
||||
"password": "Mot de passe"
|
||||
}
|
||||
},
|
||||
"description": "S\u00e9lectionnez le type de connexion. Local n\u00e9cessite des radiateurs avec Bluetooth"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"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"
|
||||
},
|
||||
"step": {
|
||||
"cloud": {
|
||||
"data": {
|
||||
"password": "\u05e1\u05d9\u05e1\u05de\u05d4"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"account_id": "\u05de\u05d6\u05d4\u05d4 \u05d7\u05e9\u05d1\u05d5\u05df",
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "Nem siker\u00fclt csatlakozni",
|
||||
"invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "Fi\u00f3k ID",
|
||||
"connection_type": "V\u00e1lassza ki a kapcsolat t\u00edpus\u00e1t",
|
||||
"host": "C\u00edm",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "Gagal terhubung",
|
||||
"invalid_auth": "Autentikasi tidak valid"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "ID Akun",
|
||||
"connection_type": "Pilih jenis koneksi",
|
||||
"host": "Host",
|
||||
"password": "Kata Sandi"
|
||||
}
|
||||
},
|
||||
"description": "Pilih jenis koneksi. Lokal membutuhkan pemanas dengan bluetooth"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "Impossibile connettersi",
|
||||
"invalid_auth": "Autenticazione non valida"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "ID account",
|
||||
"connection_type": "Seleziona il tipo di connessione",
|
||||
"host": "Host",
|
||||
"password": "Password"
|
||||
}
|
||||
},
|
||||
"description": "Seleziona il tipo di connessione. Locale richiede riscaldatori con bluetooth"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
|
||||
"invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "\u30a2\u30ab\u30a6\u30f3\u30c8ID",
|
||||
"connection_type": "\u63a5\u7d9a\u30bf\u30a4\u30d7\u306e\u9078\u629e",
|
||||
"host": "\u30db\u30b9\u30c8",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
29
homeassistant/components/adax/translations/lt.json
Normal file
29
homeassistant/components/adax/translations/lt.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "Kan geen verbinding maken",
|
||||
"invalid_auth": "Ongeldige authenticatie"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "Account ID",
|
||||
"connection_type": "Selecteer verbindingstype",
|
||||
"host": "Host",
|
||||
"password": "Wachtwoord"
|
||||
}
|
||||
},
|
||||
"description": "Selecteer verbindingstype. Lokaal vereist verwarming met bluetooth"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "Tilkobling mislyktes",
|
||||
"invalid_auth": "Ugyldig godkjenning"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "Konto-ID",
|
||||
"connection_type": "Velg tilkoblingstype",
|
||||
"host": "Vert",
|
||||
"password": "Passord"
|
||||
}
|
||||
},
|
||||
"description": "Velg tilkoblingstype. Lokalt krever varmeovner med bluetooth"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia",
|
||||
"invalid_auth": "Niepoprawne uwierzytelnienie"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "Identyfikator konta",
|
||||
"connection_type": "Wybierz typ po\u0142\u0105czenia",
|
||||
"host": "Nazwa hosta lub adres IP",
|
||||
"password": "Has\u0142o"
|
||||
}
|
||||
},
|
||||
"description": "Wybierz typ po\u0142\u0105czenia. \"Lokalny\" wymaga grzejnik\u00f3w z bluetooth."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"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."
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"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",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "Ba\u011flanma hatas\u0131",
|
||||
"invalid_auth": "Ge\u00e7ersiz kimlik do\u011frulama"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "Hesap Kimli\u011fi",
|
||||
"host": "Ana bilgisayar",
|
||||
"connection_type": "Ba\u011flant\u0131 t\u00fcr\u00fcn\u00fc se\u00e7in",
|
||||
"host": "Sunucu",
|
||||
"password": "Parola"
|
||||
}
|
||||
},
|
||||
"description": "Ba\u011flant\u0131 t\u00fcr\u00fcn\u00fc se\u00e7in. Yerel Bluetooth'lu \u0131s\u0131t\u0131c\u0131lar gerektirir"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,27 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"invalid_auth": "\u65e0\u6548\u7684\u6388\u6743"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "\u8fde\u63a5\u5931\u8d25"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"connection_type": "\u9009\u62e9\u8fde\u63a5\u7c7b\u578b",
|
||||
"password": "\u5bc6\u7801"
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,37 @@
|
||||
{
|
||||
"config": {
|
||||
"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": {
|
||||
"cannot_connect": "\u9023\u7dda\u5931\u6557",
|
||||
"invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548"
|
||||
},
|
||||
"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": {
|
||||
"data": {
|
||||
"account_id": "\u5e33\u865f ID",
|
||||
"connection_type": "\u9078\u64c7\u9023\u7dda\u985e\u578b",
|
||||
"host": "\u4e3b\u6a5f\u7aef",
|
||||
"password": "\u5bc6\u78bc"
|
||||
}
|
||||
},
|
||||
"description": "\u9078\u64c7\u9023\u7dda\u985e\u578b\u3002\u672c\u5730\u7aef\u5c07\u9700\u8981\u5177\u5099\u85cd\u82bd\u52a0\u71b1\u5668"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ from homeassistant.const import (
|
||||
CONF_VERIFY_SSL,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
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)
|
||||
|
||||
async def add_url(call) -> None:
|
||||
async def add_url(call: ServiceCall) -> None:
|
||||
"""Service call to add a new filter subscription to AdGuard Home."""
|
||||
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."""
|
||||
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."""
|
||||
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."""
|
||||
await adguard.filtering.disable_url(
|
||||
allowlist=False, url=call.data.get(CONF_URL)
|
||||
)
|
||||
await adguard.filtering.disable_url(allowlist=False, url=call.data[CONF_URL])
|
||||
|
||||
async def refresh(call) -> None:
|
||||
async def refresh(call: ServiceCall) -> None:
|
||||
"""Service call to refresh the filter subscriptions in AdGuard Home."""
|
||||
await adguard.filtering.refresh(
|
||||
allowlist=False, force=call.data.get(CONF_FORCE)
|
||||
)
|
||||
await adguard.filtering.refresh(allowlist=False, force=call.data[CONF_FORCE])
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_ADD_URL, add_url, schema=SERVICE_ADD_URL_SCHEMA
|
||||
|
@ -80,8 +80,8 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
adguard = AdGuardHome(
|
||||
user_input[CONF_HOST],
|
||||
port=user_input[CONF_PORT],
|
||||
username=username, # type:ignore[arg-type]
|
||||
password=password, # type:ignore[arg-type]
|
||||
username=username,
|
||||
password=password,
|
||||
tls=user_input[CONF_SSL],
|
||||
verify_ssl=user_input[CONF_VERIFY_SSL],
|
||||
session=session,
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "AdGuard Home",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/adguard",
|
||||
"requirements": ["adguardhome==0.5.0"],
|
||||
"requirements": ["adguardhome==0.5.1"],
|
||||
"codeowners": ["@frenck"],
|
||||
"iot_class": "local_polling"
|
||||
}
|
||||
|
@ -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."
|
||||
},
|
||||
"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": {
|
||||
"hassio_confirm": {
|
||||
|
@ -2,6 +2,15 @@
|
||||
"config": {
|
||||
"error": {
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,8 +9,8 @@
|
||||
},
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani Home Assistantot AdGuard Home-hoz val\u00f3 csatlakoz\u00e1shoz, {addon} kieg\u00e9sz\u00edt\u0151 \u00e1ltal?",
|
||||
"title": "Az AdGuard Home a Home Assistant kieg\u00e9sz\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel"
|
||||
"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 b\u0151v\u00edtm\u00e9nnyel"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
|
@ -19,7 +19,7 @@
|
||||
"port": "Porta",
|
||||
"ssl": "Utilizza un certificato SSL",
|
||||
"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."
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
},
|
||||
"step": {
|
||||
"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"
|
||||
},
|
||||
"user": {
|
||||
|
@ -14,7 +14,7 @@
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Ana bilgisayar",
|
||||
"host": "Sunucu",
|
||||
"password": "Parola",
|
||||
"port": "Port",
|
||||
"ssl": "SSL sertifikas\u0131 kullan\u0131r",
|
||||
|
@ -13,7 +13,7 @@
|
||||
"host": "\u4e3b\u673a\u5730\u5740",
|
||||
"password": "\u5bc6\u7801",
|
||||
"port": "\u7aef\u53e3",
|
||||
"ssl": "\u4f7f\u7528 SSL \u8bc1\u4e66\u51ed\u8bc1",
|
||||
"ssl": "\u4f7f\u7528 SSL \u8bc1\u4e66",
|
||||
"username": "\u7528\u6237\u540d",
|
||||
"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
Loading…
x
Reference in New Issue
Block a user