Compare commits

..

2 Commits

Author SHA1 Message Date
57263bb65a goe_charger cleanups and improvements 2021-09-15 21:25:23 +02:00
86533e3599 Added goe_charger 2021-09-13 12:27:15 +02:00
10838 changed files with 128437 additions and 409751 deletions

View File

@@ -1,134 +0,0 @@
# Defines a list of files that are part of main core of Home Assistant.
# Changes to these files/filters define how our CI test suite is ran.
core: &core
- homeassistant/*.py
- homeassistant/auth/**
- homeassistant/helpers/*
- homeassistant/package_constraints.txt
- homeassistant/util/*
- pyproject.toml
- requirements.txt
- setup.cfg
# Our base platforms, that are used by other integrations
base_platforms: &base_platforms
- homeassistant/components/air_quality/*
- homeassistant/components/alarm_control_panel/*
- homeassistant/components/binary_sensor/*
- homeassistant/components/button/*
- homeassistant/components/calendar/*
- homeassistant/components/camera/*
- homeassistant/components/climate/*
- homeassistant/components/cover/*
- homeassistant/components/device_tracker/*
- homeassistant/components/diagnostics/*
- homeassistant/components/fan/*
- homeassistant/components/geo_location/*
- homeassistant/components/humidifier/*
- homeassistant/components/image_processing/*
- homeassistant/components/light/*
- homeassistant/components/lock/*
- homeassistant/components/media_player/*
- homeassistant/components/notify/*
- homeassistant/components/number/*
- homeassistant/components/remote/*
- homeassistant/components/scene/*
- homeassistant/components/select/*
- homeassistant/components/sensor/*
- homeassistant/components/siren/*
- homeassistant/components/stt/*
- homeassistant/components/switch/*
- homeassistant/components/tts/*
- homeassistant/components/vacuum/*
- homeassistant/components/water_heater/*
- homeassistant/components/weather/*
# Extra components that trigger the full suite
components: &components
- homeassistant/components/alert/*
- homeassistant/components/alexa/*
- homeassistant/components/auth/*
- homeassistant/components/automation/*
- homeassistant/components/cloud/*
- homeassistant/components/config/*
- homeassistant/components/configurator/*
- homeassistant/components/conversation/*
- homeassistant/components/demo/*
- homeassistant/components/device_automation/*
- homeassistant/components/dhcp/*
- homeassistant/components/discovery/*
- homeassistant/components/energy/*
- homeassistant/components/ffmpeg/*
- homeassistant/components/frontend/*
- homeassistant/components/google_assistant/*
- homeassistant/components/group/*
- homeassistant/components/hassio/*
- 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/*
- homeassistant/components/input_text/*
- homeassistant/components/logbook/*
- homeassistant/components/logger/*
- homeassistant/components/lovelace/*
- homeassistant/components/media_source/*
- homeassistant/components/mqtt/*
- homeassistant/components/network/*
- homeassistant/components/onboarding/*
- homeassistant/components/otp/*
- homeassistant/components/persistent_notification/*
- homeassistant/components/person/*
- homeassistant/components/recorder/*
- homeassistant/components/safe_mode/*
- homeassistant/components/script/*
- homeassistant/components/shopping_list/*
- homeassistant/components/ssdp/*
- homeassistant/components/stream/*
- homeassistant/components/sun/*
- homeassistant/components/system_health/*
- homeassistant/components/tag/*
- homeassistant/components/template/*
- homeassistant/components/timer/*
- homeassistant/components/usb/*
- homeassistant/components/webhook/*
- homeassistant/components/websocket_api/*
- homeassistant/components/zeroconf/*
- homeassistant/components/zone/*
# Testing related files that affect the whole test/linting suite
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
- *core
- *other
- *tests

View File

@@ -27,16 +27,15 @@ 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/__init__.py
homeassistant/components/agent_dvr/alarm_control_panel.py
homeassistant/components/agent_dvr/camera.py
homeassistant/components/agent_dvr/const.py
homeassistant/components/agent_dvr/helpers.py
homeassistant/components/airnow/__init__.py
homeassistant/components/airnow/sensor.py
homeassistant/components/airthings/__init__.py
homeassistant/components/airthings/sensor.py
homeassistant/components/airtouch4/__init__.py
homeassistant/components/airtouch4/climate.py
homeassistant/components/airtouch4/const.py
@@ -50,13 +49,11 @@ omit =
homeassistant/components/alarmdecoder/sensor.py
homeassistant/components/alpha_vantage/sensor.py
homeassistant/components/amazon_polly/*
homeassistant/components/amberelectric/__init__.py
homeassistant/components/ambiclimate/climate.py
homeassistant/components/ambient_station/*
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/*
@@ -67,6 +64,7 @@ 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
@@ -74,9 +72,6 @@ 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
@@ -87,6 +82,7 @@ omit =
homeassistant/components/aurora/binary_sensor.py
homeassistant/components/aurora/const.py
homeassistant/components/aurora/sensor.py
homeassistant/components/aurora_abb_powerone/sensor.py
homeassistant/components/avea/light.py
homeassistant/components/avion/light.py
homeassistant/components/azure_devops/__init__.py
@@ -94,7 +90,6 @@ omit =
homeassistant/components/azure_devops/sensor.py
homeassistant/components/azure_service_bus/*
homeassistant/components/baidu/tts.py
homeassistant/components/balboa/__init__.py
homeassistant/components/beewi_smartclim/sensor.py
homeassistant/components/bbb_gpio/*
homeassistant/components/bbox/device_tracker.py
@@ -121,7 +116,6 @@ 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
@@ -129,7 +123,6 @@ omit =
homeassistant/components/bosch_shc/__init__.py
homeassistant/components/bosch_shc/binary_sensor.py
homeassistant/components/bosch_shc/const.py
homeassistant/components/bosch_shc/cover.py
homeassistant/components/bosch_shc/entity.py
homeassistant/components/bosch_shc/sensor.py
homeassistant/components/braviatv/__init__.py
@@ -138,15 +131,12 @@ omit =
homeassistant/components/braviatv/remote.py
homeassistant/components/broadlink/__init__.py
homeassistant/components/broadlink/const.py
homeassistant/components/broadlink/light.py
homeassistant/components/broadlink/remote.py
homeassistant/components/broadlink/switch.py
homeassistant/components/broadlink/updater.py
homeassistant/components/brottsplatskartan/sensor.py
homeassistant/components/browser/*
homeassistant/components/brunt/__init__.py
homeassistant/components/brunt/cover.py
homeassistant/components/brunt/const.py
homeassistant/components/bsblan/climate.py
homeassistant/components/bt_home_hub_5/device_tracker.py
homeassistant/components/bt_smarthub/device_tracker.py
@@ -180,13 +170,7 @@ omit =
homeassistant/components/coolmaster/climate.py
homeassistant/components/coolmaster/const.py
homeassistant/components/cppm_tracker/device_tracker.py
homeassistant/components/crownstone/__init__.py
homeassistant/components/crownstone/const.py
homeassistant/components/crownstone/listeners.py
homeassistant/components/crownstone/helpers.py
homeassistant/components/crownstone/devices.py
homeassistant/components/crownstone/entry_manager.py
homeassistant/components/crownstone/light.py
homeassistant/components/cpuspeed/sensor.py
homeassistant/components/cups/sensor.py
homeassistant/components/currencylayer/sensor.py
homeassistant/components/daikin/*
@@ -206,6 +190,7 @@ 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
@@ -218,7 +203,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/dlna_dmr/media_player.py
homeassistant/components/dnsip/sensor.py
homeassistant/components/dominos/*
homeassistant/components/doods/*
@@ -257,10 +242,6 @@ 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
@@ -279,10 +260,7 @@ omit =
homeassistant/components/enphase_envoy/__init__.py
homeassistant/components/enphase_envoy/sensor.py
homeassistant/components/entur_public_transport/*
homeassistant/components/environment_canada/__init__.py
homeassistant/components/environment_canada/camera.py
homeassistant/components/environment_canada/sensor.py
homeassistant/components/environment_canada/weather.py
homeassistant/components/environment_canada/*
homeassistant/components/envirophat/sensor.py
homeassistant/components/envisalink/*
homeassistant/components/ephember/climate.py
@@ -293,7 +271,6 @@ omit =
homeassistant/components/eq3btsmart/climate.py
homeassistant/components/esphome/__init__.py
homeassistant/components/esphome/binary_sensor.py
homeassistant/components/esphome/button.py
homeassistant/components/esphome/camera.py
homeassistant/components/esphome/climate.py
homeassistant/components/esphome/cover.py
@@ -304,6 +281,7 @@ omit =
homeassistant/components/esphome/select.py
homeassistant/components/esphome/sensor.py
homeassistant/components/esphome/switch.py
homeassistant/components/essent/sensor.py
homeassistant/components/etherscan/sensor.py
homeassistant/components/eufy/*
homeassistant/components/everlights/light.py
@@ -312,7 +290,6 @@ omit =
homeassistant/components/ezviz/camera.py
homeassistant/components/ezviz/coordinator.py
homeassistant/components/ezviz/const.py
homeassistant/components/ezviz/entity.py
homeassistant/components/ezviz/binary_sensor.py
homeassistant/components/ezviz/sensor.py
homeassistant/components/ezviz/switch.py
@@ -345,7 +322,6 @@ omit =
homeassistant/components/fjaraskupan/const.py
homeassistant/components/fjaraskupan/fan.py
homeassistant/components/fjaraskupan/light.py
homeassistant/components/fjaraskupan/number.py
homeassistant/components/fjaraskupan/sensor.py
homeassistant/components/fleetgo/device_tracker.py
homeassistant/components/flexit/climate.py
@@ -358,6 +334,7 @@ omit =
homeassistant/components/flume/sensor.py
homeassistant/components/flunearyou/__init__.py
homeassistant/components/flunearyou/sensor.py
homeassistant/components/flux_led/light.py
homeassistant/components/folder/sensor.py
homeassistant/components/folder_watcher/*
homeassistant/components/foobot/sensor.py
@@ -372,11 +349,9 @@ 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
@@ -384,6 +359,7 @@ omit =
homeassistant/components/fritzbox_callmonitor/const.py
homeassistant/components/fritzbox_callmonitor/base.py
homeassistant/components/fritzbox_callmonitor/sensor.py
homeassistant/components/fronius/sensor.py
homeassistant/components/frontier_silicon/media_player.py
homeassistant/components/futurenow/light.py
homeassistant/components/garadget/cover.py
@@ -392,8 +368,7 @@ 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/generic_hygrostat/*
homeassistant/components/github/sensor.py
homeassistant/components/gitlab_ci/sensor.py
homeassistant/components/gitter/sensor.py
@@ -402,12 +377,11 @@ omit =
homeassistant/components/glances/sensor.py
homeassistant/components/gntp/notify.py
homeassistant/components/goalfeed/*
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/goalzero/__init__.py
homeassistant/components/goalzero/binary_sensor.py
homeassistant/components/goalzero/sensor.py
homeassistant/components/goalzero/switch.py
homeassistant/components/google/*
homeassistant/components/google_cloud/tts.py
homeassistant/components/google_maps/device_tracker.py
homeassistant/components/google_pubsub/__init__.py
@@ -416,6 +390,8 @@ omit =
homeassistant/components/google_travel_time/sensor.py
homeassistant/components/gpmdp/media_player.py
homeassistant/components/gpsd/sensor.py
homeassistant/components/greeneye_monitor/*
homeassistant/components/greeneye_monitor/sensor.py
homeassistant/components/greenwave/light.py
homeassistant/components/group/notify.py
homeassistant/components/growatt_server/sensor.py
@@ -436,6 +412,9 @@ omit =
homeassistant/components/harmony/data.py
homeassistant/components/harmony/remote.py
homeassistant/components/harmony/util.py
homeassistant/components/hassio/binary_sensor.py
homeassistant/components/hassio/entity.py
homeassistant/components/hassio/sensor.py
homeassistant/components/haveibeenpwned/sensor.py
homeassistant/components/hdmi_cec/*
homeassistant/components/heatmiser/climate.py
@@ -445,9 +424,8 @@ omit =
homeassistant/components/hisense_aehw4a1/climate.py
homeassistant/components/hitron_coda/device_tracker.py
homeassistant/components/hive/__init__.py
homeassistant/components/hive/alarm_control_panel.py
homeassistant/components/hive/binary_sensor.py
homeassistant/components/hive/climate.py
homeassistant/components/hive/binary_sensor.py
homeassistant/components/hive/light.py
homeassistant/components/hive/sensor.py
homeassistant/components/hive/switch.py
@@ -464,7 +442,6 @@ 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
@@ -472,6 +449,7 @@ omit =
homeassistant/components/hp_ilo/sensor.py
homeassistant/components/htu21d/sensor.py
homeassistant/components/huawei_lte/*
homeassistant/components/huawei_router/device_tracker.py
homeassistant/components/hue/light.py
homeassistant/components/hunterdouglas_powerview/__init__.py
homeassistant/components/hunterdouglas_powerview/scene.py
@@ -513,13 +491,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/*
homeassistant/components/iota/*
homeassistant/components/iperf3/*
homeassistant/components/iqvia/*
homeassistant/components/irish_rail_transport/sensor.py
@@ -538,8 +513,6 @@ omit =
homeassistant/components/isy994/switch.py
homeassistant/components/itach/remote.py
homeassistant/components/itunes/media_player.py
homeassistant/components/jellyfin/__init__.py
homeassistant/components/jellyfin/media_source.py
homeassistant/components/joaoapps_join/*
homeassistant/components/juicenet/__init__.py
homeassistant/components/juicenet/const.py
@@ -560,10 +533,7 @@ omit =
homeassistant/components/keyboard_remote/*
homeassistant/components/kira/*
homeassistant/components/kiwi/lock.py
homeassistant/components/knx/__init__.py
homeassistant/components/knx/climate.py
homeassistant/components/knx/cover.py
homeassistant/components/knx/notify.py
homeassistant/components/knx/*
homeassistant/components/kodi/__init__.py
homeassistant/components/kodi/browse_media.py
homeassistant/components/kodi/const.py
@@ -573,25 +543,23 @@ omit =
homeassistant/components/kostal_plenticore/__init__.py
homeassistant/components/kostal_plenticore/const.py
homeassistant/components/kostal_plenticore/helper.py
homeassistant/components/kostal_plenticore/select.py
homeassistant/components/kostal_plenticore/sensor.py
homeassistant/components/kostal_plenticore/switch.py
homeassistant/components/kwb/sensor.py
homeassistant/components/lacrosse/sensor.py
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/*
@@ -609,15 +577,9 @@ omit =
homeassistant/components/logi_circle/const.py
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/loopenergy/sensor.py
homeassistant/components/luci/device_tracker.py
homeassistant/components/luftdaten/__init__.py
homeassistant/components/luftdaten/sensor.py
homeassistant/components/lupusec/*
homeassistant/components/lutron/*
@@ -629,6 +591,7 @@ omit =
homeassistant/components/lutron_caseta/scene.py
homeassistant/components/lutron_caseta/switch.py
homeassistant/components/lw12wifi/light.py
homeassistant/components/lyft/sensor.py
homeassistant/components/lyric/__init__.py
homeassistant/components/lyric/api.py
homeassistant/components/lyric/climate.py
@@ -665,6 +628,7 @@ omit =
homeassistant/components/miflora/sensor.py
homeassistant/components/mikrotik/hub.py
homeassistant/components/mikrotik/device_tracker.py
homeassistant/components/mill/__init__.py
homeassistant/components/mill/climate.py
homeassistant/components/mill/const.py
homeassistant/components/mill/sensor.py
@@ -677,7 +641,13 @@ omit =
homeassistant/components/mitemp_bt/sensor.py
homeassistant/components/mjpeg/camera.py
homeassistant/components/mochad/*
homeassistant/components/modbus/base_platform.py
homeassistant/components/modbus/binary_sensor.py
homeassistant/components/modbus/cover.py
homeassistant/components/modbus/climate.py
homeassistant/components/modbus/modbus.py
homeassistant/components/modbus/sensor.py
homeassistant/components/modbus/validators.py
homeassistant/components/modem_callerid/sensor.py
homeassistant/components/motion_blinds/__init__.py
homeassistant/components/motion_blinds/const.py
@@ -693,6 +663,7 @@ omit =
homeassistant/components/mutesync/binary_sensor.py
homeassistant/components/nest/const.py
homeassistant/components/mvglive/sensor.py
homeassistant/components/mychevy/*
homeassistant/components/mycroft/*
homeassistant/components/mysensors/__init__.py
homeassistant/components/mysensors/binary_sensor.py
@@ -715,10 +686,8 @@ omit =
homeassistant/components/myq/light.py
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/nanoleaf/util.py
homeassistant/components/neato/__init__.py
homeassistant/components/neato/api.py
homeassistant/components/neato/camera.py
@@ -727,18 +696,14 @@ omit =
homeassistant/components/neato/switch.py
homeassistant/components/neato/vacuum.py
homeassistant/components/nederlandse_spoorwegen/sensor.py
homeassistant/components/nello/lock.py
homeassistant/components/nest/legacy/*
homeassistant/components/netdata/sensor.py
homeassistant/components/netgear/__init__.py
homeassistant/components/netgear/device_tracker.py
homeassistant/components/netgear/router.py
homeassistant/components/netgear/sensor.py
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
@@ -759,10 +724,11 @@ omit =
homeassistant/components/nuki/const.py
homeassistant/components/nuki/binary_sensor.py
homeassistant/components/nuki/lock.py
homeassistant/components/nut/sensor.py
homeassistant/components/nx584/alarm_control_panel.py
homeassistant/components/nzbget/coordinator.py
homeassistant/components/obihai/*
homeassistant/components/octoprint/__init__.py
homeassistant/components/octoprint/*
homeassistant/components/oem/climate.py
homeassistant/components/oasa_telematics/sensor.py
homeassistant/components/ohmconnect/sensor.py
@@ -785,16 +751,10 @@ 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
homeassistant/components/opengarage/__init__.py
homeassistant/components/opengarage/binary_sensor.py
homeassistant/components/opengarage/cover.py
homeassistant/components/opengarage/entity.py
homeassistant/components/opengarage/sensor.py
homeassistant/components/openhome/__init__.py
homeassistant/components/openhome/media_player.py
homeassistant/components/openhome/const.py
@@ -810,6 +770,7 @@ omit =
homeassistant/components/openweathermap/sensor.py
homeassistant/components/openweathermap/weather.py
homeassistant/components/openweathermap/weather_update_coordinator.py
homeassistant/components/openweathermap/abstract_owm_sensor.py
homeassistant/components/opnsense/*
homeassistant/components/opple/light.py
homeassistant/components/orangepi_gpio/*
@@ -817,22 +778,6 @@ 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
@@ -848,7 +793,6 @@ 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
@@ -878,6 +822,7 @@ 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
@@ -886,6 +831,7 @@ 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
@@ -916,11 +862,11 @@ omit =
homeassistant/components/remote_rpi_gpio/*
homeassistant/components/rest/notify.py
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/rituals_perfume_genie/binary_sensor.py
homeassistant/components/rituals_perfume_genie/number.py
homeassistant/components/rituals_perfume_genie/select.py
homeassistant/components/rocketchat/notify.py
homeassistant/components/roomba/__init__.py
homeassistant/components/roomba/binary_sensor.py
@@ -946,14 +892,13 @@ omit =
homeassistant/components/russound_rnet/media_player.py
homeassistant/components/sabnzbd/*
homeassistant/components/saj/sensor.py
homeassistant/components/samsungtv/bridge.py
homeassistant/components/satel_integra/*
homeassistant/components/schluter/*
homeassistant/components/scrape/sensor.py
homeassistant/components/screenlogic/__init__.py
homeassistant/components/screenlogic/binary_sensor.py
homeassistant/components/screenlogic/climate.py
homeassistant/components/screenlogic/light.py
homeassistant/components/screenlogic/number.py
homeassistant/components/screenlogic/sensor.py
homeassistant/components/screenlogic/services.py
homeassistant/components/screenlogic/switch.py
@@ -964,14 +909,6 @@ 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
@@ -982,7 +919,6 @@ omit =
homeassistant/components/shodan/sensor.py
homeassistant/components/shelly/__init__.py
homeassistant/components/shelly/binary_sensor.py
homeassistant/components/shelly/climate.py
homeassistant/components/shelly/entity.py
homeassistant/components/shelly/light.py
homeassistant/components/shelly/sensor.py
@@ -1031,12 +967,10 @@ 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
@@ -1045,17 +979,7 @@ omit =
homeassistant/components/somfy/switch.py
homeassistant/components/somfy_mylink/__init__.py
homeassistant/components/somfy_mylink/cover.py
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/sonos/*
homeassistant/components/sony_projector/switch.py
homeassistant/components/spc/*
homeassistant/components/spider/*
@@ -1066,32 +990,23 @@ omit =
homeassistant/components/squeezebox/__init__.py
homeassistant/components/squeezebox/browse_media.py
homeassistant/components/squeezebox/media_player.py
homeassistant/components/ssdp/util.py
homeassistant/components/starline/*
homeassistant/components/starlingbank/sensor.py
homeassistant/components/steam_online/sensor.py
homeassistant/components/stiebel_eltron/*
homeassistant/components/stookalert/__init__.py
homeassistant/components/stookalert/binary_sensor.py
homeassistant/components/stookalert/diagnostics.py
homeassistant/components/stookalert/*
homeassistant/components/stream/*
homeassistant/components/streamlabswater/*
homeassistant/components/suez_water/*
homeassistant/components/supervisord/sensor.py
homeassistant/components/surepetcare/__init__.py
homeassistant/components/surepetcare/entity.py
homeassistant/components/surepetcare/binary_sensor.py
homeassistant/components/surepetcare/sensor.py
homeassistant/components/swiss_hydrological_data/sensor.py
homeassistant/components/swiss_public_transport/sensor.py
homeassistant/components/swisscom/device_tracker.py
homeassistant/components/switchbot/switch.py
homeassistant/components/switchbot/binary_sensor.py
homeassistant/components/switchbot/__init__.py
homeassistant/components/switchbot/const.py
homeassistant/components/switchbot/entity.py
homeassistant/components/switchbot/cover.py
homeassistant/components/switchbot/sensor.py
homeassistant/components/switchbot/coordinator.py
homeassistant/components/switchmate/switch.py
homeassistant/components/syncthing/__init__.py
homeassistant/components/syncthing/sensor.py
@@ -1101,12 +1016,8 @@ 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
@@ -1117,6 +1028,7 @@ 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
@@ -1137,6 +1049,14 @@ omit =
homeassistant/components/telnet/switch.py
homeassistant/components/temper/sensor.py
homeassistant/components/tensorflow/image_processing.py
homeassistant/components/tesla/__init__.py
homeassistant/components/tesla/binary_sensor.py
homeassistant/components/tesla/climate.py
homeassistant/components/tesla/const.py
homeassistant/components/tesla/device_tracker.py
homeassistant/components/tesla/lock.py
homeassistant/components/tesla/sensor.py
homeassistant/components/tesla/switch.py
homeassistant/components/tfiac/climate.py
homeassistant/components/thermoworks_smoke/sensor.py
homeassistant/components/thethingsnetwork/*
@@ -1154,14 +1074,6 @@ omit =
homeassistant/components/todoist/calendar.py
homeassistant/components/todoist/const.py
homeassistant/components/tof/sensor.py
homeassistant/components/tolo/__init__.py
homeassistant/components/tolo/binary_sensor.py
homeassistant/components/tolo/button.py
homeassistant/components/tolo/climate.py
homeassistant/components/tolo/fan.py
homeassistant/components/tolo/light.py
homeassistant/components/tolo/select.py
homeassistant/components/tolo/sensor.py
homeassistant/components/tomato/device_tracker.py
homeassistant/components/toon/__init__.py
homeassistant/components/toon/binary_sensor.py
@@ -1178,25 +1090,18 @@ omit =
homeassistant/components/totalconnect/binary_sensor.py
homeassistant/components/totalconnect/const.py
homeassistant/components/touchline/climate.py
homeassistant/components/tplink/common.py
homeassistant/components/tplink/switch.py
homeassistant/components/tplink_lte/*
homeassistant/components/traccar/device_tracker.py
homeassistant/components/traccar/const.py
homeassistant/components/trackr/device_tracker.py
homeassistant/components/tractive/__init__.py
homeassistant/components/tractive/binary_sensor.py
homeassistant/components/tractive/device_tracker.py
homeassistant/components/tractive/entity.py
homeassistant/components/tractive/sensor.py
homeassistant/components/tractive/switch.py
homeassistant/components/tradfri/__init__.py
homeassistant/components/tradfri/base_class.py
homeassistant/components/tradfri/config_flow.py
homeassistant/components/tradfri/cover.py
homeassistant/components/tradfri/fan.py
homeassistant/components/tradfri/light.py
homeassistant/components/tradfri/sensor.py
homeassistant/components/tradfri/switch.py
homeassistant/components/tradfri/*
homeassistant/components/trafikverket_train/sensor.py
homeassistant/components/trafikverket_weatherstation/__init__.py
homeassistant/components/trafikverket_weatherstation/sensor.py
homeassistant/components/transmission/sensor.py
homeassistant/components/transmission/switch.py
@@ -1204,26 +1109,15 @@ 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
homeassistant/components/tuya/camera.py
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
homeassistant/components/tuya/number.py
homeassistant/components/tuya/scene.py
homeassistant/components/tuya/select.py
homeassistant/components/tuya/sensor.py
homeassistant/components/tuya/siren.py
homeassistant/components/tuya/switch.py
homeassistant/components/tuya/util.py
homeassistant/components/tuya/vacuum.py
homeassistant/components/twentemilieu/const.py
homeassistant/components/twentemilieu/sensor.py
homeassistant/components/twilio_call/notify.py
homeassistant/components/twilio_sms/notify.py
homeassistant/components/twitter/notify.py
@@ -1240,11 +1134,7 @@ omit =
homeassistant/components/upnp/*
homeassistant/components/upc_connect/*
homeassistant/components/uscis/sensor.py
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/vallox/*
homeassistant/components/vasttrafik/sensor.py
homeassistant/components/velbus/__init__.py
homeassistant/components/velbus/binary_sensor.py
@@ -1255,16 +1145,12 @@ omit =
homeassistant/components/velbus/sensor.py
homeassistant/components/velbus/switch.py
homeassistant/components/velux/*
homeassistant/components/venstar/__init__.py
homeassistant/components/venstar/binary_sensor.py
homeassistant/components/venstar/climate.py
homeassistant/components/venstar/sensor.py
homeassistant/components/verisure/__init__.py
homeassistant/components/verisure/alarm_control_panel.py
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
@@ -1274,22 +1160,14 @@ 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
homeassistant/components/vicare/sensor.py
homeassistant/components/vicare/water_heater.py
homeassistant/components/vicare/*
homeassistant/components/vilfo/__init__.py
homeassistant/components/vilfo/sensor.py
homeassistant/components/vilfo/const.py
homeassistant/components/vivotek/camera.py
homeassistant/components/vlc/media_player.py
homeassistant/components/vlc_telnet/__init__.py
homeassistant/components/vlc_telnet/media_player.py
homeassistant/components/volkszaehler/sensor.py
homeassistant/components/volumio/__init__.py
@@ -1301,12 +1179,13 @@ omit =
homeassistant/components/waterfurnace/*
homeassistant/components/watson_iot/*
homeassistant/components/watson_tts/tts.py
homeassistant/components/watttime/__init__.py
homeassistant/components/watttime/sensor.py
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/wink/*
homeassistant/components/wirelesstag/*
homeassistant/components/wolflink/__init__.py
homeassistant/components/wolflink/sensor.py
@@ -1349,20 +1228,16 @@ omit =
homeassistant/components/xiaomi_miio/select.py
homeassistant/components/xiaomi_miio/sensor.py
homeassistant/components/xiaomi_miio/switch.py
homeassistant/components/xiaomi_miio/vacuum.py
homeassistant/components/xiaomi_tv/media_player.py
homeassistant/components/xmpp/notify.py
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
@@ -1410,6 +1285,5 @@ exclude_lines =
raise AssertionError
raise NotImplementedError
# TYPE_CHECKING and @overload blocks are never executed during pytest run
# TYPE_CHECKING block is never executed during pytest run
if TYPE_CHECKING:
@overload

View File

@@ -15,7 +15,7 @@ body:
attributes:
label: The problem
description: >-
Describe the issue you are experiencing here, to communicate to the
Describe the issue you are experiencing here to communicate to the
maintainers. Tell us what you were trying to do and what happened.
Provide a clear and concise description of what the problem is.
@@ -28,12 +28,10 @@ body:
validations:
required: true
attributes:
label: What version of Home Assistant Core has the issue?
label: What is version of Home Assistant Core has the issue?
placeholder: core-
description: >
Can be found in: [Configuration panel -> Info](https://my.home-assistant.io/redirect/info/).
[![Open your Home Assistant instance and show your Home Assistant version information.](https://my.home-assistant.io/badges/info.svg)](https://my.home-assistant.io/redirect/info/)
Can be found in the Configuration panel -> Info.
- type: input
attributes:
label: What was the last working version of Home Assistant Core?
@@ -46,9 +44,7 @@ body:
attributes:
label: What type of installation are you running?
description: >
Can be found in: [Configuration panel -> Info](https://my.home-assistant.io/redirect/info/).
[![Open your Home Assistant instance and show your Home Assistant version information.](https://my.home-assistant.io/badges/info.svg)](https://my.home-assistant.io/redirect/info/)
If you don't know, you can find it in: Configuration panel -> Info.
options:
- Home Assistant OS
- Home Assistant Container
@@ -59,15 +55,15 @@ body:
attributes:
label: Integration causing the issue
description: >
The name of the integration. For example: Automation, Philips Hue
The name of the integration, for example, Automation or Philips Hue.
- type: input
id: integration_link
attributes:
label: Link to integration documentation on our website
placeholder: "https://www.home-assistant.io/integrations/..."
description: |
Providing a link [to the documentation][docs] helps us categorize the
issue, while also providing a useful reference for others.
Providing a link [to the documentation][docs] help us categorizing the
issue, while providing a useful reference at the same time.
[docs]: https://www.home-assistant.io/integrations
@@ -79,8 +75,8 @@ body:
attributes:
label: Example YAML snippet
description: |
If applicable, please provide an example piece of YAML that can help reproduce this problem.
This can be from an automation, script, scene or configuration.
If this issue has an example piece of YAML that can help reproducing this problem, please provide.
This can be an piece of YAML from, e.g., an automation, script, scene or configuration.
render: yaml
- type: textarea
attributes:
@@ -92,3 +88,5 @@ body:
label: Additional information
description: >
If you have any additional information for us, use the field below.
Please note, you can attach screenshots or screen recordings here, by
dragging and dropping files in the field below.

View File

@@ -107,7 +107,7 @@ To help with the load of incoming pull requests:
- [ ] I have reviewed two other [open pull requests][prs] in this repository.
[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
[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
<!--
Thank you for contributing <3

View File

@@ -10,12 +10,11 @@ on:
env:
BUILD_TYPE: core
DEFAULT_PYTHON: 3.9
DEFAULT_PYTHON: 3.8
jobs:
init:
name: Initialize build
if: github.repository_owner == 'home-assistant'
runs-on: ubuntu-latest
outputs:
architectures: ${{ steps.info.outputs.architectures }}
@@ -24,12 +23,12 @@ jobs:
publish: ${{ steps.version.outputs.publish }}
steps:
- name: Checkout the repository
uses: actions/checkout@v2.4.0
uses: actions/checkout@v2.3.4
with:
fetch-depth: 0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.3.1
uses: actions/setup-python@v2.2.2
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -57,29 +56,29 @@ jobs:
uses: home-assistant/actions/helpers/codenotary@master
with:
source: file://${{ github.workspace }}/OFFICIAL_IMAGE
token: ${{ secrets.CAS_TOKEN }}
user: ${{ secrets.VCN_USER }}
password: ${{ secrets.VCN_PASSWORD }}
organisation: home-assistant.io
build_python:
name: Build PyPi package
needs: init
runs-on: ubuntu-latest
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
if: needs.init.outputs.publish == 'true'
steps:
- name: Checkout the repository
uses: actions/checkout@v2.4.0
uses: actions/checkout@v2.3.4
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.3.1
uses: actions/setup-python@v2.2.2
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Build package
shell: bash
run: |
# Remove dist, build, and homeassistant.egg-info
# when build locally for testing!
pip install twine build
python -m build
pip install twine wheel
python setup.py sdist bdist_wheel
- name: Upload package
shell: bash
@@ -91,7 +90,6 @@ jobs:
build_base:
name: Build ${{ matrix.arch }} base core image
if: github.repository_owner == 'home-assistant'
needs: init
runs-on: ubuntu-latest
strategy:
@@ -99,11 +97,11 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v2.4.0
uses: actions/checkout@v2.3.4
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.channel == 'dev'
uses: actions/setup-python@v2.3.1
uses: actions/setup-python@v2.2.2
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -122,32 +120,31 @@ jobs:
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
- name: Login to DockerHub
uses: docker/login-action@v1.12.0
uses: docker/login-action@v1.10.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v1.12.0
uses: docker/login-action@v1.10.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image
uses: home-assistant/builder@2021.12.0
uses: home-assistant/builder@2021.07.0
with:
args: |
$BUILD_ARGS \
--${{ matrix.arch }} \
--target /data \
--with-codenotary "${{ secrets.VCN_USER }}" "${{ secrets.VCN_PASSWORD }}" "${{ secrets.VCN_ORG }}" \
--validate-from "${{ secrets.VCN_ORG }}" \
--generic ${{ needs.init.outputs.version }}
env:
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
build_machine:
name: Build ${{ matrix.machine }} machine core image
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_base"]
runs-on: ubuntu-latest
strategy:
@@ -173,50 +170,38 @@ jobs:
- tinker
steps:
- name: Checkout the repository
uses: actions/checkout@v2.4.0
- name: Set build additional args
run: |
# Create general tags
if [[ "${{ needs.init.outputs.version }}" =~ d ]]; then
echo "BUILD_ARGS=--additional-tag dev" >> $GITHUB_ENV
elif [[ "${{ needs.init.outputs.version }}" =~ b ]]; then
echo "BUILD_ARGS=--additional-tag beta" >> $GITHUB_ENV
else
echo "BUILD_ARGS=--additional-tag stable" >> $GITHUB_ENV
fi
uses: actions/checkout@v2.3.4
- name: Login to DockerHub
uses: docker/login-action@v1.12.0
uses: docker/login-action@v1.10.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v1.12.0
uses: docker/login-action@v1.10.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image
uses: home-assistant/builder@2021.12.0
uses: home-assistant/builder@2021.07.0
with:
args: |
$BUILD_ARGS \
--target /data/machine \
--with-codenotary "${{ secrets.VCN_USER }}" "${{ secrets.VCN_PASSWORD }}" "${{ secrets.VCN_ORG }}" \
--validate-from "${{ secrets.VCN_ORG }}" \
--machine "${{ needs.init.outputs.version }}=${{ matrix.machine }}"
env:
CAS_API_KEY: ${{ secrets.CAS_TOKEN }}
publish_ha:
name: Publish version files
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_machine"]
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2.4.0
uses: actions/checkout@v2.3.4
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master
@@ -244,28 +229,27 @@ jobs:
publish_container:
name: Publish meta container
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_base"]
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2.4.0
uses: actions/checkout@v2.3.4
- name: Login to DockerHub
uses: docker/login-action@v1.12.0
uses: docker/login-action@v1.10.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v1.12.0
uses: docker/login-action@v1.10.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install CAS tools
uses: home-assistant/actions/helpers/cas@master
- name: Install VCN tools
uses: home-assistant/actions/helpers/vcn@master
- name: Build Meta Image
shell: bash
@@ -309,7 +293,8 @@ jobs:
function validate_image() {
local image=${1}
if ! cas authenticate --signerID notary@home-assistant.io "docker://${image}"; then
state="$(vcn authenticate --org home-assistant.io --output json docker://${image} | jq '.verification.status // 2')"
if [[ "${state}" != "0" ]]; then
echo "Invalid signature!"
exit 1
fi

File diff suppressed because it is too large Load Diff

View File

@@ -7,15 +7,14 @@ on:
jobs:
lock:
if: github.repository_owner == 'home-assistant'
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v3
- uses: dessant/lock-threads@v2.1.2
with:
github-token: ${{ github.token }}
issue-inactive-days: "30"
exclude-issue-created-before: "2020-10-01T00:00:00Z"
issue-lock-inactive-days: "30"
issue-exclude-created-before: "2020-10-01T00:00:00Z"
issue-lock-reason: ""
pr-inactive-days: "1"
exclude-pr-created-before: "2020-11-01T00:00:00Z"
pr-lock-inactive-days: "1"
pr-exclude-created-before: "2020-11-01T00:00:00Z"
pr-lock-reason: ""

View File

@@ -1,18 +0,0 @@
{
"problemMatcher": [
{
"owner": "python",
"pattern": [
{
"regexp": "^=+ slowest durations =+$"
},
{
"regexp": "^((.*s)\\s(call|setup|teardown)\\s+(.*)::(.*))$",
"message": 1,
"file": 2,
"loop": true
}
]
}
]
}

View File

@@ -12,19 +12,18 @@ on:
- "**strings.json"
env:
DEFAULT_PYTHON: 3.9
DEFAULT_PYTHON: 3.8
jobs:
upload:
name: Upload
if: github.repository_owner == 'home-assistant'
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2.4.0
uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.3.1
uses: actions/setup-python@v2.2.2
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -36,14 +35,14 @@ jobs:
download:
name: Download
needs: upload
if: github.repository_owner == 'home-assistant' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2.4.0
uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.3.1
uses: actions/setup-python@v2.2.2
with:
python-version: ${{ env.DEFAULT_PYTHON }}

View File

@@ -16,13 +16,12 @@ on:
jobs:
init:
name: Initialize wheels builder
if: github.repository_owner == 'home-assistant'
runs-on: ubuntu-latest
outputs:
architectures: ${{ steps.info.outputs.architectures }}
steps:
- name: Checkout the repository
uses: actions/checkout@v2.4.0
uses: actions/checkout@v2.3.4
- name: Get information
id: info
@@ -43,27 +42,22 @@ 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.3.1
uses: actions/upload-artifact@v2.2.4
with:
name: env_file
path: ./.env_file
- name: Upload requirements_diff
uses: actions/upload-artifact@v2.3.1
uses: actions/upload-artifact@v2.2.4
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:
@@ -74,7 +68,7 @@ jobs:
- "3.9-alpine3.14"
steps:
- name: Checkout the repository
uses: actions/checkout@v2.4.0
uses: actions/checkout@v2.3.4
- name: Download env_file
uses: actions/download-artifact@v2
@@ -87,7 +81,7 @@ jobs:
name: requirements_diff
- name: Build wheels
uses: home-assistant/wheels@2022.01.2
uses: home-assistant/wheels@2021.07.0
with:
tag: ${{ matrix.tag }}
arch: ${{ matrix.arch }}
@@ -95,7 +89,7 @@ jobs:
wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-user: wheels
env-file: true
apk: "build-base;cmake;git;linux-headers;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;cargo"
apk: "build-base;cmake;git;linux-headers;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev"
pip: "Cython;numpy"
skip-binary: aiohttp
constraints: "homeassistant/package_constraints.txt"
@@ -104,7 +98,6 @@ 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:
@@ -115,7 +108,7 @@ jobs:
- "3.9-alpine3.14"
steps:
- name: Checkout the repository
uses: actions/checkout@v2.4.0
uses: actions/checkout@v2.3.4
- name: Download env_file
uses: actions/download-artifact@v2
@@ -157,7 +150,7 @@ jobs:
done
- name: Build wheels
uses: home-assistant/wheels@2022.01.2
uses: home-assistant/wheels@2021.07.0
with:
tag: ${{ matrix.tag }}
arch: ${{ matrix.arch }}
@@ -165,9 +158,9 @@ jobs:
wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-user: wheels
env-file: true
apk: "build-base;cmake;git;linux-headers;libexecinfo-dev;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;cargo"
apk: "build-base;cmake;git;linux-headers;libexecinfo-dev;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev"
pip: "Cython;numpy;scikit-build"
skip-binary: aiohttp,grpcio
skip-binary: aiohttp
constraints: "homeassistant/package_constraints.txt"
requirements-diff: 'requirements_diff.txt'
requirements: "requirements_all.txt"

2
.gitignore vendored
View File

@@ -1,4 +1,4 @@
/config
config/*
config2/*
tests/testing_config/deps

View File

@@ -1,11 +1,11 @@
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v2.31.0
rev: v2.23.3
hooks:
- id: pyupgrade
args: [--py39-plus]
args: [--py38-plus]
- repo: https://github.com/psf/black
rev: 21.12b0
rev: 21.7b0
hooks:
- id: black
args:
@@ -13,26 +13,26 @@ repos:
- --quiet
files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$
- repo: https://github.com/codespell-project/codespell
rev: v2.1.0
rev: v2.0.0
hooks:
- id: codespell
args:
- --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
- --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
- --skip="./.*,*.csv,*.json"
- --quiet-level=2
exclude_types: [csv, json]
exclude: ^tests/fixtures/
- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2
hooks:
- id: flake8
additional_dependencies:
- pycodestyle==2.8.0
- pyflakes==2.4.0
- pycodestyle==2.7.0
- pyflakes==2.3.1
- flake8-docstrings==1.6.0
- pydocstyle==6.1.1
- flake8-comprehensions==3.7.0
- flake8-noqa==1.2.1
- pydocstyle==6.0.0
- flake8-comprehensions==3.5.0
- flake8-noqa==1.1.0
- mccabe==0.6.1
files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit
@@ -45,7 +45,7 @@ repos:
- --configfile=tests/bandit.yaml
files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/isort
rev: 5.10.0
rev: 5.9.3
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
@@ -61,7 +61,7 @@ repos:
- --branch=master
- --branch=rc
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.26.3
rev: v1.26.1
hooks:
- id: yamllint
- repo: https://github.com/pre-commit/mirrors-prettier
@@ -95,30 +95,17 @@ 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|setup\.cfg|\.pre-commit-config\.yaml|script/gen_requirements_all\.py)$
files: ^(homeassistant/.+/manifest\.json|\.pre-commit-config\.yaml)$
- 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|\.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)$
files: ^(homeassistant/.+/(manifest|strings)\.json|\.coveragerc|homeassistant/.+/services\.yaml)$

View File

@@ -2,31 +2,7 @@
# 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.*
@@ -41,161 +17,99 @@ 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.*
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.*
homeassistant.components.devolo_home_network.*
homeassistant.components.dlna_dmr.*
homeassistant.components.dnsip.*
homeassistant.components.dsmr.*
homeassistant.components.dunehd.*
homeassistant.components.efergy.*
homeassistant.components.elgato.*
homeassistant.components.esphome.*
homeassistant.components.energy.*
homeassistant.components.evil_genius_labs.*
homeassistant.components.fastdotcom.*
homeassistant.components.fitbit.*
homeassistant.components.flunearyou.*
homeassistant.components.flux_led.*
homeassistant.components.forecast_solar.*
homeassistant.components.fritzbox.*
homeassistant.components.fronius.*
homeassistant.components.frontend.*
homeassistant.components.fritz.*
homeassistant.components.geo_location.*
homeassistant.components.gios.*
homeassistant.components.goalzero.*
homeassistant.components.greeneye_monitor.*
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.*
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.*
homeassistant.components.modem_callerid.*
homeassistant.components.media_source.*
homeassistant.components.mysensors.*
homeassistant.components.nam.*
homeassistant.components.nanoleaf.*
homeassistant.components.neato.*
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.*
homeassistant.components.recorder.purge
homeassistant.components.recorder.repack
homeassistant.components.recorder.statistics
homeassistant.components.remote.*
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.*
homeassistant.components.switch.*
homeassistant.components.switcher_kis.*
homeassistant.components.synology_dsm.*
homeassistant.components.systemmonitor.*
homeassistant.components.tag.*
homeassistant.components.tailscale.*
homeassistant.components.tautulli.*
homeassistant.components.tcp.*
homeassistant.components.tile.*
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.*
homeassistant.components.vacuum.*
homeassistant.components.vallox.*
homeassistant.components.velbus.*
homeassistant.components.vlc_telnet.*
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
View File

@@ -16,7 +16,9 @@
"label": "Pytest",
"type": "shell",
"command": "pytest --timeout=10 tests",
"dependsOn": ["Install all Test Requirements"],
"dependsOn": [
"Install all Test Requirements"
],
"group": {
"kind": "test",
"isDefault": true
@@ -45,7 +47,9 @@
"label": "Pylint",
"type": "shell",
"command": "pylint homeassistant",
"dependsOn": ["Install all Requirements"],
"dependsOn": [
"Install all Requirements"
],
"group": {
"kind": "test",
"isDefault": true
@@ -60,7 +64,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 --numprocesses=auto",
"command": "pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing",
"group": {
"kind": "test",
"isDefault": true
@@ -112,21 +116,6 @@
"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": [

File diff suppressed because it is too large Load Diff

View File

@@ -7,21 +7,12 @@ ENV \
WORKDIR /usr/src
## Setup Home Assistant Core dependencies
COPY requirements.txt homeassistant/
COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
RUN \
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
-r homeassistant/requirements.txt
COPY requirements_all.txt homeassistant/
RUN \
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
-r homeassistant/requirements_all.txt
## Setup Home Assistant Core
## Setup Home Assistant
COPY . homeassistant/
RUN \
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
-r homeassistant/requirements_all.txt \
&& pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
-e ./homeassistant \
&& python3 -m compileall homeassistant/homeassistant

View File

@@ -17,7 +17,6 @@ RUN \
libswresample-dev \
libavfilter-dev \
libpcap-dev \
libturbojpeg0 \
git \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
@@ -31,12 +30,11 @@ RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
WORKDIR /workspaces
# Install Python dependencies from requirements
COPY requirements.txt ./
COPY requirements.txt requirements_test.txt requirements_test_pre_commit.txt ./
COPY homeassistant/package_constraints.txt homeassistant/package_constraints.txt
RUN pip3 install -r requirements.txt
COPY requirements_test.txt requirements_test_pre_commit.txt ./
RUN pip3 install -r requirements_test.txt
RUN rm -rf requirements.txt requirements_test.txt requirements_test_pre_commit.txt homeassistant/
RUN pip3 install -r requirements.txt \
&& pip3 install -r requirements_test.txt \
&& rm -rf requirements.txt requirements_test.txt requirements_test_pre_commit.txt homeassistant/
# Set the default shell to bash instead of sh
ENV SHELL /bin/bash

View File

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

22
build.json Normal file
View File

@@ -0,0 +1,22 @@
{
"image": "homeassistant/{arch}-homeassistant",
"shadow_repository": "ghcr.io/home-assistant",
"build_from": {
"aarch64": "ghcr.io/home-assistant/aarch64-homeassistant-base:2021.08.0",
"armhf": "ghcr.io/home-assistant/armhf-homeassistant-base:2021.08.0",
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.08.0",
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.08.0",
"i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.08.0"
},
"labels": {
"io.hass.type": "core",
"org.opencontainers.image.title": "Home Assistant",
"org.opencontainers.image.description": "Open-source home automation platform running on Python 3",
"org.opencontainers.image.source": "https://github.com/home-assistant/core",
"org.opencontainers.image.authors": "The Home Assistant Authors",
"org.opencontainers.image.url": "https://www.home-assistant.io/",
"org.opencontainers.image.documentation": "https://www.home-assistant.io/docs/",
"org.opencontainers.image.licenses": "Apache License 2.0"
},
"version_tag": true
}

View File

@@ -1,20 +0,0 @@
image: homeassistant/{arch}-homeassistant
shadow_repository: ghcr.io/home-assistant
build_from:
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2021.09.0
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2021.09.0
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2021.09.0
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2021.09.0
i386: ghcr.io/home-assistant/i386-homeassistant-base:2021.09.0
codenotary:
signer: notary@home-assistant.io
base_image: notary@home-assistant.io
labels:
io.hass.type: core
org.opencontainers.image.title: Home Assistant
org.opencontainers.image.description: Open-source home automation platform running on Python 3
org.opencontainers.image.source: https://github.com/home-assistant/core
org.opencontainers.image.authors: The Home Assistant Authors
org.opencontainers.image.url: https://www.home-assistant.io/
org.opencontainers.image.documentation: https://www.home-assistant.io/docs/
org.opencontainers.image.licenses: Apache License 2.0

View File

@@ -6,28 +6,4 @@ coverage:
default:
target: 90
threshold: 0.09
config-flows:
target: auto
threshold: 1
paths:
- homeassistant/components/*/config_flow.py
patch:
default:
target: auto
config-flows:
target: 100
threshold: 0
paths:
- homeassistant/components/*/config_flow.py
comment: false
# To make partial tests possible,
# we need to carry forward.
flag_management:
default_rules:
carryforward: false
individual_flags:
- name: full-suite
paths:
- ".*"
carryforward: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -2,21 +2,13 @@
from __future__ import annotations
import argparse
import faulthandler
import os
import platform
import subprocess
import sys
import threading
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)
from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__
def validate_python() -> None:
@@ -32,7 +24,7 @@ def validate_python() -> None:
def ensure_config_path(config_dir: str) -> None:
"""Validate the configuration directory."""
# pylint: disable=import-outside-toplevel
from . import config as config_util
import homeassistant.config as config_util
lib_dir = os.path.join(config_dir, "deps")
@@ -66,11 +58,10 @@ def ensure_config_path(config_dir: str) -> None:
def get_arguments() -> argparse.Namespace:
"""Get parsed passed in arguments."""
# pylint: disable=import-outside-toplevel
from . import config as config_util
import homeassistant.config as config_util
parser = argparse.ArgumentParser(
description="Home Assistant: Observe, Control, Automate.",
epilog=f"If restart is requested, exits with code {RESTART_EXIT_CODE}",
description="Home Assistant: Observe, Control, Automate."
)
parser.add_argument("--version", action="version", version=__version__)
parser.add_argument(
@@ -97,6 +88,12 @@ 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,
@@ -113,31 +110,123 @@ def get_arguments() -> argparse.Namespace:
"--log-no-color", action="store_true", help="Disable color logs"
)
parser.add_argument(
"--script", nargs=argparse.REMAINDER, help="Run one of the embedded scripts"
"--runner",
action="store_true",
help=f"On restart exit with code {RESTART_EXIT_CODE}",
)
parser.add_argument(
"--ignore-os-check",
action="store_true",
help="Skips validation of operating system",
"--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"
)
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
pid = os.fork()
if pid > 0:
sys.exit(0)
# Decouple fork
os.setsid()
# Create second fork
pid = os.fork()
if pid > 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, "-m", "homeassistant"] + list(sys.argv[1:])
return [sys.executable] + [arg for arg in sys.argv if arg != "--daemon"]
return sys.argv
return [arg for arg in sys.argv if arg != "--daemon"]
def check_threads() -> None:
"""Check if there are any lingering threads."""
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.
try:
nthreads = sum(
thread.is_alive() and not thread.daemon for thread in threading.enumerate()
@@ -151,27 +240,64 @@ def check_threads() -> 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()
args = get_arguments()
# 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)
if not args.ignore_os_check:
validate_os()
args = get_arguments()
if args.script is not None:
# pylint: disable=import-outside-toplevel
from . import scripts
from homeassistant 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 . import runner
from homeassistant import runner
runtime_conf = runner.RuntimeConfig(
config_dir=config_dir,
@@ -185,16 +311,9 @@ def main() -> int:
open_ui=args.open_ui,
)
fault_file_name = os.path.join(config_dir, FAULT_LOG_FILENAME)
with open(fault_file_name, mode="a", encoding="utf8") as fault_file:
faulthandler.enable(fault_file)
exit_code = runner.run(runtime_conf)
faulthandler.disable()
if os.path.getsize(fault_file_name) == 0:
os.remove(fault_file_name)
check_threads()
exit_code = runner.run(runtime_conf)
if exit_code == RESTART_EXIT_CODE and not args.runner:
try_to_restart()
return exit_code

View File

@@ -1,42 +0,0 @@
"""Provide backwards compat for async_timeout."""
from __future__ import annotations
import asyncio
from typing import Any
import async_timeout
from .helpers.frame import report
def timeout(
delay: float | None, loop: asyncio.AbstractEventLoop | None = None
) -> async_timeout.Timeout:
"""Backwards compatible timeout context manager that warns with loop usage."""
if loop is None:
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.3",
error_if_core=False,
)
if delay is not None:
deadline: float | None = loop.time() + delay
else:
deadline = None
return async_timeout.Timeout(deadline, loop)
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.3; use asyncio.current_task instead",
error_if_core=False,
)
return asyncio.current_task()
def enable() -> None:
"""Enable backwards compat transitions."""
async_timeout.timeout = timeout
async_timeout.current_task = current_task # type: ignore[attr-defined]

View File

@@ -3,14 +3,13 @@ from __future__ import annotations
import asyncio
from collections import OrderedDict
from collections.abc import Mapping
from datetime import timedelta
from typing import Any, Optional, cast
from typing import Any, Dict, Mapping, Optional, Tuple, cast
import jwt
from homeassistant import data_entry_flow
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.util import dt as dt_util
@@ -22,9 +21,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):
@@ -156,7 +155,6 @@ class AuthManager:
self._providers = providers
self._mfa_modules = mfa_modules
self.login_flow = AuthManagerFlowManager(hass, self)
self._revoke_callbacks: dict[str, list[CALLBACK_TYPE]] = {}
@property
def auth_providers(self) -> list[AuthProvider]:
@@ -215,19 +213,11 @@ class AuthManager:
return None
async def async_create_system_user(
self,
name: str,
*,
group_ids: list[str] | None = None,
local_only: bool | None = None,
self, name: str, group_ids: list[str] | None = None
) -> models.User:
"""Create a system user."""
user = await self._store.async_create_user(
name=name,
system_generated=True,
is_active=True,
group_ids=group_ids or [],
local_only=local_only,
name=name, system_generated=True, is_active=True, group_ids=group_ids or []
)
self.hass.bus.async_fire(EVENT_USER_ADDED, {"user_id": user.id})
@@ -235,18 +225,13 @@ class AuthManager:
return user
async def async_create_user(
self,
name: str,
*,
group_ids: list[str] | None = None,
local_only: bool | None = None,
self, name: str, group_ids: list[str] | None = None
) -> models.User:
"""Create a user."""
kwargs: dict[str, Any] = {
"name": name,
"is_active": True,
"group_ids": group_ids or [],
"local_only": local_only,
}
if await self._user_should_be_owner():
@@ -290,12 +275,6 @@ class AuthManager:
self, user: models.User, credentials: models.Credentials
) -> None:
"""Link credentials to an existing user."""
linked_user = await self.async_get_user_by_credentials(credentials)
if linked_user == user:
return
if linked_user is not None:
raise ValueError("Credential is already linked to a user")
await self._store.async_link_user(user, credentials)
async def async_remove_user(self, user: models.User) -> None:
@@ -306,7 +285,7 @@ class AuthManager:
]
if tasks:
await asyncio.gather(*tasks)
await asyncio.wait(tasks)
await self._store.async_remove_user(user)
@@ -318,18 +297,13 @@ class AuthManager:
name: str | None = None,
is_active: bool | None = None,
group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> None:
"""Update a user."""
kwargs: dict[str, Any] = {}
for attr_name, value in (
("name", name),
("group_ids", group_ids),
("local_only", local_only),
):
if value is not None:
kwargs[attr_name] = value
if name is not None:
kwargs["name"] = name
if group_ids is not None:
kwargs["group_ids"] = group_ids
await self._store.async_update_user(user, **kwargs)
if is_active is not None:
@@ -367,7 +341,8 @@ class AuthManager:
"System generated users cannot enable multi-factor auth module."
)
if (module := self.get_auth_mfa_module(mfa_module_id)) is None:
module = self.get_auth_mfa_module(mfa_module_id)
if module is None:
raise ValueError(f"Unable find multi-factor auth module: {mfa_module_id}")
await module.async_setup_user(user.id, data)
@@ -381,7 +356,8 @@ class AuthManager:
"System generated users cannot disable multi-factor auth module."
)
if (module := self.get_auth_mfa_module(mfa_module_id)) is None:
module = self.get_auth_mfa_module(mfa_module_id)
if module is None:
raise ValueError(f"Unable find multi-factor auth module: {mfa_module_id}")
await module.async_depose_user(user.id)
@@ -472,28 +448,6 @@ class AuthManager:
"""Delete a refresh token."""
await self._store.async_remove_refresh_token(refresh_token)
callbacks = self._revoke_callbacks.pop(refresh_token.id, [])
for revoke_callback in callbacks:
revoke_callback()
@callback
def async_register_revoke_token_callback(
self, refresh_token_id: str, revoke_callback: CALLBACK_TYPE
) -> CALLBACK_TYPE:
"""Register a callback to be called when the refresh token id is revoked."""
if refresh_token_id not in self._revoke_callbacks:
self._revoke_callbacks[refresh_token_id] = []
callbacks = self._revoke_callbacks[refresh_token_id]
callbacks.append(revoke_callback)
@callback
def unregister() -> None:
if revoke_callback in callbacks:
callbacks.remove(revoke_callback)
return unregister
@callback
def async_create_access_token(
self, refresh_token: models.RefreshToken, remote_ip: str | None = None
@@ -512,7 +466,7 @@ class AuthManager:
},
refresh_token.jwt_key,
algorithm="HS256",
)
).decode()
@callback
def _async_resolve_provider(
@@ -544,7 +498,8 @@ class AuthManager:
Will raise InvalidAuthError on errors.
"""
if provider := self._async_resolve_provider(refresh_token):
provider = self._async_resolve_provider(refresh_token)
if provider:
provider.async_validate_refresh_token(refresh_token, remote_ip)
async def async_validate_access_token(
@@ -552,9 +507,7 @@ class AuthManager:
) -> models.RefreshToken | None:
"""Return refresh token if an access token is valid."""
try:
unverif_claims = jwt.decode(
token, algorithms=["HS256"], options={"verify_signature": False}
)
unverif_claims = jwt.decode(token, verify=False)
except jwt.InvalidTokenError:
return None

View File

@@ -8,20 +8,17 @@ 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 (
ACCESS_TOKEN_EXPIRATION,
GROUP_ID_ADMIN,
GROUP_ID_READ_ONLY,
GROUP_ID_USER,
)
from .permissions import system_policies
from .permissions.models import PermissionLookup
from .const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY, GROUP_ID_USER
from .permissions import PermissionLookup, system_policies
from .permissions.types import PolicyType
# mypy: disallow-any-generics
STORAGE_VERSION = 1
STORAGE_KEY = "auth"
GROUP_NAME_ADMIN = "Administrators"
@@ -45,7 +42,7 @@ class AuthStore:
self._groups: dict[str, models.Group] | None = None
self._perm_lookup: PermissionLookup | None = None
self._store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
STORAGE_VERSION, STORAGE_KEY, private=True
)
self._lock = asyncio.Lock()
@@ -89,7 +86,6 @@ class AuthStore:
system_generated: bool | None = None,
credentials: models.Credentials | None = None,
group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> models.User:
"""Create a new user."""
if self._users is None:
@@ -100,7 +96,8 @@ class AuthStore:
groups = []
for group_id in group_ids or []:
if (group := self._groups.get(group_id)) is None:
group = self._groups.get(group_id)
if group is None:
raise ValueError(f"Invalid group specified {group_id}")
groups.append(group)
@@ -112,14 +109,14 @@ class AuthStore:
"perm_lookup": self._perm_lookup,
}
for attr_name, value in (
("is_owner", is_owner),
("is_active", is_active),
("local_only", local_only),
("system_generated", system_generated),
):
if value is not None:
kwargs[attr_name] = value
if is_owner is not None:
kwargs["is_owner"] = is_owner
if is_active is not None:
kwargs["is_active"] = is_active
if system_generated is not None:
kwargs["system_generated"] = system_generated
new_user = models.User(**kwargs)
@@ -156,7 +153,6 @@ class AuthStore:
name: str | None = None,
is_active: bool | None = None,
group_ids: list[str] | None = None,
local_only: bool | None = None,
) -> None:
"""Update a user."""
assert self._groups is not None
@@ -164,18 +160,15 @@ class AuthStore:
if group_ids is not None:
groups = []
for grid in group_ids:
if (group := self._groups.get(grid)) is None:
group = self._groups.get(grid)
if group is None:
raise ValueError("Invalid group specified.")
groups.append(group)
user.groups = groups
user.invalidate_permission_cache()
for attr_name, value in (
("name", name),
("is_active", is_active),
("local_only", local_only),
):
for attr_name, value in (("name", name), ("is_active", is_active)):
if value is not None:
setattr(user, attr_name, value)
@@ -426,8 +419,6 @@ class AuthStore:
is_active=user_dict["is_active"],
system_generated=user_dict["system_generated"],
perm_lookup=perm_lookup,
# New in 2021.11
local_only=user_dict.get("local_only", False),
)
for cred_dict in data["credentials"]:
@@ -455,14 +446,16 @@ class AuthStore:
)
continue
if (token_type := rt_dict.get("token_type")) is None:
token_type = rt_dict.get("token_type")
if token_type is None:
if rt_dict["client_id"] is None:
token_type = models.TOKEN_TYPE_SYSTEM
else:
token_type = models.TOKEN_TYPE_NORMAL
# old refresh_token don't have last_used_at (pre-0.78)
if last_used_at_str := rt_dict.get("last_used_at"):
last_used_at_str = rt_dict.get("last_used_at")
if last_used_at_str:
last_used_at = dt_util.parse_datetime(last_used_at_str)
else:
last_used_at = None
@@ -513,7 +506,6 @@ class AuthStore:
"is_active": user.is_active,
"name": user.name,
"system_generated": user.system_generated,
"local_only": user.local_only,
}
for user in self._users.values()
]

View File

@@ -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)
config = module.CONFIG_SCHEMA(config) # type: ignore
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
hass, module_path, module.REQUIREMENTS # type: ignore
)
processed.add(module_name)

View File

@@ -38,12 +38,12 @@ class InsecureExampleModule(MultiFactorAuthModule):
@property
def input_schema(self) -> vol.Schema:
"""Validate login flow input data."""
return vol.Schema({vol.Required("pin"): str})
return vol.Schema({"pin": str})
@property
def setup_schema(self) -> vol.Schema:
"""Validate async_setup_user input data."""
return vol.Schema({vol.Required("pin"): str})
return vol.Schema({"pin": str})
async def async_setup_flow(self, user_id: str) -> SetupFlow:
"""Return a data entry flow handler for setup module.

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
import asyncio
from collections import OrderedDict
import logging
from typing import Any
from typing import Any, Dict
import attr
import voluptuous as vol
@@ -25,7 +25,7 @@ from . import (
SetupFlow,
)
REQUIREMENTS = ["pyotp==2.6.0"]
REQUIREMENTS = ["pyotp==2.3.0"]
CONF_MESSAGE = "message"
@@ -56,10 +56,10 @@ def _generate_secret() -> str:
def _generate_random() -> int:
"""Generate a 32 digit number."""
"""Generate a 8 digit number."""
import pyotp # pylint: disable=import-outside-toplevel
return int(pyotp.random_base32(length=32, chars=list("1234567890")))
return int(pyotp.random_base32(length=8, chars=list("1234567890")))
def _generate_otp(secret: str, count: int) -> str:
@@ -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")
@@ -100,7 +100,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
super().__init__(hass, config)
self._user_settings: _UsersDict | None = None
self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
STORAGE_VERSION, STORAGE_KEY, private=True
)
self._include = config.get(CONF_INCLUDE, [])
self._exclude = config.get(CONF_EXCLUDE, [])
@@ -110,7 +110,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
@property
def input_schema(self) -> vol.Schema:
"""Validate login flow input data."""
return vol.Schema({vol.Required(INPUT_FIELD_CODE): str})
return vol.Schema({INPUT_FIELD_CODE: str})
async def _async_load(self) -> None:
"""Load stored data."""
@@ -118,7 +118,9 @@ class NotifyAuthModule(MultiFactorAuthModule):
if self._user_settings is not None:
return
if (data := await self._user_store.async_load()) is None:
data = await self._user_store.async_load()
if data is None:
data = {STORAGE_USERS: {}}
self._user_settings = {
@@ -205,7 +207,8 @@ class NotifyAuthModule(MultiFactorAuthModule):
await self._async_load()
assert self._user_settings is not None
if (notify_setting := self._user_settings.get(user_id)) is None:
notify_setting = self._user_settings.get(user_id)
if notify_setting is None:
return False
# user_input has been validate in caller
@@ -222,7 +225,8 @@ class NotifyAuthModule(MultiFactorAuthModule):
await self._async_load()
assert self._user_settings is not None
if (notify_setting := self._user_settings.get(user_id)) is None:
notify_setting = self._user_settings.get(user_id)
if notify_setting is None:
raise ValueError("Cannot find user_id")
def generate_secret_and_one_time_password() -> str:
@@ -245,7 +249,8 @@ class NotifyAuthModule(MultiFactorAuthModule):
await self._async_load()
assert self._user_settings is not None
if (notify_setting := self._user_settings.get(user_id)) is None:
notify_setting = self._user_settings.get(user_id)
if notify_setting is None:
_LOGGER.error("Cannot find user %s", user_id)
return

View File

@@ -18,7 +18,7 @@ from . import (
SetupFlow,
)
REQUIREMENTS = ["pyotp==2.6.0", "PyQRCode==1.2.1"]
REQUIREMENTS = ["pyotp==2.3.0", "PyQRCode==1.2.1"]
CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({}, extra=vol.PREVENT_EXTRA)
@@ -77,14 +77,14 @@ class TotpAuthModule(MultiFactorAuthModule):
super().__init__(hass, config)
self._users: dict[str, str] | None = None
self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
STORAGE_VERSION, STORAGE_KEY, private=True
)
self._init_lock = asyncio.Lock()
@property
def input_schema(self) -> vol.Schema:
"""Validate login flow input data."""
return vol.Schema({vol.Required(INPUT_FIELD_CODE): str})
return vol.Schema({INPUT_FIELD_CODE: str})
async def _async_load(self) -> None:
"""Load stored data."""
@@ -92,7 +92,9 @@ class TotpAuthModule(MultiFactorAuthModule):
if self._users is not None:
return
if (data := await self._user_store.async_load()) is None:
data = await self._user_store.async_load()
if data is None:
data = {STORAGE_USERS: {}}
self._users = data.get(STORAGE_USERS, {})
@@ -161,7 +163,8 @@ class TotpAuthModule(MultiFactorAuthModule):
"""Validate two factor authentication code."""
import pyotp # pylint: disable=import-outside-toplevel
if (ota_secret := self._users.get(user_id)) is None: # type: ignore
ota_secret = self._users.get(user_id) # type: ignore
if ota_secret is None:
# even we cannot find user, we still do verify
# to make timing the same as if user was found.
pyotp.TOTP(DUMMY_SECRET).verify(code, valid_window=1)
@@ -181,7 +184,7 @@ class TotpSetupFlow(SetupFlow):
# to fix typing complaint
self._auth_module: TotpAuthModule = auth_module
self._user = user
self._ota_secret: str = ""
self._ota_secret: str | None = None
self._url = None # type Optional[str]
self._image = None # type Optional[str]

View File

@@ -39,7 +39,6 @@ class User:
is_owner: bool = attr.ib(default=False)
is_active: bool = attr.ib(default=False)
system_generated: bool = attr.ib(default=False)
local_only: bool = attr.ib(default=False)
groups: list[Group] = attr.ib(factory=list, eq=False, order=False)

View File

@@ -1,29 +1,21 @@
"""Permissions for Home Assistant."""
from __future__ import annotations
from collections.abc import Callable
from typing import Any
import logging
from typing import Any, Callable
import voluptuous as vol
from .const import CAT_ENTITIES
from .entities import ENTITY_POLICY_SCHEMA, compile_entities
from .merge import merge_policies
from .merge import merge_policies # noqa: F401
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",
]
_LOGGER = logging.getLogger(__name__)
class AbstractPermissions:
@@ -41,7 +33,9 @@ class AbstractPermissions:
def check_entity(self, entity_id: str, key: str) -> bool:
"""Check if we can access entity."""
if (entity_func := self._cached_entity_func) is None:
entity_func = self._cached_entity_func
if entity_func is None:
entity_func = self._cached_entity_func = self._entity_func()
return entity_func(entity_id, key)

View File

@@ -2,7 +2,7 @@
from __future__ import annotations
from collections import OrderedDict
from collections.abc import Callable
from typing import Callable
import voluptuous as vol

View File

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

View File

@@ -1,16 +1,15 @@
"""Helpers to deal with permissions."""
from __future__ import annotations
from collections.abc import Callable
from functools import wraps
from typing import Optional, cast
from typing import Callable, Dict, 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(
@@ -73,7 +72,8 @@ def compile_policy(
def apply_policy_funcs(object_id: str, key: str) -> bool:
"""Apply several policy functions."""
for func in funcs:
if (result := func(object_id, key)) is not None:
result = func(object_id, key)
if result is not None:
return result
return False

View File

@@ -22,6 +22,8 @@ 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"
@@ -140,7 +142,7 @@ async def auth_provider_from_config(
module = await load_auth_provider_module(hass, provider_name)
try:
config = module.CONFIG_SCHEMA(config)
config = module.CONFIG_SCHEMA(config) # type: ignore
except vol.Invalid as err:
_LOGGER.error(
"Invalid configuration for auth provider %s: %s",
@@ -167,12 +169,15 @@ async def load_auth_provider_module(
if hass.config.skip_pip or not hasattr(module, "REQUIREMENTS"):
return module
if (processed := hass.data.get(DATA_REQS)) is None:
processed = hass.data.get(DATA_REQS)
if processed is None:
processed = hass.data[DATA_REQS] = set()
elif provider in processed:
return module
reqs = module.REQUIREMENTS
# https://github.com/python/mypy/issues/1424
reqs = module.REQUIREMENTS # type: ignore
await requirements.async_process_requirements(
hass, f"auth provider {provider}", reqs
)

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
import asyncio
import collections
from collections.abc import Mapping
import logging
import os
@@ -16,6 +17,8 @@ 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"
@@ -145,13 +148,10 @@ class CommandLineLoginFlow(LoginFlow):
user_input.pop("password")
return await self.async_finish(user_input)
schema: dict[str, type] = collections.OrderedDict()
schema["username"] = str
schema["password"] = str
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required("username"): str,
vol.Required("password"): str,
}
),
errors=errors,
step_id="init", data_schema=vol.Schema(schema), errors=errors
)

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio
import base64
from collections import OrderedDict
from collections.abc import Mapping
import logging
from typing import Any, cast
@@ -18,6 +19,8 @@ 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"
@@ -61,7 +64,7 @@ class Data:
"""Initialize the user data store."""
self.hass = hass
self._store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
STORAGE_VERSION, STORAGE_KEY, private=True
)
self._data: dict[str, Any] | None = None
# Legacy mode will allow usernames to start/end with whitespace
@@ -79,7 +82,9 @@ class Data:
async def async_load(self) -> None:
"""Load stored data."""
if (data := await self._store.async_load()) is None:
data = await self._store.async_load()
if data is None:
data = {"users": []}
seen: set[str] = set()
@@ -88,7 +93,9 @@ class Data:
username = user["username"]
# check if we have duplicates
if (folded := username.casefold()) in seen:
folded = username.casefold()
if folded in seen:
self.is_legacy = True
logging.getLogger(__name__).warning(
@@ -332,13 +339,10 @@ class HassLoginFlow(LoginFlow):
user_input.pop("password")
return await self.async_finish(user_input)
schema: dict[str, type] = OrderedDict()
schema["username"] = str
schema["password"] = str
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required("username"): str,
vol.Required("password"): str,
}
),
errors=errors,
step_id="init", data_schema=vol.Schema(schema), errors=errors
)

View File

@@ -1,6 +1,7 @@
"""Example auth provider."""
from __future__ import annotations
from collections import OrderedDict
from collections.abc import Mapping
import hmac
from typing import Any, cast
@@ -14,6 +15,8 @@ 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,
@@ -100,7 +103,7 @@ class ExampleLoginFlow(LoginFlow):
self, user_input: dict[str, str] | None = None
) -> FlowResult:
"""Handle the step of the form."""
errors = None
errors = {}
if user_input is not None:
try:
@@ -108,19 +111,16 @@ class ExampleLoginFlow(LoginFlow):
user_input["username"], user_input["password"]
)
except InvalidAuthError:
errors = {"base": "invalid_auth"}
errors["base"] = "invalid_auth"
if not errors:
user_input.pop("password")
return await self.async_finish(user_input)
schema: dict[str, type] = OrderedDict()
schema["username"] = str
schema["password"] = str
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required("username"): str,
vol.Required("password"): str,
}
),
errors=errors,
step_id="init", data_schema=vol.Schema(schema), errors=errors
)

View File

@@ -19,6 +19,8 @@ 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"
@@ -100,7 +102,5 @@ class LegacyLoginFlow(LoginFlow):
return await self.async_finish({})
return self.async_show_form(
step_id="init",
data_schema=vol.Schema({vol.Required("password"): str}),
errors=errors,
step_id="init", data_schema=vol.Schema({"password": str}), errors=errors
)

View File

@@ -14,7 +14,7 @@ from ipaddress import (
ip_address,
ip_network,
)
from typing import Any, Union, cast
from typing import Any, Dict, List, Union, cast
import voluptuous as vol
@@ -27,6 +27,8 @@ 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]
@@ -74,12 +76,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]:
@@ -192,12 +194,6 @@ class TrustedNetworksAuthProvider(AuthProvider):
if any(ip_addr in trusted_proxy for trusted_proxy in self.trusted_proxies):
raise InvalidAuthError("Can't allow access from a proxy server")
if "cloud" in self.hass.config.components:
from hass_nabucasa import remote # pylint: disable=import-outside-toplevel
if remote.is_cloud_request.get():
raise InvalidAuthError("Can't allow access from Home Assistant Cloud")
@callback
def async_validate_refresh_token(
self, refresh_token: RefreshToken, remote_ip: str | None = None
@@ -248,7 +244,5 @@ class TrustedNetworksLoginFlow(LoginFlow):
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{vol.Required("user"): vol.In(self._available_users)}
),
data_schema=vol.Schema({"user": vol.In(self._available_users)}),
)

View File

@@ -1 +0,0 @@
"""Backports from newer Python versions."""

View File

@@ -1,33 +0,0 @@
"""Enum backports from standard lib."""
from __future__ import annotations
from enum import Enum
from typing import Any, TypeVar
T = TypeVar("T", bound="StrEnum")
class StrEnum(str, Enum):
"""Partial backport of Python 3.11's StrEnum for our basic use cases."""
def __new__(cls: type[T], value: str, *args: Any, **kwargs: Any) -> T:
"""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)
def __str__(self) -> str:
"""Return self.value."""
return str(self.value)
@staticmethod
def _generate_next_value_( # pylint: disable=arguments-differ # https://github.com/PyCQA/pylint/issues/5371
name: str, start: int, count: int, last_values: list[Any]
) -> Any:
"""
Make `auto()` explicitly unsupported.
We may revisit this when it's very clear that Python 3.11's
`StrEnum.auto()` behavior will no longer change.
"""
raise TypeError("auto() is not supported by this implementation")

View File

@@ -1,18 +1,14 @@
"""Block blocking calls being done in asyncio."""
"""Block I/O being done in asyncio."""
from http.client import HTTPConnection
import time
from .util.async_ import protect_loop
from homeassistant.util.async_ import protect_loop
def enable() -> None:
"""Enable the detection of blocking calls in the event loop."""
"""Enable the detection of I/O 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)

View File

@@ -15,28 +15,24 @@ from typing import TYPE_CHECKING, Any
import voluptuous as vol
import yarl
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 .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 (
from homeassistant import config as conf_util, config_entries, core, loader
from homeassistant.components import http
from homeassistant.const import REQUIRED_NEXT_PYTHON_DATE, REQUIRED_NEXT_PYTHON_VER
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 (
DATA_SETUP,
DATA_SETUP_STARTED,
DATA_SETUP_TIME,
async_set_domains_to_be_loaded,
async_setup_component,
)
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
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
if TYPE_CHECKING:
from .runner import RuntimeConfig
@@ -50,6 +46,7 @@ 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
@@ -112,8 +109,9 @@ async def async_setup_hass(
config_dict = None
basic_setup_success = False
safe_mode = runtime_config.safe_mode
if not (safe_mode := runtime_config.safe_mode):
if not safe_mode:
await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass)
try:
@@ -243,20 +241,18 @@ async def async_from_config_dict(
stop = monotonic()
_LOGGER.info("Home Assistant initialized in %.2fs", stop - start)
if (
REQUIRED_NEXT_PYTHON_HA_RELEASE
and sys.version_info[:3] < REQUIRED_NEXT_PYTHON_VER
):
if REQUIRED_NEXT_PYTHON_DATE and sys.version_info[:3] < REQUIRED_NEXT_PYTHON_VER:
msg = (
"Support for the running Python version "
f"{'.'.join(str(x) for x in sys.version_info[:3])} is deprecated and will "
f"be removed in Home Assistant {REQUIRED_NEXT_PYTHON_HA_RELEASE}. "
f"be removed in the first release after {REQUIRED_NEXT_PYTHON_DATE}. "
"Please upgrade Python to "
f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER[:2])}."
f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER)} or "
"higher."
)
_LOGGER.warning(msg)
persistent_notification.async_create(
hass, msg, "Python version", "python_version"
hass.components.persistent_notification.async_create(
msg, "Python version", "python_version"
)
return hass
@@ -372,7 +368,8 @@ async def async_mount_local_lib_path(config_dir: str) -> str:
This function is a coroutine.
"""
deps_dir = os.path.join(config_dir, "deps")
if (lib_dir := await async_get_user_site(deps_dir)) not in sys.path:
lib_dir = await async_get_user_site(deps_dir)
if lib_dir not in sys.path:
sys.path.insert(0, lib_dir)
return deps_dir
@@ -497,13 +494,17 @@ async def _async_set_up_integrations(
_LOGGER.info("Domains to be set up: %s", domains_to_setup)
logging_domains = domains_to_setup & LOGGING_INTEGRATIONS
# Load logging as soon as possible
if logging_domains := domains_to_setup & LOGGING_INTEGRATIONS:
if logging_domains:
_LOGGER.info("Setting up logging: %s", logging_domains)
await async_setup_multi_components(hass, logging_domains, config)
# Start up debuggers. Start these first in case they want to wait.
if debuggers := domains_to_setup & DEBUGGER_INTEGRATIONS:
debuggers = domains_to_setup & DEBUGGER_INTEGRATIONS
if debuggers:
_LOGGER.debug("Setting up debuggers: %s", debuggers)
await async_setup_multi_components(hass, debuggers, config)
@@ -523,7 +524,9 @@ async def _async_set_up_integrations(
stage_1_domains.add(domain)
if (dep_itg := integration_cache.get(domain)) is None:
dep_itg = integration_cache.get(domain)
if dep_itg is None:
continue
deps_promotion.update(dep_itg.all_dependencies)
@@ -561,14 +564,6 @@ async def _async_set_up_integrations(
except asyncio.TimeoutError:
_LOGGER.warning("Setup timed out for stage 2 - moving forward")
# Wrap up startup
_LOGGER.debug("Waiting for startup to wrap up")
try:
async with hass.timeout.async_timeout(WRAP_UP_TIMEOUT, cool_down=COOLDOWN_TIME):
await hass.async_block_till_done()
except asyncio.TimeoutError:
_LOGGER.warning("Setup timed out for bootstrap - moving forward")
watch_task.cancel()
async_dispatcher_send(hass, SIGNAL_BOOTSTRAP_INTEGRATONS, {})
@@ -581,3 +576,11 @@ async def _async_set_up_integrations(
)
},
)
# Wrap up startup
_LOGGER.debug("Waiting for startup to wrap up")
try:
async with hass.timeout.async_timeout(WRAP_UP_TIMEOUT, cool_down=COOLDOWN_TIME):
await hass.async_block_till_done()
except asyncio.TimeoutError:
_LOGGER.warning("Setup timed out for bootstrap - moving forward")

View File

@@ -1,17 +1,14 @@
"""Support for the Abode Security System."""
from __future__ import annotations
from functools import partial
from abodepy import Abode, AbodeAutomation as AbodeAuto
from abodepy.devices import AbodeDevice as AbodeDev
from abodepy import Abode
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,
@@ -19,14 +16,15 @@ from homeassistant.const import (
CONF_PASSWORD,
CONF_USERNAME,
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, entity
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.entity import Entity
from .const import ATTRIBUTION, CONF_POLLING, DEFAULT_CACHEDB, DOMAIN, LOGGER
from .const import ATTRIBUTION, DEFAULT_CACHEDB, DOMAIN, LOGGER
CONF_POLLING = "polling"
SERVICE_SETTINGS = "change_setting"
SERVICE_CAPTURE_IMAGE = "capture_image"
@@ -44,7 +42,7 @@ ATTR_APP_TYPE = "app_type"
ATTR_EVENT_BY = "event_by"
ATTR_VALUE = "value"
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
CONFIG_SCHEMA = cv.deprecated(DOMAIN)
CHANGE_SETTING_SCHEMA = vol.Schema(
{vol.Required(ATTR_SETTING): cv.string, vol.Required(ATTR_VALUE): cv.string}
@@ -55,39 +53,39 @@ CAPTURE_IMAGE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
AUTOMATION_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
PLATFORMS = [
Platform.ALARM_CONTROL_PANEL,
Platform.BINARY_SENSOR,
Platform.LOCK,
Platform.SWITCH,
Platform.COVER,
Platform.CAMERA,
Platform.LIGHT,
Platform.SENSOR,
"alarm_control_panel",
"binary_sensor",
"lock",
"switch",
"cover",
"camera",
"light",
"sensor",
]
class AbodeSystem:
"""Abode System class."""
def __init__(self, abode: Abode, polling: bool) -> None:
def __init__(self, abode, polling):
"""Initialize the system."""
self.abode = abode
self.polling = polling
self.entity_ids: set[str | None] = set()
self.entity_ids = set()
self.logout_listener = None
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass, config_entry):
"""Set up Abode integration from a config entry."""
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
polling = entry.data[CONF_POLLING]
username = config_entry.data.get(CONF_USERNAME)
password = config_entry.data.get(CONF_PASSWORD)
polling = config_entry.data.get(CONF_POLLING)
cache = hass.config.path(DEFAULT_CACHEDB)
# For previous config entries where unique_id is None
if entry.unique_id is None:
if config_entry.unique_id is None:
hass.config_entries.async_update_entry(
entry, unique_id=entry.data[CONF_USERNAME]
config_entry, unique_id=config_entry.data[CONF_USERNAME]
)
try:
@@ -103,7 +101,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data[DOMAIN] = AbodeSystem(abode, polling)
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
await setup_hass_events(hass)
await hass.async_add_executor_job(setup_hass_services, hass)
@@ -112,13 +110,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass, config_entry):
"""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(entry, PLATFORMS)
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.stop)
await hass.async_add_executor_job(hass.data[DOMAIN].abode.logout)
@@ -129,22 +129,22 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return unload_ok
def setup_hass_services(hass: HomeAssistant) -> None:
def setup_hass_services(hass):
"""Home Assistant services."""
def change_setting(call: ServiceCall) -> None:
def change_setting(call):
"""Change an Abode system setting."""
setting = call.data[ATTR_SETTING]
value = call.data[ATTR_VALUE]
setting = call.data.get(ATTR_SETTING)
value = call.data.get(ATTR_VALUE)
try:
hass.data[DOMAIN].abode.set_setting(setting, value)
except AbodeException as ex:
LOGGER.warning(ex)
def capture_image(call: ServiceCall) -> None:
def capture_image(call):
"""Capture a new image."""
entity_ids = call.data[ATTR_ENTITY_ID]
entity_ids = call.data.get(ATTR_ENTITY_ID)
target_entities = [
entity_id
@@ -156,9 +156,9 @@ def setup_hass_services(hass: HomeAssistant) -> None:
signal = f"abode_camera_capture_{entity_id}"
dispatcher_send(hass, signal)
def trigger_automation(call: ServiceCall) -> None:
def trigger_automation(call):
"""Trigger an Abode automation."""
entity_ids = call.data[ATTR_ENTITY_ID]
entity_ids = call.data.get(ATTR_ENTITY_ID)
target_entities = [
entity_id
@@ -183,10 +183,10 @@ def setup_hass_services(hass: HomeAssistant) -> None:
)
async def setup_hass_events(hass: HomeAssistant) -> None:
async def setup_hass_events(hass):
"""Home Assistant start and stop callbacks."""
def logout(event: Event) -> None:
def logout(event):
"""Logout of Abode."""
if not hass.data[DOMAIN].polling:
hass.data[DOMAIN].abode.events.stop()
@@ -202,10 +202,10 @@ async def setup_hass_events(hass: HomeAssistant) -> None:
)
def setup_abode_events(hass: HomeAssistant) -> None:
def setup_abode_events(hass):
"""Event callbacks."""
def event_callback(event: str, event_json: dict[str, str]) -> None:
def event_callback(event, event_json):
"""Handle an event callback from Abode."""
data = {
ATTR_DEVICE_ID: event_json.get(ATTR_DEVICE_ID, ""),
@@ -244,17 +244,21 @@ def setup_abode_events(hass: HomeAssistant) -> None:
)
class AbodeEntity(entity.Entity):
class AbodeEntity(Entity):
"""Representation of an Abode entity."""
_attr_attribution = ATTRIBUTION
def __init__(self, data: AbodeSystem) -> None:
def __init__(self, data):
"""Initialize Abode entity."""
self._data = data
self._available = True
self._attr_should_poll = data.polling
async def async_added_to_hass(self) -> None:
@property
def available(self):
"""Return the available state."""
return self._available
async def async_added_to_hass(self):
"""Subscribe to Abode connection status updates."""
await self.hass.async_add_executor_job(
self._data.abode.events.add_connection_status_callback,
@@ -264,29 +268,29 @@ class AbodeEntity(entity.Entity):
self.hass.data[DOMAIN].entity_ids.add(self.entity_id)
async def async_will_remove_from_hass(self) -> None:
async def async_will_remove_from_hass(self):
"""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) -> None:
def _update_connection_status(self):
"""Update the entity available property."""
self._attr_available = self._data.abode.events.connected
self._available = self._data.abode.events.connected
self.schedule_update_ha_state()
class AbodeDevice(AbodeEntity):
"""Representation of an Abode device."""
def __init__(self, data: AbodeSystem, device: AbodeDev) -> None:
def __init__(self, data, device):
"""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) -> None:
async def async_added_to_hass(self):
"""Subscribe to device events."""
await super().async_added_to_hass()
await self.hass.async_add_executor_job(
@@ -295,21 +299,22 @@ class AbodeDevice(AbodeEntity):
self._update_callback,
)
async def async_will_remove_from_hass(self) -> None:
async def async_will_remove_from_hass(self):
"""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) -> None:
def update(self):
"""Update device state."""
self._device.refresh()
@property
def extra_state_attributes(self) -> dict[str, str]:
def extra_state_attributes(self):
"""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,
@@ -317,16 +322,16 @@ class AbodeDevice(AbodeEntity):
}
@property
def device_info(self) -> entity.DeviceInfo:
def device_info(self):
"""Return device registry information for this entity."""
return entity.DeviceInfo(
identifiers={(DOMAIN, self._device.device_id)},
manufacturer="Abode",
model=self._device.type,
name=self._device.name,
)
return {
"identifiers": {(DOMAIN, self._device.device_id)},
"manufacturer": "Abode",
"name": self._device.name,
"device_type": self._device.type,
}
def _update_callback(self, device: AbodeDev) -> None:
def _update_callback(self, device):
"""Update the device state."""
self.schedule_update_ha_state()
@@ -334,16 +339,17 @@ class AbodeDevice(AbodeEntity):
class AbodeAutomation(AbodeEntity):
"""Representation of an Abode automation."""
def __init__(self, data: AbodeSystem, automation: AbodeAuto) -> None:
def __init__(self, data, automation):
"""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) -> None:
def update(self):
"""Update automation state."""
self._automation.refresh()

View File

@@ -1,33 +1,25 @@
"""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, AbodeSystem
from .const import DOMAIN
from . import AbodeDevice
from .const import ATTRIBUTION, DOMAIN
ICON = "mdi:security"
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode alarm control panel device."""
data: AbodeSystem = hass.data[DOMAIN]
data = hass.data[DOMAIN]
async_add_entities(
[AbodeAlarm(data, await hass.async_add_executor_job(data.abode.get_alarm))]
)
@@ -39,35 +31,37 @@ 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) -> str | None:
def state(self):
"""Return the state of the device."""
if self._device.is_standby:
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
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
def alarm_disarm(self, code: str | None = None) -> None:
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._device.set_standby()
def alarm_arm_home(self, code: str | None = None) -> None:
def alarm_arm_home(self, code=None):
"""Send arm home command."""
self._device.set_home()
def alarm_arm_away(self, code: str | None = None) -> None:
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._device.set_away()
@property
def extra_state_attributes(self) -> dict[str, str]:
def extra_state_attributes(self):
"""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,

View File

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

View File

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

View File

@@ -1,9 +1,4 @@
"""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
from abodepy.helpers.errors import MFA_CODE_REQUIRED
@@ -11,12 +6,12 @@ from requests.exceptions import ConnectTimeout, HTTPError
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 homeassistant.const import CONF_PASSWORD, CONF_USERNAME, HTTP_BAD_REQUEST
from .const import CONF_POLLING, DEFAULT_CACHEDB, DOMAIN, LOGGER
from .const import DEFAULT_CACHEDB, DOMAIN, LOGGER
CONF_MFA = "mfa_code"
CONF_POLLING = "polling"
class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
@@ -24,7 +19,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
def __init__(self) -> None:
def __init__(self):
"""Initialize."""
self.data_schema = {
vol.Required(CONF_USERNAME): str,
@@ -34,13 +29,13 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
vol.Required(CONF_MFA): str,
}
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
self._cache = None
self._mfa_code = None
self._password = None
self._polling = False
self._username = None
async def _async_abode_login(self, step_id: str) -> FlowResult:
async def _async_abode_login(self, step_id):
"""Handle login with Abode."""
self._cache = self.hass.config.path(DEFAULT_CACHEDB)
errors = {}
@@ -50,21 +45,18 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
Abode, self._username, self._password, True, False, False, self._cache
)
except AbodeException as ex:
except (AbodeException, ConnectTimeout, HTTPError) as ex:
if ex.errcode == MFA_CODE_REQUIRED[0]:
return await self.async_step_mfa()
LOGGER.error("Unable to connect to Abode: %s", ex)
if ex.errcode == HTTPStatus.BAD_REQUEST:
if ex.errcode == HTTP_BAD_REQUEST:
errors = {"base": "invalid_auth"}
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
@@ -72,7 +64,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return await self._async_create_entry()
async def _async_abode_mfa_login(self) -> FlowResult:
async def _async_abode_mfa_login(self):
"""Handle multi-factor authentication (MFA) login with Abode."""
try:
# Create instance to access login method for passing MFA code
@@ -95,7 +87,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return await self._async_create_entry()
async def _async_create_entry(self) -> FlowResult:
async def _async_create_entry(self):
"""Create the config entry."""
config_data = {
CONF_USERNAME: self._username,
@@ -115,13 +107,9 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_abort(reason="reauth_successful")
return self.async_create_entry(
title=cast(str, self._username), data=config_data
)
return self.async_create_entry(title=self._username, data=config_data)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
@@ -136,9 +124,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return await self._async_abode_login(step_id="user")
async def async_step_mfa(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
async def async_step_mfa(self, user_input=None):
"""Handle a multi-factor authentication (MFA) flow."""
if user_input is None:
return self.async_show_form(
@@ -149,15 +135,13 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return await self._async_abode_mfa_login()
async def async_step_reauth(self, config: dict[str, Any]) -> FlowResult:
async def async_step_reauth(self, config):
"""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: dict[str, Any] | None = None
) -> FlowResult:
async def async_step_reauth_confirm(self, user_input=None):
"""Handle reauthorization flow."""
if user_input is None:
return self.async_show_form(

View File

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

View File

@@ -1,23 +1,15 @@
"""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, AbodeSystem
from . import AbodeDevice
from .const import DOMAIN
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode cover devices."""
data: AbodeSystem = hass.data[DOMAIN]
data = hass.data[DOMAIN]
entities = []
@@ -30,17 +22,15 @@ async def async_setup_entry(
class AbodeCover(AbodeDevice, CoverEntity):
"""Representation of an Abode cover."""
_device: AbodeCV
@property
def is_closed(self) -> bool:
def is_closed(self):
"""Return true if cover is closed, else False."""
return not self._device.is_open
def close_cover(self, **kwargs: Any) -> None:
def close_cover(self, **kwargs):
"""Issue close command to cover."""
self._device.close_cover()
def open_cover(self, **kwargs: Any) -> None:
def open_cover(self, **kwargs):
"""Issue open command to cover."""
self._device.open_cover()

View File

@@ -1,10 +1,6 @@
"""Support for Abode Security System lights."""
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 (
@@ -16,23 +12,18 @@ 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, AbodeSystem
from . import AbodeDevice
from .const import DOMAIN
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode light devices."""
data: AbodeSystem = hass.data[DOMAIN]
data = hass.data[DOMAIN]
entities = []
@@ -45,9 +36,7 @@ async def async_setup_entry(
class AbodeLight(AbodeDevice, LightEntity):
"""Representation of an Abode light."""
_device: AbodeLT
def turn_on(self, **kwargs: Any) -> None:
def turn_on(self, **kwargs):
"""Turn on the light."""
if ATTR_COLOR_TEMP in kwargs and self._device.is_color_capable:
self._device.set_color_temp(
@@ -67,42 +56,40 @@ class AbodeLight(AbodeDevice, LightEntity):
self._device.switch_on()
def turn_off(self, **kwargs: Any) -> None:
def turn_off(self, **kwargs):
"""Turn off the light."""
self._device.switch_off()
@property
def is_on(self) -> bool:
def is_on(self):
"""Return true if device is on."""
return bool(self._device.is_on)
return self._device.is_on
@property
def brightness(self) -> int | None:
def brightness(self):
"""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 255 if brightness == 100 else ceil(brightness * 255 / 99.0)
return None
return ceil(brightness * 255 / 99.0)
@property
def color_temp(self) -> int | None:
def color_temp(self):
"""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) -> tuple[float, float] | None:
def hs_color(self):
"""Return the color of the light."""
_hs = None
if self._device.has_color:
_hs = self._device.color
return _hs
return self._device.color
@property
def supported_features(self) -> int:
def supported_features(self):
"""Flag supported features."""
if self._device.is_dimmable and self._device.is_color_capable:
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR | SUPPORT_COLOR_TEMP

View File

@@ -1,23 +1,15 @@
"""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, AbodeSystem
from . import AbodeDevice
from .const import DOMAIN
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Abode lock devices."""
data: AbodeSystem = hass.data[DOMAIN]
data = hass.data[DOMAIN]
entities = []
@@ -30,17 +22,15 @@ async def async_setup_entry(
class AbodeLock(AbodeDevice, LockEntity):
"""Representation of an Abode lock."""
_device: AbodeLK
def lock(self, **kwargs: Any) -> None:
def lock(self, **kwargs):
"""Lock the device."""
self._device.lock()
def unlock(self, **kwargs: Any) -> None:
def unlock(self, **kwargs):
"""Unlock the device."""
self._device.unlock()
@property
def is_locked(self) -> bool:
def is_locked(self):
"""Return true if device is on."""
return bool(self._device.is_locked)
return self._device.is_locked

View File

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

View File

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

View File

@@ -1,19 +1,9 @@
{
"config": {
"abort": {
"reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e",
"single_instance_allowed": "\u0420\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0430 Abode."
},
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
},
"step": {
"reauth_confirm": {
"data": {
"password": "\u041f\u0430\u0440\u043e\u043b\u0430",
"username": "Email"
}
},
"user": {
"data": {
"password": "\u041f\u0430\u0440\u043e\u043b\u0430",

View File

@@ -3,11 +3,6 @@
"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"
}
}
}
}

View File

@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt.",
"reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt",
"single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges."
},
"error": {

View File

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

View File

@@ -1,35 +0,0 @@
{
"config": {
"abort": {
"reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f",
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"invalid_auth": "\u7121\u52b9\u306a\u8a8d\u8a3c",
"invalid_mfa_code": "\u7121\u52b9\u306aMFA\u30b3\u30fc\u30c9"
},
"step": {
"mfa": {
"data": {
"mfa_code": "MFA\u30b3\u30fc\u30c9(6\u6841)"
},
"title": "Abode\u306eMFA\u30b3\u30fc\u30c9\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044"
},
"reauth_confirm": {
"data": {
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9",
"username": "E\u30e1\u30fc\u30eb"
},
"title": "Abode\u306e\u30ed\u30b0\u30a4\u30f3\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044"
},
"user": {
"data": {
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9",
"username": "E\u30e1\u30fc\u30eb"
},
"title": "Abode\u306e\u30ed\u30b0\u30a4\u30f3\u60c5\u5831\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044"
}
}
}
}

View File

@@ -27,8 +27,7 @@
"data": {
"password": "Parola",
"username": "E-posta"
},
"title": "Abode giri\u015f bilgilerinizi doldurun"
}
}
}
}

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from datetime import timedelta
import logging
from typing import Any
from typing import Any, Dict
from accuweather import AccuWeather, ApiError, InvalidApiKeyError, RequestsExceededError
from aiohttp import ClientSession
@@ -11,7 +11,7 @@ from aiohttp.client_exceptions import ClientConnectorError
from async_timeout import timeout
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.const import CONF_API_KEY
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@@ -20,7 +20,7 @@ from .const import ATTR_FORECAST, CONF_FORECAST, DOMAIN
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
PLATFORMS = ["sensor", "weather"]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@@ -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__(

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Final
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT
from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_CLOUDY,
@@ -22,6 +22,7 @@ from homeassistant.components.weather import (
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_CUBIC_METER,
DEVICE_CLASS_TEMPERATURE,
LENGTH_FEET,
LENGTH_INCHES,
LENGTH_METERS,
@@ -122,21 +123,21 @@ FORECAST_SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureMax",
device_class=SensorDeviceClass.TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name="RealFeel Temperature Max",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureMin",
device_class=SensorDeviceClass.TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name="RealFeel Temperature Min",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureShadeMax",
device_class=SensorDeviceClass.TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name="RealFeel Temperature Shade Max",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
@@ -144,7 +145,7 @@ FORECAST_SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureShadeMin",
device_class=SensorDeviceClass.TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name="RealFeel Temperature Shade Min",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
@@ -214,12 +215,12 @@ FORECAST_SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
AccuWeatherSensorDescription(
key="ApparentTemperature",
device_class=SensorDeviceClass.TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name="Apparent Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
state_class=STATE_CLASS_MEASUREMENT,
),
AccuWeatherSensorDescription(
key="Ceiling",
@@ -227,7 +228,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
name="Cloud Ceiling",
unit_metric=LENGTH_METERS,
unit_imperial=LENGTH_FEET,
state_class=SensorStateClass.MEASUREMENT,
state_class=STATE_CLASS_MEASUREMENT,
),
AccuWeatherSensorDescription(
key="CloudCover",
@@ -236,33 +237,33 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=PERCENTAGE,
unit_imperial=PERCENTAGE,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
state_class=STATE_CLASS_MEASUREMENT,
),
AccuWeatherSensorDescription(
key="DewPoint",
device_class=SensorDeviceClass.TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name="Dew Point",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
state_class=STATE_CLASS_MEASUREMENT,
),
AccuWeatherSensorDescription(
key="RealFeelTemperature",
device_class=SensorDeviceClass.TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name="RealFeel Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
state_class=SensorStateClass.MEASUREMENT,
state_class=STATE_CLASS_MEASUREMENT,
),
AccuWeatherSensorDescription(
key="RealFeelTemperatureShade",
device_class=SensorDeviceClass.TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name="RealFeel Temperature Shade",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
state_class=STATE_CLASS_MEASUREMENT,
),
AccuWeatherSensorDescription(
key="Precipitation",
@@ -270,7 +271,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
name="Precipitation",
unit_metric=LENGTH_MILLIMETERS,
unit_imperial=LENGTH_INCHES,
state_class=SensorStateClass.MEASUREMENT,
state_class=STATE_CLASS_MEASUREMENT,
),
AccuWeatherSensorDescription(
key="PressureTendency",
@@ -286,25 +287,25 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
name="UV Index",
unit_metric=UV_INDEX,
unit_imperial=UV_INDEX,
state_class=SensorStateClass.MEASUREMENT,
state_class=STATE_CLASS_MEASUREMENT,
),
AccuWeatherSensorDescription(
key="WetBulbTemperature",
device_class=SensorDeviceClass.TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name="Wet Bulb Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
state_class=STATE_CLASS_MEASUREMENT,
),
AccuWeatherSensorDescription(
key="WindChillTemperature",
device_class=SensorDeviceClass.TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name="Wind Chill Temperature",
unit_metric=TEMP_CELSIUS,
unit_imperial=TEMP_FAHRENHEIT,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
state_class=STATE_CLASS_MEASUREMENT,
),
AccuWeatherSensorDescription(
key="Wind",
@@ -312,7 +313,7 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
name="Wind",
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
state_class=SensorStateClass.MEASUREMENT,
state_class=STATE_CLASS_MEASUREMENT,
),
AccuWeatherSensorDescription(
key="WindGust",
@@ -321,6 +322,6 @@ SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
unit_metric=SPEED_KILOMETERS_PER_HOUR,
unit_imperial=SPEED_MILES_PER_HOUR,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
state_class=STATE_CLASS_MEASUREMENT,
),
)

View File

@@ -2,7 +2,7 @@
"domain": "accuweather",
"name": "AccuWeather",
"documentation": "https://www.home-assistant.io/integrations/accuweather/",
"requirements": ["accuweather==0.3.0"],
"requirements": ["accuweather==0.2.0"],
"codeowners": ["@bieniu"],
"config_flow": true,
"quality_scale": "platinum",

View File

@@ -3,12 +3,10 @@ from __future__ import annotations
from typing import Any, cast
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, DEVICE_CLASS_TEMPERATURE
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -61,7 +59,6 @@ async def async_setup_entry(
class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
"""Define an AccuWeather entity."""
_attr_attribution = ATTRIBUTION
coordinator: AccuWeatherDataUpdateCoordinator
entity_description: AccuWeatherSensorDescription
@@ -78,7 +75,7 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
self._sensor_data = _get_sensor_data(
coordinator.data, forecast_day, description.key
)
self._attrs: dict[str, Any] = {}
self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION}
if forecast_day is not None:
self._attr_name = f"{name} {description.name} {forecast_day}d"
self._attr_unique_id = (
@@ -95,19 +92,19 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
else:
self._unit_system = API_IMPERIAL
self._attr_native_unit_of_measurement = description.unit_imperial
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, coordinator.location_key)},
manufacturer=MANUFACTURER,
name=NAME,
)
self._attr_device_info = {
"identifiers": {(DOMAIN, coordinator.location_key)},
"name": NAME,
"manufacturer": MANUFACTURER,
"entry_type": "service",
}
self.forecast_day = forecast_day
@property
def native_value(self) -> StateType:
"""Return the state."""
if self.forecast_day is not None:
if self.entity_description.device_class == SensorDeviceClass.TEMPERATURE:
if self.entity_description.device_class == DEVICE_CLASS_TEMPERATURE:
return cast(float, self._sensor_data["Value"])
if self.entity_description.key == "UVIndex":
return cast(int, self._sensor_data["Value"])
@@ -117,7 +114,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 == SensorDeviceClass.TEMPERATURE:
if self.entity_description.device_class == DEVICE_CLASS_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"])

View File

@@ -1,22 +0,0 @@
{
"config": {
"abort": {
"single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\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",
"invalid_api_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447"
},
"step": {
"user": {
"data": {
"api_key": "API \u043a\u043b\u044e\u0447",
"latitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0448\u0438\u0440\u0438\u043d\u0430",
"longitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430",
"name": "\u0418\u043c\u0435"
},
"title": "AccuWeather"
}
}
}
}

View File

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

View File

@@ -6,7 +6,7 @@
"error": {
"cannot_connect": "Sikertelen csatlakoz\u00e1s",
"invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs",
"requests_exceeded": "Accuweather API-hoz enged\u00e9lyezett lek\u00e9r\u00e9sek sz\u00e1ma t\u00fal lett l\u00e9pve. Meg kell v\u00e1rnia m\u00edg a tilt\u00e1s lej\u00e1r vagy m\u00f3dos\u00edtania kell az API-kulcsot."
"requests_exceeded": "T\u00fall\u00e9pt\u00e9k az Accuweather API-hoz beny\u00fajtott k\u00e9relmek megengedett sz\u00e1m\u00e1t. Meg kell v\u00e1rnia vagy m\u00f3dos\u00edtania kell az API-kulcsot."
},
"step": {
"user": {

View File

@@ -1,41 +0,0 @@
{
"config": {
"abort": {
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002"
},
"error": {
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
"invalid_api_key": "\u7121\u52b9\u306aAPI\u30ad\u30fc",
"requests_exceeded": "Accuweather API\u3078\u306e\u30ea\u30af\u30a8\u30b9\u30c8\u6570\u304c\u8a31\u53ef\u3055\u308c\u305f\u6570\u3092\u8d85\u3048\u307e\u3057\u305f\u3002\u6642\u9593\u3092\u7f6e\u304f\u304b\u3001API\u30ad\u30fc\u3092\u5909\u66f4\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002"
},
"step": {
"user": {
"data": {
"api_key": "API\u30ad\u30fc",
"latitude": "\u7def\u5ea6",
"longitude": "\u7d4c\u5ea6",
"name": "\u540d\u524d"
},
"description": "\u8a2d\u5b9a\u306b\u3064\u3044\u3066\u30d8\u30eb\u30d7\u304c\u5fc5\u8981\u306a\u5834\u5408\u306f\u3001https://www.home-assistant.io/integrations/accuweather/ \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044\n\n\u4e00\u90e8\u306e\u30bb\u30f3\u30b5\u30fc\u306f\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u306e\u8a2d\u5b9a\u5f8c\u306b\u3001\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u30ec\u30b8\u30b9\u30c8\u30ea\u3067\u6709\u52b9\u306b\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002\n\u5929\u6c17\u4e88\u5831\u306f\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u6709\u52b9\u306b\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002",
"title": "AccuWeather"
}
}
},
"options": {
"step": {
"user": {
"data": {
"forecast": "\u5929\u6c17\u4e88\u5831"
},
"description": "\u5236\u9650\u306b\u3088\u308a\u7121\u6599\u30d0\u30fc\u30b8\u30e7\u30f3\u306eAccuWeather API\u30ad\u30fc\u3067\u306f\u3001\u5929\u6c17\u4e88\u5831\u3092\u6709\u52b9\u306b\u3057\u3066\u3082\u30c7\u30fc\u30bf\u306e\u66f4\u65b0\u306f40\u5206\u3067\u306f\u306a\u304f80\u5206\u3054\u3068\u3067\u3059\u3002",
"title": "AccuWeather\u306e\u30aa\u30d7\u30b7\u30e7\u30f3"
}
}
},
"system_health": {
"info": {
"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"
}
}
}

View File

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

View File

@@ -1,9 +0,0 @@
{
"state": {
"accuweather__pressure_tendency": {
"falling": "\u4e0b\u964d",
"rising": "\u4e0a\u6607",
"steady": "\u5b89\u5b9a"
}
}
}

View File

@@ -1,9 +0,0 @@
{
"state": {
"accuweather__pressure_tendency": {
"falling": "D\u00fc\u015f\u00fcyor",
"rising": "Y\u00fckseliyor",
"steady": "Sabit"
}
}
}

View File

@@ -5,8 +5,7 @@
},
"error": {
"cannot_connect": "Ba\u011flanma hatas\u0131",
"invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131",
"requests_exceeded": "Accuweather API i\u00e7in izin verilen istek say\u0131s\u0131 a\u015f\u0131ld\u0131. API Anahtar\u0131n\u0131 beklemeniz veya de\u011fi\u015ftirmeniz gerekir."
"invalid_api_key": "Ge\u00e7ersiz API anahtar\u0131"
},
"step": {
"user": {
@@ -16,7 +15,6 @@
"longitude": "Boylam",
"name": "Ad"
},
"description": "Yap\u0131land\u0131rmayla ilgili yard\u0131ma ihtiyac\u0131n\u0131z varsa buraya bak\u0131n: https://www.home-assistant.io/integrations/accuweather/ \n\n Baz\u0131 sens\u00f6rler varsay\u0131lan olarak etkin de\u011fildir. Bunlar\u0131, entegrasyon yap\u0131land\u0131rmas\u0131ndan sonra varl\u0131k kay\u0131t defterinde etkinle\u015ftirebilirsiniz.\n Hava tahmini varsay\u0131lan olarak etkin de\u011fildir. Entegrasyon se\u00e7eneklerinde etkinle\u015ftirebilirsiniz.",
"title": "AccuWeather"
}
}
@@ -27,7 +25,6 @@
"data": {
"forecast": "Hava Durumu tahmini"
},
"description": "AccuWeather API anahtar\u0131n\u0131n \u00fccretsiz s\u00fcr\u00fcm\u00fcn\u00fcn s\u0131n\u0131rlamalar\u0131 nedeniyle, hava tahminini etkinle\u015ftirdi\u011finizde, veri g\u00fcncellemeleri her 40 dakikada bir yerine 80 dakikada bir ger\u00e7ekle\u015ftirilir.",
"title": "AccuWeather Se\u00e7enekleri"
}
}

View File

@@ -5,18 +5,18 @@
},
"error": {
"cannot_connect": "\u9023\u7dda\u5931\u6557",
"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"
"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"
},
"step": {
"user": {
"data": {
"api_key": "API \u91d1\u9470",
"api_key": "API \u5bc6\u9470",
"latitude": "\u7def\u5ea6",
"longitude": "\u7d93\u5ea6",
"name": "\u540d\u7a31"
},
"description": "\u5047\u5982\u4f60\u9700\u8981\u5354\u52a9\u9032\u884c\u8a2d\u5b9a\uff0c\u8acb\u53c3\u95b1\uff1ahttps://www.home-assistant.io/integrations/accuweather/\n\n\u67d0\u4e9b\u611f\u6e2c\u5668\u9810\u8a2d\u70ba\u672a\u555f\u7528\uff0c\u53ef\u4ee5\u65bc\u6574\u5408\u8a2d\u5b9a\u4e2d\u555f\u7528\u9019\u4e9b\u5be6\u9ad4\u3002\u5929\u6c23\u9810\u5831\u9810\u8a2d\u672a\u958b\u555f\u3002\u53ef\u4ee5\u65bc\u6574\u5408\u9078\u9805\u4e2d\u958b\u555f\u3002",
"description": "\u5047\u5982\u4f60\u9700\u8981\u5354\u52a9\u9032\u884c\u8a2d\u5b9a\uff0c\u8acb\u53c3\u95b1\uff1ahttps://www.home-assistant.io/integrations/accuweather/\n\n\u67d0\u4e9b\u50b3\u611f\u5668\u9810\u8a2d\u70ba\u672a\u555f\u7528\uff0c\u53ef\u4ee5\u65bc\u6574\u5408\u8a2d\u5b9a\u4e2d\u555f\u7528\u9019\u4e9b\u5be6\u9ad4\u3002\u5929\u6c23\u9810\u5831\u9810\u8a2d\u672a\u958b\u555f\u3002\u53ef\u4ee5\u65bc\u6574\u5408\u9078\u9805\u4e2d\u958b\u555f\u3002",
"title": "AccuWeather"
}
}
@@ -27,7 +27,7 @@
"data": {
"forecast": "\u5929\u6c23\u9810\u5831"
},
"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",
"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",
"title": "AccuWeather \u9078\u9805"
}
}

View File

@@ -17,15 +17,8 @@ from homeassistant.components.weather import (
WeatherEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_NAME,
SPEED_MILES_PER_HOUR,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
from homeassistant.const import CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.dt import utc_from_timestamp
@@ -67,31 +60,18 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity):
"""Initialize."""
super().__init__(coordinator)
self._unit_system = API_METRIC if coordinator.is_metric else API_IMPERIAL
wind_speed_unit = self.coordinator.data["Wind"]["Speed"][self._unit_system][
"Unit"
]
if wind_speed_unit == "mi/h":
self._attr_wind_speed_unit = SPEED_MILES_PER_HOUR
else:
self._attr_wind_speed_unit = wind_speed_unit
self._attr_name = name
self._attr_unique_id = coordinator.location_key
self._attr_temperature_unit = (
TEMP_CELSIUS if coordinator.is_metric else TEMP_FAHRENHEIT
)
self._attr_attribution = ATTRIBUTION
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, coordinator.location_key)},
manufacturer=MANUFACTURER,
name=NAME,
# You don't need to provide specific details for the URL,
# so passing in _ characters is fine if the location key
# is correct
configuration_url="http://accuweather.com/en/"
f"_/_/{coordinator.location_key}/"
f"weather-forecast/{coordinator.location_key}/",
)
self._attr_device_info = {
"identifiers": {(DOMAIN, coordinator.location_key)},
"name": NAME,
"manufacturer": MANUFACTURER,
"entry_type": "service",
}
@property
def condition(self) -> str | None:

View File

@@ -53,7 +53,7 @@ def setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
discovery_info: DiscoveryInfoType,
) -> None:
"""Connect with serial port and return Acer Projector."""
serial_port = config[CONF_FILENAME]
@@ -111,7 +111,8 @@ class AcerSwitch(SwitchEntity):
"""Write msg, obtain answer and format output."""
# answers are formatted as ***\answer\r***
awns = self._write_read(msg)
if match := re.search(r"\r(.+)\r", awns):
match = re.search(r"\r(.+)\r", awns)
if match:
return match.group(1)
return STATE_UNKNOWN
@@ -128,7 +129,8 @@ class AcerSwitch(SwitchEntity):
self._attr_available = False
for key in self._attributes:
if msg := CMD_DICT.get(key):
msg = CMD_DICT.get(key)
if msg:
awns = self._write_read_format(msg)
self._attributes[key] = awns
self._attr_extra_state_attributes = self._attributes

View File

@@ -1,30 +1,35 @@
"""The Rollease Acmeda Automate integration."""
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant import config_entries, core
from .const import DOMAIN
from .hub import PulseHub
CONF_HUBS = "hubs"
PLATFORMS = [Platform.COVER, Platform.SENSOR]
PLATFORMS = ["cover", "sensor"]
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
):
"""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, {})[config_entry.entry_id] = hub
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][config_entry.entry_id] = hub
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
):
"""Unload a config entry."""
hub = hass.data[DOMAIN][config_entry.entry_id]

View File

@@ -77,11 +77,11 @@ class AcmedaBase(entity.Entity):
return self.roller.name
@property
def device_info(self) -> entity.DeviceInfo:
def device_info(self):
"""Return the device info."""
return entity.DeviceInfo(
identifiers={(DOMAIN, self.unique_id)},
manufacturer="Rollease Acmeda",
name=self.roller.name,
via_device=(DOMAIN, self.roller.hub.id),
)
return {
"identifiers": {(DOMAIN, self.unique_id)},
"name": self.roller.name,
"manufacturer": "Rollease Acmeda",
"via_device": (DOMAIN, self.roller.hub.id),
}

View File

@@ -9,7 +9,6 @@ import async_timeout
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_ID
from .const import DOMAIN
@@ -28,9 +27,9 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
if (
user_input is not None
and self.discovered_hubs is not None
and user_input[CONF_ID] in self.discovered_hubs
and user_input["id"] in self.discovered_hubs
):
return await self.async_create(self.discovered_hubs[user_input[CONF_ID]])
return await self.async_create(self.discovered_hubs[user_input["id"]])
# Already configured hosts
already_configured = {
@@ -56,7 +55,7 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_ID): vol.In(
vol.Required("id"): vol.In(
{hub.id: f"{hub.id} {hub.host}" for hub in hubs}
)
}
@@ -66,4 +65,4 @@ class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_create(self, hub):
"""Create the Acmeda Hub entry."""
await self.async_set_unique_id(hub.id, raise_on_progress=False)
return self.async_create_entry(title=hub.id, data={CONF_HOST: hub.host})
return self.async_create_entry(title=hub.id, data={"host": hub.host})

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
"name": "Rollease Acmeda Automate",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/acmeda",
"requirements": ["aiopulse==0.4.3"],
"requirements": ["aiopulse==0.4.2"],
"codeowners": ["@atmurray"],
"iot_class": "local_push"
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,15 +0,0 @@
{
"config": {
"abort": {
"no_devices_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u4e0a\u306b\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093"
},
"step": {
"user": {
"data": {
"id": "\u30db\u30b9\u30c8ID"
},
"title": "\u8ffd\u52a0\u3059\u308b\u30cf\u30d6\u306e\u9078\u629e"
}
}
}
}

View File

@@ -1,14 +1,10 @@
{
"config": {
"abort": {
"no_devices_found": "A\u011fda cihaz bulunamad\u0131"
},
"step": {
"user": {
"data": {
"id": "Ana bilgisayar kimli\u011fi"
},
"title": "Eklemek i\u00e7in bir merkez se\u00e7in"
}
}
}
}

View File

@@ -71,7 +71,8 @@ class ActiontecDeviceScanner(DeviceScanner):
if not self.success_init:
return False
if (actiontec_data := self.get_actiontec_data()) is None:
actiontec_data = self.get_actiontec_data()
if actiontec_data is None:
return False
self.last_results = [
device for device in actiontec_data if device.timevalid > -60

View File

@@ -2,10 +2,9 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
PLATFORMS = [Platform.CLIMATE]
PLATFORMS = ["climate"]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@@ -17,18 +16,3 @@ 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

View File

@@ -1,10 +1,10 @@
"""Support for Adax wifi-enabled home heaters."""
from __future__ import annotations
import logging
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 (
@@ -15,19 +15,17 @@ 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,
)
from homeassistant.core import HomeAssistant
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, CONNECTION_TYPE, DOMAIN, LOCAL
from .const import ACCOUNT_ID
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
@@ -36,17 +34,6 @@ 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],
@@ -54,11 +41,8 @@ async def async_setup_entry(
)
async_add_entities(
(
AdaxDevice(room, adax_data_handler)
for room in await adax_data_handler.get_rooms()
),
True,
AdaxDevice(room, adax_data_handler)
for room in await adax_data_handler.get_rooms()
)
@@ -74,86 +58,69 @@ class AdaxDevice(ClimateEntity):
def __init__(self, heater_data: dict[str, Any], adax_data_handler: Adax) -> None:
"""Initialize the heater."""
self._device_id = heater_data["id"]
self._heater_data = heater_data
self._adax_data_handler = adax_data_handler
self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, heater_data["id"])},
name=self.name,
manufacturer="Adax",
)
@property
def name(self) -> str:
"""Return the name of the device, if any."""
return self._heater_data["name"]
@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode."""
if self._heater_data["heatingEnabled"]:
return HVAC_MODE_HEAT
return HVAC_MODE_OFF
@property
def icon(self) -> str:
"""Return nice icon for heater."""
if self.hvac_mode == HVAC_MODE_HEAT:
return "mdi:radiator"
return "mdi:radiator-off"
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set hvac mode."""
if hvac_mode == HVAC_MODE_HEAT:
temperature = max(self.min_temp, self.target_temperature or self.min_temp)
temperature = max(
self.min_temp, self._heater_data.get("targetTemperature", self.min_temp)
)
await self._adax_data_handler.set_room_target_temperature(
self._device_id, temperature, True
self._heater_data["id"], temperature, True
)
elif hvac_mode == HVAC_MODE_OFF:
await self._adax_data_handler.set_room_target_temperature(
self._device_id, self.min_temp, False
self._heater_data["id"], self.min_temp, False
)
else:
return
await self._adax_data_handler.update()
@property
def current_temperature(self) -> float | None:
"""Return the current temperature."""
return self._heater_data.get("temperature")
@property
def target_temperature(self) -> int | None:
"""Return the temperature we try to reach."""
return self._heater_data.get("targetTemperature")
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
await self._adax_data_handler.set_room_target_temperature(
self._device_id, temperature, True
self._heater_data["id"], temperature, True
)
async def async_update(self) -> None:
"""Get the latest data."""
for room in await self._adax_data_handler.get_rooms():
if room["id"] != self._device_id:
continue
self._attr_name = room["name"]
self._attr_current_temperature = room.get("temperature")
self._attr_target_temperature = room.get("targetTemperature")
if room["heatingEnabled"]:
self._attr_hvac_mode = HVAC_MODE_HEAT
self._attr_icon = "mdi:radiator"
else:
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
if room["id"] == self._heater_data["id"]:
self._heater_data = room
return

View File

@@ -5,140 +5,69 @@ 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_IP_ADDRESS,
CONF_PASSWORD,
CONF_TOKEN,
CONF_UNIQUE_ID,
)
from homeassistant.const import CONF_PASSWORD
from homeassistant.core import HomeAssistant
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,
CLOUD,
CONNECTION_TYPE,
DOMAIN,
LOCAL,
WIFI_PSWD,
WIFI_SSID,
)
from .const import ACCOUNT_ID, DOMAIN
_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 = 2
VERSION = 1
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=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(
async def async_step_user(
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}
)
"""Handle the initial step."""
if user_input is None:
return self.async_show_form(step_id="cloud", data_schema=data_schema)
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA
)
errors = {}
await self.async_set_unique_id(str(user_input[ACCOUNT_ID]))
await self.async_set_unique_id(user_input[ACCOUNT_ID])
self._abort_if_unique_id_configured()
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")
try:
await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="cloud",
data_schema=data_schema,
errors=errors,
else:
return self.async_create_entry(
title=user_input[ACCOUNT_ID], data=user_input
)
return self.async_create_entry(
title=str(user_input[ACCOUNT_ID]),
data={
ACCOUNT_ID: account_id,
CONF_PASSWORD: password,
CONNECTION_TYPE: CLOUD,
},
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""

View File

@@ -2,9 +2,4 @@
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"

View File

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

View File

@@ -2,19 +2,6 @@
"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%]"
@@ -25,10 +12,7 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"abort": {
"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%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
}
}

View File

@@ -1,31 +0,0 @@
{
"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",
"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",
"password": "\u041f\u0430\u0440\u043e\u043b\u0430"
}
}
}
}
}

View File

@@ -1,37 +1,19 @@
{
"config": {
"abort": {
"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"
"already_configured": "El dispositiu ja est\u00e0 configurat"
},
"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"
}
}
}
}

View File

@@ -1,37 +1,19 @@
{
"config": {
"abort": {
"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"
"already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno"
},
"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"
}
}
}
}

View File

@@ -1,37 +1,19 @@
{
"config": {
"abort": {
"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"
"already_configured": "Ger\u00e4t ist bereits konfiguriert"
},
"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"
}
}
}
}

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